文章目录
- NoSQL数据库简介:
- Redis概述和安装:
- Redis相关知识:
- Redis 对客户端连接的管理方式和命令处理的过程:
- Redis的常见命令:
- 常用数据类型:
- Redis配置文件详解:
- Redis事务操作:
- Redis持久化:
- Redis纯缓存模式:
- Redis管道:
- Redis主从复制:
- Redis哨兵模式:
- Redis集群:
- redis常用的三种架构:
- Redis应用问题解决:
- Redis6的新功能:
NoSQL数据库简介:
技术的分类:
- 解决功能性问题
- 解决扩展性的问题
- 解决性能的问题
NoSQL引入对访问性能的提升:
概述:
NoSQL泛指非关系型数据库,其不依赖于业务逻辑方式存储,而以简单的key-value模式存储,故很大程度上提升了数据库的性能。
- 不遵循SQL标准、不支持事物ACID的特性,故性能远超SQL;
- 适用于对数据高并发的读写、海量数据读写、对数据高可扩展性的场景;
常见的NoSQL数据库:
Memcache:
- 很早出现的NoSql数据库。
- 数据都在内存中,一般不持久化。
- 支持简单的key-value模式,支持类型单一。
- 一般是作为缓存数据库辅助持久化的数据库。
Redis:
-
几乎覆盖了Memcached的绝大部分功能,且读写性能极高,可用作数据库、缓存、消息中间件。
-
数据都在内存中,支持异步持久化,主要用作备份恢复(重启恢复、master-slave主从备份等)。
-
除了支持简单的key-value模式,还支持多种数据结构的存储,比如 list、set、hash、zset等。
Reids提供
pub/sub
和Stream
操作,使得Redis能作为一个很好的消息队列来使用。 -
一般是作为缓存数据库辅助持久化的数据库。
-
高可用架构搭配:单机模式、主从模式、哨兵模式、集群模式。
MongoDB:
- 高性能、开源、模式自由(schema free)的文档型数据库。
- 数据都在内存中, 如果内存不足,会把不常用的数据保存到硬盘。
- 虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能。
- 支持二进制数据及大型对象。
- 可以根据数据的特点替代RDBMS ,成为独立的数据库。或者配合RDBMS,存储特定的数据。
Redis概述和安装:
redis概述:
Redis是一种基于C/S模型、请求/响应协议的TCP服务。一个请求会遵循以下步骤:
- 客户端向服务端发送命令分四步(发送命令→命令排队→命令执行→返回结果),期间监听Socket返回(通常以阻塞模式等待服务端响应)。
- 服务端处理命令,并将结果返回给客户端。
Redis涵盖的知识:
- 和Memcached类似,它支持存储的value类型相对更多,这些数据类型都支持push/pop、add/remove及取交集、并集和差集及更丰富的操作,而且这些操作都是原子性的。
- 与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。并在此基础上实现了master-slave(主从)同步。
Redis
提供了主从模式、Redis Sentinel
和Redis Cluster
集群架构方案。
应用场景:
- 配合关系型数据库做高速缓存:
- 高频次,热门数据的存储,降低关系型数据库的IO;
- 分布式架构中,做session共享;
- 多样的数据结构存储持久化数据:
- 最新的N个数据 <-- 通过List实现自然时间排序的数据
- 排行榜,Top N <-- 利用zset(有序集合)
- 时效性的数据,比如手机验证码 <-- expire过期
- 计数器,秒杀 <-- 原子性,自增方法INCR、DECR
- 去除大量数据中的重复数据 <-- 利用set集合
- 构建队列 <-- 利用list集合
- 发布-订阅消息系统 <-- pub/sub模式
安装:
# 1. 更新gcc的版本:(参照“集群聊天服务器”博客)
gcc --version
# [root@sunguangyuan redis-6.2.1]# gcc --version
# gcc (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)
# 2. 下载`redis-6.2.1.tar.gz`并放入`/opt`目录下
# 3. 执行解压命令:
tar -zxvf redis-6.2.1.tar.gz
# 4. 进入目录:
cd ./redis-6.2.1
# 5. 在该目录下执行`make`命令,来编译 redis-6.2.1
make
# 6. 跳过 make test 继续执行: make install
make install
# 7.默认安装路径:/usr/local/bin
redis服务的启动:
前台启动:
启动命令:redis-server
命令行窗口不能关闭,否则服务器停止。
后台启动:
# 1. 备份`redis.conf`到`/redis`目录下:
cp /opt/redis-6.2.1/redis.conf /myredis/
# 2. 后台启动,需要将文件`/myredis/redis.conf`中,`daemonize no`设置为`daemonize yes`,让服务器在后台启动
daemonize yes
# 3. redis服务的启动:
cd /usr/local/bin
redis-server /myredis/redis.conf
# 4. 用客户端访问redis服务:redis-cli
redis-cli
# 5. 单实例关闭:
redis-cli shutdown
# 6. 多实例关闭:
redis-cli -p 6379 shutdown
卸载:
# 停止redis服务
redis-cli shutdown
ps -ef | grep redis
# 删除/usr/local/lib目录下,与redis相关的文件
ls -l /usr/local/bin/redis-*
rm -rf /usr/local/bin/redis-*
Redis相关知识:
-
默认16个库,初始化默认使用0号库;
-
所有库,使用统一密码管理;
-
redis:单线程+多路IO复用技术。
多路复用:使用一个线程检查多个文件描述符的就绪状态,之后可在 “同一线程中” 或 “启动线程池” 执行。
几种IO方式:串行、多线程+锁(memcached采用的方式)、单线程+多路IO复用(redis采用的方式)。
Redis 对客户端连接的管理方式和命令处理的过程:
-
对于每个连接的客户端,Redis 会将其存储在 clients 字段中。当客户端发送请求时,Redis 会将请求协议的前一部分存储到 client 对象的定长缓冲区中,然后将请求协议的后一部分存储到 client 对象的不定长缓冲区中。
-
当客户端发送命令请求时,Redis 会先解析请求协议,获取命令名称,然后在字典中查找对应的命令处理函数及其他信息,最后执行命令处理函数,完成命令的处理过程。
注意:Redis 的客户端连接管理和命令处理方式是在 Redis 服务器内部实现的,应用程序开发者不需要直接关注这些细节,只需要使用 Redis 提供的客户端 API 和命令接口即可。
Redis的常见命令:
-
select <dbindex>
:切换数据库;(默认16个库,初始化默认使用0号库;所有库密码相同) -
dbsize
:查看当前数据库的key数量; -
key *
:查看当前库中的所有key; -
exists key
:判断某个key是否存在; -
type key
:查看key是什么类型; -
删除指定的key:
del key
:删除指定的key;unlink key
:仅将key
从keyspace
中的元数据删除,之后真正的删除会在后续的异步操作执行; -
expire key 10
:设置key在10s后过期; -
ttl key
:查看key还有多少秒过期,-1表示永不过期,-2表示已过期 -
flushdb
:清除当前库; -
flushall
:通杀全部库;
常用数据类型:
注意:下面提到的数据类型均指的都是value的类型,key的类型均为String类型。
命令不区分大小写,而key区分大小写。
帮助命令:help @string
、help @list
、help @hash
、help @hyperloglog
、…
字符串String:
介绍:
- String是Redis中最基本的类型,是二进制安全的即Redis中的String可以包含任何数据,如jpg图片或序列化的对象。
- 一个Redis中,字符串value最多可为512M。
数据结构:
String数据结构为简单的动态字符串(Simple Dynamic String,简称SDS),内部采用“预分配冗余空间”的方式,来减少内存的频繁分配。
常见命令:
-
set <key> <value>
:添加键值对NX
:当数据库中key不存在时,可以将key-value添加到数据库XX
:当数据库中key存在时,可以将key-value添加到数据库,与NX参数互斥EX
:key超时秒数PX
:key的超时毫秒数,与EX互斥 -
setnx <key> <value>
:只有在key不存在时,可以将key-value添加到数据库 -
get <key>
:查询对应键值 -
append <key> <value>
:将给定的value追加到原值的末尾 -
strlen <key>
:获得值的长度 -
incr <key>
:将key中存储的数字值增1,只能对数字值操作,如果为空,新增值为1 -
decr <key>
:将key中存储的数字值减1,只能对数字值操作,如果为空,新增值为-1 -
incrby / decrby <key> <步长>
:将key中存储的数字值依“自定义步长”增减
注意:因Redis
是原子操作,故操作是 “原子性” 的。
mset <key1> <value1> <key2> <value2> ...
:同时设置一个或多个key-value对mget <key1> <key2> ...
:同时获取一个或多个valuemsetnx <key1> <value1> <key2> <value2> ...
:只有所有给定key都不存在时,才会同时设置一个或多个key-value对(注意:原子性,有一个失败则全部失败)getrange <key> <起始位置> <结束位置>
:获得值的范围,类似于substr的功能setrange <key> <起始位置> <value>
:用value覆写key所存储的字符串值,从<起始位置>开始(注意:索引从0开始)setex <key> <过期时间> <value>
:设置键值和过期时间(单位为秒)getset <key> <value>
:以新换旧,设置新值同时获得旧值
列表List:
单键多值:
Redis列表,是简单的字符串列表,按照插入顺序排序,可以添加一个元素在列表的头/尾部。
底层是一个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。
数据结构:
Redis将双向指针和ziplist
结合起来组成quicklist
。这样既满足快速插入/删除性能,又不会出现太大的空间冗余。
- 列表元素较少的情况下,使用一块连续的内存存储,这个结构是“压缩链表”
ziplist
。 - 数据量较多时,双指针占用的空间太多比较浪费空间,会改成“快速链表”
quicklist
。
常见命令:
-
lpush/rpush <key> <value1> <value2> <value3> ...
:从左/右边插入一个或多个值 -
lpop/rpop <key>
:从左/右边吐出一个值(值光键亡) -
rpoplpush <key1> <key2>
:从key1列表右边吐出一个值,插到列表左边 -
lrange <key> <起始位置> <结束位置>
:按照索引下标获得元素(从左到右)lrange <key> 0 -1
:0左边第一个,-1右边第一个,即获取所有 -
lindex <key> <index>
:按照索引下标获得元素(从左到右) -
llen <key>
:获得列表长度 -
linsert <key> before/after <value> <newvalue>
:在value的前/后面插入newvalue值 -
lrem <key> <n> <value>
:从左到右删除n个value -
lset <key> <index> <value>
:将列表key下标为index的值替换为value
集合Set:
数据结构:
set是string类型的无序集合,底层是hash表且所有value均指向同一个内部值,所以添加、删除、查找的复杂度均为O(1)。
set是可以自动去重的,且set提供了判断某个成员是否在一个set集合内的重要接口。
常见命令:
sadd <key> <value1> <value2> ...
:将一个或多个member元素加入到集合key中(已存在的member元素将被忽略)smembers <key>
:取出该集合所有值sismember <key> <value>
:判断集合key是否含有该value值,1有、0没有scard <key>
:返回该集合的元素个数srem <key> <value1> <value2> ...
:删除集合中的某个元素spop <key> n
:随机从该集合中吐出n个值srandmember <key> n
:随机从该集合中取出n个值(但不会从集合中删除)smove <source> <destination> value
:将集合中一个值移动到另一个集合sinter <key1> <key2>
:返回两个集合的交集元素sunion <key1> <key2>
:返回两个集合的并集元素sdiff <key1> <key2>
:返回两个集合的差集元素
哈希Hash:
介绍:
Redis hash数据结构是一个键值对集合,类似于map<string, map<object, object>>
。hash是一个string类型的field
和value
映射表,hash结构特别适合用于存储对象。
主要的几种存储方式:
- 每次修改用户的某个属性需要,先反序列化改好后再序列化回去,开销较大。
- 用户ID数据冗余。
- 通过**key(用户ID)+ field(属性标签)**来操作对应属性数据,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。
数据结构:
Hash类型对应的数据结构有两种:ziplist
(压缩列表)、hashtable
(hash表)。当field-value
长度较短且个数较少时,使用ziplist
,否则使用hashtable
。
常用命令:
hset <key> <field> <value>
:给key集合中的field键赋值value;hmset <key> <field1> <value1> <field2> <value2> ...
:批量给key集合添加<field, value>;hsetnx <key> <field> <value>
:当且仅当field不存在时,才能将hash表key集合中,field设置为value;hmget <key> <field1> <field2> ...
:从key集合field1、field2、…取出其value;hget <key> <field>
:从key集合field取出value;hexists <key> <field>
:查看key集合中,是否有field存在;hkeys <key>
:列出该key集合中的所有field;hvals <key>
:列出该key集合中的所有value;hincrby <key> <field> <increment>
:为hash表中,key集合的所有field增加increment;hdel <key> <feild1> <field2>...
:删除hash表中,key集合的field1、field2…
有序集合Zset:(sorted set)
介绍:
- Redis中,有序集合 zset 与普通集合 set 不同之处在于:zset中每个成员都关联了一个double类型的评分score,用来(从最低分到最高分)排序集合中的成员,还可通过score的范围来获取元素的列表。
- 集合的成员是唯一的,但是评分可以是重复的。
- 由于是有序集合,故可以很快获取一个范围的元素;同时,访问一个中间元素也很快。
数据结构:
zset(sorted set)底层使用两个数据结构,
-
hash:目的关联元素value(即是hash中的field)和权重score(即是hash中的value),保障元素value的唯一性,可以通过元素value找到相应的score值。
-
跳跃表:目的在于给元素value排序,根据score的范围获取元素列表。
跳跃表效率堪比红黑树,实现远比红黑树简单。
常用命令:
-
zadd <key> <score1> <value1> <score2> <value2> ...
:将一个或多个member元素及其score值加入到有序集key中; -
zrange <key> <start> <stop> [withscores]
:返回有序集key中,下标为start、stop之间的元素,带withscores
则可让score和value返回到结果集;默认按照score进行升序排列;zrevrange <key> <start> <stop> [withscores]
:返回有序集key中,下标为start、stop之间的元素,带withscores
则可让score和value返回到结果集;按照score进行降序排列;zrange <key> 0 -1
:返回该key对应的集合的所有value值,同时会升序排列; -
zrangebyscore key min max [withscores] [limit offset count]
:返回有序集key中,所有score∈[min, max]的成员。有序集成员按score值递增排列。zrevrangebyscore key max min [withscores] [limit offset count]
:返回有序集key中,所有score∈[min, max]的成员。有序集成员按score值递减排列。 -
zincrby <key> <increment> <value>
:为元素value的score加上增量increment; -
zrem <key> <value>
:删除该集合下,指定值的元素; -
zcard key
:获取集合中元素的数量; -
zcount <key> <min> <max>
:统计该集合,分数区间score∈[min, max]
内的元素个数; -
zrank <key> <value>
:返回该值在集合中的下标值(从0开始);zrevrank <key> <value>
:返回该值在集合中的逆序下标值;
位图Bitmaps:
介绍:
Bitmaps位图本质是数组,它是基于String数据类型的按位的操作。
- Bitmaps实际上就是字符串(key-value),但是它可以对字符串的位进行操作;
- Bitmaps可看作以位为单位的数组(数组的每位只能为0/1,下标为偏移量);
- Bitmap支持的最大位数是
2^32
位,它可以极大的节约存储空间,使用512M
内存就可以存储多大42.9亿的字节信息(2^32 = 4294967296)。
命令:
-
setbit <key> <offset> <value>
:设置Bitmaps中某个偏移量的值;注意:偏移量offset,从0开始。
注意:第一次初始化时Bitmaps时,如果偏移量非常大,则整个初始化过程执行会比较慢,可能会造成Redis的阻塞。
-
getbit <key> <offset>
:获取Bitmaps中某个偏移量的值; -
bitcount <key> [start end]
:统计字符串从start到end字节比特值为1的数量;注意:
bitcount <key> 0 -1
:表示查看整个字符串中1的个数。 -
bitop and/or/not/xor <destkey> [key...]
:对多个bitmaps进行复合操作,并将结果保存在destkey中;注意:
key...
包含了要参与复合操作的所有bitmaps对应的键。 -
strlen <key>
:统计中的bitmap占用的字节数。
使用场景:
访问量较大的情况下,用户是否访问某网站的统计。
基数统计HyperLogLog:
介绍:
基数:一个集合元素经过“去重”后,真实的元素个数。
redis中,每个HyperLogLog
键只需花费12KB
内存,就可以计算接近2^64
个不同元素的基数。(这与计算基数时,元素越多耗费内存就越多形成对比。)
但HyperLogLog
只会根据输入元素来计算基数,并不会存储输入元素本身,所以HyperLogLog
不能像集合一样,返回输入的各个元素。
常用命令:
pfadd <key> <element> [element...]
:添加指定元素到HyperLogLog中,如果HLL估计的近似基数发生变化,则返回1否则返回0。pfcount <key> [key...]
:计算HLL的近似基数,可计算多个HLL。pfmerge <destkey> <sourcekey> [sourcekey...]
:将一个或多个HLL合并后的结果,存储在另一个HLL中。
Geospatial:
介绍:
redis基于该类型,提供了经纬度设置、查询、范围查询、距离查询、经纬度hash等操作。
命令:
geoadd <key> <longitude> <latitude> <member> {longitude latitude member...}
:添加经、纬度坐标和名称。getpos <key> <member> [member...]
:获取指定地区的坐标值。geodist <key> <member1> <member2> [m|km|mi|ft]
:获取两个位置之间的直线距离。georadius <key> <longitude> <latitude> radius m|km||mi|ft
:以给定的经纬度为中心,找出某一半径内的元素。
*redis Stream流:
redis5.0前,消息队列的2种方案:
List实现消息队列:
列表的头部(左边)插入或者尾部(右边)取出,按照插入顺序排序。
常用来做异步队列使用,将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。
缺点:仅支持点对点模式。
发布-订阅 (pub/sub) 实现消息队列:
发布-订阅机制是一种消息通信模式,即发送者pub
发送消息,订阅者sub
接收消息。本质上,是一个轻量的队列,但数据不会被持久化,一般用来处理实时性较高的异步消息。
注意:redis客户端,可订阅任意数量的频道。
redis
的发布-订阅模式包含了两种角色,分别是消息的发布者和订阅者。
- 订阅者可以订阅一个或者多个频道
channel
,发布者可以向指定的频道channel
发送消息且订阅该频道的所有订阅者都会收到此消息。 - 多个客户端同时订阅一个
channel
时,会被redis服务器放在channel
名为键值为一个链表上(将所有订阅该频道的客户端挂到上面)。
- 订阅频道的命令是
subscribe
,可以同时订阅多个频道,用法是subscribe channel1 [channel2...]
。
执行该命令客户端会进入进入订阅阻塞状态,等待该频道上的信息。此状态下客户端不能使用除subscribe、unsubscribe、psubscribe和punsubscribe
这四个属于"发布/订阅"之外的命令,否则会报错。
进入订阅状态后客户端可能收到3种类型的回复。每种类型的回复都包含3个值,第一个值是消息的类型,根据消类型的不同,第二个和第三个参数的含义可能不同。消息类型的取值可能是以下3个:
subscribe
:表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个是当前客户端订阅的频道数量。message
:表示接收到的消息,第二个值表示产生消息的频道名称,第三个值是消息的内容。unsubscribe
:表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态,之后就可以执行其他非"发布/订阅"模式的命令了。
缺点:消息无法持久化(如果出现网络断开、宕机等,消息就会被丢弃),也没有Ack
机制来保证数据的可靠性(假设一个消费者都没有,那消息就直接被丢弃了)。
Stream消息队列:
Redis5.0
新增的数据结构,即Redis版的MQ消息中间件+阻塞队列。它支持消息的持久化和主从复制的功能、支持自动生成全局唯一的ID、支持ack确认消息模式、支持消费组模式等,使消息队列更稳定、可靠。
Stream底层结构和原理:
一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容。
Message Content | 消息内容 |
---|---|
Consumer group | 消费组,通过XGROUP CREATE 命令创建,同一个消费组可以有多个消费者 |
Last_delivered_id | 游标,每个消费组会有个游标 last_delivered_id。任意一个消费者读取了消息,都会使游标 last_delivered_id 往前移动。 |
Consumer | 消费组中的消费者 |
Pending_ids | 消费者会有一个状态变量pending_ids,用于记录被当前消费已读但未ack(Acknowledge character:确认字符)的消息Id。它用来确保客户端至少消费了消息一次,而不会在网络传输的中途丢失了没处理。 |
基本操作:
队列相关的指令:
指令名称 | 指令作用 | 举例 |
---|---|---|
XADD | 添加消息到队列末尾 | XADD mystream * key1 value1 key2 value2 |
XTRIM | 限制Stream的长度,超出会截取 | XTRIM mystream maxlen 2 ,对流进行修剪,仅保留两个ID;XTRIM mystream minid msgID ,表示将小于msgID的ID全部抛弃; |
XDEL | 删除消息 | XDEL msgID ,表示删除ID对应的消息 |
XLEN | 获取Stream队列的消息的长度 | |
XRANGE | 获取消息列表(可指定范围),忽略删除的消息 | XRANGE mystream - + count 2 ,表示获取mystream流中前2个ID对应的值 |
XREVRANGE | 和XRANGE相比,区别在于反向获取,ID从大到小 | |
XREAD | 获取消息(阻塞/非阻塞),返回大于指定ID的消息 | XREAD [COUNT count][BLOCK milliseconds] STREAMS key [key...] ID [ID...] ,COUNT表示最多读取多少条消息,BLOCK是否以阻塞的方式读取消息,默认不阻塞,milliseconds=0则永远阻塞。 |
非阻塞读:
阻塞读:
redis通过XADD
和XREAD
循环阻塞读,来实现简单的消息队列:
消费组相关的指令:
消费组的作用:让组内的多个消费者共同分担读取消息,从而实现消息读取负载在多个消费者间是均衡分布的。
Stream实现的消息队列,如何保证消费者发生故障/宕机重启后,仍可以读取未处理完的消息?
Streams
会自动使用内部队列(也称为PENDING List
)留存消费组里每个消费者读取的消息保底措施,直到消费者使用XACK
命令通知 Streams
“消息已经处理完成”后该消息才会被删除。
指令名称 | 指令作用 | 举例 |
---|---|---|
XGROUP CREATE | 创建消费者组 | XGROUP CREATE mystream groupA $ ,表示从Stream的尾开始消费;XGROUP CREATE mystream groupA 0 ,表示从Stream的头开始消费; |
XREADGROUP GROUP | 读取消费者组中的消息 | > :表示从第一条尚未被消费的消息开始读取; |
XACK | ack消息,消息被标记为“已处理” | |
XGROUP SETID | 设置消费组最后递送消息的ID | |
XGROUP DELCONSUMER | 删除消费者组 | |
XPENDING | 打印待处理消息的详细信息 | 查看每个消费组内,所有消费者“已读未确认”的消息;查看某个消费者具体读取了哪些数据; |
XCLAIM | 转移消息的归属权(长期未被处理的消息,转交给其他消费者进行处理) | |
XINFO | 打印Stream/Consumer/Group的详细信息 | |
XINFO GROUPS | 打印消费者组的详细信息 | |
XINFO STREAM | 打印Stream的详细信息 |
使用细节:
- 同一消费组的消费者不可消费同一条消息,但不同消费组的消费者可消费同一条消息。
- 创建并从mystream的头开始,groupA消费组的消费者comsumer1,消费组中的所有数据。
- groupD消费组ACK签收了消费。
- 查看每个消费组内,所有消费者“已读未确认”的消息。
- 查看某个消费者具体读取了哪些数据。
四个特殊符号:
- +
:最小/大可能出现的Id。$
:表示只消费新的消息,当前流中最大的id,可用于将要到来的信息。>
:用于XREADGROUP
命令,表示迄今还没有发送给使用者的信息,会更新消费者组的最后ID。*
:用于XADD
命令中,让系统自动生成id。
Redis配置文件详解:
自定义的配置文件目录:/myredis/redis.conf
。
Units单位:
只支持 bytes,不支持 bit,且大小不敏感。
网络相关配置:
bind:
默认情况bind=127.0.0.1
只能接受本机的访问请求,不写则无限制接受任何ip地址的访问。
注意:生产环境中,服务器是需要远程访问的,所以需要将其注释掉该bind
语句。
如果开启了protected-mode
,那么在没有设定bind ip
且没有设密码的情况下,Redis 只允许接受本机的响应。
Port:
默认使用 6379 端口。
TCP-backlog:
设置tcp的连接队列backlog:backlog连接队列总和 = 半连接队列(未完成三次握手队列) + 全连接队列(已经完成三次握手队列)。
注意:在高并发环境下,需要一个高backlog值,来避免慢客户端连接问题。
另外,Linux内核会将这个值减小到/proc/sys/net/core/somaxconn (128)
的值,所以需要确认增大/proc/sys/net/core/somaxconn
和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)
两个值来达到想要的效果。
TimeOut:
一个空闲的客户端维持多少秒会关闭,0表示永不关闭。
TCP-keeplive:
对访问的客户端的一种心跳检测,每隔n秒检测一次,设置为0则不会进行Keepalive
检测。
GENERAL通用:
daemonize:
守护进程,后台启动。
pidfile:
存放pid文件的位置,每个实例会产生一个不同的pid文件。
loglevel:
指定日志记录级别,Redis共支持四个级别:debug、verbose、notice、warning
,默认为notice
。
logfile:
日志文件名称。
databases 16:
设定库的数量 默认16,默认使用0号数据库,可以使用SELECT <dbid>
命令在连接上指定数据库id。
LIMITS限制:
maxclients:
设置redis同时可以与多少个客户端连接。
- 默认情况下,为10000个客户端;
- 如果达到该限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出
max number of clients reached
;
maxmemory:
- 建议必须设置,否则内存被占满时,服务器会宕机;
- 设置redis可以使用的内存量:
- 一旦到达该内存使用上限,redis会使用
maxmemory-policy
来移除内部部分数据; - 如果redis无法根据移除规则移除内存中的数据或设置了“不允许移除”,则redis会针对那些需要申请内存的指令返回错误;
- 一旦到达该内存使用上限,redis会使用
maxmemory-policy:
volatile-lru
:使用lru算法,移除key(只对设置了过期时间的键);volatile-random
:在过期集合中,移除随机的key(只对设置了过期时间的键);volatile-ttl
:移除那些TTL值最小的key,即最近要过期的key;allkeys-lru
:在所有集合key中,使用lru算法移除key;allkeys-random
:在所有集合key中,移除随机的key;noeviction
:不进行移除,只针对写返回错误信息;
maxmemory-samples:
- 设置样本数量,redis默认会检查
maxmemory-samples
个key并选择其中LRU的那些; - 一般设置3~7,数值越小样本越精确,但性能消耗越大。
Redis事务操作:
Redis的事务定义:
Redis事务是一个单独的隔离的操作:事务中所有命令都会序列化,按顺序地执行。
事务在执行过程中,不会被其他客户端发送来的命令/请求所打断。
Redis事务的主要作用:串联多个命令,防止别的命令插队。
事务的三大特性:
-
单独的隔离操作:
事务中的所有命令都会序列化、按顺序执行。事务在执行过程中,不会被其他客户端发送来的命令请求所打断。
-
没有隔离级别的概念:
队列中的命令,没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
-
不保证原子性:
事务中,如果有一条命令执行失败,其后的命令仍然会被执行,不会回滚。
事务的命令:
Multi、Exec、discard:
从输入Multi命令开始,输入的命令会依次加入命令队列但并不会执行,直到输入Exec后,才会将之前的命令队列中的命令依次执行。
组队过程中,可通过discard来放弃组队(之前组队的命令,也不会执行)。
注意细节:
- 组队过程中,某个命令报告了错误,执行时整个队列都会被取消。
- 执行阶段,某个命令报错,则只有报错的命令不会被执行,其他命令会正常执行,且不会回滚。
事务冲突的问题:
悲观锁:
悲观锁Pessimistic Lock
(就是很悲观),每次在拿数据的时候都会上锁,这样别人想拿该数据就会block直到拿到锁。
传统的关系型数据库很多就用到了这种锁机制,比如行锁、表锁、读锁/写锁等,都是在做操作之前先上锁。
乐观锁:
乐观锁Optimistic Lock
(就是很乐观),每次去拿数据的时候都不会上锁,但是在更新的时候会(使用版本号等机制)判断一下在此期间别人有没有去更新这个数据。
- 乐观锁策略:提交版本 > 记录当前版本,才能去更新,
- 乐观锁适用于多读的应用类型,这样可以提高吞吐量。
Redis使用watch
提供乐观锁,类似于check-and-set
机制。
watch key1 [key2 …]:
在执行multi之前,先执行watch key1 [key2 ...]
可监视一个或多个key,如果事务在执行前检测到这个(或这些)key被其他事务的命令所改动,那么该事务将被打断。
unwatch:
取消对所有key的监控。
Redis持久化:
Redis持久化之RDB:
RDB:Redis DataBase,在指定的时间间隔内,将内存中的数据集snapshot
快照写入磁盘。恢复时,可将快照文件直接读到内存中。
RDB保存备份时,执行的是全量快照,即将内存中的所有数据都记录到磁盘中。
RDB是Redis数据的一个非常紧凑的单文件时间点表示,非常适合备份、灾难恢复。
备份是如何执行的?
Redis会单独创建一个(fork)子进程来进行持久化,会先将数据写入到一个临时文件中,持久化结束后会用该临时文件替换上次持久化好的文件。
整个过程中,主进程是不进行任何I/O操作的,这确保了极高的性能。但RDB相较于AOF的缺点是:最后一次持久化后的数据可能丢失(最后一次持久化后,redis突然挂了,导致还有一部分数据未来的及持久化,造成了数据丢失)。
注意:介绍一下fork:
- fork会复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)都和源进程保持一致,但是是一个全新的作为原进程的子进程存在。
- 一般Linux采用“写时复制技术”,起初父子进程会共用同一段物理内存,但当进程空间各段内容要发生变化时,则会将父进程的内容复制一份给子进程。
RDB持久化的流程:
在/myredis/redis.conf
文件中,默认会生成dump.rdb
文件。
rdb文件的保存路径,是可修改的,默认为Redis启动时命令行所在的目录下dir ./
即/usr/local/bin
目录下。
- 可进行自定义路径的修改,如
/myredis/dumpfiles/
。 config get dir
,可查看定义的RDB文件的保存路径。
如何触发RDB快照?
自动触发:
save
参数:配置文件中save
参数,是用来触发RDB的条件。
注:一般情况下,需要将备份的dump.rdb
文件与生产redis服务器分开,以防止生产物理机损坏后备份文件丢失。
手动触发:
命令save VS bgsave
:
save
:阻塞当前redis服务器,直到持久化完成(不推荐使用)。bgsave
(自动触发的默认方式):Redis会在后台通过fork的子进程来进行异步快照操作保存快照,主进程仍可以响应客户端请求。
注:通过lastsave
命令,可获取最后一次成功执行快照的时间。
执行shutdown:
执行shutdown且没有设置开启AOF持久化,也会触发RDB快照。(redis默认优先使用AOF方式进行持久化)
主从复制时,主节点自动触发
flushall命令:
flushall
命令,会产生dump.rdb
文件,但里面是空的,无意义。
rdb备份:
将dump.rdb
的文件进行备份cp dump.rdb dump.rdb.bak
,之后可通过该备份文件恢复redis的数据mv dump.rdb.bak dump.rdb
。
配置文件snapshotting模块:
stop-writes-on-bgsave-error:
当Redis无法写入磁盘的话,直接关掉Redis的写操作,推荐用yes。
rdbcompression压缩文件:
对于存储到磁盘中的快照,可设置是否进行压缩存储。
注意:如果不想消耗CPU来进行压缩的话,可关闭该功能。
rdbchecksum检查完整性:
在存储快照后,还可让redis使用CRC64
算法进行数据校验,推荐yes。
但这会增加约10%的性能消耗,如果希望获取更大的性能提升,则可关闭该功能。
禁用RDB:
修改配置文件/myredis/redis.conf
中,save ""
。
优劣势说明:
优势:
- RDB快照是全量备份,存储数据的二进制形式,非常紧凑,故适合大规模的数据恢复;
- 对数据完整性和一致性要求不高时,更适合使用;
- 按照业务定时备份;
- 恢复速度快:RDB文件在内存中的加载速度,要比AOF快很多;
劣势:
-
内存数据的全量同步,在数据量太大时,会导致I/O严重影响服务器性能;
-
fork时,内存中数据被克隆了一份,故需要考虑2倍的膨胀问题;
-
虽然Redis在fork时,使用了写时拷贝技术,但如果数据庞大时还是比较耗时的;
-
备份周期,会在一定间隔时间做一次备份,所以如果Redis意外挂了,就会丢失最后一次快照后的所有修改。
总结:
Redis持久化之AOF:
AOF:Append Of File.
以日志形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件,并从前到后执行一次以重新构建数据。
*AOF持久化流程:
- 客户端的请求写命令,会被append追加到AOF缓冲区内;
- AOF缓冲区根据AOF持久化策略
[always、everysec、no]
,将操作sync同步到磁盘的AOF文件中;(AOF缓冲区存在的目的:当这些命令达到一定量以后再写入磁盘,避免频繁的磁盘IO操作。) - AOF文件大小超过重写策略或手动重写时,会对AOF文件
rewrite
重写,压缩AOF文件容量; - Redis服务重启时,会重新load加载AOF文件中的写操作,达到数据恢复的目的;
redis,conf的配置参数:
AOF默认不开启:
appendonly no
,默认不开启AOF持久化方式。
可在/myredis/redis.conf
中配置文件名称,默认为appendonly.aof
。
AOF文件的保存路径,与RDB路径一致。
注意:如果RDB和AOF同时开启,Redis默认读取AOF中的数据(不会造成数据丢失)。
AOF启动/修复/恢复:
- AOF的备份机制和性能与RDB不同,但备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统时会自动加载。
- 正常恢复:
- 修改默认的
appendonly no
为yes
; - 将
.aof
文件复制一份保存到对应目录(config get dir
可查看目录信息); - 恢复:重启redis后重新加载;
- 修改默认的
- 异常恢复:
- 修改默认的
appendonly no
为yes
; - 如遇到
.aof
文件损坏,通过/usr/local/bin/redis-check-aof --fix appendonly.aof
进行恢复; - 备份被写坏的AOF文件;
- 恢复:重启redis后重新加载;
- 修改默认的
AOF同步频率设置:
appendfsync always
:始终同步,每个写命令执行完会立刻将日志刷入磁盘,性能较差但数据完整性较好。appendfsync everysec
:每秒同步,每个写命令执行完会先将日志写入AOF文件的内存缓冲区,之后每隔一秒会刷入磁盘一次,故redis挂了可能会丢失本秒的数据。appendfsync no
:redis不进行主动同步,每个写命令执行完先将日志写入AOF文件的内存缓冲区,之后同步时机由操作系统控制。
Rewrite重写机制:
是什么?
AOF采用追加的方式,会导致文件越来越大,为避免这种问题提出了重写机制。
触发机制,何时重写:
自动重写:
当AOF文件大小超过阈值后,redis会启动AOF文件的内容压缩,只保留可恢复数据的最小指令集。
重写虽节省了大量磁盘空间,减少恢复时间,但每次重写还是有一定负担的,故Redis要满足一定条件才会进行重写。
auto-aof-rewrite-percentage
:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件2倍时触发)。auto-aof-rewrite-min-size
:设置重写的基准值,最小文件64MB(达到该值则开始重写)。
# Redis会记录上次重写时,AOF的大小。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 上述为默认配置:当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。
手动重写:
手动则使用命令bgrewriteaof
来重写(即客户端向服务器发送bgrewriteaof
命令,来完成AOF的重写操作)。
*重写的流程:
-
bgrewriteaof
触发重写,判断是否当前有bgsave
或bgrewriteaof
在运行,如果有,则等待该命令结束后再继续执行。 -
主进程fork出子进程,会读取现有的AOF文件,并分析压缩其包含的指令后写入新的AOF文件中。
-
子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入
aof_buf
缓冲区和aof_rewrite_buf
重写缓冲区,保证原AOF文件可用性(避免在重写过程中出现意外)、新AOF文件生成期间的新的数据修改动作不会丢失。 -
1)子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。
2)主进程把
aof_rewrite_buf
中的数据写入到新的AOF文件中。 -
使用新的AOF文件,覆盖旧的AOF文件,完成AOF重写。
重写原理,如何实现重写:
AOF文件持续增长而过大时,会fork一条新进程来将文件重写(也是先写临时文件,再rename)。redis 4.0
后,是指将rdb
的快照,以二进制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。
no-appendfsync-on-rewrite=yes
,不写入aof文件只写入缓存,用户请求不会阻塞,即通过降低数据安全性来提高性能。no-appendfsync-on-rewrite=no
,还是将数据刷入磁盘,但遇到重写操作,可能会发生阻塞,即通过降低性能来换取数据安全。
优劣势:
优势:
备份机制更稳健,丢失数据概率更低。
劣势:
- 比RDB占用更多的磁盘空间。
- 恢复备份速度更慢。
- AOF的
always
同步策略(always/everysec/no
),有一定的性能压力。
总结:
对比RDB和AOF:
-
RDB持久化方式,能够在指定的时间间隔内,对数据进行快照存储。
-
AOF持久化方式,记录每次对服务器的写操作,当服务器重启时,会重新执行这些命令来恢复原始的数据,且AOF命令以redis协议追加保存每次写的操作到文件末尾。
Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。如果硬盘许可,应尽量减少AOF rewrite的频率,重写的基础大小默认值为64M太小,可设为5G以上。默认超出原大小的100%时重写,可改到适当的值。
-
选取策略:
1)如果只做缓存,则可不使用任何持久化方式。
2)如果对数据不敏感,则可单独用RDB。
3)不建议单独用AOF,因为可能会出现Bug。
4)同时开启下redis重启,会优先载入AOF文件恢复原始数据(因大多数情况下,AOF文件保存的数据集要比RDB文件保存的数据集完整)。
AOF和RDB的混合持久化:
加载顺序:
- 重启会优先载入AOF文件,来恢复原始的数据(AOF保存的数据比RDB要完整)。
- AOF在不断变化不好备份,故RDB更适合于备份数据库(RDB存在的合理性)。
原理:
- 开启混合方式设置:
aof-use-rdb-preamble yes
。 - RDB+AOF的混合方式:(RDB做全量持久化,AOF做增量持久化)
- 先使用RDB进行快照存储;
- 之后使用AOF记录所有的写操作,当重写策略或手动重写满足时,将最新的数据保存到RDB中。
重点:在重启redis服务时,会从RDB和AOF两部分恢复数据,既保证了数据的完整性,又提高了恢复数据的性能。
Redis纯缓存模式:
需要同时关闭RDB和AOF持久化方式。
-
禁用RDB:
save ""
禁用RDB模式下,仍然可以通过
save/bgsave
命令,手动生成rdb文件。 -
禁用AOF:
appendonly no
禁用AOF模式下,仍然可以通过
bgrewriteaof
命令,手动生成rdb文件。
Redis管道:
介绍:
管道pipeline
可以一次性发送多条命令给服务端,待服务端依次处理完完毕后,通过一条响应一次性将结果返回。
通过减少客户端与redis的通信次数,来实现降低往返延时RTT
时间。
pipeline
实现的原理是队列,先进先出特性就保证数据的顺序性。
举例:
pipeline与原生批量命令对比:
- 原生批量命令(如mset和mget等)是原子性的,pipeline管道是非原子性的。
- 原生批量命令一次只能执行一种命令,pipeline支持批量执行的不同命令。
- 原生批量命令是在服务端完成的,pipeline需要C/S共同完成。
pipeline与事务对比:
- 事务是原子性的,pipeline管道是非原子性的。
- 管道一次性将多条命令发送给服务器,事务是一条条发送且只有在收到exec命令后才会执行。
- 执行事务时会阻塞其他命令,而执行管道中的命令则不会。
注意:
使用pipeline组装的命令不能太多,不然会长时间阻塞,导致服务被迫回复一个队列答复,会占很多内存。
Redis主从复制:
介绍:
主机数据更新后根据配置和策略,自动将新数据异步同步到备机中的master/slave
机制,Master
以写为主,Slave
以读为主。
作用:
- 读写分离,性能扩展
- 容灾快速恢复
- 数据备份
- 水平扩容,支撑高并发
注:一般主从复制,指的是“一主多从”的分布。
主从复制的原理:
-
slave启动,发送sync给master:slave启动并成功连接master后,会发送一个sync命令给master;
-
首次连接,全量复制:主服务器master收到从服务器slave发送的同步消息后,把主服务器数据进行持久化到rdb文件中,并发送给从服务器,之后从服务器将其存盘并加载到内存中,从而完成了一次完全同步(从服务器的原数据会被覆盖清除);
-
心跳持续,保持通信:默认每隔10s,master会向其从服务器slave发送ping命令,检验其是否还存在。
-
进入平稳,增量复制:每次主服务器进行写操作后,会和从服务器进行数据同步;
-
从机下线,重连续传:master会检查backlog中的offset,只会把offset后面的数据复制给slave,类似断点续传。
两种复制方式:
一般来说,redis节点默认为主节点的,如果需要给其配置从节点,使用以下命令:
SLAVEOF 主节点ip 主节点port
# 执行这个命令之后会给主节点发送一个sync同步命令,如果是一开始的状态,那么就会执行“全量复制”;如果不是,就会执行“增量复制”。
- 全量复制:主节点执行
BGSAVE
命令,生成一个RDB文件,然后发送给从服务器slave,其在接收后将其存盘并加载到内存中。 - 增量复制:master 继续将新的所有收集到的修改命令依次传给slave,完成同步。在主节点及其从节点都有一个复制偏移量,如果主节点和从节点的的偏移量不一致,就会从主节点的缓冲区复制偏移量差值大小的数据到从节点。缓冲区默认大小为
1M
。
主从复制的缺点:
-
主从复制不是自动的,需要手动操作。
-
复制延时,信号衰减:由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙时延迟问题会更严重,增加Slave机器数量也会使该问题更加严重。
-
master一旦宕机,默认情况下slave无法替补。
配置一主两从:
// 1. 创建/myredis文件夹
mkdir /myredis
// 2. 复制redis.conf配置文件到文件夹中
cp /usr/local/bin/redis.conf /myredis/redis.conf
// 3. 配置一主两从,即创建三个配置文件
touch redis_6379.conf
touch redis_6380.conf
touch redis_6381.conf
// 配置/myredis/redis.conf
appendonly no
// 4. 分别给上述三个配置文件写入内容
# redis_6379.conf #
include "/myredis/redis.conf"
pidfile "/var/run/redis_6379.pid"
port 6379
dir "/myredis"
dbfilename "dump_6379.rdb"
# 下面两个save策略是“或”的关系:
# 1h内有数据变更,则写入RDB文件
save 3600 1
# 30秒内有10条数据变更,则写入RDB文件
save 30 10
# redis_6380.conf #
include "/myredis/redis.conf"
pidfile "/var/run/redis_6380.pid"
port 6380
dir "/myredis"
dbfilename "dump_6380.rdb"
# 主节点登录密码(如果主节点没有登录密码,则无需配置此选项)
masterauth ********* # 从机需要配置,主机不用
# redis_6381.conf #
include "/myredis/redis.conf"
pidfile "/var/run/redis_6381.pid"
port 6381
dir "/myredis"
dbfilename "dump_6381.rdb"
# 主节点登录密码(如果主节点没有登录密码,则无需配置此选项)
masterauth ********* # 从机需要配置,主机不用
## config get dir,可查看持久化文件的保存路径 ##
# 如果上述修改无法如期,可直接修改修改原redis.conf文件并重命名,代替include。
// 5. 启动三个不同port的redis服务(注意:先启动master主库,再启动slave从库)
redis-server /myredis/redis_6379.conf
redis-server /myredis/redis_6380.conf
redis-server /myredis/redis_6381.conf
# 查看系统进程中,已启动的redis服务
ps -ef | grep redis
// 6, 通过`redis-cli -p ****`连接三台redis服务,查看三台主机情况
info replication
// 7. 配从(库)不配主(库)
slaveof <主库IP> <主库Port> # 成为某个实例的从服务器(仅本次有效)
replicaof <主库IP> <主库Port> # 直接修改配置文件redis.conf(持久存在)
# 补充:slaveof no one,使当前数据库停止与其他数据库的同步,转为主数据库,即“自立为王”
slave 127.0.0.1 6379 # 在redis_6380和redis_6381从机中,分别执行该语句,即设置6380、6381端口对应的从机的主机为6379端口
// 8.先启动master主库,再启动slave从库
redis-server /myredis
常用的三种情况:
一主二仆:
- 当一台从机突然挂掉了,重启后会复制主机的所有数据;
- 从机只能读,主机可读写;
- 主机shutdown后,从机并不会上位,但会断开与主机的连接;
薪火相传:
上一个slave可以是下一个slave的master,即slave同样可以接受其他slave的连接和同步请求,那么该slave可作为链条中下一个的master,从而有效地减轻master的写压力,去中心化降低风险。
注意:中途变更转向,则会清除之前的数据,重新建立拷贝最新的数据。
风险:一旦某个slave挂了,后面的slave都无法完成备份。
反客为主:
当一个master宕机后,后面的slave可立刻升为master(slaveof no one
:可将从机变为主机),再后面的slave不用做任何修改。
Redis哨兵模式:
介绍:
反客为主的自动版,能够后台监控主机是否故障。一旦发生故障,则会根据投票数,自动将从库转换为主库。
哨兵的作用:
- 主从监控:利用 “心跳检测” 机制监视每一个节点,包括 master 和 slave。
- 消息通知:哨兵可将故障转移的结果发送给客户端。
- 故障转移:当 master 宕机,会进行主从切换,将其中的一个 slave切换为 master。
- 配置中心:客户端通过连接哨兵,来获得当前 Redis 服务的主节点地址。
注意:实际生产中,为保证高可用,实际生产中 Sentinel 哨兵都是分布在不同的主机上。一般不会出现三台 sentinel 同时宕机的问题。
sentinel.conf
配置文件中的内容:
配置项 | 说明 | |
---|---|---|
bind <IP> | 服务监听地址,用于客户端连接,默认为本机地址0.0.0.0 | 必须 |
daemonize <yes/no> | 是否以后台daemon方式运行,默认为yes | 必须 |
protected-mode <yes/no> | 安全保护模式,默认为no | 必须 |
port <portNumber> | 端口 | 必须 |
logfile <logFilePath> | 日志文件路径 | 必须 |
pidfile <pidFilePath> | pid文件路径 | 必须 |
dir <workDir> | 工作目录 | 必须 |
sentinel monitor <master-name> <ip> <port> <quorum> | quorum 表示确认“客观下线”的最少的哨兵数量,同一故障迁移的法定票数,避免无效的迁移。master-name是对某个master+slave组合的一个区分标识(一套sentinel可以监听多组master+slave这样的组合)。 | 必须 |
sentinel auth-pass <master-name> <password> | 在sentinel哨兵的配置文件中,配置连接master服务的密码。 | 必须 |
sentinel down-after-milliseconds <master-name> <milliseconds> | 指定多少毫秒之后,主节点没有应答哨兵,此时哨兵主观上认为主节点下线,即“主观下线”。 | 可选 |
sentinel parallel-syncs <master-name> <nums> | 表示允许并行同步的slave个数,当Master挂了后,哨兵会选出新的Master,此时,剩余的slave会向新的master发起同步数据。 | 可选 |
sentinel failover-timeout <master-name> <milliseconds> | 故障转移的超时时间,进行故障转移时,如果超过设置的毫秒,表示故障转移失败。 | 可选 |
sentinel notification-script <master-name> <script-path> | 配置当某一事件发生时所需要执行的脚本 | 可选 |
sentinel client-reconfig-script <master-name> <script-path> | 客户端重新配置主节点参数脚本 | 可选 |
使用步骤:
// 1. 按照前面配置“一主二从”的操作,调整为“一主二仆”模式,6379带着6380和6381
// 注意:这时主机的配置文件中,也需要配置masterauth *********(保证master宕机重启变成从机后能正常访问新的主机)
// 2. 自动定义的/myredis目录下,新建sentinel.conf文件
touch /myredis/sentinel.conf
// 3. 配置哨兵,即给/myredis/sentinel.conf填写内容
// 还需要根据实际情况配置:bind、daemonize、protected-mode、port、logfile、pidfile、dir
sentinel moniter mymaster 127.0.0.1 6379 1
# 其中,mymaster是对监控对象起的别名,1为至少有多少个哨兵同意迁移的数量
sentinel auth-pass mymaster *********
# 在sentinel哨兵的配置文件中,配置连接master服务的密码
// 4. 启动哨兵
redis-sentinel --sentinel /myredis/sentinel.conf
// 5. 当127.0.0.1:6379挂了,即shutdown后,哨兵会选择一个从机作为主机上位。之后,127.0.0.1:6379重新上线后,只能变为新主机的从机。
注意:
- sentinel.conf文件的内容,在运行期间会被sentinel动态进行更改。
- master-slave切换后,master_redis.conf、slave_redis.conf、sentinel.conf的内容都会发生改变,即master_redis.conf中会多一行slaveof的配置、slave_redis.conf中会少一行slaveof的配置、sentinel.conf的监控目标会随之调换。
哨兵运行流程和选举原理:
运行流程:
当某个哨兵检测到某个主节点下线了,该哨兵就会判断其为主观下线。然后去询问其他哨兵,如果判断其下线的哨兵数量达到一定数量,就会判断其为客观下线。之后,哨兵经过选举选出一个领头哨兵。这个领头哨兵会根据优先级从下线的主节点的从节点中挑选出一个作为新的主节点并进行故障转移。
当主从配置中的 master 失效后,从哨兵集群中挑选 “领导者 sentinel” 负责选举出一个新的 master 来自动接替原 master 的工作,主从配置中的其他 redis 服务自动指向新的 master 同步数据。
一般建议 sentinel 采用“奇数”台,防止某台 sentinel 无法连接到 master 导致误切换。
选举原理:
主观下线:
Subjective Down
主观下线:单个sentinel自己主观上检测到的关于master的状态:如果发送ping心跳后,一定时间内没有收到合法的回复,则达到了SDown
的条件(主观上认为其不可用了)。
客观下线:
Objective Down
客观下线:多个哨兵达成一致意见才能认为一个master客观上已经宕机。
为避免某个sentinel节点可能因为自身网络等原因导致无法连接master,所以需要多个sentinel都一致认为该master有问题,才可进行下一步操作,即保证了公平性和高可用。
选出领导者哨兵:
当主节点被判断客观下线后,各个哨兵节点会进行协商,先选举出一个领导者哨兵节点并由该领导者节点,进行故障迁移failover
。
选举使用的算法是Raft
算法:
Raft算法的基本思路先到先得,即在一轮选举中,哨兵A向B发送成为领导者的申请,如果B没有同意过其他哨兵,则会同意A成为领导者。
选出新的master:
选出新master的规则,前提剩余slave节点是健康的,则进行如下图操作:
-
配置文件
redis.conf
中,优先级replica-priority
最高的从节点(数字越小优先级越高)。 -
replica offset
:复制偏移位置offset
最大的从节点。 -
最小
Run ID
的从节点。(字典顺序,ASCII码排列)
新master的上位:
Sentinel leader
会执行slaveof no one
命令,让选出来的从节点成为新的主节点,并通过slaveof
命令让其他节点成为其从节点。
旧master恢复:
当旧master重新上线后,Sentinel leader
让其成为新master主节点的从节点。
总结:
- 当主机挂掉后,从机选举产生新的主机。选举的过程,会根据优先级
slave-priority/replica-priority
(值越小,优先级越高) — 偏移量offset(值越小,获取原主机的数据越全) —runid
产生(redis启动后,会随机产生的40位的runid)。 - 原主机重启后,会变成从机。
- 由于所有写操作都发生在master上,然后同步到slave上,所以该过程是有延迟的且slave的数量越多延迟越明显。
使用建议:
- 为保证高可用,哨兵本身应该是集群,其数量尽可能为奇数。
- 各哨兵节点的配置应一致。
- 哨兵集群+主从复制,并不能保证数据零丢失(sentinel在进行故障转移failover时,会有一定的时间消耗,导致业务的写操作会丢失),故而引出集群。
Redis集群:
问题:
-
容量不够时,redis如何进行扩容?
-
并发写操作,redis如何分离?
-
另外,主从模式、薪火相传、主机宕机等,都会导致IP地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。
之前通过代理主机来解决,redis3.0后提供了无中心化集群的配置。
- 代理主机:通过一台代理服务器转发客户端的请求,到不同的业务服务器。
- 无中心化集群:服务器之间相互可以访问,从而大大节省了服务器的数量。任意一个节点均可作为访问的入口。
介绍:
Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis集群通过分区partition来提供一定程度的可用性availability:即使集群中部分节点失效或无法通信,集群也可以继续处理命令请求。
作用:
- Redis集群支持多个Master,且每个Master节点可挂载多个Slave节点。
- 客户端与Redis节点的连接,只需连接集群中任一可用节点即可。
- 槽位Slot负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽、数据之间的关系。
分片和槽位
redis集群的槽位slot:
- redis集群没有一致性的概念,引入了哈希槽的概念。
- redis集群有16384个哈希槽,
hash_slot = CRC16(key) mod 16389
决定了每个key存放在哪个槽。 - redis集群的每个节点,负责一部分hash槽。
redis集群的分片:
为了找到给定key的分片,通过得到的HASH_SLOT
值,经过确定性哈希函数后,会将给定的key多次始终映射到同一个分片。
举例:
当前集群有三个节点,则每个key槽位的分配:
优势:
最大的优势:方便扩/缩容、数据分派和查找。
- 如果想要在集群中添加一个节点,可从其他节点匀出部分槽位到自己节点上。
- 如果想要移除集群中的一个节点,只需将其的槽位分给其他节点。
注意:将哈希槽从一个节点移动到另一个节点,并不会停止服务,故改变节点的槽位数并不会造成该节点变成不可用状态。
slot槽位映射的算法:
哈希取余分区:
根据hash(key)%N
(N
机器的台数),计算出哈希值,用来决定数据映射到哪个节点上。
- 优点:简单直接。只需提前规划好节点,就能起到负载均衡+分而治之的作用。
- 缺点:原来规划好的节点,进行扩容或者缩容就比较麻烦。如果需要弹性扩容或故障停机时,取模公式就需要发生变化,会导致全部数据重新洗牌。
一致性哈希算法:
设计目标:解决分布式缓存数据变动和映射问题,即当服务器个数变化时尽量减少影响客户端到服务器的映射关系。
算法的步骤:
-
构建一致性hash环:一致性哈希环,通过对2^32取模(代替了哈希取余分区的对机器台数进行取模),将整个哈希值空间组织成一个虚拟的圆环,逻辑上是一个环形空间。
-
redis服务器IP节点映射:将集群中各个IP节点(经过hash运算)映射到环上某个位置。
-
key落在服务器的落键规则:计算key的hash值
hash(key)
,确定在环上的位置,沿环顺时针“行走”第一台遇到的服务器,将该键值对存储在该节点上即可。
举例:有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。
优缺点:
优点:容错性、扩展性。
在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。
需要增加一台节点NodeX(X在A和B之间),只需重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。
缺点:数据倾斜的问题。
数据的分布和节点的位置有关,在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题。如下图。只有两台服务器:
哈希槽分区:
引出:
哈希槽实质上是一个数组,[0,2^14-1]形成的hash slot空间。
作用:
主要是为了解决一致性hash中,数据倾斜的问题。
解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。
多少个hash槽:
一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。
为何最大槽数为16384?
-
如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
在消息头中最占空间的是myslots[CLUSTER_SLOTS/8]
。 当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb。当槽位为16384时,这块的大小是: 16384÷8÷1024=2kb。
因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。
-
redis的集群主节点数量基本不可能超过1000个。
集群节点越多,心跳包的消息体内携带的数据越多,也会导致网络拥堵。因此redis作者不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。 -
槽位越小,节点少的情况下,压缩比高,容易传输。
Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap
的形式来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率slots / N
很高的话(N表示节点数),bitmap的压缩率就很低。
6个服务器组成的无中心化集群:
无中心化主从集群:无论从哪台主机写的数据,其他主机上都能读到数据。
注意:即使连接的不是主机,集群也会自动切换到主机存储。主机写,从机读。
集群的创建:
三主三从。
Linux_docker搭建redis主从复制集群、扩/缩容。
// 1. 删除之前的持久化数据
rm ***.rdb, ***.aof
// 2. 重新修改配置文件,并创建六个相同的配置文件redis_6379.conf
// 注意:不同的节点,port、一些文件的命名不同。
// 节点端口号:6379、6380、6381、6389、6390、6391
#include /myredis/cluster/redis.conf
bind 0.0.0.0
# 关闭保护模式
protected-mode no
# 修改为后台启动
daemonize yes
# 修改端口号
port 6379
# 指定数据文件存储位置
logfile "/myredis/cluster/redis_err_6379.log"
pidfile "/var/run/redis_6379.pid"
dir "/myredis/cluster"
dbfilename "dump_6379.rdb"
# 开启aof模式持久化
appendonly yes
appendfilename "appendonly_6379.aof"
# 设置连接Redis需要密码
requirepass *********
# 设置Redis节点与节点之间访问需要密码
masterauth *********
# 打开集群模式
cluster-enabled yes
# 设置节点配置文件名
cluster-config-file nodes_6379.conf
# 设定节点失联时间,超过该时间ms,集群自动进行主从切换
cluster-node-timeout 15000
// 3. 启动redis的六个集群节点
// 4. 将六个节点合成一个集群(组合前,确保启动的6个节点都生成了nodes_****.conf文件)
cd /opt/redis-6.2.1/src/
redis-cli -a *********(登录密码) --cluster create --cluster-replicas 1 192.168.10.5:6379 192.168.10.5:6380 192.168.10.5:6381 192.168.10.5:6389 192.168.10.5:6390 192.168.10.5:6391
# 此处,需采用真实的IP地址(不能使用127.0.0.1),生产环境中这些IP肯定是不同的(这里采用不同的端口,模拟不同的主机)
# --replicas 1,表示采用最简单的方式配置集群,即一台主机配一台从机(共三组),创建成功后会自动分配主从关系。
# 分配原则:尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。
# 注意,执行命令后,选确认配置
yes
一个 Redis 集群包含 16384 个插槽(hash slot
), 数据库中每个键都属于这 16384 个插槽中的一个, 集群使用公式 CRC16(key) % 16384
来计算键 key 属于哪个槽(CRC16(key)
:指的是键 key 的 CRC16
校验和) 。
集群中,每个节点负责处理一部分插槽,[0, 5460]
、[5461,10922]
、[10923, 16383]
号插槽。
集群的验证:
# 通过客户端登录并访问后台redis服务器
# -a:密码认证
# -c:连接集群
# -h:集群中任意一个Redis节点IP、端口
redis-cli -a ********* -c -h 192.168.10.5 -p 6379/6380/6381/6389/6390/6391
# -c:采用“集群策略”连接来“优化路由”,设置数据会“自动切换”到相应的“写主机”
# 定位key属于哪个槽位
cluster keyslot 键名称
# 查看集群中,当前节点的信息
INFO REPLICATION
# 查看集群中,各个节点的信息(槽位信息、主从信息)
CLUSTER NODES
CLUSTER INFO # 查看集群的信息
主从容错切换迁移:
在 Redis 集群中,当主节点下线后,集群会根据预设的故障转移机制自动将一个从节点升级为新的主节点,以继续提供服务。这个过程是自动进行的,无需人工干预。
-
当原主节点恢复后,它会变成一个新的从节点,开始复制新的主节点的数据。这个过程也是自动进行的,不会影响 Redis 集群的正常运行。
-
注意:主节点下线 ~ 新的主节点选举出来之前,Redis 集群会进入一个短暂的不可用状态,这个时间通常不超过几秒钟。
在此期间,客户端可能会收到一些错误响应,需要在自己的代码中处理这些错误情况。
redis集群中不保证强一致性。在特定的条件下,redis集群可能会丢失一些写请求命令。
如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?
cluster-require-full-coverage yes
,那么整个集群都挂掉;cluster-require-full-coverage no
,那么该插槽数据全都不能使用,也无法存储。
集群的操作:
主节点的添加:
# 添加节点:可添加节点将一个新的节点添加到集群中
# -a:密码认证(没有密码不用带此参数)
# --cluster add-node 添加节点IP:添加节点端口 任意存活节点IP:任意存活节点端口
redis-cli -a ********* --cluster add-node newIP:newPort existedIP:existedPort
## 使用cluster nodes命令查看集群信息表,已经添加到了新的集群,但并没有任何的槽位信息,这时就需要给其迁移分配槽位
# 槽位迁移:将其它主节点的分片迁移到当前节点中
# -a:密码认证(没有密码不用带此参数)
# --cluster reshard:节点IP:节点端口,中迁移槽位到当前节点中
redis-cli --cluster reshard 节点IP:节点端口
## 该过程中,需要完成的步骤:
## 1. 需要指定分配多少个槽位出来给新节点
## 2. 将这些槽位给哪个节点ID
## 3.1. all:从每个有槽位的节点,取出一部分移动到这个新节点中
## 3.2. sourceID + done:即可从sourceID中直接取出指定个数的槽位给(1.)提到的新节点
从节点添加:
# 先将从节点添加到集群中
# 添加节点:可添加节点将一个新的节点添加到集群中
# -a:密码认证(没有密码不用带此参数)
# --cluster add-node 添加节点IP:添加节点端口 任意存活节点IP:任意存活节点端口
redis-cli -a ********* --cluster add-node newIP:newPort existedIP:existedPort
## 使用cluster nodes命令查看集群信息表,已经添加到了新的集群,但新添加的节点都是主节点
# 将新添加的节点(主节点)设置为从节点
redis-cli -a ********* -h newIP -p newPort
## 将当前节点分配为 NodeID 对应的节点的从节点
CLUSTER REPLICATE NodeID
# 查看集群中,节点信息
cluster nodes
删除主节点:
# 将待删除的主节点的槽位进行迁移
## 槽位迁移:将待删除的主节点的槽位迁移到目标节点中
### -a:密码认证(没有密码不用带此参数)
### --cluster reshard:节点IP:节点端口,中迁移槽位到当前节点中
redis-cli --cluster reshard 目标节点IP:目标节点端口
### 该过程中,需要完成的步骤:
### 1. 需要指定分配多少个槽位出来给目标节点
### 2. 将这些槽位给哪个目标节点ID
### 3. sourceID + done:即可从sourceID中直接取出指定个数的槽位给(1.)提到的新节点
# 查看集群的节点信息,保证待删除结点已无槽位
cluster nodes
# 执行如下命令删除节点:
# -a:密码认证(没有密码不用带此参数)
# --cluster del-node 连接任意一个存活的节点IP:连接任意一个存活的节点端口 待删除节点NodeID
redis-cli -a ********* --cluster del-node 任一存活节点的IP:任一存活节点的Port 待删除节点NodeID
删除从节点:
# 执行如下命令删除节点:
# -a:密码认证(没有密码不用带此参数)
# --cluster del-node 连接任意一个存活的节点IP:连接任意一个存活的节点端口 待删除节点NodeID
./bin/redis-cli -a ********* --cluster del-node 任一存活节点的IP:任一存活节点的Port 待删除节点NodeID
重新分配槽位:
该功能涉及到集群槽位的大量迁移,会导致整个Redis阻塞/停止处理客户端的请求。
# -a:密码认证(没有密码不用带此参数)
# --cluster rebalance 任意一个存活的节点IP:连接任意一个存活的节点端口
redis-cli -a ********* --cluster rebalance 任一存活节点的IP:任一存活节点的Port
# 分配完成后,可以看到所有的主节点的槽位都被重新分配了
cluster nodes
在集群中录入值:
在redis-cli中,每次录入、查询键值,redis会计算出key对应的槽位。如果不是该客户端对应服务器的插槽,redis会报错并告知要前往的redis实例地址和端口。
redis-cli -c
登入后,录入、查询键值对,可实现自动重定向。
# 不在一个shot下的键值,不能使用mset、mget等多键操作
# 可通过{}定义键所属的组,从而使key中{}相同组的键值对放在一个slot中
mset key1{groupName}value1 key2{groupName}value2 ...
查询集群中的值:
# 查询集群中,某个节点的插槽中,键值对的个数
cluster countkeysinslot slotID
# 查询集群中,某个节点的插槽中,前n个键
cluster countkeysinslot slotID n
# 查询集群中,某个组的所有键对应哪个插槽
cluster keyslot groupName
优缺点:
优点:实现扩容、分摊压力、无中心配置相对简单;
缺点:多键操作不被支持、LUA脚本不被支持;
redis常用的三种架构:
单例、哨兵、集群。
-
单例,最简单。
-
哨兵模式下,有哨兵节点监视master和slave,若master宕机可自动将slave转为master,存在的问题:不能动态扩充即存储大小受每个节点的内存大小限制、会存在一定的数据丢失。
-
集群模式Redis-Cluster,采用无中心结构,每个节点都和集群内其他节点有连接,数据可以跨主机分布式存储,解决了存储大小受主机限制的问题,Redis集群预分好16384个插槽(slot),每个节点分配一部分slot,当需要在 Redis 集群中放置一个 key-value 时,根据哈希算法决定将key放到哪个slot中,进而找到对应存放数据的主机,查询数据也一样。
集群模式内部同样可以配置主从,例如集群有六个数据节点,可以设置三个主节点,每个主节点对应一个从节点,当一个主节点宕机,可以自动将从节点变成主节点,保证整个集群还能用。但是一个主节点和对应的从节点都宕机后集群将不可用,但每个主节点可以配置多个从节点来解决该问题。
Redis应用问题解决:
缓存穿透:
问题描述:
key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,从而都会压到数据源,可能造成数据源的崩溃。
缓存一定不存在、查询不到的数据(由于缓存是不命中时被动写的,并且处于容错考虑,如果存储层查不到数据则不写入缓存),这将导致该不存在的数据每次请求都到存储层去查询,失去了缓存意义。
解决方案:
-
对空值缓存:如果一个查询返回的数据为空(不管数据是否为空),仍然将这个空结果(null)进行缓存,设置空结果的过期时间会很短(最长不超过5min)。缺点:存储空对象增加缓存开销。
-
设置可访问的名单(白名单):使用
bitmaps
类型定义一个可访问的名单(名单id作为bitmaps
的偏移量),每次访问和bitmaps
里面的id进行比较(如果访问id不在bitmaps
中,进行拦截,不允许访问)。 -
采用布隆过滤器:实际是一个很长的二进制向量(位图)和一系列随机映射函数(hash函数),可用来检索一个元素是否在一个集合中。优点:空间效率和查询时间远远超过一般算法;缺点:有一定的误识别率和删除困难的问题。
将所有可能存在的数据hash到一个足够大的
bitmaps
中,一个一定不存在的数据会被这个bitmaps
拦截掉,从而避免了对底层存储系统的查询压力。
缓存击穿:
问题描述:
key对应的数据存在,但在redis中过期,如果此时有大量并发的请求(会发现缓存中key已过期),会从数据库中加载并回设到缓存中,该过程中大量出现的并发请求数据库可能导致崩溃。
key可能会在某些时间点被超高并发地访问,是一种非常热的数据,就需要考虑缓存被“击穿”的问题。
解决方案:
-
预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis中,且加大这些热门数据的时长。
-
使用锁:
- 当缓存失效(即返回空)时,不会立即去查询数据库;
- 设置key的排它锁,保证只有一个线程去查询数据库,其他并发查询该key的线程处于阻塞状态;
- 直到该线程查询到数据并同步到缓存中,则释放排它锁,其他线程可查询。
缓存雪崩:
问题描述:
缓存雪崩和缓存击穿的区别:针对很多key缓存过期,而后者则是某个key缓存过期。
正常的访问:
缓存失效瞬间:
缓存失效时,产生的雪崩效应,对底层系统的数据库有很可怕的影响!
解决方案:
- 构建多级缓存架构:nginx缓存 + redis缓存 + 其他缓存。
- 使用锁或队列:用加锁/队列的方式,保证不会有大量的线程对数据库进行一次性的读写,从而避免失效时大量的并发请求落到底层存储系统上。缺点:不适用高并发情况。
- 设置过期标志更新缓存:记录缓存数据是否过期(提前设置),如果过期则会触发通知另外的线程在后台去更新实际key的缓存。
- 将缓存失效的时间分散开:如可通过在过期时间+1-5分钟的随机值,避免集中失效事件发生。
分布式锁:
介绍:
随着业务的发展,原单体单机部署的系统被演化为分布式集群系统后,由于分布式系统多线程、多进程且分布在不同机器上,这会导致原单机部署情况下的并发控制锁策略失效。
分布式锁主流的实现方案:
- 基于数据库实现分布式锁;
- 基于缓存(redis等);(基于redis实现分布式锁,性能最高)
- 基于zookeeper;(可靠性最高)
使用redis实现分布式锁:
redis命令:
set key val NX PX 1000
EX second
:设置键的过期时间为second秒,且SET key value EX second
等同于SETEX key second value
;PX millisecond
:设置键的过期时间为millisecond毫秒,且SET key value PX second
等同于PXSETEX key second value
;NX
:只有在键不存在时,才对键进行设置操作,set key value NX
等同于SETNX key value
;NX
:只有在键已经存在时,才对键进行设置操作。
redis分布式锁的实现过程:
- 多个客户端同时获取锁
setnx
; - 某个客户端获取成功后,执行业务逻辑(从数据库中获取数据,放入缓存中),执行完成后释放锁(del);
- 其他客户端等待获取锁…
存在的问题:
-
当
setnx
获取到锁,但业务逻辑出现了问题,导致锁无法释放。解决方法:设置过期时间,过期后自动释放锁。
-
当某个服务器因业务逻辑执行时间较长,超过了key的过期时间,会导致误删其它服务器的锁(会出现该服务器的业务逻辑只执行了一部分后就没锁了)。
解决方法:
setnx
获取锁时,设置一个指定的唯一值(如uuid
),释放前通过uuid
判断是否是自己的锁。 -
当某个服务器验证完uuid准备释放锁前,上图提到
<key=lock, value=uuid>
的锁刚好到期被自动释放了,从而出现其他服务器重新对key=lock
上锁并重设uuid
,之后前面的服务器会误删掉该重设uuid
的服务器拿到的锁(可能会出现该服务器的业务逻辑只执行了一部分后就没锁了)。解决方法:通过LUA脚本,保证删除的原子性。
总结:
为了确保分布式锁的可用,至少要确保锁的实现同时满足一下四个条件:
- 互斥性:任意时刻,只有一个客户端持有锁。
- 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 解铃还须系铃人:加锁和解锁,必须为同一个客户端。
- 加/解锁的原子性:加锁和解锁,必须是原子性的。
Redis6的新功能:
ACL:
介绍:
Access Control List访问控制列表,缩写是ACL,该功能允许根据可执行的命令和可访问的键来限制某些连接。
Redis6提供ACL的功能对用户进行更细粒度的权限控制:
- 接入权限:用户名和密码。
- 可执行的命令。
- 可操作的Key。
命令:
acl list
:查看用户权限列表。
acl cat
:查看添加权限指令类别。
acl whoami
:查看当前用户。
-
使用
aclsetuser
命令创建和编辑用户ACL
:-
ACL
规则:ACL规则 类型 参数 说明 启动和禁用用户 on 激活某用户账号 off 禁用某用户账号 权限的添加和删除 +<command>
将指令添加到用户可以调用的指令列表中 -<command>
从用户可以调用的指令列表中移除指令 +@<category>
添加该类别中,用户要调用的所有指令。可通过 acl cat
获取完整有效的类别,特殊的@all
表示所有命令。-@<category>
从用户可调用指令中,移除类别 可操作键的添加或删除 ~<pattern>
添加可作为用户可操作的键的模式。 ~*
:允许所有键 -
通过命令创建新用户默认权限:
acl setuser userName
-
设置有用户名、密码、
ACL
权限:acl setuser userName on >password ~cached:* +get
-
切换用户,验证权限:
-
IO多线程:
介绍:
IO多线程,指客户端交互部分的网络IO交互处理模块多线程,而Redis6执行命令仍是单线程。
原理架构:
Redis6的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍是单线程。如此设计的原因:不想因为多线程而变得复杂,需要去控制 key
、lua
、事务,LPUSH/LPOP
等的并发问题。
整体设计,大体如下:
# 多线程IO:默认不开启,需要通过配置文件配置
io-threads-do-reads yes
io-threads 4
Redis6其他新功能:
-
RESP3
通信协议:优化服务端与客户端之间通信。 -
Client side caching
客户端缓存:基于RESP3
协议实现的客户端缓存功能。为了进一步提升缓存的性能,将客户端经常访问的数据缓存到客户端,减少TCP
网络交互。 -
Proxy
集群代理模式:让Cluster
拥有像单实例一样的接入方式,降低使用cluster
的门槛。注意:代理不改变
Cluster
的功能限制,不支持的命令还是不会支持(比如跨slot
的多Key
操作)。 -
Modules API
:Redis
模块API
开发进展非常大,因为Redis Labs
为开发复杂功能,从一开始就用上Redis
模块。Redis
可以变成一个框架,是一个向编写各种系统开放的平台。