前面讲解到实战问题】-- 设计礼品领取的架构设计以及多次领取现象解决?,如果出现网络延迟的情况下,多个请求阻塞,那么恶意攻击就可以全部请求领取接口成功,而针对这种做法,我们使用setnx来解决,确保只有一个请求可以进入接口请求。

    public String receiveGitf(int activityId,int giftId,String uid){
// isExist判断活动是否存在,内部包括redis和数据库请求,省略
if(isActivityExist(activityId,giftId)){
// 活动和礼品有效,判断是否领取过
if(!userReceived(uid,activityId,giftId)){
// 没有领取过,调用C系统
try {
// setnx
if(redis.setnx("uid_activityId_giftId")){
boolean receivedResult = Http.getMethod(C_Client.class, "distributeGift");
if(receivedResult){
// 领取成功更新mysql
updateMysql(uid,activityId,giftId);
}else{
// 领取成功更新redis
deleteRedis(uid,activityId,giftId);
return "已经领过/领取失败";
}
}else{
return "已经领过/领取失败";
}
}catch (Exception e){
// 记录日志
logHelper.log(e);
return "调用领券系统失败,请重试";
}
}
}
return "领取失败,活动不存在";
}

下面,我们就专门讲解一下setnxsetnx可以用作分布式锁,但是这个场景并不是分布式锁的一个较好的实践,因为每个用户的key都是不一样的,我们主要是防止同一个用户恶意领取setnx本身是一个原子操作,可以保证多个线程只有一个能拿到锁,能返回true,其他的都会返回false

但是上面的做法,没有设置过期时间,在生产上一般是不可以这么使用。不设置过期时间的key多了之后,redis服务器很容易内存打满,这时候不知道哪些是强制依赖的,只能扩容,从代码层面去清理,如果直接清理不常用的,也很难保证不出事。(基本不允许这么干,除非是基础数据,跟着服务器启动,写入redis的,不会变更的,比如城市数据,国家数据等等,当然,这些也可以考虑在本地内存中实现)

如果在上面的代码中,加入超时时间,假设是一个月或者半年,流程变成这样:

设置key的超时时间使用expire,但是这样还有缺陷么?

redis 2.6.12之前,setnxexpire都不是原子操作,也就是很有可能在setnx成功之后,redis当季,expire设置失败,也就不会有超时时间了。虽然这个影响在当前业务不是很大,但是还是一个小缺陷。

Redis2.6.12以上版本,可以用set获取锁,set包含setnxexpire,实现了原子操作。也就是两步要么一起成功,要么一起失败。

除此之外,上面的流程可能还存在的一个问题,是请求C服务的时候出现超时,然后删除key,恰好这个时候redis有问题,删除失败了,这个key就永远存在了。表现在业务上,就是A用户点击了领取,领取失败了,但是后面再怎么点,都是已经领取的状态了。

那这种现象怎么优化呢?

这种情况,其实已经是很少见的情况,按照我们当前的业务场景也看,就是当前的用户,redis记录了它已经领取过了,但是由于接口的失败,成功之后还没将mysql/其他数据库更新,两个数据库不一致了。

我能想到的一个方法,就是再删除失败的时候,告警,并且将业务相关的数据记录下来,比如keyuid等等,针对这部分数据,做一次补发,或者手动删除key。

或者,启动一个定时任务或者lua脚本,去判定redis和数据库不一致的情况,但是切记不要全部查询,应该是隔一段时间,查询最后增加的部分,做一个校验以及相应的处理。枚举key是十分耗时的操作!!!

setnx 除了解决上面的问题,还可以应用在解决缓存击穿的问题上。

譬如现在有热点数据,不仅在mysql数据库存储了,还在redis中存了一份缓存,那么如果有一个时间点,缓存失效了,这时候,大量的请求打过来,同时到达,缓存拿不到数据,都去数据库取数据,假设数据库操作比较耗时,那么压力全都在数据库服务器上了。

这个时候所有的请求都去更新数据,明显是不合适的,应该是使用分布式锁,让一个线程去请求mysql一次即可。但是为了避免死锁的情况,如果超时,得及时额外释放锁,要不可能请求mysql都失败了,其他线程又拿不到锁,那么数据就会一直为null了。

可以使用以下的命令:

SETNX lock.foo <current Unix time + lock timeout + 1>

关于这个场景下的setnx先讲到这里,后面再讲讲分布式锁相关的知识。

【刷题笔记】

Github仓库地址:https://github.com/Damaer/codeSolution

笔记地址:https://damaer.github.io/codeSolution/

【作者简介】

秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指Offer,LeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。

2020年我写了什么?

开源刷题笔记

平日时间宝贵,只能使用晚上以及周末时间学习写作,关注我,我们一起成长吧~

