个人博客
欢迎访问个人博客: https://www.crystalblog.xyz/
备用地址: https://wang-qz.gitee.io/crystal-blog/
简介
Redis前面的基础部分此处不做记录 , 本篇记录周阳老师讲解的Redis配置及高级应用的知识.
尚硅谷-周阳思维导图 链接: https://pan.baidu.com/s/1jY8bh8D0MN_4WN1hqwIQeQ 提取码: ezog 课件,别感谢我!
B站视频: 尚硅谷超经典Redis教程,redis实战,阳哥版从入门到精通
Redis官网
Redis中文网
https://redis.com.cn/
http://www.redis.cn/
https://www.redis.net.cn/
1. 解析Redis配置文件
解压后的Redis目录下有一个redis.conf 文件, 里面是实现redis各种功能的配置项.
1.1 Units单位
配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit. 对大小写不敏感.
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.
1.2 INCLUDES包含
可以通过includes包含,redis.conf可以作为总闸,包含其他。
################################## INCLUDES ###################################
# Include one or more other config files here. This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings. Include files can include
# other files, so use this wisely.
#
# Note that option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf
1.3 NETWORK网络
# 开启只能本机访问
bind 127.0.0.1 -::1
# 保护模式
protected-mode yes
# http端口
port 6379
# 设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列+已经完成三次握手队列。
# 在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。
# 注意linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backing两个值来达到想要的效果。
tcp-backlog 511
# 超时关闭连接, 0不关闭
timeout 0
# 心跳机制!!! 单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60
tcp-keepalive 300
1.4 GENERAL通用
# 守护线程启动
daemonize no
# 进程管道文件
pidfile /var/run/redis_6379.pid
# 日志级别 debug verbose notice warning; 级别越高,日志越少
# 生产模式 warning
loglevel notice
# 日志文件名称
logfile ""
# 是否把日志输出到syslog中
syslog-enabled no
# 指定syslog里的日志标志
syslog-ident redis
# 指定syslog设备,值可以是user或local0-local7 , 默认0
syslog-facility local0
# 默认16个库
databases 16
1.5 SNAPSHOTTING快照
# save 秒钟 写操作次数
save <seconds> <changes>
# 默认是yes,如果配置成no,表示不在乎数据不一致或者有其他的手段发现和控制。
stop-writes-on-bgsave-error yes
# rdbcompression:对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。
# 如果不想消耗CPU来进行压缩的话,可以设置为no关闭此功能。默认是yes
rdbcompression yes
# rdbchecksum:在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗;
# 如果希望获取到最大的性能提升,可以关闭此功能。默认是yes
rdbchecksum yes
# 快照文件名, 默认dump.rdb, 建议以dump-端口.rdb命名
dbfilename dump.rdb
# 数据备份文件的目录; 日志文件的默认目录等
dir ./
1.6 REPLICATION复制
protected-mode no
port 6380
cluster-enabled yes
cluster-config-file nodes-6380.conf
cluster-node-timeout 5000
daemonize yes
pidfile /var/run/redis_6380.pid
logfile "6380.log"
masterauth 123456
requirepass 123456
1.7 SECURITY安全
访问密码的查看, 设置和取消.
requirepass foobared
客户端操作密码
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456"
OK
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/redis-6.x"
1.8 LIMITS限制
设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。当你无法设置进程文件句柄限制时,redis会设置为当前的文件句柄限制值减去32,因为redis会为自身内部处理逻辑留一些句柄出来。如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
maxclients 10000
设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy 来指定。如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是master redis(说明你的redis有slave redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素.
maxmemory <bytes>
内存淘汰策略
- volatile-lru:使用LRU算法移除key,从设置了过期时间的key中最近最少使用的数据淘汰.
- allkeys-lru:使用LRU算法移除key, 从所有key中挑选最近最少使用的数据淘汰.
- volatile-lfu : 使用LFU算法移除key,从设置了过期时间的key中挑选最近使用次数最少的数据淘汰
- allkeys-lfu : 使用LFU算法移除key,从所有key中挑选最近使用次数最少的数据淘汰
- volatile-random:从设置了过期时间的key中移除随机的key.
- allkeys-random:从所有key中随机移除key.
- volatile-ttl:移除那些TTL值最小的key,即那些即将要过期的key
- noeviction:不进行移除。针对写操作,只是返回错误信息
maxmemory-policy noeviction
设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个.
maxmemory-samples 5
1.9 APPEND ONLY MODE追加
# yes开启aof配置, 默认no
appendonly no
# aof备份文件名
appendfilename "appendonly.aof"
# appendfsync always|everysec|no aof持久化策略
# always 同步持久化,每次发生数据变更会立即记录到磁盘,性能较差但数据完整性比较好
# everysec 出厂默认推荐,异步操作,每秒记录,如果一秒内宕机,有数据丢失
# no 不aof备份
appendfsync everysec
# 重写时是否可以运行Appendfsync,用默认no即可,保证数据安全性。
no-appendfsync-on-rewrite no
# 设置重写的基准值
auto-aof-rewrite-percentage 100
# 设置重写的基准值
auto-aof-rewrite-min-size 64mb
1.10 常见配置redis.conf介绍
工作中遇到不会的可以按照这份常用配置进行查阅.
redis.conf 配置项说明如下:
(1). Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no
(2). 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid
(3). 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379
(4). 绑定的主机地址
bind 127.0.0.1
(5).当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
(6). 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
loglevel verbose
(7). 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile stdout
(8). 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
databases 16
(9). 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合 save <seconds> <changes> Redis默认配置文件中提供了三个条件:
save 900 1 save 300 10 save 60 10000 分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
(10). 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
(11). 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
(12). 指定本地数据库存放目录
dir ./
(13). 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
slaveof
(14). 当master服务设置了密码保护时,slav服务连接master的密码
masterauth
(15). 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭
requirepass foobared
(16). 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128
(17). 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory
(18). 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no
(19). 指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof
(20). 指定更新日志条件,共有3个可选值: no:表示等操作系统进行数据缓存同步到磁盘(快) always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec
(21). 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
vm-enabled no
(22). 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap
(23). 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0
(24). Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
vm-page-size 32
(25). 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
vm-pages 134217728
(26). 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4
(27). 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes
(28). 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64 hash-max-zipmap-value 512
(29). 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes
(30). 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf
2. Redis持久化
2.1 RDB
2.1.1 RDB介绍
RDB (Redis DataBase), 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,它恢复时将快照文件直接读到内存里。
Redis会单独创建(Fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能. RDB保存的是dump.rdb 文件.
如果要进行大规模数据恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
Fork 的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量‘程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
优势: 适合大规模的数据恢复; 对数据完整性和一致性要求不高;
劣势: Fork的时候,内存中的数据被克隆了一份,大约2倍的膨胀性需要考虑。
关闭rdb: 动态停止RDB保存规则的方法:redis-cli config set save “”
2.1.2 如何触发RDB
开放配置文件中默认的快照配置, 也可以修改自定义数值. 命令save或者是bgsave, 高版本的redis优化为bgsave指令实现. 可以自行查阅save和bgsave指令备份的区别.
# save 秒钟 写操作次数
save <seconds> <changes>
save和bgsave
save : 只管保存,其他不管,全部zuse
bgsave :Redis会在后台异步进行快照操作,快照操作同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间。
在客户端也可以手动执行save 进行备份, 将数据写入dump.rdb文件, 相当于commit操作.
执行flushall 命令,也会产生dump.rdb文件,但里面是空的,无意义。
如果恢复数据
将备份文件(dump.rdb)移动到redis安装目录并启动服务即可. 使用config get dir 获取redis安装目录.
冷拷贝后重新使用, 可以cp dump.rdb dump_new.rdb, 等晚上业务停用时进行数据恢复.
2.1.3 客户端演示
不直接在原来配置上做修改, 防止配置错误无法恢复, 首先将redis.conf 的复制一份到启动目录下, 修改下面的配置项, 假设为60s内写操作5次就触发rdb备份.
确保关闭了aof配置, 否则aof持久化数据也会生效, 并在重启redis时以aof方式恢复数据, 达不到rdb恢复数据的效果.
# 60s内写操作5次就触发rdb备份
save 60 5
# 关闭aof持久化
appendonly no
如果之前启动过redis, 先删除原来的dump.rdb文件, 再启动redis服务. 然后连接redis客户端, 写入数据达到上面的触发要求.
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set name cysw
OK
127.0.0.1:6379> set name sdfs
OK
127.0.0.1:6379> set name panda
OK
127.0.0.1:6379> set age 55
OK
127.0.0.1:6379> set age 345
OK
127.0.0.1:6379> set age 29
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> SHUTDOWN
not connected> exit
[root@centos7-01 redis-6.x]
total 96
-rw-r--r--. 1 root root 0 Apr 3 17:18 appendonly.aof
drwxr-xr-x. 2 root root 150 Jan 17 21:49 bin
-rw-r--r--. 1 root root 117 Apr 3 17:20 dump.rdb
-rwxr-xr-x. 1 root root 93754 Apr 3 17:19 redis.conf
[root@centos7-01 redis-6.x]
[offset 0] Checking RDB file dump.rdb
[offset 26] AUX FIELD redis-ver = '6.2.6'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1648977645'
[offset 67] AUX FIELD used-mem = '872144'
[offset 83] AUX FIELD aof-preamble = '0'
[offset 85] Selecting DB ID 0
[offset 117] Checksum OK
[offset 117] \o/ RDB looks OK! \o/
[info] 2 keys read
[info] 0 expires
[info] 0 already expired
现在重启redis服务, redis会以rdb方式恢复数据.
[root@centos7-01 redis-6.x]
root 3215 3035 0 15:50 pts/1 00:00:00 less redis.conf
root 3444 2977 0 17:21 pts/0 00:00:00 grep --color=auto redis
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name
"panda"
127.0.0.1:6379> get age
"29"
2.1.4 RDB总结
内存中的数据对象 --- rdbsave---> 磁盘中的RDB文件
内存中的数据对象 <--- rdbLoad--- 磁盘中的RDB文件
优点:
缺点:
2.2 AOF
2.2.1 AOF介绍
AOF(Append Only File), 以日志的形式来记录每个写操作 ,将Redis执行过的所有写指令 记录下来(读操作不记录 ),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话根据日志文件的内容将写指令 从前到后执行一次已完成数据的恢复工作。AOF保存的是appendonly.aof文件。
2.2.2 AOF启动/修复/恢复
正常恢复
在redis.conf 配置文件中配置appendonly yes 开启aof持久化, 使用默认每秒备份策略:
appendfsync everysec
如果之前启动过, 先删除redis安装目录下的appendonly.aof文件, 再重启redis服务会重新加载数据.
[root@centos7-01 redis-6.x]
total 92
drwxr-xr-x. 2 root root 150 Jan 17 21:49 bin
-rwxr-xr-x. 1 root root 93745 Apr 3 21:06 redis.conf
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379>
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/redis-6.x"
127.0.0.1:6379>
127.0.0.1:6379> set k1 v2
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
127.0.0.1:6379> set k4 v4
OK
127.0.0.1:6379> set k5 v5
OK
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> SHUTDOWN
not connected> exit
[root@centos7-01 redis-6.x]
total 100
-rw-r--r--. 1 root root 185 Apr 3 21:07 appendonly.aof
drwxr-xr-x. 2 root root 150 Jan 17 21:49 bin
-rw-r--r--. 1 root root 92 Apr 3 21:07 dump.rdb
-rwxr-xr-x. 1 root root 93745 Apr 3 21:06 redis.conf
查看备份的命令日志文件appendonly.aof , 可以看到文件中记录了写操作的命令.
[root@centos7-01 redis-6.x]
*2
$6
SELECT
$1
0
*3
$3
set
$2
k1
$2
v2
*3
$3
set
$2
k2
$2
v2
*3
$3
set
$2
k3
$2
v3
*3
$3
set
$2
k4
$2
v4
*3
$3
set
$2
k5
$2
v5
*1
$7
flushdb
因为appendonly.aof文件最后的指令是flushdb, 所以宕机重启服务后恢复的数据也是空的.
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> keys *
(empty array)
现在我们重新写入数据, 再次模拟宕机, 然后重启看是否可以恢复数据.
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379>
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> SHUTDOWN
not connected> exit
[root@centos7-01 redis-6.x]
total 100
-rw-r--r--. 1 root root 266 Apr 3 21:14 appendonly.aof
drwxr-xr-x. 2 root root 150 Jan 17 21:49 bin
-rw-r--r--. 1 root root 111 Apr 3 21:14 dump.rdb
-rwxr-xr-x. 1 root root 93745 Apr 3 21:06 redis.conf
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
注意: 运维人员可以将有数据的aof文件复制一份保存到对应目录(config get dir), 让数据从aof文件中恢复.
异常恢复
在生产环境中, 可能会出现网络延迟等现象导致redis写操作没有完成, appendonly.aof文件中记录的写命令就是不完整的, 那么redis宕机后能从异常的aof文件中恢复数据吗? 下面我们通过实操验证, 先在appendonly.aof文件末尾手动添加一条错误的写命令模拟异常文件.
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected>
从上面的验证结果可以看出, 如果appendonly.aof文件中出现错误命令, 是无法正常启动redis服务的. 可以通过官方提供的redis-check-aof 命令对损坏的appendonly.aof文件进行修复, 也就是将错误的命令删除.
./bin/redis-check-aof --fix appendonly.aof
[root@centos7-01 redis-6.x]
0x 11a: Expected to read 2 bytes, got 0 bytes
AOF analyzed: size=282, ok_up_to=266, ok_up_to_line=67, diff=16
This will shrink the AOF from 282 bytes, with 16 bytes, to 266 bytes
Continue? [y/N]: y
Successfully truncated AOF
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
2.2.3 AOF重写
AOF采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。
可以使用命令: bgrewriteaof
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
127.0.0.1:6379> exit
[root@centos7-01 redis-6.x]
REDIS0009? redis-ver6.2.6?
?edis-bits?@?ctime?)?Ibused-mem??N
aof-preamble???k1v1k2v2?R????[root@centos7-01 redis-6.x]
-bash: xterm-256colorxterm-256color: command not found
[root@centos7-01 redis-6.x]
total 100
-rw-r--r--. 1 root root 111 Apr 3 21:42 appendonly.aof
drwxr-xr-x. 2 root root 150 Jan 17 21:49 bin
-rw-r--r--. 1 root root 111 Apr 3 21:22 dump.rdb
-rwxr-xr-x. 1 root root 93745 Apr 3 21:06 redis.conf
重写原理
AOF文件持续膨胀而过大时,会fork 出一条新进程将文件重写(也是先写临时文件最后在rename),遍历新进程的内存中数据,每条记录有一条Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
重写触发机制
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。
# 设置重写的基准值, AOF文件大小达到上次rewrite后大小的一倍
auto-aof-rewrite-percentage 100
# 设置重写的基准值, AOF文件大小大于64MB时
auto-aof-rewrite-min-size 64mb
redis.conf 文件中有这么一段话, 大概意思就是: RDB和AOF可以共存,但是恢复的时候找的是AOF,如果AOF文件异常,可以通过redis-check-aof 进行AOF修复。这也就明白了上面aof备份时, 也同时产生了RDB文件.
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
?
2.2.4 AOF总结
优点:
-
appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好. -
appendfsync everysec 异步操作 ,每秒记录 如果一秒内宕机,有数据丢失。 -
appendfsync no 从不同步 -
AOF文件是一个只进行追加的日志文件 -
Redis可以在AOF文件体积变得过大时,自动地在后台对AOF进行重写 -
AOF文件有序地保存了对数据执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析也很轻松。
缺点:
- 相同数据集的数据而言, aof文件体积要远大于rdb文件,恢复速度慢于rdb
- 根据所使用的fsync策略,AOF的速度可能会慢于RDB, 不同步时效率和RDB相同 。
3. Redis事务
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞。 也就是一个队列中,一次性、顺序性、排他性地执行一系列命令.
3.1 Redis事务常用命令
DISCARD:取消事务,放弃执行事务块内的所有命令。
EXEC:执行所有事务块的命令。
MULTI:标记一个事务块的开始。
UNWATCH:取消WATCH命令对多有key的监视。
WATCH key [key…]:监视一个或多个key,如果在事务执行前该key被其他命令所改动,那么事务将打断。
3.2 Redis事务三阶段
- 开启:以
MULTI 开始一个事务 - 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的
事务队列 里面。 - 执行:由
EXEC 命令触发事务提交
3.4 Redis事务三特性
- 单独的隔离操作:事务中的所有命令都会被序列化、按顺序地执行。事务在执行的构成中,不会被其他客户端发送来的命令请求所打断。
- 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在“事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题。
- 不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。
3.5 Redis事务演示
3.5.1 正常放行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v2"
4) OK
3.5.2 取消事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 22
QUEUED
127.0.0.1:6379(TX)> set k3 33
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get k2
"v2"
3.5.3 全体连坐
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v4
QUEUED
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4
(nil)
3.5.4 冤有头债有主
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 22
QUEUED
127.0.0.1:6379(TX)> set k3 33
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> get k4
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) OK
5) "v4"
127.0.0.1:6379> get k4
"v4"
3.5.5 watch监控
在学习watch监控之前, 我们先来了解一下悲观锁/乐观锁 相关知识.
悲观锁
顾名思义,每次去拿数据的时候都被认为别人会修改 ,所以每次在拿数据的时候都会被锁上,这样别人想拿这个数据就会block直到它拿到锁,传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁等,读锁、写锁等,都是在做操作之前先锁上。
乐观锁
每次去拿数据的时候都认为别人不会修改 ,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号, CAS等机制。
CAS机制
CAS(Compare And Swap), 比较并替换. CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新, 直到成功。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为新值B。
CAS的缺点
(1). CPU开销较大 在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
(2). 不能保证代码块的原子性 CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
扣减余额操作, 无加塞篡改,先监控再开启multi,保证两笔金额变动在同一个事务内.
127.0.0.1:6379> set balance 100
OK
127.0.0.1:6379> set debt 0
OK
127.0.0.1:6379> WATCH balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY balance 20
QUEUED
127.0.0.1:6379(TX)> INCRBY debt 20
QUEUED
127.0.0.1:6379(TX)> exec
(nil)
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> get debt
"20"
有加塞篡改: 被监控的数据, 在事务执行过程中, 其他客户端修改了被监控的数据.
127.0.0.1:6379> WATCH balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY balance 20
QUEUED
127.0.0.1:6379(TX)> INCRBY debt 20
QUEUED
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get balance
"800"
客户端2, 在客户端1开启对balance的监控后, 在客户端1事务提交之前, 修改balance值为800.
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> set balance 800
OK
一定要取消监控后或者没有其他客户端修改被监控的key, 事务才能提交成功.
一旦执行了exec,之前加的监控锁watch都会被取消掉.
127.0.0.1:6379> WATCH balance
OK
127.0.0.1:6379> set balance 500
OK
127.0.0.1:6379> get balance
"500"
127.0.0.1:6379> UNWATCH
OK
127.0.0.1:6379> WATCH balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set balance 80
QUEUED
127.0.0.1:6379(TX)> set debt 20
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) OK
127.0.0.1:6379> get balance
"80"
总结
watch指令,类似乐观锁,事务提交时,如果Key的值已经被别的客户端改变,比如某个list已经被别的客户端push/pop过了,整个事务队列都不会被执行。
通过WATCH命令在事务执行之前监控了多个keys,倘若在WATCH之后有任何key的值的变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。
4. Redis的发布订阅
4.1 介绍
进程间的一种消息通信模式:发送者(publish)发送消息,订阅者(subscribe)接受消息。
下图展示了频道channel1, 以及订阅这个频道的三个客户端: client1, client2 和 client5 之间的关系.
当有新消息通过publish命令发送给频道channel1时, 这个消息就会被发送给订阅它的三个客户端.
4.2 常用命令
subscribe channel [channel2 ...]
psubscribe pattern [pattern2 ...]
publish channel message
unsubscribe channel [channel2 ...]
punsubscribe pattern [pattern2 ...]
4.3 案例
先订阅后发布才能收到信息
可以一次性订阅多个,SUBSCRIBE c1 c2 c3
消息发布,PUBLISH c2 hello-redis
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> SUBSCRIBE c1 c2 c3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "c1"
3) (integer) 1
1) "subscribe"
2) "c2"
3) (integer) 2
1) "subscribe"
2) "c3"
3) (integer) 3
1) "message"
2) "c1"
3) "hello-redis"
1) "message"
2) "c2"
3) "hello2-redis"
1) "message"
2) "c3"
3) "hello3-redis"
发布消息
[root@centos7-01 redis-6.x]
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> PUBLISH c1 hello-redis
(integer) 1
127.0.0.1:6379> PUBLISH c2 hello2-redis
(integer) 1
127.0.0.1:6379> PUBLISH c3 hello3-redis
(integer) 1
订阅多个,通配符*,PSUBSCRIBE new*
收到消息,PUBLISH new1 hello1
这里就不演示了, 和上面的操作一样, 我是使用的redis-6.x的版本, 没有起到通配符的效果, 把new*当作一个频道了.
5. Redis的主从复制(Master/Slave)
5.1 介绍
主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slver机制,Master以写为主,Slave以读为主。
可以进行读写分离, 容灾恢复等功能的实现.
5.2 主从复制原理
- Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步。
- 全量控制:slave服务在接收到master的数据库文件数据后,将其存盘并加载到内存中。
- 增量控制:master继续将新的所有收集到的修改命令一次传给slave,完成同步。
- 遇到slave停机后, 只要是重新连接master,一次完全同步(全量复制)将被自动执行。
5.3 主从复制演示
5.3.1 一主二从
从库配置:slaveof master_IP master_port
slave每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件.
Info replication 命令可以查看当前服务的角色信息, 是主库还是从库.
现在先做一主二从的机器准备, 将redis.conf 配置文件复制出来三份, 并修改相关的配置信息, 修改配置文件细节操作:
# 关闭保护模式,用于公网访问
protected-mode no
# 修改端口
port 6380
# 后台启动
daemonize yes
pidfile /var/run/redis_6380.pid
# 防止在其他目录启动,最好写绝对路径的文件名 /usr/local/redis-6.x/data/6380.log
logfile "./data/6380.log"
# 此处绑定ip可以是内网ip和本机ip, 也可以直接注释掉该项
# bind 127.0.0.1
# 用于连接主节点密码
masterauth 123456
# 设置redis密码, 各个节点请保持密码一致
requirepass 123456
# 修改rdb文件名称
dbfilename dump_6380.rdb
# 数据备份文件的目录; 日志文件的默认目录等; 防止在其他目录启动,最好写绝对路径
dir /usr/local/redis-6.x/data
准备的配置文件
[root@centos7-01 redis-6.x]
/usr/local/redis-6.x
[root@centos7-01 redis-6.x]
total 376
drwxr-xr-x. 2 root root 150 Jan 17 21:49 bin
drwxr-xr-x. 2 root root 118 Apr 4 16:59 data
-rwxr-xr-x. 1 root root 93807 Apr 4 17:04 redis6379.conf
-rwxr-xr-x. 1 root root 93807 Apr 4 17:05 redis6380.conf
-rwxr-xr-x. 1 root root 93807 Apr 4 17:05 redis6381.conf
-rwxr-xr-x. 1 root root 93745 Apr 3 21:06 redis.conf
三台服务的配置修改完成后, 分别启动三台服务进行初始化操作, 并查看三台服务各自的角色和状态信息, 发现此时的三台服务都是master, 因为没有指定主从关系.
现在分配主从关系, 6379为master, 6380, 6381为slave. 通过客户端执行命令 slaveof 127.0.0.1 6379 ; 再次查看三台服务的主从关系, 以及数据同步情况.
查看master主机6379.log日志.
DB loaded from append only file: 0.001 seconds
5122:M 04 Apr 2022 18:02:31.255 * Ready to accept connections
5122:M 04 Apr 2022 18:03:20.478 * Replica 127.0.0.1:6380 asks for synchronization
5122:M 04 Apr 2022 18:03:20.478 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '39ab3c51e0304f13d9738e798332df54cc604a48', my replication IDs are '7b7c05d015af0b045eb1f22c505464ffb51bb3ea' and '0000000000000000000000000000000000000000')
5122:M 04 Apr 2022 18:03:20.478 * Replication backlog created, my new replication IDs are '47715abc4fa6f05dbfacd1befe65bbab07e62b95' and '0000000000000000000000000000000000000000'
5122:M 04 Apr 2022 18:03:20.478 * Starting BGSAVE for SYNC with target: disk
5122:M 04 Apr 2022 18:03:20.510 * Background saving started by pid 5143
5143:C 04 Apr 2022 18:03:20.513 * DB saved on disk
5143:C 04 Apr 2022 18:03:20.514 * RDB: 4 MB of memory used by copy-on-write
5122:M 04 Apr 2022 18:03:20.535 * Background saving terminated with success
5122:M 04 Apr 2022 18:03:20.535 * Synchronization with replica 127.0.0.1:6380 succeeded
5122:M 04 Apr 2022 18:03:27.140 * Replica 127.0.0.1:6381 asks for synchronization
5122:M 04 Apr 2022 18:03:27.141 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '6e958e46e653bf8aae144d6f45faff64cd675e00', my replication IDs are '47715abc4fa6f05dbfacd1befe65bbab07e62b95' and '0000000000000000000000000000000000000000')
5122:M 04 Apr 2022 18:03:27.141 * Starting BGSAVE for SYNC with target: disk
5122:M 04 Apr 2022 18:03:27.144 * Background saving started by pid 5146
5146:C 04 Apr 2022 18:03:27.149 * DB saved on disk
5146:C 04 Apr 2022 18:03:27.150 * RDB: 4 MB of memory used by copy-on-write
5122:M 04 Apr 2022 18:03:27.189 * Background saving terminated with success
5122:M 04 Apr 2022 18:03:27.189 * Synchronization with replica 127.0.0.1:6381 succeeded
查看slave从机6380.log的日志
5130:S 04 Apr 2022 18:03:20.477 * Connecting to MASTER 127.0.0.1:6379
5130:S 04 Apr 2022 18:03:20.477 * MASTER <-> REPLICA sync started
5130:S 04 Apr 2022 18:03:20.477 * REPLICAOF 127.0.0.1:6379 enabled (user request from 'id=3 addr=127.0.0.1:36190 laddr=127.0.0.1:6380 fd=9 name= age=31 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=42 qbuf-free=40912 argv-mem=20 obl=0 oll=0 omem=0 tot-mem=61484 events=r cmd=slaveof user=default redir=-1')
5130:S 04 Apr 2022 18:03:20.477 * Non blocking connect for SYNC fired the event.
5130:S 04 Apr 2022 18:03:20.477 * Master replied to PING, replication can continue...
5130:S 04 Apr 2022 18:03:20.478 * Trying a partial resynchronization (request 39ab3c51e0304f13d9738e798332df54cc604a48:1).
5130:S 04 Apr 2022 18:03:20.511 * Full resync from master: 47715abc4fa6f05dbfacd1befe65bbab07e62b95:0
5130:S 04 Apr 2022 18:03:20.511 * Discarding previously cached master state.
5130:S 04 Apr 2022 18:03:20.535 * MASTER <-> REPLICA sync: receiving 175 bytes from master to disk
5130:S 04 Apr 2022 18:03:20.535 * MASTER <-> REPLICA sync: Flushing old data
5130:S 04 Apr 2022 18:03:20.536 * MASTER <-> REPLICA sync: Loading DB in memory
5130:S 04 Apr 2022 18:03:20.538 * Loading RDB produced by version 6.2.6
5130:S 04 Apr 2022 18:03:20.540 * RDB age 0 seconds
5130:S 04 Apr 2022 18:03:20.540 * RDB memory usage when created 1.85 Mb
5130:S 04 Apr 2022 18:03:20.541
5130:S 04 Apr 2022 18:03:20.542 * MASTER <-> REPLICA sync: Finished with success
5130:S 04 Apr 2022 18:03:20.544 * Background append only file rewriting started by pid 5145
5130:S 04 Apr 2022 18:03:20.618 * AOF rewrite child asks to stop sending diffs.
5145:C 04 Apr 2022 18:03:20.618 * Parent agreed to stop sending diffs. Finalizing AOF...
5145:C 04 Apr 2022 18:03:20.622 * Concatenating 0.00 MB of AOF diff received from parent.
5145:C 04 Apr 2022 18:03:20.622 * SYNC append only file rewrite performed
5145:C 04 Apr 2022 18:03:20.623 * AOF rewrite: 4 MB of memory used by copy-on-write
5130:S 04 Apr 2022 18:03:20.636 * Background AOF rewrite terminated with success
5130:S 04 Apr 2022 18:03:20.637 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB)
5130:S 04 Apr 2022 18:03:20.637 * Background AOF rewrite finished successfully
主从问题演示
(1) 切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的123是否也可以复制
? 可以, 会先进行全量复制, 然后master主机的写命令会同步给slave从机更新数据.
(2) 从机是否可以写?set可否? 读写分离, master负责写, slave负责读, slave不能写.
(3) 主机shutdown后情况如何?从机是上位还是原地待命
? 主机shutdown后, 从机不会上位到master, 仍然在原地待命, 后面的集群-哨兵机制可以解决这个问题.
(4) 主机又回来了后,主机新增记录,从机还能否顺利复制?
? master主机重新启动后, 主机写数据, 从机会重新同步主机的数据, 可以顺利复制.
(5) 其中一台从机down后情况如何?依照原有它能跟上大部队吗?
? slave从机宕机后, 重新启动不会与master主机建立关系, slave重启后就是一个单例的独立master, 需要重新执行命令slaveof 127.0.0.1 6379 与master主机6379建立主从关系.
5.3.2 薪火相传
上一个slave可以是下一个slave的Master,slave同样可以接受其他slaves的连接和同步请求,那么该slave作为链条中下一个的master,可以有效减轻master的写压力。
中途变更转向:会清除之前的数据,重新建立拷贝最新的。 slaveof 新主库IP 新主库端口.
可以看到6379写的数据会同步6380, 然后6380再同步到6381, 在这个链条上一直传下去. 但注意6380虽然是6381的主机, 但6380本身是6379的从机, 所以6380从机是不能写操作的, 从下面的报错信息也可以验证.
5.3.3 反客为主
SLAVEOF no one:使当前数据库通知与其他数据库的同步,转成主数据库。
SLAVEOF no one 实现的反客为主, 虽然能让当前slave服务转为master, 但是其他slave需要手动重新建立与新的master建立主从关系. 这个问题在下面的哨兵模式中可以解决, 哨兵模式就相当于将手动建立主从关系的操作改为自动选举和建立主从关系.
5.4 哨兵模式(sentinel)
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库, 并让其他从库与新的主库建立主从关系.
现在重新调整redis服务的主从结果, 6379为master, 6380, 6382为slave.
创建哨兵配置文件 , redis安装目录下新建sentinel.conf 文件,名字决不能错。编辑sentinel.conf添加如下内容:
sentinel monitor 被监控数据库名字(自定义)127.0.0.1 6379 1
上面最后一个数字1 ,表示主机挂掉后slave投票数超过1后, 就让谁接替成为master主机,得票数多的成为主机.
# sentinel monitor 被监控数据库名字(自定义)127.0.0.1 6379 1
sentinel monitor redis6379 127.0.0.1 6379 1
启动哨兵, 这里采用的是非守护进程的方式启动, 如果窗口关闭, 哨兵服务也就关闭了. 后面介绍后台运行的配置方式.
[root@centos7-01 redis-6.x]
5691:X 04 Apr 2022 22:46:17.937
5691:X 04 Apr 2022 22:46:17.937
5691:X 04 Apr 2022 22:46:17.937
5691:X 04 Apr 2022 22:46:17.938 * Increased maximum number of open files to 10032 (it was originally set to 1024).
5691:X 04 Apr 2022 22:46:17.938 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.6 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 5691
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
5691:X 04 Apr 2022 22:46:17.951
5691:X 04 Apr 2022 22:46:17.959
5691:X 04 Apr 2022 22:46:17.959
6379的master关闭后, 哨兵自动选举一个slave成为新的master.
好奇怪, 我自己操作的过程中, slave一直重复投票给已经挂掉的6379服务, 具体原因不清楚, 可能是配置问题吧, 日志如下:
5691:X 04 Apr 2022 22:46:47.969
5691:X 04 Apr 2022 22:46:47.969
5691:X 04 Apr 2022 22:46:47.977
5691:X 04 Apr 2022 22:46:47.977
5691:X 04 Apr 2022 22:46:47.977
5691:X 04 Apr 2022 22:46:48.069
5691:X 04 Apr 2022 22:46:48.138
5691:X 04 Apr 2022 22:52:48.601
5691:X 04 Apr 2022 22:52:48.601
5691:X 04 Apr 2022 22:52:48.611
5691:X 04 Apr 2022 22:52:48.611
5691:X 04 Apr 2022 22:52:48.611
5691:X 04 Apr 2022 22:52:48.679
5691:X 04 Apr 2022 22:52:48.740
5691:X 04 Apr 2022 22:58:49.580
5691:X 04 Apr 2022 22:58:49.580
5691:X 04 Apr 2022 22:58:49.584
5691:X 04 Apr 2022 22:58:49.584
5691:X 04 Apr 2022 22:58:49.584
5691:X 04 Apr 2022 22:58:49.687
5691:X 04 Apr 2022 22:58:49.754
这里附上周阳老师操作的自动选举结果截图
选举成功后, 通过info replication 查看状态, 6380被选举为新的master, 6381作为slave从机, 与6380建立了主从关系.
问题:如果之前的master(6379)重启回来,会不会双master冲突? 也就是6379会不会和6380冲突?
不会冲突,之前的master回来之后,哨兵会监测到,之前的master会变成slave
注意:
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
后台运行哨兵的配置
port 26379
daemonize no
pidfile /var/run/redis-sentinel.pid
logfile ""
dir /tmp
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
acllog-max-len 128
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
SENTINEL resolve-hostnames no
SENTINEL announce-hostnames no
6. Redis的Java客户端Jedis
6.1 测试连通性
创建SpringBoot项目, 导入Jedis相关的依赖.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.crys</groupId>
<artifactId>boot-jedis</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml 添加Jedis连接的配置, 下面列出单机的配置, 也支持集群配置.
server:
port: 8081
spring:
redis:
host: 192.168.65.129
password: 123456
port: 6379
jedis:
pool:
max-idle: 50
max-active: 100
min-idle: 10
max-wait: 10000
timeout: 2000
创建测试类, 测试Jedis操作Redis的连通性.
@RunWith(SpringRunner.class)
@SpringBootTest
public class JedisTest {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Test
public void testJedis() {
Jedis jedis = new Jedis(host, port);
System.out.println(jedis);
jedis.auth(password);
jedis.set("name", " crysw");
System.out.println("name=" + jedis.get("name"));
System.out.println("ping=" + jedis.ping());
Set<String> keys = jedis.keys("*");
System.out.println("keys * : " + keys);
jedis.close();
}
}
测试结果
redis.clients.jedis.Jedis@7e87ef9e
name= crysw
ping=PONG
keys * : [k3, name, k4, balance, debt, k1, k2]
6.2 Jedis常用API
Jedis的API使用, 和Redis的原生命令一模一样, 按照Redis命令使用就可以了.
@RunWith(SpringRunner.class)
@SpringBootTest
public class JedisApiTest {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
private Jedis jedis;
@Before
public void before() {
jedis = new Jedis(host, port);
jedis.auth(password);
}
@Test
public void testKey() {
Set<String> keys = jedis.keys("*");
System.out.println("keys *: " + keys);
System.out.println("jedis.exists====>" + jedis.exists("k2"));
System.out.println("jedis.ttl: " + jedis.ttl("k1"));
}
@Test
public void testString() {
jedis.append("k1", ", myredis");
System.out.println("jedis.get(k1): " + jedis.get("k1"));
jedis.set("k4", "k4_redis");
System.out.println("k4: " + jedis.get("k4"));
jedis.mset("str1", "v1", "str2", "v2", "str3", "v3");
List<String> mget = jedis.mget("str1", "str2", "str3");
System.out.println("jedis.mget: " + mget);
}
@Test
public void testList() {
jedis.lpush("mylist", "v1", "v2", "v3", "v4", "v5");
List<String> mylist = jedis.lrange("mylist", 0, -1);
System.out.println("mylist: " + mylist);
}
@Test
public void testSet() {
jedis.sadd("orders", "jd001");
jedis.sadd("orders", "jd002");
jedis.sadd("orders", "jd003");
Set<String> orders = jedis.smembers("orders");
System.out.println("orders: " + orders);
jedis.srem("orders", "jd002");
Set<String> orders1 = jedis.smembers("orders");
System.out.println("orders1: " + orders1);
}
@Test
public void testHash() {
jedis.hset("hash1", "userName", "lisi");
System.out.println("username: " + jedis.hget("hash1", "userName"));
Map<String, String> map = new HashMap<String, String>();
map.put("telphone", "13811814763");
map.put("address", "atguigu");
map.put("email", "abc@163.com");
jedis.hmset("hash2", map);
List<String> hmget = jedis.hmget("hash2", "telphone", "email");
System.out.println("hmget: " + hmget);
}
@Test
public void testZset() {
jedis.zadd("zset01", 60d, "v1");
jedis.zadd("zset01", 70d, "v2");
jedis.zadd("zset01", 80d, "v3");
jedis.zadd("zset01", 90d, "v4");
Set<String> zset01 = jedis.zrange("zset01", 0, -1);
System.out.println("zset01: " + zset01);
Set<String> zset011 = jedis.zrangeByScore("zset01", 60, 70);
System.out.println("zset011: " + zset011);
}
}
6.3 Jedis操作Redis事务
Jedis如何实现Redis事务的操作呢?
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestTx {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
private Jedis jedis;
@Before
public void before() {
jedis = new Jedis(host, port);
jedis.auth(password);
}
@Test
public void testTx() {
boolean retValue = this.transMethod();
System.out.println("retValue: " + retValue);
}
private boolean transMethod() {
int balance;
int debt;
int amtToSubtract = 10;
System.out.println("before modify, balance : " + jedis.get("balance"));
System.out.println("before modify, debt : " + jedis.get("debt"));
jedis.watch("balance");
balance = Integer.parseInt(jedis.get("balance"));
if (balance < amtToSubtract) {
jedis.unwatch();
System.out.println("the balance is not enough");
return false;
} else {
System.out.println(">>>>>>transaction>>>>");
Transaction transaction = jedis.multi();
transaction.decrBy("balance", amtToSubtract);
transaction.incrBy("debt", amtToSubtract);
List<Object> exec = transaction.exec();
System.out.println("exec: " + exec);
if (exec == null) {
System.out.println("modified by others");
return false;
}
balance = Integer.parseInt(jedis.get("balance"));
debt = Integer.parseInt(jedis.get("debt"));
System.out.println("balance: " + balance);
System.out.println("debt: " + debt);
jedis.unwatch();
return true;
}
}
}
测试场景1, 可用余额不足. 上面的程序放开 jedis.set("balance", "5");
before modify, balance : 50
before modify, debt : 40
the balance is not enough
retValue: false
测试场景2, 被监控的balance对象被其他程序修改了, 事务执行失败. 上面的程序放开 jedis.set("balance", "50");
before modify, balance : 5
before modify, debt : 40
>>>>>>transaction>>>>
exec: null
modified by others
retValue: false
测试场景3, 事务执行成功, 余额扣减, 债务增加成功. 先将balace重置到50.
before modify, balance : 50
before modify, debt : 40
>>>>>>transaction>>>>
exec: [40, 50]
balance: 40
debt: 50
retValue: true
6.4 Jedis操作主从复制
测试Jedis操作Redis的主从复制, 6379设置为master主机, 6380为其slave从机, 同步6379的数据.
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestMS {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Test
public void testMS() {
Jedis jedis_master = new Jedis(host, 6379);
Jedis jedis_slave = new Jedis(host, 6380);
jedis_master.auth(password);
jedis_slave.auth(password);
jedis_slave.slaveof(host, 6379);
jedis_master.set("class", "123456");
String result = jedis_slave.get("class");
System.out.println("result: " + result);
}
}
第一次执行测试用例, 发现slave读取的数据为null, 但是在CentOS的服务器上连接slave查询是已经同步了数据的, 这是因为程序在JVM内存中运行太快导致, Redis主从还没有同步完成, Java程序就执行完成了. 只要第二次执行slave就可以正常读取到master的数据了.
注意: 开始连接6380怎么操作都连接失败, 后面经过排查,发现是因为测试程序在Windows本机上, 而Redis服务在CentOS上, 需要开放对应的端口访问权限.
firewall-cmd --permanent --zone=public --add-port=6380/tcp
firewall-cmd --permanent --list-ports
firewall-cmd --permanent --query-port=6380/tcp
firewall-cmd --reload
开放6380端口访问权限
[root@centos7-01 redis-6.x]
no
[root@centos7-01 redis-6.x]
success
[root@centos7-01 redis-6.x]
success
[root@centos7-01 redis-6.x]
success
[root@centos7-01 redis-6.x]
yes
6.5 Jedis工具封装
JedisConfig配置类, 实例化JedisPool池.
@Configuration
@Slf4j
public class JedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.jedis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.jedis.pool.min-idle}")
private int minIdle;
@Value("${spring.redis.jedis.pool.max-wait}")
private int maxWaitMillSeconds;
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Bean
public JedisPool jedisPool(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setMaxTotal(maxActive);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillSeconds);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
log.info("JedisPool连接成功: {}:{}",host,port);
return jedisPool;
}
}
获取Jedis的简易工具封装
@Component
public class JedisUtils {
@Autowired
private JedisPool jedisPool;
@Value("${spring.redis.password}")
private String password;
public Jedis getJedis() {
Jedis jedis = jedisPool.getResource();
jedis.auth(password);
return jedis;
}
public void close(Jedis jedis) {
if (jedis != null) jedis.close();
}
public int calcTimeHour(int hours) {
int seconds = hours * 60 * 60;
return seconds;
}
}
|