Java面试准备之Redis系列二

hresh 574 0

Java面试准备之Redis系列二

Redis 持久化机制

Redis 是基于内存的,如果不想办法将数据保存在硬盘上,一旦 Redis重启 (退出/故障),内存的数据将会全部丢失。 此时我们需要持久化数据,也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据,或者是为了防止系统故障而将数据备份到一个远程位置。

Redis 不同于 Memcached 的很重一点就是,Redis 支持持久化,而且支持两种不同的持久化操作。Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。

快照(snapshotting)持久化(RDB)

Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本,生成一个 RDB 文件,它是一个经过压缩的二进制文件。Redis 创建快照之后,可以对快照进行备份,将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。

快照持久化是 Redis 默认采用的持久化方式,可以手动执行如下两个命令来生成 RDB 文件:

  • SAVE会**阻塞 **Redis 服务器进程,服务器不能接收任何请求,直到 RDB 文件创建完毕为止。
  • BGSAVE创建出一个子进程,由子进程来负责创建 RDB 文件,服务器进程可以继续接收请求。

也可以在 Redis.conf 配置文件中做如下配置:

save 900 1           #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 300 10          #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 60 10000        #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

RDB 持久化主要依靠 fork 和 cow。fork是指 Redis 通过创建子进程来进行 RDB 操作,cow 指的是 copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

AOF(append-only file)持久化

与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:

appendonly yes

开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的 AOF 文件。AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。

在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no        #让操作系统决定何时进行同步

为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。

AOF 重写

AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。

Redis 将 AOF 重写程序放到子进程里执行(BGREWRITEAOF命令),像BGSAVE命令一样 fork 出一个子进程来完成重写 AOF 的操作,从而不会影响到主进程。 Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作 。

RDB和AOF对过期键的策略

RDB 持久化对过期键的策略:

  • 执行SAVE或者BGSAVE命令创建出的 RDB 文件,程序会对数据库中的过期键检查,已过期的键不会保存在 RDB 文件中
  • 载入 RDB 文件时,程序同样会对 RDB 文件中的键进行检查,过期的键会被忽略

AOF 持久化对过期键的策略:

  • 如果数据库的键已过期,但还没被惰性/定期删除,AOF 文件会保留过期键,当过期的键被删除了以后,会追加一条 DEL 命令来显示记录该键被删除了
  • 重写 AOF 文件时,程序会对文件中的键进行检查,过期的键会被忽略

复制模式:

  • 主服务器来控制从服务器统一删除过期键(保证主从服务器数据的一致性)

优缺点

RDB

优点:载入时恢复数据快、文件体积小。

缺点:会一定程度上丢失数据(因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。)

AOF

优点:丢失数据少(默认配置只丢失一秒的数据)。

缺点:恢复数据相对较慢,文件体积大。

如何选择?

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。

如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

Redis 事务

Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。

对于关系型数据库来说,事务还有回滚机制,即事务命令要么全部执行成功,只要有一条失败就回滚,回到事务执行前的状态。Redis 不支持回滚,它的事务只保证命令依次被执行,即使中间一条命令出错也会继续往下执行,所以说它只支持简单的事务。

Redis分布式锁

分布式锁一般有如下的特点:

  • 互斥性: 同一时刻只能有一个线程持有锁
  • 可重入性: 同一节点上的同一个线程如果获取了锁之后能够再次获取锁
  • 锁超时:和 JUC 中的锁一样支持锁超时,防止死锁
  • 高性能和高可用: 加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效
  • 具备阻塞和非阻塞性:能够及时从阻塞状态中被唤醒

实现方式:

1、利用 setnx+expire 命令,setnx 和 expire 是分开的两步操作,不具有原子性,如果执行完第一条指令应用异常或者重启了,锁将无法过期。

2、使用 Lua 脚本(包含setnx和expire两条指令)来保证原子性。

3、使用 set key value EX seconds[NX|XX] 命令 (正确做法)

4、Redlock算法 与 Redisson 实现

推荐阅读:基于Redis的分布式锁实现

Redis异步队列

一般使用 list 结构作为队列,rpush **生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep一会再重试。 当然也可以不使用 sleep,list 还有个指令叫 **blpop,在没有消息的时候,它会阻塞住直到消息到来。

Redis同步机制

Redis 的主从同步机制可以确保 Redis 的 master 和 slave 之间的数据同步。按照同步内容的多少可以分为全同步和部分同步,按照同步的时机可以分为 slave 刚启动时的初始化同步和正常运行过程中的数据修改同步。

全同步

1)在slave启动时,会向master发送一条SYNC指令。
2)master收到这条指令后,会启动一个备份进程将所有数据写到rdb文件中去。
3)更新master的状态(备份是否成功、备份时间等),然后将rdb文件内容发送给等待中的slave。

