前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >超详细图文之Redis(下篇)

超详细图文之Redis(下篇)

作者头像
爪哇缪斯
发布2023-05-10 11:01:58
2150
发布2023-05-10 11:01:58
举报
文章被收录于专栏:爪哇缪斯

一、NoSQL四大分类整体对比


二、Redis安装

  • 官网下载最新版本redis (https://redis.io或http://www.redis.cn)
  • 解压redis-6.2.6.tar,然后进入redis的解压目录,执行make进行安装操作
  • 修改redis.conf,配置它后台运行
  • 运行redis
  • 使用redis客户端连接redis(如果是本机安装且采用默认port,则可以不指定-h和-p)
  • 关闭redis服务

三、Redis-benchmark测试工具

  • redis自带的用于测试redis性能的工具,它具有如下的可配置参数
  • 我们来进行压测,开启200个并发,总请数为20万
  • 我们看一下GET的压测结果
代码语言:javascript
复制
====== SET ======
  200000 requests completed in 1.74 seconds
  100 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration "save": 3600 1 300 100 60 10000
  host configuration "appendonly": no
  multi-thread: no

Latency by percentile distribution:
0.000% <= 0.151 milliseconds (cumulative count 1)
50.000% <= 0.423 milliseconds (cumulative count 115987)
75.000% <= 0.447 milliseconds (cumulative count 153373)
87.500% <= 0.495 milliseconds (cumulative count 175602)
93.750% <= 0.583 milliseconds (cumulative count 187813)
96.875% <= 0.671 milliseconds (cumulative count 193905)
98.438% <= 0.751 milliseconds (cumulative count 197076)
99.219% <= 0.807 milliseconds (cumulative count 198570)
99.609% <= 0.871 milliseconds (cumulative count 199245)
99.805% <= 0.975 milliseconds (cumulative count 199623)
99.902% <= 1.103 milliseconds (cumulative count 199807)
99.951% <= 1.431 milliseconds (cumulative count 199903)
99.976% <= 2.047 milliseconds (cumulative count 199952)
99.988% <= 2.391 milliseconds (cumulative count 199976)
99.994% <= 2.583 milliseconds (cumulative count 199988)
99.997% <= 2.663 milliseconds (cumulative count 199994)
99.998% <= 2.711 milliseconds (cumulative count 199997)
99.999% <= 2.735 milliseconds (cumulative count 199999)
100.000% <= 2.751 milliseconds (cumulative count 200000)
100.000% <= 2.751 milliseconds (cumulative count 200000)

Cumulative distribution of latencies:
0.000% <= 0.103 milliseconds (cumulative count 0)
0.003% <= 0.207 milliseconds (cumulative count 6)
0.015% <= 0.303 milliseconds (cumulative count 30)
20.573% <= 0.407 milliseconds (cumulative count 41146)
88.780% <= 0.503 milliseconds (cumulative count 177561)
94.910% <= 0.607 milliseconds (cumulative count 189819)
97.677% <= 0.703 milliseconds (cumulative count 195355)
99.285% <= 0.807 milliseconds (cumulative count 198570)
99.704% <= 0.903 milliseconds (cumulative count 199409)
99.843% <= 1.007 milliseconds (cumulative count 199687)
99.903% <= 1.103 milliseconds (cumulative count 199807)
99.939% <= 1.207 milliseconds (cumulative count 199878)
99.947% <= 1.303 milliseconds (cumulative count 199893)
99.950% <= 1.407 milliseconds (cumulative count 199900)
99.955% <= 1.503 milliseconds (cumulative count 199910)
99.959% <= 1.607 milliseconds (cumulative count 199917)
99.963% <= 1.703 milliseconds (cumulative count 199925)
99.966% <= 1.807 milliseconds (cumulative count 199933)
99.971% <= 1.903 milliseconds (cumulative count 199942)
99.975% <= 2.007 milliseconds (cumulative count 199949)
99.978% <= 2.103 milliseconds (cumulative count 199956)
100.000% <= 3.103 milliseconds (cumulative count 200000)

Summary:
  throughput summary: 115273.77 requests per second
  latency summary (msec):
  avg       min       p50       p95       p99       max
0.447     0.144     0.423     0.615     0.783     2.751

四、Redis的数据库介绍

  • Redis默认配置了16个数据库,默认我们使用的是序号=0的这个数据库。 /Users/muse/redis-6.2.6/redis.conf配置信息如下所示:
  • 切换数据库 SELECT [数据库序号(从0开始)]
  • 查看该数据库的大小 DBSIZE
  • 不同的数据库,存储不同的信息
  • 清空当前数据库中的数据 FLUSHDB
  • 清空所有数据库中的数据 FLUSHALL

五、Redis三种特殊类型

5.1> geospatial地理位置

5.1.1> 概述

  • 可以用于基于地理位置的业务场景。比如查询两地直接的举例,方圆几里存在的地理位置等等。
  • Redis提供了geospatial相关的8个指令,如下图所示:

5.1.2> GEOADD(v3.2.0)

  • 官方文档: http://www.redis.cn/commands/geoadd.html
  • 指令格式:GEOADD key longitude latitude member [longitude latitude member ...]
  • 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。
  • 这些数据将会存储到Zset,这样的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。
  • 该命令以采用标准格式的参数x,y,所以经度必须在纬度之前
  • 这些坐标的限制是可以被编入索引的,区域面积可以很接近极点但是不能索引。具体的限制,由EPSG:900913 / EPSG:3785 / OSGEO:41001 规定如下:
  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。当坐标位置超出上述指定范围时,该命令将会返回一个错误。
  • 操作示范 经纬度查询 https://jingweidu.bmcx.com):

5.1.3> GEODIST(v3.2.0)

  • 官方文档: http://www.redis.cn/commands/geodist.html
  • 指令格式:GEODIST key member1 member2 [unit]
  • 返回两个给定位置之间的距离。如果两个位置之间的其中一个不存在, 那么命令返回空值。
  • 指定单位的参数unit必须是以下单位的其中一个:

unit

解释

m

表示单位为米(默认值)

km

表示单位为千米

mi

表示单位为英里

ft

表示单位为英尺

  • 如果用户没有显式地指定单位参数, 那么GEODIST默认使用米作为单位。
  • GEODIST命令在计算距离时会假设地球为完美的球形,在极限情况下,这一假设最大会造成 0.5%的误差。
  • 操作示范

5.1.4> GEOHASH(v3.2.0)

  • 官方文档: http://www.redis.cn/commands/geohash.html
  • 指令格式:GEOHASH key member [member ...]
  • 返回一个或多个位置元素的Geohash表示。通常使用表示位置的元素使用不同的技术,使用Geohash位置52点整数编码。由于编码和解码过程中所使用的初始最小和最大坐标不同,编码的编码也不同于标准。
  • Geohash字符串属性 该命令将返回11个字符的Geohash字符串,所以没有精度Geohash。返回的geohashes具有以下特性:
  • 他们可以缩短从右边的字符。它将失去精度,但仍将指向同一地区。
  • 它可以在geohash.org网站使用,网址 http://geohash.org/<geohash-string>。查询例子:http://geohash.org/sqdtr74hyu0
  • 操作示范

5.1.5> GEOPOS(v3.2.0)

  • 官方文档: http://www.redis.cn/commands/geopos.html
  • 指令格式:GEOPOS key member [member ...]
  • 从key里返回所有给定位置元素的位置(经度和纬度)
  • 因为GEOPOS命令接受可变数量的位置元素作为输入,所以即使用户只给定了一个位置元素,命令也会返回数组回复。
  • 操作演示

5.1.6> GEORADIUS(v3.2.0)

  • 官方文档: http://www.redis.cn/commands/georadius.html
  • 指令格式:GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
  • 以给定的经纬度为中心,返回键包含的位置元素当中,与中心的距离不超过给定最大距离的所有位置元素。范围可以使用以下其中一个单位:

unit

解释

m

表示单位为米(默认值)

km

表示单位为千米

mi

表示单位为英里

ft

表示单位为英尺

  • 在给定以下可选项时,命令会返回额外的信息:
  • 命令默认返回未排序的位置元素。通过以下两个参数,用户可以指定被返回位置元素的排序方式:
  • 在默认情况下,GEORADIUS命令会返回所有匹配的位置元素。虽然用户可以使用COUNT <count>选项去获取前N个匹配元素,但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时,即使只使用COUNT选项去获取少量元素,命令的执行速度也可能会非常慢。但是从另一方面来说,使用COUNT选项去减少需要返回的元素数量,对于减少带宽来说仍然是非常有用的。
  • 操作演示

5.1.7> GEORADIUSBYMEMBER(v3.2.0)

  • 官方文档: http://www.redis.cn/commands/georadiusbymember.html
  • 指令格式:GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
  • 这个命令和GEORADIUS命令一样,都可以找出位于指定范围内的元素,但是GEORADIUSBYMEMBER的中心点是由给定的位置元素决定的,而不是像GEORADIUS那样,使用输入的经度和纬度来决定中心点指定成员的位置被用作查询的中心。
  • 操作演示:

5.2> hyperloglog预估集合的基数

5.2.1> 概述

  • hyperloglog常用的使用场景,一般是非精准性的统计计数。比如:统计访问网站的UV数,商品评论数或点击量等等。
  • HyperLogLog 是一种用于计算唯一事物的概率数据结构(从技术上讲,这称为预估集合的基数)
  • 它占用的空间很小,只需要12KB的内存,可以存储2^64不同的元素数量。但是它的统计是有小于1%的误差的,所以并不适合精准统计的使用场景。
  • Redis提供了hyperloglog相关的3个指令,如下图所示:

5.2.2> PFADD(v2.8.9)

  • 官方文档: http://www.redis.cn/commands/pfadd.html
  • 指令格式:PFADD key element [element ...]
  • 将element集合存储到以key为变量名的HyperLogLog结构中.
  • 操作演示:

5.2.3> PFCOUNT(v2.8.9)

  • 官方文档: http://www.redis.cn/commands/pfcount.html
  • 指令格式:PFCOUNT key [key ...]
  • 获得指定key为变量名的HyperLogLog结构中中元素的个数
  • 操作演示:

5.2.4> PFMERGE(v2.8.9)

  • 官方文档: http://www.redis.cn/commands/pfmerge.html
  • 指令格式:PFMERGE destkey sourcekey [sourcekey ...]
  • 将多个HyperLogLog合并(merge)为一个新的HyperLogLog ,合并后的HyperLogLog的基数接近于所有输入HyperLogLog的可见集合(observed set)的并集。合并得出的HyperLogLog会被储存在目标变量(第一个参数)里面,如果该键并不存在,那么命令在执行之前, 会先为该键创建一个空的.
  • 操作演示:

5.3> bitmap位图

5.3.1> 概述

  • 我们可以利用bitmap指定其二进制位是0或1,来实现类似“是”or“否”的相关操作。它的特点也是占用内存空间特别的小。比如,我们要记录每个用户当天是否活跃(即:是否登录过系统),那么如果我们要记录他一年的是否登录的记录,只需要365个bit即可存储。
  • Redis提供了位图相关的7个指令,我们只针对其中常用的3个进行操作演示。如下图所示:

5.3.2> SETBIT(v2.6.0)

  • 官方文档: http://www.redis.cn/commands/setbit.html
  • 指令格式:SETBIT key offset value
  • 设置或者清空key的value(字符串)在offset处的bit值。那个位置的bit要么被设置,要么被清空,这个由value(只能是0或者1)来决定。当key不存在的时候,就创建一个新的字符串value。要确保这个字符串大到在offset处有bit值。参数offset需要大于等于0,并且小于2^32(限制bitmap大小为512MB)。当key对应的字符串增大的时候,新增的部分bit值都是设置为0。
  • 操作演示:

5.3.3> GETBIT(v2.2.0)

  • 官方文档 http://www.redis.cn/commands/getbit.html
  • 指令格式:GETBIT key offset
  • 获取key中某个offset位置上的值
  • 操作演示:

5.3.4> BITCOUNT(v2.6.0)

  • 官方文档: http://www.redis.cn/commands/pfmerge.html
  • 指令格式:BITCOUNT key [start end]
  • 获取key中从start到end范围内1的个数
  • 操作演示:

六、Redis的事务管理

6.1> 概述

  • 事务的本质,其实就是一组命令的集合。一个事务中的所有命令都会按照命令的顺序去执行,而中间不会被其他命令加塞。
  • Redis提供了事务相关的5个指令,如下图所示:

6.2> MULTI(v1.2.0)

  • 官方文档: http://www.redis.cn/commands/multi.html
  • 指令格式:MULT
  • 标记一个事务块的开始。随后的指令将在执行EXEC时作为一个原子执行。简而言之,我们可以使用MULTI来开启一个事务。
  • 操作演示:

【解释】

我们发现,在事务中每次执行一条指令,就会返回QUEUED,表明指令已经存入了这个事务的执行队列中了。但是需要注意的一点是,只是放入了事务队列,但并没有去执行。那什么时候会执行呢?那就来看一下下个指令EXEC。

6.3> EXEC(v1.2.0)

  • 官方文档: http://www.redis.cn/commands/bitfield.html
  • 指令格式:EXEC
  • 执行事务中所有在排队等待的指令并将链接状态恢复到正常。当使用WATCH时,只有当被监视的键没有被修改,且允许检查设定机制时,EXEC会被执行。简而言之,我们可以使用EXEC来提交一个事务。
  • 操作演示:

【解释】

调用完EXEC之后,正确执行的都会返回OK,并且当我们再次查询account里面的金额的时候,也正确的返回了1100。这就说明,一个事务内的指令是按照顺序执行的。

6.4> DISCARD(v2.2.0)

  • 官方文档: http://www.redis.cn/commands/discard.html
  • 指令格式:DISCARD
  • 刷新一个事务中所有在排队等待的指令,并且将连接状态恢复到正常。如果已使用WATCH,DISCARD将释放所有被WATCH的key。
  • 操作演示:

6.5> WATCH(v2.2.0)

  • 官方文档: http://www.redis.cn/commands/watch.html
  • 指令格式:WATCH
  • 标记所有指定的key被监视起来,在事务中有条件的执行。可以利用WATCH实现Redis的乐观锁
  • 操作演示1:
    • 客户端A:
  • 客户端B:
  • 操作演示2:
    • 在客户端A中我们再次开始事务
  • 客户端B:

【结论】当执行了EXEC指令之后,watch就被隐式的执行了unwatch。如果需要再次监控,就需要再次调用WATCH指令。

6.6> UNWATCH(v2.2.0)

  • 官方文档: http://www.redis.cn/commands/unwatch.html
  • 指令格式:UNWATCH
  • 刷新一个事务中已被监视的所有key。如果执行EXEC或者DISCARD,则不需要手动执行UNWATCH
  • 操作演示:(略)

6.7> 事务中异常的处理

6.7.1> 命令语法错误

  • 针对语法错误,会导致整个事务执行被中断
  • 操作演示

6.7.2> 运行操作错误

  • 针对执行中的异常,只会导致该条指令的执行失败,而不会影响事务中其他的指令
  • 操作演示

七、Redis实现订阅发布

7.1> 概述

  • 如果熟悉消息中间件,那么对发布订阅一定不陌生。发布者Publish一条消息,消息发送到Channel通道中,然后所有订阅了这个通道的订阅者Subscriber都会接收到这条消息。如下图所示:
  • 针对发布订阅,redis提供了9个相关的指令,如下所示:

7.2> SUBSCRIBE(v2.0.0)

  • 官方文档: http://www.redis.cn/commands/subscribe.html
  • 指令格式:SUBSCRIBE channel [channel ...]
  • 订阅给指定频道的信息。一旦客户端进入订阅状态,客户端(Jedis、lettuce等)就只可接受订阅相关的命令: SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE和PUNSUBSCRIBE除了这些命令,其他命令一律失效。
  • 操作演示:

7.3> PUBLISH(v2.0.0)

  • 官方文档: http://www.redis.cn/commands/publish.html
  • 指令格式:PUBLISH channel message
  • 将信息message发送到指定的频道channel
  • 操作演示:

7.4> PSUBSCRIBE(v2.0.0)

  • 官方文档: http://www.redis.cn/commands/psubscribe.html
  • 指令格式:PSUBSCRIBE pattern [pattern ...]
  • 订阅给定的模式(patterns)。如果想输入普通的字符,可以在前面添加\。支持的模式(patterns)有:
  • pattern等于h?llo时(?表示匹配一个字符),会订阅到hello,hallo,hxllo等等
  • pattern等于h*llo时(* 表示匹配0或任意个字符),会订阅到hllo,heeeello等等
  • pattern等于h[ae]llo时([ae]表示a或者e),会订阅到hello,hallo但是不能匹配订阅hillo
  • 操作演示:

7.5> PUBSUB CHANNELS

  • 官方文档: http://www.redis.cn/commands/pubsub.html
  • 指令格式:PUBSUB CHANNELS [pattern]
  • 列出指定channel频道中含有一个或多个订阅者(不包括从模式接收订阅的客户端)。如果pattern未提供,所有的信道都被列出,否则只列出匹配上指定pattern模式的信道被列出.
  • 操作演示:

7.6> PUBSUB HELP(v6.2.0)

  • 官方文档: http://www.redis.cn/commands/unwatch.html
  • 指令格式:PUBSUB HELP

7.7> PUBSUB NUMPAT(v2.8.0)

  • 官方文档: http://www.redis.cn/commands/pubsub.html
  • 指令格式:PUBSUB NUMPAT
  • 返回订阅模式的数量(使用命令PSUBSCRIBE实现)。注意,这个命令返回的不是订阅模式的客户端的数量,而是客户端订阅的所有模式的数量总和。
  • 操作演示:

7.8> PUBSUB NUMSUB(v2.8.0)

  • 官方文档: http://www.redis.cn/commands/pubsub.html
  • 指令格式:PUBSUB NUMSUB [channel-1 ... channel-N]
  • 列出指定信道的订阅者个数(不包括订阅模式的客户端订阅者)
  • 操作演示

7.9> UNSUBSCRIBE(v2.0.0)

  • 官方文档: http://www.redis.cn/commands/unsubscribe.html
  • 指令格式:UNSUBSCRIBE channel [channel ...]
  • 取消普通订阅。指示客户端退订给定的频道,若没有指定频道,则退订所有频道。如果没有频道被指定,即:一个无参数的UNSUBSCRIBE调用被执行,那么客户端使用SUBSCRIBE命令订阅的所有频道都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的频道。该指令是为Jedis或lettuce等客户端服务的。

7.10> PUNSUBSCRIBE(v2.0.0)

  • 官方文档: http://www.redis.cn/commands/punsubscribe.html
  • 指令格式:PUNSUBSCRIBE [pattern [pattern ...]]
  • 取消模式订阅。指示客户端退订指定模式,若果没有提供模式则退出所有模式。如果没有模式被指定,即一个无参数的 PUNSUBSCRIBE 调用被执行,那么客户端使用 PSUBSCRIBE 命令订阅的所有模式都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的模式。该指令是为Jedis或lettuce等客户端服务的。

八、Redis主从复制

8.1> 概述

  • 主从复制,是指将一台Redis服务器的数据复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower);数据是从主节点复制到从节点的。其中,主节点负责写数据(当然有读的权限),从节点负责读数据(它没有写数据的权限)。默认的配置下,每个Redis都是主节点
  • 一个主节点可以有多个从节点,但是一个从节点只能有一个主节点,即:主从节点是1对N的关系。如下图所示:
  • 主从复制的用处
  • 数据冗余

