1、为什么使用Redis?

我觉得在项目中使用 Redis,主要是从两个角度去考虑:性能和并发。

1.1、性能

我们在碰到需要执行耗时特别久,且结果不频繁变动的 SQL,就特别适合将运行结果放入缓存。

Redis作为缓存,数据存在于内存中,因此读取的速度非常快。另外,Redis还提供了持久化方法,可以将内存中的数据保存到硬盘中,这个下面会谈。

1.2、并发

在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。

这时候,将一部分数据缓冲到Redis中,让请求先访问Redis,如果没有,再访问数据库。

2、Redis单线程为什么这么快?

回答主要是以下三点:
纯内存操作
单线程操作,避免了频繁的上下文切换
采用了非阻塞 I/O 多路复用机制

刚才说了,Redis是内存数据库,数据是存放在内存中的,因此访问速度非常快。

那怎么理解单线程呢?

这里的单线程并不是指Redis服务只有一个线程,而是指Redis响应客户端请求的过程是单线程的。

那Redis是怎么响应请求的呢?如果你了解过NIO就会知道,客户端的请求通过tcp/ip来发送数据,我们通过socket来操作这些连接(socket是对tcp/ip的封装),连接有不同的状态,通过I/O多路复用就可以将socket的不同状态路由到不同的事件处理中去,这样就避免了Redis服务的阻塞,而区别于NIO,NIO于Redis都采用多路复用,但NIO仍然是多个线程(Selector占用一个,数据读写虽然一个线程可以处理多个通道,但通道多了就需要一些线程来处理)。

3、Redis 的数据类型,以及每种数据类型的使用场景

Redis有五种数据类型:String,Hash,List,Set,Sorted Set

3.1、String

这个没啥好说的,最常规的 set/get 操作,Value 可以是 String 也可以是数字。一般做一些复杂的计数功能的缓存。

3.2、Hash

这里 Value 存放的是结构化的对象,比较方便的就是操作其中的某个字段。

比如做单点登录的时候,用这种数据结构存储用户信息,以 SessionId作为 Key,设置缓存过期时间,能很好的模拟出类似 Session 的效果,例如:123456(SessionId) user zhangsan。

3.3、List

使用 List 的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用 lrange 命令,做基于 Redis 的分页功能,性能极佳,用户体验好。

3.4、Set

因为 Set 堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用 JVM 自带的 Set 进行去重?

因为我们的系统一般都是集群部署,使用 JVM 自带的 Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。

另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。

3.5、SortedSet

Sorted Set多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。

可以做排行榜应用,取 TOP N 操作。Sorted Set 可以用来做延时任务。最后一个应用就是可以做范围查找。

4、Redis持久化

Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化文件即可实现数据恢复。

4.1、RDB

RDB持久化是把当前进程数据生成快照保存到硬盘的过程。

手动触发:save、bgsave(推荐)

自动触发:在conf文件中默认这样配置,第一行表示900s内有一个key变动则触发(bgsave)。

集成redis是强一致的吗_集群

集成redis是强一致的吗_集成redis是强一致的吗_02

Redis在执行fork的时候,会再启动一个进程(子进程),父进程和子进程同时共享这块内存空间,并将这个时刻的内存中的数据复制一份,写到硬盘中,生成RDB文件。同时父进程仍然可以响应其它命令。

4.1.1、RDB的优缺点

RDB的优点:

  • RDB是一个紧凑压缩的二进制文件,代表Redis在某一个时间点上的数据快照。非常适合用于备份,全量复制等场景。
  • Redis加载RDB恢复数据远远快于AOF方式。

 RDB的缺点:

  • RDB方式数据没办法做到实时持久化/秒级持久化。因为每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。

 针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。

4.2、AOF

 4.2.1、AOF(append only file)持久化:

以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。

开启AOF功能需要设置配置:appendonly yes,默认不开启。

集成redis是强一致的吗_持久化_03

流程如下:

1) 所有的写入命令会追加到aof_buf(缓冲区)中。

2) AOF缓冲区根据对应的策略向硬盘做同步操作。

3) 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。

4) 当Redis服务重启时,可以加载AOF文件进行数据恢复。

简单的说,就是将命令以日志的方式,逐一写到文件中(日志在分布式应用中是非常有效的一种手段,它实现了数据的串行化,解决了对该数据的竞争写入)

4.2.2、AOF缓冲区同步文件策略

之所以要配置这个,是因为文件写入硬盘是需要与操作系统打交道的,即便Redis将数据给了操作系统,操作系统仍然会将这个数据放入缓存中,批量写入,操作系统默认的配置是30s,如果这段时间,操作系统出了问题,那么这部分数据就丢失了,Redis可以使用参数appendfsync控制,不同值的含义如表所示:

集成redis是强一致的吗_单线程_04

一般情况下,配置everysec即可。

4.2.2、重写机制

随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。

重写使用进程内数据直接生成,并不依赖原来的AOF文件,这样新的AOF文件只保留最终数据的写入命令。

 AOF重写过程可以手动触发和自动触发:

  • 手动触发:直接调用bgrewriteaof命令
  • 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机
  • auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默认为64MB
  • auto-aof-rewrite-percentage:代表当前AOF文件空间(aof_current_size)和上一次重写后AOF文件空间(aof_base_size)的值

集成redis是强一致的吗_集成redis是强一致的吗_05

相比于上一张图,这张图表明,在执行fork操作之后,3.1会继续向旧AOF文件写入,3.2则会将新执行的命令写入到新的AOF文件中,并将旧文件替换。

4.3、重启加载

 AOF和RDB文件都可以用于服务器重启时的数据恢复。

集成redis是强一致的吗_持久化_06

由此可见:Redis在启动的时候,优先以AOF方式恢复数据,如果没有AOF文件,则用RDB文件恢复。

5、Redis集群

Redis集群的搭建方式类似MySQL,采用主从和分库分表的方式。分库分表在Redis集群中的术语叫做Hash slot。

5.1、主从模式

集成redis是强一致的吗_单线程_07

对比到MySQL中,两者在原理上是相同的,主Redis服务器负责写入,从Redis服务器负责读取,这种结构可以通过扩展slave的数量来解决读并发的问题,但是因为只有一个Master,那么这个Master的写入能力就成为了瓶颈,而且,各个slave中的数据都是相同的,那么就造成了大量的数据冗余。

搭建这个主从模式,用到了之前讲到的RDB持久化方式,Master会定期(可配置)将数据快照(RDB)发送给Salve,Slave先将Master那边获取到的信息写入磁盘,再load进内存,完成数据的初始化,然后Master再将执行的命令发送给Slave(2.8版本之后),Slave通过执行这些命令进行增量更新。

5.2、Hash slot

集成redis是强一致的吗_Redis_08

1、 对象保存到Redis之前先经过CRC16算法哈希到一个指定的Node上,例如Object4最终Hash到了Node1上。
2、 Redis内置了16384个Slot,每个Node被平均分配了一个Slot段,对应着0-16384,每个Node的Slot不能重复也不能缺失,否则会导致对象重复存储或无法存储。
3, Node之间也互相监听,一旦有Node退出或者加入,会按照Slot为单位做数据的迁移。例如Node1如果掉线了,0-5640这些Slot将会平均分摊到Node2和Node3上,由于Node2和Node3本身维护的Slot还会在自己身上不会被重新分配,所以迁移过程中不会影响到5641-16384Slot段的使用。

简单总结下哈希Slot的优缺点:

缺点:每个Node承担着互相监听、高并发数据写入、高并发数据读出,工作任务繁重

优点:将Redis的写操作分摊到了多个节点上,提高写的并发能力,扩容简单。

5.3、集群

集成redis是强一致的吗_单线程_09

 

Redis集群是用了以上两种方式,Hash slot的Node采用主从模式,将读写分离,通过CRC16和Slot实现了数据的路由,读写操作被路由到相应的Redis服务器上,解决了并发和数据冗余问题。

6、Redis 的过期策略以及内存淘汰机制

比如你 Redis 只能存 5G 数据,可是你写了 10G,那会删 5G 的数据。怎么删的,这个问题思考过么?

还有,你的数据已经设置了过期时间,但是时间到了,内存占用率还是比较高,有思考过原因么?

因为Redis 采用的是定期删除+惰性删除策略。

定期删除,Redis 默认每隔 100ms 检查,是否有过期的 Key,有过期 Key 则删除。

需要说明的是,Redis 不是每隔 100ms 将所有的 Key 检查一次,而是随机抽取进行检查(如果每隔 100ms,全部 Key 进行检查,Redis 岂不是卡死)。

因此,如果只采用定期删除策略,会导致很多 Key 到时间没有删除。于是,惰性删除派上用场。

也就是说在你获取某个 Key 的时候,Redis 会检查一下,这个 Key 如果设置了过期时间,那么是否过期了?如果过期了此时就会删除。

采用定期删除+惰性删除就没其他问题了么?

不是的,如果定期删除没删除 Key。然后你也没即时去请求 Key,也就是说惰性删除也没生效。这样,Redis的内存会越来越高。那么就应该采用内存淘汰机制。

在 redis.conf 中有一行配置:maxmemory-policy allkeys-lru

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。推荐使用。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。应该也没人用吧,你不删最少使用 Key,去随机删。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。不推荐。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。不推荐。

7、使用Redis有哪些需要注意的地方?

7.1、缓存和数据库双写一致性问题

一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。

先明白一个前提,就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。

另外,我们所做的方案从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。

回答:首先,采取正确更新策略,先更新数据库,再更新缓存。其次,因为可能存在更新缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。

7.2、如何解决 Redis 的并发竞争 Key 问题?

这个问题大致就是,同时有多个子系统去 Set 一个 Key。这个时候大家思考过要注意什么呢?

可以使用Redis事务机制,但并不推荐使用 Redis 的事务机制。因为我们的生产环境,基本都是 Redis 集群环境,做了数据分片操作。你一个事务中有涉及到多个 Key 操作的时候,这多个 Key 不一定都存储在同一个 redis-server 上。因此,Redis 的事务机制,十分鸡肋。

这个问题可以利用队列,将 set 方法变成串行访问,方法多样。

以上内容如有错误,欢迎指正。