注意,master并不会立即将rdb内容发送给slave。而是为每个等待中的slave注册写事件,当slave对应的socket可以发送数据时,再将rdb内容发送给slave。

部分同步

当 Redis 的 master/slave 服务启动后,首先进行全同步。之后,所有的写操作都在 master 上,而所有的读操作都在 slave 上。因此写操作需要及时同步到所有的 slave 上,这种同步就是部分同步。

1)master收到一个操作,然后判断是否需要同步到salve。
2)如果需要同步,则将操作记录到aof文件中。
3)遍历所有的salve,将操作的指令和参数写入到savle的回复缓存中。
4)一旦slave对应的socket发送缓存中有空间写入数据,即将数据通过socket发出去。

推荐阅读:Redis 数据同步机制分析

Redis集群

Java面试准备之Redis系列二

上图展示了 Redis Cluster 典型的架构图,集群中的每一个 Redis 节点都 互相两两相连,客户端任意直连到集群中的任意一台,就可以对其他 Redis 节点进行读写 的操作。

基本原理

Java面试准备之Redis系列二

常见的分区规则哈希分区和顺序分区,Redis 集群使用了哈希分区,顺序分区暂用不到,不做具体说明;

Rediscluster采用了哈希分区的“虚拟槽分区”方式(哈希分区分节点取余、一致性哈希分区和虚拟槽分区)。

RedisCluster采用此分区,所有的键根据哈希函数(CRC16[key]&16383)映射到0-16383槽内,共16384个槽位,每个节点维护部分槽及槽所映射的键值数据,每个节点通过 gossip 消息得知其它节点的信息。

哈希函数: Hash()=CRC16[key]&16383 按位与

Java面试准备之Redis系列二

Java面试准备之Redis系列二

redis用虚拟槽分区原因:解耦数据与节点关系,节点自身维护槽映射关系,分布式存储

redisCluster 的缺陷:

a,键的批量操作支持有限,比如mset, mget,如果多个键映射在不同的槽,就不支持了

b,键事务支持有限,当多个key分布在不同节点时无法使用事务,同一节点是支持事务

c,键是数据分区的最小粒度,不能将一个很大的键值对映射到不同的节点

d,不支持多数据库,只有0,select 0

e,复制结构只支持单层结构,不支持树型结构。

集群的主要作用

  1. 数据分区: 数据分区 (或称数据分片) 是集群最核心的功能。集群将数据分散到多个节点,一方面突破了 Redis 单机内存大小的限制,存储容量大大增加另一方面 每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。Redis 单机内存大小受限问题,在介绍持久化和主从复制时都有提及,例如,如果单机内存太大,bgsavebgrewriteaoffork 操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出……
  2. 高可用: 集群支持主从复制和主节点的 自动故障转移 (与哨兵类似),当任一节点发生故障时,集群仍然可以对外提供服务。

客户端是如何访问集群的?

客户端会先访问集群中的任意一个节点, 然后向该节点发送一条get/set命令,接收的节点首先会依据该 key 计算对应槽位,然后再找到槽位所在的节点,判断找到的节点是否是自身, 如果是则在当前节点执行该命令,否则回复客户端 moved 异常,异常中包含真正执行命令的节点的信息,客户端需要使用获取到节点信息,重新连接获取到的节点并发送命令,但是该行为不是自动的,需要主动操作(如下图中第4步,该步骤需要在客户端通过专门编写逻辑代码执行);客户端依据返回的moved异常中的节点信息,进行的转移连接操作就是moved重定向。

GET x
-MOVED 3999 127.0.0.1:6381

MOVED 指令第一个参数 3999key 对应的槽位编号,后面是目标节点地址,MOVED 命令前面有一个减号,表示这是一个错误的消息。客户端在收到 MOVED 指令后,就立即纠正本地的 槽位映射表,那么下一次再访问 key 时就能够到正确的地方去获取了。

Java面试准备之Redis系列二

这里其实还有个迁移中的情况,如果访问的槽正在迁移,则返回ask命令,客户端会被引导去目标节点查找。

推荐阅读:玩转Redis集群之ClusterRedis应用学习——Redis Cluster客户端 原

新的主服务器是怎样被挑选出来的?

Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。

关于 slave 的选择,有如下依据:

  • 跟master断开连接的时长
  • slave优先级
  • 复制offset
  • run id

Redis Sentinel 哨兵

Java面试准备之Redis系列二

上图展示了一个典型的哨兵架构图,它由两部分组成,哨兵节点和数据节点:

  • 哨兵节点: 哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的 Redis 节点,不存储数据;
  • 数据节点: 主节点和从节点都是数据节点;

