Skip to content

动画学Redis进阶内容

视频地址: https://www.bilibili.com/video/BV1xm42177Fq

实现分布式全局ID、浏览数、值缓存、分布式session

Stirng 应用-值缓存

举一个例子,存在一个树结构【角色 → 人物】的一个树结构

image.png|300

考虑将列表内容存入到 Redis 中,

  • 将查询结果反序列化为一个 Json 数据,存入到 Redis 中
  • 作为一个查询缓存

image.png

三级下拉框

根据上文,这里扩展一下,一般来说,下拉框数据也可以使用 hash 结构,数据量少的话可以使用 string 类型。

Hash结构,以 国家、省份、城市 为示例

一级

键 countries 值 列表《国家ID,名称 》

二级:

键 privinces:国家ID, 值 列表《省份ID,名称 》

三级:

键 privinces:省份ID, 值 列表《城市ID,名称 》

Stirng应用-浏览数

image.png

Stirng应用-分布式系统全局 ID

image.png

扩展:在高并发情况下,每次创建一个订单,就需要和 Redis 交互一次,对于 redis 的压力会非常大的

如何优化这个场景?

我们可以每一次取 1000 个订单 ID(各服务取1000个 ID 备用);

每当订单服务需要创建订单的时候,就自增一次服务器内部的这个自增ID,直到 服务器内部的这个备用 ID 都用光了,再去 redis 中取 1000 个订单ID

可以使用命令 incr key count 来实现这个操作

image.png

命令

分布式 session

将 session 存入到 redis 中,通过 session ID 来获取 session 信息,实现跨服务器访问应用服务(共享 session)

image.png

Redis分布式锁,锁订阅、锁续期、Lua原子执行原理、顺便讲Sync锁升级

可重入锁

单体,JVM 进行通过加锁 synchronized

跨进程:Redis 分布式锁

setnx 设置会返回一个响应,如果存在再设置

问题

  • 可重入锁设置
  • 使用 hash 结构解决重入锁问题
    • hash 的 key1:锁的名称
    • hash 的 key2:锁的持有者线程 UUID
    • hash 的 value:重入次数

image.png

复习一个基础概念:redis 一般是键值对结构,键始终是字符串,值有多种类型,比如 set、string、list、zset、hash 等。

这里的可重入锁的 key 为 锁名称, value 是 一个 hash 结构,结构为:锁持有者,重入计数 (计数器结构)

由于是 hash 结构,因此这里不能使用 setnx 命令,而是使用的 lua 命令

redisson 的部分代码

    <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return this.commandExecutor.syncedEval(this.getRawName(), LongCodec.INSTANCE, command, "if ((redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getRawName()), new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)});
    }

这条Lua脚本的目的是原子性地处理一个计数器,通常用于实现锁机制。它检查一个锁键是否存在或一个特定的锁标识是否存在,如果满足条件,则递增锁标识的计数器,并设置或更新键的过期时间。如果计数器递增成功,脚本返回nil;如果没有递增(即锁已被其他客户端持有),则返回当前锁的剩余过期时间。

image.png

每当我们每次重入一次,则计数 + 1,如果释放锁,计数 - 1

释放锁

image.png

锁订阅

锁续期

Lua原子执行原理

Sync锁升级

  • 锁升级过程
    • 偏向锁 单线程
    • 轻量级锁 多线程交互持有
    • 重量级锁 锁竞争,发生竞争的判断依据就是根据线程使用CAS指令加锁或者解锁的时候是否失败来决定的
  • 重量级锁实现原理
    • sychronized底层是 monitor(监视器) 实现的,
    • monitor 包含三个东西:owner、entrylist、waitset,另外monitor是属于锁对象的,mark word和monitor都是对象头的一部分。

Redis红锁,主从情况下问题、持久化不彻底问题

Redis消息队列,List,发布订阅,Stream实现及如何保证可靠性,消息确认机制

消息队列

通常三种方式来实现消息队列

  • 1、List
  • 2、发布订阅
  • 3、Stream (Redis 5.0 中加入的数据结构)
