影音先锋资源网站建设,做网站开发赚钱吗,sem与seo的区别,seo的优化策略有哪些一 分布式锁的概念
1#xff1a;概念
分布式锁#xff08;多服务共享锁#xff09; 在分布式的部署环境下#xff0c;通过锁机制来让多客户端互斥的对共享资源进行访问控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共…一 分布式锁的概念
1概念
分布式锁多服务共享锁 在分布式的部署环境下通过锁机制来让多客户端互斥的对共享资源进行访问控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源往往需要互斥来防止彼此干扰以保证一致性。
2锁/分布式锁/事务区别
锁 单进程的系统中存在多线程同时操作一个公共变量此时需要加锁对变量进行同步操作保证多线程的操作线性执行消除并发修改。解决的是单进程中的多线程并发问题。分布式锁 只要的应用场景是在集群模式的多个相同服务可能会部署在不同机器上解决进程间安全问题防止多进程同时操作一个变量或者数据库。解决的是多进程的并发问题 事务 解决一个会话过程中上下文的修改对所有数据库表的操作要么全部成功要不全部失败。所以应用在service层。解决的是一个会话中的操作的数据一致性。分布式事务 解决一个联动操作比如一个商品的买卖分为添加商品到购物车、修改商品库存此时购物车服务和商品库存服务可能部署在两台电脑这时候需要保证对两个服务的操作都全部成功或者全部回退。解决的是组合服务的数据操作的一致性问题
3reddison的公平锁
reddison公平锁枷锁
默认的加锁逻辑是非公平的。在加锁失败时线程会进入 while 循环一直尝试获得锁这时候是多线程进行竞争。就是说谁抢到就是谁的。Redisson 提供了公平锁机制使用方式如下
RLock fairLock redisson.getFairLock(anyLock);
// 最常见的使用方法
fairLock.lock();看门狗机制是在 RedissonBaseLock#scheduleExpirationRenewal 方法中这块公平锁和非公平锁并无区别。前文已经了解到公平锁加锁失败之后会将当前放到等待队列中通过 Java 代码中的循环不断尝试获得锁。
reddison公平锁释放
公平锁的释放同样分为主动释放和超时释放。
主动释放即自己调用释放锁。超时删除则分为两种一种是持锁线程超时删除这种和非公平锁没有任何区别因为这个锁也是含有超时时间看门狗续租的。另一种则是等待队列中的超时删除是在每次获取锁之前判断第一个等待线程的时间戳是否超时从而移除锁。
二 、使用的案例场景
需求 当在打车软件中乘客下了订单。多个司机抢单此时因为单子只有一个多个司机对此共享资源进行抢此处应该使用分布式锁后台服务部署在多台服务器上 controller层代码 GetMapping(/do/{orderId})public String grab(PathVariable(orderId) int orderId, int driverId){System.out.println(order:orderId,driverId:driverId);//此处调用锁控制层代码grabService.grabOrder(orderId,driverId);return ;}
锁控制层代码使用synchronized 不成功 使用synchronized 不能保证多台服务器只有一个抢成功因为synchronized 只能锁本服务的资源多台服务的资源是锁不住的
AutowiredOrderService orderService;Overridepublic String grabOrder(int orderId, int driverId) {String lock (orderId);synchronized (lock.intern()) {try {System.out.println(司机:driverId 执行抢单逻辑);//此处调用订单业务代码boolean b orderService.grab(orderId, driverId);if(b) {System.out.println(司机:driverId 抢单成功);}else {System.out.println(司机:driverId 抢单失败);}} finally {}}return null;}
调用的订单业务代码 这一层就是写的伪代码后续并不关注他
三、Redis解决方案-红锁
介绍 红锁本质上就是使用多个Redis做锁。例如有5个Redis一次锁的获取会对每个请求都获取一遍如果获取锁成功的数量超过一半(2.5)则获取锁成功反之失败 释放锁也需要对每个Redis释放 红锁原理
在Redis的分布式环境中我们假设有5个Redis master。这些节点完全互相独立,没有主从关系线程1向这5个redis加锁当加到第三个的时候4和5加不上了但是符合红锁的n/21原则所以线程1获取到了锁当redis3挂了此时线程1获取到了锁正在顺序执行线程2来到了redis抢占锁因为3挂了12有锁只有4和5可以加锁因为我们注册的时候是5台4和5这两台不满足n/21原则抢占锁失败
当锁遇到故障转移
单实例肯定不是很可靠吧加锁成功之后结果 Redis 服务宕机了这不就凉凉~ 这时候会提出来将 Redis 主从部署。即使是主从也是存在巧合的 主从结构中存在明显的竞态
客户端 A 从 master 获取到锁在 master 将锁同步到 slave 之前master 宕掉了。 slave 节点被晋级为 master 节点客户端 B 取得了同一个资源被客户端 A 已经获取到的另外一个锁。安全失效有时候程序就是这么巧比如说正好一个节点挂掉的时候多个客户端同时取到了锁。如果你可以接受这种小概率错误那用这个基于复制的方案就完全没有问题。 那我使用集群呢如果还记得前面的内容应该是知道对集群进行加锁的时候其实是通过 CRC16 的 hash 函数来对 key 进行取模将结果路由到预先分配过 slot 的相应节点上。 发现其实还是发到单个节点上的 这时候 Redis 作者提出了 RedLock 的概念 总结一下就是对集群的每个节点进行加锁如果大多数N/21加锁成功了则认为获取锁成功。
RedLock 的问题 看着 RedLock 好像是解决问题了
客户端 A 锁住了集群的大多数一半以上 客户端 B 也要锁住大多数 这里肯定会冲突所以 客户端 B 加锁失败。 那实际解决问题了么
加锁 key 的问题
有一个很大的疑问我加锁 lock1、lock2、lock3但是 RedissonRedLock 是如何保证这三个 key 是在归属于 Redis 集群中不同的 master 呢因为按照 RedLock 的理论是需要在半数以上的 master 节点加锁成功。阅读完源码之后发现 RedissonRedLock 完全是 RedissonMultiLock 的子类只是重写了 failedLocksLimit 方法保证半数以上加锁成功即可。所以这三个 key是需要用户来保证分散在不同的节点上的。
红锁的争议
那我使用 5 个单节点的客户端然后再使用红锁听着好像是可以的并且 RedissonRedLock 可以这样使用。但是那和 Redis 集群还有啥关系啊所以依然没有解决我的问题在 redis 集群下 针对master节点集群还是需要用户自己来“手工定位锁”使锁的节点分散到不同的master 集群节点下。 手工定位锁这个…… 我考虑了下还是不用 RedLock 吧如果master节点变动则锁也存在问题master集群同步等等锁的同步和锁失效也是需要考虑的问题 Redisson 的开发者认为 Redis 的红锁也存在争议前文介绍的那个争议但是为了保证可用性RLock 对象执行的每个 Redis 命令执行都通过 Redis 3.0 中引入的 WAIT 命令进行同步。 Redisson 的开发者认为 Redis 的红锁也存在争议前文介绍的那个争议但是为了保证可用性RLock 对象执行的每个 Redis 命令执行都通过 Redis 3.0 中引入的 WAIT 命令进行同步。 源码在这一部分。 看源码同时发送了一个 WAIT 1 1000 到 Redis。
结论Redisson RedLock 是基于联锁 MultiLock 实现的但是使用过程中需要自己判断 key 落在哪个节点上对使用者不是很友好。
红锁使用说明-官网介绍
基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁每个RLock对象实例可以来自于不同的Redisson实例。
RLock lock1 redissonInstance1.getLock(lock1);
RLock lock2 redissonInstance2.getLock(lock2);
RLock lock3 redissonInstance3.getLock(lock3);RedissonRedLock lock new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();
大家都知道如果负责储存某些分布式锁的某些Redis节点宕机以后而且这些锁正好处于锁住的状态时这些锁会出现锁死的状态。为了避免这种情况的发生Redisson内部提供了一个监控锁的看门狗它的作用是在Redisson实例被关闭前不断的延长锁的有效期。默认情况下看门狗的检查锁的超时时间是30秒钟也可以通过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
RedissonRedLock lock new RedissonRedLock(lock1, lock2, lock3);
// 给lock1lock2lock3加锁如果没有手动解开的话10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);// 为加锁等待100秒时间并在加锁成功10秒钟后自动解开
boolean res lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
红锁实战
1.注册红锁的RedissonClient
Component
public class RedisConfig {Bean(name redissonRed1)Primarypublic RedissonClient redissonRed1(){Config config new Config();config.useSingleServer().setAddress(127.0.0.1:6379).setDatabase(0);return Redisson.create(config);}Bean(name redissonRed2)public RedissonClient redissonRed2(){Config config new Config();config.useSingleServer().setAddress(127.0.0.1:6380).setDatabase(0);return Redisson.create(config);}Bean(name redissonRed3)public RedissonClient redissonRed3(){Config config new Config();config.useSingleServer().setAddress(127.0.0.1:6381).setDatabase(0);return Redisson.create(config);}Bean(name redissonRed4)public RedissonClient redissonRed4(){Config config new Config();config.useSingleServer().setAddress(127.0.0.1:6382).setDatabase(0);return Redisson.create(config);}Bean(name redissonRed5)public RedissonClient redissonRed5(){Config config new Config();config.useSingleServer().setAddress(127.0.0.1:6383).setDatabase(0);return Redisson.create(config);}}
配置方式2 基于 Redis 的 Redisson 分布式联锁 RedissonMultiLock 对象可以将多个 RLock 对象关联为一个联锁每个 RLock 对象实例可以来自于不同的 Redisson 实例。 按照官方文档的说法这里 Redisson 客户端可以不是同一个。当然一般工作中也不会说不用一个客户端吧可以看出 遍历所有的锁依次加锁。加锁逻辑就和可重入锁加锁并无区别了。所以 Lua 脚本就不进行分析了
2. 红锁使用
package com.online.taxi.order.service.impl;import com.online.taxi.order.constant.RedisKeyConstant;
import com.online.taxi.order.service.GrabService;
import com.online.taxi.order.service.OrderService;import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;Service
public class GrabRedisRedissonRedLockLockServiceImpl implements GrabService {// 红锁AutowiredQualifier(redissonRed1)private RedissonClient redissonRed1;AutowiredQualifier(redissonRed2)private RedissonClient redissonRed2;AutowiredQualifier(redissonRed3)private RedissonClient redissonRed3;AutowiredQualifier(redissonRed4)private RedissonClient redissonRed4;AutowiredQualifier(redissonRed5)private RedissonClient redissonRed5;AutowiredOrderService orderService;Overridepublic String grabOrder(int orderId , int driverId){System.out.println(红锁实现类);//生成keyString lockKey ( orderId).intern();//redisson锁 单节点
// RLock rLock redissonRed1.getLock(lockKey);//红锁 redis sonRLock rLock1 redissonRed1.getLock(lockKey);RLock rLock2 redissonRed2.getLock(lockKey);RLock rLock3 redissonRed3.getLock(lockKey);RLock rLock4 redissonRed4.getLock(lockKey);RLock rLock5 redissonRed5.getLock(lockKey);RedissonRedLock rLock new RedissonRedLock(rLock1,rLock2,rLock3,rLock4,rLock5);try {/**红锁* waitTimeout 尝试获取锁的最大等待时间超过这个值则认为获取锁失败* leaseTime 锁的持有时间,超过这个时间锁会自动失效值应设置为大于业务处理的时间确保在锁有效期内业务能处理完*/boolean b1 rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);if (b1){System.out.println(加锁成功);// 此代码默认 设置key 超时时间30秒过10秒再延时System.out.println(司机:driverId 执行抢单逻辑);boolean b orderService.grab(orderId, driverId);if(b) {System.out.println(司机:driverId 抢单成功);}else {System.out.println(司机:driverId 抢单失败);}System.out.println(加锁成功);}else {System.out.println(加锁失败);}} finally {rLock.unlock();}return null;}}
四、单机版redission 使用 dependencygroupIdorg.redisson/groupIdartifactIdredisson-spring-boot-starter/artifactIdversion3.18.0/version/dependency1.配置 Value(${spring.redis.host})private String url;Value(${spring.redis.port})private Integer port;Value(${spring.redis.password})private String password;Beanpublic RedissonClient redissonClient(){// 配置Config config new Config();log.info(Redisson 初始化 {},String.format(redis://%s:%d,url,port));config.useSingleServer().setAddress(String.format(redis://%s:%d,url,port) ).setPassword(password);// 创建RedissonClient对象return Redisson.create(config);}2. 注入分布式锁
此处枷锁后在释放锁是解决了锁的读写机制问题
Autowired
private RedissonClient redissonClient;public List? extends Object getCachedConfigList(String ode) {ListStructureVo configuration null;String key getCacheKey(orgCode, STR_CONFIG_LIST);ListObject cacheConfig redisCache.getCacheList(key);if (!CollectionUtils.isEmpty(cacheConfig)) {return cacheConfig;}RLock lock redissonClient.getLock(getReddisonEbomCacheKey());try {lock.lock(DEFAULT_EXPIRE_SECOND,TimeUnit.SECONDS);ListObject secondCacheConfig redisCache.getCacheList(key);if (!CollectionUtils.isEmpty(secondCacheConfig)) {log.info(second lock reddison 查询到缓存释放锁);reddisnUnlock(getReddisonEbomCacheKey());return cacheConfig;}log.info(reddison枷锁成功存放缓存资源);configuration this.queryAllConfiguration();redisCache.setCacheList(key, configuration);redisCache.expire(key, DEFAULT_EXPIRE_HOURS, TimeUnit.HOURS);} catch (Exception e) {log.error(Happen Exception: e.getMessage(),e);}finally {reddisnUnlock(getReddisonEbomCacheKey());}return configuration;}释放锁方法 private void reddisnUnlock(String key) {log.info( reddison key {}锁释放,key);try {RLock unlock redissonClient.getLock(key);if (unlock ! null unlock.isHeldByCurrentThread()) {unlock.unlock();}} catch (IllegalMonitorStateException e) {log.info(reddison锁释放 Exception: e.getMessage(),e);}}Redis 单实例版本锁 NX
单实例加锁
SET resource_name my_random_value NX PX 30000对于单实例 Redis 只需要使用这个命令即可。
NX仅在不存在 key 的时候才能被执行成功PX失效时间传入 30000就是 30s 后自动释放锁my_random_value就是随机值可以是线程号之类的。主要是为了更安全的释放锁释放锁的时候使用脚本告诉 Redis: 只有 key 存在并且存储的值和我指定的值一样才能删除成功。
简单描述即为
指定一个 key 作为锁标记存入 Redis 中指定一个 唯一的用户标识作为 value。当 key 不存在时才能设置值确保同一时间只有一个客户端进程获得锁满足互斥性特性。设置一个过期时间防止因系统异常导致没能删除这个 key满足防死锁特性。当处理完业务之后需要清除这个 key 来释放锁清除 key 时需要校验 value 值需要满足只有加锁的人才能释放锁
可以通过以下 Lua 脚本实现锁释放
if redis.call(get,KEYS[1]) ARGV[1] thenreturn redis.call(del,KEYS[1])
elsereturn 0
end
为什么要设置随机值
主要是为了防止锁被其他客户端删除。有这么一种情况
客户端 A 获得了锁还没有执行结束但是锁超时自动释放了客户端 B 此时过来是可以获得锁的加锁成功此时客户端 A 执行结束了要去释放锁如果不对比随机值就会把客户端 B 的锁给释放了。 当然前面看过 Redisson 的处理这个 my_random_value 存放的是 UUID:ThreadId 组合成的一个类似 931573de-903e-42fd-baa7-428ebb7eda80:1 的字符串。
。
为什么要用lua脚本操作redis数据库?
1.减少开销–减少向redis服务器的请求次数 2.原子操作–redis将lua脚本作为一个原子执行 3.可复用–其他客户端可以使用已经执行过的lua脚本 4.增加redis灵活性–lua脚本可以帮助redis做更多的事情