在复制的基础上,哨兵实现了 自动化的故障恢复 功能,下方是官方对于哨兵功能的描述:

  • 监控(Monitoring): 哨兵会不断地检查主节点和从节点是否运作正常。
  • 自动故障转移(Automatic failover):主节点 不能正常工作时,哨兵会开始 自动故障转移操作,它会将失效主节点的其中一个 从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
  • 配置提供者(Configuration provider): 客户端在初始化时,通过连接哨兵来获得当前 Redis 服务的主节点地址。
  • 通知(Notification): 哨兵可以将故障转移的结果发送给客户端。

其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移。而配置提供者和通知功能,则需要在与客户端的交互中才能体现。

推荐阅读:从零单排学Redis【铂金二】Redis(9)——集群入门实践教程

Redis Cluster 着眼于扩展性,在单个 Redis 内存不足时,使用 Cluster 进行分片存储。

缓存雪崩

什么是缓存雪崩?

缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。 数据库由于承受压力过大,可能直接崩溃,导致整个服务瘫痪。

原因:

  • Redis 挂掉了,请求全部走数据库。
  • 对缓存数据(热点数据)设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。

有哪些解决办法?

针对Redis服务不可用的情况:

  1. 采用 Redis 集群 (主从架构+Sentinel 或者Redis Cluster) 实现高可用,避免单机出现问题整个缓存服务都没办法使用。
  2. 设置本地缓存(ehcache)+限流(hystrix),避免数据库崩溃。

针对热点缓存失效的情况:

  1. 设置不同的过期时间,比如随机设置缓存的失效时间。
  2. 缓存永不失效。

缓存穿透

什么是缓存穿透?

缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。

有哪些解决办法?

缓存无效key

如果缓存和数据库都查不到某个 key 的数据 ,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了。这种情况我们一般会将空对象设置一个较短的过期时间

布隆过滤器

由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层!

需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是: 布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。

我们需要了解一下布隆过滤器的背后原理!以及为何存在误判的现象。

当一个元素加入布隆过滤器中的时候,会进行哪些操作:

  1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
  2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。

当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行哪些操作:

  1. 对给定元素再次进行相同的哈希计算;
  2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

然后,一定会出现这样一种情况:不同的字符串可能哈希出来的位置相同。 (可以适当增加位数组大小或者调整我们的哈希函数来降低概率)

扩展:缓存击穿

缓存击穿跟缓存雪崩有点像,但是又有一点不一样,缓存雪崩是因为大面积的缓存失效,导致数据库崩溃,而缓存击穿不同的是缓存击穿是指一个 Key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。

热点key的访问要如何解决?

1、可以通过 hash 分 key,把一个 key 拆分成多个 key,分布到不同的节点,防止单点过热。比如一个 key 之前就分到一个节点上,把 key 做了拆分之后,就像一致性 hash 的虚拟节点,分散访问。

2、使用本地缓存,比如 Ehcahe。

Redis中的一致性hash使用

一致性Hash算法原理:

先构造一个长度为2^32的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 2^32-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值计算得到其Hash值(其分布也为[0, 2^32-1]),接着在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。

这种算法解决了普通余数Hash算法伸缩性差的问题,可以保证在上线、下线服务器的情况下尽量有多的请求命中原来路由到的服务器。

当然,万事不可能十全十美,一致性Hash算法比普通的余数Hash算法更具有伸缩性,但是同时其算法实现也更为复杂。

一致性Hash算法实现,可以在很大程度上解决很多分布式环境下不好的路由算法导致系统伸缩性差的问题,但是会带来另外一个问题:负载不均

比如说有Hash环上有A、B、C三个服务器节点,分别有100个请求会被路由到相应服务器上。现在在A与B之间增加了一个节点D,这导致了原来会路由到B上的部分节点被路由到了D上,这样A、C上被路由到的请求明显多于B、D上的,原来三个服务器节点上均衡的负载被打破了。某种程度上来说,这失去了负载均衡的意义,因为负载均衡的目的本身就是为了使得目标服务器均分所有的请求

解决这个问题的办法是引入虚拟节点,其工作原理是:将一个物理节点拆分为多个虚拟节点,并且同一个物理节点的虚拟节点尽量均匀分布在Hash环上(物理服务器数越大,需要的虚拟节点越多)。采取这样的方式,就可以有效地解决增加或减少节点时候的负载不均衡的问题。

解决方案之一: 给每个真实结点后面根据虚拟节点加上后缀再取Hash值,比如"192.168.0.0:111"就把它变成"192.168.0.0:111&&VN0"到"192.168.0.0:111&&VN4",VN就是Virtual Node的缩写,还原的时候只需要从头截取字符串到"&&"的位置就可以了。

推荐阅读:对一致性Hash算法,Java代码实现的深入研究

对于缓存雪崩、缓存穿透、缓存击穿的总结

