前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis 分布式锁2

Redis 分布式锁2

作者头像
张云飞Vir
发布2022-09-29 12:23:09
3760
发布2022-09-29 12:23:09
举报
文章被收录于专栏:写代码和思考

场景

一般电商网站都会遇到秒杀、特价之类的活动,大促活动有一个共同特点就是访问量激增,在高并发下会出现成千上万人抢购一个商品的场景。虽然在系统设计时会通过限流、异步、排队等方式优化,但整体的并发还是平时的数倍以上,参加活动的商品一般都是限量库存,如何防止库存超卖,避免并发问题呢?分布式锁就是一个解决方案。

“分布式锁”是用来解决分布式应用中“并发冲突”的一种常用手段,实现方式一般有基于zookeeper及基于redis二种

自己写一个简单的 redis分布式锁

加锁时

加锁时使用 set 命令,使用 加锁执行命令

代码语言:javascript
复制
SET resource_name random_value NX PX 30000

由于就一条命令,是原子性,比较安全。

代码语言:javascript
复制
SET 命令格式说明:
  SET key value [EX seconds] [PX milliseconds] [NX|XX]
示例:
  SET lock1 100 NX PX 30000
  这设置了一个  名字叫做 lock1 的锁;100标识随机数;NX 表示只在键不存在时,才对键进行设置操作;PX 和后面的数字表示过期时间。

这个随机数,由客户端生成,用来标识持有锁的人,在删除时只能由持有锁的人来删除。

解锁

所以在解锁之前先判断一下是不是自己加的锁,是自己加的锁再释放,不是就不释放。所以伪代码如下

代码语言:javascript
复制
if (random_value .equals(redisClient.get(resource_name))) {
  del(key)
}

因为判断和解锁是2个独立的操作,不具有原子性,所以解锁的过程要执行如下的Lua脚本,通过Lua脚本来保证判断和解锁具有原子性

代码语言:javascript
复制
if redis.call("exists",KEYS[1]) == 0 then
    return 0
end

if redis.call('get',KEYS[1]) == ARGV[1] then
    return redis.call('del',KEYS[1])
else
    return 0
end

Redis执行Lua脚本的命令,从Redis2.6开始,内嵌Lua环境,通过 EVAL 命令可以执行脚本

代码语言:javascript
复制
命令格式:
  EVAL script numkeys key [key...] arg [arg...]
参数说明:
  script 表示脚本内容。
  numkeys 表示 参数的个数
  key 表示 键
  arg 表示参数

问:在 LUA 脚本中如何掉 redis的 set get 等命令呢?
答:使用 redis.call(...)  方法来调用

用 java 代码实现 上锁

代码语言:javascript
复制
/**
    * 上锁
    *
    * @param key     锁名
    * @param uid     使用者的标识,可用随机数等
    * @param timeout 超时(毫秒)
    * @return
    */
   public boolean tryLock(String key, String uid, long timeout) {
       //System.out.printf("尝试获得锁%s, uid=%s \n", key, uid);
       // 等同于:SET lock1 100 NX PX 30000
       Boolean isok = stringRedisTemplate.opsForValue().setIfAbsent(
               LOCK_PREFIX + key,
               uid,
               timeout, TimeUnit.MILLISECONDS);
       //System.out.println("获得锁=" + isok);
       return isok != null && isok;
   }

用 java 代码实现 释放锁

先把上面的 解锁的lua 脚本放到一个文件 unlock.lua 里,放置到项目的资源文件夹中。

代码语言:javascript
复制
/**
  * 解锁
  * @param key
  * @param uid
  * @return
  */
 public Long unLock(String key, String uid) {
     //System.out.printf("尝试释放锁 %s ,uid=%s\n", key, uid);
     DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
     defaultRedisScript.setResultType(Long.class);
     defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis_lock/unlock.lua")));
     Long execute = stringRedisTemplate.execute(defaultRedisScript, Arrays.asList(LOCK_PREFIX + key), uid);
     //System.out.println("释放锁=" + execute);
     return execute;
 }

最后

如果不用 Redisson,自己写得也能用,不够也有缺陷: 缺陷:

  • 会有业务未执行完,锁过期的问题,也就是锁不具有可重入性的特点。
  • 在尝试获取锁的时候,是非阻塞的,不满足在一定期限内不断尝试获取锁的场景。

以上两点,都可以采用 Redisson框架里的锁 解决

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景
  • 自己写一个简单的 redis分布式锁
    • 加锁时
      • 解锁
        • 用 java 代码实现 上锁
          • 用 java 代码实现 释放锁
          • 最后
          相关产品与服务
          云数据库 Redis®
          腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档