主从复制实现了数据的备份,实际上提供了数据冗余的实现方式。

  • 故障恢复

当主节点出现异常时,可以由从节点提供服务,实现快速的故障恢复,实际上提供了服务冗余的实现方式。

  • 负载均衡

在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器的负载;

在写少读多的业务场景下,通过多个从节点分担读负载,可以大大提高Redis服务器是并发量。

  • 高可用

哨兵配合主从复制,可以是实现Redis集群的高可用。

8.2> 环境搭建

  • 创建redis-cluster目录,然后复制3份redis(也可以一份redis3份不同的配置文件,启动的时候,读取响应的配置文件),如下图所示:
  • 分别修改它们的redis.conf配置文件
代码语言:javascript
复制
# redis-6380/redis.conf
port 6380
pidfile /var/run/redis-6380.pid
logfile "redis-6380.log"
dbfilename dump-6380.rdb
daemonize yes

# redis-6381/redis.conf
port 6381
pidfile /var/run/redis-6381.pid
logfile "redis-6381.log"
dbfilename dump-6381.rdb
daemonize yes
# 如果不通过修改配置文件,也可以在客户端中输入“SLAVEOF 127.0.0.1 6380”即刻生效!!
# 也可以在客户端中输入“SLAVEOF NO ONE”来断开主从关系
replicaof 127.0.0.1 6380 

# redis-6382/redis.conf
port 6382
pidfile /var/run/redis-6382.pid
logfile "redis-6382.log"
dbfilename dump-6382.rdb
daemonize yes
replicaof 127.0.0.1 6380
  • 启动这3个redis服务
  • 开启三个客户端,来连接这3个redis服务实例;利用ping查看服务是否正常,并且通过info replication查看自己的角色
    • redis-6380
  • redis-6381
  • redis-6382

8.3> 相关特性

8.3.1> 从节点是只读的

  • 我们测试一下主节点redis-6380的读写操作,读写都ok
  • 我们测试一下从节点redis-6381的读写操作,发现不能执行写入操作,但是可以读取数据,其中muse是我们在6380主节点中添加的
  • 我们测试一下从节点redis-6382的读写操作,也一样是只读的

8.3.2> 主节点意外宕机

  • 我们关闭主节点6380的服务,查看从节点的对外服务是否收到影响
  • 测试两个从节点是否可以对外正常的提供服务。如下所示,我们可以看到,从节点依然可以堆外提供只读的服务
  • 虽然主节点挂掉了,但是这两个从节点并不会自动的成为主节点,他们依然是从节点的角色。我们可以通过info replication来确认一下
  • 我们重新启动主节点,并且添加数据,我们来确认一下,这两个从节点会不会依然能够获得主节点同步过来的新数据

