Redis 不谈缓存和队列(redis缓存的使用逻辑)
mhr18 2024-11-05 10:23 47 浏览 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,我们可以解决两个用户同时操作一个数据流程的时候:
- 当用户要购买此商品的时候,我们可以创建一个key,流程完成后,并改变此商品的出售状态(此商品已卖完),删除key。
- 其他用户在此想购买此商品的时候,去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。
- 当创建秒杀商品A的时候,创建redis对象:set pA 10 和 数据库秒杀数据: pA 10
- 当1w用户进行秒杀时,调用redis: INCRBY pA -1 (每次步长都为-1,然后返回扣减之后的库存量) 和 在redis中添加购买人与购买时间: hset pA 1 1478584205(hset 产品 用户 秒杀时间)
- 如果get pA < 0了,那么就不让用户购买了,echo “秒杀已卖完”
- 当用户付款后,扣减数据库中秒杀库存数,删除redis hdel pA 1
- 每一段时间都跑一下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处了缓存和队列以后还有哪些实际的应用场景,文章仅起到抛砖引玉的作用,文章中的设计都是使用伪代码进行书写的,读者只需查看逻辑和处理方式,不必过多注意细节。
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)