为什么Redis执行速度快
- 纯内存操作。Redis将数据存储在内存中,并且数据的读写操作也是在内存上进行的,这种读写方式远远快于在对磁盘的数据操作。
- I/O多路复用。同时监听多个客户端连接,只有当网络事件发生时才会进行实际的I/O。这样有效的利用了CPU,减少了无谓的等待。
- 高效的数据结构。Redis采用了哈希表、有序集合等数据结构,有效的提高了数据的读写效率。
Redis如何保证线程安全?
Redis在6.0版本前,所有客户端的请求处理、命令执行和数据读写都是在一个主线程中完成的,这种设计目的是为了防止多线程环境下锁竞争和上下文切换带来的性能开销,从而在保证线程安全性的同时提高性能。Redis在6.0版本后,采用IO多路复用监听多个客户端的连接,并且使用多个工作线程处理连接请求,但是指令执行仍然由单个主线程完成,所以不会存在线程安全问题。
在实际工作中,Redis实现了哪些业务场景?
- 缓存。Redis提供了键过期功能和键淘汰策略。
- 排行榜。Redis提供的有序集合(zset)能方便的实现排行榜功能。
- 分布式会话。在集群模式下,一般会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。
- 分布式锁。在高并发场景下,可以利用Redis的setnx功能来编写分布式锁。
Redis常用数据类型
- 字符串。String 是一种二进制安全的数据类型,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。
- 应用场景。
- 需要存储常规数据的场景。
- 举例:缓存 Session、Token、图片地址、序列化后的对象(相比较于 Hash 存储更节省内存)。
- 相关命令:
SET、GET。
- 需要计数的场景。
- 举例:用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数。
- 相关命令:
SET、GET、INCR、DECR。
- 需要存储常规数据的场景。
- 应用场景。
- 列表。Redis 中的 List 其实就是链表数据结构的实现。
- 应用场景
- 信息流展示。
- 举例:最新文章、最新动态。
- 相关命令:
LPUSH、LRANGE。
- 信息流展示。
- 应用场景
- 哈希。Redis 中的 Hash 是一个 String 类型的 field-value(键值对) 的映射表,特别适合用于存储对象,后续操作的时候,你可以直接修改这个对象中的某些字段的值。
- 应用场景。
- 对象数据存储场景。
- 举例:用户信息、商品信息、文章信息、购物车信息。
- 相关命令:
HSET(设置单个字段的值)、HMSET(设置多个字段的值)、HGET(获取单个字段的值)、HMGET(获取多个字段的值)。
- 对象数据存储场景。
- 应用场景。
- 集合。Redis 中的 Set 类型是一种无序集合,集合中的元素没有先后顺序但都唯一,有点类似于 Java 中的
HashSet。当你需要存储一个列表数据,又不希望出现重复数据时,Set 是一个很好的选择,并且 Set 提供了判断某个元素是否在一个 Set 集合内的重要接口,这个也是 List 所不能提供的。你可以基于 Set 轻易实现交集、并集、差集的操作,比如你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。这样的话,Set 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。- 应用场景。
- 需要存放的数据不能重复的场景。
- 举例:网站 UV 统计(数据量巨大的场景还是
HyperLogLog更适合一些)、文章点赞、动态点赞等场景。 - 相关命令:
SCARD(获取集合数量) 。
- 举例:网站 UV 统计(数据量巨大的场景还是
- 需要获取多个数据源交集、并集和差集的场景。
- 举例:共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集) 等场景。
- 相关命令:
SINTER(交集)、SINTERSTORE(交集)、SUNION(并集)、SUNIONSTORE(并集)、SDIFF(差集)、SDIFFSTORE(差集)。
- 需要存放的数据不能重复的场景。
- 应用场景。
- 有序集合。Sorted Set 类似于 Set,但和 Set 相比,Sorted Set 增加了一个权重参数
score,使得集合中的元素能够按score进行有序排列,还可以通过score的范围来获取元素的列表。有点像是 Java 中HashMap和TreeSet的结合体。- 应用场景。
- 需要随机获取数据源中的元素根据某个权重进行排序的场景。
- 举例:各种排行榜比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。
- 相关命令:
ZRANGE(从小到大排序)、ZREVRANGE(从大到小排序)、ZREVRANK(指定元素排名)。
- 需要存储的数据有优先级或者重要程度的场景 比如优先级任务队列。
- 举例:优先级任务队列。
- 相关命令:
ZRANGE(从小到大排序)、ZREVRANGE(从大到小排序)、ZREVRANK(指定元素排名)。
- 需要随机获取数据源中的元素根据某个权重进行排序的场景。
- 应用场景。
- BitMap(位图)。Bitmap 存储的是连续的二进制数字(0 和 1),通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 Bitmap 本身会极大的节省储存空间。你可以将 Bitmap 看作是一个存储二进制数字(0 和 1)的数组,数组中每个元素的下标叫做 offset(偏移量)。
- 应用场景。
- 需要保存状态信息(0/1 即可表示)的场景。
- 举例:用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
- 相关命令:
SETBIT、GETBIT、BITCOUNT、BITOP。
- 需要保存状态信息(0/1 即可表示)的场景。
- 应用场景。
- HyperLogLog(基数统计)。HyperLogLog 是一种有名的基数计数概率算法 ,基于 LogLog Counting(LLC)优化改进得来,并不是 Redis 特有的,Redis 只是实现了这个算法并提供了一些开箱即用的 API。
- 应用场景。
- 数量量巨大(百万、千万级别以上)的计数场景。
- 举例:热门网站每日/每周/每月访问 ip 数统计、热门帖子 uv 统计。
- 相关命令:
PFADD、PFCOUNT。
- 数量量巨大(百万、千万级别以上)的计数场景。
- 应用场景。
- Geospatial (地理位置)。Geospatial index(地理空间索引,简称 GEO) 主要用于存储地理位置信息,基于 Sorted Set 实现。通过 GEO 我们可以轻松实现两个位置距离的计算、获取指定位置附近的元素等功能。
- 应用场景。
- 需要管理使用地理空间数据的场景。
- 举例:附近的人。
- 相关命令:
GEOADD、GEORADIUS、GEORADIUSBYMEMBER。
- 需要管理使用地理空间数据的场景。
- 应用场景。
Redis分布式锁实现
分布式锁的作用:分布式锁可以管理 多个系统对资源的同步控制。
我们首先希望在同一时间只能有一个客户端持有锁:
- setnx(set if not exists)指令。
- 使用。setnx key value。
- 问题。在代码执行一半时,特殊原因导致程序崩溃而没有执行解锁操作,导致死锁。
- setnx ex/px指令。
- set key value nx px 3000。
- 问题。不能解决锁续期和锁重入。
- Lua脚本。可以解决锁重入,但是不能解决锁续期问题。
- Redisson框架。可以同时解决锁重入和锁续期问题,且实现简单。
缓存击穿
高并发时,一个key是热点数据,需要应对大量并发请求,这时key如果失效,在失效的瞬间会有大量请求跨国redis直接请求数据库,并将查询到的值回写到缓存,这会导致性能的大幅下降,这种现象就叫缓存击穿。
解决方案:
- 将过期时间设置为永不过期。
- 方案一。将可能的热门商品的key值设置成永不过期。
- 方案二。日均访问量达到某个阈值时将key设置成永不过期。
- 方案三。用定时任务对访问量达到某个阈值的数据进行续期。
- 加锁排队。使用分布式锁或者双重检查锁对数据库请求代码进行加锁,使同时只会有一个线程对数据库放松请求。
缓存雪崩
缓存集中过期或缓存服务器宕机,导致大量请求访问数据库,造成数据库瞬间压力过大而造成宕机,这种现象称为缓存雪崩。
解决方案:
- (加锁排队)随机失效时间(针对缓存集中过期)。
- redis集群(针对缓存服务器宕机)。
- 缓存预热,即在上线前,根据当天情况,将热数据直接加载到缓存。
缓存穿透
当key值在数据库和缓存中都不存在,导致每次请求都回去查询数据库。这种场景可能涉及到攻击者对不真实数据的大量攻击请求,可能会导致数据库压力过大从而宕机。
解决方案:
- 业务层参数校验。可以根据请求参数进行校验,对于明显错误的参数,直接拦截返回。
- 缓存空对象。
- 布隆过滤器。
布隆过滤器
布隆过滤器是一种能降低查询的时间复杂度又能减小存储的空间复杂度的一种数据结构。
- 优点
- 数据检索性能快。
- 空间占用小。
- 缺点
- 存在误判性。
- 适用场景。
- 大数据量。
- 能容忍一定误判性。
- 过滤流程。
- 后端应用到redis(布隆过滤器集成于redis中)中查找,判断是否存在key。
- 如果存在,则读取redis中的缓存数据。如果不存在,则拦截无效请求。
- 实现原理。布隆过滤器的底层实现是bit位数组。
- 除了非负整形数据之外的存储逻辑。计算hash值,通过hash值取模来获取存储的下标地址,将对应下标地址的位值置为1,因为存在hash冲突,所以存在误判现象。
- 降低误判的概率。可以通过这几种方式来降低误判发生的概率。
- 增大bitmap的存储长度。
- 多次hash取交集及再hash。
redis和数据库如何保证最终一致性
在redis作为缓存和数据库交互过程中存在两种一般操作:
读取数据:
尝试查询缓存获取数据,如果缓存命中,则返回数据。如果没有命中,则查询数据库,获取到数据后将其存储到redis中并设置过期时间,最后将数据返回给客户端。
写数据:
写数据有四种情况:
- 先更新缓存,再更新数据库
- 先删除缓存,再更新数据库
- 先更新数据库,再更新缓存
- 先更新数据库,再删除缓存
有两种解决方案:
延迟双删
延迟双删的写操作是先删除缓存再更新数据库的解决方案,存在数据不一致的场景如下(写读操作并发):线程一做写数据操作,会先删除redis缓存数据,再更新数据库数据,如果在写数据库时发生了网络故障,线程二这时正在读取数据,则会先在redis中查询数据,发现没有,就会去数据查询数据,因为这时线程一还未把修改后的数据写入数据库,所以线程二读取的数据为旧数据,并会将旧数据更新到redis中返回给客户端,在这个老数据过期自动删除之前,读操作获取到的数据将一直是老数据,这就出现了数据不一致的情况。解决方案就是在线程一更新完数据库后,过一小段时间(500ms)再让它进行一次redis的数据删除。
为什么需要延迟删除。为了避免读到旧数据的线程将第二次删除操作覆盖掉。
旁路缓存模式
旁路缓存的写操作是先更新数据库再删除缓存的解决方案,存在数据不一致的场景如下(写读操作并发):线程一在更新完数据库后,在删除缓存时失败了,则此时其他线程读到的缓存数据都是旧数据。解决方案就是删除缓存重试机制。
删除缓存重试机制
删除重试机制可以这么实现,当删除失败时将删除失败的key异步发送给MQ,系统监听MQ系统,当有删除失败的消息则再次尝试删除对应缓存,这样的缺点是业务代码和mq的操作耦合度较高。
比较好的方法是采用canal框架进行解耦:
canal会监听MYSQL的binlog日志,当出现数据库的修改时,canal会发送数据修改的通知给canal客户端,canal客户端会尝试延迟删除redis,当删除失败canal客户端会尝试发送一条消息给MQ,MQ会异步通知订阅者进行删除重试。
为什么延迟双删中写操作不是更新缓存而是删除缓存
避免并发场景下的数据不一致:
- 更新缓存是“写值”操作,在高并发下若多个线程同时修改数据,由于线程调度不可控,可能出现旧值覆盖新值的情况,导致缓存与数据库不一致。
- 删除缓存是“清空”操作,无论执行多少次,最多只是造成缓存缺失,不会引入脏数据。后续读请求会自动从数据库加载最新值并回填缓存,确保一致性。
降低系统开销,提升性能:
- 缓存数据往往来源于复杂查询或聚合计算(如关联多张表)。每次写操作都重新计算并更新缓存,会造成大量无效计算,尤其当缓存很少被读取时。
- 删除缓存采用懒加载(Lazy Loading) 思想:仅在真正需要时才重新计算并加载,节省资源。
简化一致性保障逻辑:
- 删除缓存后,数据库成为唯一可信数据源,读操作始终能获取最新数据,即使缓存未及时重建,也不会返回脏数据。
- 若更新缓存失败(如网络异常、缓存节点宕机),需处理分布式事务;而删除缓存失败可通过异步重试机制(如消息队列)补偿,实现最终一致性。
延迟双删的适用场景要求:
- 在两次删除之间被其他请求写入旧值,抵消双删效果。
- 增加复杂度,违背“简单、可靠”原则。
Redis部署架构
单机模式
单机模式的读写操作都在一台机器上进行,单点故障整个服务变为不可用。
优点:部署简单
缺点:应对不了单点故障和高并发场景
主从模式
一主多从的模式,其中主节点负责处理写操作,从节点负责处理读操作,实现了读写分离和负载均衡。
主从同步策略:在slave和master刚连接时会进行全量同步,全量同步结束后开始增量同步,在后续增量同步时如果增量同步失败,slave就会再次进行全量同步。
故障修复策略:故障节点是从节点时,能正常运行;如果故障节点是主节点则需要人工手动将其中一个从节点转换为主节点。
优点:相对单实例提高了服务的可用性和读取性能,避免了单点故障的同时实现了负载均衡。
缺点:主节点故障需要手动将从节点转换为主节点,效率低下且需人工介入。
哨兵模式
哨兵模式继承了主从模式,不同的是会在独立的redis实例集群上运行哨兵进程,用来监控主从节点的运行状态,当主节点挂掉时,会基于预设的投票机制从从节点中选出一个节点升级为主节点。
主观下线和客观下线:哨兵会对主从节点进行心跳检测,如果节点响应超时,sentinel会将节点标记为主管下线。当一个哨兵节点发现某个节点处于主观下线状态时,该哨兵节点会向其他哨兵节点询问当前节点状态,如果超过quorum个节点认为当前节点主管下线时,则sentinel会将当前节点标记为客观下线。
优点:提高了主从模式的可用性,能自动进行故障转移。
cluster模式
集群模式是Redis官方提供的分布式解决方案,其采用无中心架构,通过数据分片和主从复制实现高可用性与水平扩展,核心机制是基于16384个哈希槽的无中心结构。
特点:
- 所有redis节点彼此互联,内部使用二进制协议优化传输速度和带宽
- 节点的fail是通过集群中超过半数的节点检测失效时才生效
- 客户端与redis节点直连,不需要中间代理层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
工作机制:
- 在Redis的每个节点上,都有一个插槽(slot),取值范围为0-16383
- 当我们存取key的时候,Redis会根据CRC16的算法得出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作
- 为了保证高可用,Cluster模式也引入主从复制模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点
- 当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点都宕机了,那么该集群就无法再提供服务了
总结
单机模式部署简单适合低流量简单项目场景,但是应对不了单点故障。主从模式能实现读写分离和负载均衡,但是当主节点故障时需要人工介入修复,无法进行自动故障修复。主从加哨兵模式实现了自动故障转移,但是无数据分片,缓存容量受限于单机内存,无法进行在线扩容。集群模式实现了分布式存储,能进行在线扩容,达到了高可用,但是运维和客户端实现较为复杂,批量操作支持性还做的不够好。
redis持久化机制
RDB持久化
rdb持久化会定期将内存中的数据集快照写入磁盘,每次都是从redis中生成一个快照进行数据的全量备份。
流程:rdb持久化方案进行备份时,redis会单独fork一个紫禁城来进行持久化,会将数据写入一个临时文件中,持久化完成后用来替换旧的RDB文件。整个持久化过程中,主进程不参与IO操作,这样能确保Redis服务的高性能,RDB持久化机制适合对数据完整性要求不高但追求高校回复的场景。
优点:
恢复数据效率高:RDB持久化是通过生成一个快照文件来保存数据,因此数据恢复速度非常快。
资源占用小:RDB文件是二进制格式的数据库文件,相对AOF文件来说,文件体积较小。
缺点:意外宕机时,上次快照之前的数据都会丢失。
AOF持久化
AOF持久化会将客户端所有写命令追加到AOF缓冲区中,缓冲区中的数据会根据Redis配置文件中配置的同步策略来同步到磁盘上的APOF文件中,同时当AOF文件达到重新策略的阈值时,Redis会对AOF日志文件进行重写,给文件瘦身。Redis服务重启时,会通过加载AOF日志文件来恢复数据。
优点:
可靠性:AOF持久化记录了每个写指令操作,因此在出现故障时,可以通过重新执行AOF文件来保证数据的完整性。
保留写命令历史:AOF文件是一个追加日志文件,可以用于回访过去的写操作。
缺点:
文件较大:由于记录了所有的写指令,所以AOF文件体积通常比RDB文件要大
恢复速度满:当需要通过AOF文件进行数据恢复时,需要执行整个AOF文件指令,恢复速度较慢
混合持久化
在AOF重写之前,RDB和AOF都是按照他们各自的持久化策略工作的。当AOF重写被触发时,混合持久化才开始发挥作用:将当前的数据集以RDB格式写入新AOF文件的顶部,然后追加新的命令到文件的末尾。
优点:结合了RDB持久化和AOF持久化的优点,既能快速恢复数据,又能保证近期的大部分数据修改不会丢失,持久化文件的大小也得到了控制。
Redis中的大Key
Redis没有显示定义大Key,它一般用来描述在存储和性能上可能带来问题的键,通常指代占用大量内存空间的键,例如大型字符串、列表、哈希表或集合。
典型的大KEY场景:
- Key本身的数据量过大:一个String类型的Key大小为5MB
- Key中的成员数过多:ZSET类型中某个Key的成员数量高达1W个
- Key中成员的数量过大:Hash类型中某个Key成员数量虽然只有1K,但是成员的Value总大小有100MB
大key引发的问题
- 内存占用过高。大key占用大量内存空间,可能导致Redis实例内存不足,影响其他键的存储和访问。
- 性能下降。对于大key的操作,如读取、写入、删除等,都会消耗更多的cpu时间和内存资源,进一步降低系统性能。
- 阻塞其他操作。某些对大key的操作可能会导致redis实例阻塞。例如,使用DEL命令删除一个大key时,可能会导致redis实例在一段时间内无法响应其他客户端请求,从而 影响系统的响应时间和吞吐量。
- 网络拥塞。每次获取大key产生的网络流量较大,可能造成机器或局域网的带宽被打满,同时波及其他服务。例如:一个大key占用空间是1MB,每秒访问1000次,就有1000MB的流量。
- 持久化问题。对大key惊醒持久化可能会导致备份和恢复操作变得困难和耗时。
- 数据倾斜。在redis集群中,大key可能导致数据在节点之间不均匀分布,影响负载均衡和集群性能。
大key产生的原因
- 大型数据结构存储。存储大型字符串、列表、哈希表或集合时,如果这些数据结构的大小超过了一定阈值,就会产生大key。
- 缓存滥用。将大量数据作为单个键的值进行缓存,而不是按需进行分解或分割,导致某些键变得特别大。
- 数据导入。从外部数据源导入数据时,如果数据量较大且以单个键存储,可能回到导致生成大key。
- 应用设计不佳。应用程序设计中未考虑到redis键的大小限制,或没有有效地处理大型数据的情况,导致产生大key。
- 数据积累。随着时间的推移,某些键可能会不断积累数据,导致其大小超出预期,从而成为大key。
如何快速找出大key
- SCAN命令。通过使用reids的scan命令,我们可以逐步遍历数据库中的所有key。结合其他命令识别出大key(如STRLEN、LLEN、SCARD、HLEN等)。scan命令的优势在于它可以在不阻塞redis实例的情况下进行遍历。
- bigkeys参数。使用redis-cli命令行客户端,连接redis服务时,加上–bigkeys参数,可以扫描每种数据类型中最大的key
- redis rdb tools工具。redis insight会给出分析报告,打印出成员长度和数量前n的key。
大key的优化方案
- 拆分成多个小key。降低单key的大小,读取可以用mget批量读取。
- 优化数据结构。使用String类型的时候,使用压缩算法减少value大小。或者是使用hash类型存储,因为hash类型底层使用了压缩列表数据结构。
- 设置合理的过期时间。为每个key设置合理的过期时间,以便在数据失效后自动清理,避免长时间积累成大key。
- 启用内存淘汰策略。启用redis的内存淘汰策略,例如LRU,以便在内存不足时自动淘汰最近最少使用的数据,防止大ke长时间占用内存。
- 数据分片。例如使用redis cluster将数据分散到多个redis实例,以减轻单个实例的负担,降低大key问题的风险。
- 删除大key。当大key不再使用时,使用unlink而不是del命令删除大key,因为unlink命令时del命令的异步版本,避免阻塞redis实例。
- 增加内存容量。如果经过以上优化仍无法解决大key导致的内存问题,可以考虑增加redis实例的内存容量,提升整体性能和稳定性。
redis对于memcached有哪些优势
- 数据类型更丰富。memcached仅支持value为string类型,而redis支持string、hash、list、set、sort set等,多以redis存储数据更灵活,更适用于复杂类型操作。
- 数据容量更大。memcached单value容量只有1M,而redis则最大支持512M。
- redis支持数据持久化。memcached只能作为缓存,没有可靠性支持,断电或服务关闭后数据就会丢失,而redis支持数据持久化,且在突发情况下能最大限度保证数据不丢失。
- redis支持复数指令的原子性操作。redis支持lua脚本,能实现复数指令的原子性操作,面对复杂的业务场景,能保证操作数据的原子性需求。
MySQL里有2000w数据redis中只存20w的数据,如何保证redis中的数据都是热点数据?
首先可以看到redis的空间时间上比mysql少的多,那么redis如何能够筛选出热点数据,这道题主要考察的时redis的数据淘汰策略,在redis4.0之后为我们提供了8种淘汰策略,主要是新增了LFU算法,真正意义上是5中,针对random、LRU、LFU是提供了两种不同数据范围的策略,一种是针对设置了过期时间的,一种是没有设置过期时间的。具体的五种策略分别是:
- noeviction选择这种策略则代表不进行数据淘汰,同时它也是redis中默认的淘汰策略,当缓存写满时redis就不再提供写服务了,写请求则直接返回失败。
- random随机策略分为两种。一种是volatile-random,这种从设置了过期时间的数据集中随机进行淘汰。另外一种是allkeys-random,这种是从所有数据集中进行随即淘汰。
- volatile-ttl时针对设置了过期时间的数据集,并按过期时间的先后顺序进行删除,越早过期的越先被删除。
- LRU策略也分了两种。一种是volatile-lru,从设置了过期时间的数据集中基于最近最少使用算法进行淘汰,这样能保证未设置过期时间的数据不会被选中淘汰。另一种是allkeys-lru,从全体数据中,基于最近最少使用算法进行淘汰。使用场景包括:
- 数据库查询缓存。对于数据库查询结果,LRU可以保证最近查询的结果被保留,从而提高查询效率。
- 会话管理:在web应用中,用户的会话信息可以通过LRU进行管理,确保活跃的会话被保留。
- 游戏排行:在游戏应用中,LRU可以保留玩家最近查看的排行榜数据。
- LFU则是遵循数据在一段时间被访问的次数越少,那么在未来被访问的几率就越小的思想来淘汰数据,同样分为volatile-lfu和allkeys-lfu两种。LFU算法适合具有长尾效应的数据集。
高并发场景下我们如何保证幂等性
一个幂等操作的特点是任意多次执行锁产生的影响均与一次执行的影响相同。经常出现在实际业务中的幂等场景有:
- 电商场景中用户因网络问题多次点击导致重复下单问题。
- MQ消息队列的重复消费
- RPDC中的超时重试机制
那么如何解决幂等性问题呢?有如下方案:
- 数据库唯一主键实现幂等性。例如Oracle中将SYS_GUID()生成的ID作为主键使用。
- 乐观锁实现幂等性。在表中增加版本号标志,只有版本号标识一致才更新成功。
- 分布式锁。分布式排他锁能保证同一时间只有一个线程执行同步代码快的代码,但是会降低系统的吞吐量,需要注意控制锁的粒度以提高程序的执行性能。
- 获取token。
- 服务端提供获取Token的接口,请求前客户端调用接口获取Token。
- 然后将该穿存入redis数据库中,以该Token作为redis的键(注意设置过期时间)。
- 将Token返回到客户端,在执行业务请求带上该Token。
- 服务端接收到请求后根据Token到redis中查找该key是否存在(注意原子性)。
- 如果存在就将该key删除,然后正常执行业务逻辑。如果不存在就抛出异常,返回重复提交的错误信息。

发表回复