杭州公司做网站,佛山信息技术网站开发,网站建设 项目文档,ppt一键优化分布式锁三种实现方式#xff1a;
基于数据库实现分布式锁#xff1b;基于缓存#xff08;Redis等#xff09;实现分布式锁#xff1b;基于Zookeeper实现分布式锁#xff1b;
一#xff0c; 基于数据库实现分布式锁
悲观锁
利用select … where … for update 排他锁…分布式锁三种实现方式
基于数据库实现分布式锁基于缓存Redis等实现分布式锁基于Zookeeper实现分布式锁
一 基于数据库实现分布式锁
悲观锁
利用select … where … for update 排他锁
注意: 其他附加功能与实现一基本一致这里需要注意的是“where namelock ”name字段必须要走索引否则会锁表。有些情况下比如表不大mysql优化器会不走这个索引导致锁表问题。
乐观锁
所谓乐观锁与前边最大区别在于基于CAS思想是不具有互斥性不会产生锁等待而消耗资源操作过程中认为不存在并发冲突只有update version失败后才能觉察到。我们的抢购、秒杀就是用了这种实现以防止超卖。 通过增加递增的版本号字段实现乐观锁
二 基于缓存Redis等实现分布式锁
使用命令介绍 1SETNX SETNX key val当且仅当key不存在时set一个key为val的字符串返回1若key存在则什么都不做返回0。 2expire expire key timeout为key设置一个超时时间单位为second超过这个时间锁会自动释放避免死锁。 3delete delete key删除key
在使用Redis实现分布式锁的时候主要就会使用到这三个命令。 实现思想 1获取锁的时候使用setnx加锁并使用expire命令为锁添加一个超时时间超过该时间则自动释放锁锁的value值为一个随机生成的UUID通过此在释放锁的时候进行判断。 2获取锁的时候还设置一个获取的超时时间若超过这个时间则放弃获取锁。 3释放锁的时候通过UUID判断是不是该锁若是该锁则执行delete进行锁释放。 分布式锁的简单实现代码
/*** 分布式锁的简单实现代码 */public class DistributedLock {private final JedisPool jedisPool;public DistributedLock(JedisPool jedisPool) {this.jedisPool jedisPool;}/*** 加锁* param lockName 锁的key* param acquireTimeout 获取超时时间* param timeout 锁的超时时间* return 锁标识*/public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {Jedis conn null;String retIdentifier null;try {// 获取连接conn jedisPool.getResource();// 随机生成一个valueString identifier UUID.randomUUID().toString();// 锁名即key值String lockKey lock: lockName;// 超时时间上锁后超过此时间则自动释放锁int lockExpire (int) (timeout / );// 获取锁的超时时间超过这个时间则放弃获取锁long end System.currentTimeMillis() acquireTimeout;while (System.currentTimeMillis() end) {if (conn.setnx(lockKey, identifier) ) {conn.expire(lockKey, lockExpire);// 返回value值用于释放锁时间确认retIdentifier identifier;return retIdentifier;}// 返回-代表key没有设置超时时间为key设置一个超时时间if (conn.ttl(lockKey) -) {conn.expire(lockKey, lockExpire);}try {Thread.sleep();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}} catch (JedisException e) {e.printStackTrace();} finally {if (conn ! null) {conn.close();}}return retIdentifier;}/*** 释放锁* param lockName 锁的key* param identifier 释放锁的标识* return*/public boolean releaseLock(String lockName, String identifier) {Jedis conn null;String lockKey lock: lockName;boolean retFlag false;try {conn jedisPool.getResource();while (true) {// 监视lock准备开始事务conn.watch(lockKey);// 通过前面返回的value值判断是不是该锁若是该锁则删除释放锁if (identifier.equals(conn.get(lockKey))) {Transaction transaction conn.multi();transaction.del(lockKey);ListObject results transaction.exec();if (results null) {continue;}retFlag true;}conn.unwatch();break;}} catch (JedisException e) {e.printStackTrace();} finally {if (conn ! null) {conn.close();}}return retFlag;}}测试刚才实现的分布式锁
例子中使用50个线程模拟秒杀一个商品使用–运算符来实现商品减少从结果有序性就可以看出是否为加锁状态。
模拟秒杀服务在其中配置了jedis线程池在初始化的时候传给分布式锁供其使用。
public class Service {private static JedisPool pool null;private DistributedLock lock new DistributedLock(pool);int n 500;static {JedisPoolConfig config new JedisPoolConfig();// 设置最大连接数config.setMaxTotal(200);// 设置最大空闲数config.setMaxIdle(8);// 设置最大等待时间config.setMaxWaitMillis(1000 * 100);// 在borrow一个jedis实例时是否需要验证若为true则所有jedis实例均是可用的config.setTestOnBorrow(true);pool new JedisPool(config, 127.0.0.1, 6379, 3000);}public void seckill() {// 返回锁的value值供释放锁时候进行判断String identifier lock.lockWithTimeout(resource, 5000, 1000);System.out.println(Thread.currentThread().getName() 获得了锁);System.out.println(--n);lock.releaseLock(resource, identifier);}
}模拟线程进行秒杀服务;
public class ThreadA extends Thread {private Service service;public ThreadA(Service service) {this.service service;}Overridepublic void run() {service.seckill();}
}public class Test {public static void main(String[] args) {Service service new Service();for (int i 0; i 50; i) {ThreadA threadA new ThreadA(service);threadA.start();}}
}结果如下结果为有序的 若注释掉使用锁的部分
public void seckill() {// 返回锁的value值供释放锁时候进行判断//String indentifier lock.lockWithTimeout(resource, 5000, 1000);System.out.println(Thread.currentThread().getName() 获得了锁);System.out.println(--n);//lock.releaseLock(resource, indentifier);
}从结果可以看出有一些是异步进行的 三 基于Zookeeper实现分布式锁
ZooKeeper是一个为分布式应用提供一致性服务的开源组件它内部是一个分层的文件系统目录树结构规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下
1创建一个目录mylock 2线程A想获取锁就在mylock目录下创建临时顺序节点 3获取mylock目录下所有的子节点然后获取比自己小的兄弟节点如果不存在则说明当前线程顺序号最小获得锁 4线程B获取所有节点判断自己不是最小节点设置监听比自己次小的节点 5线程A处理完删除自己的节点线程B监听到变更事件判断自己是不是最小的节点如果是则获得锁。
这里推荐一个Apache的开源库Curator它是一个ZooKeeper客户端Curator提供的InterProcessMutex是分布式锁的实现acquire方法用于获取锁release方法用于释放锁。
实现源码如下
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;/*** 分布式锁Zookeeper实现**/
Slf4j
Component
public class ZkLock implements DistributionLock {
private String zkAddress zk_adress;private static final String root package root;private CuratorFramework zkClient;private final String LOCK_PREFIX /lock_;Beanpublic DistributionLock initZkLock() {if (StringUtils.isBlank(root)) {throw new RuntimeException(zookeeper root cant be null);}zkClient CuratorFrameworkFactory.builder().connectString(zkAddress).retryPolicy(new RetryNTimes(2000, 20000)).namespace(root).build();zkClient.start();return this;}public boolean tryLock(String lockName) {lockName LOCK_PREFIXlockName;boolean locked true;try {Stat stat zkClient.checkExists().forPath(lockName);if (stat null) {log.info(tryLock:{}, lockName);stat zkClient.checkExists().forPath(lockName);if (stat null) {zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(lockName, 1.getBytes());} else {log.warn(double-check stat.version:{}, stat.getAversion());locked false;}} else {log.warn(check stat.version:{}, stat.getAversion());locked false;}} catch (Exception e) {locked false;}return locked;}public boolean tryLock(String key, long timeout) {return false;}public void release(String lockName) {lockName LOCK_PREFIXlockName;try {zkClient.delete().guaranteed().deletingChildrenIfNeeded().forPath(lockName);log.info(release:{}, lockName);} catch (Exception e) {log.error(删除, e);}}public void setZkAddress(String zkAddress) {this.zkAddress zkAddress;}
}优点具备高可用、可重入、阻塞锁特性可解决失效死锁问题。
缺点因为需要频繁的创建和删除节点性能上不如Redis方式。
四对比
数据库分布式锁实现 缺点
1.db操作性能较差并且有锁表的风险 2.非阻塞操作失败后需要轮询占用cpu资源; 3.长时间不commit或者长时间轮询可能会占用较多连接资源
Redis(缓存)分布式锁实现 缺点
1.锁删除失败 过期时间不好控制 2.非阻塞操作失败后需要轮询占用cpu资源;
ZK分布式锁实现 缺点性能不如redis实现主要原因是写操作获取锁释放锁都需要在Leader上执行然后同步到follower。
总之ZooKeeper有较好的性能和可靠性。
从理解的难易程度角度从低到高数据库 缓存 Zookeeper
从实现的复杂性角度从低到高Zookeeper 缓存 数据库
从性能角度从高到低缓存 Zookeeper 数据库
从可靠性角度从高到低Zookeeper 缓存 数据库