百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Redis 不谈缓存和队列(redis缓存的使用逻辑)

mhr18 2024-11-05 10:23 36 浏览 0 评论

导语

redis作为目前最流行的k-v存储系统之一,在实际的应用场景中如何使用Redis就变的非常重要了。 由于大家都熟知Redis可以用于缓存/队列的使用,并且网上有很多讲解内容,故在此不介绍Redis的缓存/队列使用场景,本文更偏重于Redis的其他使用场景。

分布式锁

应用场景

在电商系统中,为了推荐自己的品牌和吸引用户量,那么会推出一个产品,这个产品只能被一个用户购买,如果一个用户正在购买时,其他用户点击购买的时候,则告知该用户,商品已经售出。

场景分析

假设购买流程为:1.用户看到商品,2.用户点击购买,3.用户使用支付宝或微信支付,4.用户购买成功

目前有2个用户:A和B

场景并发现象:

用户A:1.用户看到商品,2.用户点击购买

用户B:1.用户看到商品,2.用户点击购买

此时用户A和用户B都进入到了第三步:3.用户使用支付宝或微信支付

那可能就会出现并发问题,两个用户都同时购买了1个商品。

解决方案

根据以往经验,可知,我们如上流程可以通过锁来进行控制,传统的数据库锁的方式在此不进行介绍了,来看一下redis是如何实现的。

使用redis,我们可以解决两个用户同时操作一个数据流程的时候:

  1. 当用户要购买此商品的时候,我们可以创建一个key,流程完成后,并改变此商品的出售状态(此商品已卖完),删除key。
  2. 其他用户在此想购买此商品的时候,去redis中读取这个key,如果key存在,那么就证明此商品正在被一个用户购买(这个用户正在购买的流程中),告知该用户,此商品已卖完


这是我提出来的一个大概流程,但仔细思考起来,里边还有很多小细节,那我们来慢慢分析。

版本1

用户A:1.用户看到商品 -> 2.用户点击购买 -> set buying A (设置一个key的名称是buying,value是A) -> 3.用户使用支付宝或微信支付 -> 4.用户购买成功 -> 更改商品出售状态 -> delete buying (删除redis的key)

用户B:1.用户看到商品 -> 2.用户点击购买 -> get buying -> echo “此商品已卖完”

上边流程如果看懂了后,我们思考一个问题,当用户A操作到3.用户使用支付宝或微信支付时,突然用户由于一些突发情况,然后页面退出了,那么由于流程没有完成,所以这个redis中的key(buying)将一直存在,也就是说商品没有卖出,但是其他人再也不能买了。

版本2

由于要解决以上问题,我们需要把这样的一个购买时间设置一个有效期,比如说如果用户在30分钟内都未购买成功,那么不管是什么原因,我都应该让这个redis中的key失效,让用户此次购买的行为失效。


也就是说,我们应该把buying设置一个有效期

用户A:1.用户看到商品 -> 2.用户点击购买 -> set buying A (设置一个key的名称是buying,value是A) -> set expire 30-> 3.用户使用支付宝或微信支付 -> 4.用户购买成功 -> 更改商品出售状态 -> delete buying (删除redis的key)

用户B:1.用户看到商品 -> 2.用户点击购买 -> get buying -> echo “此商品已卖完”

这个版本2中的用户A设置了一个key后,即使中途真的发生什么意外导致退出后,依然在指定时间后,其他用户可以继续购买。

那是否已经觉得这个方案很完美了呢?其实不然,我们把焦点放到set buying A(在redis设置key)的这点。

用户是这样操作的:

用户A:1.用户看到商品 -> 2.用户点击购买 -> get buying

用户B:1.用户看到商品 -> 2.用户点击购买 -> get buying

此时由于用户A和用户B同时点击购买(2.用户点击购买) 然后去读取buying,可能因为网络延迟问题,导致了同时都读取了buying,但是两个人读取buying这个key是不存在的,会产生后边的流程:

用户A:1.用户看到商品 -> 2.用户点击购买 -> get buying -> set buying A

