Redis基础教程

141 阅读22分钟

一 概述

Redis (Remote Dictionary Service)远程字典服务

Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库高速缓存消息队列代理。它支持字符串哈希表列表集合有序集合位图hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。

  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。

  • 原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。

  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

参考资料:

www.redis.net.cn/

1.1 基础命令

Redis中设有16个数据库,默认使用0号数据库,可通过select命名切换数据库

 # 切换数据库 select 1 # 查看当前数据库中的数据量 dbsize # 查看当前数据库中的所有值 keys * # 清空当前数据库 flushdb # 清空所有数据库  flushall # 判断某个key是否存在 exisit key # 设置带有过期时间的key(单位:秒) expire key 10 # 获取key剩余过期时间 ttl key # 查看key的类型 type key

Redis是单线程,为什么依然这么高效?

  • Redis是基于内存操作,性能瓶颈是内存和网络带宽,而不是CPU

  • 数据结构简单,对数据的操作命令也简单

  • 采用单线程,避免了不必要的上下文切换和竞态条件

  • 使用多路复用I/O模型,非阻塞IO

  • 使用底层模型不同,Redis自己构建了VM机制,更高效

1.2 基础类型

String

 # 设置key-value值 set key name # 查看key get key # 获取所有的key keys * # 判断某个key是否存在 exists key # 为key追加内容,如果key不存在,则新增该key append key "Hello" # 获取key长度 strlen key # 自加1 incr key # 自减1 decr key # 每次增加n incrby key n #每次减少n decrby key n # 获取字符串指定位置字符, 0 -1 获取全部 getrange key start end # 使用新值替换指定位置的字符 setrange key index newValue # 创建带有过期时间的keyset with expire) setex key expiretime value         # 如果数据库中无key则新增该keyset if not exists) 在分布式锁中经常使用 setnx key value # 先获取再设置 getset key value

List

 # 从头部设置值 lpush list value  # 从尾部设置值 rpush list value # 获取list指定范围的值 0 -1 获取所有的值 lrange list start end # 从头部移除值 lpop list value  # 从尾部移除值 rpop list value # 获取list中指定位置的值 lindex list index  # 获取list中指数组长度 llen list # 移除指定的值 lrem list 1 value # 截断list ltrim list start end # 移除列表中的最后一个元素到另一个list中 lpoplpush srcList targerLsit
  • List可看做是一个链表

  • 在头尾插入元素效率高,在中间插入数据效率低

  • 可作为消息队列使用(Lpush Rpop)

  • 可作为栈使用(Lpush Lpop)

Set

 # 新增元素 sadd mySet hello # 查看元素内容 smembers mySet # 判断集合中是否有某个元素 sismember mySet hello # 获取集合中的元素数量 scard mySet # 移除集合中的元素 srem mySet hello # 随机获取元素 srandmember mySet # 随机删除元素 spop mySet # 移动指定集合中的元素到另一个集合中 smove mySet anotherSet hello # 两个集合中的差集 sdiff mySet1 mySet2 # 两个集合中的交集   共同好友 sinter mySet1 mySet2 # 两个集合中的并集 sunion mySet1 mySet2 # 获取所有的keys hkeys myHash # 获取所有的value hvals myHash ​ ​
  • 无序不重复集合

  • 微博好友,关注等

Hash

 # 设置key-value hset myHash userName hello # 获取指定key的value hget myHash userName # 获取所有的vlue hgetall myHash # 删除指定key hdel myHash userName # 获取hash长度 hlen myHash # 判断是否存在指定的key hexists myHash key1 ​
  • 结构类似于java中的Map

  • 适合于存储经常变动的数据,更适合对象的存储

Zset

 # 添加值,需指定顺序 zadd mySet 1 one zadd mySet 2 two ​ # 获取所有元素 zrange mySet 0 -1 # 排序 zrangebyscore mySet -inf +inf  zrangebyscore mySet -inf +inf withscores # 逆序 zrevrange score 0 -1 # 获取集合中的元素个数 zcard mySet
  • 在set的基础上,增加排序

  • 对象排序,

Geospatial

 # 设置城市的经纬度 geoadd china:city 39.90 116.40 beijing # 获取经纬度 geopos china:city beijing # 返回两个位置间的距离 geodis china:city beijing shanghai km # 寻找指定位置附近的人 georadius china:city 110 30 1000 km georadiusbymember chiba:city beijing 1000 km # 查看地图中所有元素 zrange china:city 0 -1 # 移除地图中的指定元素 zrem china:city beijing ​
  • 设置经纬度可以计算距离以及查询附近的人

  • 底层使用Zset实现

Hyperloglog

 # 增加值 pf add myKey a b c d  # 统计 pfcount myKey # 合并 pfmerge myKey1 myKey2
  • 基数-不重复的元素 ,有一些误差

  • 占用的内存很小,2^64

  • 常用于浏览器计数、访问量、粉丝数,

Bitmap

# 第一个位置设为有效
setbit sign 0 1
setbit sign 1 0
# 获取某个位置的状态
getbit sign 1
# 统计 状态为1的个数
bitcount sign start end
  • 位存储,省空间

  • 统计活跃用户、休眠用户

  • 适用于只有两种状态的场景

1.3 事务

Redis事务本质

将一组命令打包运行,这些命令都会被序列化,然后按照顺序依次执行,Redis事务不保证原子性

  • 开启事务(multi)

  • 命名入列 (redis命令)

  • 执行事务(exec)

  • 放弃事务(discard)

    开启事务

    multi

    执行命令

    set key1 value1 set key2 value2 set key3 value3

    执行事务

    exec #放弃事务 discard

编译时异常会回滚所有命令

运行时异常会导致部分成功,部分失败

1.4 监控(乐观锁)

Redis支持监控数据,相当于一个乐观锁

# 监视指定的key,记录此刻的值,在执行事务时比较,如果值发生改变则执行不成功
watch key

unwatch

二 核心功能

2.1 Redis配置文件

Redis配置文件保存了redis启动时的一些默认命令

redis对大小写不敏感
# 指定默认的端口号
prot 6379

# 设置密码
requirepass abcd123
# 连接队列的大小 backlog队列总和= 未完成三次握手队列+已经完成三次握手队列
# 通常用于高并发场景下提高客户端连接数量
tcp-backlog 511
# 将redis服务设置为守 候服务,仅在Unix有效
daemonize  yes
# 绑定主机
bind 127.0.0.1 10.0.0.1
# 保持会话时间,推荐60s
tcp-keepalive 0
# 设置日志级别
loglevel notice
# 指定日志存储的文件
logfile "Logs/redis_log.txt"
# 自动开启wondows的事件日志 
syslog-enabled yes
# 设置Redis的数据库,共有16(0~15)个,通常默认为0
databases 16
# 持久化机制
save 900 1       # 900s(15min)内至少发生一次变更
save 300 10      # 300s(15min)内至少发生10次变更
save 60 10000    # 60s(1min)内至少发生10000次变更
# 如果持久化失败,是否继续工作
stop-writes-on-bgsave-error yes
# 是否压缩rdb文件,需要消耗一些cpu
rdbcompression yes
# 保存rdb文件时,自动校验 
rdbchecksum yes
# rdb文件保存位置
dir ./
# 设置连接redis的最大客户端数量
maxclients 10000
# 设置最大内存
maxmemory <bytes>
# 内存超限后的处理策略
# volatile-lru 使用LRU算法删除过期集合的key
# allkeys-lru   在主键空间中,优先移除最近未使用的key
# volatile-random 在设置过期时间的键空间中,随机删除某个key
# allkeys-random  岁间删除全部键空间的某个key
# volatile-ttl 在设置了过期时间的键空间中,优先删除更早过期时间的key
# novaiction  直接报错
maxmemory-policy noeviction

# 是否开启AOF持久化方式
appendonly no
# 设置AOF持久化方式的文件名
appendfilename "appendonly.aof"
# 设置同步策略,默认每秒同步一次(可能会丢失数据)
appendfsync everysec
# 设置同步策略,每次修改都会同步
appendfsync always
# 设置同步策略,不执行同步,依赖操作系统的同步机制
appendfsync no

2.2 数据持久化

Redis中的数据全部存储在内存中,如果突然宕机,数据会全部丢失,因此必须有一种持久化机制来存储redis中的数据

2.2.1 RDB

RDB持久化是指在指定时间间隔内将内存中的数据快照写入磁盘,类似于一个快照,通常会生成一个dump.rdb文件

  • 触发方式

    • save

      save命令手动触发RDB持久化,save命令会阻塞主进程,直到RDB持久化结束。当Redis中有大量数据需要持久化时,会造成较长时间的阻塞,不建议生产上使用

    • bgsave

      bgsave也会手动触发RDB持久化,不同的是,它不会阻塞主线程。在后台fork出子进程来执行持久化,仅在fork的一瞬间阻塞redis服务,通常时间很短

      1) 执行bgsave命令,redis首先会判断当前是否存在子进程,如果存在直接结束

      2) redis进程fork出子进程,此过程会阻塞服务

      3) fork结束后,bgsave命令执行结束,此后主进程继续提供服务,子进程在后台进行持久化磁盘

      4)子进程根据redis进程的内存生成快照文件,并替换原有的rdb文件

      5)持久化结束后,子进程会通过信号量的方式告知主进程

  • 拓展 Copy-On-Write

    详解COW

    通常为了提高fork的效率,操作系统会采用COW方式来创建子进程

    fork之后,kernel会把父进程中所有的内存页的权限设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,共享同一份内存,当其中某个进程写内存时,会触发页异常中断(Page-Fault),中断进程就会把触发异常单内存页复制一份

    fork-复制出一个完全一样的进程

    exec-替换掉当前进程的地址空间

    exec函数的作用

  • 优点

    RDB是一个非常紧凑的二进制压缩文件,是Redis在某个时间点的完整快照,因而RDB的文件数据恢复的速度要远远快于AOF,非常适合全量备份、灾难恢复等场景

  • 缺点

    需要一定的时间进行持久化操作,可能会丢失最后一次同步前的数据

    fork子进程的时候,会占用一定的内存空间

2.2.2 AOF

AOF(Append Only File),把每次对Redis的写命令都记录到日志中,然后在指定时间内同步到磁盘中,当需要数据恢复时只需把AOF文件重新执行一遍即可。AOF解决了持久化的实时性。

详解RDB和AOF

  • 执行步骤

    1)命令追加,将所有的写命令追加到AOF缓冲区内

    2)文件同步,根据不同的策略将AOF缓冲区同步到AOF文件中

    # 设置同步策略,默认每秒同步一次(可能会丢失数据)
    appendfsync everysec
    # 设置同步策略,每次修改都会同步
    appendfsync always
    # 设置同步策略,不执行同步,依赖操作系统的同步机制
    appendfsync no
    

    3) 文件重写,定期对AOF文件进行重写,节约空间

    4)数据加载,当需要恢复数据时,重新执行AOF文件

  • 优点

    每次修改都会同步,实时性更好

    以文本形式保存了写命令,更易读

  • 缺点

    AOF文件远大于RDB文件,修复速度也比较慢

    AOF运行效率比较低,因而默认的持久化策略为RDB

2.3.3 混合持久化

Redis4.0后新增了新的功能-混合持久化。将RDB文件的内容和增量的AOF日志文件存在一起,这里的AOF文件不再是全量的日志,而是自持久化开始到持久化结束的这段时间内发生的增量的AOF日志,通常这部分日志的体积很小。当Redis重启时,可以先加载RDB文件,然后再重放AOF日志,数据恢复的速度得到很大的提高。

img

2.3 发布订阅

Redis支持有限的消息监听机制,以实现发布订阅模式。Redis客户端可订阅任意数量的topic

pubsub1

pubsub2

# 订阅主题
subscribe myTopic1 myTopic2 myTopic3
# 发送消息
publish myTopic1 Hello
# 取消订阅
unsubscribe myTopic1

Redis的消息并没有持久化,一旦redis宕机,所有的消息都会丢失。在消费过程中,一旦某个消费者宕机又恢复后,在此过程中的消息将不会被重新消费。

  • 使用场景

    实时消息系统

    聊天室

    订阅、关注等

2.4 主从复制

将一台Redis服务器中的数据复制到其他服务器中,数据的复制只能是单向的,只从Master到Slave。

  • 数据冗余 主从复制实现了数据的热备份,持久化的一种数据冗余方式

  • 故障恢复 主节点宕机后,可以平滑迁移到从节点中,实现故障的快速恢复

  • 负载均衡 实现读写分离,主服务器提供写服务,从服务器提供读服务,从而提高Redis的并发量

  • 高可用 实现哨兵以及集群的基础

Windows配置主从服务器

  • 复制Redis安装包,重写redis.windows.conf文件

    image-20210123150546039

    # 更改从服务器的端口号
    port 6380
    # 配置主服务器
    slaveof 127.0.0.1 6379
    masterauth jincong
    
    # 从机变成主机
    slaveof no one
    
  • 查看主从服务器

    # 主服务器
    > localhost:6379 connected!
    > info replication
    # Replication
    role:master
    connected_slaves:2
    slave0:ip=127.0.0.1,port=6380,state=online,offset=827,lag=0
    slave1:ip=127.0.0.1,port=6381,state=online,offset=827,lag=1
    master_repl_offset:827
    repl_backlog_active:1
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:2
    repl_backlog_histlen:826
    
    
    # 从服务器6380
    > info replication
    # Replication
    role:slave
    master_host:127.0.0.1
    master_port:6379
    master_link_status:up
    master_last_io_seconds_ago:7
    master_sync_in_progress:0
    slave_repl_offset:57
    slave_priority:100
    slave_read_only:1
    connected_slaves:0
    master_repl_offset:0
    repl_backlog_active:0
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:0
    repl_backlog_histlen:0
    
    # 从服务器6381
    > info replication
    # Replication
    role:slave
    master_host:127.0.0.1
    master_port:6379
    master_link_status:up
    master_last_io_seconds_ago:7
    master_sync_in_progress:0
    slave_repl_offset:57
    slave_priority:100
    slave_read_only:1
    connected_slaves:0
    master_repl_offset:0
    repl_backlog_active:0
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:0
    repl_backlog_histlen:0
    

主从服务器特点

  • 主机可以设置值,从机只能读取,从机会自动与主机保持同步

  • 主机宕机,从机依旧保持与主机关系,无法处理写请求。主机恢复后,会自动同步数据。从机宕机恢复后,也是如此

    • Slave启动成功后,会向master发送一个sync命令

    • master接受到命令后,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行结束后,master将传送整个数据文件到slave,并完成一次完全同步

      • 全量复制 slave服务在接收到数据文件后,将其存盘并加载到内存中

      • 增量复制 master继续将所有收集到的修改命令依次传给slave,完成同步

      但是只要是重新连接master,就会自动执行一次全量复制

2.5 哨兵模式

当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,非但不高效还会导致一段时间内服务不可用。Redis从2.8开始提供Sentinel模式,来自动选取主服务器。哨兵模式是一个独立的进程,通过实时监控整个Redis服务来确定存活的实例,一旦主服务器宕机,就会触发内部的重新选举机制。哨兵模式实现了故障回复的自动化处理,缺陷是写操作无法负载均衡,存储能力受到单机的限制。

参考链接:[Redis哨兵模式与高可用集群(juejin.cn/post/684490…)

img

img

img

  • 工作模式

    1)创建一个Redis哨兵模式,通常由多个哨兵进程、一个主机、多个从机组成

    2)Sential中记录了主从节点的信息,并持续监控主从节点的健康,一旦主节点宕机,内部会根基自己的投票算法自动选取一个从节点作为新的主机

    3)客户端来连接集群时,会首先访问Sential节点获取当前的主节点,然后与主节点进行数据交互。当主节点宕机后,客户端会重定向到到sentinel获取新的主机地址。从而无需外部干预即可平滑切换主机。

  • 如何启动哨兵模式

    哨兵模式配置文件 1表示开启自动投票机制,得票多的称为新的主机

    sentinel monitor mysentinel 127.0.0.1 6379 1

    开启哨兵监控模式

    redis-sentinel.exe sentinel.conf

  • 主观下线和客观下线

    默认情况下,每个Sentinel节点都会以每秒一次的频率对Redis的其他节点(主节点、从节点、其他的Sentinel节点)发送ping命令,并通过节点的 回复来判断节点是否正常

    • 主观下线 适用于所有的主节点和从节点,如果在down-after-milliseconds 时间内,sentinel没有收到目标节点的有效回复,则会判定该节点为主观下线

    • 客观下线 仅适用于主节点。如果主节点发生故障,Sentinel节点会通过sentinel is-master-down-by-addr 命令向其他Sentinel节点询问对该节点的状态判断,如果超过quorum个数的节点判定主节点不可达,则该Sentinel节点会判断主节点为客观下线

  • 详解Sentinel的工作原理

    1) 每个Sentinel以每秒一次的频率,向它所知的主服务器、从服务器及其其他Sentinel发送一个PING命令

    img

