0%

Redis

1. Redis为什么早期选择单线程?

其实就是历史原因,开发者嫌多线程麻烦,后来这个CPU的利用问题就被抛给了使用者。

Redis 4.0 之后开始变成多线程,除了主线程外,它也有后台线程在处理一些较为缓慢的操作,例如清理脏数据、无用连接的释放、大 Key 的删除等等。

2. Redis6.0使用多线程是怎么回事?

其实Redis在执行命令时,使用的还是单线程;在执行I/O和协议解析等操作时,执行的是多线程。

这是因为Redis的性能瓶颈在网络I/O而非CPU。

Redis6.0多线程

3. Redis持久化方式有哪些?有什么区别?

Redis持久化方式有AOF和RDB。

  • AOF:以独立日志的方式记录每次写命令, 重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。
  • RDB:RDB方式则是将当前数据快照存储到磁盘,分手动触发和自动触发,RDB文件是一个压缩的二进制文件。

4. RDB 和 AOF 各自有什么优缺点?

RDB:

  • 优点:
    • 数据恢复快
    • 容灾性好,可以将备份数据远程拷贝到其他机器,用于容灾恢复
  • 缺点:
    • 实时性低,RDB并不能做到秒级持久化,而是会间隔一段时间再持久化

AOF:

  • 优点:
    • 实时性好
  • 缺点:
    • AOF文件较大,并且恢复速度慢

5. Redis的主从复制原理了解吗?

  1. 首先在从节点(slave)保存主节点的ip和端口号
  2. 从节点(slave)发现主节点(master)后,向主节点(master)发送ping请求进行首次通信,本次通信是检测主从之间网络套接字是否可用、主节点当前是否可以接受处理命令。
  3. 若主节点要求密码验证,从节点需要发送账号密码用于验证
  4. 建立连接后,主节点会把数据全部发送给从节点
  5. 之后的主节点会持续把写命令发送给从节点,保证数据一致性

6. 主从复制存在哪些问题呢?

主从复制虽好,但也存在一些问题:

  • 一旦主节点出现故障,需要手动将一个从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令其他从节点去复制新的主节点,整个过程都需要人工干预。
  • 主节点的写能力受到单机的限制。
  • 主节点的存储能力受到单机的限制。

第一个问题是Redis的高可用问题,第二、三个问题属于Redis的分布式问题。

7. Redis Sentinel(哨兵)了解吗?

主从复制存在一个问题,没法完成自动故障转移。所以我们需要一个方案来完成自动故障转移,它就是Redis Sentinel(哨兵)。

Redis Sentinel ,它由两部分组成,哨兵节点和数据节点:

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

8. 什么是缓存击穿、缓存穿透、缓存雪崩?

  • 缓存击穿:缓存击穿是指访问量很大的某个key突然失效,请求一下全部打到数据库。解决方案如下:
    • 若缓存是基本不更新的,可以设置为永不过期
    • 若缓存中数据经常使用,且为热点数据,那么可以每查询一次便重新更新他的失效时间(我在项目中就是这样做的)
    • 加锁更新,比如请求查询A,发现缓存中没有,对A这个key加锁,同时去数据库查询数据,写入缓存,再返回给用户,这样后面的请求就可以从缓存中拿到数据了(感觉这样性能比较差)
  • 缓存穿透:缓存穿透是指查询的是缓存中和数据库都不存在的数据,这样每次请求都打到数据库上。解决方案如下:
    • 每次都数据库查数据时,如果没查到数据,就写一个空值到缓存中去,再设置一个失效时间,这样再有相同请求走的都是缓存。
  • 缓存雪崩:缓存雪崩是指大量缓存突然失效,造成大量请求打到数据库
    • 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。
    • 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
    • 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

9. 什么是布隆过滤器?

布隆过滤器,它是一个连续的数据结构,每个存储位存储都是一个bit,即0或者1, 来标识数据是否存在。

存储数据的时时候,使用K个不同的哈希函数将这个变量映射为bit列表的的K个点,把它们置为1。

我们判断缓存key是否存在,同样,K个哈希函数,映射到bit列表上的K个点,判断是不是1:

  • 如果全不是1,那么key不存在;
  • 如果都是1,也只是表示key可能存在。

简单说,布隆过滤器就是判断某个数据在不在布隆过滤器内,使用方式如下:

Redis实现布隆过滤器的底层是通过bitmap数据结构。

1
2
3
4
5
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.4</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:26379");
config.useSingleServer().setPassword("myredis");
config.useSingleServer().setDatabase(0);
RedissonClient client = Redisson.create(config);
RBloomFilter<Object> bloomFilter = client.getBloomFilter("bloomnumber");
// 初始化布隆过滤器,设计预计元素数量为1000000L, 误差率为1%
int n = 1000000;
bloomFilter.tryInit(1000000L, 0.01);
for (int i = 0; i < n; i++) {
bloomFilter.add(String.valueOf(i));
}
int count = 0;
for (int i = 0; i < (n*2); i++) {
if (bloomFilter.contains(String.valueOf(i))) {
count++;
}
}
System.out.println("过滤器误判率:" + (count - n)/Double.valueOf(n));
}

布隆过滤器也有一些缺点:

  1. 它在判断元素是否在集合中时是有一定错误几率,因为哈希算法有一定的碰撞的概率。
  2. 不支持删除元素。

在openArt项目中,我们在判断用户短信验证码是否重复验证时使用了。

10. Redis的过期淘汰策略?

Redis的过期淘汰策略是:定时删除 + 惰性删除。

Redis的默认定时100ms拿出一批数据来删除其中过期的数据。

但是如果数据量过大,有10wkey,就不可能每次删除都遍历一遍看是否过期然后删除,那样性能消耗过高。所以Redis执行的是定时随机抽取一批数据,看里面是否有过期的数据,如果过期,便删除。

但是这样会有大量过期的Key没被删除,这样就会走内存淘汰策略

Redis的内存淘汰策略有以下几种:

  • 当内存不足以容纳新写入数据时,新写入操作会报错(很少有人用)
  • 当内存不足以容纳新写入数据时,在键空间内,删除最近最久未使用的key(常用)
  • 当内存不足以容纳新写入数据时,随机干掉某个key(很少有人用)
  • 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近 最少使用的 key(这个一般不太合适)
  • 在设置了过期时间的键空间中,随机 移除某个 key

11. Redis报内存不足怎么处理?

  • 增加Redis可用内存
  • 修改内存淘汰策略
  • 使用 Redis 集群模式,进行横向扩容

12. 大key问题了解吗?

大key的成因:

  • 单个的key的value很大,超过10kb
  • list、set、zset或hash存储了过多元素

大key会造成什么问题呢?

  • 客户端耗时增加,甚至超时

  • 对大key进行IO操作时,会严重占用带宽和CPU

  • 造成Redis集群中数据倾斜

  • 主动删除、被动删等,可能会导致阻塞

如何找到大key?

  • bigkeys命令:使用bigkeys命令以遍历的方式分析Redis实例中的所有Key,并返回整体统计信息与每个数据类型中Top1的大Key
  • redis-rdb-tools:redis-rdb-tools是由Python写的用来分析Redis的rdb快照文件用的工具,它可以把rdb快照文件生成json文件或者生成报表用来分析Redis的使用详情。

怎么处理大key?

  • 删除大key
    • 当Redis版本大于4.0时,可使用UNLINK命令安全地删除大Key,该命令能够以非阻塞的方式,逐步地清理传入的Key。
    • 当Redis版本小于4.0时,避免使用阻塞式命令KEYS,而是建议通过SCAN命令执行增量迭代扫描key,然后判断进行删除。
  • 压缩和拆分key
    • 当vaule是string时,比较难拆分,则使用序列化、压缩算法将key的大小控制在合理范围内,但是序列化和反序列化都会带来更多时间上的消耗。
    • 当value是string,压缩之后仍然是大key,则需要进行拆分,一个大key分为不同的部分,记录每个部分的key,使用multiget等操作实现事务读取。
    • 当value是list/set等集合类型时,根据预估的数据规模来进行分片,不同的元素计算后分到不同的片。

13. Redis底层数据结构有哪些,编码方式有哪些?

  • 底层数据结构:ZipList、SDS、SkipList、ht(字典)、list、intset(整数集合)
类型-编码-结构

14. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?

使用 keys 指令可以扫出指定模式的 key 列表。但是要注意 keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

欢迎关注我的其它发布渠道