【解释】我们发现,两个从节点都可以获取到新添加的bob。说明,只要主节点再次成功启动,主从结构依然可以自动的建立起来。

8.4> 实现原理

  • Redis的主从复制可以分为两个阶段:sync阶段和command propagate阶段。

8.4.1> sync阶段

  • 当从节点启动后,会发送sync指令给主节点,要求全量同步数据。具体步骤如下图所示:

【解释】

  • 步骤1:Slave启动后,连接Master节点并发送sync指令。
  • 步骤2:Master节点接到sync指令后,会执行BGSAVE指令,生成RDB文件。此外,在Master节点生成RDB文件时,会将此后客户端执行的增删改操作都存入缓冲区。
  • 步骤3:文件生成后,会发送给Slave节点,Slave节点接收到后,会删除所有旧的数据,然后加载RDB数据,实现数据全量同步操作。
  • 步骤4:当Slave数据加载完毕后,Master会将缓冲区的指令发送给Slave
  • 步骤5:由Slave去执行缓冲区新增的指令。

8.4.2> command propagate阶段

  • 即:命令传播阶段。
  • 上面我们介绍了,Slave节点通过sync指令请求Master节点全量数据的同步操作。那么,如果后续Master节点接收到新的增删改操作,也需要Slave节点接收同步的更新,那么这种就是command propagate

8.4.3> psync指令

  • 当主从节点都正在运行的时候,出现了网络抖动,造成连接断开,那么当网络恢复,两个节点再次建立起连接的时候。从节点发送sync指令后,主节点依然需要重新生成RDB,并对从节点进行全量数据的同步造成。那么这中间的耗时是非常严重的,并且传输备份文件也会对网络带宽造成很大的消耗。那么为了解决这个问题,从Redis 2.8开始,引入了psync指令来代替sync指令
  • psync指令会根据不同的情况,来确定执行全量重同步还是部分重同步
  • 全量重同步 当从节点是第一次与主节点建立连接的时候,那么就会执行全量重同步,这个同步过程与上面我们介绍的sync阶段+command propagate阶段一样
  • 部分重同步 从节点的复制偏移量无法在复制积压缓冲区中找相应待同步的数据 主节点与从节点不是第一次同步(根据Redis节点ID判断)
  • 什么是复制偏移量? Master节点和Slave节点都保存着一份赋值偏移量。 当Master节点每次向Slave节点发送n字节数据的时候,就会在Master节点偏移量加上n;而Slave节点每次接收到n个字节的时候,也会在Slave节点偏移量上加n。 在命令传播阶段,Slave节点会定期的发送心跳REPLCONF ACK{offset}指令,这里的offset就是Slave节点的offset。当Master节点接收到这个心跳指令后,会对比自己的offset和命令里的offset,如果发现有数据丢失,那么Master节点就会推送丢失的那段数据给Slave节点。如下图所示:
  • 什么是复制积压缓冲区? 复制积压缓冲区是由主节点维护的一个固定长度(默认1MB)的队列。 它存储了每个字节值与对应的复制偏移量。 因为复制积压缓冲区的大小是固定的,所以它保存的是主节点近期执行的写命令。当从节点将offset发送给主节点后,主节点便会根据offset与复制积压缓冲区的大小来决定是否可以使用部分重同步。如果offset 之后的数据仍然在复制积压缓冲区内,则执行部分重同步;否则还是执行全量重同步。
  • 节点ID Redis节点服务启动之后,就会产生一个用来唯一标识Redis节点的ID。 当Master节点与Salve节点进行第一次连接同步的时候,Master节点会将ID发送给Slave节点,Slave节点接收到会,会对其进行保存。那么当主从服务之间发生了中断重连的时候,Slave服务器会将这个ID发送给Master服务器,Master服务器会拿自己的ID进行对比,如果相同,则说明主从之前是连接过的。否则,则说明是第一次建立的连接。那么,就需要全量去同步数据了。

九、Redis哨兵