一般避免以上情况发生我们从三个时间段去分析下:

  • 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。
  • 事中:本地 ehcache 缓存 + Hystrix 限流+降级,避免MySQL被打死。
  • 事后:Redis 持久化 RDB+AOF,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

因为缓存穿透等原因导致服务挂了,如何恢复?

1、重启服务,站在运维人员的角度,优先重启 Redis 和数据库服务

2、在启动服务时,把流量摘掉,可以先把流量拦截者入口的地方,比如简单粗暴的通过 Nginx 的配置把请求都转到一个设计好的错误页面。防止流量过大,直接把重启的服务挨个挂掉的情况出现。

3、Redis 缓存预热,把热点 key 放进去

如何保证缓存与数据库双写时的数据一致性?

你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?

有两个选择:

  1. 先删除缓存,再更新数据库
  2. 先更新数据库,再删除缓存

先删缓存,再更新数据库

该方案仍然有可能导致不一致。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库

上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
那么,如何解决呢?采用延时双删策略

先删除缓存,再写数据库(这两步和原来一样),休眠1秒,再次删除缓存 。可以将1秒内所造成的缓存脏数据,再次删除。

具体休眠多久,读者应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

先更新数据库,再删缓存

正常的情况是这样的:

  • 先更新数据库,成功;
  • 再删除缓存,也成功;

如果在高并发的场景下,出现数据库与缓存数据不一致的概率特别低,也不是没有。

假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生:
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存

概率低的原因在于:只有步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。

解决思路:

1、利用消息队列进行失败重试

Java面试准备之Redis系列二

流程如下所示:
(1)更新数据库数据;
(2)缓存因为种种问题删除失败
(3)将需要删除的key发送至消息队列
(4)自己消费消息,获得需要删除的key
(5)继续重试删除操作,直到成功

然而,该方案有一个缺点,对业务线代码造成大量的侵入。

2、订阅 binlog 程序

Java面试准备之Redis系列二

流程如下图所示:
(1)更新数据库数据
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)尝试删除缓存操作,发现删除失败
(6)将这些信息发送至消息队列
(7)重新从消息队列中获得该数据,重试操作。

上述的订阅 binlog 程序在 MySQL 中有现成的中间件叫 canal,可以完成订阅 binlog 日志的功能。至于 oracle 中,目前不知道有没有现成中间件可以使用。

推荐阅读:【原创】分布式之数据库和缓存双写一致性方案解析

Redission如何解决锁释放的问题?

一开始redis作为分布式锁用的是setnx,再这基础上设置个定时过期时间,但这种方式有什么问题呢?

首先是原子性问题,setnx+过期时间这两个操作必须是原子性的,所以这可以用lua脚本解决。

除此之外呢,为了区分来自不同客户端的锁操作。我们在加锁操作时,可以让每个客户端给锁变量设置一个唯一值,这里的唯一值就可以用来标识当前操作的客户端

关于释放锁的时机存在如下问题:

不管我们定多少过期时间,都不能保证,在这段时间内锁住的代码执行完成了,所以这个时间定多少都不好;

如果不定时间,当执行完成后释放锁,问题就是如果执行到一半机器宕机,那这把锁就永远放不掉了

那Redisson是如何解决上述问题的呢?

它对代码进行了精简的封装,我们的使用非常简单,甚至我们不用主动设置过期时间

它设计了个watch dog看门狗,每隔10秒会检查一下是否还持有锁,若持有锁,就给他更新过期时间30秒;通过这样的设计,可以让它在没有释放锁之前一直持有锁,哪怕宕机了,也能自动释放锁。而不能获得锁的客户端则是不断循环尝试加锁。

通过记录锁的客户端id,可以把它设计成可重入锁

参考文献

面试官:你真的了解Redis分布式锁吗?

为什么redis可以做分布式锁

redis为什么可以实现分布式锁?和zookeeper实现分布式锁的区别

Redis如何解决超卖问题

总体思路就是要减少对数据库的访问,尽可能将数据缓存到Redis缓存中,从缓存中获取数据。

  • 在系统初始化时,将商品的库存数量加载到Redis缓存中;
  • 接收到秒杀请求时,在Redis中进行预减库存,当Redis中的库存不足时,直接返回秒杀失败,否则继续进行第3步;
  • 将请求放入异步队列中,返回正在排队中;
  • 服务端异步队列将请求出队,出队成功的请求可以生成秒杀订单,减少数据库库存,返回秒杀订单详情。
  • 当后台订单创建成功之后可以通过websocket向用户发送一个秒杀成功通知。前端以此来判断是否秒杀成功,秒杀成功则进入秒杀订单详情,否则秒杀失败。

参考文献
SpringBoot + redis解决商品秒杀库存超卖,看这篇文章就够了

发表评论 取消回复
表情 图片 链接 代码

分享