96SEO 2026-02-20 05:10 0
。

如果没有适当的锁机制#xff0c;就可能导致数据不一致或并发冲突的问题。
分布式锁是一种用于在分布式系统中实现互斥访问的机制它可以确保在多个节点、或进程同时访问共享资源。
如果没有适当的锁机制就可能导致数据不一致或并发冲突的问题。
需要一个多个微服务节点都能访问的存储介质需要能保存锁信息。
该工具上锁时要能保证原子操作能处理并发且能对结果进行感知。
节点具有强一致性不论几个节点客户端最终结果一致。
老生常谈的高可用高性能。
不同的对象不能拿到同一个锁同一个对象可以再次访问该锁。
需要避免死锁死锁四大条件里面只要破坏一个就可以避免死锁。
互斥条件资源是排他的一个资源一次只能被一个对象获取。
请求与保持条件当前对象持有这个资源的是时候会请求其他的资源并且不会放弃持有当前资源。
不可剥夺条件不可以强行从一个对象手中剥夺资源。
循环等待条件当前对象需要去等待其他对象的资源其他对象也要等待当前对象的资源。
解决方案
超时退出最简单。
锁对象独占能拿到锁能校验锁也能解除锁保证锁的独占性。
尝试获取时间
超时自动释放一个是尝试获取锁时多久超时失败。
一个是拿到锁后多久自动释放。
高并发高可用除了锁介质需要满足这些实现锁的方案上也有满足。
互斥性在任何时刻只有一个客户端能够持有锁。
这是分布式锁最基本的要求确保了数据的一致性和安全性。
锁超时释放锁应该有一个超时时间在超时后自动释放以防止客户端因异常情况无法释放锁而造成死锁。
可重入性同一个客户端在持有锁的情况下可以重复获取锁防止客户端因再次请求锁而陷入死锁。
公平性锁的获取应该尽可能公平避免某些客户端长时间等待或饿死。
容错性在分布式系统中网络或节点故障是常见问题。
一个健壮的分布式锁应该能够在部分故障的情况下仍然保持其功能。
高可用性和高性能分布式锁应该能够在高并发和高负载的情况下稳定运行且性能损失尽可能小。
具有较低的延迟和高吞吐量以确保对共享资源的访问不会成为系统性能的瓶颈。
支持多种锁模式如共享锁读锁和排他锁写锁以适应不同的业务场景。
锁的状态同步在分布式环境中锁的状态需要在不同的节点间同步确保一致性。
锁的粒度控制可以根据需要设置锁的粒度如对整个资源加锁或只对资源的一部分加锁。
锁的阻塞和非阻塞获取支持客户端选择阻塞或非阻塞方式获取锁以满足不同的业务需求。
安全性分布式锁应具备一定的安全性措施如防止恶意客户端获取或释放锁。
可靠性Reliability分布式锁应该能够在各种异常情况下保持正确的行为包括网络分区、节点故障、客户端崩溃等情况。
即使某个客户端持有锁的过程中发生异常也应该确保锁最终能够被释放以避免死锁等问题。
命令可以保证操作的原子性。
虽然没有达到强一致但是多节点时可以保证最终一致性。
机制更容易避免分布式锁争抢中的「惊群效应」问题抢占分布式锁时被频繁唤醒和重新休眠造成浪费。
解决方案为分布式锁释放后只唤醒满足条件的下一个节点。
ZK实现分布式锁的基本原理是以某个资源为下面的节点就是需要获取锁的客户端未获取到锁的客户端注册需要注册Watcher到上一个客户端。
如下图所示
临时节点和事件监听机制创建临时有序节点判断是否是最小从而获取到锁。
通过事件监听等待锁的释放。
解锁则删除节点。
MySQL的事务机制和锁机制足够满足上述的基本需求。
阻塞等机制我们可以借助MySQL的事务和锁机制select...for
update。
性能较低不适合高并发场景实现比较繁琐很少使用MySQL去实现分布式锁。
性能Redis基于内存读写性能高适合高并发。
ZK/Etcd相对弱一些。
运维成本Redis更常用、是基础组件运维也更简单。
ZK/Etcd都是分布式系统运维相对复杂一些。
易用性都有较成熟的客户端封装差别不大。
高可用均支持Redis采用Redlock方案ZK/Etcd本身就是高可用的。
RedissonRedLock。
在锁的处理上数据库算是性能最差的占用资源最多。
通常用上分布式锁的时候系统已经比较大了这个时候大概率已经分库分表增加了复杂度。
对于一些复杂的功能数据库实现不了解锁判断锁。
用数据库做分布式锁还不如让它作为乐观锁。
Kleppmann是英国剑桥大学的分布式系统的研究员之前和Redis之父
进行过关于RedLock红锁是否安全有过激烈讨论。
正如Martin所说我们使用分布式锁一般有两个场景正确性和效率。
1、保证数据正确性比如抢红包、秒杀下单等场景需要保障不会出现超卖等问题。
因为红包、秒杀商品等场景下只能被先到达的人才能抢到所以要顺序执行库存扣减等操作这样就需要限制同一时间只能有一个线程或进程对资源进行访问和修改。
分布式锁可以确保在多个副本部署服务或高并发的情况下同一时间只有一个线程或进程能够执行相应的业务代码从而避免数据不一致的问题。
2、避免重复执行某些操作浪费资源。
比如多个客户端可能都执行发送短信通知但是需要保证这个通知只被发送一次。
这些操作可能是非幂等性的即执行多次会产生不同的结果。
为了避免重复执行这些操作可以使用分布式锁来确保同一时间只有一个客户端能够执行该操作。
比如业务逻辑可能包括以下步骤
这样只有第一次执行该逻辑时才能成功发送此处应用分布式锁能避免因并发操作导致的重复发送问题。
类比单个服务中操作全局共享变量会先加锁避免并发修改资源造成错误分布式锁是用于分布式环境下避免并发修改资源导致破坏数据正确性用于控制某个资源在同一时刻只能被一个应用所使用。
如下图所示
的访问数据库的访问等等场景都可以通过分布式锁来控制。
而往业务场景去偏移包括超卖问题
重复消费问题等等也都在分布式锁的解决范围之内。
同时可以在一定程度上避免数据库级别的锁竞争问题。
避免同时的数据写入和修改。
这种最常见的场景在于缓存过期的问题上当并发到来的时候如果缓存服务器即将过期可能会基于缓存的特性限制缓存的重复读取和写入。
避免查询重复的数据。
再就例如分布式ID的场景下也会通过分布式锁类似的方式来获取一个粗粒度的
组件微服务之间没有互相通信那么就需要分布式锁来限制任务触发的频率。
对应的还包括
的访问频率也可以在分布式锁的基础上进行扩展主要就是要求原子性的计数。
由于分布式场景的特性可能在单机上面被视为原子对象的资源在分布式场景下就变成了多个资源。
分布式锁并不能改变这种状态但是可以增强一致性
要实现锁的等待首先要有个明确的等待时间然后在业务代码里面等待比如自旋Java的锁。
锁的主键一般情况下实现的时候都是通过
它是通过线程ID来实现的重入如果同一个应用线程相同就可能存在问题。
则不可以删除锁只是设置时间redis.call(pexpire,
首先在设置过期时间时要结合业务场景去考虑尽量设置一个比较合理的值就是理论上正常处理的话在这个过期时间内是一定能处理完毕的。
接着考虑对这个问题进行兜底设计。
关于这个问题目前常见的解决方法有两种
守护线程“续命”额外起一个线程定期检查线程是否还持有锁如果有则延长过期时间。
Redisson
里面就实现了这个方案使用“看门狗”定期检查每1/3的锁时间检查1次如果线程还持有锁则刷新过期时间。
超时回滚当解锁时发现锁已经被其他线程获取了说明此时我们执行的操作已经是“不安全”的了此时需要进行回滚并返回失败。
同时需要进行告警人为介入验证数据的正确性然后找出超时原因是否需要对超时时间进行优化等等。
会启动一个后台线程看门狗它会在锁的过期时间的一半时检查锁是否仍然被当前线程持有。
续期锁如果锁仍然被持有看门狗会延长锁的过期时间。
这确保了即使业务逻辑执行时间较长锁也不会过期。
执行业务逻辑在锁的保护下执行业务逻辑。
释放锁当业务逻辑执行完毕后调用
调用确保了锁能够被释放防止死锁。
看门狗线程终止一旦锁被释放看门狗线程会停止续期操作并结束。
提供了一个简单而强大的机制来处理分布式锁的自动续期从而减少了锁过期导致的问题。
分布式锁过期的情况是指当一个线程因为执行时间过长导致持有的分布式锁过期而其他线程又获取了同一把锁时原线程需要能够检测到这一情况并执行业务逻辑的回滚操作。
无论业务逻辑是否成功执行都需要在
块中释放锁以避免死锁。
在释放锁之后如果业务逻辑执行失败可能需要通知用户或者记录日志以便进一步处理。
获取锁使用SETNX命令尝试设置唯一锁标识符。
返回1表示成功创建锁返回0表示锁已被占用。
设置过期时间成功获取锁后用EXPIRE命令为锁设定超时时间防止客户端崩溃导致锁无法释放。
释放锁完成任务后使用DEL命令删除锁标识符释放锁。
需要注意的是在获取锁后执行业务逻辑时应设定合理的超时时间以避免锁被长时间占用。
这种方式实现的锁存在一定的缺陷当
服务器故障或者出现网络分区时可能会导致锁无法正常释放从而导致死锁的问题。
SETNX
先拿setnx来争抢锁抢到之后再用expire给锁加一个过期时间防止锁忘记了释放。
如果在setnx之后执行expire之前进程意外crash或者要重启维护了那会怎么样
set指令有非常复杂的参数这个应该是可以同时把setnx和expire合成一条指令来使用。
表示只有在锁不存在的情况下才设置锁。
释放锁完成任务后使用DEL命令删除锁标识符释放锁。
缺陷一个客户端可能会误删除别的客户端的锁。
比如客户端A获得分布式锁之后执行业务操作过久导致分布式锁已经过期了。
此时客户端B成功获得分布式锁之后A完成业务操作就把客户端B的锁操作删除了。
多个客户端使用同一个lock_key但是各个客户端应该使用自己唯一的unique_val以便删除的时候进行校验防止自己的锁被别的客户端误操作删除。
是否相同相同则使用DEL命令删除锁标识符释放锁。
为保证释放锁为原子操作需使用lua脚本完成这两步操作。
获取当前时间戳所有Redis实例使用相同的时间源例如NTP获取当前时间戳。
尝试在多个Redis实例上获取锁在每个Redis实例上尝试使用SET命令获取锁设置一个带有唯一标识符的键设置的键名应该是全局唯一的以避免与其他锁冲突。
计算获取锁所花费的时间计算从第一步获取时间戳到成功获取锁所花费的时间记为elapsed_time。
判断锁是否获取成功如果获取锁的时间elapsed_time小于设定的锁超时时间并且大多数例如大于一半的Redis实例成功获取了锁那么认为锁获取成功。
释放锁在所有成功获取锁的Redis实例上执行释放锁的操作使用DEL命令删除对应的键。
作者把这个方案一经提出就马上受到业界著名的分布式系统专家Martin的质疑。
Martin指出了分布式系统的三类异常场景NPC
安全性问题由于GC问题会导致分布式锁的正确性出现问题如下图所示
作者同意对方关于时钟跳跃对Redlock的影响但认为通过运维手段是可以避免的。
Redlock中有超时时间判断的机制可以有效避免NPC问题但是如果Redlock
首先Redis能做分布式锁主要是因为是单线程执行的所以如果能在一个指令里面操作完获取以及设置锁的话就不会有并发问题。
比如setnx指令以及新版的set支持相关的参数。
当然也可以用lua脚本来保证多个指令的原子性。
但是基于简单的一条指令去做锁的话是一个不可重入锁另外一个锁过期时间不好把控可能会出现业务没有执行完锁过期。
所以redisson主要解决这2个问题当然还有一些比如读写锁、联锁等等。
可重入的话基于luahash去实现可重入锁。
然后时间把控如果你不知道锁多久过期redisson会基于时间轮递归来时间锁续期。
也就是我们说的看门狗。
时间轮就是hashWheelTimer是一个netty包下的类它主要实现的功能是延时执行任务。
它的实现逻辑是会启动一个线程去轮询数组然后任务根据延时多久添加到对应数组如果轮询到了的话通过多线程去执行相关的任务。
去延时判断key是否存在如果存在就续期并且递归做到只要key还在就一直续期。
如果key不存在就不再递归。
节点上同时加锁。
它允许多个资源例如不同的Redis键同时被锁定只有当所有的资源都成功锁定时联锁才会认为锁定成功。
这种机制非常适合于需要同时锁定多个资源以执行某些操作的场景。
联锁的目的是因为redis是属于AP模式的中间件会存在数据丢失那么锁就会失效。
所以联锁主要做的一件事情就是尽可能的去保证数据不丢失加锁会加在不同的独立集群机器。
当满足一半成功就成功。
其实主要思想就是把鸡蛋分散到不同的篮子。
降低风险。
只要不是超过一半的失败就是成功的。
首先想到的还是Redis的单点故障问题如果Redis挂了分布式锁就不能正常工作因此可以引入Redis主从模式。
但是如果在Redis集群的master节点上拿到了锁但是加锁的key还没同步到slave节点。
恰好这时master节点发生故障一个slave节点就会升级为master节点。
但是此时分布式锁也已经失效了。
Redlock红锁。
为此需要部署5个单独的RedisRedlock的实现步骤如下
master节点请求加锁。
根据设置的超时时间来判断加锁的总耗时要小于锁设置的过期时间是不是要跳过该master节点。
如果大于等于3个节点加锁成功并且使用的时间小于锁的有效期即可认定加锁成功。
释放锁向全部节点发起释放锁请求。
哨兵模式或集群模式中由于网络原因导致主节点Master与哨兵Sentinel和从节点Slave的通讯中断此时哨兵就会误以为主节点已宕机就会在从节点中选举出一个新的主节点此时
清空当前数据此时第四步旧客户端给他的发送的数据就丢失了。
之后再加载
为我们提供了以下两个配置通过以下两个配置可以尽可能的避免数据丢失的问题
min-slaves-to-write与主节点通信的从节点数量必须大于等于该值主节点否则主节点拒绝写入。
min-slaves-max-lag主节点与从节点通信的
的要求那么主节点就会被禁止写入脑裂造成的数据丢失情况自然也就解决了。
网络分区当网络发生分区时主节点可能会与部分从节点失去连接但仍然能够接收客户端的写入请求。
如果剩余的从节点数量满足
的要求主节点将继续处理写入操作而这些写入可能不会同步到被网络分区隔离的从节点从而导致数据不一致。
假故障如果主节点或从节点发生临时故障如进程暂停、网络延迟等在故障恢复之前主节点可能会拒绝写入这会影响系统的可用性。
配置复杂性正确配置这两个参数需要深入理解系统的性能和网络状况配置不当可能会导致不必要的写入拒绝或数据不一致。
Broadcast协议来保证分布式系统的一致性。
该协议确保了即使在发生网络分区时也只有一个领导节点能够处理写操作。
集群中的每个操作都需要得到超过半数节点的同意才能执行。
这确保了在网络分区时至少有一半的节点与领导节点相连从而避免了脑裂问题。
限流是指在各种应用场景中通过技术和策略手段对数据流量、请求频率或资源消耗进行有计划的限制以避免系统负载过高、性能下降甚至崩溃的情况发生。
限流的目标在于维护系统的稳定性和可用性并确保服务质量。
使用率饱和从而引发系统响应慢、无法正常服务的问题。
防止资源滥用确保有限的服务资源被合理公平地分配给所有用户防止个别用户或恶意程序过度消耗资源。
优化用户体验对于网站和应用程序而言如果任由高并发导致响应速度变慢会影响所有用户的正常使用体验。
保障安全在网络层面限流有助于防范
攻击降低系统遭受恶意攻击的风险。
运维成本控制合理的限流措施可以帮助企业减少不必要的硬件投入节省运营成本。
计数器算法将时间周期划分为固定大小的窗口如每分钟、每小时并在每个窗口内统计请求的数量。
当窗口内的请求数达到预设的阈值时后续请求将被限制。
时间窗口结束后计数器清零。
优点实现简单易于理解。
缺点在窗口切换时刻可能会有突刺流量问题即在窗口结束时会有短暂的大量请求被允许通过。
滑动窗口算法改进了计算器算法固定窗口算法的突刺在短时间内突然出现的流量高峰或数据量激增的现象问题将时间窗口划分为多个小的时间段桶每个小时间段有自己的计数器。
随着时间流逝窗口像滑块一样平移过期的小时间段的计数会被丢弃新时间段加入计数。
所有小时间段的计数之和不能超过设定的阈值。
优点更平滑地处理流量避免了突刺问题。
缺点实现相对复杂需要维护多个计数器。
漏桶算法想象一个固定容量的桶水请求以恒定速率流入桶中同时桶底部有小孔让水以恒定速率流出。
当桶满时新来的水请求会被丢弃。
此算法主要用来平滑网络流量防止瞬时流量过大。
优点可以平滑突发流量保证下游系统的稳定。
缺点无法处理突发流量高峰多余的请求会被直接丢弃。
令牌桶算法与漏桶相反有一个固定速率填充令牌的桶令牌代表请求许可。
当请求到达时需要从桶中取出一个令牌如果桶中有令牌则允许请求通过否则拒绝。
桶的容量是有限的多余的令牌会被丢弃。
优点既能平滑流量又能处理一定程度的突发流量因为令牌可以累积。
缺点需要精确控制令牌生成速度实现较漏桶复杂。
基于计数器和过期时间实现的计数器算法使用一个计数器存储当前请求量每次使用
方法相加并设置一个过期时间计数器在一定时间内自动清零。
计数器未到达限流值就可以继续运行反之则不能继续运行。
基于有序集合ZSet实现的滑动窗口算法将请求都存入到
个时间戳内的所有请求通过获取的请求数和限流数进行比较并判断从而实现限流。
基于列表List实现的令牌桶算法在程序中使用定时任务给
的计数器保存当前请求的数量。
设置一个过期时间使得计数器在一定时间内自动清零。
每次收到请求时检查计数器当前值如果未达到限流阈值则增加计数器的值否则拒绝请求。
使用有序集合ZSet来存储每个时间窗口内的请求时间戳成员member表示请求的唯一标识分数score表示请求的时间戳。
每次收到请求时将请求的时间戳作为成员当前时间戳作为分数加入到有序集合中。
根据有序集合的时间范围和滑动窗口的设置判断当前时间窗口内的请求数量是否超过限流阈值。
添加当前请求的时间戳到有序集合jedis.zadd(ZSET_KEY,
String.valueOf(currentTimestamp));//
currentTimestamp;jedis.zremrangeByScore(ZSET_KEY,
jedis.zrangeByScoreWithScores(ZSET_KEY,
requestTimestamps.size();jedis.close();//
每秒中添加一个令牌当然也可以通过修改定时任务的执行时间来控制令牌的发放速度具体实现代码如下
redisTemplate.opsForList().rightPush(limit_list,UUID.randomUUID().toString());②
redisTemplate.opsForList().leftPop(limit_list);
中获取一个令牌如果拿到令牌了那就说明没超出限制可以继续执行反之则不能执行。
高性能基于内存的键值存储系统读写速度非常快适合用于需要高性能的限流场景。
分布式环境友好支持分布式部署可以在多个服务器之间共享计数器和状态。
灵活的限流策略数据结构多可以实现各种复杂的限流策略如固定窗口、滑动窗口等。
原子操作如
服务器如果服务器出现问题会影响限流功能的正常运行。
网络延迟虽然
性能高但网络请求比本地计算要慢可能会引入额外的延迟。
资源消耗虽然
在微服务架构中经常被用于限流尤其是在需要跨服务限流或全局限流的场景下。
然而微服务架构中不使用
Redis可能会引入单点故障的风险。
简化架构有些微服务架构可能会选择更简单的限流方案如使用本地计数器或基于时间的令牌桶算法以减少对外部系统的依赖。
性能考虑对于一些对性能要求极高的微服务可能会选择在服务内部实现限流逻辑以避免网络通信的开销。
持久化就是把内存的数据写到磁盘中去防止服务宕机了内存数据丢失。
Redis
功能核心函数rdbSave(生成RDB文件)和rdbLoad从文件加载内存两个函数
每当执行服务器(定时)任务或者函数时flushAppendOnlyFile
AOF文件比RDB更新频率高优先使用AOF还原数据。
AOF比RDB更安全也更大。
RDB性能比AOF好。
如果两个都配了优先加载AOF。
在现代的互联网应用中Redis因其出色的性能和简便的使用方法被广泛用作缓存和消息队列系统。
然而Redis作为一个基于内存的数据库一旦服务宕机内存中的数据就会全部丢失。
因此Redis的数据持久化机制对于数据恢复至关重要。
数据持久化是确保系统稳定性和数据完整性的重要手段。
对于Redis而言持久化可以防止因服务宕机导致的内存数据丢失。
如果不进行持久化一旦Redis服务异常停止所有未持久化的数据都将丢失这对于一些对数据一致性要求极高的应用场景来说是不可接受的。
Database通过创建数据集的时间点快照来实现持久化。
RDB持久化可以手动触发也可以根据配置文件中设置的时间间隔自动触发。
AOFAppend
File记录服务器接收到的每个写操作并将这些操作追加到文件末尾。
在Redis重启时通过重放这些操作来重建原始数据集。
RDB
AOF结合使用RDB和AOF两种持久化方式以获得两者的优势。
无持久性完全不启用持久化这种方式风险极高不推荐在生产环境中使用。
RDB持久化通过生成内存数据的快照并保存到磁盘上的dump.rdb文件来实现。
Redis提供了save和bgsave两个命令来生成RDB文件。
save命令在主线程中执行会阻塞其他操作而bgsave命令会在后台创建一个子进程来执行快照操作不影响主线程。
为了减少对性能的影响Redis允许通过配置文件redis.conf设置自动快照的条件例如
RDB文件是某一时间点的数据快照适合用于数据备份和全量复制。
使用LZF算法压缩文件体积小恢复速度快。
快照方式无法做到实时持久化存在数据丢失的风险。
使用bgsave创建快照时频繁fork子进程对性能有一定影响。
与RDB不同AOF持久化记录了服务器接收到的每个写操作命令并在Redis重启时重放这些命令来恢复数据。
AOF持久化提供了三种同步策略
Always每个写命令后都同步数据安全性高但可能影响性能。
Everysec每秒同步一次折衷了性能和数据安全性。
No由操作系统控制何时同步可能在宕机时丢失数据。
随着时间的推移AOF文件可能会变得非常大恢复速度慢。
需要定期进行AOF日志重写以优化性能。
4.0引入了RDB和AOF的混合持久化方式。
这种方式结合了两者的优点快照不频繁执行减少了对主线程的影响AOF日志只记录两次快照间的操作避免了文件过大的问题。
仅使用RDB从最近的RDB快照文件恢复数据。
仅使用AOF重放AOF日志中的写操作来恢复数据。
RDB
AOF优先使用AOF日志恢复因为它可能包含了RDB快照之后的数据变更。
无持久化如果没有任何持久化数据将无法恢复数据。
Redis的持久化策略对于确保数据的安全性和系统的稳定性至关重要。
选择合适的持久化方式需要根据具体的业务需求和性能考虑。
在实践中通常推荐使用RDB和AOF的混合持久化策略以获得较好的性能和数据安全性。
同时定期的备份和监控也是保证Redis数据安全的重要措施。
实现分布式锁是两步操作设置一个key增加一个过期时间所以我们首先需要保证的就是这两个操作是一个原子操作。
在获取锁和释放锁的过程中要保证这个操作的原子性确保加锁操作与设置过期时间操作是原子的。
Redis
为了保证锁的释放防止死锁的发生获取到的锁需要设置一个过期时间也就是说当锁的持有者因为出现异常情况未能正确的释放锁时锁也会到达这个时间之后自动释放避免对系统造成影响。
如果释放锁的过程中发生系统异常或者网络断线问题不也会造成锁的释放失败吗是的这个极小概率的问题确实是存在的。
所以我们设置锁的过期时间就是必须的。
当发生异常无法主动释放锁的时候就需要靠过期时间自动释放锁了。
在上面对锁都加锁正常的情况下在锁释放时能正确的释放自己的锁吗所以每个客户端应该提供一个唯一的标识符确保在释放锁时能正确的释放自己的锁而不是释放成为其他的锁。
一般可以使用客户端的ID作为标识符在释放锁时进行比较确保只有当持有锁的客户端才能释放自己的锁。
如果我们加的锁没有加入唯一标识在多线程环境下可能就会出现释放了其他线程的锁的情况发生。
有些朋友可能就会说了在多线程环境中线程A加锁成功之后线程B在线程A没有释放锁的前提下怎么可以再次获取到锁呢所以也就没有释放其他线程的锁这个说法。
下面我们看这么一个场景如果线程A执行任务需要10s锁的时间是5s也就是当锁的过期时间设置的过短在任务还没执行成功的时候就释放了锁此时线程B就会加锁成功等线程A执行任务执行完成之后执行释放锁的操作此时就把线程B的锁给释放了这不就出问题了吗。
所以为了解决这个问题就是在锁上加入线程的ID或者唯一标识请求ID。
对于锁的过期时间短这个只能根据业务处理时间大概的计算一个时间还有就是看门狗进行锁的续期。
非阻塞获取意味着获取锁的操作不会阻塞当前线程或进程的执行。
通常在尝试获取锁时如果锁已经被其他客户端持有常见的做法是让当前线程或进程等待直到锁被释放。
这种方式称为阻塞获取锁。
相比之下非阻塞获取锁不会让当前线程或进程等待锁的释放而是立即返回获取锁的结果。
如果锁已经被其他客户端持有那么获取锁的操作会失败返回一个失败的结果或者一个空值而不会阻塞当前线程或进程的执行。
非阻塞获取锁通常适用于一些对实时性要求较高、不希望阻塞的场景比如轮询等待锁的释放。
当获取锁失败时可以立即执行一些其他操作或者进行重试而不需要等待锁的释放。
在
命令尝试获取锁如果返回成功即返回1表示获取锁成功如果返回失败即返回0表示获取锁失败。
通过这种方式可以实现非阻塞获取锁的操作。
在规定的时间范围内假如说500ms自旋不断获取锁不断尝试加锁。
如果成功则返回。
如果失败则休息50ms然后在开始重试获取锁。
如果到了超时时间也就是500ms时则直接返回失败。
对应的锁还没有释放的话在使用相同的key去加锁大概率是会失败的。
下面有这样一个场景需要获取满足条件的菜单树后台程序在代码中递归的去获取知道获取到所有的满足条件的数据。
我们要知道菜单是可能随时都会变的所以这个地方是可以加入分布式锁进行互斥的。
后台程序在递归获取菜单树的时候第一层加锁成功第二层、第n层
{if(level10){this.fun(level,lockKey,requestId);}
如果直接使用的话看起来问题不大但是真正执行程序之后就会发现报错啦。
因为从根节点开始第一层递归加锁成功之后还没有释放这个锁就直接进入到了第二层的递归之中。
因为锁名为lockKey并且值为requestId的锁已经存在所以第二层递归大概率会加锁失败最后就是返回结果只有底层递归的结果返回了。
所以我们还需要一个可重入的特性。
框架中已经实现了可重入锁的功能所以可以直接使用加锁主要通过以下代码实现的。
KEYS[1]);KEYS[1]锁名ARGV[1]过期时间ARGV[2]uuid
2、接下来判断如果key和requestId值都存在则使用hincrby命令给该key和requestId值计数每次都加1。
注意一下这里就是重入锁的关键锁重入一次值就加1。
对于大量写入的业务场景使用普通的分布式锁就可以实现我们的需求。
但是对于写入操作少的有大量读取操作的业务场景直接使用普通的redis锁就会浪费性能了。
所以对于锁的优化来说我们就可以从业务场景读写锁来区分锁的颗粒度尽可能将锁的粒度变细提升我们系统的性能。
对于降低锁的粒度上面我们知道了读写锁也算事在业务层面进行降低锁粒度的一种方式所以下面我们以
redisson.getReadWriteLock(readWriteLock);
redisson.getReadWriteLock(readWriteLock);
}通过将锁分为读锁与写锁最大的提升之后就在与大大的提高系统的读性能因为读锁与读锁之间是没有冲突的不存在互斥然后又因为业务系统中的读操作是远远多与写操作的所以我们在提升了读锁的性能的同时系统整体锁的性能都得到了提升。
上面我们通过业务层面的读写锁进行了锁粒度的减小下面我们在通过锁的分段减少锁粒度实现锁性能的提升。
如果你对
的源码了解的话你就会知道分段锁的原理了。
是的就是你想的那样把一个大的锁划分为多个小的锁。
100个商品那么分段的意思就是将100个商品分成10份相当于有
具体的实现就是在秒杀的过程中对用户进行取模操作算出来当前用户应该对哪一份商品进行秒杀。
通过上述将大锁拆分为小锁的过程以前多个线程只能争抢一个锁现在可以争抢10个锁大大降低了冲突提升系统吞吐量。
不过需要注意的就是使用分段锁确实可以提升系统性能但是相对应的就是编码难度的提升并且还需要引入取模等算法所以我们在实际业务中也要综合考虑。
在上面我们也说过了因为业务执行时间太长导致锁自动释放了也就是说业务的执行时间远远大于锁的过期时间这个时候
针对这种情况我们可以使用锁的续期增加一个定时任务如果到了超时时间业务还没有执行完成就需要对锁进行一个续期。
TimeUnit.MILLISECONDS);获取到锁之后自动的开启一个定时任务每隔
中自动刷新一次过期时间。
这种机制就是上面我们提到过的看门狗。
对于自动续期操作我们还是推荐使用
0;需要注意的一点就是锁的续期不是一直续期的业务如果一直执行不完到了一个总的超时时间或者执行续期的次数超过几次我们就不再进行续期操作了。
就宕机了此时后面在来新的线程获取锁A也是可以加锁成功的所以分布式锁也就失效了。
集群只要当加锁成功的集群有5/21个节点加锁成功意味着这次加锁就是成功的。
4、环境包括单机、主从、哨兵、集群可以一种或者多种混合都可以。
4、如果中途各个节点加锁的总耗时大于等于设置的最大等待时间直接返回加锁失败。
理论应该都是知道的所以我们在选择分布式锁的时候也可以参考这个。
其实在我们绝大多数的业务场景中使用Redis已经可以满足因为数据的不一致我们还可以使用
理论的最终一致性方案解决。
因为如果系统不可用了对用户来说体验肯定不是那么好的。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback