1.redis九种数据类型

     1,string:最基本的数据类型,二进制安全的字符串,最大512M。
     2,list:按照添加顺序保持顺序的字符串列表。
     3,set:无序的字符串集合,不存在重复的元素。
     4,sorted set:已排序的字符串集合。     
     5,hash:key-value对的一种集合。          
     6,bitmap:更细化的一种操作,以bit为单位 (string类型)。
     7,hyperloglog:基于概率的数据结构。 # 2.8.9新增
     8,Geo:地理位置信息储存起来, 并对这些信息进行操作   # 3.2新增
     9,流(Stream)# 5.0新增

2.Redis Stream详细介绍

Redis Stream 是 Redis 5.0 版本新增加的数据结构。Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。

Redis Stream 的结构如下所示,它有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容:

 

每个 Stream 都有唯一的名称,它就是 Redis 的 key,在我们首次使用 xadd 指令追加消息时自动创建。

每个 Stream 都可以挂多个消费组(Consumer Group),每个消费组会有个游标 last_delivered_id 在 Stream 数组之上往前移动,表示当前消费组已经消费到哪条消息了。每个消费组都有一个 Stream 内唯一的名称,消费组不会自动创建,它需要单独的指令 xgroup create 进行创建,需要指定从 Stream 的某个消息 ID 开始消费,这个 ID 用来初始化 last_delivered_id 变量。

每个消费组的状态都是独立的,相互不受影响。也就是说同一份 Stream 内部的消息会被每个消费组都消费到。

同一个消费组可以挂接多个消费者(Consumer),这些消费者之间是竞争关系,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。每个消费者有一个组内唯一名称。

消费者内部会有一个状态变量 pending_ids,它记录了当前已经被客户端读取,但是还没有 ack 的消息。如果客户端没有 ack,这个变量里面的消息 ID 就会越来越多,一旦某个消息被 ack,它就开始减少。这个 pending_ids 变量在 Redis 官方被称为 PEL,也就是 Pending Entries List,这是一个核心的数据结构,它用来确保客户端至少消费了消息一次,而不会在网络传输的中途丢失了而没被处理。具体命令查看

2.redis 比较流行的几个模块

loadmodule /root/docker/docker-redis/redis/modules/libredis_cell.so
loadmodule /root/docker/docker-redis/redis/modules/redisbloom.so
loadmodule /root/docker/docker-redis/redis/modules/redisearch.so

   redis-cell 文档

   redis-cell 命令

    该模块只提供了一个命令:CL.THROTTLE

参数说明
CL.THROTTLE test 100 400 60 3
 
test: redis key
 
100: 官方叫max_burst,其值为令牌桶的容量 - 1, 首次执行时令牌桶会默认填满
 
400: 与下一个参数一起,表示在指定时间窗口内允许访问的次数
 
60: 指定的时间窗口,单位:秒
 
3: 表示本次要申请的令牌数,不写则默认为 1
 
以上命令表示从一个初始值为100的令牌桶中取3个令牌,该令牌桶的速率限制为400次/60秒。
127.0.0.1:6379> CL.THROTTLE test 100 400 60 3
1) (integer) 0
2) (integer) 101
3) (integer) 98
4) (integer) -1
5) (integer) 0
127.0.0.1:6379> 
1: 是否成功,0:成功,1:拒绝
 
2: 令牌桶的容量,大小为初始值+1
 
3: 当前令牌桶中可用的令牌
 
4: 若请求被拒绝,这个值表示多久后才令牌桶中会重新添加令牌,单位:秒,可以作为重试时间
 
5: 表示多久后令牌桶中的令牌会存满

   redis-bloom 文档

   redis-bloom 命令

   BF.RESERVE 创建、预分配

# {键} {期望错误率} {期望容纳元素数量} [EXPANSION n 超出增加 n 备容量 | 超出返回错误] EXPANSION 和 NONSCALING 二选一
BF.RESERVE {key} {error_rate} {capacity} [EXPANSION {expansion}] [NONSCALING]

# 例子
# 错误率 0.001,期望添加 200 个元素,元素数量超出增加 3 备容量 = 800
BF.RESERVE test 0.001 200 EXPANSION 3
# 错误率 0.001,期望添加 200 个元素,元素数量超出返回错误
BF.RESERVE test 0.001 200 NONSCALING

BF.ADD 添加元素
BF.ADD {key} {item}
# 例子
BF.ADD test value1
BF.EXISTS 添加元素
BF.EXISTS {key} {item}
# 例子
BF.EXISTS test value
删除过滤器用 Redis 本身的 DEL 指令
127.0.0.1:6379> bf.reserve email 0.001  2000000
OK
127.0.0.1:6379> bf.add email 15036144926@163.com
(integer) 1
127.0.0.1:6379> bf.add email 1755866004@qq.com
(integer) 1
127.0.0.1:6379> bf.add email 18520819223@163.com
(integer) 1
127.0.0.1:6379> bf.add email 1399691593@qq.com
(integer) 1
127.0.0.1:6379> bf.exists email 15036144926@163.com
(integer) 1
127.0.0.1:6379> bf.exists email 15036144926@164.com
(integer) 0
127.0.0.1:6379> del email
(integer) 1

redis-search 文档

redis-search 命令

  FT.CREATE {index} 
    [MAXTEXTFIELDS] [NOOFFSETS] [NOHL] [NOFIELDS] [NOFREQS]
    [STOPWORDS {num} {stopword} ...]
    SCHEMA {field} [TEXT [NOSTEM] [WEIGHT {weight}] | NUMERIC | GEO] [SORTABLE] [NOINDEX] ...

在redisearch中最为基本的是要创建一个index(索引,下标),所以不管什么操作都离不开create。create的选项有几个,但是和其他比起来并不算多,我们要首先知道最基本的使用方法:

ft.create products schema id numeric name text

上面创建了一个index叫products ,它有两个字段 一个叫id,是数字类型的;一个是name,是文本类型的。
在redisearch中字段一共有三种类型:numeric(数字)、 text(文本) 、geo(地理位置)

MAXTEXTFIELDS: redisearch在text字段大小32字节及以下时对索引编码的方式和32字节以上的编码方式不一样,当text字段超过32字节时,启用该选项以提高索引效率。

NOOFFSETS: 启用该选项代表不储存document的偏移量。

NOHL: no highlight。代表不启用高亮,在搜索时,如果启用高亮,会在搜索结果中的搜索词加上“高亮”,默认显示是加<b>:<b>搜索词<b>。

NOFIELDS: 在过滤时,某些时候会使用字段进行过滤,启用该选项后不允许该形式的过滤。

NOFREQS: 弄 frequent.启用该选项,避免频繁保存term。这个term我把握不准怎么理解。可能指的是document中的一条数据?

 [STOPWORDS {num} {stopword} ...]: 在索引、搜索时忽略的一组词。默认有一组stopword,其中包含了像as,a,in这些。num代表接下来设置的stopword的个数,0代表关闭默认stopword。>0=n,代表后面跟着n个stopword。记住启用了stopword代表不用默认的stopwords。

SCHEMA 字段关键字,随后添加字段名与字段描述。

以下是字段的可选选项:
SORTABLE:text或者numeric是可排序的,如果搜索时要sortby(排序)则声明。注意会增加内存开销,不需要排序则不启用。

NOSTEM:redisearch默认会有一套stem(词干)分析的法则,类似于你搜索go,在结果中可能会有go,going这类词干一致的结果。nostem代表不启用。

NOINDEX:字段可以选择不被索引。

FT.ADD {index} {docId} {score} 
  [NOSAVE]
  [REPLACE [PARTIAL]]
  [LANGUAGE {language}] 
  [PAYLOAD {payload}]
  [IF {condition}]
  FIELDS {field} {value} [{field} {value}...]
 ft.add products product1 1 fields id 001 name dept001
代表向index products添加一条document,这条document的id为product1,score为1,字段id的值为数字1,name为"phone"。
    docId: 这里说是id并不代表只能说数字,可以是字符串。作为document的唯一标识。
    score: 评分,类似于zset里的score,范围从0~1,如果不知道打多少可以默认打1。
    NOSAVE:如果开启该选项我们不会在索引时保存真正的document。
    REPLACE:更新或者插入,删除原本的document
    PARTIAL (only applicable with REPLACE):在replace的时候指定对应的列
    FIELDS: 字段对应create index时的schema
    PAYLOAD {payload}: 在查询的时候使用,还不是太理解。。。
    IF {condition}: 配合replace使用,对判断语句进行判断后决定是否生效replace e.g. FT.ADD idx doc 1 REPLACE IF "@timestamp < 23323234234".
    LANGUAGE language: 指定语言,可以是中文:chinese
 FT.ADDHASH {index} {docId} {score} [LANGUAGE language] [REPLACE]
hset product2 id 002 name xiaomi
ft.addhash products product2 1
FT.ALTER {index} SCHEMA ADD {field} {options} ...
修改schema的字段结构。注意新add的会被重新索引,原本存在的不会改变。
 FT.SEARCH products "phone" LIMIT 0 10

3.Redis的几种高可用集群配置

Redis 一主多从

高可靠性:一方面,采用双击主备架构,能够在主库出现故障时自动进行主备切换,从库提升为主库提供服务,保证服务平稳运行;另一方面,开启数据持久化功能和配置合理的备份策略,能有效的解决数据误操作和数据异常丢失的问题。
读写分离策略:从节点可以扩展主库节点的读能力,有效应对大并发量的读操作。
故障恢复复杂,如果没有Redis HA 系统(需要开发),当主库节点出现故障时,需要手动将一个从节点晋升为主节点,同时需要通知业务方变更配置,并且需要让其他从库节点去复制新主库节点,整个过程需要人为干预,比较繁琐。
主库的写能力受到单机的限制,可以考虑分片。

主库的存储能力受到单机的限制,可以考虑Pika。

原生复制的弊端在早期的版本中也会比价突出,如:Redis 复制中断后,Slave 会发起 psync ,此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时,可能会造成毫秒或秒级的卡顿。

又由于COW 机制,导致极端情况下的主库内存溢出,程序异常退出或宕机;主库节点生成备份文件导致服务器磁盘IO和CPU 资源消耗;发送数GB 大小的备份文件导致服务器出口带宽暴增,阻塞请求,建议升级到最新版本。 


Redis Sentinel(哨兵)

其中Redis Sentinel 集群是由若干Sentinel 节点组成的分布式集群,可以实现故障发现,故障自动转移,配置中心和客户端通知。Redis Sentinel 的节点数量要满足 2n + 1 (n>=1)的奇数个。
Redis Sentinel 集群部署简单;
能够解决 Redis 主从模式下的高可用切换问题;
很方便实现 Redis 数据节点的线形扩展,轻松突破 Redis 自身单线程瓶颈,可极大满足 Redis 大容量或高性能的业务需求;
可以实现一套 Sentinel 监控一组 Redis 数据节点或多组数据节点。
部署相对 Redis 主从模式要复杂一些,原理理解更繁琐;
资源浪费,Redis 数据节点中 slave 节点作为备份节点不提供服务;
Redis Sentinel 主要是针对 Redis 数据节点中的主节点的高可用切换,对 Redis 的数据节点做失败判定分为主观下线和客观下线两种,对于 Redis 的从节点有对节点做主观下线操作,并不执行故障转移。
不能解决读写分离问题,实现起来相对复杂。

Redis Cluster 集群节点最小配置 6 个节点以上(3 主 3 从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。

Redis Cluster 采用虚拟槽分区,所有的键根据哈希函数映射到 0~16383 个整数槽内,每个节点负责维护一部分槽以及槽所映射的键值数据。
无中心架构;
数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布;
可扩展性:可线性扩展到 1000 多个节点,节点可动态添加或删除;
高可用性:部分节点不可用时,集群仍可用。通过增加 Slave 做 standby 数据副本,能够实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升;
降低运维成本,提高系统的扩展性和可用性。
Client 实现复杂,驱动要求实现 Smart Client,缓存 slots mapping 信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。目前仅 JedisCluster 相对成熟,异常处理部分还不完善,比如常见的“max redirect exception”。
节点会因为某些原因发生阻塞(阻塞时间大于 clutser-node-timeout),被判断下线,这种 failover 是没有必要的。
数据通过异步复制,不保证数据的强一致性。
多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况。
Slave 在集群中充当“冷备”,不能缓解读压力,当然可以通过 SDK 的合理设计来提高 Slave 资源的利用率。
Key 批量操作限制,如使用 mset、mget 目前只支持具有相同 slot 值的 Key 执行批量操作。对于映射为不同 slot 值的 Key 由于 Keys 不支持跨 slot 查询,所以执行 mset、mget、sunion 等操作支持不友好。
Key 事务操作支持有限,只支持多 key 在同一节点上的事务操作,当多个 Key 分布于不同的节点上时无法使用事务功能。
Key 作为数据分区的最小粒度,不能将一个很大的键值对象如 hash、list 等映射到不同的节点。
不支持多数据库空间,单机下的 redis 可以支持到 16 个数据库,集群模式下只能使用 1 个数据库空间,即db  0 。
复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。
避免产生 hot-key,导致主库节点成为系统的短板。
避免产生 big-key,导致网卡撑爆、慢查询等。
重试时间应该大于 cluster-node-time 时间。
Redis Cluster 不建议使用 pipeline和multi-keys 操作,减少 max redirect 产生的场景

hasht-tag

当一个 key 包含 {} 的时候,就不对整个 key 做 hash,而仅对 {} 包括的字符串做 hash。

假设 hash 算法为 sha1。对 user:{user1}:ids 和 user:{user1}:detail,其 hash 值都等同于 sha1 (user1)。

3.redis 备份与恢复

4.redis 常见问题

1.redis 是单线程还是多线程

redis 6.0之前单线程是指其网络I/O和键值对的读写是由一个线程完成,redis6.0多线程是指网络请求过程是多线程,而键值对的读写依然是单线程,所以redis读写依然是安全的,也就是只有网络请求模块和数据操作模块是单线程的,其他的比如持久化、 异步删除、集群数据同步等,是由额外的线程执的。

2.redis 单线程为什么这么快

  1.命令基于内存操作,一条命令在内存的操作也就是几十纳秒,2.命令是单线程操作没有上下文切换开销,3.基于I/O多路复用机制提升redis的I/O利用率,4.高效的数据存储结构:全局hash表以及跳表、压缩表、链表等数据结构。

 

3. redis 中 key 的过期删除策略

 1、定时删除

在设置某个 key 的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。

优点:通过使用定时器,可以保证过期 key 可以被尽快的删除,并且释放过期 key 所占用的内存

缺点:对 CPU 是不友好的,当过期键比较多的时候,删除过期 key 会占用相当一部分的 CPU 资源,对服务器的响应时间和吞吐量造成影响。

2、惰性删除

惰性删除,当一个键值对过期的时候,只有再次用到这个键值对的时候才去检查删除这个键值对,也就是如果用不着,这个键值对就会一直存在。

优点:对 CPU 是友好的,只有在取出键值对的时候才会进行过期检查,这样就不会把 CPU 资源花费在其他无关紧要的键值对的过期删除上。

缺点:如果一些键值对永远不会被再次用到,那么将不会被删除,最终会造成内存泄漏,无用的垃圾数据占用了大量的资源,但是服务器却不能去删除。

惰性删除过程

a、固定的时间执行一次定期删除;

b、采样一定个数的key,采样个数可以进行配置,并将其中过期的key全部删除;

c、如果过期 key 的占比超过可接受的过期key的百分比,则重复删除的过程,直到过期key的比例降至可接受的过期key的百分比以下;

d、对于从库创建的过期 key 同样从库是不能进行删除的。

3、定期删除

定期删除是对上面两种删除策略的一种整合和折中

每个一段时间就对一些 key 进行采样检查,检查是否过期,如果过期就进行删除

1、采样一定个数的key,采样的个数可以进行配置,并将其中过期的 key 全部删除;

2、如果过期 key 的占比超过可接受的过期 key 的百分比,则重复删除的过程,直到过期key的比例降至可接受的过期 key 的百分比以下。

优点:定期删除,通过控制定期删除执行的时长和频率,可以减少删除操作对 CPU 的影响,同时也能较少因过期键带来的内存的浪费。

缺点:执行的频率不太好控制频率过快对 CPU 不友好,如果过慢了就会对内存不太友好,过期的键值对不能及时的被删除掉同时如果一个键值对过期了,但是没有被删除,这时候业务再次获取到这个键值对,那么就会获取到被删除的数据了,这肯定是不合理的。

上面的三种策略,都有或多或少的问题。Redis 中实际采用的策略是惰性删除加定期删除的组合方式。

组合方式的使用

定期删除,获取 CPU 和 内存的使用平衡,针对过期的 KEY 可能得不到及时的删除,当 KEY 被再次获取的时候,通过惰性删除再做一次过期检查,来避免业务获取到过期内容。

3 redis 内存淘汰策略

当redis 内存超过maxmemory限定时,触发清理策略

主动清理策略在4.0之前一共实现了6中淘汰策略,在4.0之后有增加了2种,总共8种淘汰策略

    a).针对了设置过期时间的策略

       1.volatile-ttl :在筛选时,会针对设置了过期的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。

       2.volatile-random:就像它的名称一样,在设置过期时间的键值对中随机删除。

       3.volatile-lru :会使用LRU算法筛选设置了过期时间的键值对删除

       4.volatile-lfu:会使用LFU算法筛选设置了过期的键值对删除

    b).针对了所有的key处理

    1. allkeys-random:从所有键值对中随机选择并删除

    2.allkeys-lru:使用LRU算法在所有数据中进行筛选删除

    3.allkeys-lfu:使用lfu算法在所有数据中进行筛选删除

    c).不处理

       1.no-enviction(驱逐):禁止驱逐数据,这也是默认策略。意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,采用no-enviction策略可以保证数据不被丢失。

备注 :

        LRU (least recently used) 淘汰很久没被访问的数据,以最近一次没被访问的时间作为参考

        LFU(least frequency used) 淘汰最近一次一段时间很少被访问的数据,以次数作为参考

默认maxmemory-policy noeviction

4.redis持久化策略

   RDB 快照

   在默认情况下redis 将内存数据快照保存在名字为dump.rdb的文件中,你可以对redis设置它在N秒钟至少有M个改动,满足这一要求自动保存一次。

save 900 1
save 300 10
save 60 10000

 save和bgsave 同样可以保存,区别save是同步,bgsave是异步 (注意bgsave的写实复制)

   AOF

  快照功能并不能耐久,如果redis 因为某些原因宕机,那么服务器将会丢失最近写入,且未同步到快照中的那些数据。从1.1版本开始,redis增加了一种耐持久的持久化方式,AOF持久化,将修改的每一条指令记录到appendonly.aof中

appendonly yes
appendfilename "appendonly.aof"
#appendfsync always  //每次有新的修改,这个命令就会追加到文件末尾 (比较安全,性能低)
appendfsync everysec //每秒fsync 一次足够,并且故障只会丢失1秒钟数据
#appendfsync no //将数据交给操作系统来处理

 AOF 文件里面可能有太多无用的指令,所以AOF会定期根据内存的最新数据生成AOF文件

auto-aof-rewrite-percentage 100  //aof文件自上一次重写文件大小增加了100%触发重写
auto-aof-rewrite-min-size 64mb //aof文件要至少达到64M才能重写

 AOF 可以手动重写执行bgwriteaof 重写aof文件

生成环境都可以启用,如果启动既有rdb文件又有aof文件,优先选择aof恢复数据,因为aof数据更安全一些

   混合持久化

重启redis我们很少用rdb 恢复数据,因为会丢失大量数据。我们通常用aof来重放日志,使用aof相对rdb来说比较慢,这样在redis 实例很大的情况下会需要较长时间, redis 4.0之后可以使用混合持久化,通过如下配置可以开启持久化

aof-use-rdb-preamble yes

 如果开启了混合持久化,AOF在重写时不再是单纯的将内存数据转换为RESP命令写入AOF,而是将重写这一刻之前的数据做RDB快照处理,并且将RDB快照内容和增量的AOF命令放在一起,都写入新的aof文件,新的文件一开始不叫appendonly,等到重写完新的aof才改名,覆盖原有的aof文件,达到新老文件替换。

对于性能较高的要求,master不做持久化操作,slave 做持久化操作

5.redis 保证数据一致性

第一种:先删除缓存,再更新数据库

在出现失败时可能出现的问题:

1:线程A删除缓存成功,线程A更新数据库失败;

2 :线程B从缓存中读取数据;由于缓存被删,进程B无法从缓存中得到数据,进而从数据库读取数据;此时数据库中的数据更新失败,线程B从数据库成功获取旧的数据,然后将数据更新到了缓存。
最终,缓存和数据库的数据是一致的,但仍然是旧的数据。

第二种:先更新数据库,再删除缓存

假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生

(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存
如果发生上述情况,确实是会发生脏数据。
然而,发生这种情况的概率又有多少呢?
发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。
数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。
先更新数据库,再删缓存依然会有问题,不过,问题出现的可能性会因为上面说的原因,变得比较低。

第三种:给所有的缓存一个失效期

第三种方案可以说是一个大杀器,任何不一致,都可以靠失效期解决,失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定。
1.并发不高的情况:
读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;
写: 写mysql->成功,再写redis;
2.并发高的情况:
读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;
写:异步话,先写入redis的缓存,就直接返回;定期或特定动作将数据保存到mysql,可以做到多次更新,一次保存;

第四种:加锁,使线程顺序执行

如果一个服务部署到了多个机器,就变成了分布式锁,或者是分布式队列按顺序去操作数据库或者 Redis,带来的副作用就是:数据库本来是并发的,现在变成串行的了,加锁或者排队执行的方案降低了系统性能,所以这个方案看起来不太可行。

第五种:采用双删

先删除缓存,再更新数据库,当更新数据后休眠一段时间再删除一次缓存。(php 单线程,会造成阻塞)

第六种:异步更新缓存(基于订阅binlog的同步机制)

MySQL binlog增量订阅消费+消息队列+增量数据更新到redis读Redis

热数据基本都在Redis写MySQL:增删改都是操作MySQL更新Redis数据:MySQ的数据操作binlog,来更新到Redis:

1)数据操作主要分为两大块:一个是全量(将全部数据一次写入到redis)一个是增量(实时更新)。

这里说的是增量,指的是mysql的update、insert、delate变更数据。

2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。
这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。
这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。

8.redis bigkey 问题

通常我们会将含有较大数据或含有大量成员、列表数的Key称之为大Key,下面我们将用几个实际的例子对大Key的特征进行描述:

  • 一个STRING类型的Key,它的值为5MB(数据过大)
  • 一个LIST类型的Key,它的列表数量为20000个(列表数量过多)
  • 一个ZSET类型的Key,它的成员数量为10000个(成员数量过多)
  • 一个HASH格式的Key,它的成员数量虽然只有1000个但这些成员的value总大小为100MB(成员体积过大)

   bigkey 带来的问题:

 bigkey 的读写操作会阻塞线程,降低 Redis 的处理效率
