认识Redis
Redis是目前最受欢迎的NoSQL数据库之一;db-engines排名上,所有数据库类型排名第六,键值对数据库排名第一。
Redis是用C语言编写的开源、支持多种数据结构、支持网络、基于内存、可选持久性的键值对内存型数据库,具备以下特征:
- 基于内存运行、效率高
- 单线程运行
- 支持分布式
- key-value的存储结构
- 开源,基于C语言编写,遵守BSD协议,提供多种语言API
相比于其他数据库,Redis是C/S通信模型,单进程单线程模型,操作具有原子性,支持持久化,支持Lua脚本
Redis应用场景
Redis的应用场景主要包括:缓存系统(“热点”数据:高频读、低频写)、计数器、排行榜、消息队列、社交网络和实时系统、分布式锁、限流等。这里不进行详细描述,后续随实际应用说明。
Redis的数据类型
Redis提供的数据类型包括:字符串、哈希、列表、集合、有序集合、自定义类型等,也包括位图、GEO(3.2+)、Stream(5.0+)等
通用指令
通用指令是指不分数据类型,都可以使用的命令,常见的有
- keys:查看符合模板的所有key,遍历所有的键,时间复杂度为O(n),不建议在生产环境使用
- dbsize:直接获取redis内置的键总数遍历
- del:删除一个key,返回删除成功key的个数
- type:获取键的数据结构类型;键不存在返回none
- exists:查看一个key是否存在
- expire:给一个key设置有效期,到期后key自动删除;过期时间单位是s
- ttl:查看一个key的剩余有效期;-1代表永不过期,-2代表不存在key,其余表示剩余有效期
一、String类型
String是最基本的key-value结构,key是唯一标识,value是二进制安全的字符串,不仅可以存储字符串,也可以是数字(整数或浮点数)或者图片视频等多种形式,最大长度支持512M。
1.1 常用指令
127.0.0.1:6379> set name zhangsan # 最基本的string类型设置,设置成功返回OK
OK
127.0.0.1:6379> get name # 获取name的值,若name不存在,返回nil
"zhangsan"
127.0.0.1:6379> exists name # 监测一个key是否存在,存在返回1,不存在返回0
(integer) 1
127.0.0.1:6379> strlen name # 获取name的value字符长度;中文字符占3位
(integer) 8
127.0.0.1:6379> del name # 删除一个key
(integer) 1
127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置键值对
OK
127.0.0.1:6379> mget key1 key2 # 批量获取键值对
1) "value1"
2) "value2"
127.0.0.1:6379> set number 0 # 设置数字类型
OK
127.0.0.1:6379> incr number # 将number的值自增1,返回自增后的值
(integer) 1
127.0.0.1:6379> incrby number 10 # 将number的值自增指定数值,返回自增后的值
(integer) 11
127.0.0.1:6379> decrby number 10 # 将number的值自减指定数值,返回自减后的值
(integer) 1
127.0.0.1:6379> decr number # 将number的值自减1,返回自减后的值
(integer) 0
127.0.0.1:6379> get number
"0"
127.0.0.1:6379> expire number 60 # 设置key的过期时间,单位是s;针对已经存在的key
(integer) 1
127.0.0.1:6379> ttl number # 查看key还有多久过期
(integer) 56
127.0.0.1:6379> ttl number
(integer) 55
127.0.0.1:6379> set key name EX 60 # 设置key-value类型的值,并设置该key的过期时间
OK
127.0.0.1:6379> ttl name # ttl监测一个不存在的key,返回-2
(integer) -2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> setnx name2 zhangsan2 # 不存在就插入
(integer) 1
1.2 应用场景
1.2.1、缓存对象
使用String来缓存对象可以有两种方式;
第一种,直接缓存整个对象的JSON字符串,例子:set user:1 '{"name":"张三", "age":18}'
第二种,将key分离成 user: id : 属性 的形式,采用mset来存储,mget来获取属性值,例子:mset user:1:name 张三 mset user:1:age 19
1.2.2、常规计数
因为Redis处理命令是单线程运行的,执行命令的过程是原子性的。所以String类型适合计数场景,比如点赞、转发、库存数等
127.0.0.1:6379> set article:readcount:1001 0
OK
127.0.0.1:6379> incr article:readcount:1001
(integer) 1
1.2.3、分布式锁
set命令有个NX参数可以实现 “key不存在就插入” 的效果,可以用它实现分布式锁(最简单的,不建议使用,仅供了解)
set lock_key unique_value NX PX 10000
解锁的过程就是将lock_key删除,要保证操作的客户端就是加锁的客户端。在解锁的时候,先要判断unique_vale是否为加锁客户端,如果是,则将lock_key删除。
1.2.4、共享Session信息
在管理或者登录系统时,常用Session保存用户的会话状态,这些Session会被保存在服务器端,但这只适合单系统应用,对于分布式应用系统,则需要Redis的保驾护航;所有的后端服务器连接Redis,从Redis中获取Session信息,这样可以避免用户请求被分配到不同服务器导致的会话丢失问题。
二、List类型
列表是一个插入顺序排序(有序)的字符串元素集合,可以从头部或者尾部插入元素,列表的最大长度为2^32-1;
列表底层是由双向链表或压缩链表实现:如果列表中元素个数小于512(list-max-ziplist-entries)同时列表中每个元素的值都小于64(list-max-ziplist-value)字节,Redis使用ziplist作为List类型的底层数据结果;反之,使用linkedlist作为List类型的底层数据结构。
自Redis3.2+,默认使用quicklist作为List类型的底层数据结构
2.1 常用命令
127.0.0.1:6379> lpush mqlist 1 2 3 4 5 6 # 从头部添加一个或多个元素,先入的在尾部,后入的在头部
(integer) 6
127.0.0.1:6379> rpush mqlist 9 8 7 # 从尾部添加一个或多个元素,先入的在头部,后入的在尾部
(integer) 9
127.0.0.1:6379> lrange mqlist 0 -1 # 遍历列表中的元素,索引从0开始
1) "6"
2) "5"
3) "4"
4) "3"
5) "2"
6) "1"
7) "9"
8) "8"
9) "7"
127.0.0.1:6379> lpop mqlist 2 # 从左边“弹出”两个元素
1) "6"
2) "5"
127.0.0.1:6379> rpop mqlist # 从右边“弹出”一个元素,不指定count默认为1
1) "7"
127.0.0.1:6379> lrange mqlist 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "9"
6) "8"
127.0.0.1:6379> lset mqlist 4 0 # 将列表中指定索引的元素设为指定值
OK
127.0.0.1:6379> lset mqlist 5 -1
OK
127.0.0.1:6379> llen mqlist # 获取列表的长度
(integer) 6
127.0.0.1:6379> lrange mqlist 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "0"
6) "-1"
2.2 应用场景
2.2.1、消息队列
在讲消息队列的实现前,需要直到如何使用list相关的指令实现队列(先入先出)和栈(先入后出)的效果:
通过LPUSH/RPOP和RPUSH/LPOP实现队列:
$ lpush myList value1
$ lpush myList value2 value3
$ rpop myList => "value1"
$ rpop myList => "value2"
通过LPUSH/LPOP和RPUSH/RPOP实现栈:
$ rpush myList value1 value2 value3
$ rpop myList => "value3"
如果要使用List作为消息队列保存消息,可以利用List类型先进先出的特点,满足消息队列消息保序的需求。
使用lpush+rpop(rpush+lpop)命令实现消息队列
在生产者向list中写入数据时,list并不会主动通知消费者接收消息,需要消费者不断调用rpop命令。这样带来的问题是性能的损耗,因为要保证及时接收消息,就需要不停rpop,消费者一直不停的rpop,对于CPU来说是种没必要的损耗
为了解决这一问题,Redis提出了brpop命令:阻塞式读取,消费端在没有读到队列中数据时,自动阻塞,直到有新的数据写入队列。
同时,要保证消息不被重复消费,就需要在写入的时候指定一个全局唯一ID。
List是不支持多个消费者消费同一条消息的,因为一条消息被一个消费者拉取到后,就会立即从List中删除;要实现一条消息可以被多个消费者消费,就不得不提消费组的概念,但是List不支持消费组的实现。Redis从5.0版本开始提供了Stream数据类型,可以更好的实现消息队列
三、Hash类型
Hash是一个String类型的键值对的映射表,特别适合存储对象,类似jdk1.8前的HashMap。
Hash类型的底层数据结构是ziplist或hashtable实现的:如果元素个数小于512(hash-max-ziplist-entries)且所有值小于64(hash-max-ziplist-value)字节,选用ziplsit;否则,选用hashtable作为底层数据结构
Redis7.0+采用listpack数据结构实现Hash类型。
3.1 常用命令
127.0.0.1:6379> hset hash1 name lisi # 设置hash类型key-value
(integer) 1
127.0.0.1:6379> hmset hash1 age 18 sex man # 批量设置hash类型key-value
OK
127.0.0.1:6379> hget hash1 name # 获取指定字段的值
"lisi"
127.0.0.1:6379> hdel hash1 name # 删除指定字段,返回删除成功的个数
(integer) 1
127.0.0.1:6379> hlen hash1 # 获取hash类型字段个数
(integer) 2
127.0.0.1:6379> hgetall hash1 # 获取hash类型所有字段的key-value
1) "age"
2) "18"
3) "sex"
4) "man"
127.0.0.1:6379> hincrby hash1 age 2 # 对指定字段做自增自减操作(正数为自增、负数为自减)
(integer) 20
127.0.0.1:6379> hexists hash1 name # 判断hash类型中指定字段是否存在,存在返回,不存在返回0
(integer) 0
3.2 应用场景
3.2.1、缓存对象
Hash类型的结构(key:{field:value})与对象的结构类似,可以用来存储对象。比如User对象的结构如下:
uid(pk) | uname | uage | ugender |
---|---|---|---|
e368e5b2-e5d0-425e-8abb-e439d23c288c | 李四 | 19 | 男 |
对应在Redis中,可以这样存储:
$ hmset uid:e368e5b2-e5d0-425e-8abb-e439d23c288c uname 李四 uage 19 ugender 男
3.2.2、购物车
利用hincrby命令实现商品数量的增减
127.0.0.1:6379> hset cart:user-1 macbook 1 # 添加商品
(integer) 1
127.0.0.1:6379> hincrby cart:user-1 macbook 1 # 添加数量
(integer) 2
127.0.0.1:6379> hlen cart:user-1 # 获取商品总数
(integer) 1
127.0.0.1:6379> hdel cart:user-1 macbook # 删除商品
(integer) 1
127.0.0.1:6379> hgetall cart:user-1 # 获取购物车
(empty array)
四、Set类型
Set类型是一个无序且唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储。一个集合最多可以存储2^32-1个元素。
Set类型的底层数据结构是由HashTable或IntSet实现的:
1. 如果集合中的元素都是整数且元素个数小于512(默认值,set-maxintset-entries配置),Redis会使用IntSet作为Set类型的底层实现
2. 反之,使用HashTable作为Set类型的底层数据结构
4.1 常用命令
127.0.0.1:6379> sadd article:001 user1
(integer) 1
127.0.0.1:6379> sadd article:001 user2 # 向集合中存入元素
(integer) 1
127.0.0.1:6379> srem article:001 user1 # 从集合中删除元素
(integer) 1
127.0.0.1:6379> smembers article:001 # 列出集合中的所有元素
1) "user2"
127.0.0.1:6379> scard article:001 # 获取集合中的元素个数
(integer) 1
127.0.0.1:6379> sismember article:001 user1 # 判断集合是否存在指定的元素
(integer) 0
127.0.0.1:6379> srandmember article:001 # 随机列出count个元素
"user2"
127.0.0.1:6379> smembers article:001
1) "user2"
127.0.0.1:6379> spop article:001 # 随机弹出count个元素,弹出即删除
"user2"
127.0.0.1:6379> smembers article:001
(empty array)
运算操作:
Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞
127.0.0.1:6379> sinter article:001 article:002 # 交集
1) "user1"
127.0.0.1:6379> sunion article:001 article:002 # 并集
1) "user1"
2) "user2"
127.0.0.1:6379> sdiff article:001 article:002 # 差集
1) "user2"
4.2 应用场景
set 类型的应用场景主要是利用集合的特性,比如:
- 去重,利用 sadd 和 scard 命令实现元素的添加和计数。
- 交集,并集,差集,利用 sinter,sunion 和 sdiff 命令实现集合间的运算。
- 随机抽取,利用 srandmember 命令实现随机抽奖或者抽样。
五、ZSet类型
ZSet是一种有序集合类型,他可以存储不重复的字符串信息,并且给每个元素赋予一个排序权重值(score),通过score来为集合中的元素进行排序,ZSet中的成员是唯一的,但是权重值可以重复。一个ZSet类型的键最多可以存储2^32-1个元素
5.1 常用命令
127.0.0.1:6379> zadd article:003 10 user1
(integer) 1
127.0.0.1:6379> zadd article:003 109 user2
(integer) 1
127.0.0.1:6379> zadd article:003 100 user3 # 添加元素
(integer) 1
127.0.0.1:6379> zscore article:003 user2 # 获取元素得分
"109"
127.0.0.1:6379> zrem article:003 user2 # 删除元素
(integer) 1
127.0.0.1:6379> zcard article:003 # 获取元素个数
(integer) 2
127.0.0.1:6379> zincrby article:003 100 user1 # 元素自增指定值
"110"
127.0.0.1:6379> zrange article:003 0 -1 withscores # 正序获取索引范围内的元素
1) "user3"
2) "100"
3) "user1"
4) "110"
127.0.0.1:6379> zrevrange article:003 0 -1 withscores # 倒序获取索引范围内的元素
1) "user1"
2) "110"
3) "user3"
4) "100"
127.0.0.1:6379> zrangebyscore article:003 99 105 # 返回指定分数区间内的元素,从低到高
1) "user3"
5.2 应用场景
zset 类型的应用场景主要是利用分数和排序的特性,比如:
- 排行榜,利用 zadd 和 zrange 命令实现分数的更新和排名的查询
- 延时队列,利用 zadd 和 zpop 命令实现任务的添加和执行,并且可以定期地获取已经到期的任务
- 访问统计,可以使用 zset 来存储网站或者文章的访问次数,并且可以按照访问量进行排序和筛选。
部署配置
安装环境:
系统:CentOS 7.9
内核版本:3.10.0-1160.92.1.el7.x86_64 #1 SMP Tue Jun 20 11:48:01 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
安装版本:6.0.12
Redis的部署大体分三种:单机模式、哨兵模式、集群模式;后两者是对单机模式的扩展,突出可扩展性和高可用性。
不论是何种规模的部署,一些前置配置的调整是相同的:
# 主机名设置
hostnamectl --static set-hostname redis-node-1
# 关闭selinux(需重启生效)
vim /etc/sysconfig.selinux
SELINUX=disabled
# 关闭防火墙
systemctl stop firewalld
# 内核参数调整
# 1、内存分配策略,允许分配所有的物理内存,尽量避免OOM
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl -p
# 2、禁用Linux大页
echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled # 临时生效
sudo chmod +x /etc/rc.local # 永久生效
sudo cat >> /etc/rc.local << EOF
echo never > /sys/kernel/mm/transparent_hugepage/enabled
EOF
# 3、TCP监听队列长度
echo 2048 > /proc/sys/net/core/somaxconn
echo "echo 2048 > /proc/sys/net/core/somaxconn" >> /etc/rc.local
sysctl -p
一、单点部署
# 安装编译需要的gcc环境
yum -y install cpp binutils glibc glibc-kernheaders glibc-common glibc-devel gcc make gcc-c++ libstdc++-devel tcl wget
# 若gcc版本不在5.3以上,需要先进行升级
#升级到 5.3及以上版本
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
# 临时启用
scl enable devtoolset-9 bash
# 永久开启
echo "source /opt/rh/devtoolset-9/enable" >> /etc/profile
# 获取安装包并解压
cd /opt && wget http://download.redis.io/releases/redis-6.0.12.tar.gz && tar -zvxf redis-6.0.12.tar.gz
# 编译安装
cd /opt/redis-6.0.12 && make -j2 && make install PREFIX=/usr/local/redis
# 创建所需目录
mkdir -p /usr/local/redis/{conf,data,log}
# 拷贝可执行文件到安装目录下
\cp -rp /opt/redis-6.0.12/src /usr/local/redis
# 默认配置文件拷贝到安装位置下 或者 直接新建配置文件(保持属主、权限一致)
# cp /opt/redis-6.0.12/redis.conf /usr/local/redis/conf
touch /usr/local/redis/conf/redis.conf
# 修改配置文件
cat >> /usr/local/redis/conf/redis.conf << EOF
bind 0.0.0.0 # 绑定ip
port 16379 # 绑定端口
tcp-backlog 2048 # socket连接数
protected-mode no # 关闭保护模式
requirepass password # 密码认证
daemonize yes # 后台守护启动
logfile "/usr/local/redis/log/redis.log" # 日志文件
dir "/usr/local/redis/data" # RDB文件目录
appendonly yes # 开启AOF持久化
appendfilename "redis.aof" # AOF文件名
rename-command KEYS "RENAMEKEYS" # 高危命令重命名
rename-command FLUSHALL "RENAMEFLUSHALL"
rename-command FLUSHDB "RENAMEFLUSHDB"
maxmemory-policy volatile-lru # 达到最大内存限制时,使用lru算法移除过期key
EOF
# 配置systemd托管(可选)
cat >> /usr/lib/systemd/system/redis.service << EOF
[Unit]
Description=Redis Server
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf
ExecStop=/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 16379 shutdown
Type=forking
[Install]
WantBy=multi-user.target
EOF
systemctl daemon-reload
# 加入环境变量
echo "REDIS_HOME=/usr/local/redis/bin" >> /etc/profile
echo "$PATH=$PATH:/$REDIS_HOME" >> /etc/profile
source /etc/profile
二、主从模式
主从模式是在单点模式的基础上增加了主从复制的配置,可以是一主一从,也可以是一主多从。通常情况下说,一般是一主多从。
主从复制:从Redis Master节点复制到其他Slave节点,以实现数据的高可用性和读写分离。数据的复制只能是单向的(M->S)
# 搭建单点redis、参考单点模式部署
# 以一主两从为例,两个从节点配置文件增加以下内容:
replicaof 192.168.252.16 16379 # 指定要同步的Master节点ip和port
replica-priority 100 # 主节点选举优先级,默认100
master-auth password # 若主节点开启了认证,对应从节点需要配置master密码
验证主从复制:在主节点执行 redis-cli info replication
,若输出结果中 connected_slaves
为2且有从节点的信息,则代表主从复制配置成功。
三、哨兵模式
哨兵的功能:在主从复制的基础上,引入对于主节点的自动故障转移
哨兵是一个分布式系统,对于主从结构中的服务进行监控,当出现故障后通过投票的机制选出新的Master节点,并将所有的Slace链接到新的Master,运行哨兵的集群的节点数量不能小于3个(为了保证Master投票选举)
哨兵模式的故障转移:
- 哨兵节点定期监控发现主节点是否故障,哨兵节点周期性的向主节点、从节点和其他哨兵节点发送Ping测,若是没有做有效回复,则对节点做主观下线处理;若主观下线的是主节点,会联系其他哨兵进行判断,当超过半数认为主节点down掉后,做客观下线处理。
- 主节点出现故障后,哨兵节点通过Raft算法选举出Leader哨兵节点;由Leader哨兵节点执行故障转移
- 选举新的主节点,其他从节点指向新的主节点;若原主节点恢复,则降级为从节点并指向新的主节点。
- 通知客户端主节点已变更
主节点选举原则:
- 选择健康状态(排除下线、断线)的从节点,排除超过5s没有心跳的,排除没有回应哨兵Ping测的
- 选择配置文件中优先级配置最高的(replica-priority,默认值为100;值越小优先级越高)
- 选择复制偏移量最大的,也就是复制最完整的从节点
哨兵的部署配置
Sentinel实际上就是一个特殊的Redis服务,默认监听26379/Tcp端口;哨兵一般同Redis服务部署在一起;哨兵的配置文件指向sentinel.conf,使用redis-sentinel启动。
grep -Ev '^$|^#' /usr/local/redis/conf/sentinel.conf
port 26379
daemonize yes
logfile "/usr/local/redis/log/sentinel.log"
dir "/usr/local/redis/data"
sentinel monitor redis-master 127.0.0.1 16379 2 # 指定监听Master节点信息,2代表quorm,即有几个sentinel参与选举
sentinel auth-pass redis-master password # 若Master配置认证,需要配置Master的密码
sentinel down-after-milliseconds redis-master 30000 # 判断节点主观下线的时间,单位ms
sentinel deny-scripts-reconfig yes # 禁止修改脚本
添加哨兵服务,方便管理
cat <<EOF > /usr/lib/systemd/system/redis-sentinel.service
[Unit]
Description=Redis Sentinel
After=network.target
[Service]
ExecStart=/usr/local/redis/bin/redis-sentinel /usr/local/redis/conf/sentinel.conf
ExecStop=/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 26379 shutdown
Type=forking
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
主节点执行 info sentinel
命令可以查看sentinel状态。
四、集群模式
Redis Cluster是Redis 3.0后推出的分布式解决方案,提供数据分片、高可用功能;使用Redis Cluster能解决负载均衡的问题,内部采用哈希分片规则。
安装集群所需软件:Redis集群需要使用ruby命令
yum install -y ruby
yum install -y rubygems
gem install redis
在执行 gem install redis
命令时会报错:
# gem install redis
Fetching: connection_pool-2.4.1.gem (100%)
ERROR: Error installing redis:
connection_pool requires Ruby version >= 2.5.0.
这是因为在CentOS中,yum源对于ruby的支持版本为2.0.0,而gem安装redis提示需要2.5.0+,需要采用rvm来更新ruby
curl -sSL https://get.rvm.io | bash -s stable
# 命令执行完毕后,会在屏幕打印一串这样的代码,需要我们手动复制并执行
gpg2 --keyserver hkp://keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
加载文件,查看库中存在的ruby版本并安装
source /usr/local/rvm/scripts/rvm # 加载文件
rvm list know # 加载列表
rvm install 2.6.0 # 安装新版本ruby
rvm use 2.6.0 --default # 设置默认使用的ruby版本
重新执行 gem install redis
安装成功,至此,Redis Cluster的前置依赖安装完毕!
gem install redis
Redis Cluster是在单点Redis的配置上,启动Cluster相关的配置;配置完毕后启动redis,加入集群
cluster-enabled yes(启动集群模式)
cluster-config-file node-16379.conf
cluster-node-timeout 15000
/usr/local/redis/src/redis-trib.rb create --replicas 1 xxxx.xx xxxx.xx xxxx.xx xxxx.xx
## --replicas 指定主从复制比例为1:1,即一个主节点对应一个从节点
执行完后可以使用 cluster info
cluster nodes
查看集群节点信息;还可以使用 redis-cli --cluster check
检查集群状态。
集群节点操作:
# 添加主节点:127.0.0.1:36372
/usr/local/redis/bin/redis-cli -a password --cluster add-node 127.0.0.1:36372 127.0.0.1:16372
# 这个时候新加入的主节点是没有槽位的,也就是说无法进行读写,需要分配槽位后才能进行读写
/usr/local/redis/bin/redic-cli -a password --cluster reshard 127.0.0.1:36372
# 该命令是交互式的,会先询问要分配多少槽位?
How many slots do you want to move(from 1 to 16384)? 4096
# 接着会询问要分配给哪个节点ID,这里要填写新分配的主节点ID
What is the receiving node ID? 新分配主节点ID(通过redis-cli --cluster check 127.0.0.1:36372可以查看)
# 接着会询问要将哪个已有的节点槽位分配给新的主节点,一般填all即可,代表所有节点平均分配
Source node? all
# 最后输入yes表示执行分配
# 给指定主节点添加从节点
/usr/local/redis/bin/redis-cli -a password --cluster add-node 127.0.0.1:46372 127.0.0.1:16372 --cluster-slave --cluster-master-id [新分配主节点ID](通过redis-cli --cluster check 127.0.0.1:36372可以查看)
# 删除节点
# 删除主节点首先要删除从节点、之后要把被删除节点的槽位分配出去;在删除节点之前,确保节点无会话登录
/usr/local/redis/bin/redis-cli -a password --cluster del-node 127.0.0.1:16372 [被删除从节点id]
/usr/local/redis/bin/redis-cli -a password --cluster reshard 127.0.0.1:16372
# 相似的,这里也是交互式操作,会询问
/usr/local/redis/bin/redis-cli -a password --cluster del-node 127.0.0.1:16372 [被删除主节点id]
数据持久化
Redis是一个基于内存的数据库。服务一旦宕机,内存中的数据就会全部丢失。通常的解决方案是从后端数据库恢复数据,但是后端数据库有性能瓶颈,如果是大数据量的恢复,1、会对数据库带来巨大的压力,2、数据库性能不如Redis,程序响应慢。所以,对于Redis来说,实现数据的持久化,避免从后端数据库来恢复数据,显得至关重要。
从严格意义上说,Redis服务提供四种持久化存储方案:
RDB
、AOF
、虚拟内存(VM)
和DISKSTORE
。虚拟内存(VM)方式,从Redis Version 2.4开始就被官方明确表示不再建议使用,Version 3.2版本中更找不到关于虚拟内存(VM)的任何配置范例,Redis的主要作者Salvatore Sanfilippo还专门写了一篇论文,来反思Redis对虚拟内存(VM)存储技术的支持问题。至于DISKSTORE方式,是从Redis Version 2.8版本开始提出的一个存储设想,到目前为止Redis官方也没有在任何stable版本中明确建议使用这用方式。在Version 3.2版本中同样找不到对于这种存储方式的明确支持。从网络上能够收集到的各种资料来看,DISKSTORE方式和RDB方式还有着一些千丝万缕的联系,不过各位读者也知道,除了官方文档以外网络资料很多就是大抄。
最关键的是目前官方文档上能够看到的Redis对持久化存储的支持明确的就只有两种方案:
RDB
和AOF
RDB 和 AOF 是可以同时开启的,在这种情况下,当Redis重启的时候会优先载入 AOF 文件来恢复原始的数据。
一、RDB持久化
RDB是Redis Database的缩写,中文名为快照/内存快照,RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,所以快照中的值要早于或者等于内存中的值。
1.1 触发方式
触发rdb持久化的方式有2种,分别是手动触发和自动触发。
1.1.1 手动触发
手动触发分别对应save和bgsave命令
- save:阻塞当前redis服务器,直至rdb过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用
- bgsave:redis进程fork出子进程,rdb持久化操作由子进程负责,完成后自动结束,阻塞只发生在fork子进程阶段,时间很短,流程如下:
1.1.2 自动触发
会在以下四种情况下触发:
-
redis.conf中配置
save m n
,即在m秒内有n次修改时,自动触发bgsave生成rdb文件; -
主从复制时,从节点要从主节点进行全量复制时也会触发bgsave操作,生成当时的快照发送到从节点;
-
执行debug reload命令重新加载redis时也会触发bgsave操作;
-
默认情况下执行shutdown命令时,如果没有开启aof持久化,那么也会触发bgsave操作;
1.2 配置RDB
内存快照虽然可以通过技术人员执行save或bgsave命令来进行,但生产环境大多情况会设置成周期性执行条件
Redis中的默认的周期性配置
# 周期性执行条件的设置格式为:
save <seconds> <changes>
# 默认的配置是:
save 900 1
save 300 10
save 60 10000
# 以下设置为关闭rdb快照功能
save ""
以上三项默认信息设置代表的意义是:
- 如果900秒内有1条Key信息发生变化,则进行快照
- 如果300秒内有10条Key信息发生变化,则进行快照
- 如果60秒内有10000条Key信息发生变化,则进行快照
其他配置:
# 文件名
dbfilename dump.rdb
# 文件路径
dir ./
# rdb出现错误,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 是否压缩
rdbcompression yes
# 导入时是否检查
rdbchecksum yes
1.3 RDB深入了解
由于生产环境中我们为Redis开辟的内存空间足够大,那么将内存中的数据同步到硬盘的过程中可能就会持续比较长的时间,而实际情况是这段时间内Redis服务会持续收到数据写请求,那么如何保证数据一致性呢?
RDB中的核心思路是Copy-on-Write,来保证在进行快照操作的这段时间,需要压缩写入磁盘上的数据在内存中不会发生变化。在正常的快照操作中,一方面Redis主进程会fork一个新的快照进程专门来做这个事情,这样保证了Redis服务不会停止对客户端包括写请求在内的任何响应。另一方面这段时间发生的数据变化会以副本的方式存放在另一个新的内存区域,待快照操作结束后才会同步到原来的内存区域。
举个例子:如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
在进行rdb操作的这段时间,如果发生服务崩溃怎么办?
在没有将数据全部写入到磁盘前,这次快照操作都不算成功。如果出现服务崩溃的情况,将以上一次完整的rdb快照文件作为恢复内存数据的参考。也就是说,在快照操作过程中不能影响上一次的备份数据。Redis服务会在磁盘上创建一个临时文件进行数据操作,待操作成功后才会用这个临时文件替换掉上一次的备份。
可以每秒做一次快照吗?
如果频繁执行全量快照,会带来两方面的开销:
- 一方面,频繁的将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始了,容易造成恶性循环
- 另一方面,bgsave子进程需要通过fork操作从主进程创建出来。虽然子进程在创建后不会阻塞主进程,但是,fork这个动作本身就会阻塞主线程,主线程内存越大,阻塞时间越长
1.4 RDB的优缺点
- 优点
- RDB文件是某个时间点的快照,默认使用LZF算法压缩,压缩后的文件体积远远小于内存大小,常用于备份、全量复制等场景
- Redis加载RDB文件恢复数据要远远快于AOF方式
- 缺点
- 实时性不够,无法做到秒级持久化
- 每次调用bgsave都需要fork子进程,频繁执行成本较高
- RDB文件是二进制的,没有可读性;而AOF文件在了解其结构的情况下可以手动修改或补全
- 版本兼容问题
针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。
二、AOF持久化
Redis是“写后”日志,Redis先执行命令,把数据写入内存,然后才记录日志。日志里记录的是Redis收到的每一条命令,这些命令是以文本形式保存。
PS: 大多数的数据库采用的是写前日志(WAL),例如MySQL,通过写前日志和两阶段提交,实现数据和逻辑的一致性。
为什么采用写后日志?
- 避免额外开销:redis向aof记录日志的时候,并不会先去对这些命令进行语法检查。如果先记录日志再执行命令的话,日志中有可能记录了错误的命令,导致恢复数据出错
- 不会阻塞当前的写进程
但这种方式也会存在潜在的风险:
- 如果命令执行完毕,写日志之前宕机了,会丢失数据
- 主线程写磁盘压力大,导致写盘慢,阻塞后续操作
2.1 实现AOF
AOF日志记录redis的每个写命令,步骤分为:命令追加(append)、文件写入(write)和文件同步(sync)
当AOF持久化功能开启后,服务器在执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器的aof_buffer缓冲区;关于何时将aof_buffer内的内容写入到文件中,redis提供了三种写回策略。
配置项 | 写回机制 | 优点 | 缺点 |
---|---|---|---|
Always | 同步写回 | 可靠性高,数据基本不丢失 | 每个写命令都要落盘,性能影响较大 |
Everysec | 每秒写回 | 性能适中 | 宕机时丢失1秒内的数据 |
No | 操作系统控制写回 | 性能好 | 宕机时丢失数据较多 |
reids.conf配置AOF:
默认情况下,redis是没有开启AOF的
# appendonly参数开启AOF持久化
appendonly no
# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"
# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir ./
# 同步策略
# appendfsync always
appendfsync everysec
# appendfsync no
# aof重写期间是否同步
no-appendfsync-on-rewrite no
# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 加载aof出错如何处理
aof-load-truncated yes
# 文件重写策略
aof-rewrite-incremental-fsync yes
2.2 AOF重写
AOF会记录每个写命令到AOF文件,随着时间越来越长,AOF文件会变得越来越大。如果不加以控制,会对Redis服务器,甚至对操作系统造成影响,而且AOF文件越大,数据恢复也越慢。为了解决AOF文件体积膨胀的问题,Redis提供AOF文件重写机制来对AOF文件进行“瘦身”。
2.2.1 AOF重写会阻塞吗?
AOF重写过程由后台进程bgrewriteaof完成。主线程fork出后台的bgrewriteaof子进程,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
AOF在重写时,在fork进程时是会阻塞主线程的。
2.2.2 AOF日志何时会重写?
有两个配置项控制AOF重写的触发:
- auto-aof-rewrite-min-size:运行AOF重写的文件的最小触发值,单位为MB
- auto-aof-rewrite-percentage:当前aof文件大小和上次重写后aof文件大小的差值/上次重写后aof文件的大小;增量大小与上次重写aof文件的比值。
2.2.3 重写日志时,有新数据写入怎么办?
重写过程总结为:“一个拷贝,两处日志”。在fork出子进程时的拷贝,以及在重写时,如果有新数据写入,主线程就会将命令记录到两个aof日志内存缓冲区中。如果AOF写回策略配置的是always,则直接将命令写回旧的日志文件,并且保存一份命令至AOF重写缓冲区,这些操作对新的日志文件是不存在影响的。(旧的日志文件:主线程使用的日志文件,新的日志文件:bgrewriteaof进程使用的日志文件)
而在bgrewriteaof子进程完成会日志文件的重写操作后,会提示主线程已经完成重写操作,主线程会将AOF重写缓冲中的命令追加到新的日志文件后面。这时候在高并发的情况下,AOF重写缓冲区积累可能会很大,这样就会造成阻塞,Redis后来通过Linux管道技术让aof重写期间就能同时进行回放,这样aof重写结束后只需回放少量剩余的数据即可。
最后通过修改文件名的方式,保证文件切换的原子性。
在AOF重写日志期间发生宕机的话,因为日志文件还没切换,所以恢复数据时,用的还是旧的日志文件。
2.2.4 为什么AOF重写不复用原来的AOF日志?
- 父子进程同写一个文件会产生竞争问题,影响父进程的性能
- 如果AOF重写过程失败了,相当于污染了原本的AOF文件,无法做恢复数据使用
三、RDB和AOF混合方式
Redis4.0中提出了一个混合使用AOF日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用AOF日志记录这期间的所有命令操作。
这样一来,快照不用很频繁的执行,避免了频繁fork对主线程的影响;而且,AOF日志也只记录了两次快照间的操作,也就是说,不需要记录所有操作,因此,不会出现文件过大的情况,避免重写开销。
这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势, 实际环境中用的很多。
常用运维指令
一、info监控指令
redis本身提供的info指令可以查看丰富的实例运行监控信息,这个命令是redis监控工具的基础。
指令 | 监控类别 |
---|---|
info server | 查看server端监控信息,包括pid、配置文件、端口等 |
info clients | 查看客户端统计信息 |
info stats | 查看通用统计信息 |
info keyspace | 查看数据库整体统计情况 |
info commandstats | 查看各命令调用统计信息 |
info cpu | 查看CPU使用信息 |
info memory | 查看Memory使用信息 |
info replication | 查看主从复制统计信息 |
info persistence | 查看RDB、AOF运行信息 |
info cluster | 查看集群统计信息 |
二、monitor命令
monitor指令用于实时监控redis数据库的操作,需要注意的是,执行完monitor后,当前redis窗口会话会阻塞,执行不了其他指令
monitor指令返回当前正在执行的命令详情,包括时间戳、客户端信息、命令详情等
三、慢日志查询及分析
redis的慢查询日志负责记录超过指定执行时间的查询操作。这个执行时间只是命令的耗时,并不包括I/O操作耗时。
一条查询命令分为三步:1、客户端发送命令 2、redis服务端收到命令,进行排队处理 3、执行命令 4、返回结果;慢查询只记录其中的第三步耗时。
3.1 慢查询的两个配置参数
慢查询日志的配置有两个参数控制:
1、slowlog-log-slower-than:指定执行时间的最大值(上限值),单位是us;redis会记录超过这个配置值的查询操作;若是将该参数配置为负数,则代表禁用慢查询日志;若该参数配置为0,则代表记录每条查询命令
2、slowlog-max-len:用于指定慢查询日志的最大长度,最小值为0;当慢查询日志超过最大长度时,redis就会从慢查询日志队列中找到最老的一条记录并移除
可以在配置文件(redis.conf)中配置上面两个参数;也可以在redis运行时使用config set
指令重新指定配置值并使用 config rewrite
将配置持久化到配置文件中。
127.0.0.1:16379> config set slowlog-log-slower-than 0
OK
127.0.0.1:16379> config set slowlog-max-len 10
OK
127.0.0.1:16379> config rewrite
OK
3.2 慢查询相关命令
为了方便测试,上面已经将slowlog-log-slower-than
参数设置为0,代表记录所有查询命令。
# slowlog get n 查询最近n条慢查询记录
127.0.0.1:16379> slowlog get 1
1) 1) (integer) 11
2) (integer) 1718614641
3) (integer) 5
4) 1) "get"
2) "key2"
5) "127.0.0.1:36824"
6) ""
# slowlog len 查询当前的慢查询日志队列长度
127.0.0.1:16379> slowlog len
(integer) 10
# slowlog reset 重置慢查询日志,一旦执行,之前记录的慢查询日志会被删除
127.0.0.1:16379> slowlog reset
OK
127.0.0.1:16379> slowlog len
(integer) 1
3.3 慢查询配置
慢查询可以帮助我们排查到redis可能存在的性能瓶颈,但在使用过程中要注意以下几点:
- slowlog-max-len:线上建议调大该参数配置,记录慢查询时redis会对命令做截断处理,并不会大量占用内存。增大慢查询列表可以减缓慢查询被剔除的可能性,线上建议配置为1000以上
- slowlog-log-slower-than:默认值为10ms判定为慢查询,线上可以根据业务并发量调整该参数
- 慢查询日志只会记录命令执行时间,并不包括命令排队和网络传输时间,因此客户端执行命令的时间会大于命令实际执行时间
- 慢查询日志队列是一个FIFO的队列,如果慢查询较多的情况下,会丢失部分慢查询日志;建议定期执行
slowlog get
指令将慢查询日志转移到其他存储中
BigKey分析及解决
一、什么是大key和热key?
Redis中的大Key主要指,单个简单的key(String类型)存储的value大或者Hash、Set、Zset、List、Zlist这种数据类型中存储的元素过多(>10000); 热Key指的是那些查询QPS大或者占用带宽和CPU集中的Key。
二、大Key和热Key带来的问题?
读写大Key会使客户端执行时间过长,严重的导致超时甚至阻塞服务;对于大Key执行Delete操作,可能会对其他请求造成阻塞,进而引发服务异常
热Key会占用Redis服务器大量的CPU和内存资源;当热Key的请求QPS超过Redis的最大承受能力时,会造成缓存击穿等问题
三、如何查找大Key和热Key?
注:对于大Key和热Key的查找一般效率都不会也别高,存在阻塞的风险,若实例是主从架构,建议以下的命令优先执行在从节点
3.1 命令查找
redis-cli自带参数 --bigkeys和--hotkeys(version 4.0+ 提供),可以查找出各数据类型中最大的那个Key。
原理:以loop的方式分析实例中的所有key,返回key的整体统计信息以及Top1的那个key;准确性和实时性较差,并且只能展示Top1
[root@hcss-ecs-1b32 ~]# redis-cli -p 16379 -a password --bigkeys
[00.00%] Biggest string found so far '"key3"' with 6 bytes
-------- summary -------
Sampled 3 keys in the keyspace!
Total key length in bytes is 12 (avg len 4.00)
Biggest string found '"key3"' has 6 bytes
0 lists with 0 items (00.00% of keys, avg size 0.00)
0 hashs with 0 fields (00.00% of keys, avg size 0.00)
3 strings with 18 bytes (100.00% of keys, avg size 6.00)
0 streams with 0 entries (00.00% of keys, avg size 0.00)
0 sets with 0 members (00.00% of keys, avg size 0.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)
除redis-cli自带的参数查询以外,redis还提供了一个debug object [key]
的命令,可以查看每个key的具体信息,从中得到一个key的value序列化后的大小。但是,debug object的执行效率较低,且如果想查找库中所有key的大小,需要循环调用该命令,会有阻塞redis的风险。该命令可做调试用。
127.0.0.1:16379> debug object key3
Value at:0x7f68de00e4e0 refcount:1 encoding:embstr serializedlength:7 lru:7418869 lru_seconds_idle:2364
4.0以上的版本还提供了 memory uage [key]
命令,可以更简单直接的查看当前key的内存占用值,返回值单位是字节
127.0.0.1:16379> memory usage key3
(integer) 56
3.2 云数据库统计
如果使用的是云数据库Redis,各服务商会提供有实时BigKey和HotKey统计功能,如阿里云的实时TopKey统计功能,腾讯云的数据库智能管家等功能,都能够帮助使用者快速查找数据库中存在的BigKey和HotKey。
3.3 离线分析
离线分析是基于RDB文件来操作,因此,要开展离线分析,需要先开启RDB转存功能:在配置文件中指定save配置,手动执行一次转存可以使用bgsave
命令。
介绍一款比较经典的离线rdb分析工具:rdb-tools,这是一个python编写的工具,需要python环境。例如,我的云主机python version是2.7。先使用pip安装下rdb-tools:pip install rdb-tools
,结果提示 pip: command not found
,这里就需要先安装pip管理工具,使用python -m ensurepip
重装下pip组件,接着提示no moudle named ensurepip
,到这里可以看出主机上的python环境其实是并不完善的;没办法,继续解决问题;查找到一篇csdn的博文,与我的情况类似;解决步骤如下:
第一步,下载get-pip.py文件
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
第二步,运行
puthon get-pip.py
第三步,运行报错:ERROR: This script does not work on Python 2.7 The minimum supported Python version is 3.7. Please use https://bootstrap.pypa.io/pip/2.7/get-pip.py instead. 根据提示替换链接重新下载执行解决pip工具未安装的问题。
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py && python get-pip.py
之后,需要下载rdb-tools,由于python2官方源没有收录rdb-tools,所以,这里我们需要指定pip源:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple rdbtools
另外,还需要安装lzf包(可选,最好安装),用于提高rdb-tools工具解析速度;在安装时报错,报错信息如下:
lzf_module.c:3:10: fatal error: Python.h: No such file or directory
3 | #include "Python.h"
| ^~~~~~~~~~
compilation terminated.
error: command 'gcc' failed with exit status 1
这是因为缺少Python.h,需要安装python对应的devel环境。
yum install -y python-devel # 先安装devel环境,之后安装lzf包成功
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple python-lzf
当然,python2其实已经不适用于现在的业务环境;除特殊情况外,建议将原主机的python升级至3.6以上,能够避免很多问题。
rdb-tools使用:
rdb-tools的使用主要是了解其参数的意义;常用的参数有-c/--command,-f/--file,-k/--key,-t/--type,-l/--largest。
-c 指定处理格式,有json、memory、justkeys、justkeyvals、protocol等
-f 指定输出文件
-k 指定导出的key,这是一个正则表达式
-t 指定解析的数据类型
-b 指定大于或等于该值的key输出
-l 指定输出前N个key
usage: usage: rdb [options] /path/to/dump.rdb
Example : rdb --command json -k "user.*" /var/redis/6379/dump.rdb
rdb --command json dump.rdb -f dump_bigkey.json
rdb -c memory -b 128 -l -1 -f dump_bigkey_mr128b.csv
# 输出结果包括这几列:database,type,key,size_in_bytes,encoding,num_elements,len_largest_element,expiry
四、优化大Key和热Key
1、对大Key进行拆分,比如将有数万个元素的hash key拆分成多个hash key
2、使用读写分离机制,如果热Key的产生来自于读请求,可以将实例改造成读写分离架构来降低请求压力
3、定期删除过期key和大Key,删除建议:
3.1、redis4.0+的版本,使用 unlink
命令安全删除大key;unlink命令与del命令相似,但它是在后台异步删除,不会阻塞当前客户端,也不会阻塞redis主线程;并且unlink可以删除多种类型的key。4.0之前的版本使用del
命令删除,但是要注意删除大Key会有阻塞风险,建议渐进式删除。
3.2、借助scan
命令,遍历大Key,每次取一部分,进行删除,删除完毕后再进行下一部分的删除
3.3、使用rename
将大Key改名,这样任何客户端都不会访问到该key,相当与逻辑删除,再进行少量多批次的删除
参考资料:
1、https://help.aliyun.com/zh/redis/user-guide/identify-and-handle-large-keys-and-hotkeys
2、https://www.cnblogs.com/jelly12345/p/14972572.html
3、https://www.cnblogs.com/zping/p/15185064.html
标签:127.0,6379,认识,redis,Redis,0.1,key From: https://www.cnblogs.com/bluemoon17/p/18254775