西充县住房和城乡建设局网站,深圳建设局网站打不开,网店推广方案范文,我的主页设计概述
为了防止分布式系统中的多个进程之间相互干扰#xff0c;我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。
为什么要使用分布式锁 成员变量 A 存在 JVM1、JVM2、JVM3 三个 JVM 内存中 成员变量 A 同时都会在 JVM …概述
为了防止分布式系统中的多个进程之间相互干扰我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。
为什么要使用分布式锁 成员变量 A 存在 JVM1、JVM2、JVM3 三个 JVM 内存中 成员变量 A 同时都会在 JVM 分配一块内存三个请求发过来同时对这个变量操作显然结果是不对的 不是同时发过来三个请求分别操作三个不同 JVM 内存区域的数据变量 A 之间不存在共享也不具有可见性处理的结果也是不对的 注该成员变量 A 是一个有状态的对象 如果我们业务中确实存在这个场景的话我们就需要一种方法解决这个问题这就是分布式锁要解决的问题
分布式锁应该具有哪些特点
在分布式系统环境下一个方法在同一时间只能被一个机器的一个线程执行 高可用的获取锁与释放锁 高性能的获取锁与释放锁 具备可重入特性 具备锁失效机制防止死锁 具备非阻塞锁特性即没有获取到锁将直接返回获取锁失败。
实现分布式锁的N种方式
数据库实现排他锁Redis和 Memcached 的方式类似利用 Redis 的 setnx 命令。此命令同样是原子性操作只有在 key 不存在的情况下才能 set 成功。Zookeeper利用 Zookeeper 的顺序临时节点来实现分布式锁和等待队列。Zookeeper 设计的初衷就是为了实现分布式锁服务的。Memcached利用 Memcached 的 add 命令。此命令是原子性操作只有在 key 不存在的情况下才能 add 成功也就意味着线程得到了锁。ChubbyGoogle 公司实现的粗粒度分布式锁服务底层利用了 Paxos 一致性算法。 接下来介绍三种常用的方式
1.基于Mysql实现分布式锁
表结构
-- auto-generated definition
create table distributed_lock
(id int auto_incrementprimary key,method_name varchar(100) not null comment 获取锁的方法名,remark varchar(100) null comment 备注信息,status int not null comment 分配状态: 1-未分配2-已分配,update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP,version int not null comment 版本号,constraint uidx_method_nameunique (method_name)
)comment 分布式锁表;先获取锁的信息
select id, method_name, status,version from distributed_lock where status1 and method_namemethodName;### 占有锁 update t_resoure set status2, version2, update_timenow() where method_name‘methodName’ and status1 and version2;
如果没有更新影响到一行数据则说明这个资源已经被别人占位了。 以上也是cap理论的悲观锁在数据库上面的应用。
缺点
1、这把锁强依赖数据库的可用性数据库是一个单点一旦数据库挂掉会导致业务系统不可用。 2、这把锁没有失效时间一旦解锁操作失败就会导致锁记录一直在数据库中其他线程无法再获得到锁。 3、这把锁只能是非阻塞的因为数据的insert操作一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列要想再次获得锁就要再次触发获得锁操作。 4、这把锁是非重入的同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
解决方案 数据库是单点搞两个数据库数据之前双向同步。一旦挂掉快速切换到备库上。 没有失效时间只要做一个定时任务每隔一定时间把数据库中的超时数据清理一遍。 非阻塞的搞一个while循环直到insert成功再返回成功。 非重入的在数据库表中加个字段记录当前获得锁的机器的主机信息和线程信息那么下次再获取锁的时候先查询数据库如果当前机器的主机信息和线程信息在数据库可以查到的话直接把锁分配给他就可以了。
2.基于Redis实现分布式锁 首先要弄清楚几个redis命令的概念
setnx() setnx 的含义就是 SET if Not Exists其主要有两个参数 setnx(key, value)。该方法是原子的如果 key 不存在则设置当前 key 成功返回 1如果当前 key 已经存在则设置当前 key 失败返回 0。 expire() expire 设置过期时间要注意的是 setnx 命令不能设置 key 的超时时间只能通过 expire() 来对 key 设置。
getset() 这个命令主要有两个参数 getset(keynewValue)。该方法是原子的对 key 设置 newValue 这个值并且返回 key 原来的旧值。假设 key 原来是不存在的那么多次执行这个命令会出现下边的效果 getset(key, “value1”) 返回 null 此时 key 的值会被设置为 value1 getset(key, “value2”) 返回 value1 此时 key 的值会被设置为 value2 依次类推
使用步骤
1.setnx(lockkey, 当前时间 过期超时时间)如果返回 1则获取锁成功如果返回 0 则没有获取到锁转向 2。
2.get(lockkey) 获取值 oldExpireTime 并将这个 value 值与当前的系统时间进行比较如果小于当前系统时间则认为这个锁已经超时可以允许别的请求重新获取转向 3。
3.计算 newExpireTime 当前时间 过期超时时间然后 getset(lockkey, newExpireTime) 会返回当前 lockkey 的值currentExpireTime。
4.判断 currentExpireTime 与 oldExpireTime 是否相等如果相等说明当前 getset 设置成功获取到了锁。如果不相等说明这个锁又被别的请求获取走了那么当前请求可以直接返回失败或者继续重试。
5.在获取到锁之后当前线程可以开始自己的业务处理当处理完毕后比较自己的处理时间和对于锁设置的超时时间如果小于锁设置的超时时间则直接执行 delete 释放锁如果大于锁设置的超时时间则不需要再锁进行处理。
话不多说上代码:
public final class RedisLockUtil {
private static final int defaultExpire 60;
private RedisLockUtil() {//}
/*** 加锁* param key redis key* param expire 过期时间单位秒* return true:加锁成功false加锁失败*/public static boolean lock(String key, int expire) {
RedisService redisService SpringUtils.getBean(RedisService.class);long status redisService.setnx(key, 1);
if(status 1) {redisService.expire(key, expire);return true;}
return false;}
public static boolean lock(String key) {return lock2(key, defaultExpire);}
/*** 加锁* param key redis key* param expire 过期时间单位秒* return true:加锁成功false加锁失败*/public static boolean lock2(String key, int expire) {
RedisService redisService SpringUtils.getBean(RedisService.class);
long value System.currentTimeMillis() expire;long status redisService.setnx(key, String.valueOf(value));
if(status 1) {return true;}long oldExpireTime Long.parseLong(redisService.get(key, 0));if(oldExpireTime System.currentTimeMillis()) {//超时long newExpireTime System.currentTimeMillis() expire;long currentExpireTime Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));if(currentExpireTime oldExpireTime) {return true;}}return false;}
public static void unLock1(String key) {RedisService redisService SpringUtils.getBean(RedisService.class);redisService.del(key);}
public static void unLock2(String key) { RedisService redisService SpringUtils.getBean(RedisService.class); long oldExpireTime Long.parseLong(redisService.get(key, 0)); if(oldExpireTime System.currentTimeMillis()) { redisService.del(key); }}
}3.基于Zookeeper实现分布式锁
首先我们先来看看zookeeper的相关知识。
zk 一般由多个节点构成单数采用 zab 一致性协议。因此可以将 zk 看成一个单点结构对其修改数据其内部自动将所有节点数据进行修改而后才提供查询服务。
zk 的数据以目录树的形式每个目录称为 znode znode 中可存储数据一般不超过 1M还可以在其中增加子节点。 子节点有三种类型。序列化节点每在该节点下增加一个节点自动给该节点的名称上自增。临时节点一旦创建这个 znode 的客户端与服务器失去联系这个 znode 也将自动删除。最后就是普通节点。 Watch 机制client 可以监控每个节点的变化当产生变化会给 client 产生一个事件。
zookeeper基本锁
**原理**利用临时节点与 watch 机制。每个锁占用一个普通节点 /lock当需要获取锁时在 /lock 目录下创建一个临时节点创建成功则表示获取锁成功失败则 watch/lock 节点有删除操作后再去争锁。临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁。
**缺点**所有取锁失败的进程都监听父节点很容易发生羊群效应即当释放锁后所有等待进程一起来创建节点并发量很大。
zookeeper分布式锁实现:
**原理**上锁改为创建临时有序节点每个上锁的节点均能创建节点成功只是其序号不同。只有序号最小的可以拥有锁如果这个节点序号不是最小的则 watch 序号比本身小的前一个节点 (公平锁)。
步骤 在 /lock 节点下创建一个有序临时节点 (EPHEMERAL_SEQUENTIAL)。 判断创建的节点序号是否最小如果是最小则获取锁成功。不是则取锁失败然后 watch 序号比本身小的前一个节点。 当取锁失败设置 watch 后则等待 watch 事件到来后再次判断是否序号最小。 取锁成功则执行代码最后释放锁删除该节点。
代码如下:
public final class RedisLockUtil {
private static final int defaultExpire 60;
private RedisLockUtil() {//}
/*** 加锁* param key redis key* param expire 过期时间单位秒* return true:加锁成功false加锁失败*/public static boolean lock(String key, int expire) {
RedisService redisService SpringUtils.getBean(RedisService.class);long status redisService.setnx(key, 1);
if(status 1) {redisService.expire(key, expire);return true;}
return false;}
public static boolean lock(String key) {return lock2(key, defaultExpire);}
/*** 加锁* param key redis key* param expire 过期时间单位秒* return true:加锁成功false加锁失败*/public static boolean lock2(String key, int expire) {
RedisService redisService SpringUtils.getBean(RedisService.class);
long value System.currentTimeMillis() expire;long status redisService.setnx(key, String.valueOf(value));
if(status 1) {return true;}long oldExpireTime Long.parseLong(redisService.get(key, 0));if(oldExpireTime System.currentTimeMillis()) {//超时long newExpireTime System.currentTimeMillis() expire;long currentExpireTime Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));if(currentExpireTime oldExpireTime) {return true;}}return false;}
public static void unLock1(String key) {RedisService redisService SpringUtils.getBean(RedisService.class);redisService.del(key);}
public static void unLock2(String key) { RedisService redisService SpringUtils.getBean(RedisService.class); long oldExpireTime Long.parseLong(redisService.get(key, 0)); if(oldExpireTime System.currentTimeMillis()) { redisService.del(key); }}
}优缺点:
优点
有效的解决单点问题不可重入问题非阻塞问题以及锁无法释放的问题。实现起来较为简单。
缺点
性能上可能并没有缓存服务那么高因为每次在创建锁和释放锁的过程中都要动态创建、销毁临时节点来实现锁功能。ZK 中创建和删除节点只能通过 Leader 服务器来执行然后将数据同步到所有的 Follower 机器上。还需要对 ZK的原理有所了解。
三种方案的比较
上面几种方式哪种方式都无法做到完美。就像CAP一样在复杂性、可靠性、性能等方面无法同时满足所以根据不同的应用场景选择最适合自己的才是王道。
从理解的难易程度角度从低到高 数据库 缓存 Zookeeper
从实现的复杂性角度从低到高 Zookeeper 缓存 数据库
从性能角度从高到低 缓存 Zookeeper 数据库
从可靠性角度从高到低 Zookeeper 缓存 数据库
获取更多互联网知识请关注公众号