在 Redis 基本 IO 模型中,主要是主线程在执⾏操作,任何耗时的操作,例如 bigkey、全量返回等操作,都是潜在的性能瓶颈。
AOF 重写过程中:主进程 fork 出后台的⼦进程会阻塞住⼦进程,阻塞时间取决于整个实例的内存⼤⼩。当主线程收到新写或修改的操作时,主线
程会申请新的内存空间,⽤来保存新写或修改的数据,如果操作的是 bigkey,也就是y,也就是数据量⼤的集合类型数据,那么主线程会因为申请⼤空间⽽⾯
临阻塞风险。因为操作系统在分配内存空间时,有查找和锁的开销,这就会导致阻塞。
  bigkey发现

使⽤ redis-cli 客户端的命令 --bigkeys
⽣成 rdb ⽂件,离线分析 rdb ⽂件。⽐如:redis-rdb-cli,rdbtools;

redis-cli -h 127.0.0.1 -p 6379 -a '123456' --bigkeys



通过 scan 命令,对扫描出来的key进⾏类型判断,例如:string长度⼤于10K,list长度⼤于10240认为是big bigkeys

优化BigKey
拆,big list: list1、list2、…listN
big hash:可以讲数据分段存储,⽐如⼀个⼤的key,假设存了1百万的⽤户数据,可以拆分成 200个key,每个key下⾯存放5000个⽤户数据
控制key的⽣命周期
建议使⽤expire设置过期时间(条件允许可以打散过期时间,防⽌集中过期)。
如果⽆法避免使⽤BigKey
可以使⽤其他的存储形式,⽂档性数据库 MongoDB。

BigKey删除

1. 字符串
⼀般来说,对于string类型使⽤del命令不会产⽣阻塞。
del bigkey
2. hash
使⽤hscan命令,每次获取部分(例如100个)field-value,在利⽤hdel删除每个field(为了快速可以使⽤pipeline)。

3.list
Redis并没有提供lscan这样的API来遍历列表类型,但是提供了ltrim这样的命令可以渐进式的删除列表元素,直到把列表删除。

4. set
使⽤sscan命令,每次获取部分(例如100个)元素,在利⽤srem删除每个元素。

5. sorted set
使⽤zscan命令,每次获取部分(例如100个)元素,在利⽤zremrangebyrank删除元素。

9.redis 脑裂问题

脑裂是指因为网络问题,导致redis master节点跟redis slave节点和sentinel集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。此时存在两个不同的master节点,就像一个大脑分裂成了两个。
集群脑裂问题中,如果客户端还在基于原来的master节点继续写入数据,那么新的master节点将无法同步这些数据,当网络问题解决之后,sentinel集群将原先的master节点降为slave节点,此时再从新的master中同步数据,将会造成大量的数据丢失。

redis的配置文件中存在两个参数

    min-slaves-to-write 3 (连接master的最少slave数量),

    min-slaves-max-lag 10(slave连接到master的最大延迟时间)

    按照上面的配置,要求至少有3个slave节点,且数据复制和同步延迟不能超过10秒否则的话master就拒绝写入,配置这两个参数,如果发生了脑裂master就会拒绝客户端的写入,这样写操作就会在新的master上。

10.redis 雪崩,缓存穿透,缓存击穿

   1,redis 雪崩大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。

  • 均匀设置过期时间;
  • 互斥锁;
  • 双 key 策略;
  • 后台更新缓存;

   2,缓存击穿如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。直接访问数据库,在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题

  •  互斥锁方案:保证同一时间只允许一个线程重建缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值
  • 永不过期:不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间

  3,缓存穿透当发生缓存雪崩或击穿时,数据库中还是保存了应用要访问的数据,一旦缓存恢复相对应的数据,就可以减轻数据库的压力,而缓存穿透就不一样了。当用户访问的数据既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当短时间内有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题

应对缓存穿透的方案,常见的方案有三种。

  • 非法请求的限制;
  • 缓存空值或者默认值;
  • 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在;

11.redis主动工作原理,同步数据应该注意的问题

      

12.redis 阻塞问题

del 命令 hash  和set zset

13 randomkey 过期

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