用户B:1.用户看到商品 -> 2.用户点击购买 -> get buying -> set buying B

不管用户A还是用户B最终设置了buying这个redis的key值,依然产生了并发问题,结果肯定不是我们想要的。

版本3

我们知道redis本身是指令级别的,不管是使用JRdis还是spring提供的redisTemplate,我们都无法改变这个行为,那如果解决版本2中出现的问题呢?

redis中有一个指令是:setnx

Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for "SET if Not eXists".

返回值是这样规定的:

Integer reply, specifically: 1 if the key was set 0 if the key was not set

@see:http://redis.io/commands/setnx

这个命令可以当我们设置一个key的时候,判断这个key是否已经有值,如果没有值,则设置这样的key-value,并返回1,如果有值则不设置,直接返回0。

有了这个指令就很好的解决了版本2中的问题

用户A:1.用户看到商品 -> 2.用户点击购买 -> setnx buying A (设置一个key的名称是buying,value是A) -> set expire 30-> 3.用户使用支付宝或微信支付 -> 4.用户购买成功 -> 更改商品出售状态 -> delete buying (删除redis的key)

用户B:1.用户看到商品 -> 2.用户点击购买 -> get buying -> echo “此商品已卖完”

版本4

以上的版本3已经可以帮我们完成的差不多了。、

但是还有一点就是如果当用户A在redis中设置值的时候,突然掉线了,设置失效指定没有发送,那么其他用户依然再也不能买这个商品了。

解决方案是使用pipe管道将setnx buying A和set expire 30 放到一起,一起发送给redis.

小结

以上便是redis作为分布式锁的一个应用场景,笔者提到的本案例只起到抛砖引玉的作用,读者可以根据自己的业务需求,再动态添加redis的一些事物等高级功能。

秒杀防超卖

应用场景

有一个秒杀商品,秒杀库存为10,在12:00的时候有1w用户进行点击购买,如何防止超卖

场景分析

例如有一秒杀产品A,1w用户在同一秒钟进行抢购,如果每一个用户在秒杀时,都去读取秒杀库存数,判断未超买后,再去增加占用库存数,那么这样的并发问题是显而易见的。

当然数据操作还有一个就是使用表的行锁进行控制,但是笔记认为这个从用户体验来讲,对于这个场景来说,是不可取的。

由于redis的读写高性能的特点,我们将使用redis去处理这样的秒杀场景。

解决方案

假设产品A,秒杀库存10。

  1. 当创建秒杀商品A的时候,创建redis对象:set pA 10 和 数据库秒杀数据: pA 10
  2. 当1w用户进行秒杀时,调用redis: INCRBY pA -1 (每次步长都为-1,然后返回扣减之后的库存量) 和 在redis中添加购买人与购买时间: hset pA 1 1478584205(hset 产品 用户 秒杀时间)
  3. 如果get pA < 0了,那么就不让用户购买了,echo “秒杀已卖完”
  4. 当用户付款后,扣减数据库中秒杀库存数,删除redis hdel pA 1
  5. 每一段时间都跑一下job去查询一下对于秒杀商品pA下单还没有付款的人,超过一段时间如果不付款则认为用户不想秒杀,则归还redis库存:INCRBY pA 1

以上步骤中,第5步,可以根据自己的业务而定。


小结

秒杀防超卖的方式有很多,以上我提供了一个redis的思考方向,其实实际生产环境,更应该考虑限流、缓存等操作。

对象关系存储

应用场景

需要开发一个客服系统,此系统要求实时监控用户的登录和登出状态,并且将用户快速分配给客服。

场景分析

因为客服系统会基于websock进行通讯,所以有可能发生断网,掉线等情况,但是却要求客服和用户的状态都是实时监控和记录的。

故采用redis的存储方式解决该该应用场景出现的问题。

redis 主要应该记录用户信息,客服信息,以及两者之间的关系:用户-客服 1-1 客服-用户 n-1

所以这里边我们用到了redis中的hash结构,set结构。

解决方案

用户设计

