Redis
简介
REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。
Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。
Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、**集合(sets)和有序集合(sorted sets)**等类型。
Redis 采用经典的Server-Client架构。
高性能
C语言编写,Redis 数据存储在内存中,因此能够提供极快的读写操作。它采用单线程模型和异步 I/O,避免了多线程的竞争和阻塞,从而达到了非常高的性能。
Redis的命令是原子性的。
获取请求达到每秒110000次,设置请求达到每秒80000次。
Redis基于内存操作,CPU不是其性能瓶颈,其性能瓶颈是内存频率和网络带宽。
注意:高性能服务器不一定是多线程的,因为线程切换需要进行上下文切换,会消耗一定的资源。
数据结构多样
Redis 支持多种数据结构,包括字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)。这些数据结构提供了丰富的操作命令,使得开发者可以方便地处理各种数据需求。
持久化支持
Redis 提供了两种持久化方式,即快照(Snapshotting)和日志追加(Append-only file,AOF)。快照方式将 Redis 内存数据以二进制格式写入磁盘,而 AOF 则通过追加记录 Redis 的操作命令来实现持久化。
发布/订阅
Redis 支持发布/订阅模式,可以用作消息代理。发布者将消息发送到指定的频道,订阅者则可以接收和处理这些消息。这种模式在构建实时通信、事件驱动系统和消息队列等场景中非常有用。
分布式缓存
Redis 可以通过主从复制和分片来实现数据的分布式存储和高可用性。主从复制可以将数据复制到多个从节点,实现读写分离和数据备份。而分片则可以将数据分布在多个Redis节点上,实现横向扩展和负载均衡。
事务支持
Redis 支持事务,但其与常规事务不同,无法实现原子性。
功能丰富
Redis不仅仅是一个简单的缓存,它还提供了许多其他功能,如事务支持、Lua脚本执行、定时任务、原子操作等。这使得开发者可以在Redis中实现更复杂的应用逻辑。
参考文档
安装
基于LInux-CentOS操作系统
yum方式
# 安装redis
[root@newname ~]# yum install redis
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
...
Complete!
# 启用redis开机自启
[root@newname ~]# systemctl enable redis
Created symlink from /etc/systemd/system/multi-user.target.wants/redis.service to /usr/lib/systemd/system/redis.service.
# 启动redis
[root@newname ~]# systemctl start redis
# 查看redis状态
# systemctl status redis 等价 systemctl status redis.service
[root@cloudhost ~]# systemctl status redis
# 服务名称为 redis.service,描述为 redis 持久化 键值对 数据库
● redis.service - Redis persistent key-value database
# 服务被加载到/usr/lib/systemd/system/redis.service,开机自启,原始软件包建议默认是禁用该服务的
Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/redis.service.d
└─limit.conf
# 当前正在运行,运行开始时间,运行时长
Active: active (running) since Sat 2025-05-17 18:43:12 CST; 1s ago
# 上次shutdown的情况
Process: 11176 ExecStop=/usr/libexec/redis-shutdown (code=exited, status=0/SUCCESS)
Main PID: 11211 (redis-server)
Tasks: 3
Memory: 1.0M
CGroup: /system.slice/redis.service
└─11211 /usr/bin/redis-server 127.0.0.1:6379
May 17 18:43:12 cloudhost systemd[1]: Starting Redis persistent key-value database...
May 17 18:43:12 cloudhost systemd[1]: Started Redis persistent key-value database.
连接
进入数据库
redis-cli [OPTIONS] [cmd [arg [arg ...]]]
-h 主机
-p 端口
-a 密码
[root@cloudhost ~]# redis-cli
127.0.0.1:6379>
身份认证
# 验证密码,此处错误指出未设置密码(即无需认证即可正常使用)
127.0.0.1:6379> AUTH 123456
(error) ERR Client sent AUTH, but no password is set
测试连通
127.0.0.1:6379> PING
PONG
127.0.0.1:6379> PING "hello redis"
"hello redis"
127.0.0.1:6379> ECHO "hello redis"
"hello redis"
离开数据库
# 只是关闭客户端,并不会关闭服务
127.0.0.1:6379> QUIT
[root@cloudhost ~]#
基本数据类型
字符串类型
其实也可以是数字类型
常见用途:计数器(统计量、频率限制器),字符串缓存(验证码,TOKEN等)
高级用途:分布式锁
列表类型
本质是链表(可作为栈,队列)
常见用途:栈,队列,消息队列
集合类型
集合是无序且不重复的
常见用途:共同关注
哈希类型
redis数据库是key-value型数据库,此哈希类型本质也是一个小的key-value映射
常见用途:存储频繁变动的对象(通常是类对象,例如用户信息等)
有序集合类型
自动根据权重升序(背后应该是堆或者树),元素不可重复,权重可重复
常见用途:排行榜,权重消息队列
特殊数据类型
地理位置
本质是zset
常见用途:附近推荐
基数统计
本质是string
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
即唯一元素的个数。
特点:内存占用小(2^64只需要约12KB),不保留元素(无法返回输入的元素),存在一定误差(约0.81%错误率)
常见用途:浏览人数统计
位图
本质是string
注意:一定要记住BITPOS和BITOP的操作单位是字节
常见用途:布尔状态追踪(打卡签到)
数据操作
Readme
所有命令及其选项都大小写不敏感
默认情况下所有区间都是闭区间
通过(修饰可以实现开区间,例如: (3,5指(3.5] 3,(5指[3,5)
-inf指负无穷大 +inf指正无穷大
位图部分行为以字节为单位
Database
数据库命令
SELECT
# 默认进入O号数据库,redis默认有0-15共16个数据库
[root@cloudhost ~]# redis-cli
# 切换数据库
127.0.0.1:6379> SELECT 3
OK
DBSIZE
# 输出的是键值对数量
127.0.0.1:6379[3]>
(integer) 0
127.0.0.1:6379[3]> SET name ssydx
OK
127.0.0.1:6379[3]> DBSIZE
(integer) 1
FLUSHDB
# 清空当前数据库
127.0.0.1:6379[3]> FLUSHDB
OK
127.0.0.1:6379[3]> DBSIZE
(integer) 0
FLUSHALL
# 清空所有数据库
127.0.0.1:6379[3]> FLUSHALL
OK
Key
键命令
KEYS
# 获取所有键
127.0.0.1:6379> KEYS *
1) "age"
2) "set"
3) "hash"
4) "ls"
5) "name"
TYPE
# 查看键类型
127.0.0.1:6379> TYPE ls
list
127.0.0.1:6379> TYPE set
set
127.0.0.1:6379> TYPE hash
hash
127.0.0.1:6379> TYPE name
string
RANDOMKEY
# 随机获取键名
127.0.0.1:6379> RANDOMKEY
"age"
127.0.0.1:6379> RANDOMKEY
"set"
EXISTS
# 判断是否存在指定键
127.0.0.1:6379> EXISTS name
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
MOVE
# 将键值对移动到其他数据库
127.0.0.1:6379> MOVE age 1
(integer) 1
DEL
# 删除键值对
127.0.0.1:6379[1]> DEL age
(integer) 1
EXPIRE|PEXPIRE
# 设置过期时间-时间间隔,单位秒
127.0.0.1:6379> EXPIRE name 10
(integer) 1
# 等待10秒
127.0.0.1:6379> get name
(nil)
# 设置过期时间-时间间隔,单位毫秒
127.0.0.1:6379> PEXPIRE name 1000000
(integer) 1
# EXPIREAT
# PEXPIREAT
TTL|PTTL
# 查看距离过期剩余秒数
127.0.0.1:6379> TTL name
(integer) 995
# 查看距离过期剩余毫秒数
127.0.0.1:6379> PTTL name
(integer) 989586
PERSIST
# 设置键永不过期
127.0.0.1:6379> PERSIST name
(integer) 1
127.0.0.1:6379> TTL name
(integer) -1
其他命令
RENAME
RENAMENX
DUMP
String
字符串命令
SET
127.0.0.1:6379> SELECT 1
# 设置键值对,永不过期
127.0.0.1:6379[1]> SET k1 v1
OK
# 1000秒后过期
127.0.0.1:6379[1]> SET k2 v2 EX 1000
OK
# 10000000毫秒后过期
127.0.0.1:6379[1]> SET k3 v3 PX 10000000
OK
127.0.0.1:6379[1]> KEYS *
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379[1]> TTL k1
(integer) -1
127.0.0.1:6379[1]> TTL k2
(integer) 961
127.0.0.1:6379[1]> PTTL k3
(integer) 9971520
# 存在该键才设置
127.0.0.1:6379[1]> SET k3 v4 NX
(nil)
# 不存在该键才设置
127.0.0.1:6379[1]> SET k4 v4 XX
(nil)
SETEX|PSETEX
# SET 命令也能实现以下操作
# 设置键值对并指定1000秒后过期
127.0.0.1:6379[1]> SETEX k5 1000 v5
OK
127.0.0.1:6379[1]> GET k5
"v5"
127.0.0.1:6379[1]> PTTL k5
(integer) 988009
# 设置键值对并指定10000毫秒后过期
127.0.0.1:6379[1]> PSETEX k6 10000 v6
OK
127.0.0.1:6379[1]> TTL k6
(integer) 3
GET
# 获取键对应的值
127.0.0.1:6379[1]> GET k1
"v1"
APPEND
127.0.0.1:6379[1]> EXISTS k1
(integer) 1
# 追加子串,已存在
127.0.0.1:6379[1]> APPEND k1 hello
(integer) 7
127.0.0.1:6379[1]> GET k1
"v1hello"
# 追加子串,不存在
127.0.0.1:6379[1]> EXISTS k4
(integer) 0
127.0.0.1:6379[1]> APPEND k4 hello
(integer) 5
STRLEN
127.0.0.1:6379[1]> STRLEN k1
(integer) 7
SETRANGE
127.0.0.1:6379[1]> GET k1
"v1hello"
# 设置子串,长度相等
127.0.0.1:6379[1]> SETRANGE k1 2 world
(integer) 7
127.0.0.1:6379[1]> GET k1
"v1world"
# 设置子串,旧短新长
127.0.0.1:6379[1]> SETRANGE k1 1 zhangsan
(integer) 9
127.0.0.1:6379[1]> GET k1
"vzhangsan"
# 设置子串,旧长新短
127.0.0.1:6379[1]> SETRANGE k1 1 aaaaa
(integer) 9
127.0.0.1:6379[1]> GET k1
"vaaaaasan"
GETRANGE
# 注意:和很多语言不同,其为闭区间,而不是左闭右开
127.0.0.1:6379[1]> SET k1 'hello world'
OK
127.0.0.1:6379[1]> GET k1
"hello world"
# 获取子串,只能到末尾
127.0.0.1:6379[1]> GETRANGE k1 3 8
"lo wor"
# 获取子串,直到末尾
127.0.0.1:6379[1]> GETRANGE k1 3 -1
"lo world"
MSET|MGET
# 批量设置
127.0.0.1:6379[1]> MSET key1 val1 key2 val2
OK
# 批量获取
127.0.0.1:6379[1]> MGET key1 key2
1) "val1"
2) "val2"
127.0.0.1:6379[1]>
SETNX|MSETNX
# SETNX 和 SETEX 常常用来实现分布式锁,实际更推荐使用 SET, 可以在一个原子操作中完成两者,释放锁需要借助Lua脚本
# 看门狗机制实现自动续期,REDLOCK实现锁的一致性和可用性(即锁本身的分布式)
# 不存在时才设置
127.0.0.1:6379[1]> SETNX key1 value1
(integer) 0
# 不存在时才设置,注意:任意一个存在都会导致整体失败
127.0.0.1:6379[1]> MSETNX key1 value1 key3 value3
(integer) 0
127.0.0.1:6379[1]> MGET key1 key2 key3
1) "val1"
2) "val2"
3) (nil)
GETSET
# 获取并设置,键存在
127.0.0.1:6379[1]> GETSET key1 value1
"val1"
127.0.0.1:6379[1]> GET key1
"value1"
# 获取并设置,键不存在
127.0.0.1:6379[1]> GETSET key3 value3
(nil)
127.0.0.1:6379[1]> GET key3
"value3"
INCRE|INCREBY
# 注意:redis字符串类型其实是支持数字的,redis会自动进行推断
127.0.0.1:6379[1]> SET num1 5
OK
127.0.0.1:6379[1]> GET num1
"5"
# 自增
127.0.0.1:6379[1]> INCR num1
(integer) 6
127.0.0.1:6379[1]> GET num1
# 增加3
"6"
127.0.0.1:6379[1]> INCRBY num1 3
(integer) 9
# 字符型数字也会被视为数字
127.0.0.1:6379[1]> SET num2 "5"
OK
127.0.0.1:6379[1]> INCR num2
(integer) 6
# 非数字类型会报错
127.0.0.1:6379[1]> SET num3 str
OK
127.0.0.1:6379[1]> INCR num3
(error) ERR value is not an integer or out of range
DECR|DECRBY
# 逻辑同 INCRE|INCREBY
127.0.0.1:6379[1]> DECR num1
(integer) 8
127.0.0.1:6379[1]> DECRBY num1 5
(integer) 3
其他命令
INCRBYFLOAT
List
列表命令
LPUSH|RPUSH|LRANGE|LLEN|LPOP|RPOP
127.0.0.1:6379[1]> SELECT 2
# 头插
127.0.0.1:6379[2]> LPUSH ls1 one two three
(integer) 3
# 尾插
127.0.0.1:6379[2]> RPUSH ls1 1 2 3
(integer) 6
# 查看列表
127.0.0.1:6379[2]> LRANGE ls1 0 -1
1) "three"
2) "two"
3) "one"
4) "1"
5) "2"
6) "3"
# 列表长度
127.0.0.1:6379[2]> LLEN ls1
(integer) 6
# 头删
127.0.0.1:6379[2]> LPOP ls1
"three"
# 尾删
127.0.0.1:6379[2]> RPOP ls1
"3"
LINDEX|LINSERT|LREM|LTRIM
127.0.0.1:6379[2]> LRANGE ls1 0 -1
1) "two"
2) "one"
3) "1"
4) "2"
# 获取指定索引处的值
127.0.0.1:6379[2]> LINDEX ls1 1
"one"
# 设置指定索引处的值
127.0.0.1:6379[2]> LSET ls1 1 hello
OK
127.0.0.1:6379[2]> LINDEX ls1 1
"hello"
127.0.0.1:6379[2]> LINSERT ls1 BEFORE hello world
(integer) 5
# 前插
127.0.0.1:6379[2]> LRANGE ls1 0 -1
1) "two"
2) "world"
3) "hello"
4) "1"
5) "2"
# 后插
127.0.0.1:6379[2]> LINSERT ls1 AFTER hello ssydx
(integer) 6
127.0.0.1:6379[2]> LRANGE ls1 0 -1
1) "two"
2) "world"
3) "hello"
4) "ssydx"
5) "1"
6) "2"
# 删除指定数量的指定值
127.0.0.1:6379[2]> LRANGE ls1 0 -1
1) "two"
2) "world"
3) "hello"
4) "ssydx"
5) "1"
6) "ssydx"
7) "2"
# 删除超过存在的个数的指定值,不报错,返回实际删除的个数
127.0.0.1:6379[2]> LREM ls1 5 two
(integer) 1
# 删除少于存在的个数的指定值,从左(头)开始删除
127.0.0.1:6379[2]> LREM ls1 1 ssydx
(integer) 1
127.0.0.1:6379[2]> LRANGE ls1 0 -1
1) "world"
2) "hello"
3) "1"
4) "ssydx"
5) "2"
# 保留指定索引范围的值
127.0.0.1:6379[2]> LTRIM ls1 1 3
OK
127.0.0.1:6379[2]> LRANGE ls1 0 -1
1) "hello"
2) "1"
3) "ssydx"
LPUSHX|RPUSHX|RPOPLPUSH
# 列表存在才头插,此处不存在不插入
127.0.0.1:6379[2]> LPUSHX ls2 zhangsan
(integer) 0
# 列表存在才头插
127.0.0.1:6379[2]> LPUSHX ls1 zhangsan
(integer) 4
# 列表存在才尾插
127.0.0.1:6379[2]> RPUSHX ls1 lisi
(integer) 5
127.0.0.1:6379[2]> LRANGE ls1 0 -1
1) "zhangsan"
2) "hello"
3) "1"
4) "ssydx"
5) "lisi"
# 原列表进行尾删并头插到新列表,新列表不存在时会自动创建
127.0.0.1:6379[2]> RPOPLPUSH ls1 ls2
"lisi"
127.0.0.1:6379[2]> LRANGE ls1 0 -1
1) "zhangsan"
2) "hello"
3) "1"
4) "ssydx"
127.0.0.1:6379[2]> LRANGE ls2 0 -1
1) "lisi"
BLPOP|BRPOP
127.0.0.1:6379[2]> LPUSH ls3 one
(integer) 1
# 阻塞型头删
# 等待指定时间,一旦列表不为空就立刻返回键名和头删的元素,否则等待至超时,等待时间的单位是秒
127.0.0.1:6379[2]> BLPOP ls3 3
1) "ls3"
2) "one"
127.0.0.1:6379[2]> EXISTS ls3
(integer) 0
# 等待指定时间,一旦列表不为空就立刻返回键名和尾删的元素,否则等待至超时,等待时间的单位是秒
127.0.0.1:6379[2]> BLPOP ls3 3
(nil)
(3.05s)
# 同理,阻塞型尾删
127.0.0.1:6379[2]> BRPOP ls3 3
(nil)
(3.00s)
# 多列表阻塞型头删
# 按照给定的顺序,此处ls3(空),ls2(非空),返回首个非空列表的尾删结果
127.0.0.1:6379[2]> BLPOP ls3 ls2 3
1) "ls2"
2) "lisi"
# 多列表阻塞型尾删
# 按照给定的顺序,此处ls1(非空),ls3(空),返回首个非空列表的尾删结果
127.0.0.1:6379[2]> BRPOP ls1 ls3 3
1) "ls1"
2) "ssydx"
# 按照给定的顺序,此处ls1(非空),ls2(非空),返回首个非空列表的尾删结果
127.0.0.1:6379[2]> BRPOP ls1 ls2 3
1) "ls1"
2) "1"
# 同理,阻塞型尾删头插
127.0.0.1:6379[2]> BRPOPLPUSH ls3 ls2 3
(nil)
(3.10s)
Set
集合命令
SADD|SCARD|SMEMBERS|SRANDMEMBER|SISMEMBER
127.0.0.1:6379[2]> SELECT 3
# 添加元素,重复元素被忽略
127.0.0.1:6379[3]> SADD set1 one two three one four five
(integer) 5
# 元素个数
127.0.0.1:6379[3]> SCARD set1
(integer) 5
# 查看集合
127.0.0.1:6379[3]> SMEMBERS set1
1) "four"
2) "one"
3) "three"
4) "two"
5) "five"
# 随机获取指定个数,小于元素个数
127.0.0.1:6379[3]> SRANDMEMBER set1 3
1) "one"
2) "five"
3) "two"
# 随机获取指定个数,大于元素个数,返回全部
127.0.0.1:6379[3]> SRANDMEMBER set1 10
1) "four"
2) "one"
3) "two"
4) "three"
5) "five"
# 判断是否存在于集合
127.0.0.1:6379[3]> SISMEMBER set1 one
(integer) 1
# 判断是否存在于集合
127.0.0.1:6379[3]> SISMEMBER set1 ten
(integer) 0
SPOP|SREM|SMOVE
# 随机删除指定个数
127.0.0.1:6379[3]> SPOP set1 3
1) "three"
2) "two"
3) "one"
127.0.0.1:6379[3]> SCARD set1
(integer) 2
# 删除指定元素
127.0.0.1:6379[3]> SREM set1 ten
(integer) 0
# 删除指定元素
127.0.0.1:6379[3]> SREM set1 four
(integer) 1
# 移动集合的指定元素到另一集合
127.0.0.1:6379[3]> SMOVE set1 set2 five
(integer) 1
127.0.0.1:6379[3]> SMEMBERS set1
(empty list or set)
127.0.0.1:6379[3]> SMEMBERS set2
1) "five"
SDIFF|SINTER|SUNION
127.0.0.1:6379[3]> SADD seta a b c d e
(integer) 5
127.0.0.1:6379[3]> SADD setb a c d f g h
(integer) 6
# 差集
127.0.0.1:6379[3]> SDIFF seta setb
1) "e"
2) "b"
# 交集
127.0.0.1:6379[3]> SINTER seta setb
1) "d"
2) "a"
3) "c"
# 并集
127.0.0.1:6379[3]> SUNION seta setb
1) "b"
2) "d"
3) "e"
4) "g"
5) "f"
6) "a"
7) "h"
8) "c"
# 以上三个命令不仅支持两个集合的差交并,也支持更多集合的
其他命令
SDIFFSTORE
SINTERSTORE
SUNIONSTORE
SSCAN
Hash
哈希命令
HSET|HLEN|HEXISTS|HSTRLEN|HGET
127.0.0.1:6379[3]> SELECT 4
# 设置子键值对
127.0.0.1:6379[4]> HSET hash1 k1 v1
(integer) 1
127.0.0.1:6379[4]> HSET hash1 k2 v2
(integer) 1
127.0.0.1:6379[4]> HSET hash1 k3 v3
(integer) 1
# 子键值对数量
127.0.0.1:6379[4]> HLEN hash1
(integer) 3
# 指定子键是否存在
127.0.0.1:6379[4]> HEXISTS hash1 k1
(integer) 1
# 指定子键是否存在
127.0.0.1:6379[4]> HEXISTS hash1 k4
(integer) 0
# 指定子键对应的值的字符串长度
127.0.0.1:6379[4]> HSTRLEN hash1 k1
(integer) 2
# 获取子键对应的值
127.0.0.1:6379[4]> HGET hash1 k1
"v1"
HGETALL|HKEYS|HVALS
# 获取所有子键值对
127.0.0.1:6379[4]> HGETALL hash1
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"
# 获取所有子键
127.0.0.1:6379[4]> HKEYS hash1
1) "k1"
2) "k2"
3) "k3"
# 获取所有子键对应的值
127.0.0.1:6379[4]> HVALS hash1
1) "v1"
2) "v2"
3) "v3"
HMSET|HMSET
# 批量设置子键值对
127.0.0.1:6379[4]> HMSET hash1 key1 val1 key2 val2 key3 val3
OK
# 批量获取子键对应的值
127.0.0.1:6379[4]> HMSET hash1 key1 key2 key3
1) "val1"
2) "val2"
3) "val3"
HSETNX
# 不存在时才设置
127.0.0.1:6379[4]> HSETNX hash1 key1 value1
(integer) 0
127.0.0.1:6379[4]> HSETNX hash1 key4 value4
(integer) 1
HDEL
# 删除指定子键
127.0.0.1:6379[4]> HDEL hash1 key1 key2 key3 key4
(integer) 4
HINCRBY
127.0.0.1:6379[4]> HMSET hash2 k1 5 k2 15
OK
127.0.0.1:6379[4]> HINCRBY hash2 k1 3
(integer) 8
127.0.0.1:6379[4]> HINCRBY hash2 k2 -3
(integer) 12
其他命令
HINCRBYFLOAT
HSCAN
Zset
有序集合命令
ZADD|ZCARD|ZRANGE|ZREVRANGE
127.0.0.1:6379[4]> SELECT 5
OK
# 设置集合元素,还有一些附带选项,自行探索
127.0.0.1:6379[5]> ZADD zset1 1 one 2 two 3 three 4 four
(integer) 4
# 集合元素数量
127.0.0.1:6379[5]> ZCARD zset1
(integer) 4
# 权重在指定范围的元素数量
127.0.0.1:6379[5]> ZCOUNT zset2 3 7
(integer) 2
# 注意:0 -1 前者表示start -1表示stop
# 查看集合
127.0.0.1:6379[5]> ZRANGE zset1 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
# 带权重查看
127.0.0.1:6379[5]> ZRANGE zset1 0 -1 WITHSCORES
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
7) "four"
8) "4"
# 逆序
127.0.0.1:6379[5]> ZREVRANGE zset1 0 -1
1) "ten"
2) "eight"
3) "six"
4) "four"
5) "three"
6) "two"
7) "one"
ZRANGEBYSCORE|ZREVRANGEBYSCORE
127.0.0.1:6379[5]> ZADD zset1 6 six 8 eight 10 ten
(integer) 3
127.0.0.1:6379[5]> ZRANGEBYSCORE zset1 -inf +inf
1) "one"
2) "two"
3) "three"
4) "four"
5) "six"
6) "eight"
7) "ten"
# 带权重输出
127.0.0.1:6379[5]> ZRANGEBYSCORE zset1 (2 +inf WITHSCORES
1) "three"
2) "3"
3) "four"
4) "4"
5) "six"
6) "6"
7) "eight"
8) "8"
9) "ten"
10) "10"
# 指定范围排序后偏移几条记录后取指定个数的记录
127.0.0.1:6379[5]> ZRANGEBYSCORE zset1 (2 +inf WITHSCORES LIMIT 1 3
1) "four"
2) "4"
3) "six"
4) "6"
5) "eight"
6) "8"
# ZREVRANGEBYSCORE 逆序后输出
ZRANK|ZREVRANK
127.0.0.1:6379[5]> ZRANGE zset1 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
5) "six"
6) "eight"
7) "ten"
# 排序从0开始
127.0.0.1:6379[5]> ZRANK zset1 eight
(integer) 5
# ZREVRANK 逆序后获取排名
ZSCORE
127.0.0.1:6379[5]> ZSCORE zset1 ten
"10"
ZREM
127.0.0.1:6379[5]> ZREM zset1 ten one
(integer) 2
ZINCREBY
127.0.0.1:6379[5]> ZINCRBY zset1 10 six
"16"
127.0.0.1:6379[5]> ZRANGE zset1 0 -1
1) "two"
2) "three"
3) "four"
4) "eight"
5) "six"
127.0.0.1:6379[5]> ZINCRBY zset1 10 six
"16"
127.0.0.1:6379[5]> ZINCRBY zset1 1 two
"3"
127.0.0.1:6379[5]> ZRANGE zset1 0 -1 WITHSCORES
1) "three"
2) "3"
3) "two"
4) "3"
5) "four"
6) "4"
7) "eight"
8) "8"
9) "six"
10) "16"
ZREMRANGEBYRANK|ZREMRANGEBYSCORE
# 删除指定排名范围的元素
127.0.0.1:6379[5]> ZREMRANGEBYRANK zset1 3 4
(integer) 2
127.0.0.1:6379[5]> ZRANGE zset1 0 -1 WITHSCORES
1) "three"
2) "3"
3) "two"
4) "3"
5) "four"
6) "4"
# ZREMRANGEBYSCORE 同理删除指定权重范围的元素
字典区间类命令
# 只适用于权重值相同的集合元素,用于按字典序进行操作
ZLEXCOUNT key min max
ZRANGEBYLEX key min max [LIMIT offset count]
ZREVRANGEBYLEX key max min [LIMIT offset count]
ZREMRANGEBYLEX key min max
队列操作命令
# 非阻塞队列
ZPOPMIN key [count]
ZPOPMAX key [count]
# 阻塞队列
BZPOPMIN key [key...] timeout
BZPOPMAX key [key...] timeout
集合操作命令
ZDIFF numkeys key [key...] [WITHSCORES]
ZINTER numkeys key [key...]
ZUNION numkeys key [key...]
其他命令
ZDIFFSTORE destination numkeys key [key...]
ZINTERSTORE destination numkeys key [key...]
ZUNIONSTORE destination numkeys key [key...]
ZSCAN key cursor [MATCH pattern] [COUNT count]
Geo
地理位置命令
测试数据
城市 经度 纬度 拼音
北京 116.407001 39.904600 beijing
上海 121.474000 31.230001 shanghai
广州 113.264999 23.129101 guangzhou
深圳 114.058000 22.542800 shenzhen
苏州 120.585999 31.297301 suzhou
杭州 120.209000 30.247100 hangzhou
南京 118.796000 32.058300 nanjing
武汉 114.305001 30.592800 wuhan
宁波 121.624273 29.860428 ningbo
重庆 116.407001 39.904600 chongqing
成都 104.066284 30.572939 chengdu
西安 108.940000 34.341101 xian
郑州 113.625000 34.747201 zhengzhou
青岛 120.383000 36.066099 qingdao
GEOADD
127.0.0.1:6379[5]> SELECT 6
OK
# 插入地理位置
# GEOADD key longitude latitude member [longitude latitude member ...]
127.0.0.1:6379[6]> GEOADD china 116.407001 39.904600 beijing 121.474000 31.230001 shanghai 113.264999 23.129101 guangzhou 114.058000 22.542800 shenzhen 120.585999 31.297301 suzhou 120.209000 30.247100 hangzhou 118.796000 32.058300 nanjing 114.305001 30.592800 wuhan 121.624273 29.860428 ningbo 116.407001 39.904600 chongqing 104.066284 30.572939 chengdu 108.940000 34.341101 xian 113.625000 34.747201 zhengzhou 120.383000 36.066099 qingdao
GEOPOS
# 查看指定城市的经纬度
# GEOPOS key member [member ...]
127.0.0.1:6379[6]> GEOPOS china beijing ningbo
1) 1) "116.40699952840805054"
2) "39.9046006105751232"
2) 1) "121.62427157163619995"
2) "29.86042836405917456"
GEOHASH
# 获取指定城市的经纬度hash后的字符串,字符串越相似距离越近
# GEOHASH key member [member ...]
127.0.0.1:6379[6]> GEOHASH china shanghai shenzhen
1) "wtw3sjnxvq0"
2) "ws105ry9240"
GEODIST
# 直线距离
# GEODIST key member1 member2 [m|km|ft|mi]
# m:米,默认单位 km:千米 mi:英里 ft :英尺。
127.0.0.1:6379[6]> GEODIST china suzhou shanghai
"84757.3936"
127.0.0.1:6379[6]> GEODIST china beijing shanghai km
"1067.7185"
GEORADIUS|GEORADIUSBYMEMBER
# 基于指定经纬度查询指定半径的城市及其直线距离和经纬度
# GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
# GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
# 注意:所有with的选项和store的选项互斥,store选项之间也互斥
# store选项会把数据存储到有序集合中
127.0.0.1:6379[6]> GEORADIUS china 115 35 300 km WITHHASH
1) 1) "zhengzhou"
2) (integer) 4064941973330510
127.0.0.1:6379[6]> GEORADIUS china 115 35 800 km WITHCOORD WITHDIST
1) 1) "zhengzhou" # 城市名称
2) "128.5818" # 直线距离
3) 1) "113.62500160932540894" # 经度
2) "34.74720093294700263" # 纬度
2) 1) "nanjing"
2) "480.4742"
3) 1) "118.79600018262863159"
2) "32.0583002896489333"
3) 1) "qingdao"
2) "501.3811"
3) 1) "120.38299888372421265"
2) "36.06609999794931554"
4) 1) "beijing"
2) "559.4648"
3) 1) "116.40699952840805054"
2) "39.9046006105751232"
5) 1) "chongqing"
2) "559.4648"
3) 1) "116.40699952840805054"
2) "39.9046006105751232"
6) 1) "wuhan"
2) "494.4799"
3) 1) "114.30500060319900513"
2) "30.59280055695393941"
7) 1) "hangzhou"
2) "719.1873"
3) 1) "120.20899862051010132"
2) "30.24710007691633251"
8) 1) "suzhou"
2) "663.2751"
3) 1) "120.58599919080734253"
2) "31.29730095598060302"
9) 1) "shanghai"
2) "734.2831"
3) 1) "121.47399812936782837"
2) "31.23000157447899738"
10) 1) "xian"
2) "559.0813"
3) 1) "108.93999785184860229"
2) "34.34110058240350583"
# 基于指定经纬度查询指定半径的城市及其直线距离和经纬度,按距离升序取出前五个
# 即使不指定ASC也可以,默认即取出距离最近的n个
127.0.0.1:6379[6]> GEORADIUS china 115 35 800 km WITHCOORD WITHDIST COUNT 5 ASC
1) 1) "zhengzhou"
2) "128.5818"
3) 1) "113.62500160932540894"
2) "34.74720093294700263"
2) 1) "nanjing"
2) "480.4742"
3) 1) "118.79600018262863159"
2) "32.0583002896489333"
3) 1) "wuhan"
2) "494.4799"
3) 1) "114.30500060319900513"
2) "30.59280055695393941"
4) 1) "qingdao"
2) "501.3811"
3) 1) "120.38299888372421265"
2) "36.06609999794931554"
5) 1) "xian"
2) "559.0813"
3) 1) "108.93999785184860229"
2) "34.34110058240350583"
# 把结果存储到有序集合中,注意此处的权重是经纬度hash后的整数
127.0.0.1:6379[6]> GEORADIUS china 115 35 800 km COUNT 5 ASC STORE citys
(integer) 5
127.0.0.1:6379[6]> TYPE citys
zset
127.0.0.1:6379[6]> ZRANGE citys 0 -1 WITHSCORES
1) "xian"
2) "4040116417781167"
3) "wuhan"
4) "4052121461471153"
5) "zhengzhou"
6) "4064941973330510"
7) "nanjing"
8) "4066006847772383"
9) "qingdao"
10) "4067545186376599"
# 把结果存储到有序集合中,注意此处的权重是距指定经纬度的距离
127.0.0.1:6379[6]> GEORADIUS china 115 35 800 km COUNT 5 ASC STOREDIST city_dists
(integer) 5
127.0.0.1:6379[6]> TYPE city_dists
zset
127.0.0.1:6379[6]> ZRANGE city_dists 0 -1 WITHSCORES
1) "zhengzhou"
2) "128.58183420777524"
3) "nanjing"
4) "480.4742301934462"
5) "wuhan"
6) "494.47987039655112"
7) "qingdao"
8) "501.38111037505377"
9) "xian"
10) "559.08132388792546"
# GEORADIUSBYMEMBER 同理
其他命令
# 只读形式的方法,ro即readonly
georadius_ro
georadiusbynumber_ro
HyperLogLog
基数统计命令
PFADD
# 添加元素
# PFADD key element [element ...]
127.0.0.1:6379[6]> SELECT 7
OK
127.0.0.1:6379[7]> PFADD hp 1 2 3 4 5 6 7 8 9 2 4 6 14 67 24 56 23 45 23 76 45 23 12 45 67 32 45 57 89 32 43
(integer) 1
PFCOUNT
# 获取基数
# PFCOUNT key [key ...]
127.0.0.1:6379[7]> PFCOUNT hp
(integer) 21
127.0.0.1:6379[7]> PFADD hp 34 35 67 23 65 78 43 12 67 f hg sadf dgh est wer dg dfg adf
(integer) 1
127.0.0.1:6379[7]> PFCOUNT hp
(integer) 34
PFMERGE
# 合并基数
# PFMERGE destkey sourcekey [sourcekey ...]
127.0.0.1:6379[7]> PFADD hp1 1 2 3 4 5 6 2 3
(integer) 1
127.0.0.1:6379[7]> PFADD hp2 2 4 5 6 7 8 8 7
(integer) 1
127.0.0.1:6379[7]> PFADD hp3 1 3 6 7
(integer) 1
127.0.0.1:6379[7]> PFCOUNT hp1
(integer) 6
127.0.0.1:6379[7]> PFCOUNT hp2
(integer) 6
127.0.0.1:6379[7]> PFCOUNT hp3
(integer) 4
127.0.0.1:6379[7]> PFMERGE hp_merge hp1 hp2 hp3
OK
127.0.0.1:6379[7]> PFCOUNT hp_merge
(integer) 8
Bitmaps
位图命令
SETBIT|GETBIT
127.0.0.1:6379[7]> SELECT 8
OK
# 设置位
127.0.0.1:6379[8]> SETBIT login 0 1
(integer) 0
127.0.0.1:6379[8]> SETBIT login 1 1
(integer) 0
127.0.0.1:6379[8]> SETBIT login 2 1
(integer) 0
127.0.0.1:6379[8]> SETBIT login 3 0
(integer) 0
127.0.0.1:6379[8]> SETBIT login 4 0
(integer) 0
127.0.0.1:6379[8]> SETBIT login 5 0
(integer) 0
127.0.0.1:6379[8]> SETBIT login 6 1
(integer) 0
127.0.0.1:6379[8]> SETBIT login 7 1
(integer) 0
127.0.0.1:6379[8]> SETBIT login 8 0
(integer) 0
127.0.0.1:6379[8]> SETBIT login 9 0
(integer) 0
127.0.0.1:6379[8]> SETBIT login 10 1
(integer) 0
127.0.0.1:6379[8]> SETBIT login 11 1
(integer) 0
127.0.0.1:6379[8]> SETBIT login 12 1
(integer) 0
127.0.0.1:6379[8]> SETBIT login 13 1
(integer) 0
127.0.0.1:6379[8]> SETBIT login 14 0
(integer) 0
127.0.0.1:6379[8]> SETBIT login 15 0
(integer) 0
127.0.0.1:6379[8]> SETBIT login 16 0
(integer) 0
127.0.0.1:6379[8]> TYPE login
string
# 获取位
127.0.0.1:6379[8]> GETBIT login 3
(integer) 0
BITCOUNT
# 获取整个位图的1位的个数
127.0.0.1:6379[8]> BITCOUNT login
(integer) 9
# 等价
127.0.0.1:6379[8]> BITCOUNT login 0 -1
(integer) 9
# 此处也等价(特殊)
127.0.0.1:6379[8]> BITCOUNT login 0 1
(integer) 9
# 注意:按字节偏移而不是位
127.0.0.1:6379[8]> BITCOUNT login 0 0
(integer) 5
# 注意:按字节偏移而不是位
127.0.0.1:6379[8]> BITCOUNT login 1 1
(integer) 4
BITPOS
# 返回的是整个位图中 bit 级别的索引,而不是相对于你传入的起始字节的偏移。
# 整个位图从左起第一个设置为0的位的索引
127.0.0.1:6379[8]> BITPOS login 0
(integer) 3
# 整个位图从左起第一个设置为1的位的索引
127.0.0.1:6379[8]> BITPOS login 1
(integer) 0
# 注意偏移按字节,此处指第二个字节中,也即8~15位中左数第一个0位在整个位图中的索引
127.0.0.1:6379[8]> BITPOS login 0 1 1
(integer) 8
# 注意偏移按字节,此处指第二个字节中,也即8~15位中左数第一个1位在整个位图中的索引
127.0.0.1:6379[8]> BITPOS login 1 1 1
(integer) 10
BITOP
# # 支持 AND | OR | XOR | NOT
# 操作以字节为单位
# 例如:对两个位图 bita, bitb(其中 bita 长度大于 bitb )进行操作
# bita 会扩展到整数字节 bita',bitb 扩展到跟 bita' 一样长的 bitb'
# 例如:对单个位图 bit 进行操作,会扩展到整数字节 bit'
# 扩展用0填充尾部
# 位图1
127.0.0.1:6379[8]> SETBIT bit1 0 1
(integer) 0
127.0.0.1:6379[8]> SETBIT bit1 1 1
(integer) 0
127.0.0.1:6379[8]> SETBIT bit1 2 0
(integer) 0
127.0.0.1:6379[8]> SETBIT bit1 3 0
(integer) 0
127.0.0.1:6379[8]> SETBIT bit1 4 1
(integer) 0
# 位图2
127.0.0.1:6379[8]> SETBIT bit2 0 0
(integer) 0
127.0.0.1:6379[8]> SETBIT bit2 1 1
(integer) 0
127.0.0.1:6379[8]> SETBIT bit2 2 1
(integer) 0
# 实际 bit1: 11001000
# 实际 bit2: 01100000
# 或运算
127.0.0.1:6379[8]> BITOP OR bit_or bit1 bit2
(integer) 1
127.0.0.1:6379[8]> BITCOUNT bit_or
(integer) 4
# 且运算
127.0.0.1:6379[8]> BITOP AND bit_and bit1 bit2
(integer) 1
127.0.0.1:6379[8]> BITCOUNT bit_and
(integer) 1
# 异或运算
127.0.0.1:6379[8]> BITOP XOR bit_xor bit1 bit2
(integer) 1
127.0.0.1:6379[8]> BITCOUNT bit_xor
(integer) 3
# 非运算,此处结果是5,原因依然是操作以字节为单位
127.0.0.1:6379[8]> BITOP NOT bit_not bit1
(integer) 1
127.0.0.1:6379[8]> BITCOUNT bit_not
(integer) 5
其他命令
BITFIELD
事务管理
特性
局限性很大,不能实现真正意义上的事务
redis的事务本质是命令队列,依次执行,不具备原子性和隔离性
即事务中如果某操作出错,之前的操作并不会回滚,之后的操作也不会终止
它只能保证事务执行时其他命令不能插队(由单线程保证)
常见用途:批量操作,伪乐观锁(不能从根本上避免插队,只适合不关注原值的场景)
实现多指令的原子性应借助Lua脚本
MULTI|EXEC|DISCARD
127.0.0.1:6379> SELECT 9
OK
# 开启事务
127.0.0.1:6379[9]> MULTI
OK
# 操作一一入队
127.0.0.1:6379[9]> SET k1 v1
QUEUED
127.0.0.1:6379[9]> SET k2 v2
QUEUED
127.0.0.1:6379[9]> SET k3 v3
QUEUED
127.0.0.1:6379[9]> GET k1
QUEUED
# 把入队的操作一次性执行完毕
127.0.0.1:6379[9]> EXEC
1) OK
2) OK
3) OK
4) "v1"
# 开启事务
127.0.0.1:6379[9]> MULTI
OK
# 操作入队
127.0.0.1:6379[9]> SET k4 v1
QUEUED
127.0.0.1:6379[9]> LPUSH ls1 e1 e2 e3
QUEUED
# 放弃事务
127.0.0.1:6379[9]> DISCARD
OK
# 查看结果
127.0.0.1:6379[9]> KEYS *
1) "k3"
2) "k1"
3) "k2"
# 以下操作展示了顺序性
127.0.0.1:6379[9]> FLUSHDB
OK
127.0.0.1:6379[9]> MULTI
OK
127.0.0.1:6379[9]> SET k1 v1
QUEUED
127.0.0.1:6379[9]> GET k2
QUEUED
127.0.0.1:6379[9]> SET k2 v2
QUEUED
127.0.0.1:6379[9]> EXEC
1) OK
2) (nil)
3) OK
# 执行时错误不回滚、不终止
#
127.0.0.1:6379[9]> FLUSHDB
OK
127.0.0.1:6379[9]> MULTI
OK
127.0.0.1:6379[9]> SET k1 v1
QUEUED
127.0.0.1:6379[9]> INCR k1
QUEUED
127.0.0.1:6379[9]> SET k2 v2
QUEUED
# 执行结果,错误被跳过
127.0.0.1:6379[9]> EXEC
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
# 执行前错误(类似编译错误)事务被强制取消
127.0.0.1:6379[9]> MULTI
OK
127.0.0.1:6379[9]> SETN k1 v1
(error) ERR unknown command 'SETN'
127.0.0.1:6379[9]> SET k2 v2
QUEUED
# 执行结果,事务被取消
127.0.0.1:6379[9]> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
WATCH|UNWACH
# 监视或取消监视必须在开启事务之前
# 即:即使在事务中首先进行了取消监视,如果事务执行前监视的键被修改,事务依然会执行失败
# 这实现了对redis数据库本身的乐观锁,保证了在监视操作和事务操作之间不会被其他操作插队
# 但请注意:一般事务操作前势必要先获取键(根据值决定事务逻辑或是否进行执行),而这个获取键和对其设置监视的过程中仍然存在插队的可能性,因此监视并不能完全实现真正的乐观锁
# 不关注原值,即不需要根据原值来决定事务逻辑或是否执行的场景,监视是完全可行的,例如计数器,标记设置(频率限制、库存扣减等就不适用)等
127.0.0.1:6379[9]> FLUSHDB
OK
# 设置两个键
127.0.0.1:6379[9]> SET total 100
OK
127.0.0.1:6379[9]> SET sale 0
OK
# 开始监视
127.0.0.1:6379[9]> WATCH total sale
OK
# 在执行前另开客户端对监视的键的值进行操作,例如
127.0.0.1:6379[9]> INCRBY total 100
(integer) 200
# 开启事务
127.0.0.1:6379[9]> MULTI
OK
# 操作入队
127.0.0.1:6379[9]> DECRBY total 10
QUEUED
127.0.0.1:6379[9]> INCRBY sale 10
QUEUED
# 执行事务,但发现监视的键被改变,执行失败
127.0.0.1:6379[9]> EXEC
(nil)
# 一旦事务 执行或取消 之后,会自动取消原来监视的键
# 重新执行
127.0.0.1:6379[9]> MULTI
OK
127.0.0.1:6379[9]> INCRBY sale 10
QUEUED
127.0.0.1:6379[9]> DECRBY total 10
QUEUED
# 执行成功
127.0.0.1:6379[9]> EXEC
1) (integer) 10
2) (integer) 190
# 事务中UNWATCH
127.0.0.1:6379[9]> FLUSHDB
OK
# 设置两个键
127.0.0.1:6379[9]> SET total 100
OK
127.0.0.1:6379[9]> SET sale 0
OK
# 开始监视
127.0.0.1:6379[9]> WATCH total sale
OK
# 插队修改了被监视的键
127.0.0.1:6379[9]> INCRBY total 100
(integer) 200
# 开启事务
127.0.0.1:6379[9]> MULTI
OK
# 取消监视,此时取消无效
127.0.0.1:6379[9]> UNWATCH
QUEUED
127.0.0.1:6379[9]> DECRBY total 10
QUEUED
127.0.0.1:6379[9]> INCRBY sale 10
QUEUED
# 执行失败
127.0.0.1:6379[9]> EXEC
(nil)
# 事务前的UNWATCH
127.0.0.1:6379[9]> FLUSHDB
OK
127.0.0.1:6379[9]> SET total 100
OK
127.0.0.1:6379[9]> SET sale 0
OK
# 监视
127.0.0.1:6379[9]> WATCH total sale
OK
# 插队
127.0.0.1:6379[9]> INCRBY total 100
(integer) 200
# 取消监视
127.0.0.1:6379[9]> UNWATCH
OK
# 或者在这插队
# 127.0.0.1:6379[9]> INCRBY total 100
# (integer) 200
# 开启事务
127.0.0.1:6379[9]> MULTI
OK
127.0.0.1:6379[9]> DECRBY total 10
QUEUED
127.0.0.1:6379[9]> INCRBY sale 10
QUEUED
127.0.0.1:6379[9]> INCRBY total 100
(integer) 200
# 执行成功
127.0.0.1:6379[9]> EXEC
1) (integer) 190
2) (integer) 10
发布订阅
特性
原理:redis-server 维护了一个字典,字典的键就是频道,值则是链表(链表存储订阅该频道的所有客户端)
订阅操作会往字典中 新增 频道-订阅者链表 键值对 或 更新 频道这个键对应的链表值
发布操作会 在字典中查找频道,遍历对应的订阅者链表 发送消息
常见用途:消息队列,网络聊天室,订阅通知
对于复杂场景要借助消息中间件,例如RabbitMQ,Kafka或RocketMQ等
SUBSCRIBE|PSUBSCRIBE|PUBLISH
# 注意:先订阅再发布,不然会错过订阅前发布的消息
# 订阅者1
127.0.0.1:6379> SUBSCRIBE ch1 ch2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ch1"
3) (integer) 1
1) "subscribe"
2) "ch2"
3) (integer) 2
# 订阅者2
127.0.0.1:6379> SUBSCRIBE ch1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ch1"
3) (integer) 1
# 发布者
127.0.0.1:6379[10]> PUBLISH ch1 "hello ch1"
(integer) 2
127.0.0.1:6379[10]> PUBLISH ch2 "hello ch2"
(integer) 1
# 订阅者1
1) "message"
2) "ch1"
3) "hello ch1"
1) "message"
2) "ch2"
3) "hello ch2"
# 订阅者2
1) "message"
2) "ch1"
3) "hello ch1"
# UNSUBSCRIBE 该命令有设计缺陷,一旦进入订阅模式后就无法再执行命令,而要退出订阅模式只能CTRL+C,此时连客户端一并退出了,客户端一退出,所有订阅都失效(即使再次进入),因此,取消订阅相关的操作都不能执行
# 起码客户端之际进行退订操作是不可行的!!!
# 同理
# 模式支持 glob 风格的正则表达式。
# 每个模式以 * 作为匹配符。
# it* 匹配所有以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等)
# news.* 匹配所有以 news. 开头的频道( news.it 、 news.global.today 等等),诸如此类。
PSUBSCRIBE pattern [pattern ...]
# 同样客户端不可用
PUNSUBSCRIBE
其他命令
PUBSUB
配置文件
redis-server启动时根据配置文件运行,该文件通常在/etc/redis.conf
进行配置可以直接编写配置文件,也可以通过客户端命令:CONFIG SET field value
内存配置的单位
# 以下均可,且大小写不敏感,例如: gb=GB=gB=Gb
1k => 1000 bytes
1kb => 1024 bytes
1m => 1000000 bytes
1mb => 1024*1024 bytes
1g => 1000000000 bytes
1gb => 1024*1024*1024 bytes
包含导入
# 对同一参数的重复配置以最后出现的为准
# 导入其他配置文件(个性配置),会加入到当前配置文件(主配置)
# 实际使用时应放到后面,以覆盖主配置文件的一些设置,也可根据情况进行安排导入位置,达到部分可个性配置,部分不可的需求
# include /path/to/local.conf
# include /path/to/other.conf
网络配置
# ip设置,默认只能本机连接
bind 127.0.0.1
# 保护模式,默认开启
protected-mode yes
# 端口设置,默认的6379,0表示不监听
port 6379
通用配置
# 以守护进程的方式运行,默认为no,需要配置为yes,设置为yes需要配置pid文件
daemonize yes
# 如果以守护进程运行,需要指定pid文件,当一台主机启动多个服务时应设置为不同的文件名,不可重复,该文件不必创建
pidfile "/var/run/redis_6379.pid"
# 日志级别,默认为notice
# debug verbose notice warning
loglevel notice
# 日志文件的存储位置
logfile "/var/log/redis/redis.log"
# 数据库数量,默认为16个,0~15号
databases 16
快照配置(RDB配置)
# RDB策略的持久化规则
# 默认为60秒内写入10000次将持久化一次,其他两者同理
save 900 1
save 300 10
save 60 10000
# 持久化出错后是否继续工作,默认为yes
stop-writes-on-bgsave-error yes
# 是否压缩rdb文件,默认为yes
rdbcompression yes
# 是否校验rdb文件
rdbchecksum yes
# rdb文件的文件名称
dbfilename "dump.rdb"
# rdb或aof文件的保存目录
dir "/var/lib/redis"
复制配置
# 该主机是哪个主机的从机
# slaveof <masterip> <masterport>
# 主机的密码
# masterauth <master-password>
slave-serve-stale-data yes
# 从机只读,默认为yes
slave-read-only yes
# 从机的优先级,即被选为主机的优先级,,值越小优先级越高,但0表示不能设置为主机,默认为100
slave-priority 100
安全配置
# 是否需要密码验证,默认被注释也即不需要
# requirepass foobared
# 例如设置为123456
requirepass 123456
# 命令重命名
# 通过把危险的命令重命名为空字符串或特殊字符串,可以实现一般人难以执行或不能执行
# 例如FLUSHALL FLUSHDB CONFIG SET KEYS SHUTDOWN等
# rename-command CONFIG ""
限制设置
# 客户端的最大数量
# maxclients 10000
# 最大内存容量,单位为字节
# maxmemory <bytes>
# 内存溢出策略
# volatile-lru 对设置了过期时间的键使用LRU算法进行移除
# allkeys-lru 使用LRU算法移除
# volatile-random 随机移除设置了过期时间的键
# allkeys-random 随机移除任意键
# volatile-ttl 移除距离过期最近的一些键
# noeviction 永不移除,返回错误
# maxmemory-policy noeviction
AOF配置
# AOF是否开启,默认不开启
appendonly no
# aof文件的文件名称
appendfilename "appendonly.aof"
# AOF策略的持久化规则,总是,每秒,从不
# appendfsync always
appendfsync everysec
# appendfsync no
# 重写时不追加,默认不开启,建议保持默认
no-appendfsync-on-rewrite no
# 文件大小每翻倍一次就自动执行一次重写,0表示不重写
auto-aof-rewrite-percentage 100
# 文件大小小于64mb时不进行重写
auto-aof-rewrite-min-size 64mb
# 如果载入时发现aof文件被截断(即末尾命令不完整)也进行启动,忽略最后的不完整命令,但会提示存在文件截断
# 如果设置为no,则直接报错
# 什么时候会截断?缓冲区写入aof文件时断电或崩溃等
aof-load-truncated yes
配置命令
# 并非所有配置都支持命令行形式进行配置,最好还是通过配置文件进行配置,更加灵活
# CONFIG SET|CONFIG GET|CONFIG REWRITE
# 密码
# 查看密码,默认密码为空,即无密码
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> 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"
# 注意:此时密码配置并未写入配置文件,重启redis服务后会失效
# 将命令行进行的配置写入配置文件
127.0.0.1:6379> CONFIG REWRITE
OK
# 常见配置项
appendonly requirepass等
持久策略
注意事项
Redis是基于内存的数据库,内存是断电即失的,因此在生产环境必须进行持久化设置(除非你明确用于纯缓存场景)
正常关闭redis-server(例如 systemctl stop | restart)时会自动执行一次保存(除非未开启对应的保存策略)
数据总是应该进行备份
数据恢复前总是应该先关闭服务
保存相关命令
# 返回最近一次 Redis 成功将数据保存到磁盘上的时间
127.0.0.1:6379> LASTSAVE
(integer) 1747522611
# 即使未开启aof
BGREWRITEAOF 异步执行一个 AOF(AppendOnly File) 文件重写操作
# 即使未开启rdb
BGSAVE 在后台异步保存当前数据库的数据到磁盘
SAVE 同步保存数据到硬盘
# 保证不存在aof或rdb文件
[root@cloudhost redis]# ls
[root@cloudhost redis]#
[root@cloudhost redis]# redis-cli
# 确定未开启aof和rdb
127.0.0.1:6379> CONFIG GET appendonly
1) "appendonly"
2) "no"
127.0.0.1:6379> CONFIG GET save
1) "save"
2) ""
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> SET k1 v1
OK
127.0.0.1:6379> SET k2 v2
OK
127.0.0.1:6379> SET k3 v3
OK
# 前台保存
127.0.0.1:6379> SAVE
OK
127.0.0.1:6379> QUIT
# 查看发现保存rdb
[root@cloudhost redis]# ls
dump.rdb
# 删除rdb保证不存在
[root@cloudhost redis]# rm dump.rdb
rm: remove regular file ‘dump.rdb’? y
[root@cloudhost redis]# ls
[root@cloudhost redis]#
[root@cloudhost redis]# redis-cli
127.0.0.1:6379> DBSIZE
(integer) 3
# 后台保存
127.0.0.1:6379> BGSAVE
Background saving started
127.0.0.1:6379> QUIT
# 查看发现保存rdb
[root@cloudhost redis]# ls
dump.rdb
# 删除rdb保证不存在
[root@cloudhost redis]# rm dump.rdb
rm: remove regular file ‘dump.rdb’? y
[root@cloudhost redis]# ls
[root@cloudhost redis]#
[root@cloudhost redis]# redis-cli
127.0.0.1:6379> DBSIZE
(integer) 3
# 后台重写
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
127.0.0.1:6379> QUIT
# 查看发现保存aof
[root@cloudhost redis]# ls
appendonly.aof
RDB
原理
redis-server启动时载入rdb文件恢复原始数据库中的数据
当触发持久化规则(即配置文件中的save设置)时,redis-server会创建一个子进程,子进程将数据写入到临时的rdb文件中,写入完成时再将上次持久化的文件(保存位置和文件名称在配置文件中的dir和dbfilename设置)替换掉
该过程主进程不进行IO操作,因此不影响数据库的读写性能,此外rdb的载入恢复速度也很快,缺点是最后一次持久化前的数据(丢失的数量参考其持久化规则)可能会丢失:断电,崩溃,手动杀死进程(正常关闭服务则不会)
测试
# 设置持久化规则为60秒内修改5次
# 注意:测试后应恢复原样,实际生产中应至少设置一个 save xx 1
# 使用 save "" 可以取消rdb自动备份,但正常关闭服务时仍然会触发,手动保存也可以正常进行
# 如果要彻底关闭rdb,则注释掉所有SAVE配置即可
# save 900 1
# save 300 10
# save 60 10000
save 60 5
# 测试持久化规则是否生效
# 清除原rdb文件(其实是备份)
[root@cloudhost ~]# cd /var/lib/redis/
[root@cloudhost redis]# ls
dump.rdb
[root@cloudhost redis]# mv dump.rdb dump.rdb_bark
[root@cloudhost redis]# ls
dump.rdb_bark
# 开始进行数据库写入操作,此处的操作只要是涉及数据修改的即可
[root@cloudhost redis]# redis-cli
127.0.0.1:6379> SELECT 11
OK
127.0.0.1:6379[11]> SET key1 val1
OK
127.0.0.1:6379[11]> SET key2 val2
OK
127.0.0.1:6379[11]> SET key3 val3
OK
127.0.0.1:6379[11]> SET key4 val4
OK
127.0.0.1:6379[11]> QUIT
# 此时不满足60秒写入5次,因此尚未生成rdb文件
[root@cloudhost redis]# ls
dump.rdb_bark
# 再进行一次数据库写入操作
[root@cloudhost redis]# redis-cli
127.0.0.1:6379> SELECT 12
OK
127.0.0.1:6379[12]> SET key5 val5
OK
127.0.0.1:6379[12]> QUIT
# 满足持久化规则,因此生成rdb文件
[root@cloudhost redis]# ls
dump.rdb dump.rdb_bark
# 进一步测试数据丢失
# 写入4条数据,未触发持久化规则
[root@cloudhost redis]# redis-cli
127.0.0.1:6379> SELECT 13
OK
127.0.0.1:6379[13]> DBSIZE
(integer) 0
127.0.0.1:6379[13]> SET k1 v1
OK
127.0.0.1:6379[13]> SET k2 v2
OK
127.0.0.1:6379[13]> SET k3 v3
OK
127.0.0.1:6379[13]> SET k4 v4
OK
127.0.0.1:6379[13]> QUIT
# 强制杀死redis-server(注意必须强制杀死进程或断电等极端条件才行)
# 在客户端内执行 DEBUG SEGFAULT 命令也可以
[root@cloudhost redis]# ps -ef | grep redis
redis 5230 1 0 04:02 ? 00:00:00 /usr/bin/redis-server 127.0.0.1:6379
root 5305 1564 0 04:04 pts/1 00:00:00 grep --color=auto redis
[root@cloudhost redis]# kill -9 5230
[root@cloudhost redis]# systemctl status redis
● redis.service - Redis persistent key-value database
Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/redis.service.d
└─limit.conf
Active: failed (Result: signal) since Sun 2025-05-18 04:04:41 CST; 5s ago
Process: 5054 ExecStop=/usr/libexec/redis-shutdown (code=exited, status=1/FAILURE)
Process: 5230 ExecStart=/usr/bin/redis-server /etc/redis.conf --supervised systemd (code=killed, signal=KILL)
Main PID: 5230 (code=killed, signal=KILL)
May 18 04:02:41 cloudhost systemd[1]: Starting Redis persistent key-value database...
May 18 04:02:42 cloudhost systemd[1]: Started Redis persistent key-value database.
May 18 04:04:41 cloudhost systemd[1]: redis.service: main process exited, code=killed, status=9/KILL
May 18 04:04:41 cloudhost systemd[1]: Unit redis.service entered failed state.
May 18 04:04:41 cloudhost systemd[1]: redis.service failed.
[root@cloudhost redis]# systemctl start redis
[root@cloudhost redis]# systemctl status redis
● redis.service - Redis persistent key-value database
Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/redis.service.d
└─limit.conf
Active: active (running) since Sun 2025-05-18 04:05:05 CST; 2s ago
Process: 5054 ExecStop=/usr/libexec/redis-shutdown (code=exited, status=1/FAILURE)
Main PID: 5359 (redis-server)
Tasks: 3
Memory: 4.2M
CGroup: /system.slice/redis.service
└─5359 /usr/bin/redis-server 127.0.0.1:6379
May 18 04:05:05 cloudhost systemd[1]: Starting Redis persistent key-value database...
May 18 04:05:05 cloudhost systemd[1]: Started Redis persistent key-value database.
# 查看数据库,发现数据不存在
[root@cloudhost redis]# redis-cli
127.0.0.1:6379> SELECT 13
OK
127.0.0.1:6379[13]> DBSIZE
(integer) 0
# 恢复配置
save 900 1
save 300 10
save 60 10000
# 必须先停止服务再恢复原数据库,顺序相反会造成恢复的文件被重启服务时保存的文件覆盖!!!
# 停止服务
[root@cloudhost redis]# systemctl stop redis
[root@cloudhost redis]# systemctl status redis
● redis.service - Redis persistent key-value database
Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/redis.service.d
└─limit.conf
Active: inactive (dead) since Sun 2025-05-18 06:54:27 CST; 9s ago
Process: 14055 ExecStop=/usr/libexec/redis-shutdown (code=exited, status=0/SUCCESS)
Process: 14018 ExecStart=/usr/bin/redis-server /etc/redis.conf --supervised systemd (code=exited, status=0/SUCCESS)
Main PID: 14018 (code=exited, status=0/SUCCESS)
May 18 06:53:54 cloudhost systemd[1]: Starting Redis persistent key-value database...
May 18 06:53:54 cloudhost systemd[1]: Started Redis persistent key-value database.
May 18 06:54:27 cloudhost systemd[1]: Stopping Redis persistent key-value database...
May 18 06:54:27 cloudhost systemd[1]: Stopped Redis persistent key-value database.
# 恢复原数据库
[root@cloudhost redis]# mv dump.rdb_bark dump.rdb
mv: overwrite ‘dump.rdb’? y
AOF
原理
redis-server启动时把aof文件的命令从头到尾执行一遍,将写入操作转换成对应的协议格式并写入aof缓冲区中
当触发持久化规则(即配置文件中的appendfsync设置)时,主进程将缓冲区的aof记录追加到临时的aof文件中
当触发日志重写规则时,redis-server会创建一个子进程,同时继续将写入操作转换成对应的协议格式并写入aof重写缓冲区中,子进程利用内存中的数据库进行完整的命令重构(注意:直接基于内存中的数据库,而不是旧的aof文件,这依赖于操作系统的写时复制技术),进而将命令集写入到临时的aof文件中,写入完毕后通知主进程,主进程再把重写缓冲区的aof记录写入到这个临时的aof文件中,最后主进程再用该文件替换掉原文件
以上过程基本不影响主进程的正常工作,数据一致性很强,在每秒写入一次的持久化规则下,数据丢失的可能性和数量都大大减少,并且重写前的命令可以直接进行显式的撤回(例如误操作删库,只要重写未发生都可以停止redis进行恢复),缺点是启动慢(毕竟要执行一遍aof文件的命令)以及aof文件体积大于rdb
另外,不必过多担心aof文件的损坏,损坏的文件redis启动时会报错,并且官方提供了恢复工具,该工具会截掉从错误命令起后面的所有命令
测试
# 备份数据库
[root@cloudhost ~]# cd /var/lib/redis/
[root@cloudhost redis]# ls
dump.rdb
[root@cloudhost redis]# mv dump.rdb dump.rdb_bark
[root@cloudhost redis]# ls
dump.rdb_bark
[root@cloudhost redis]# redis-cli
# 清空数据库(一定保证前面备份了数据库)
# 开启aof持久化并进行数据写入操作
127.0.0.1:6379> SELECT 14
OK
127.0.0.1:6379[14]> FLUSHALL
OK
127.0.0.1:6379[14]> CONFIG GET appendonly
1) "appendonly"
2) "no"
127.0.0.1:6379[14]> CONFIG SET appendonly yes
OK
127.0.0.1:6379[14]> CONFIG GET appendonly
1) "appendonly"
2) "yes"
# 不要忘了把临时配置持久化到配置文件
127.0.0.1:6379[14]> CONFIG REWRITE
OK
127.0.0.1:6379[14]> SET k1 v1
OK
127.0.0.1:6379[14]> SET k2 v2
OK
127.0.0.1:6379[14]> SET k3 v3
OK
127.0.0.1:6379[14]> QUIT
# 满足持久化规则(超过了1秒),因此生成rdb文件
# 为什么也生成了新的rdb,因为两者可以并存,且并未关闭rdb持久化
[root@cloudhost redis]# ls
appendonly.aof dump.rdb dump.rdb_bark
# 查看aof文件,不难看出本质就是记录了每个写入操作,此外也包含数据库切换操作
[root@cloudhost redis]# cat appendonly.aof
*2 # 命令的参数总个数为2
$6 # 参数1的字节长度
SELECT # 参数的值1
$2 # 参数2的字节长度
14 # 参数的值2
*3
$3
SET
$2
k1
$2
v1
*3
$3
SET
$2
k2
$2
v2
*3
$3
SET
$2
k3
$2
v3
# 继续追加数据
[root@cloudhost redis]# redis-cli
127.0.0.1:6379> SELECT 14
OK
127.0.0.1:6379[14]> SELECT 15
OK
127.0.0.1:6379[15]> SET counter 0
OK
127.0.0.1:6379[15]> INCR counter
(integer) 1
127.0.0.1:6379[15]> INCR counter
(integer) 2
127.0.0.1:6379[15]> INCR counter
(integer) 3
127.0.0.1:6379[15]> QUIT
# 查看aof文件,发现重复的INCR操作并未合并,而切换数据库不进行写入操作,该切换操作也不记录
[root@cloudhost redis]# cat appendonly.aof
*2
$6
SELECT
$2
14
*3
$3
SET
$2
k1
$2
v1
*3
$3
SET
$2
k2
$2
v2
*3
$3
SET
$2
k3
$2
v3
*2
$6
SELECT
$2
15
*3
$3
SET
$7
counter
$1
0
*2
$4
INCR
$7
counter
*2
$4
INCR
$7
counter
*2
$4
INCR
$7
counter
# 对aof文件进行手动修改,修改后如下
[root@cloudhost redis]# cat appendonly.aof
*2
$6
SELECT
$2
14
*3
$3
SET
$2
k1
$2
v1
*3
$3
SET
$2
k2
$2
v2
*3
$3
SET
$2
k3
$2
v3
# 此处删除了切换数据库的操作
*3
$3
SET
$7
counter
$1
0
*2
$4
INCR
$7
counter
*2
$4
INCR
$7
count # 此处写了错误的键名
*2
$4
INCR
$7
counter
# 重启服务发现失败
[root@cloudhost redis]# systemctl restart redis
[root@cloudhost redis]# systemctl status redis
● redis.service - Redis persistent key-value database
Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/redis.service.d
└─limit.conf
Active: failed (Result: exit-code) since Sun 2025-05-18 06:18:06 CST; 1min 29s ago
Process: 12053 ExecStop=/usr/libexec/redis-shutdown (code=exited, status=0/SUCCESS)
Process: 12069 ExecStart=/usr/bin/redis-server /etc/redis.conf --supervised systemd (code=exited, status=1/FAILURE)
Main PID: 12069 (code=exited, status=1/FAILURE)
May 18 06:18:06 cloudhost systemd[1]: Starting Redis persistent key-value database...
May 18 06:18:06 cloudhost systemd[1]: Started Redis persistent key-value database.
May 18 06:18:06 cloudhost systemd[1]: redis.service: main process exited, code=exited, status=1/FAILURE
May 18 06:18:06 cloudhost systemd[1]: Unit redis.service entered failed state.
May 18 06:18:06 cloudhost systemd[1]: redis.service failed.
# 修复文件后再次启动,成功
[root@cloudhost redis]# redis-check-aof --fix appendonly.aof
0x bd: Expected \r\n, got: 2a32
AOF analyzed: size=223, ok_up_to=171, diff=52
This will shrink the AOF from 223 bytes, with 52 bytes, to 171 bytes
Continue? [y/N]: y
Successfully truncated AOF
[root@cloudhost redis]# systemctl start redis
[root@cloudhost redis]# systemctl status redis
● redis.service - Redis persistent key-value database
Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/redis.service.d
└─limit.conf
Active: active (running) since Sun 2025-05-18 06:23:25 CST; 5s ago
Process: 12053 ExecStop=/usr/libexec/redis-shutdown (code=exited, status=0/SUCCESS)
Main PID: 12358 (redis-server)
Tasks: 3
Memory: 4.2M
CGroup: /system.slice/redis.service
└─12358 /usr/bin/redis-server 127.0.0.1:6379
May 18 06:23:25 cloudhost systemd[1]: Starting Redis persistent key-value database...
May 18 06:23:25 cloudhost systemd[1]: Started Redis persistent key-value database.
# 查看aof文件发现counter位于14号数据库,
# 从错误count的命令起之后的内容被截断
# 可至数据库验证结果
[root@cloudhost redis]# cat appendonly.aof
*2
$6
SELECT
$2
14
*3
$3
SET
$2
k1
$2
v1
*3
$3
SET
$2
k2
$2
v2
*3
$3
SET
$2
k3
$2
v3
*3
$3
SET
$7
counter
$1
0
*2
$4
INCR
$7
counter
# 关闭aof持久化
[root@cloudhost redis]# redis-cli
127.0.0.1:6379> CONFIG SET appendonly no
OK
127.0.0.1:6379> CONFIG REWRITE
OK
# 必须先停止服务再恢复原数据库,顺序相反会造成恢复的文件被重启服务时保存的文件覆盖!!!
# 停止服务
[root@cloudhost redis]# systemctl stop redis
[root@cloudhost redis]# systemctl status redis
● redis.service - Redis persistent key-value database
Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/redis.service.d
└─limit.conf
Active: inactive (dead) since Sun 2025-05-18 06:54:27 CST; 9s ago
Process: 14055 ExecStop=/usr/libexec/redis-shutdown (code=exited, status=0/SUCCESS)
Process: 14018 ExecStart=/usr/bin/redis-server /etc/redis.conf --supervised systemd (code=exited, status=0/SUCCESS)
Main PID: 14018 (code=exited, status=0/SUCCESS)
May 18 06:53:54 cloudhost systemd[1]: Starting Redis persistent key-value database...
May 18 06:53:54 cloudhost systemd[1]: Started Redis persistent key-value database.
May 18 06:54:27 cloudhost systemd[1]: Stopping Redis persistent key-value database...
May 18 06:54:27 cloudhost systemd[1]: Stopped Redis persistent key-value database.
# 恢复原数据库
[root@cloudhost redis]# rm appendonly.aof
rm: remove regular file ‘appendonly.aof’? y
[root@cloudhost redis]# mv dump.rdb_bark dump.rdb
mv: overwrite ‘dump.rdb’? y
集成开发
Java
底层实现 | 原生 Socket 直连 | Netty 异步非阻塞 | 封装了 Jedis 或 Lettuce |
线程安全 | ❌ 不安全(需连接池) | ✅ 安全(自带连接池) | ✅ 安全(由底层客户端决定) |
连接复用 | 需要手动管理连接池 | 自带连接池,支持复用 | 通过配置选择 Jedis/Lettuce |
API 设计 | 接口直接对应 Redis 命令 | 支持同步/异步/响应式编程 | 提供更高级别的抽象 API |
集成 Spring | 可以集成但不如 Spring Data Redis 简洁 | 可以集成但不如 Spring Data Redis 简洁 | ✅ 最推荐的 Spring 整合方式 |
适合场景 | 轻量级项目、学习用途 | 高并发、分布式系统 | Spring Boot 项目首选 |
Jedis
依赖载入
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<!-- <version>3.2.0</version> -->
</dependency>
基本用法
// 进行简单的redis操作
import com.alibaba.fastjson2.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestJedis {
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379);) {
System.out.println(jedis.auth("123456"));
System.out.println(jedis.ping());
System.out.println(jedis.select(1));
System.out.println(jedis.dbSize());
String select = jedis.select(0);
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "ssydx");
jsonObject.put("age", 25);
String jsonString = jsonObject.toJSONString();
Transaction multi = jedis.multi();
multi.set("name", "ssydx");
multi.set("age", "25");
multi.set("user", jsonString);
multi.exec();
String name = jedis.get("name");
String age = jedis.get("age");
String user = jedis.get("user");
System.out.println(name);
System.out.println(age);
System.out.println(user);
jedis.flushDB();
jedis.quit();
}
}
}
Lettuce
采用netty,线程安全,自带连接池,天生支持连接复用
依赖载入
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<!-- <version>5.2.2.RELEASE</version> -->
</dependency>
基本用法
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
public class TestLettuce {
public static void main(String[] args) {
// 构建带认证信息的 URI
RedisURI redisURI = RedisURI.Builder.redis("localhost", 6379)
.withPassword("123456".toCharArray())
.build();
// 创建Redis客户端
RedisClient redisClient = RedisClient.create(redisURI);
// 获取连接
StatefulRedisConnection<String, String> connection = redisClient.connect();
// 获取同步命令
RedisCommands<String, String> syncCommands = connection.sync();
// 执行一些命令
syncCommands.set("key", "Hello, Redis!");
System.out.println(syncCommands.get("key"));
// 关闭连接
connection.close();
// 关闭客户端
redisClient.shutdown();
}
}
Spring Data Redis
载入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- <version>3.4.5</version> -->
</dependency>
配置类
// 如果不进行自定义RedisTemplate,对象存储会采用Java序列化机制,POJO类必须实现IMPLEMENT接口
// Java序列化机制还会造成数据库乱码(不可直接阅读),因此推荐使用JSON序列化机制
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// 配置序列化器
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(jackson2JsonRedisSerializer);
return template;
}
}
POJO类
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
// 由于使用了Jackson的序列化,此处不序列化也行
public class User implements Serializable {
private String name;
private Integer age;
}
基本用法
// 仅作为演示,实际通常会包装一个redis工具类,而不是直接使用template
import java.util.Properties;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@SpringBootTest
public class TestRedis {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Test
void test() {
System.out.println("RedisTemplate: " + stringRedisTemplate);
String requirepasswd = stringRedisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
Properties config = connection.getConfig("requirepass");
return config.getProperty("requirepass");
}
});
System.out.println(requirepasswd);
stringRedisTemplate.opsForValue().set("name", "ssydx");
stringRedisTemplate.opsForList().leftPush("ls", "ssydx");
stringRedisTemplate.opsForSet().add("set", "ssydx");
stringRedisTemplate.opsForHash().put("hash", "name", "ssydx");
stringRedisTemplate.opsForZSet().add("zset", "ssydx", 1);
Set<String> keys = stringRedisTemplate.keys("*");
System.out.println(keys);
stringRedisTemplate.execute(new RedisCallback<Void>() {
@Override
public Void doInRedis(RedisConnection connection) throws DataAccessException {
connection.flushDb();
return null;
}
});
User user = new User("ssydx", 25);
redisTemplate.opsForValue().set("user", user);
System.out.println(redisTemplate.opsForValue().get("user"));
redisTemplate.delete("user");
}
}
其他命令
SHUTDOWN
# SHUTDOWN [NOSAVE|SAVE]
[root@cloudhost ~]# ps -ef | grep redis
redis 14722 1 0 22:10 ? 00:00:00 /usr/bin/redis-server 127.0.0.1:6379
root 14839 14321 0 22:12 pts/1 00:00:00 grep --color=auto redis
[root@cloudhost ~]# redis-cli
# 异步保存数据到硬盘,并关闭服务器
# 可选参数SAVE(强制保存后关闭) NOSAVE(强制不保存关闭)
127.0.0.1:6379> SHUTDOWN
not connected> exit
[root@cloudhost ~]# ps -ef | grep redis
root 14887 14321 0 22:13 pts/1 00:00:00 grep --color=auto redis
INFO
# INFO [Section]
# 获取 Redis 服务器的各种信息和统计数值
# 获取全部
127.0.0.1:6379> INFO
# Server
redis_version:3.2.12 # Redis版本
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:7897e7d0e13773f
redis_mode:standalone # 模式,当前为单机运行
os:Linux 3.10.0-1160.119.1.el7.x86_64 x86_64 # 内核信息
arch_bits:64
multiplexing_api:epoll
gcc_version:4.8.5
process_id:17252 # 进程id
run_id:0f0445514ecab58555f6c44d6fc4473a1acc8535
tcp_port:6379 # 端口
uptime_in_seconds:586 # 启动时长
uptime_in_days:0 # 启动时长
hz:10
lru_clock:2696232
executable:/usr/bin/redis-server # 二进制可执行文件位置
config_file:/etc/redis.conf # 配置文件位置
# Clients
connected_clients:1 # 当前连接该服务器的客户端数量
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
# Memory
used_memory:813528
used_memory_human:794.46K
used_memory_rss:5918720
used_memory_rss_human:5.64M
used_memory_peak:813528
used_memory_peak_human:794.46K
total_system_memory:1841668096 # 系统内存大小
total_system_memory_human:1.72G # 系统内存大小
used_memory_lua:37888
used_memory_lua_human:37.00K
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
mem_fragmentation_ratio:7.28
mem_allocator:jemalloc-3.6.0
# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1747526110
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:-1
rdb_current_bgsave_time_sec:-1
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
# Stats
total_connections_received:1
total_commands_processed:2
instantaneous_ops_per_sec:0
total_net_input_bytes:60
total_net_output_bytes:9991
instantaneous_input_kbps:0.00
instantaneous_output_kbps:0.00
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:0
migrate_cached_sockets:0
# Replication
role:master
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
# CPU
used_cpu_sys:0.21
used_cpu_user:0.15
used_cpu_sys_children:0.00
used_cpu_user_children:0.00
# Cluster
cluster_enabled:0
# Keyspace
# 获取指定部分
127.0.0.1:6379> INFO Server
# Server
redis_version:3.2.12
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:7897e7d0e13773f
redis_mode:standalone
os:Linux 3.10.0-1160.119.1.el7.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.8.5
process_id:17252
run_id:0f0445514ecab58555f6c44d6fc4473a1acc8535
tcp_port:6379
uptime_in_seconds:633
uptime_in_days:0
hz:10
lru_clock:2696279
executable:/usr/bin/redis-server
config_file:/etc/redis.conf
CLIENT LIST
# 获取连接到服务器的客户端连接列表
127.0.0.1:6379> CLIENT LIST
id=2 addr=127.0.0.1:54824 fd=5 name= age=61 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
id=3 addr=127.0.0.1:54836 fd=6 name= age=4 idle=4 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=command
CLIENT SETNAME|CLIENT GETNAME
127.0.0.1:6379> CLIENT SETNAME redis01
OK
127.0.0.1:6379> CLIENT GETNAME
"redis01"
DEBUG SEGFAULT
# 让 Redis 服务崩溃
127.0.0.1:6379> DEBUG SEGFAULT
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> QUIT
TIME
# 获取服务器时间戳
127.0.0.1:6379> TIME
1) "1747526185"
2) "884419"
其他命令
CLIENT PAUSE 在指定时间内终止运行来自客户端的命令
COMMAND 获取 Redis 命令详情数组
COMMAND COUNT 获取 Redis 命令总数
COMMAND GETKEYS 获取给定命令的所有键
COMMAND INFO 获取指定 Redis 命令描述的数组
CONFIG RESETSTAT 重置 INFO 命令中的某些统计数据
DEBUG OBJECT 获取 key 的调试信息
MONITOR 实时打印出 Redis 服务器接收到的命令,调试用
SLOWLOG 管理 redis 的慢日志
服务部署
部署时要特别注意细节(尤其是配置文件的修改)
部署相关命令
ROLE
# 返回主从实例所属的角色
127.0.0.1:6379> ROLE
1) "master"
2) (integer) 0
3) (empty list or set)
SLAVEOF
# 将当前服务器转变从节点(slave server)
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
# 将当前服务器转变为主节点
127.0.0.1:6380> SLAVEOF NO ONE
OK
SYNC
# 用于从主节点进行一次全量复制
127.0.0.1:6380> SYNC
Entering slave output mode... (press Ctrl-C to quit)
SYNC with master, discarding 183 bytes of bulk transfer...
SYNC done. Logging commands from master.
"PING"
^C
[root@cloudhost ~]# redis-cli -p 6380
CLUSTER SLOTS
# CLUSTER SLOTS 获取集群节点的映射数组
主从复制
特点
主从复制是指将一台redis-server中的数据复制到其他redis-server中。前者被称为主节点,后者被称为从节点。主节点只能有一个,从节点可以有任意个(通常至少有两个)
默认情况下每台服务器都是主节点
默认情况下主节点可以读写,从节点只读。借助redisson等中间件可以实现读写分离(自动路由)
从机启动时,通过全量复制进行数据同步
从机运行中,主机中进行的写入操作会通过增量复制进行数据同步
用途:冗余备份,读写分离,手动故障转移
配置
# 在同一台主机上配置一主二从,模拟生产环境
# 实际生产中通常布置在不同主机上
# 复制配置文件
[root@cloudhost ~]# cd /etc/
[root@cloudhost etc]# ls | grep redis
redis.conf
redis-sentinel.conf
[root@cloudhost etc]# cp redis.conf redis-79.conf
[root@cloudhost etc]# cp redis.conf redis-80.conf
[root@cloudhost etc]# cp redis.conf redis-81.conf
[root@cloudhost etc]# ls | grep redis
redis-79.conf
redis-80.conf
redis-81.conf
redis.conf
redis-sentinel.conf
# 编辑配置文件
# 开启守护运行
# 修改端口号分别为6379、6380、6381
# 修改pid文件为redis_6379、redis_6380、redis_6381
# 修改log文件为redis-6379、redis-6380、redis-6381
# 修改rdb文件为dump-6379、dump-6380、dump-6381
# 如果开启了aof还需要修改aof文件
# 配置主从机(后面采用redis命令进行临时配置)
# 如果主机有密码,需要修改配置文件或CONFIG SET masterauth yourmasterpassword
# 其他如需配置自行进行
[root@cloudhost ~]# cd /usr/bin
[root@cloudhost bin]# ls | grep redis
redis-benchmark
redis-check-aof
redis-check-rdb
redis-cli
redis-sentinel
redis-server
# 根据指定配置文件启动redis
[root@cloudhost bin]# redis-server /etc/redis-79.conf
[root@cloudhost bin]# redis-server /etc/redis-80.conf
[root@cloudhost bin]# redis-server /etc/redis-81.conf
# 查看启动情况
[root@cloudhost bin]# ps -ef | grep redis
root 9419 1 0 00:49 ? 00:00:00 redis-server 127.0.0.1:6379
root 9427 1 0 00:49 ? 00:00:00 redis-server 127.0.0.1:6380
root 9433 1 0 00:49 ? 00:00:00 redis-server 127.0.0.1:6381
root 9440 7415 0 00:49 pts/0 00:00:00 grep --color=auto redis
# 在Xshell不同窗口下分别进入三台服务器对应的客户端
[root@cloudhost ~]# redis-cli -p 6379
[root@cloudhost ~]# redis-cli -p 6380
[root@cloudhost ~]# redis-cli -p 6381
# 配置从节点
# 6380这个客户端执行命令,配置后该主机不能再进行写入操作
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6380> ROLE
1) "slave"
2) "127.0.0.1"
3) (integer) 6379
4) "connected"
5) (integer) 1122
127.0.0.1:6380> INFO replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:926
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6380> SET name ssydx
(error) READONLY You can't write against a read only slave.
# 6381同理
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6381> ROLE
1) "slave"
2) "127.0.0.1"
3) (integer) 6379
4) "connected"
5) (integer) 1136
127.0.0.1:6381> INFO replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:6
master_sync_in_progress:0
slave_repl_offset:996
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6381> SET name ssydx
(error) READONLY You can't write against a read only slave.
# 查看6379这个客户端的从机信息,发现存在两个从机,说明主从复制设置成功
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=29,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=29,lag=1
master_repl_offset:29
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:28
127.0.0.1:6379> ROLE
1) "master"
2) (integer) 1136
3) 1) 1) "127.0.0.1"
2) "6380"
3) "1136"
2) 1) "127.0.0.1"
2) "6381"
3) "1136"
# 写入操作成功
127.0.0.1:6379> SET name ssydx
OK
# 读取操作成功
127.0.0.1:6379> GET name
"ssydx"
# 两个从机读取也成功
127.0.0.1:6380> GET name
"ssydx"
127.0.0.1:6381> GET name
"ssydx"
# 关闭主机
127.0.0.1:6379> SHUTDOWN SAVE
not connected> QUIT
# 查看从机,依然是从机,只是与主机断联
127.0.0.1:6380> ROLE
1) "slave"
2) "127.0.0.1"
3) (integer) 6379
4) "connect"
5) (integer) -1
127.0.0.1:6381> ROLE
1) "slave"
2) "127.0.0.1"
3) (integer) 6379
4) "connect"
5) (integer) -1
# 如果主机恢复,从机自动连接,可自行测试
# 一个节点B除了是从节点,也可以是其他节点C的主节点,这就是链式节点
# 当节点B的主节点A没有出现故障时,B仍然是只读的,C则基于B进行同步
# 当节点A宕机时,可以手动把B的父节点设置为空:SLAVEOF no one,从而使B成为主节点,C继续基于B进行同步
# 当节点A回归时,可以手动将其父节点设置为B,注意:切不可重新让B成为A的从节点,会造成A节点宕机期间的数据丢失
# 以上为手动的故障转移,不够灵活,哨兵模式可解决这个问题
哨兵配置文件
# 哨兵绑定的网址,默认只能通过本机访问
# bind 127.0.0.1 192.168.1.1
# 是否启用保护模式
# protected-mode no
# 哨兵监听的端口
port 26379
# 哨兵的id,通常删除,让哨兵启动时自动生成
sentinel myid c0111f20747e5d48513af52f763605c9ce82cbb7
# 主从节点的密码认证设置
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 下线阈值,超过这个时间则认为下线
# sentinel down-after-milliseconds <master-name> <milliseconds>
# 核心配置,确定监视的初始主节点的名称(仅用于哨兵)、ip、端口、多少哨兵同意才确认下线
sentinel monitor mymaster 127.0.0.1 6379 2
# 切换时最多有多少从节点可以同时从新主节点上获取最新数据,值越小整个同步过程越长但同时不可读的从节点也越少,反之亦然
# 同步时从节点是不可读的
# sentinel parallel-syncs <master-name> <numslaves>
# 故障转移的最大等待时间(毫秒),超时后将强制完成切换
# sentinel failover-timeout <master-name> <milliseconds>
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port> 这些参数会被传递给脚本
# 通知脚本
# sentinel notification-script <master-name> <script-path>
# 客户端重配置脚本
# sentinel client-reconfig-script <master-name> <script-path>
# 日志文件路径
logfile "/var/log/redis/sentinel.log"
哨兵模式
特点
哨兵模式是基于主从复制的,哨兵会对各个节点进行监视,哨兵之间也会进行监视,哨兵应该具有奇数个以避免脑裂(实际往往是三个)
每个哨兵都会独立地进行主节点是否宕机的判断,当认为主节点宕机的哨兵过半时就把主节点视为宕机(这么设计是为了避免网络波动等造成单个哨兵的误判)
已经判断主节点宕机后,哨兵之间会进行投票(1.基于配置文件中的slaver-priority非0最小值 2.基于复制进度 3.基于run_id字典序最小)选择一个从节点作为主节点,投票完成后由其中一个哨兵通过发布订阅机制进行公布,其他哨兵也将自己监视的主节点更新为新选出的主节点
单哨兵的下线判断称为主观下线,过半哨兵的下线判断称为客观下线
用途:读写分离,冗余备份,自动故障转移
配置
# 前置条件,在同一台主机上配置一主二从,模拟生产环境,参考主从复制的配置
# 在同一台主机上配置三个哨兵,模拟生产环境
# 复制配置文件
[root@cloudhost etc]# cp redis-sentinel.conf redis-sentinel-79.conf
[root@cloudhost etc]# cp redis-sentinel.conf redis-sentinel-80.conf
[root@cloudhost etc]# cp redis-sentinel.conf redis-sentinel-81.conf
[root@cloudhost etc]# ls | grep redis
redis-79.conf
redis-80.conf
redis-81.conf
redis.conf
redis.conf_bark
redis-sentinel-79.conf
redis-sentinel-80.conf
redis-sentinel-81.conf
redis-sentinel_bark.conf
redis-sentinel.conf
# 编辑配置文件
# 如果存在形如 sentinel myid c0111f20747e5d48513af52f763605c9ce82cbb7 的行,将其删除或者对不同配置文件值设置不同的值
# sentinel monitor mymaster 127.0.0.1 6380 2
# mymaster为主节点的名称(不重要,自行设置),127.0.0.1 6380为初始的主节点的IP及端口,2指的是至少2个哨兵同意才视为整体同意
# 修改端口号分别为26379、26380、26381
# 修改log文件为sentinel-26379.log、sentinel-26380.log、sentinel-26381.log
# 如果服务器有密码(请保证主从节点密码相同),修改配置文件sentinel auth-pass mymaster masterpassword
# 其他如需配置自行进行
[root@cloudhost ~]# cd /usr/bin
[root@cloudhost bin]# ls | grep redis
redis-benchmark
redis-check-aof
redis-check-rdb
redis-cli
redis-sentinel
redis-server
# 根据指定配置文件运行并放入后台运行(注意&)
[root@cloudhost etc]# redis-sentinel /etc/redis-sentinel-79.conf &
[1] 19650
[root@cloudhost etc]# redis-sentinel /etc/redis-sentinel-80.conf &
[2] 19658
[root@cloudhost etc]# redis-sentinel /etc/redis-sentinel-81.conf &
[3] 19665
# 确认三个哨兵都已启动
[root@cloudhost etc]# redis-cli -p 26379 ping
PONG
[root@cloudhost etc]# redis-cli -p 26380 ping
PONG
[root@cloudhost etc]# redis-cli -p 26381 ping
PONG
# 确认三个哨兵可以互相发现
[root@cloudhost etc]# redis-cli -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6380,slaves=2,sentinels=3
[root@cloudhost etc]# redis-cli -p 26380 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6380,slaves=2,sentinels=3
[root@cloudhost etc]# redis-cli -p 26381 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6380,slaves=2,sentinels=3
# 将主节点6379的服务器关闭
# 等待片刻,查看另外两个服务器的角色,有一位被切换为主节点,另一位则成为它的从节点
# 通过ROLE查看未更新时,可退出客户端重新进入或执行CONFIG RESTART,实在不行可进行数据写入判断哪个是主节点,或者查看哨兵日志
# 将6379节点重新上线,其自动成为从节点,无需手动干预
# 此时客户端,服务器,哨兵均有三个
[root@cloudhost etc]# ps -ef | grep redis
root 19537 7591 0 04:04 pts/2 00:00:00 redis-cli -p 6380
root 21409 7565 0 04:41 pts/1 00:00:00 redis-cli -p 6379
root 21773 7619 0 04:48 pts/3 00:00:00 redis-cli -p 6381
root 21378 1 0 04:41 ? 00:00:00 /usr/bin/redis-server 127.0.0.1:6379
root 21439 1 0 04:42 ? 00:00:00 /usr/bin/redis-server 127.0.0.1:6380
root 21445 1 0 04:42 ? 00:00:00 /usr/bin/redis-server 127.0.0.1:6381
root 19650 7415 0 04:06 pts/0 00:00:03 /usr/bin/redis-sentinel *:26379 [sentinel]
root 19658 7415 0 04:06 pts/0 00:00:03 /usr/bin/redis-sentinel *:26380 [sentinel]
root 19665 7415 0 04:07 pts/0 00:00:03 /usr/bin/redis-sentinel *:26381 [sentinel]
root 21801 16163 0 04:49 pts/4 00:00:00 grep --color=auto redis
# 强制关闭一个哨兵
[root@cloudhost etc]# kill 19650
[root@cloudhost etc]# ps -ef | grep redis
root 19537 7591 0 04:04 pts/2 00:00:00 redis-cli -p 6380
root 19658 7415 0 04:06 pts/0 00:00:03 /usr/bin/redis-sentinel *:26380 [sentinel]
root 19665 7415 0 04:07 pts/0 00:00:03 /usr/bin/redis-sentinel *:26381 [sentinel]
root 21378 1 0 04:41 ? 00:00:00 /usr/bin/redis-server 127.0.0.1:6379
root 21409 7565 0 04:41 pts/1 00:00:00 redis-cli -p 6379
root 21439 1 0 04:42 ? 00:00:00 /usr/bin/redis-server 127.0.0.1:6380
root 21445 1 0 04:42 ? 00:00:00 /usr/bin/redis-server 127.0.0.1:6381
root 21773 7619 0 04:48 pts/3 00:00:00 redis-cli -p 6381
root 21872 16163 0 04:50 pts/4 00:00:00 grep --color=auto redis
# 再次进行主节点下线测试,仍能正常进行故障转移
# 因为前面设置了只要两个哨兵同意即可
# 如果进一步关闭哨兵将无法再进行自动转移
集群模式
特点
集群模式是基于主从复制和哨兵模式的,内部已经封装好了故障检测和故障转移的逻辑,不需要单独设置哨兵,其自有一套配置方法
多个主节点会分别存储部分数据,这就是数据分片
从节点会在主节点宕机后通过投票选择其中一个升级为主节点
当执行写入操作或读取操作时会通过一致性哈希算法自动路由到对应的主从节点上
注意:其不能执行跨分区的命令,例如KEYS *
用途:读写分离,数据冗余,故障转移,数据分片(水平扩展)
配置
# 准备三主三从节点
# 对每个节点编写配置文件(还是基于redis.conf)
port 6379 # 每个节点的端口号需不同
# 开启集群模式
cluster-enabled yes
# 集群的配置文件名
cluster-config-file nodes.conf
# 节点超时时间
cluster-node-timeout 5000
# 最好开启aof以实现更好的数据一致性
appendonly yes
# 在任意一台主机上执行集群创建
redis-cli --cluster create \
172.0.0.1:7000 172.0.0.1:7002 172.0.0.1:7004 \
172.0.0.1:7001 172.0.0.1:7003 172.0.0.1:7005 \
--cluster-replicas 1
# 访问任意主机,在客户端内执行写入和读取操作均可,redis会自动进行路由
Lua脚本
命令
SCRIPT KILL 杀死当前正在运行的 Lua 脚本。
SCRIPT LOAD 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。
EVAL 执行 Lua 脚本。
EVALSHA 执行 Lua 脚本。
SCRIPT EXISTS 查看指定的脚本是否已经被保存在缓存当中。
SCRIPT FLUSH 从脚本缓存中移除所有脚本。
TODO
常见问题
缓存穿透
问题
数据查询时未在redis缓存中命中时将会访问sql数据库,如果存在恶意用户高频访问数据库中也不存在的数据,将会造成数据库的压力持续存在,这就是缓存穿透
本质是缓存未命中
方案
1.不存在的数据也在redis缓存中设置对应的缓存,值设置为空字符串并设置过期时间(依然存在两个问题:1.仍然会消耗redis缓存存储这些无用的键,2.即使有过期时间,也会造成一定的时间窗口,不适合需要强一致性的业务)
2.使用布隆过滤器,对所有可能的查询参数以hash形式存储,在控制层进行校验,不符合要求的参数直接拒绝
缓存击穿
问题
存在一些键非常热门,持续接受着高并发,这些键在过期的瞬间,高并发的请求会集中访问数据库,造成数据库瞬时压力暴增,这就是缓存击穿
例如:各平台的热搜榜
本质是并发量过高和缓存过期
方案
1.设置热点数据永不过期
2.限制并发,使用分布式锁确保同一时刻只能有一个请求访问数据库,对于没获得锁的线程将进行阻塞(分布式锁实现较麻烦,且分布式锁将承受这部分高并发压力),也可以借助消息队列进行控制
缓存雪崩
问题
集中设置的热点键缓存具有接近的过期时间,当达到过期时间时,大批热点键在接近的时刻过期,相关请求将全部访问数据库,造成数据库压力周期性暴增,或者缓存服务器宕机造成缓存数据丢失,这就是缓存雪崩
例如:电商平台的秒杀活动,个别缓存服务器宕机断网
本质是缓存批量过期或缓存服务器宕机
方案
1.设置备用节点
2.加分布式锁或消息队列控制对数据库的并发访问量
3.数据预热,提前把热点数据访问一遍以加载到redis缓存中 或 在高并发访问时手动触发设置不同的过期时间使失效时间尽可能分散
分布式锁
看门狗机制、红锁
其他
性能测试
# 假定并发量为100,测试100000次请求
[root@cloudhost bin]# redis-benchmark -h localhost -p 6379 -c 100 -n 100000
====== PING_INLINE ======
100000 requests completed in 1.20 seconds
100 parallel clients
3 bytes payload
keep alive: 1
98.82% <= 1 milliseconds
99.89% <= 2 milliseconds
100.00% <= 3 milliseconds
100.00% <= 3 milliseconds
83402.84 requests per second
====== PING_BULK ======
100000 requests completed in 1.20 seconds
100 parallel clients
3 bytes payload
keep alive: 1
99.15% <= 1 milliseconds
99.99% <= 2 milliseconds
100.00% <= 2 milliseconds
83125.52 requests per second
# SET测试,负载3个字节,平均每秒处理83125.52个请求
====== SET ======
100000 requests completed in 1.20 seconds
100 parallel clients
3 bytes payload
keep alive: 1
98.55% <= 1 milliseconds
99.90% <= 2 milliseconds
100.00% <= 2 milliseconds
83125.52 requests per second
====== GET ======
100000 requests completed in 1.19 seconds
100 parallel clients
3 bytes payload
keep alive: 1
99.22% <= 1 milliseconds
99.88% <= 2 milliseconds
99.90% <= 3 milliseconds
99.95% <= 4 milliseconds
100.00% <= 4 milliseconds
84245.99 requests per second
...
====== MSET (10 keys) ======
100000 requests completed in 1.28 seconds
100 parallel clients
3 bytes payload
keep alive: 1
63.38% <= 1 milliseconds
99.45% <= 2 milliseconds
99.91% <= 3 milliseconds
99.97% <= 4 milliseconds
100.00% <= 4 milliseconds
78064.01 requests per second
#Redis#本专栏包含Java、Linux、MySQL、Redis、RabbitMQ、Docker、HTML、CSS、JS等等,作为个人学习记录及知识总结,将长期进行更新!