前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Redis]laravel中使用Redis分布式锁解决并发问题

[Redis]laravel中使用Redis分布式锁解决并发问题

作者头像
BinGo_Blog
发布2022-11-16 20:06:59
1.6K1
发布2022-11-16 20:06:59
举报
文章被收录于专栏:BinGo's BlogBinGo's Blog

需求描述

应用中一个第三方接口回调会产生并发请求,单次同时推送很多条信息,出现重复入库情况,需要在入库前拦截。

解决方案

  • 使用laravel队列不在此文章讨论范围;
  • 使用Redis

实现方法

1.请求处理开始前,先尝试获取锁,如果获取成功则继续执行,否则,终止执行。加锁时,需要考虑如果后续任务执行失败,能定时清理掉该锁,以防出现死锁。代码示例如下:

代码语言:javascript
复制
/**
     * 尝试获取锁
     * @param String $key               锁
     * @param String $requestId         请求id
     * @param int $expireTime           过期时间
     * @return bool                     是否获取成功
     */
    public static function tryGetLock(String $key, int $expireTime, String $requestId) {

        $lua ="return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2])";

        $result = Redis::eval($lua, 1,$key, $requestId, $expireTime);
        return self::LOCK_SUCCESS === (String)$result;
    }

2.该请求执行完成后,解除锁。示例代码如下:

代码语言:javascript
复制
/**
     * 解除锁
     * @param String $key               锁
     * @param String $requestId         请求id
     */
    public static function releaseLock( String $key, String $requestId) {
        $lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        $result = Redis::eval($lua, 1, $key, $requestId);
        return self::RELEASE_SUCCESS === $result;
    }

完整代码

代码语言:javascript
复制
use Illuminate\Support\Facades\Redis;

class RedisTool
{
    const LOCK_SUCCESS = 'OK';
    const RELEASE_SUCCESS = 1;

    /**
     * 尝试获取锁
     * @param String $key               锁
     * @param String $requestId         请求id
     * @param int $expireTime           过期时间
     * @return bool                     是否获取成功
     */
    public static function tryGetLock(String $key, int $expireTime, String $requestId) {

        $lua ="return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2])";

        $result = Redis::eval($lua, 1,$key, $requestId, $expireTime);
        return self::LOCK_SUCCESS === (String)$result;
    }


    /**
     * 解除锁
     * @param String $key               锁
     * @param String $requestId         请求id
     */
    public static function releaseLock( String $key, String $requestId) {
        $lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        $result = Redis::eval($lua, 1, $key, $requestId);
        return self::RELEASE_SUCCESS === $result;
    }
}

总结备注

1.为什么要用Lua脚本来实现?

· 加锁时,先通过setnx加锁,然后在通过expire设置过期时间,无法保证redis原子性,在setnx执行后,程序可能挂掉,造成死锁; · 解锁时,如果通过Redis::del($key),可能解除的是其他请求的锁; 总结:执行单个redis时,是可以保证原子性,如果是两个操作,则无法保证原子性。

2.加锁时为什么不直接用Redis::set($Key, $requestId, ['nx', 'ex' => $expireTime])?

· 这里我的laravel使用的是predisReis::set()方法不支持这种写法。

3.请求id$requestId是做什么的?怎么保证唯一?

· requestId是区分本次请求与其他请求的标识。在加锁区间的业务执行完成后,需要解锁,requestId保证了解锁的是当前请求的锁,而不是其他锁。·

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需求描述
  • 解决方案
  • 实现方法
  • 完整代码
  • 总结备注
    • 1.为什么要用Lua脚本来实现?
      • 2.加锁时为什么不直接用Redis::set($Key, $requestId, ['nx', 'ex' => $expireTime])?
        • 3.请求id$requestId是做什么的?怎么保证唯一?
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档