9.1> 概述

  • 我们介绍主从复制的时候发现,主节点挂掉从节点不会自动变为主节点,需要人工的去配置主节点才可以。但是这种做法费时费力,怎样能让redis在主节点挂掉的情况下,自己从从节点中选择新的主节点呢?这时候,就需要使用Sentinel哨兵了。
  • 哨兵本质就是一个Redis实例节点。哨兵模式是一种特殊的模式,它能够后台监控主机是否故障,如果故障了,则根据投票数自动将Slave节点转换为新的Master节点。首先Redis提供了哨兵的命令,哨兵是一个独立的进程,会独立的运行。它的原理是:哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。如下图所示:
  • 哨兵的作用:
  • 监控(Monitoring

Sentinel 会不断地检查主节点和从节点是否运作正常。

  • 通知(Notification)

当被监控的某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知。

  • 自动故障迁移(Automatic failover)

当一个主服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作,它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

  • 然而一个哨兵进程对Redis服务器进行监控,可能会出现问题。因此,我们可以使用多哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
  • Redis的Sentinel中,关于下线(down)有两个不同的概念:
  • 主观下线(Subjectively Down, 简称 SDOWN)

指的是单个 Sentinel 实例对服务器做出的下线判断。

  • 客观下线(Objectively Down, 简称 ODOWN)

指的是多个 Sentinel 实例在对同一个服务器做出SDOWN判断, 并且通过SENTINEL is-master-down-by-addr命令互相交流之后,得出的服务器下线判断。

一个Sentinel可以通过向另一个Sentinel发送SENTINEL is-master-down-by-addr命令来询问对方是否认为给定的服务器已下线。

9.2> 环境搭建(3哨兵1主2从)

  • 我们来搭建如下图的Sentinel集群
  • Redis源码中包含了一个名为sentinel.conf的文件,这个文件是一个带有详细注释的Sentinel配置文件示例。运行一个Sentinel所需的最少配置如下所示:
代码语言:javascript
复制
# sentinel的端口号,如果配置3个Sentinel,只需要修改这个port即可,即:26380、26381、26382
port 26380

# 监视127.0.0.1:6380的主节点,且至少有2个Sentinel判断主节点失效,才可以自动故障迁移
sentinel monitor mymaster 127.0.0.1 6380 2

# 指定了Sentinel认为服务器已经断线所需的毫秒数;如果服务器在给定的毫秒数之内,没有返回Sentinel发送的PING命令的回复,或者返回一个错误,
# 那么Sentinel将这个服务器标记为主观下线(subjectively down,简称 SDOWN )
sentinel down-after-milliseconds mymaster 60000 

# 故障迁移超时时间
sentinel failover-timeout mymaster 180000

# 指定了在执行故障转移时,最多可以有多少个从服务器同时对新的主服务器进行同步,这个数字越小,完成故障转移所需的时间就越长。
sentinel parallel-syncs mymaster 1
  • 基于上面Sentinel的配置文件,我们复制3份,只需要修改对应的端口即可,其他配置不用变化,如下所示
  • 除了上面列出的【Sentinel所需最少配置】之外,还有修改对应的端口号,如下所示:
  • 以sentinel-26380.conf为例
  • 说明1】下面我们要观察Sentinel是如何监控实例的,所以暂时我们可以把daemonize设置为no,线上环境,我们要设置为yes,让它在后台监控运行即可
  • 说明2】由于我们需要控制台查看Sentinel监控情况,所以我们设置为logfile "",这样就可以从控制台输出信息了。
  • 修改完Sentinel配置文件后,我们来启动一下redis的3个节点(我们清除掉redis.conf中配置的replicaof,采用通过SLAVEOF指令的方式来动态设置主节点,这样会更灵活)
  • 启动Sentinel的两种方式:
代码语言:javascript
复制
redis-sentinel /path/to/sentinel.conf

代码语言:javascript
复制
redis-server /path/to/sentinel.conf --sentinel
  • 启动Sentinel哨兵

9.3> 测试主节点关闭的自动选主

  • 关闭主节点6380
  • 我们来看Sentinel中输出的日志信息,从日志中我们可以看到,Sentinel将6381作为了新的主节点
  • 我们用info replication验证一下6381和6382的角色
  • 此时我们再次启动6380,我们发现它的角色已经是从节点了。也就是说,即使原主节点又恢复正常了,也只能老老实实的当从节点这个角色了。(就像朱祁镇和朱祁钰的故事)

9.4> 测试关闭一个Sentinel,其他哨兵是否正常工作

  • 我们尝试关闭26380这个Sentinel,发现其他两个Sentinel能够发现
  • 关闭Master节点6381,我们发现,Sentinel哨兵工作正常,选择了6380称为新的Master节点

9.5> 测试只剩下一个Sentinel

  • 因为我们配置Sentinel的时候,配置的是需要2个Sentinel确认主机下线才进行迁移,但是现在就剩下1个Sentinel了,是否还能执行自动的主从切换
  • 我们下线26381这个Sentinel,只留下26382
  • 这时我们把刚刚关闭的6381启动起来,那么现在就是一主(6380)两从(6381和6382),然后我们下线主节点(6380)
  • 我们来看一下26381这个Sentinel,发现没有执行选主(因为必须有个Sentinel认为主节点下线,才会执行选主;而现在只剩下这一个Sentinel了,所以无法执行选主操作)

9.6> Sentinel对配置文件都做了什么

  • 我们随便打开一个sentinel.conf配置文件,查看最后面的几行,如下所示:
  • 我们再看看redis.conf配置文件,有什么变化
  • 所以,我们发现,当Sentinel执行选主操作的时候,它也会去针对当前的主从情况对配置文件进行修改,比如当确定6382是从节点的时候,它就会往配置文件中加入replicaof 127.0.0.1 6380,来配置主节点。

9.7> 实现原理

  • 当主节点发生异常挂掉了,Sentinel会感知到并且执行自动的故障迁移,那它是如何感知到主节点异常的呢?下面我们来介绍一下Sentinel的三种定时监控任务。

9.7.1> INFO指令获得最新节点拓扑图

  • 每个Sentinel每隔10秒就会向主从节点中发送INFO指令,通过该指令可以获得整个redis的节点拓扑图。那么这时候,如果有新的节点加入或者有节点退出集群,那么Sentinel就可以很快的感知到拓扑图的变化。如下图所示:

9.7.2> 通过发布订阅获得Master节点和其他Sentinel的信息

  • 每个Sentinel每隔2秒会向指定频道上发布自己对Master节点是否正常的判断以及当前Sentinel节点的信息,并且通过订阅这个频道,可以获得其他Sentinel节点的信息和对Master节点是否存活的判断。如下图所示:

9.7.3> PING指令心跳检测

  • 每个Sentinel每隔1秒会向所有节点(Sentinel节点、Master节点、Slave节点)发送PING指令来进行心跳检测。如下图所示:

9.7.4> 选举流程

  • 当一个Sentinel判断主节点不可用的时候,会首先进行“主观下线”,此时,这个Sentinel通过sentinel is-masterdown-by-addr指令获取其他哨兵节点对主节点的判断,如果当前哨兵节点对主节点主观下线的票数超过了我们定义的quorum值,则主节点被判定为“客观下线”。
  • Leader Sentinel 节点会从原主节点的从节点中选出一个新的主节点,选举流程如下:
  • 首先,过滤掉所有主观下线的节点
  • 选择slave-priority最高的节点,如果有则返回,没有就继续下面的流程
  • 选择出复制偏移量offset最大的节点,如果有则返回,没有就继续下面的流程
  • 选择run_id(服务器运行 ID)最小的节点
  • 在选择完毕后,Leader Sentinel节点会通过SLAVEOF NO ONE命令让选择出来的从节点成为主节点,然后通过SLAVEOF命令让其他的节点成为该节点的从节点。

十、Redis Cluster

10.1> 概述

  • Redis3.0开始引入了去中心化分片集群Redis Cluster。
  • 传统的 Redis 集群是基于主从复制+哨兵的方式来实现的。但是集群中都只有一个主节点提供写服务。
  • Redis Cluster则采用多主多从的方式,支持开启多个主节点,每个主节点上可以挂载多个从节点。
  • Cluster会将数据进行分片,将数据分散到多个主节点上,而每个主节点都可以对外提供读写服务。这种做法使得Redis突破了单机内存大小限制,扩展了集群的存储容量。并且Redis Cluster也具备高可用性,因为每个主节点上都至少有一个从节点,当主节点挂掉时,Redis Cluster 的故障转移机制会将某个从节点切换为主节点。
  • Redis Cluster是一个去中心化的集群,每个节点都会与其他节点保持互连,使用gossip协议来交换彼此的信息,以及探测新加入的节点信息。并且Redis Cluster无需任何代理,客户端会直接与集群中的节点直连。

10.2> 分片方式

10.2.1> 哈希取模

  • 这种方式就类似我们使用HashMap时选址的方式,只要hash计算出来的值够散列,那么每个key都可以均匀的分散到N个节点上。
  • 但是它存在的问题就是,如果要扩容或缩容,会导致key重新计算存储位置,从而导致缓存失效。

10.2.2> 一致性哈希

  • 一致性哈希算法将整个哈希值空间组织成一个虚拟的圆环,其范围为0 ~ 2^32-1,如下图所示:
  • 一致性哈希算法的原理 我们会先对Key计算它的hash值,从而确定它在环上的位置。然后从该位置沿着环顺指针地走,找到的第一个节点,便是这个Key应该存放的服务器节点的位置。
  • 当我们向集群中增加或减少节点时,就无需像哈希取模算法那样,对整个集群Key的位置进行重新计算。一致性哈希算法将增减节点的影响限制在相邻的节点上,比如:我们在node2与node4之间增加一个节点node5,则只有node4中的一部分数据会迁移到新增节点上;如果我们想要将 node4节点置为下线状态,则node4节点的数据只会迁移到node 3中,其他节点无影响。如下图所示:
  • 一致性哈希算法的缺点

当节点比较少时,增删节点对单个节点的影响会很大,从而导致出现数据不均衡的情况。拿上图来举例,当我们删除任意一个节点,都会导致集群中的某一个节点的数据量由总数据的 1/4 变为 1/2。

10.2.3> 虚拟节点+一致性哈希

  • 该方案在一致性哈希的基础上,引入了虚拟节点这一概念。原本是由实际节点来“抢占” 哈希环的位置,现在则是将虚拟节点分配给实际节点,然后由虚拟节点来抢占。如下图所示:
  • 在引入了虚拟节点这一概念后,数据到实际节点的映射关系就变成了数据到虚拟节点,再由虚拟节点到实际节点了。Redis 集群便是采用了这种方案。一个集群包含16384个哈希槽(hash slot)也就是16384个虚拟节点。譬如,我们的集群有三个节点,那么:
  • Master1节点负责处理0~5460号slot
  • Master2节点负责处理5461~10922号slot
  • Master3节点负责处理10923~16383号slot
  • 当我们在集群中新增了一个节点Master4,那么集群只需要将Master1,Master2,Master3中负责的一部分hash slot分配给Master4节点就可以了;
  • 如果要移除某一个节点,也只需要将该节点负责的hash slot分配给其他的节点即可。这样集群便实现了良好的可扩容性。同时,由于存在16384个虚拟节点,那么这些hash slot在哈希环上可以分布均匀,从而实现负载均衡。

10.3> 搭建集群(3主3从)

  • 由于Redis Cluster要求必须要至少6个节点,所以我们就以配置3主3从为例:
  • 修改redis-6390.conf~redis-6395.conf配置文件
代码语言:javascript
复制
# 配置集群节点对应的端口号(分别为6390,6391,6392,6393,6394,6395)
port 6390
# 守护进程开启
daemonize yes
# 关闭保护模式
protected-mode no
# 将集群开启
cluster-enabled yes
cluster-config-file nodes-6390.conf(分别为6390,6391,6392,6393,6394,6395)
  • 启动集群中redis服务
  • 分配主从(--cluster-replicas 1表示创建一主一从)
代码语言:javascript
复制
./redis-cli --cluster create 127.0.0.1:6390 127.0.0.1:6391 127.0.0.1:6392 127.0.0.1:6393 127.0.0.1:6394 127.0.0.1:6395 --cluster-replicas 1
  • 执行完毕结果如下所示:
  • 我们来登录6390,看看他在集群中的角色信息是什么
  • 我们在6390客户端中添加一条记录,我们发现,它根据key值确认了slot=12965,然后将数据存储到了6392这个节点上,并且客户端也切换为6392了

10.4> 部署过程中可能出现的异常

  • 配置完集群后,可能会报如下错误,这说明16384个槽位没有分配完
  • 我们通过如下指令就可以进行检查和修复
代码语言:javascript
复制
redis-cli --cluster check 172.17.0.2:6379
redis-cli --cluster fix 172.17.0.2:6379 #官方修复功能
  • 修复后的结果如下所示:

十一、缓存常见问题及解决方案

11.1>缓存穿透(查不到数据)

11.1.1> 概述

  • 当用户想要查询一个数据,发现Redis中不存在,也就是所谓的缓存没有命中,于是这个数据请求就会打到数据库中。结果数据库中也不存在这条数据,那么结果就是什么都没查询出来。那么当用户很多时候的查询,缓存中都没有数据,请求直接打到数据库中,这样就会给数据库造成很大的压力,缓存的作用也就几近于失效了,那么这种情况就叫做缓存穿透。

11.1.2> 解决方案

  • 方案一:保存空值
  • 当数据库中也查询不到数据时,那么将返回的空对象也缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,从而起到保护数据库的作用。
  • 例如:查询userId=100的用户信息(key=[userId],value=[用户json]),那么如果缓存和DB中都不存在,则在缓存中保存一条key=100,value=""的数据,那么用户再查询userId=100的时候,就直接可以返回空了。不需要查询DB。
  • 方案二:布隆过滤器
  • 步骤1:将数据库所有的数据加载到布隆过滤器。
  • 步骤2:当有请求来的时候先去布隆过滤器查询,判断查询的数据是否存在。
  • 步骤3:如果Bloom Filter判断数据不存在,那么直接返回空给客户端。
  • 步骤4:如果Bloom Filter判断数据存在,那么则查询缓存或DB(下图以Redis中不存在数据为例)。
  • 步骤5:将DB中查询的结果返回给客户端(并且缓存到Redis中)

11.2> 缓存击穿(高并发查询某数据,且缓存过期)

11.2.1> 概念

  • 指一个非常热点的key,在不停的高并发请求着,那么当这个key在缓存中失效的一瞬间,持续对这个key的高并发就击穿了缓存,直接请求到了数据库,就像在一个屏障上早开了一个洞。
  • 当热点key过期失效的一瞬间,高并发突然融入,会对数据库突然造成巨大的压力,严重的情况甚至会造成数据库宕机。

11.2.2> 解决方案

  • 方案一:设置热点数据永不过期
  • 从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后所产生的缓存击穿问题。
  • 方案二:加互斥锁
  • 使用分布式锁,当缓存数据过期后,保证对每个热点key同时只有一个线程去查询后端服务,并将热点数据添加到缓存。

11.3>缓存雪崩(缓存大批量失效或Redis宕机)

11.3.1> 概念

  • 指在某一个时间段,缓存集中过期失效,或Redis宕机,导致针对这批数据的查询都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
  • 其实缓存集中过期,倒不是最致命的,比较致命的是Redis发生节点宕机或断网。因为缓存集中过期后,数据库压力增大,但是随着缓存的创建,压力也会逐渐变小。但是Redis服务节点宕机,对数据库服务器造成的压力是不可预知的,很有可能是持续压力而最终造成数据库宕机。

11.3.2> 解决方案

  • 方案一:配置Redis集群
  • 通过配置Redis集群,提升高可用性,那么即使挂掉几个Redis节点,集群内的其他Redis节点依然可以继续对外提供服务。
  • 方案二:限流降级
  • 缓存失效后,通过加锁或队列来控制读取数据库且写入缓存的线程数量。
  • 方案三:数据预热分散过期时间
  • 在正式部署之前,先把可能被高频访问的数据预先访问一遍,这样大部分热点数据就加载到缓存中了,并且通过设置不同的过期时间,让缓存失效的时间尽量均匀,防止同一时刻大批量缓存失效。

11.4> 布隆过滤器

11.4.1> 概念介绍

  • 布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
  • 布隆过滤器的特征是: 它可以判断某个数据一定不存在,但是无法判断一定存在。(缺失有点拗口,但当我们介绍完它的原理,就很容易明白了)

11.4.2> 使用场景

  • 原本有10亿个号码,现在又来了10万个号码,如何快速判断这10万个号码是否在10亿个号码库中?
  • 需要爬虫的网站千千万万,对于一个新的网站url,我们如何判断这个url我们是否已经爬过了?
  • 针对上面的需求,我们一般会想到两种解决方案:
  • 方案一:将10亿个号码存入数据库中,进行数据库查询,准确性有了,但是速度会比较慢。
  • 方案二:将10亿号码放入内存中,比如Redis缓存中,这里我们算一下占用内存大小:10亿*8字节=8GB,通过内存查询,准确性和速度都有了,但是大约8gb的内存空间,挺浪费内存空间的。
  • 那么对于类似这种,海量数据集合,如何准确快速的判断某个数据是否在大数据量集合中,并且不占用内存?那么布隆过滤器应运而生了。

11.4.3> 实现原理

11.4.3.1> 布隆过滤器是什么?
  • 它是一种数据结构,是由一串很长的二进制向量组成,也可以将其看成一个二进制数组。既然是二进制,那么里面存放的不是0,就是1,但是初始默认值都是0。如下图所示:
11.4.3.2> 添加数据
  • 当要插入一个元素时,将其数据分别输入k个哈希函数,产生k个哈希值。以哈希值作为位数组中的下标,将所有k个对应的比特置为1。
  • 比如,下图hash1(x)=1,那么在第2个格子将0变为1(数组是从0开始计数的),hash2(x)=6,那么将第5个格子置位1,hash3(x)=16,那么将第16个格子置位1,依次类推。如下图所示(只演示hash1~hash3):
11.4.3.3> 判断数据是否存在
  • 知道了如何向布隆过滤器中添加一个数据,那么新来一个数据,我们如何判断其是否存在于这个布隆过滤器中呢?
  • 很简单,我们只需要将这个新的数据通过上面自定义的几个哈希函数,分别算出各个值,然后看其对应的地方是否都是1,如果存在一个不是1的情况,那么我们可以说,该新数据一定不存在于这个布隆过滤器中
  • 那么反过来说,如果通过哈希函数算出来的值,对应的地方都是1,那么我们能够肯定的得出:这个数据一定存在于这个布隆过滤器中吗?
  • 答案是否定的,因为多个不同的数据通过hash函数算出来的结果是会有重复的,所以会存在某个位置是别的数据通过hash函数置为的1。即:“假阳性”(false positive)如下图所示:

十二、高频Redis面试问题

12.1> 为什么Redis是单线程的

  • 官方的回复 因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。Redis每秒可以处理十万以上的请求。
  • 采用了单线程之后,可以避免使用锁实现的复杂性和性能消耗
  • 采用了单线程,就避免了多线程导致的CPU上下文切换带来的性能消耗

12.2> Redis如何保证多客户端连接的时候,依然高吞吐

  • redis采用非阻塞I/O和I/O多路复用技术来保证在多连接的时候,系统的高吞吐量。

12.2.1> 非阻塞I/O

  • 当我们启动一个客户端时,客户端于服务端建立连接,并通过套接字Socket来处理他们之间的请求。比如我们执行了get muse指令,执行过程如下所示:
  • 步骤1:服务端要先监听客户端的请求——listen;
  • 步骤2:客户端请求发过来的时候与其建立连接——accept
  • 步骤3:紧接着服务端需要从套接字Socket中读取客户端的请求——recv
  • 步骤4:服务端对请求进行解析操作——parse
  • 步骤5:解析出来的请求类型为“get”,key为“name”,然后再根据key获取对应的value;
  • 步骤6:将响应值返回给客户端,即:向Socket中写入数据——send
  • 但是,其中存在的一个问题是,Socket默认的读写方式是阻塞的,当Redis通过recv或send向客户端读写数据时,如果数据一直没有到达,那么Redis主线程就会一直处于阻塞状态。
  • 而Redis并没有采用默认的阻塞方式,它采用的是Socket的非阻塞模式,在非阻塞I/O模式下,读写方法不会产生阻塞,而是能读多少就读多少,能写多少写多少。当客户端一直不发送数据时,主线程也不会傻傻的一直等到,而是直接返回,去做其他的事情。

12.2.2> I/O多路复用机制

  • Redis采用I/O多路复用机制,使得Redis在单线程模式下依然可以高效的处理多个I/O流
  • Redis基于Reactor模式开发了自己的文件事件处理器(File Event Handler)。
  • 文件事件处理器是由套接字I/O多路复用程序文件事件分派器事件处理器构成的。
  • I/O多路复用程序(epoll / kqueue / select...,依据操作系统的不同,会采用不同的函数,Linux 使用的是 epoll,Mac OS 则是 kqueue)会监听多个套接字,每当一个套接字准备执行应答,读写,关闭等操作时,就会产生对应的文件事件,这些事件会存储到一个事件队列中,并由队列向文件事件分派器传送。而文件事件分派器会根据这些事件对应的类型来调用不同的事件处理器执行相应的事件回调函数。I/O多路复用机制使得Redis无需一直轮询是否有请求发生,这样就避免了资源的浪费。如下图所示:

12.3> 如何从海量数据中查询除满足某一固定前缀的key

  • 针对这类前缀查询,我们有两种查询方案,分别为:keys和scan

12.3.1> keys

  • 语法:KEYS pattern
  • 在数据量不大的情况下,我们可以使用keys的方式查询除所有符合pattern的key,如下所示:
  • 但是在海量数据情况下,如果使用keys命令执行,查询速度就会非常的慢了。
  • keys命令会将所有的Key全部遍历一遍,其时间复杂度为O(N)。不仅如此,在数据量极大的情况下,该命令会阻塞Redis I/O多路复用的主线程一段时间,如果我们的Redis正在为线上的业务提供服务,主线程一旦被阻塞,那么在此期间其他的发送向Redis服务端的命令都会被阻塞,从而导致Redis出现卡顿,引发响应超时的问题。

12.3.2> scan

  • 语法:SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
  • scan命令的时间复杂度同样为O(N),不过它需要迭代多次才能返回完整的数据,并且每次返回的数据有可能会产生重复
  • cursor为查询游标,游标从0开始,也从0结束。每次返回的数据中,都有下一次游标该传入的,我们通过这个值,再去进行下一轮的迭代。
  • scan命令通过游标分布执行,不会产生线程阻塞,所以非常适合使用于海量数据的生产环境下。
  • scan命令并不保证每次执行都会返回某个给定数量的元素,每次迭代返回的元素数量都是不确定的。所以,即便是返回了0条数据,只要返回的游标值不为0,就说明需要继续迭代,只有当返回的游标值为0时,才代表迭代完毕。如下图所示:

12.4> 什么是Redis Pipeline

  • Redis 是一个基于 TCP 协议的 NoSql 数据库,而客户端与 Redis 服务端之间的交互过程可以分为以下几个简略的步骤:
  • 步骤1:客户端向Redis服务端发起一个request请求
  • 步骤2:Redis服务端收到请求,等待处理
  • 步骤3:Redis服务端处理请求
  • 步骤4:Redis 服务端将结果response响应给客户端
  • 由于Redis服务端需要等待上一条命令响应完成后再去执行下一条命令,如果此时需要执行大量的命令,通过传统的Redis请求/响应模型便会严重影响效率,这中间不仅仅多了RTT(Round Time Trip)即客户端与服务端来回交互的时间,而且还会频繁调用系统IO来发送网络请求。为了解决这样的问题,Redis为我们提供了 Pipeline(管道)技术。
  • Pipeline允许客户端一次发送多条命令,Redis Server则会对这些命令进行“批处理”,执行完毕后将结果一次性发送给客户端。Pipeline可以将多次 IO往返时间缩减为一次,前提是Pipeline执行的指令之间没有依赖的相关性,如果指令之间有依赖,则还是建议分批去发送指令。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爪哇缪斯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、Redis安装
  • 三、Redis-benchmark测试工具
  • 四、Redis的数据库介绍
  • 五、Redis三种特殊类型
    • 5.1> geospatial地理位置
      • 5.1.1> 概述
      • 5.1.2> GEOADD(v3.2.0)
      • 5.1.3> GEODIST(v3.2.0)
      • 5.1.4> GEOHASH(v3.2.0)
      • 5.1.5> GEOPOS(v3.2.0)
      • 5.1.7> GEORADIUSBYMEMBER(v3.2.0)
    • 5.2> hyperloglog预估集合的基数
      • 5.2.1> 概述
      • 5.2.2> PFADD(v2.8.9)
      • 5.2.3> PFCOUNT(v2.8.9)
      • 5.2.4> PFMERGE(v2.8.9)
    • 5.3> bitmap位图
      • 5.3.1> 概述
      • 5.3.2> SETBIT(v2.6.0)
      • 5.3.3> GETBIT(v2.2.0)
      • 5.3.4> BITCOUNT(v2.6.0)
  • 六、Redis的事务管理
    • 6.1> 概述
      • 6.2> MULTI(v1.2.0)
        • 6.3> EXEC(v1.2.0)
          • 6.4> DISCARD(v2.2.0)
            • 6.5> WATCH(v2.2.0)
              • 6.6> UNWATCH(v2.2.0)
                • 6.7> 事务中异常的处理
                  • 6.7.1> 命令语法错误
                  • 6.7.2> 运行操作错误
              • 七、Redis实现订阅发布
                • 7.1> 概述
                  • 7.2> SUBSCRIBE(v2.0.0)
                    • 7.3> PUBLISH(v2.0.0)
                      • 7.4> PSUBSCRIBE(v2.0.0)
                        • 7.5> PUBSUB CHANNELS
                          • 7.6> PUBSUB HELP(v6.2.0)
                            • 7.7> PUBSUB NUMPAT(v2.8.0)
                              • 7.8> PUBSUB NUMSUB(v2.8.0)
                                • 7.9> UNSUBSCRIBE(v2.0.0)
                                  • 7.10> PUNSUBSCRIBE(v2.0.0)
                                  • 八、Redis主从复制
                                    • 8.1> 概述
                                      • 8.2> 环境搭建
                                        • 8.3> 相关特性
                                          • 8.3.1> 从节点是只读的
                                          • 8.3.2> 主节点意外宕机
                                        • 8.4> 实现原理
                                          • 8.4.1> sync阶段
                                          • 8.4.2> command propagate阶段
                                          • 8.4.3> psync指令
                                      • 九、Redis哨兵
                                        • 9.1> 概述
                                          • 9.2> 环境搭建(3哨兵1主2从)
                                            • 9.3> 测试主节点关闭的自动选主
                                              • 9.5> 测试只剩下一个Sentinel
                                                • 9.6> Sentinel对配置文件都做了什么
                                                  • 9.7> 实现原理
                                                    • 9.7.1> INFO指令获得最新节点拓扑图
                                                    • 9.7.2> 通过发布订阅获得Master节点和其他Sentinel的信息
                                                    • 9.7.3> PING指令心跳检测
                                                    • 9.7.4> 选举流程
                                                • 十、Redis Cluster
                                                  • 10.1> 概述
                                                    • 10.2> 分片方式
                                                      • 10.2.1> 哈希取模
                                                      • 10.2.2> 一致性哈希
                                                      • 10.2.3> 虚拟节点+一致性哈希
                                                    • 10.3> 搭建集群(3主3从)
                                                      • 10.4> 部署过程中可能出现的异常
                                                      • 十一、缓存常见问题及解决方案
                                                        • 11.1>缓存穿透(查不到数据)
                                                          • 11.1.1> 概述
                                                          • 11.1.2> 解决方案
                                                        • 11.2> 缓存击穿(高并发查询某数据,且缓存过期)
                                                          • 11.2.1> 概念
                                                          • 11.2.2> 解决方案
                                                        • 11.3>缓存雪崩(缓存大批量失效或Redis宕机)
                                                          • 11.3.1> 概念
                                                          • 11.3.2> 解决方案
                                                        • 11.4> 布隆过滤器
                                                          • 11.4.1> 概念介绍
                                                          • 11.4.2> 使用场景
                                                          • 11.4.3> 实现原理
                                                      • 十二、高频Redis面试问题
                                                        • 12.1> 为什么Redis是单线程的
                                                          • 12.2> Redis如何保证多客户端连接的时候,依然高吞吐
                                                            • 12.2.1> 非阻塞I/O
                                                            • 12.2.2> I/O多路复用机制
                                                          • 12.3> 如何从海量数据中查询除满足某一固定前缀的key
                                                            • 12.3.1> keys
                                                            • 12.3.2> scan
                                                          • 12.4> 什么是Redis Pipeline
                                                          相关产品与服务
                                                          云数据库 Redis
                                                          腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                                                          领券
                                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档