摘要:分布式锁的作用在单机环境下,有个秒杀商品的活动,在短时间内,服务器压力和流量会陡然上升。分布式集群业务业务场景下,每台服务器是独立存在的。这里就用到了分布式锁这里简单介绍一下,以的事务机制来延生。
Redis 分布式锁的作用
在单机环境下,有个秒杀商品的活动,在短时间内,服务器压力和流量会陡然上升。这个就会存在并发的问题。想要解决并发需要解决以下问题
1、提高系统吞吐率也就是qps 每秒处理的请求书
2、避免商品在高并发的情况下,出现资源争抢导致的超买超买问题
解决问题一:采用内存型数据库提高系统的qps
解决问题二:就要用到经常会遇到的锁,例如MySQL 有读锁、写锁、排他锁、悲观锁、乐观锁。不过这里只讨论redis来实现锁
$redis = new Redis(); $redis->connect("127.0.0.1", 6379); //连接Redis $expire = 10;//有效期10秒 $key = "lock";//key $value = time() + $expire;//锁的值 = Unix时间戳 + 锁的有效期 $lock = $redis->setnx($key, $value); //判断是否上锁成功,成功则执行下步操作 if(!empty($lock)) { //下步操作... }
如果以这样的简单版设置锁就能解决所有问题,未免也太小看锁在程序中应用了。
按正常的操作示例基本上都是这样写的。但是这样写有一些问题
1、假如有10000 个请求访问了redis 不存在的键,这样请求就是指接到了MySQL数据,造成CPU短时间内达到100%甚至宕机。这样场景俗称缓存击穿造成的缓存雪崩。
2、 假如CPU过高或者网络延时问题,造成锁没有被删除掉或者缓存键过期没有被回收的情况,就会形成死锁。
解决问题:引用reids setnx 方法的作用是,当设置的key 不存在时,设置新的值。这样就避免了缓存击穿的问题。检测键的过期时间,避免产生死锁
解决死锁问题$expire = 10;//有效期10秒 $key = "lock";//key $value = time() + $expire;//锁的值 = Unix时间戳 + 锁的有效期 $status = true; while($status) { $lock = $redis->setnx($key, $value); if(empty($lock)) { $value = $redis->get($key); if($value < time()) { $redis->del($key); } }else{ $status = false; //下步操作.... } }
1、按理说这样解决单机版锁竞争问题已经是没有多大问题了。那么特殊情况来了,如果这个设置锁的键由于意外情况没有被删除,这样同样会有死锁的情况发生。2、分布式集群业务业务场景下,每台服务器是独立存在的。多台服务器怎么通过一个标识来相互竞争锁呢。这里就用到了分布式锁
这里简单介绍一下,以MYSQL 的事务机制来延生。事务四个特性ACID,有四种隔离级别:未提交读、已提交读、可重复读、串行化。这些特性都只在单台服务器上生效。到了分布式集群了,数据在不同的服务器上,紧靠事务很难保持数据的一致性及隔离性,事务的作用就意义不大了。Redis也是如此。
正确的分布式锁的打开方式/** * 实现Redis分布锁 */ $key = "demo"; //要更新信息的缓存KEY $lockKey = "lock:".$key; //设置锁KEY $lockExpire = 10; //设置锁的有效期为10秒 //获取缓存信息 $result = $redis->get($key); //判断缓存中是否有数据 if(empty($result)) { $status = TRUE; while ($status) { //设置锁值为当前时间戳 + 有效期 $lockValue = time() + $lockExpire; /** * 创建锁 * 试图以$lockKey为key创建一个缓存,value值为当前时间戳 * 由于setnx()函数只有在不存在当前key的缓存时才会创建成功 * 所以,用此函数就可以判断当前执行的操作是否已经有其他进程在执行了 * @var [type] */ $lock = $redis->setnx($lockKey, $lockValue); /** * 满足两个条件中的一个即可进行操作 * 1、上面一步创建锁成功; * 2、 1)判断锁的值(时间戳)是否小于当前时间 $redis->get() * 2)同时给锁设置新值成功 $redis->getset() */ if(!empty($lock) || ($redis->get($lockKey) < time() && $redis->getSet($lockKey, $lockValue) < time() )) { //给锁设置生存时间 $redis->expire($lockKey, $lockExpire); //****************************** //此处执行插入、更新缓存操作... //****************************** //以上程序走完删除锁 //检测锁是否过期,过期锁没必要删除 if($redis->ttl($lockKey)) $redis->del($lockKey); $status = FALSE; }else{ /** * 如果存在有效锁这里做相应处理 * 等待当前操作完成再执行此次请求 * 直接返回 */ sleep(2);//等待2秒后再尝试执行操作 } } }结尾
文章从知识面的广度(mysql)、示例代码优缺点的简介及应用的场景,区别于其他博客文章。嘿嘿~
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/31258.html
摘要:由于执行的原子性所以不要在中执行过长开销的程序,否则会验证影响其它请求的执行。同一个脚本生成的签名都是相同的,所以签名可以先在本地生成,然后在服务器上一次脚本,程序中只需保存和使用该签名即可。同样的脚本,是始终生成相同的签名的。 Last-Modified: 2019年6月5日15:59:34 参考链接 PHP使用Redis+Lua脚本操作的注意事项 《Redis官方文档》用Redi...
阅读 2872·2021-10-14 09:43
阅读 1663·2021-09-29 09:34
阅读 1746·2021-07-28 00:16
阅读 2965·2019-08-30 15:53
阅读 2907·2019-08-30 13:59
阅读 2963·2019-08-30 13:57
阅读 1092·2019-08-26 13:38
阅读 1895·2019-08-26 13:25