【实战问题】-- 并发的时候分布式锁setnx细节的更多相关文章

  1. redis 不可重入分布式锁(setNx()和getset()方法实现)

    通常如果在单机环境,使用synchronized或juc ReentrantLock 实现锁机制,但如果是分布式系统,则需要借助第三方工具实现,比如redis.zookeeper等.redis为单进程 ...

  2. Redis_redis分布式锁-SETNX

    因业务需要使用了redis的SETNX来实现分布式锁. 描述:Redis有一系列的命令,特点是以NX结尾,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXis ...

  3. redis分布式锁-SETNX实现

    Redis有一系列的命令,特点是以NX结尾,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists.这系列的命令非常有用,这里讲使用SETNX来实现分布式锁 ...

  4. (转)redis分布式锁-SETNX实现

    Redis有一系列的命令,特点是以NX结尾,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists.这系列的命令非常有用,这里讲使用SETNX来实现分布式锁 ...

  5. Redis分布式锁—SETNX+Lua脚本实现篇

    前言 平时的工作中,由于生产环境中的项目是需要部署在多台服务器中的,所以经常会面临解决分布式场景下数据一致性的问题,那么就需要引入分布式锁来解决这一问题. 针对分布式锁的实现,目前比较常用的就如下几种 ...

  6. Redis 分布式锁|从青铜到钻石的五种演进方案

    缓存系列文章: 缓存实战(一):20 图 |6 千字|缓存实战(上篇) 缓存实战(二):Redis 分布式锁|从青铜到钻石的五种演进方案 缓存实战(三):分布式锁中的王者方案 - Redisson 上 ...

  7. Redis的n种妙用,分布式锁,分布式唯一id,消息队列,抽奖……

    介绍 redis是键值对的数据库,常用的五种数据类型为字符串类型(string),散列类型(hash),列表类型(list),集合类型(set),有序集合类型(zset) Redis用作缓存,主要两个 ...

  8. Java分布式锁看这篇就够了

    ### 什么是锁? 在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量. 而同步的本质是通过锁来实现的 ...

  9. Java分布式锁的三种实现方案(redis)

    方案一:数据库乐观锁 乐观锁通常实现基于数据版本(version)的记录机制实现的,比如有一张红包表(t_bonus),有一个字段(left_count)记录礼物的剩余个数,用户每领取一个奖品,对应的 ...

  10. 基于 Redis 做分布式锁

    基于 REDIS 的 SETNX().EXPIRE() 方法做分布式锁 setnx() setnx 的含义就是 SET if Not Exists,其主要有两个参数 setnx(key, value) ...

随机推荐

  1. Serilog文档翻译系列(二) - 设置AspNetCore应用程序

    Serilog 日志记录适用于 ASP.NET Core.此包将 ASP.NET Core 的日志消息通过 Serilog 进行路由,使你可以将有关 ASP.NET 内部操作的信息写入与应用程序事件相 ...

  2. Round #2022/11/26

    问题 B:染色 题目描述 有长度为 \(n\) 的一个序列,编号为 \(1\) 到 \(n\) ,现要对这些元素进行染色标记,若编号 \(i-j\) 为素数,且 \(1\le i < j \le ...

  3. fluent python-chap3-1

    class collections.OrderedDict([items]) 返回一个 dict 子类的实例,它具有专门用于重新排列字典顺序的方法. """ move_t ...

  4. SQL server temporal table 学习笔记

    refer: https://blog.csdn.net/Hehuyi_In/article/details/89670462 https://docs.microsoft.com/en-us/sql ...

  5. 智慧矿山IT智能运维自动化解决方案

    矿山企业是国民经济中的重要组成部分,其资源开发和产业链条中涉及的环节与过程非常繁琐和复杂.随着我国矿山企业规模逐年扩大,为了提高其生产效率和降低其生产成本,信息化.数字化建设成为当下矿山企业发展的重要 ...

  6. auto` 作为返回值类型的一些限制

    在 C++ 中,auto 作为返回值类型有一些限制,这与类型推导的方式和时机有关. 虽然在很多场景下 auto 可以简化代码,但它不能直接用于函数返回类型,这是因为在编译时类型推导的机制不同于局部变量 ...

  7. opengl在编译的过程中,glad使用

    我在编译的过程中,遇到:无法找到 -lglad这个错误.最后才发现对于glad的使用不能用-lglad.因为我们通过glad的在线服务可以得到一些文件,其中glad.c文件我们是需要放在我们的项目下面 ...

  8. 如何理解 .Net 中的 委托

    // 委托 // 一种方法的声明和定义,也就是方法的占位符 // 一般使用在 参数 和 属性中 int Add(int a,int b) { return a + b; } // 定义委托的三种方法 ...

  9. text-align的对齐方式

    text-align的6种取值 left:左对齐 right:右对齐 center:居中 start:如果内容方向是左至右,则等于left,反之则为right. end:如果内容方向是左至右,则等于r ...

  10. webpack配置 alias用于方便引用文件

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...