分布式锁,在分布式架构中的锁的实现。接触到的使用情形主要分为两种情况:
一:该资源只允许一次访问,请注意,此种情况更像是一种独占,即并发情况下,只有一个线程能获取该资源,其他线程则放弃该资源。
二:该资源每次只允许一个线程访问,其他线程会不断尝试获取这个锁,直到所有线程都访问了这个资源。
1。redis实现分布式锁
在redis中设置一个锁名,和value值,再为这个锁名设置超时时间。
每次线程来访问先判断,这个锁名是否在redis中已经存在。如果存在则直接返回false表示该线程并未获取到锁。
设置超时时间,是为了防止死锁(某一线程获取锁后未释放该锁)。
代码:
/** * 加锁 * * @param lockName 锁名 * @param timeout 超时时间 秒 * @return */ @Override public boolean lock(String lockName, long timeout) { String value = UUIDUtil.getUUID(); String key = "key"+lockName; System.out.println(Thread.currentThread().getName()+"正在初步获取锁!"); if(redisUtil.setIfAbsent(key,value)) { redisUtil.expire(key,timeout); return true; } return false; }
/** * 释放锁 * * @param lockName */ @Override public void unLock(String lockName) { redisUtil.del(lockName); }
redisUtils中的方法:
/** * 当且仅当key不存在时,set一个key为val的字符串,返回true;若key存在,则什么都不做返回false * * @param key * @param value * @return */ public boolean setIfAbsent(String key,String value){ return redisTemplate.opsForValue().setIfAbsent(key,value); }
/** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) * @return */ public boolean expire(String key,long time){ try { if(time>0){ redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 删除缓存 * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String ... key){ if(key!=null&&key.length>0){ if(key.length==1){ redisTemplate.delete(key[0]); }else{ redisTemplate.delete(CollectionUtils.arrayToList(key)); } } }
2.ip锁,通过获取服务器的ip和配置的可执行的ip是否一致,一致即获取了锁。及指定每次获取资源的服务器(这种其实就是把分布式变成了单机版)
public class IpLock implements ConcurrentLock { private String hostAddress = IPUtil.getHostAddress(); @Override public boolean lock(String lockName, long timeout) { String server_ip = PropertiesUtils.getApplicationProperties().getProperty("server_Ip"); if(StringUtils.isNotBlank(hostAddress)&&StringUtils.isNotBlank(server_ip)){ if(server_ip.equals(hostAddress)||server_ip.equals("localhost")){ return true; } } return false; } @Override public void unLock(String lockName) { } }
3.数据库锁,依赖数据库的行级锁,每次只能有一个线程获取到update的锁。如果更新成功表示获取到了锁。未更新成功即没有拿到锁。
public class dbLock implements ConcurrentLock { private static Logger logger = LoggerFactory.getLogger(dbLock.class); private DataSource dataSource; private ThreadLocal<Connection> connectionCatch = new ThreadLocal<>(); @Override public boolean lock(String lockName, long timeout) { Connection connection = null; PreparedStatement ps = null; String sql = "update tb_dataBase_lock t set t.lock_version = t.lock_version + 1, t.update_date = sysdate where t.lock_name = ? "; try { connection = dataSource.getConnection(); if(connection!=null&& !connection.isClosed()){ connectionCatch.set(connection); connection.setAutoCommit(false); PreparedStatement statement = connection.prepareStatement(sql); statement.setString(1,lockName); int i = statement.executeUpdate(); connection.close(); return i==1; } return false; } catch (SQLException e) { connectionCatch.remove(); logger.error(e.getMessage(),e); try { if(connection!=null&&!connection.isClosed()){ connection.close(); } if(ps !=null && ps.isClosed()){ ps.close(); } } catch (SQLException e1) { logger.error(e.getMessage(),e); } } return false; } @Override public void unLock(String lockName) { Connection connection = connectionCatch.get(); try { if(connection == null|| connection.isClosed())return; connection.commit(); connection.close(); }catch (Exception e){ logger.error(e.getMessage()); } connectionCatch.remove(); } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; }
4.zookeeperLock,利用zookeeper来实现分布式锁。
未完待续........
实际应用:通过AOP来统一写锁的逻辑。
自定义注解作为切入点(pointCut)。
定义锁的执行逻辑类作为通知(advice),具体可以实现methodInterceptor.
public class ConcurrentLockInterceptor implements MethodInterceptor { private Logger logger = LoggerFactory.getLogger(ConcurrentLockInterceptor.class); private Map<String,ConcurrentLock> locks; public void setLocks(Map<String, ConcurrentLock> locks) { this.locks = locks; } @Override public Object invoke(MethodInvocation invocation) throws Throwable { LockRequired lockRequired = invocation.getMethod().getAnnotation(LockRequired.class); if(lockRequired == null){ return invocation.proceed(); } String lockName = lockRequired.lockName(); if(StringUtils.isBlank(lockName)){ lockName = invocation.getThis().getClass().getSimpleName()+"."+invocation.getMethod().getName(); } ConcurrentLock lock = locks.get("redis"); if(lockRequired.strategy().equals(LockRequired.LockStrategy.IP)){ lock= locks.get("ip"); }else if(lockRequired.strategy().equals(LockRequired.LockStrategy.DB)){ lock = locks.get("db"); } boolean locked =false; try { locked = lock.lock(lockName, lockRequired.timeout()); if (locked) { logger.info("服务器【" + IPUtil.getHostAddress() + "】获取了锁【" + lockName + "】"); invocation.proceed(); lock.unLock(lockName); } else { logger.info("服务器【" + IPUtil.getHostAddress() + "】没获取到锁【" + lockName + "】跳过执行方法!"); } }catch (Exception e){ logger.error(e.getMessage()); if(locked){ lock.unLock(lockName); logger.debug("服务器["+IPUtil.getHostAddress()+"]成功释放锁["+lockName+"]"); } } return null; } }