这里推荐哔站狂神视频,也是文章的部分来源。也包括Redis中文网。
哔哩哔哩狂神说
Redis中文官方网站
Redis官网
Redis的请求和相应
Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。
通常情况下Redis的一个请求会遵循以下步骤:
- 客户端向服务端发送一个查询请求,并监听Socket,通常是以阻塞模式,等待服务端的响应。
- 服务端收到请求后,服务端处理命令,并将结果返回给客户端。 Redis的客户端和服务端是通过网络进行连接的。不论网络连接的快慢,或者是网络延时,数据包总是能够从客户端到达服务端,并从服务端返回数据到客户端。而这个时间被称之为往返时间(RTT)。
因为Redis有一定的往返时间,即便速度再快,并且一次只能处理一个请求,等待服务器响应后才能发送第二个请求。这样大大降低了性能。
为了解决这个问题,便出现了Redis管道(Pipelining)。
Redis管道(Pipelining)
一次的请求和响应,即便旧的请求还没有被响应,也能够处理新的请求。将多个命令发送到服务器,不需要等待响应的结果,最后在一个步骤中读取该响应即可。
注意
在使用管道发送命令的时候,服务器会被迫回复一个队列答复,占用很多内存。如果需要发送大量的命令,最好的办法就是将这些命令按照合理的数量分批次的处理。发送一部分的命令(比如10K大小),读回复,然后再发送一部分的命令(10K大小)。这样的话,我们需要非常大量的内存用来组织返回的数据内容。
这里我们通过Java代码进行测试。
@Test
public void test() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 不使用管道技术
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
jedis.lpush("key", "" + i);
}
long endTime = System.currentTimeMillis();
System.out.println("不使用管道技术:" + (endTime - startTime) + "ms");
// 使用管道技术
long startTime2 = System.currentTimeMillis();
Pipeline pipelined = jedis.pipelined();
for (int i = 0; i < 100000; i++) {
pipelined.lpush("key", "" + i);
}
List<Object> list = pipelined.syncAndReturnAll();
long endTime2 = System.currentTimeMillis();
System.out.println("使用管道技术:" + (endTime2 - startTime2) + "ms");
// 清空所有的数据
jedis.flushAll();
jedis.disconnect();
}
这里可以看到使用管道技术耗时比不使用管道时间耗时要少得多。
Redis的过期(EXPIRE key seconds)
在Redis中,我们可以为我们的key设置过期时间,超多key的过期时间后,将会自动删除该key。
设置key的过期时间时,如果返回值为1,代表设置成功。如果返回值为0,设置失败。
通常我们创建的keys是没有设置过期时间的,它们会一直存在,不会过期,只能使用命令移除。
超时后只有对key执行DEL命令或者SET命令或者GETSET时才会清除。
我们也可以使用PERSIST命令可以清除超时,使其变成一个永久的key。
刷新过期时间
对已经有过期时间的key执行EXPIRE操作,将会更新它的过期时间。有很多应用有这种业务场景,例如记录会话的session。
Redis是如何淘汰过期的keys的?
Redis keys的过期方式有两种,被动方式和主动方式。
当一些客户端尝试访问它时,key会被发现并主动的过期。当时有些key我们永远都不会访问。遇到这种情况,我们就需要定时随机测试这些key的过期时间,将这些过期的key从Redis中删除。
我们可以设置特定的时间,对随机的某些key进行过期检测,删除已经过期的所有的key。如果有多余25%的key过期了,我们就重复进行检测。直到我们检测到的过期的key低于25%。这样设置的目的主要是为了确保,在任何时候,我们最多只会清除25%的过期的keys。
Redis对过期的key的清除策略
惰性删除:
当一些客户端尝试访问它时,key会被发现并主动的过期。放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键
特点:CPU友好,但如果一个key不再使用,那么它会一直存在于内存中,会造成内存空间的浪费。
定时删除:
设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作 。
定期删除:
隔一段时间,程序就对数据库进行一次检查,删除里面的过期键,至于要删除多少过期键,
以及要检查多少个数据库,则由算法决定。 即设置一个定时任务,比如10分钟删除一次过期的key;间隔小则占用CPU,间隔大则浪费内存
例如Redis每秒处理:
- 测试随机的20个keys进行相关过期检测。
- 删除所有已经过期的keys。
- 如果有多于25%的keys过期,重复步奏1。
Redis服务器实际使用的是惰性删除和定期删除两种策略:通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。
我们通过expireIfNeeded函数实现惰性删除策略,当我们操作key的时候进行判断key是否过期。
通过activeExpireCycle函数实现定期删除策略。
Redis可以当做使用LRU算法的缓存来使用
当我们将Redis当做缓存来使用的时候,我们新增加一条数据时,它可以回收之前的旧的数据。LRU是Redis唯一支持的回收方法。
这里有一个Maxmemory配置指令。maxmemory配置指令用于配置Redis存储数据时指定限制的内存大小。通过redis.conf可以对该命令进行设置,或者我们可以使用config set 命令来进行运行时的配置。
# 配置内存限制为100mb
maxmemory 100mb
当 maxmemory 为 0 的时候,代表没有内存限制。当我们选择不同的内存限制大小的时候,需要选择不同的策略。
Redis的回收策略:
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)。
allkeys-lru:尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru:尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random:回收随机的键使得新添加的数据有空间存放。
volatile-random:回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl:回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
Redis的回收进程是如何工作的?
- 一个Redis客户端运行了新的命令,添加了新的数据。
- Redis检查内存使用情况,如果内存使用情况达到了 maxmemory 的限制,就会根据我们之前设定好的策略进行回收。
- 一个新的命令被执行,等等。
- 我们不断的超过Redis的内存限制,不断的进行回收。
Redis发布/订阅
Redis发布订阅是一种消息通信模式,发送者发送消息,订阅者接收消息。
Pub和Sub就是发布(Publish)与订阅(Subscribe),在Redis中,可以设定对key值进行消息发布以及消息订阅,当该key值上进行了消息发布后,所有订阅它的客户端都会接收到相应的消息。
一些简单的实时消息系统和订阅、关注等功能都用到了Redis的订阅和发布。
但是一些复杂的场景,Redis 的订阅和发布就不行了。我们可以使用RocketMQ消息中间件等技术。
Redis主从复制
主从复制。读写分离。在项目中,80%的都是读操作。为了减缓服务器的压力,我们在进行项目架构的时候,写的操作,交给主服务器去处理,而读的操作交给我们的多台从服务器去处理。
Redis从服务器(slave)能够精确的得到并且复制Redis主服务器(master)上的数据内容。当从服务器(slave)和主服务器(master)之间断开连接的时候,从服务器(slave)会自动的和主服务器(master)重新建立连接。这期间主服务器(master)产生的数据的改变,从服务器(slave)都会重新复制到自身。
举个例子:
- 启动一台Redis作为主服务器(master)
# 启动Redis redis-server --port 8000 redis-cli -p 8000 # 向Redis中写入数据 127.0.0.1:8000> set msg doni OK 127.0.0.1:8000> get msg "doni"
- 再启动一台Redis作为从服务器(slave)
# 启动Redis redis-server --port 8001 redis-cli -p 8000 # 查看数据时,数据为空 127.0.0.1:8001> get msg (nil)
- 我们将主服务器(master)中的数据复制到从服务器(slave)
# 数据复制 127.0.0.1:8001> slaveof 127.0.0.1 8000 # 查看数据 127.0.0.1:8001> get msg "doni"
https://www.cnblogs.com/hongmoshui/p/10594639.html
主从复制还需要依靠三个重要的机制:
- 主服务器(master)和从服务器(slave)连接正常的时候,主服务器(master)会向从服务器(slave)发送一连串的命令流来保持对从服务器(slave)的更新。将自身的数据集的改变复制给从服务器(slave)。包括客户端的写入、key的过期或者被逐出等等。
- 当主服务器(master)和从服务器(slave)因为网络问题、或者是主从意识到连接超时,导致断开连接的时候。从服务器(slave)重新连接上主服务器(master)时,并尝试进行部分重同步。
- 当无法进行部分重同步的时候,从服务器(slave)会进行全量同步。这个时候主服务器(master)需要创建所有的数据快照,发送给从服务器(slave),之后在数据集进行更改的时候持续发送命令流到从服务器(slave)。
Redis默认使用的是异步复制,具有低延迟和高性能的特点,是绝大多数Redis用例的自然复制模式。
注意
- Redis使用的是异步复制,从服务器(slave)和主服务器(master)之间异步的确认处理的数据量。
- 一个主服务器(master)可以拥有多个从服务器(slave)。
- 从服务器(slave)可以接收其他从服务器(slave)的连接。
在使用Redis复制功能时的设置中,在主服务器(master)和从服务器(slave)中启用持久化,
主从复制的主要作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,这是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担负载均衡,尤其是在写少读多的场景下,通过多个节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基础:主从复制是哨兵模式和集群能够实现的基础。主从复制是Redis高可用的基础。
主从配置:
默认情况下,每一台Redis服务器都是主节点。我们只需要配置从机就可以了。
# 将地址为192.168.13.165,端口号为6380的服务器作为当前服务器的主机(找谁作为自己老大)
SLAVEOF 192.168.13.165 6380
注意:
主机(master)可以写,从机(slave)不能写,从机(slave)只能读。主机(master)中的所有信息和数据,都会被从机(slave)保存。
主机(master)断开连接,从机(slave)依旧连接到主机(master)的,但是没有写操作了。主机(master)重新连接,从机(slave)依旧可以直接获取到主机(master)写的信息。
Redis为什么会有主从复制
虽然Redis是基于内存的数据库,读取写入的速度都特别快。但是在大量访问数据库的时候,还是会产生读取压力特别大的情况。为了分担读取的压力,Redis支持主从复制。Redis主从复制可以分为全量同步和增量同步。
复制原理:
slave 启动成功连接到 master 后会发送一个sync同步命令。
master 接收到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令。在后台进程执行完毕以后,master 将传送整个数据文件到slave ,并完成一次完全同步。
Redis复制主要分为全量复制和增量复制。
全量同步
- 从服务器(slave)连接主服务器(master),发送SYNC命令。
- 主服务器(master)接收到SYNC命令后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令。
- 主服务器(master)BGSAVE执行完毕之后,向所有的从服务器(slave)发送快照文件,并在发送期间继续记录被执行的写命令。
- 从服务器(slave)收到快照文件后,丢弃所有的旧数据,载入收到的快照。
- 主服务器(master)快照发送完毕后开始向从服务器(slave)发送缓冲区中的写命令。
- 从服务器(slave)完成对快照的载入,开始接收命令请求,并执行来自主服务器(master)此时可以接收来自用户的读请求。
增量同步
Redis的增量同步指的是从服务器(slave)初始化后开始正常工作时,主服务器(master)发生的写操作同步到从服务器的过程。
增量同步的过程主要是主服务器(master)每执行一个写命令就会向从服务器(slave)发送相同的写命令,从服务器(slave)接收并执行收到的写命令。
全量复制:slave 服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:master 继续将新的所有收集到的修改命令依次传给slave,完成同步。
只要我们重新连接master ,全量复制将会被执行。
在集群环境中,当主机挂掉的时候,我们可以通过 SLAVEOF on one 命令,将自己设置为master。
# 将自己设置为主机(master)
SLAVEOF on one
Redis的主从同步策略
主服务器(master)和从服务器刚开始进行连接的时候,进行全量同步;全量同步完成以后,进行增量同步。
如果是在需要的情况下,从服务器(slave)会在任何时候都发起全量同步。
Redis主从复制原理总结
Redis主从复制的特点
- 主服务器(master)可以有多个从服务器(slave)。
- 除了多个从服务器(slave)连到相同的主服务器(master)外,从服务器(slave)也可以连接其他从服务器(slave)形成图状结构。
- 主从复制不会阻塞主服务器(master)当一个或多个从服务器(slave)与主服务器(master)进行初次同步数据时,主服务器(master)可以继续处理client发来的请求。相反,从服务器(slave)在初次同步数据时则会阻塞不能处理client的请求。
- 主从复制可以用来提高系统的可伸缩性,我们可以用多个从服务器(slave)专门用于client的读请求,比如sort操作可以使用从服务器(slave)来处理。也可以用来做简单的数据冗余。
- 可以在主服务器(master)禁用数据持久化,只需要注释掉主服务器(master)配置文件中的所有save配置,然后只在从服务器(slave)上配置数据持久化。
Redis的持久化(重点)
由于Redis是基于内存的数据库,如果不进行数据库持久化操作,服务器进程退出以后,服务器中的数据库状态也会消失。
Redis的两种持久化方式:RDB(Redis DataBase) 和 AOF(Append Only File)。
RDB(Redis DataBase)
在指定的时间间隔中,将内存中的数据以快照的方式写入磁盘。数据恢复的时候,直接将快照文件读到内存里。
RDB 保存的文件是 dump.rdb。
触发机制:
- save 的规则满足的情况下,会自动触发 rdb 规则。
- 执行 flushall 命令,也会触发 rdb 规则。
- 退出 redis,也会产生 rdb 文件。
AOF(Append Only File)
将我们的所有的命令都记录下来。
恢复的时候,将此文件全部执行一次。
以日志的形式,记录每一个写的操作。将Redis的所有的指令记录下来,只能够追加文件,不能改写文件。Redis启动的时候,会读取该文件重新构建数据库。
哨兵模式
当主服务器(master)宕机以后,需要手动把一台从服务器(slave)切换为主服务器(master),非常的不方便,费时费力。
哨兵模式是一种特殊的模式,Redis提供了哨兵的命令,哨兵是一个独立的进程,会独立运行。
原理是:哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵的作用:
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵检测到master宕机,会自动将slave切换城master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让他们切换主机。
但是一个哨兵对Redis服务器集群进行监控,可能会出现问题。我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
服务器主机宕机,其中一个哨兵先检测到这个结果后,并不会马上进行failover过程。只有后面的哨兵也检测到服务器主机宕机以后,并且达到一定的值时,哨兵之间会进行一次投票,投票由其中一个哨兵发起,进行failover操作。占据票数最多的从服务器会成为新的主服务器。
如果宕机的主机重新启动了,只能够归并到新的主机下,当做从机。
哨兵的优缺点
优点:
哨兵集群,基于主从复制的,所有的主从配置的优点,哨兵模式也全部具备。
主从可以切换,故障可以转移,系统的可用性好。
哨兵模式就是主从复制的升级,由手动到自动,更加的健壮。
缺点:
Redis难以实现在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦。
实现哨兵模式的配置比较麻烦的,里面有很多选择。
Redis的缓存穿透和缓存雪崩
缓存穿透
用户查询一个数据,发现Redis内存数据库中没有,导致了缓存没有命中,然后去持久层数据库中查询。当很多用户去查询Redis内存数据库,并且缓存没有命中,都去请求了持久层数据库,会给持久层数据库很大的压力。便出现了缓存穿透。
解决方案
- 布隆过滤器
一种数据接口,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,避免了对数据库查询的压力。
- 缓存空对象
当Redis缓存没有命中后,返回空的对象也将其缓存起来,设置一个过期时间,之后再次访问这个数据,会从缓存中获取,保护持久层数据库。
但是这种方式也会存在问题:
- 如果空值被存储,浪费缓存空间。
- 虽然对空值设置了过期时间,但是持久层数据库和Redis缓存会存在一段时间的数据不一致问题,对一些数据需要保持一致性的业务会有一定的影响。
缓存击穿
缓存击穿,指一个热点数据,所有的用户请求都对该数据进行访问,当这个数据在失效的一瞬间,持续的大并发就会穿破缓存,直接发送请求查询持久层数据库,并将最新的数据写入缓存中。就会导致数据库压力过大宕机。
解决方案
- 设置热点数据永不过期
- 加互斥锁
使用分布式锁,保证对于每个key同时只有一个线程去查询持久层的数据库,其他线程没有获得该锁的权限,就会进入等待状态。将高并发的压力转移到了分布式锁,因此会对分布式锁的考验很大。
缓存雪崩
概念
在某一个时间段,大部分Redis缓存集中过期失效,所有的请求都会达到持久层数据库,持久层数据库的调用量会暴增,造成持久层数据库宕机。
解决方案
- 限流降级
缓存失效后,通过加锁或者队列来控制数据库写缓存的线程数量。 - 数据预热
在正式部署之前,把可能的数据预先访问一边,加载到缓存中,在发生大并发访问前,手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间均匀。
如果有什么不对的地方,或者不好的地方,还请多多指正,不吝指教。
如果有什么不对的地方,或者不好的地方,还请多多指正,不吝指教。
如果有什么不对的地方,或者不好的地方,还请多多指正,不吝指教。