2)如果一个实例节点距离最后一次有效回复PING命令的时间超过down-after-milliseconds,那么这个实例会被Sentinel标记为主观下线

img

3)如果一个主服务器被标记为主观下线,那么正在监视这个主服务器的所有Sentinel节点,要以每秒一次的频率确认主服务器的确进入了主观下线状态

img

4)如果一个主服务器被标记为主观下线,并且有足够数量的Sentinel在指定的时间范围内同意这一判断,那么这个主服务器就会标记为客观下线

img

5)一般情况下,每个Sentinel会以每秒10次的频率,向它已知的所有主服务器和从服务器发送INFO命令,当一个主服务器被Sentinel标记为客观下线时,Sentinel向下线服务器的从服务器发送INFO命令的频率,会从10秒1次提升到1秒一次。

img

6)Sentinel和其他Sentinel协商主节点的状态,如果主节点处于SDOWN状态,则投票自动选出新的主节点,将剩余的从节点指向新的主节点进行数据复制。

img

7)当没有足够数量的Sentinel同意主服务器下线时,主服务器的客观下线状态就会被移除,当主服务器重新向Sentinel的PING命令返回有效回复时,主服务器的主观下线状态就会被移除

img

2.6 缓存穿透、击穿和雪崩

缓存穿透

  • 概念

    客户端查询的数据不在缓存中,直接访问数据库,也没有查到导致查询失败。大量请求穿透缓存层直达数据库,从而导致缓存穿透

  • 解决方案

    • 布隆过滤器

      img

      布隆过滤器是一种数据结构,它将数据经过多种hash运算后,生成一串二进制代码,映射到指定的数据结构中。工作时,首先将所有可能查询的参数以hash的形式存储。当查询时首先使用布隆过滤器判断是否存在,如果不存在直接返回。

    • 缓存空对象

      img

      当没有命中存储层时,即使返回空对象也将其缓存起来,同时设置一个过期时间,之后再次访问时会直接命中缓存,从而保护存储层

缓存击穿

  • 概念

在高并发场景下,多线程同时查询同一个热点对象,如果该热点对象不在缓存中或者恰巧缓存失效,导致同一时间大量请求打到数据库,从而导致数据库压力陡增

  • 解决方案

    • 设置热点数据永不过期

      从缓存层面讲,没有设置过期时间热点数据就不会失效,从而保护数据

    • 加互斥锁

      后台使用分布式锁,保证同一时间对于某个key只能有一个线程访问,这种方式将压力转移到了分布式锁上。

缓存雪崩

  • 概念

    大量热点key在同一时间过期,或者缓存宕机,从而导致大量请求直达数据库,进而引发一系列业务问题。

    img

  • 解决方案

    • redis高可用

      既然Redis可能宕机,那就提供一种高可用机制,确保即使Redis宕机,也能正常提供缓存服务。通常可以采用哨兵或者集群的方案达到高可用目的。

    • 限流降级

      在缓存失效后,通过加锁或者对列来控制对数据库的访问。比如分布式锁、阻塞对列等技术

    • 数据预热

      在正式部署前,在缓存中预存储热点数据,从而在生产上拦截掉大部分的访问,同时将这些热点key设置不同的过期时间,使缓存失效时间尽量均匀。

三 Redis使用

3.1 Windows启动Redis服务

jingyan.baidu.com/article/f25… 启动过程