通过 List 来实现消息队列

使用 Redis 的列表(list)数据结构来创建一个消息队列。列表的两端分别代表队列的头部(消费者读取端)和尾部(生产者写入端)。

# 使用 RPUSH 命令将消息添加到队列尾部
127.0.0.1:6379> RPUSH order_queue "order_data_1"
# 使用 LPOP 命令从队列头部读取消息
127.0.0.1:6379> LPOP order_queue

常用命令

  • RPUSH:添加元素到列表的尾部(右边)
  • LPOP:移除并返回列表头部(左边)的元素
  • BLPOP:BLPOP 是 LPOP 的阻塞版本,它移除并返回列表头部的元素。如果没有元素可供移除,BLPOP 会阻塞直到有新元素被添加或者超时

消息队列实现: LPUSH + RPOP 或者 RPUSH + LPOP ;一般会使用阻塞方式读取消息:BLPOP 或者 BRPOP

基于发布订阅实现消息队列

使用 Redis 的发布订阅会有 4 个基础概念:

  1. 发布者(Publisher):负责发送消息的客户端或服务。
  2. 订阅者(Subscriber):负责接收消息的客户端或服务。
  3. 频道(Channel):消息发布的目的地,订阅者监听这些频道以接收消息。
  4. 模式(Pattern):一种基于通配符的频道匹配规则,允许订阅者订阅符合特定模式的频道。

使用示例:

#订阅消息,可以多个订阅者订阅一个频道
127.0.0.1:6379> SUBSCRIBE mychannel
#发布消息,发布者使用 `PUBLISH` 命令向一个或多个频道发送消息。
127.0.0.1:6379> PUBLISH mychannel "Hello, Redis!"

订阅者在接收到消息后,可以根据业务逻辑进行处理。

订阅者还可以使用 PSUBSCRIBE 命令订阅符合特定模式的频道,这在需要订阅多个类似频道时非常有用。

127.0.0.1:6379> PSUBSCRIBE mychannel:*
通过 stream 实现消息队列

基本概念

  1. Stream:Redis Streams 是一个消息队列,它是一个包含多个消息的序列。
  2. 消息(Message):Stream 中的每个元素称为消息,每个消息都有一个唯一的 ID。
  3. 消费者组(Consumer Group):消费者组是一组消费者,它们共享对 Stream 的访问,可以并行处理消息。
  4. XADD:用于向 Stream 添加消息的命令。
  5. XREAD:用于从 Stream 读取消息的命令。
  6. XACK:用于确认消息已经被处理的命令。
  7. XGROUP:用于创建消费者组的命令。
  8. XPENDING:查看消费者组未处理的消息数量的命令。

使用示例

#创建 Stream,使用 `XADD` 命令向 Stream 添加消息。
127.0.0.1:6379> XADD mystream * myfield1 "Hello" myfield2 "World"
#创建消费者组:使用 `XGROUP` 命令创建一个消费者组
127.0.0.1:6379> XGROUP CREATE mystream mygroup $ MKSTREAM
#从消费者组中读取消息,消费者使用 `XREADGROUP` 命令从消费者组中读取消息。
127.0.0.1:6379> XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >
#消息确认,消费者处理完消息后,使用 `XACK` 命令确认消息。
127.0.0.1:6379> XACK mystream mygroup <message_id>

代码示例略过

缓存击穿、穿透、雪崩、布隆过滤器的实现

过期时间

一般是缓存数据设置过期时间 + 设置淘汰策略

当热点数据设置有效时间后,会采用预热(应用启动时加载),写入时续期,访问时续期,或者定时任务执行(为即将过期的热点数据执行续期操作);

通过设置有效时间,可以有效过滤掉冷门时间,减少 redis 中的使用内存。

缓存击穿

解决方案

  • 1、热点数据不设置过期时间
  • 2、互斥锁
    • 使用分布式锁

缓存雪崩

为何延时双删、删除重试