Redis 安装与基础配置
Linux 下安装
官网:http://redis.io/
中文官网:http://redis.cn/
1、下载安装包,官网首页下载;redis-6.2.6.tar.gz
;
2、解压 redis 的安装包,使用 xftp、winscp 等软件上传至服务器,一般上传至 /opt
文件夹下;
3、解压压缩包,tar -zxvf redis-6.2.6.tar.gz
;
4、基本环境安装
注意: redis 6 安装需要 gcc 版本 5.3 以上,Centos 7 默认安装的 gcc 版本是 4.85,需要手动升级;
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash
# 安装 C++
yum install gcc-c++
# 检查 GCC 版本,确保已安装
gcc -v
# 编译环境
make
# 执行以确认编译完成
make install
5、redis 默认安装路径 usr/local/bin
6、将 redis 配置文件复制到当前目录下,之后使用这个配置文件去启动 redis;
7、redis 默认不是后台启动的,修改配置文件,vim redis.conf
;
8、启动 redis 服务;
若连接不上可以尝试以下操作
1、控制台放通 redis 端口
2、关闭服务器防火墙systemctl stop iptables
9、使用 redis-cli 进行连接测试;
10、查看redis进程是否开启;
11、关闭 redis 服务;
12、再次查看进程是否存在;
13、后面会使用单机多 redis 启动 集群测试
测试性能
redis-benchmark 是一个 redis 官方自带的性能测试(压力测试)工具
redis-benchmark 可选参数如下所示:
序号 选项 描述 默认值 1 -h 指定服务器主机名 127.0.0.1 2 -p 指定服务器端口 6379 3 -s 指定服务器 socket 4 -c 指定并发连接数 50 5 -n 指定请求数 10000 6 -d 以字节的形式指定 SET/GET 值的数据大小 2 7 -k 1=keep alive 0=reconnect 1 8 -r SET/GET/INCR 使用随机 key, SADD 使用随机值 9 -P 通过管道传输 请求 1 10 -q 强制退出 redis。仅显示 query/sec 值 11 –csv 以 CSV 格式输出 12 -l(L 的小写字母) 生成循环,永久执行测试 13 -t 仅运行以逗号分隔的测试命令列表。 14 -I(i 的大写字母) Idle 模式。仅打开 N 个 idle 连接并等待。
测试:
# 测试:100个并发连接 100000次请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
查看分析结果:
基础的知识
redis 默认有 16 个数据库,redis.conf
配置文件中写的有 database 16
默认使用的是第 0 个数据库,可以使用 select 进行切换
127.0.0.1:6379> select 3 # 切换数据库
OK
127.0.0.1:6379[3]> DBSIZE # 查看数据库大小
(integer) 0
127.0.0.1:6379[3]> keys * # 查看当前数据库所有的 key
1) "name"
清空当前数据库 flushdb
,清空所有数据库 flushall
127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty array)
思考:为什么 redis 选择 6379 端口号
答: 点击查看答案
redis 是单线程的!
redis 是基于内存操作的,CPU 不是 redis 的瓶颈,redis 的瓶颈是根据机器内的内存和网络带宽;既然可以使用单线程实现,就是用单线程了
redis 是 C 语言写的,官方提供的数据为 100000+ 的 QPS,完全不比同样是使用 key-value 的 Memecache 差!
redis 为什么单线程还这么快?
1、误区1:高性能的服务器一定是多线程的?
2、误区2:多线程(CPU)一定比单线程效率高!
读取速度: CPU > 内存 > 硬盘
核心: redis 是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU 上下文会切换:耗时的操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个 CPU 上的,在内存情况下,这个就是最佳的方案!
数据类型
基本数据类型
1、Redis-key
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
redis 常用命令:
注意:redis 不区分大小写命令
set key value
:设置键值对 key-value
get key
:根据键名 key 获取对应的值 value
del key
:删除键 key 及其对应的值,返回值 integer
keys pattern
:列出所有符合给定模式 pattern 的 keypattern 对应的模式有
*
:表示任意多个字符?
:表示一个任意字符[ab]
:表示字符 a 或字符 b
move key db
:将键值对 key 从数据库n(当前数据库)移动到数据库db(0<=db, n<=15且db!=n)
,返回值 integer
exists key
:查看键 key 是否存在
expire key seconds
:设置键 key 的有效期(单位:秒),过期自动删除
ttl key
:# 查看键 key 的剩余存活时间
flushdb
:清空当前数据库
flushall
:清空所有数据库
select db
:从数据库n(当前数据库)切换到到数据库db(0<=db, n<=15且db!=n)
type key
:查看键 key 的类型更多命令参考:redis 中文官网
2、String(字符串)
String 相关的命令:
append key value
:若键 key 存在,则向键 key 中追加值 value;若键 key 不存在,则等同于set key value
;返回值为追加后的值 value 的长度
strlen key
:获取键 key 对应的值 value 的长度以下几个命令需要键 key 对应的值 value 为数字方可执行,否则会报错
(error) ERR value is not an integer or out of range
incr key
:自增 1
decr key
:自减 1
incrby key increment
:以步长 increment 自增
decrby key increment
:以步长 increment 自减
getrange key start end
:截取键 key 对应的值 value 从 start 开始到 end 结束的字符串[start, end]127.0.0.1:6379> get k1 "hello,your mather boom!" 127.0.0.1:6379> getrange k1 0 100 "hello,your mather boom!" 127.0.0.1:6379> getrange k1 0 -1 "hello,your mather boom!" 127.0.0.1:6379> getrange k1 0 10 "hello,your "
setrange key offset value
:替换键 key 对应的值 value 从指定位置开始的自负床为 value127.0.0.1:6379> set k2 abcdefg OK 127.0.0.1:6379> setrange k2 1 xx (integer) 7 127.0.0.1:6379> get k2 "axxdefg"
setex key seconds value
:setex(set with expire)
设置键值对 key-value 并为其设置过期时间 seconds 秒
- 若键值对存在直接覆盖
setnx key value
:setnx(set if not exists)
当键值对不存在时设置键值对 key-value,在分布式锁中经常使用
- 若键值对存在则创建失败
mset key1 value1 key2 value2 [key ...]
:批量创建键值对
mget key1 key2 [key ...]
:批量获取指定的键对应的值
msetnx key1 value1 key2 value2 [key ...]
:该操作是一个原子性的操作,若给定的键值对中有一个键存在,则整个创建操作均失败
- 假设 key1 存在,key2 不存在,执行命令后,key2 并未创建成功
# 对象 # 设置一个 user:1 对象,值为 json 字符来保存一个对象 127.0.0.1:6379> set user1: {name:XBai,age:3} OK # 这里的 key 是一个巧妙地设计:user:{id}:{filed} 127.0.0.1:6379> mset user:1:name XBai user:1:age 2 OK 127.0.0.1:6379> mget user:1:name user:1:age 1) "XBai" 2) "2"
组合命令
getset key value
:先调用 get 方法,在调用 set 方法# 如果不存在值则返回nil 127.0.0.1:6379> getset db redis (nil) 127.0.0.1:6379> get db "redis" # 如果存在值则获取原来的值,并设置为新的值 127.0.0.1:6379> getset db mongodb "redis" 127.0.0.1:6379> get db "mongodb"
3、List(列表)
可以用 List 模拟 栈、队列、阻塞队列!
所有的 List 命令都是 L 开头的
列表实际上是一个链表,在两边插入或改动值,效率最高
常用命令
lpush key element
:将一个值或多个值插入到列表 key 头部(左)rpush key element
:将一个值或多个值插入到列表 key 尾部(右)lrange key start stop
:通过区间[start, stop]获取列表 key 中的值lpop key
:移除列表 key 头部的元素(左)rpop key
:移除列表 key 尾部的元素(右)lindex key index
:获取列表 key 的 index 元素,index 从零开始计数llen key
:返回列表 key 的长度lrem key count element
:从列表 key 中移除 count 个元素 element
- 注意:列表中的元素可以重复
ltrim key start stop
:通过下标截取列表 [start, stop] 的部分,在原列表上做出改变lset key index element
:将列表 list 中 index 下标的值替换为 elementlinsert key before|after pivot value
:在列表 list 指定的值 pivot 前面/后面插入指定的值 value
- 当指定的值 pivot 有多个时,选择第一个匹配到的值进行插入
组合命令
rpoplpush resource destination
:移除列表的最后一个元素并将该元素添加至一个新的列表中127.0.0.1:6379> rpoplpush list newlist "hello2" 127.0.0.1:6379> lrange list 0 -1 1) "hello" 2) "hello1" 127.0.0.1:6379> lrange newlist 0 -1 1) "hello2"
# 将一个值或多个值插入到列表头部(左)
127.0.0.1:6379> LPUSH list one
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
# 获取 list 中的值
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
# 通过区间[0, 1]获取 list 中的值
127.0.0.1:6379> LRANGE list 0 1
1) "three"
2) "two"
# 将一个值或多个值插入到列表尾部(右)
127.0.0.1:6379> RPUSH list right
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list # 移除列表的第一个元素
"three"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> rpop list # 移除列表的最后一个元素
"right"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
4、Set(集合)
- set 中的值是不能重读的
- set 是无需不重复集合
常用命令
sadd key member
:向集合 key 中添加成员 member
- 添加成功:返回1
- 添加失败:返回0
srem key member
:从集合 key 中移除成员 member
- 移除成功:返回1
- 移除失败:返回0
members key
:列出集合 key 中的所有成员
scard key
:查找集合 key 中成员个数
sismember key member
:查找集合 key 中是否存在成员 member
- 存在:返回 1
- 不存在:返回 0
srandmember key [count=1]
:从集合 key 中随机抽选出 count 个元素
spop key [count=1]
:从集合 key 中随机删除 count 个元素
smove source destination member
:从集合 source 中移除元素 member 并将其移入集合 destination数学集合类命令
sdiff key1 key2
:查看集合 key1 中有但是集合 key2 没有的那部分成员(差集)
- 相当于 k e y 1 ∪ k e y 2 − k e y 2 key1 \cup key2 - key2 key1∪key2−key2
sinter key1 key2
:查看集合 key1 与 集合 key2 相同部分的成员(交集)
- 相当于 k e y 1 ∩ k e y 2 key1 \cap key2 key1∩key2
sunion key1 key2
:查看集合 key1 与 集合 key2 所有的成员(并集)
5、Hash(散列)
- map 集合,相当于 key-map<key, value>
- hash 更适合于对象的存储
常用命令
hset key field value [field value]
:向哈希集合 key 中添加键值对 field-valuehget key field
:获取哈希集合 key 中的field属性的值hmset key field1 value1 field2 value2 [field value ...]
:向哈希集合 key 中添加多个键值对 field-value
- 目前 hset 也可以添加多个键值对
hmget key field1 field2 [field ...]
:获取哈希集合 key中的多个字段 field 的值hdel key field [field ...]
:删除哈希集合 key 中的字段 fieldhlen key
:获取哈希集合 key 中的字段数量hexists key field
:判断哈希集合 key 中是否存在字段 field
- 存在:返回 1
- 不存在:返回 0
hgetall key
:获取全部的数据hkeys key
:获取哈希集合 key 中所有的字段 fieldhvals key
:获取哈希集合 key 中所有字段的值hincrby key field increment
:以步长 increment 增加哈希集合 key 中的 field 字段的值
- 当 increment 的值 < 0 时,等同于自减
hsetnx key field value
:当哈希集合 key 中键值对 field-value 不存在时创建该键值对
- 创建成功:返回 1
- 创建失败:返回 0
6、Zset(有序集合)
- 可以进行set集合排序,成绩表、工资表排序等
- 带权重进行判断,如消息等级(重要、一般、不重要)
常用命令
zadd key [NX|XX] [GT|LT] [CH] [INCR] score member [score member]
:向集合 key 中添加成员 member127.0.0.1:6379> zadd zset 1 one (integer) 1 127.0.0.1:6379> zadd zset 2 tow 3 three (integer) 2
zrange key start end
:查找区间内[start, end]的成员
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]
:按照 score 从小到大排序并显示结果
- withscores:结果中带上score的值
127.0.0.1:6379> ZRANGEBYSCORE zset -inf +inf 1) "one" 2) "tow" 3) "three"
zrevrangebysource key max min [WITHSCORES] [LIMIT offset count]
:按照 score 从大到小排序并显示结果
- withscores:结果中带上score的值
127.0.0.1:6379> ZREVRANGEBYSCORE zset +inf -inf withscores 1) "three" 2) "3" 3) "tow" 4) "2" 5) "one" 6) "1"
zrem key member
:从集合 key 中移除成员 member
zcard key
:查找集合 key 中成员个数
zcount key min max
:获取集合 key 指定score区间[min, max]的成员数量
特殊数据类型
1、geospatial 地理位置
redis 的 geo 在 redis 3.2 版本就推出了,这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人;
geo 底层的实现原理时 Zset,所以可以使用 Zset 命令操作 geo
可以查询一些测试数据:http://www.jsons.cn/lngcode/
常用命令
geoadd key [NX|XX] [CH] longitude latitude member
:添加位置信息,精度 longitude,维度 latitude,成员 member
南北极的位置信息无法直接添加
一般是通过 java 程序一次性导入所有需要导入的城市数据
有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。
当坐标位置超出上述指定范围时,该命令将会返回一个错误。
127.0.0.1:6379> geoadd china:city 114.352482 36.103442 anyang (integer) 1
geopos key member [member ...]
:获取key的member成员对应的经纬度信息127.0.0.1:6379> geopos china:city anyang guangzhou 1) 1) "114.35248106718063354" 2) "36.10344151006869851" 2) 1) "113.28063815832138062" 2) "23.12517743834835215"
geodist key member1 member2 [m|km|ft|mi]
:返回两个给定位置之间的距离
- 如果两个位置之间的其中一个不存在, 那么命令返回空值。
- 指定单位的参数 unit 必须是以下单位的其中一个:
- m 表示单位为米,默认值
- km 表示单位为千米
- mi 表示单位为英里
- ft 表示单位为英尺
127.0.0.1:6379> geodist city anyang guangzhou m "1447208.9352"
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH]
:以给定的 longitude、latitude 经纬度为中心,找出 radius 半径内的元素
- 可以用来实现附近的人等等
127.0.0.1:6379> georadius city 114 36 1000 km 1) "anyang"
georadiusbymember city number radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH]
:以 city 的成员 number 的经纬度为中心,查找出附近 1000 km de成员
geohash key member [member ...]
:返回一个或多个位置元素的 geohash 表示
- 该命令将返回 11个 字符的 geohash 字符串
# 将二维的经纬度转换为一维的字符串 127.0.0.1:6379> geohash city anyang 1) "ww3m6hkvvc0" 127.0.0.1:6379> geohash city anyang guangzhou 1) "ww3m6hkvvc0" 2) "ws0e9cb3yj0"
2、hyperloglog
什么是基数?
A = { 1 , 3 , 5 , 7 , 9 , 7 } , B = { 1 , 3 , 5 , 7 , 9 } A =\{1, 3, 5, 7, 9, 7\}, B =\{1, 3, 5, 7, 9\} A={1,3,5,7,9,7},B={1,3,5,7,9}
基数(不重复的元素)= 5,可以接受误差!
简介
redis 2.8.9 版本就更新了 Hyperloglog 数据结构!
redis hyperloglog 基数统计的算法
- 优点:占用的内存的是固定的,2^64 不同的元素的计数,只需要耗费 12 kb 内存!如果要从内存角度来比较的话 Hyperloglog 首选!
网页的 UV (一个人访问一个网站多次,但是还算做一个人)
- 传统的方式,set 保存用户的 uid,然后就可以统计 set 中元素数量作为判断标准,这个方式 如果保存大量的用户 uid,就会比较麻烦!我们的目的是为了计数,而不是保存用户 uid;
- 0.81% 错误率,统计 UV 任务,是可以接受这点误差的!
常用命令
pfadd key element [element ...]
:向 key 中添加元素 element127.0.0.1:6379> pfadd key1 a b c d e f g (integer) 1 127.0.0.1:6379> pfadd key2 f g h i j k l (integer) 1
pfcount key [key ...]
:计算 key 的基数,多个 key 计算其基数之和127.0.0.1:6379> pfcount key1 (integer) 7 127.0.0.1:6379> pfcount key2 (integer) 7 127.0.0.1:6379> pfcount key1 key2 (integer) 12
pfmerge destkey sourcekey [sourcekey ...]
:合并几组 sourcekey 并将其结果放入新建的 destkey 中;127.0.0.1:6379> pfmerge key3 key1 key2 OK 127.0.0.1:6379> pfcount key3 (integer) 12
-
如果允许容错,那么一定可以使用 Hyperloglog!
-
如果不允许容错,就是用 set 或者自己的数据类型即可!
3、bitmaps
位存储
统计用户信息,活跃,不活跃!登录,未登录!打卡,365 打卡!两个状态的都可以使用 Bitmaps
Bitmaps 位图,数据结构!都是操作二级制位来进行记录,就只有 0 和 1 两个状态!
常用命令
setbit key offset value
:设置某一天的打卡状态
- 打卡:0
- 未打卡:1
127.0.0.1:6379> setbit sign 0 0 (integer) 0 127.0.0.1:6379> setbit sign 1 1 (integer) 0 127.0.0.1:6379> setbit sign 2 1 (integer) 0 127.0.0.1:6379> setbit sign 3 1 (integer) 0 127.0.0.1:6379> setbit sign 4 1 (integer) 0 127.0.0.1:6379> setbit sign 5 0 (integer) 0 127.0.0.1:6379> setbit sign 6 1
getbit key offset
:查看某一天是否打卡127.0.0.1:6379> getbit sign 0 (integer) 0 127.0.0.1:6379> getbit sign 1 (integer) 1
bitcount sign [start end]
:统计打卡的天数# 可以根据结果统计是否全勤 127.0.0.1:6379> bitcount sign (integer) 5
事务(Transaction)
学习 MySQL 时,事务(Transaction)正确执行要确保 ACID:
- 原子性(Atomicity):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性(Lsolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别:
- 未提交(Read uncommitted)
- 读提交(read committed)
- 可重复读(repeatable read)
- 串行化(Serializable)
- 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
Redis 事务本质: 一组命令的集合!一个事务的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行!
一次性、顺序性、排他性!
- Redis 事务没有隔离级别的概念
- 所有命令在事务中,并没有直接执行,只有发起执行命令的时候才会执行!Exec
- Redis 单条命令是保证原子性的,但是事务是不保证原子性的!
redis 的事务:
- 开启事务(multi)
- 命令入队(…)
- 执行事务(exec)
正常执行事务!
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
放弃事务!
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 k4 v4
QUEUED
127.0.0.1:6379(TX)> discard # 取消事务
OK
127.0.0.1:6379> get k4 # 事务队列中的命令都不会被执行
(nil)
编译型异常(代码有问题!命令有错!),食物中所有的命令都不会被执行!
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> getset k1 # 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k2 # 所有的命令都不会被执行!
(nil)
运行时异常(1/0),如果事务队列存在语法性错误,那么执行命令的时候,其他命令时可以正常执行的,错误的命令抛出异常!
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1 # 运行时异常,执行失败
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)> exec
1) (error) ERR value is not an integer or out of range #
2) OK
3) "v2"
监控!Watch
- 悲观锁:
- 很悲观,认为什么时候都会出现问题,无论做什么都会加锁;
- 乐观锁:
- 很乐观,认为什么时候都不会出问题,所以不会上锁;更新数据的时候去判断一下,在此期间是否有人修改数据,使用 version 字段
- 获取 version
- 更新的时候比较 version
redis 监视测试!
- 正常执行成功;
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 监视 money 对象
OK
127.0.0.1:6379> multi # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功,此时监视结束
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
- 测试多线程修改值,使用 watch 可以当作 redis 的乐观锁操作
127.0.0.1:6379> watch money # 监视
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> exec # 执行之前,另外一个线程修改了 money 的值
(nil)
Jedis(Java Connect redis)
Jedis 是 Redis 官方推荐的 Java 连接 Redis 的开发工具,使用 Java 操作 Redis,那么一定要 Jedis 十分的熟悉!
1、导入对应的依赖
<dependencies>
<!--导入 jedis 的包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
</dependencies>
2、编码测试:
- 连接数据库
- 操作命令
- 断开连接
public class TestPing {
public static void main(String[] args) {
// 1. new Jedis 对象即可
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 如果设置了密码,在这里需要验证密码才可以继续执行指令
jedis.auth("123456");
// 2. jedis 所有的命令就是之前学习的所有指令
System.out.println(jedis.ping()); // PONG
// 3. 关闭连接
jedis.close();
}
}
3、更多指令:
Jedis 的所有指令及其参数都和 redis 客户端命令行的参数一致,在这里不做过多赘述;
SpringBoot 整合
SpringBoot 操作数据:spring-data jpa jdbc mongodb redis
说明:SpringBoot 2.x 之后,原来使用的 jedis 被替换为了 lettuce
jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全,使用 jedis pool 连接池!BIO
lettuce:采用 netty,实例可以在多个线程中进行共享,不存在线程不安全问题;NIO
@Bean
// 可以自己定义一个 redisTemplate 来替换默认的 redisTemplate
@ConditionalOnMissingBean(name = {"redisTemplate"})
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 默认的 RedisTemplate 没有过多的设置, redis 对象都是需要序列化的
// 两个泛型都是 Object 类型,后续使用需要强制类型转换<String, Object>
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
// 由于 String 是 redis 中最常使用的类型,所以说单独提取出来了一个 bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
整合测试
1、导入依赖
2、配置连接
🔒 注意:连接 redis 的密码是 user:password,没有 user 时可以不填,乱填会报错,:password 或者 password 都可以
# 配置 redis
# SpringBoot 所有的配置类都会有一个自动配置类 RedisAutoConfiguration
# 自动配置类都会绑定一个 properties 配置文件 RedisProperties
spring:
redis:
url: redis://******@***.***.***.***:6379
# password: 123456
# port: 6379
# host: ***.***.***.***
3、测试
void contextLoads() {
// redisTemplate 操作不同的数据类型,api和指令相同
// opsForValue 操作字符串 类似于String
// opsForList 操作 List 类似于 List
// opsForSet 操作 Set 类似于 Set
// opsForHash
// opsForZSet
// opsForGeo
// opsForHyperLogLog
// opsForCluster
// 获取 redis 的连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushAll();
// connection.flushDb();
redisTemplate.opsForValue().set("me", "Xbai");
System.out.print(redisTemplate.opsForValue().get("me"));
}
if (this.defaultSerializer == null) {
// 默认的序列化方式是 JDK 序列化
// 我们可能会使用 JSON 序列化
this.defaultSerializer = new JdkSerializationRedisSerializer(
this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
编写自己的 RedisTemplate
@Configuration
public class RedisConfig {
// 编写自己的 redisTemplate
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 为了开发方便,一般直接使用 <String, Object>
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
// 配置具体的序列化方式
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key 采用 String 的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash 的 key 也采用 String 的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value 序列化方式采用 jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash 的 key 也采用 String 的序列化方式
template.setHashKeySerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
Redis.conf 配置文件
启动的时候就通过配置文件来启动!
单位
![image-20211105163529284](https://cdn.jsdelivr.net/gh/Xbai-hang/blog-images/工具类/Redis/image-20211105163529284.png)
1、配置文件 unit 单位 对大小写不敏感
INCLUDES 包含
可以将其他配置文件包含进来一同生效
NETWORK 网络
bind 127.0.0.1 # 绑定的 ip
protected-mode yes # 保护模式
port 6379 # 默认端口号
GENERAL 通用
# 以守护进程的方式运行(后台运行),默认是 No
daemonize yes
# 如果以后台方式运行,就需要指定一个 pid 进程文件
pidfile /var/run/redis_6379.pid
# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" # 日志的文件位置
database 16 # 数据库的数量,默认是 16 ge数据库
always-show-logo no # 是否总是显示启动时的那个 LOGO
SNAPSHOTTING 快照
持久化,在规定的时间内执行了多少次操作,则会持久化到文件:.rdb .aof
# 如果 3600 秒内至少有 1 个 key 进行了修改,就进行持久化操作
save 3600 1
# 如果 300 秒内至少有 100 个 key 进行了修改,就进行持久化操作
save 300 100
# 如果 60 秒内至少有 10000 个 key 进行了修改,就进行持久化操作
save 60 10000
# 我们之后学习持久化,会自己定义这个测试!
# 持久化出错后是否还需要继续工作
stop-writes-on-bgsave-error yes
# 是否压缩 rdb 文件,需要消耗一些 CPU 资源
rdbcompression yes
# 保存 rdb 文件时,进行错误的检查校验
rdbchecksum yes
# 持久化的文件名字
dbfilename dump.rdb
# rdb 文件保存目录,默认当前文件夹下
dir ./
SECURITY 安全
# 设置密码
requirepass 123456
可以通过命令行查看并设置密码:
# 查看是否设置密码
> config get requirepass
# 设置密码
> config set requirepass 123456
# 验证密码,设置密码后需要验证才能继续使用
> auth 123456
CLIENTS 客户端
MEMORY MANAGEMENT 内存管理
maxclients 10000 # 设置能连接上 redis 的最大客户端数量
maxmemory <bytes> # redis 设置最大的内存容量
maxmemory-policy noeviction # 内存到达上限之后的处理策略
# 1、volatile-lru:只对设置了过期时间的 key 进行 LRU(默认值)
# 2、allkeys-lru:删除 lru 算法的 key
# 3、volatile-random:随机删除即将过期 key
# 4、allkeys-random:随即删除
# 5、volatile-ttl:删除即将过期的
# 6、noeviction:永不过期,返回错误
APPEND ONLY 模式 aof 配置
appendonly no # 默认不开启 aof 模式,默认使用 rdb 方式持久化
appendfilename "appendonly.aof" # 持久化的文件名字
# appendfsync always # 每次修改都会 sync。消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这 1s 的数据
# appendfsync no # 不执行 sync
Redis 持久化⭐️
Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以 Redis 提供了持久化功能!
RDB(Redis DataBase)
❔问:什么是 RDB
-
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot 快照,它恢复时是将快照文件直接读到内存里。
-
Redis会单独创建 ( fork )一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时件替换上次持久化好的文件。
-
整个过程中,主进程是不进行任何 I0 操作的。这就确保了极高的性能。如果需要进行大规模数据的回复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。
-
RDB的缺点是最后一次持久化后的数据可能丢失。
-
默认的就是 RDB,一般情况下不需要修改这个配置!
-
RDB 保存的文件时:dump.rdb
❔问:触发机制是什么
满足下面三种情况,就会自动生成一个 dump.rdb 备份文件;
- save 的 规则 满足的情况下,会自动触发 rdb 规则;
- 执行 flushall 命令,也会触发 rdb 规则,但是没有意义,因为文件内容为空;
- 退出 redis 时,也会产生 rdb 文件;
- 在客户端中使用 save 或者 bgsave 命令,也可以触发 RDB规则;这两种规则有所不同:
- save 命令会完全占用当前进程去进行持久化操作,也就是说,save命令只管保存,不管其他,只要有进程过来,一律阻塞;
- bgsave 命令会在后台运行,手动 fork 子进程进行操作,并且还会响应客户端的请求
❔问:如何恢复 rdb 数据文件
-
只需要将 rdb 文件放在 redis 的启动目录下即可,redis 启动的时候会检查 dump.rdb 文件恢复其中的数据;
-
查看需要存放的位置(redis 的启动目录)
> config get dir
1) "dir"
2) "/usr/local/bin"
💡:优缺点
优点:
- 适合大规模的数据恢复;数据文件📑:
dump.rdb
- 对数据的完整性要求不高;
缺点:
- 需要一定的时间间隔进程操作!如果 redis 意外宕机了,这个最后一次修改数据就没有了的;
- fork 进程的时候,会占用一定的内存空间;
AOF(Append Only File)
原理:
- 以日志的形式来记录每个写操作,将 redis 执行过的所有指令记录下来(读操作不记录);
- 只许追加文件但不可以改写文件;
- 该模式默认是关闭,需要在配置文件下修改配置项:
appendonly no
;改为 yes;
❔问:如何恢复 aof 文件
- aof 保存的是 appendonly.aof 数据文件,可通过配置文件修改;
- redis 启动时会读取该文件重新构建数据;
- 换言之,redis 重启的话就根据日志文件的内容将写指令完整执行一遍以完成数据的恢复工作;
❔问:如何确保数据文件完整性
- 如果配置文件出错,例如被人为修改错误的指令等,此时是无法启动 redis 服务的,会提示
Connection refused
; - redis 提供了文件修复工具 redis-check-aof,可以通过
redis-check-aof --fix appendonly.aof
指令去修复文件; - 注意: 修复工具是将出问题的指令删除,也就是说,当经篡改后的指令是正确的 redis 指令,该修复工具无法检查出错误,直接启动 redis 服务并不会报错;
❔问:重写机制 rewrite
- 如果 aof 文件大于 64mb,将会 fork 一个新的进程来将数据文件进行重写;
- 此配置项默认是关闭的
💡:优缺点
appendonly no # 默认不开启 aof 模式,默认使用 rdb 方式持久化
appendfilename "appendonly.aof" # 持久化的文件名字
# appendfsync always # 每次修改都会 sync。消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这 1s 的数据
# appendfsync no # 不执行 sync
优点:
- 每次修改都同步(sync),更能确保文件完整性;
- 默认每秒同步一次,可能会丢失一秒的数据;
- 从不同步,效率最高;
缺点:
- 相对于数据文件来说,
aof >>> rdb
,修复的速度也比 rdb 慢; - AOF 运行效率也要比 RDB 低;所以 redis 默认的配置是 RDB 持久化;
拓展 📖
-
RDB 持久化方式能够在指定的时间间隔内对数据进行快照存储;
-
AOF 持久化方式是记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF 命令以 redis 协议追加保存每次的写操作到文件末尾,redis 还能对 AOF 文件进行后台重写,使得AOF 文件的体积不至于过大;
-
只做缓存,如果只需要数据在服务器运行的时候存在,也可以不使用任何持久化;
-
同时开启两种持久化方式
- 在这种情况下,当 redis 重启的时会优先加载 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集完整性要比 RDB 文件高;
- RDB 的数据不实时,同时使用两者,服务器重启时也只会找 AOF 文件;
- 建议不要只使用 AOF: 因为 RDB 更适合用于备份数据库(AOF在不断追加指令,不断变化不易备份),快速重启时不会有AOF 可能潜在的 Bug,留着作为以防万一的手段;
-
性能建议
- 因为 RDB 文件只用作后备用途,建议只在 Slave 上持久化 RDB 文件,只要15分钟备份一次就够了,只保留
save 900 1
这条规则; - 如果 Enable AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只 load 自己的 AOF 文件即可,代价一是带来了持续的 lO 操作,二是 AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少 AOF rewrite 的频率,AOF重写的基础大小默认值 64Mb 太小了,可以设到 5Gb 以上,默认超过原大小 100% 大小重写可以改到适当的数值。
- 如果不Enable AOF,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔I0,也减少了rewrite 时带来的系统波动。代价是如果 Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的RDB文件,载入较新的那个,微博就是这种架构。
- 因为 RDB 文件只用作后备用途,建议只在 Slave 上持久化 RDB 文件,只要15分钟备份一次就够了,只保留
Redis 发布订阅
-
redis 发布订阅 (pub /sub)是一种 消息通信模式 :发送者(pub)发送消息,订阅者(sub)接收消息;
-
redis 客户端可以订阅任意数量的频道;
订阅/发布消息图:
- 涉及主体:消息发送者、频道、消息订阅者
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
📝 命令:
命令 | 描述 |
---|---|
PSUBSCRIBE pattern [pattern …] | 订阅一个或多个符合给定模式的频道 |
PUBSUB subcommand [argument [argument …]] | 查看订阅与发布系统状态 |
PUBLISH channel message | 将信息发送到指定的频道 |
PUNSUBSCRIBE [pattern [pattern …]] | 退订所有给定模式的频道 |
SUBSCRIBE channel [channel …]] | 订阅给定的一个或多个频道的信息 |
UNSUBSCRIBE [channel [channel …]] | 指退订给定的频道 |
🥇 测试
订阅端:
# 第一个 redis-cli 客户端
127.0.0.1:6379> subscribe Xbaiblog # 订阅一个频道 Xbaiblog
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "Xbaiblog"
3) (integer) 1
# 等待读取推送的信息
1) "message"
2) "Xbaiblog"
3) "lol"
1) "message"
2) "Xbaiblog"
3) "good game"
发送端:
# 第二个 redis-cli 客户端
127.0.0.1:6379> publish Xbaiblog lol # 发布者发布消息到频道
(integer) 1
127.0.0.1:6379> publish Xbaiblog "good game" # 发布者发布消息到频道
(integer) 1
原理:
- Redis 是使用 C 实现的,通过分析 Redis 源码里的 pubsub.c 文件,了解发布和订阅机制的底层实现,籍此加深对 Redis 的理解;
- Redis 通过 PUBLISH、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能;
- 通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是 channel,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端;SUBSCRIBE 命令的关键,就是将客户端添加到给定 channel 的订阅链表中;
- 通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者;
- Pub/Sub从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis 中,你可以设定对某一个 key 值进行消息发布及消息订阅,当一个 key 值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息;这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能;
应用场景:
1、实时消息系统
2、实时聊天室(频道当作聊天室,将信息回显给所有人即可)
3、订阅、关注系统
4、更复杂的场会使用消息中间件 MQ(Rabbit MQ、Kafuka)
Redis 主从复制
📑Tips:Redis 应用场景下 80% 都是读操作,主从复制实现了读写分离,减缓服务器压力,架构中经常使用,一主二从;
概念
主从复制,是指将一台 Redis 服务器的数据,复制到其他的Redis服务器。前者称为主节点(master / leader),后者称为从节点(slave / follower);数据的复制是单向的,只能由主节点到从节点。Master 以写为主,Slave 以读为主;
默认情况下,每台 Redis 服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点; 主从复制的作用主要包括:
- 数据冗余∶主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式;
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余;
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写 Redis 数据时应用连接主节点,读Redis 数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量;
- 高可用(集群)基石∶除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是 Redis 高可用的基础;
一般来说,要将 Redis 运用于工程项目中,只使用一台 Redis 是万万不能的,原因如下∶
- 从结构上,单个 Redis 服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;
- 从容量上,单个 Redis 服务器内存容量有限,就算一台 Redis 服务器内存容量为 256G,也不能将所有内存用作 Redis 存储内存,一般来说,单台 Redis 最大使用内存不应该超过 20G;
搭建
默认情况下,每台 Redis 服务器都是主节点;只需要配置从机,不用配置主机!
info replication
:查看当前机器127.0.0.1:6379> info replication # 查看当前库的信息 # Replication role:master # 角色 master connected_slaves:0 # 没有从机 master_failover_state:no-failover master_replid:4237a7b98dcbb5369b6eef52671b827f972668de master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
SLAVEOF host port
:从机认主
SLAVEOF no one
:从机上位
Notice:没那么多服务器,在一台服务器上门开多个端口模拟集群
- 复制 3 个配置文件,然后修改对应的信息:
- port,三个不同的端口:6379、6380、6381
- logfile,三个不同的日志文件,6379.log、6380.log、6381.log
- pidfile,三个不同的进程文件,redis_6379.pid、redis_6380.pid、redis_6381.pid
- rdbfilename,三个不同的数据备份文件,dump79.rdb、dump80.rdb、dump81.rdb
- 分别启动三个服务:
命令行
一般情况下只用配置从机即可,从机 Solve(follower) 认主 Master(leader)
# 从机1 信息
127.0.0.1:6380> info replication
# Replication
role:slave # 当前角色是从机
master_host:127.0.0.1 # 主机 ip
master_port:6379 # 主机 port
master_link_status:up # 主机状态 up 运行中,down 关机
master_last_io_seconds_ago:9
master_sync_in_progress:0
slave_read_repl_offset:28
slave_repl_offset:28
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:d425c4663667595ff0c70fc85166e18a5a645d20
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:28
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:28
# 主机信息
127.0.0.1:6379> info replication
# Replication
role:master # 当前角色是主机
connected_slaves:1 # 主机下有一台从机
# 从机信息
slave0:ip=127.0.0.1,port=6380,state=online,offset=112,lag=0
master_failover_state:no-failover
master_replid:d425c4663667595ff0c70fc85166e18a5a645d20
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:112
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:112
注意:
当我们为主机配置了密码时(事实上这很有必要),需要在从机配置文件下增加一项配置项 masterauth password
,指定主机的密码;
![image-20211106141714382](https://i-blog.csdnimg.cn/blog_migrate/3f396b855073202cbc61681bdd68f17f.png)
再次注意: 图片上的指令写错了!懒得改啦 QAQ,指令应该是 SLAVEOF host port
;
配置项
真实开发中一般采用配置项的方式进行配置,因为在命令行上使用命令后,当从机服务重启后主人就没了,需要重新输入命令;
相关配置对应的是配置文件中的
APPLICATION
需要修改的配置文件中的配置即图片中的 replicaof host port
以及 masterauth password
两个配置项;
原理
🔖 主机可以写,从机只能读!
主机中的所有信息和数据都会被从机保存;
-
主机写:
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> keys * 1) "k1"
-
从机读(从机只能读取)
127.0.0.1:6380> get k1 "v1" 127.0.0.1:6380> set k2 v2 (error) READONLY You can't write against a read only replica. 127.0.0.1:6380>
复制原理
- Slave 启动成功连接到 Master 后会发送一个 sync 同步命令;
- Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,Master 将传送整个数据文件到 Slave,并完成一次完全同步;
- 全量复制 :Slave 服务在接收到数据文件后,将其存盘并加载到内存中;
- 增量复制 :Master 持续将新的所有收集到的修改命令依次传给 Slave,完成同步;
- 只要是重新连接 Master,一次完全同步(全量复制)将被自动执行;
两种基本模式
层层链路 Master <-- Slave1 <-- Slave2
一主二从 Salve1 --> Master <-- Slave2
这两种方法都可以完成主从复制,工作时两种方式都不会使用 🐕
一主二从
📝测试:
- 当主机宕机时,从机依旧连接到主机,但是不可以进行写操作,当主机恢复服务后,从机还可以读取到主机写的数据;
- 若使用的是命令行来配置的主从关系,从机重启后就会变回主机,当重新认主后,就会立刻从主机中获取值;
- 可以通过 哨兵模式 来配置:当主机断开连接后,从它的从机中找一台来临时充当主机,以确保写操作可以在原主机断开后正常进行,保证服务的稳定性;
层层链路:
📝测试:
-
Slave2 的主机是 Slave1,但是 Slave1 也有主机 Msater,所以 Salve1 也不可以进行写操作;
-
当主机(Master)宕机了,这时候 Salve1 需要上位(谋权篡位)当主机,在 Slave1 执行
SLAVEOF no one
命令即可;127.0.0.1:6380> Slaveof no one OK 127.0.0.1:6380> info replication # Replication role:master connected_slaves:1 slave0:ip=127.0.0.1,port=6381,state=online,offset=5456,lag=0 master_failover_state:no-failover master_replid:59f4165722f5ad4408963dddd19d91492e06ddf9 master_replid2:d425c4663667595ff0c70fc85166e18a5a645d20 master_repl_offset:5456 second_repl_offset:5443 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:5456
哨兵模式
问:什么是哨兵模式?
背景:
- 主从切换技术的方法是︰当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用;
- 这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式;
- Redis从2.8开始正式提供了 Sentinel (哨兵)架构来解决这个问题;
哨兵模式:
-
哨兵模式是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行;
-
其原理是哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例;
-
谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从机转换为主机;
![哨兵模式图 哨兵模式图](https://i-blog.csdnimg.cn/blog_migrate/7ae9b4244ea1c088d800a30a2052865a.png)
-
假设主机 Master 宕机,哨兵1 先检测到这个结果,系统并不会马上进行failover 过程,仅仅是 哨兵1 主观的认为主服务器不可用,这个现象成为主观下线;
-
当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作;
-
切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线;
测试哨兵模式!
采用一主二从的配置
-
配置哨兵配置文件
sentinel.conf
:# sentinel monitor <master-name> <ip> <port> <quorum> sentinel monitor myredis 127.0.0.1 6379 1
- master-name:主机的昵称
- ip:主机 ip
- port:主机端口
- quorum:哨兵判断主节点是否发生故障的票数,设置为 1,表示 1个哨兵节点认为主节点发生了故障,一般设置为:哨兵节点数 / 2 + 1
-
启动哨兵
redis-sentinel Xconfig/sentinel.conf
-
如果主机宕机后,就会从从机中随机选择一个服务器,(底层有个投票算法)
-
主机宕机后,哨兵模式的日志:
-
当主机恢复服务后,只能归并到新的主机下,当作从机,这就是哨兵模式的规则;
注意: 如果 redis 主机配置了密码,需要在哨兵配置文件中加入配置项: sentinel auth-pass <master-name> <password>
sentinel auth-pass myredis 123456
💡:优缺点
优点:
- 哨兵集群,基于主从复制模式,有所有的主从配置的优点;
- 主从可以切换,故障可以转移,系统的可用性就会更好;
- 哨兵模式就是主从模式的升级,从手动到自动,更加健壮!
缺点:
- Redis 不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦;
- 实现哨兵模式的配置很麻烦,有许多配置要配(上面例子只是最基础的配置);
哨兵模式的全部配置
# Example sentinel.conf
# 哨兵 snetinel 实例运行的端口 默认 26379
port 26379
# 哨兵 sentinel 的工作目录
dir /tmp
# 哨兵 sentinel 监控的 redis 主节点的 ip port
# master name 主节点名称 由[A-z]、[0-9]、[.-_]任意搭配组
# quorum 配置多少个 sentine1 哨兵统一认为 master 主节点失联那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentine1 monitor mymaster 127.0.0.1 6379 2
# 当在 Redis 实例中开启了 requirepass foobared 授权密码 所有连接 Redis 实例的客户端都要提供密码
# 设置哨兵 sentine1 连接主从的密码必须和为主从设置的验证密码一致
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456
# 指定时间内若主节点没有应答哨兵 sentinel,此时哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 指定了在发生 failover 主备切换时,可以同时对新的 master 进行同步的 slave 的最大个数
# 这个数字越小,完成 failover 所需的时间就越长
# 但是这个数字越大,就意味着越多的 slave 因为 replication 而不可用
# 可以通过将这个值设为 1 来保证每次只有一个 slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout,默认三分钟。
# 可以用在以下这些方面:
# 1. 同一个 sentinel 对同一个 master 两次 failover 之间的间隔时间;
# 2. 当一个 slave 从一个错误的 master 那里同步数据开始计算时间,直到 slave 被纠正向正确的 master 那里同步数据的总耗时时;
# 3.当想要取消一个正在进行的 failover 所需要的时间;
# 4.当进行 failover 时,配置所有 slave 指向新的 master 所需的最大时间;但是,即使过了这个超时时间,slave 依然会被正确配置指向新的 master,但是就不会按照 parallel-syncs 所配置的规则执行了
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
# 配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员;例如当系统运行不正常时发邮件通知相关人员;
# 对于脚本的运行结果有以下规则:
# 若脚本执行后返回 1,那么该脚本稍后将会被再次执行,重复次数目前默认为 10
# 若脚本执行后返回 2,或者比 2 更高的一个返回值,脚本将不会重复执行。
# 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为 1 时的行为相同。
# 一个脚本的最大执行时间为 60s,如果超过这个时间,脚本将会被一个 SIGKILL 信号终止,之后重新执行。
# 通知型脚本: 当 sentinel 有任何警告级别的事件发生时
# (比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
# 这时这个脚本应该通过 邮件,SMS 等方式去通知系统管理员关于系统不正常运行的信息。
# 调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。
# 如果 sentinel.conf 配置文件中配置了这个脚本路径,那么必须保证
# 这个脚本存在于这个路径,并且是可执行的,否则 sentinel 无法正常启动成功。
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个 master 由于 failover 而发生改变时,这个脚本将会被调用,
# 通知相关的客户端关于 master 地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前 <state> 总是 “failover”,
# <role> 是 “leader” 或者 “observer” 中的一个。
# 参数 from-ip, from-port, to-ip, to-port
# 是用来和旧的 master 与新的 master (即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
# 一般都是由运维来配置!
Redis 缓存穿透和雪崩⭐️
服务器的高可用问题!
Redis 缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。
另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。
缓存穿透(查不到)
概念
缓存穿透的概念很简单,用户想要查询一个数据,发现 redis 内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以 hash 形式存储,在控制层先进性校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
![image-20211106191625045](https://i-blog.csdnimg.cn/blog_migrate/600a6bf4eabf2727b34ba3de5e54981d.png)
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据库;
![image-20211106192541734](https://i-blog.csdnimg.cn/blog_migrate/28dc552a2b68bf9d7cdb0e653cb1e026.png)
这种方法会存在两个问题:
- 如果控制能被缓存起来,意味着缓存需要更多的空间存储更多的键,这当中可能会有很多的空值的键;
- 即使空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持数据一致性的业务会有影响;
缓存击穿(高并发查)
概述
-
这需要注意和缓存穿透的区别,缓存击穿,是指一个 key 非常热点,在不停的扛着大并发,大并发集中对着一个点进行访问,当这个 key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞;
-
当某个 key 在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大;
解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。
加互斥锁
分布式锁:使用分布式锁,保证对于每个 key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因从对分布式锁的考验很大。
缓存雪崩(缓存失效)
概念
缓存雪崩,是指在某一个时间段,缓存集中过期失效,redis 宕机!
产生雪崩的原因:假设双十一零点抢购,商品时间比较集中的放入了缓存,假设缓存一个小时,那么到了凌晨一点,这批商品的缓存就都过期了,而对这批商品的访问查询,都落到了数据库上, 对于 redis 数据库而言,就会产生周期性的压力波峰。于是所有的请求就都会到达存储层(Data Base),存储层的调用量会暴增,造成存储层也会挂掉的情况。
![image-20211106210315341](https://i-blog.csdnimg.cn/blog_migrate/d37801e6b5f95aa313d03532125e36d1.png)
解决方案
redis 高可用性
这个解决方案的思想是:既然 redis 有可能挂掉,那么多增设几台 redis,这样一台服务器宕机了,其他服务器还可以继续工作,其实就是搭建的集群。(异地多活)
限流降级
这个解决方案的思想是:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据预热的含义就是在正式部署前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问签手动触发加载缓存不同的 key,设置不同的过期时间,让缓存失效的时间尽量均匀。