www.cnblogs.com/fyboke/p/62… 简单操作

www.cnblogs.com/skmobi/p/11… windows配置Redis

windows安装Redis服务

# 临时开启Redis
# 这个命令会创建Redis临时服务,不会在window Service列表出现Redis服务名称和状态,此窗口关闭,服务会自动关闭
redis-server.exe  redis.windows.conf
# 连接redis
redis-cli.exe 127.0.0.1 -p 6379
# 停止服务
redis-server –service-start

# 配置windows开机自启Redis服务
安装服务:
redis-server.exe --service-install redis.windows.conf --service-name redisserver1 --loglevel verbose

启动服务:
redis-server.exe  --service-start --service-name redisserver1

停止服务:
redis-server.exe  --service-stop --service-name redisserver1

卸载服务:
redis-server.exe  --service-uninstall--service-name redisserver1

3.2 Springboot集成Redis

Springboot中使用Redis,只需导入相关依赖即可,同时所有的对象必须支持序列化

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Springboot的redis默认使用JdkSerializationRedisSerializer,如果想使用json必须手动配置redisTemplate

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        // 将对象解析为json
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        // 设置Redis的序列化方式
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();

        return  redisTemplate;
    }

分布式锁实现机制

Redis实现分布式锁

3.3 缓存一致性

  • 缓存优点

    能够缩短服务响应时间,给用户带来更好的体验

    能够增大系统的吞吐量,提升性能

    减轻数据库压力,防止超载

  • 缓存缺点

    增加了开发难度和维护难度

    增加系统复杂度

    特殊场景下必须考虑缓存一致性

不更新缓存而是删除缓存

如果业务中只是到数据库中取值然后写入缓存,或者写入缓存中的值需要经过复杂的计算,更适合删除缓存

考虑以上场景

  • 线程A更新数据库,x=1

  • 线程B更新数据库,x=2

  • 线程B更新缓存,cache=2

  • 线程A更新缓存,cache=1

这就会出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络抖动等原因,B却比A更早更新了缓存,导致缓存了脏数据,如果仅仅是淘汰缓存,仅增加了异常cache miss。

先删缓存,再更新数据库

  • 请求A进行写操作,删除缓存 del cache

  • 请求B查询缓存不存在,cache=null

  • 请求B查询数据库,得到旧值,x=1

  • 请求B将旧值写入缓存,cache=1

  • 请求A将新值写入数据库,x=2

此种场景会出现数据不一致情况,而且如果没有给缓存设置过期时间,该数据永远为脏数据

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

  • 缓存刚好失效

  • 请求A查询数据库,得到旧值

  • 请求B将新值写入数据库

  • 请求B删除缓存

  • 请求A将查到的旧值写入缓存

此场景的出现有一定的限制,即步骤3写数据库操作比2数据库的读操作耗时更短,才有可能出现4优先于5执行。但是通常情况下,数据库的读操作要远快于写操作,因而上述的限制场景很难出现。

如果想实现基础的数据一致性策略,又不想增加太大工作量的情况下直接采用

删缓存、先更新数据库再删缓存

终极解决方案-缓存延时双删

CAP理论决定了不可能做到绝对的强一致性,缓存系统属于AP,可以做到最终一致性

采用延时双删+重试机制

图片

  • 更新数据库

  • 数据库操作日志写入binlog日志

  • 订阅程序提取所需数据及key

  • 另起一段非业务代码,获得该信息

  • 尝试删除缓存操作、发现删除失败

  • 将失败信息发送至消息队列

  • 重新消费消息队列,重试操作

分布式系统中要么通过2PC或者Paxos协议保证一致性,要么就是拼命的降低并发时脏数据的概率,缓存系统适用的场景就是非强一致性的场景,所以它属于CAP理论中的AP、BASE理论,异构数据本来就没办法做到强一致性,只是尽可能减少时间窗口,达到最终一致性。最后,设置缓存的过期时间作为兜底方案。

3.4 分布式锁

  • 设置key与过期时间应为原子操作

  • 每个线程设置唯一的Id,保证只能释放自己的持有的锁

  • 释放锁要放到finally语句块中,防止异常场景

  • 可以采用分段锁提高并发场景