normaluser:(普通用户 hash)
用户1:
normaluser:1 status 1
normaluser:1 nickname admin
normaluser:1 customerid 1
用户2:
normaluser:2 status 1
normaluser:2 nickname admin2
normaluser:2 customerid 1


redis脚本:
hset normaluser:1 status 1
hset normaluser:1 nickname admin
hset normaluser:1 customerid 1
hset normaluser:2 status 1
hset normaluser:2 nickname admin2
hset normaluser:2 customerid 1

用户设计

customer:(客服)
customer:1 status 1
customer:1 nickname aa
customer:2 status 1
customer:2 nickname bb


redis脚本:
hset customer:1 status 1
hset customer:1 nickname aa
hset customer:2 status 1
hset customer:2 nickname bb

场景1:客服登录

1)认证客服登录(认证方式可使用OAuth认证或普通用户名名认证)
2)获取客服的基本信息(如客服唯一标识位:1 和昵称:aa 并设定登录状态为登录状态:1),存放redis:
 hset customer:1 status 1
 hset customer:1 nickname aa
3)把登录后的客服放入redis中(此步骤是为了方便查询所有客服)
 sadd kf 1
 注意:客服退出后可以使用:sremove sadd kf 1,但是如果是客服掉线的情况下是否还可以转移给掉线的客服

场景2:用户登录

1)认证用户登录(目前方式使用通过appid可以直接登录,不验证用户信息,但此处需要考虑用户生成的唯一id,保证每次登录都能保证是一个用户,可能需要配合接口调用)
2)获取用户的基本信息并且通过算法分配客服(如用户标志位为:1 和昵称admin 并设定登录状态为登录状态:1 并且被分配到的客服id:1),存放redis:
 hset normaluser:1 status 1
 hset normaluser:1 nickname admin
 hset normaluser:1 customerid 1
3)存放客服对应用户的关系(1-n):
 hset customerrel:1 normaluser:1 sessionid

业务与程序需求:

1)查询所有在线客服( require update ):
 i)smembers kf
 ii) 通过上一步获取的客服id(比如为1,2,3),然后通过客服实体查询
 hget customer:1 status
 hget customer:1 nickname
2)获取某客服下的所有用户(如 客服id为1):
 hkeys customerrel:1 =>可以获取所有普通用户的id集合(如:normaluser:1,normaluser:2,normaluser:3)
3)获取某普通用户对应的客服(如 普通用户id为1):
 hget normaluser:1 customerid =>可以获取客服的id,比如1
4)客服进行转接操作(如将用户1从客服1转到客服2):
 hset normaluser:1 customerid 2
 hdel customerrel:1 normaluser:1
 hset customerrel:2 normaluser:1 1

小结

客服系统的设计使用到了hash存储方式和set存储方式,使用这两个存储方式更大的有点则是他们各自的特性。

  • 在考虑设计redis做存储的时候,建议优先考虑hash存储。
  • redis中string的方式大多用于配置或变动不大的序列化对象。
  • redis中set的方式,主要在于集合的运算。
  • redis中的list则用于队列使用。


总结

本文只从redis作为分布式锁,秒杀并发方案和对象存储三方面来讲解redis处了缓存和队列以后还有哪些实际的应用场景,文章仅起到抛砖引玉的作用,文章中的设计都是使用伪代码进行书写的,读者只需查看逻辑和处理方式,不必过多注意细节。

相关推荐

Redis合集-使用benchmark性能测试

采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...

Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好

最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...

redission YYDS(redission官网)

每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...

从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道

2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...

SpringBoot系列——实战11:接口幂等性的形而上思...

欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...

如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享

如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...

Java面试题: 项目开发中的有哪些成长?该如何回答

在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...

互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?

你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...

每日一题 |10W QPS高并发限流方案设计(含真实代码)

面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...

Java面试题:服务雪崩如何解决?90%人栽了

服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...

面试题官:高并发经验有吗,并发量多少,如何回复?

一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...

瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪

“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...

其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。

其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...

PHP技能评测(php等级考试)

公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...

你的简历在HR眼里是青铜还是王者?

你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...

取消回复欢迎 发表评论: