Redis高级应用
1、基本应用
1.1、事务管理
Redis
支持事务管理,允许一次执行多个命令,一个事务相当于一个原子操作。
事务中命令按照序列化顺序执行,不会被其他请求命令所打断
一个事务中的命令要么全部执行,要么全不执行
事务的执行会经过三个阶段,开启事务
、任务入队
、执行事务
。通过 multi 开启事务之后所有的命令都会进行入队,然后通过 exec 进行统一的执行。
127.0.0.1:7379> multi
OK
127.0.0.1:7379> set book java
QUEUED
127.0.0.1:7379> set book python
QUEUED
127.0.0.1:7379> exec
1) OK
2) OK
127.0.0.1:7379> get book
"python"
复制代码
Redis
支持使用 discard 来进行事务的取消,这条事务将不会被执行。
127.0.0.1:7379> multi
OK
127.0.0.1:7379> set book c++
QUEUED
127.0.0.1:7379> discard
OK
复制代码
Redis
不支持异常时的事务回滚,一条事务开始执行之后,异常之前的命令都会被正常执行提交数据,异常之后的命令也将会成功执行,只有异常命令不会执行成功。
127.0.0.1:7379> multi
OK
127.0.0.1:7379> set book c
QUEUED
127.0.0.1:7379> incr book
QUEUED
127.0.0.1:7379> set book c++
QUEUED
127.0.0.1:7379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:7379> get book
"c++"
复制代码
1.2、发布订阅
发布订阅模式 是一种消息的通信模式,发送者专门发送消息,订阅者负责接收消息,一个 Redis
客户端可以订阅任意数量的频道进行数据的接收,和观察者机制类似,客体随着主体的消息变化而接收进行对应的处理方式。
当一个消息被 PUBLISH
到频道中时,所有订阅了此频道的客户端都会接收到这个消息 。
- 创建并订阅频道
127.0.0.1:7379> subscribe msg
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "msg"
3) (integer) 1
复制代码
- 向频道中进行消息的推送
127.0.0.1:7379> publish msg "hello"
(integer) 1
复制代码
- 订阅客户端接收到消息
127.0.0.1:7379> subscribe msg
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "msg"
3) (integer) 1
1) "message"
2) "msg"
3) "hello"
复制代码
1.3、连接 Redis
通过 Jedis
进行连接,先导入相关连接依赖。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.2</version>
</dependency>
复制代码
依赖导入之后就可以进行数据的链接了,Jedis
提供简单的单节点连接方式,只需要端口和地址即可进行连接、设置访问密码的话需要添加密码。
Jedis jedis = new Jedis("ip", post);
复制代码
如果在运行时报错无法获取到连接的话,检查远程服务器的防火墙端口开放状态或者是更改绑定地址。Redis
默认绑定 127.0.0.1 只能在本地进行连接,不能提供远程连接,因此修改 bind 内网ip
。
在实际的框架开发当中,使用单节点连接的释放管理不方便,因此都会使用连接池来进行连接的创建分配和释放。
@Configuration
@PropertySource("classpath:redisConfig.properties")
public class RedisConfig {
/**
* 主机名
*/
@Value("${redis.host}")
private String host;
/**
* 开放端口
*/
@Value("${redis.port}")
private Integer port;
/**
* 密码
*/
@Value("${redis.password}")
private String password;
/**
* redis 连接池
*/
@Bean(name = "jedisPool")
public JedisPool jedisPool() {
JedisPool jedisPool = new JedisPool(init(), host, port, 3000, password);
return jedisPool;
}
/**
* 初始化配置
* @return
*/
public JedisPoolConfig init() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(10);
config.setMaxIdle(10);
config.setMaxWaitMillis(5 * 1000);
config.setTestOnBorrow(false);
config.setTestOnReturn(true);
return config;
}
}
复制代码
2、数据持久化
虽然 Redis
的数据是保存在内存当中的,但也支持硬盘持久化的方式来进行数据的保存,也是防止突发情况而导致内存中数据丢失,即可以进行内存数据的持久化备份。
2.1、RDB
RDB
是二进制文件,在某个时间点将内存数据写入到一个临时文件当中,写入完成之后替换之前持久化的文件,从而保存数据。默认开启,每间隔一段时间进行一次备份,时间可在配置文件中进行设置,类似于在时间点中进行数据快照,所以也叫做 snapshots
。适用于对于数据要求不严谨的情况数据保存。
-
优点
使用单独的子进程来进行持久化,避免主进程进行
IO
操作,保证了Redis
的高性能。 -
缺点
如果在间隔时间中发生异常故障,那将会丢失这段时间内的数据。
save 900 1 # 1 个变更 900 秒间隔
save 300 10 # 10 个变更 300 秒间隔
save 60 10000 # 10000 个变更 60 秒间隔
stop-writes-on-bgsave-error yes # 持久化错误无法继续时, 阻塞客户端
rdbcompression yes # 启动压缩
dbfilename dump.rdb # 文件名
dir ./ # 保存路径
复制代码
2.2、AOF
Append-Only File
将 操作 + 数据 以格式化指令的方式追加到操作文件的尾部,在日志写入完成之后才会进行数据的实际更新,相当于一个历史记录文件。当服务器进行恢复时进行文件的读入重做,即可还原所有的操作恢复数据的变动,
-
优点
相对可靠、内容解析方便,可以保持更高的数据完整性,设置较短的时间进行文件追加可保证小概率的数据丢失。当文件过大时还会自动进行命令的合并执行,例如删除某些不必要的命令。
-
缺点
文件相对于
RDB
更大,恢复速度较慢,而且一旦文件的写入不完整将不能支持日志恢复。
可以简单的认为 AOF
就是一个日志文件,如果持续进行大量的变更操作将会导致文件中记录大量的变更操作记录,而导致整个文件异常的庞大,恢复过程也将会缓慢。但实际上一条数据的多次变更只保留当前的状态,历史的操作将会进行抛弃,以为在持久化模式下还伴生了 AOF rewrite
。
AOF
的特性决定了它相对比较安全,如果期望异常情况下的数据丢失较少,可以使用此种模式。但是在服务器故障失效之后重启时需要对 AOF
文件的完整性进行检查,如果发现记录不完整,可以通过手工或是程序的方式去检查并修正不完整的记录,以便恢复能够正常。
appendonly no # 默认关闭, 需要手动打开
appendfilename "appendonly.aof" # 记录文件名
appendfsync everysec # 同步策略,有三个合法值:always everysec no
no-appendfsync-on-rewrite no # 暂缓文件同步
auto-aof-rewrite-percentage 100 # 下一次重写的数据量增幅比
auto-aof-rewrite-min-size 64mb # 文件重写界限
复制代码
AOF
是文件操作,那么对于变更操作较为密集的服务必将会造成磁盘 IO 的负载加重,此外 Linux
对文件采取了 延迟写入 的优化方式(写入操纵会先进入执行队列,达到阈值之后才会进行实际的写入),在此期间发生异常将会导致多条记录丢失。
同步策略 | 特点 | 缺点 |
---|---|---|
always | 操作之后立即进行同步 | 加重 IO 操作 |
everysec | 每一秒同步一次 | 一秒内记录丢失 |
no | 由操作系统来处理文件同步 | 数据丢失和 OS 配置有关 |
2.3、对比
RDB | AOF | |
---|---|---|
特点 | 在某个时间段将数据写入临时文件,再用临时文件替换原有的数据文件 | 将操作和数据以指令的形式添加到文件的尾部 |
优点 | 单独子进程执行,不影响整体性能 | 更高的数据完整性 |
缺点 | 间隔时间内发生异常会造成数据丢失 | 文件较之比较大、恢复时间也比较长 |
3、主从复制
3.1、问题引出
持久化保证了服务器即使重启也不会丢失数据,但是如果存储数据文件或是日志文件的硬盘损坏也是可能导致数据丢失的,通过主从复制机制就可以避免这种单点故障。
- 主机和从机保持数据的实时一致,当主机写入数据时数据自动复制到从机当中。
- 主从复制中不会阻塞主机,可以继续处理客户端的请求。
3.2、搭建步骤
在工作中一般使用一主两从或者是一主一从的方式。
-
使用 root 用户复制出一个从机
[root@localhost home]# cp redis/ redis1 -r [root@localhost home]# ll 复制代码
-
修改从机绑定主机
replicaof 127.0.0.1 6379 复制代码
-
添加主机密码验证
masterauth 123456 复制代码
-
修改从机的绑定端口
port 6380 复制代码
-
清除持久化文件
[root@localhost bin]# rm -rf dump.rdb 复制代码
-
启动从机连接访问
[root@localhost redis1]# ./bin/redis-cli -h 127.0.0.1 -p 6380 127.0.0.1:6380> auth beordie OK 127.0.0.1:6380> keys * 1) "lisi" 2) "fruit" 3) "book" 4) "name" 复制代码
由结果可以看出,从机已经同步了主机的数据,也可以在启动从机时查看日志文件,即可发现连接主机同步数据的过程记录。主机数据一旦发生更改就会同步到从机当中,但是从机不能进行数据的写入,只能读数据。
127.0.0.1:6380> get username
"zhangsan"
127.0.0.1:6380> set username lisi
(error) READONLY You can't write against a read only slave.
复制代码
3.3、复制原理
- 当从库和主库建立
MS
关系后,会向主数据库发送SYNC
命令; - 主数据库接收到
SYNC
命令后开始在后台保存快照,并将期间收到的命令缓存起来; - 快照完成之后主机将快照文件和所有缓存命令发送给从机;
- 从机接收到后载入快照文件并且执行缓存命令;
- 主机每次收到写命令时就会将命令发送给从机,从而保证数据的一致性。
- 这个过程是在内部完成的,因此从机不支持客户端进行人为的数据写入。
4、哨兵模式
4.1、问题引出
复制架构中出现节点宕机情况,如果是从机宕机了,直接重启即可;如果是主机宕机,需要从机执行 slaveof no one
命令来断开主从的关系并且自动提升为主库,此时这个从机就是新的节点主机,可以进行数据的写入;当主机修理完整之后需要执行 slaveof
设置为从机,也就是交换了两个节点的属性关系。
这样人工进行交换的方式比较麻烦而且还容易出错,因此就需要一个方法来让其在错误情况下自动完成节点主机的选取替换。
4.2、哨兵模式
哨兵是一个独立的进程,在 bin
目录下可以观察这个程序,作用是对 Redis
系统的运行情况进行监控,也就是一个站岗的节点。
- 负责监控主数据库和从数据库的运行是否正常
- 负责在主数据库挂掉的时候将从数据库提升为主数据库
4.3、配置哨兵
-
配置文件
哨兵一般是监控主节点的运行情况,因此需要在配置文件中指定主节点的端口等信息。
# 进入到 bin 目录下创建配置文件 [root@localhost bin]vim sentinel.conf 复制代码
# 添加配置信息 port 26379 # 指定监听端口,默认值 sentinel monitor master-name ip port count # master-name 名称随便定义、ip 主节点地址、port 主节点端口、count 决策通过的票数 sentinel auth-pass master-name password # password 密码 和主从节点保持一直就可以了 sentinel down-after-milliseconds master-name milliseconds # milliseconds 认为主库失效的时间 sentinel parallel-syncs master-name count # count 主从切换期间从机同步的数量 复制代码
-
启动哨兵
哨兵是一个单独的进程,在启动之前需要确保主从数据库已经正常运行。
[root@localhost bin]# ./redis-sentinel ./sentinel.conf >sent.log & 复制代码
查看
sentinel.conf
文件可以发现已经完成服务的监听。sentinel myid 34302b166051b9cfeaddf69fc90920ced7d30b8b sentinel deny-scripts-reconfig yes # Generated by CONFIG REWRITE port 26379 dir "/home/redis/bin" protected-mode no sentinel monitor redisMaster 172.19.231.182 6380 1 sentinel auth-pass redisMaster 123456 sentinel config-epoch redisMaster 5 sentinel leader-epoch redisMaster 5 sentinel known-replica redisMaster 172.19.231.182 6379 sentinel current-epoch 5 复制代码
如果需要重启哨兵进程就需要将配置文件中的
myid
删除,最好是每次都重新使用新的配置文件来运行。虽然哨兵(sentinel)解释为一个单独的可执行文件
redis-sentinel
,但实际上只是一个运行在特殊模式下的Redis
服务器,可以在启动普通Redis
服务器时通过指定--sentinel
参数来指定哨兵。 -
杀掉主库
kill -9 pid # 查询主库的进程号杀掉 复制代码
查看从机的信息会发现已经提升节点
slave
为master
,如果没有自动的完成主从的切换,检查密码是否一致、从机是否取消保护模式。127.0.0.1:6380> info replication # Replication role:slave master_host:172.19.231.182 master_port:6379 复制代码
127.0.0.1:6380> info replication # Replication role:master 复制代码
再次启动主机时,会自动配置为从数据库。
5、集群方案
5.1、Redis-cluster架构
- 所有节点之间通过 PING-PONG 机制来互联,内部使用二进制协议优化传输速度和带宽;
- 一个节点的失效判断是集群中超过半数的节点通信失败来决定;
- 客户端直接与节点连接且只需要连接任意一个可用节点即可;
Redis-cluster
把所有节点映射到 [0-16383] slot 上,集群就是维护node-slot-value
的关系
Redis
集群中内置 16384 个哈希槽,当需要往集群中添加一个 key-value 时,集群会先对键使用 crc16 算法进行计算,然后结果对槽数取余来寻找对应的映射节点,再往实际的节点数据库中写入数据。
5.2、Redis-cluster投票
- 集群中所有的主节点参与投票,如果半数以上的节点与其中一个节点的通信超时就可以认为此节点以及挂掉;
- 超时设置由
cluster-node-timeout
指定; - 如果集群中任意的主节点挂掉而且还没有从节点能够顶上来,那整个集群进入
fail
状态,也就是映射槽找不到对应的节点连接关系了; - 如果集群中半数以上的主节点挂掉,不管有没有从节点都进入到
fail
状态。
5.3、Redis-cluster搭建
-
创建一个目录用于集群的统一管理
[root@localhost redis]# mkdir redis-cluster 复制代码
-
创建六台机器,一主一从,三个主机
[root@localhost redis]# cp redis/ redis-cluster/redis-7001 -r [root@localhost redis]# cd redis-cluster/redis-7001 复制代码
将持久化文件删除,避免启动失败,配置文件打开集群支持配置,修改启动端口。
Cluster-enable yes port 7001 复制代码
复制出另外的几台机器,用于集群的创建。
[root@localhost redis-cluster]# cd redis-7002 [root@localhost redis-7002]# vim redis.conf [root@localhost redis-7002]# cd ../redis-7003 [root@localhost redis-7003]# vim redis.conf [root@localhost redis-7003]# cd ../redis-7003 [root@localhost redis-7003]# vim redis.conf [root@localhost redis-7003]# cd ../redis-7004 [root@localhost redis-7004]# vim redis.conf [root@localhost redis-7004]# cd ../redis-7005 [root@localhost redis-7005]# vim redis.conf [root@localhost redis-7005]# cd ../redis-7006 [root@localhost redis-7006]# vim redis.conf 复制代码
-
编写脚本方便启动管理
cd redis-7001 ./bin/redis-server ./redis.conf cd .. cd redis-7002 ./bin/redis-server ./redis.conf cd .. cd redis-7003 ./bin/redis-server ./redis.conf cd .. cd redis-7004 ./bin/redis-server ./redis.conf cd .. cd redis-7005 ./bin/redis-server ./redis.conf cd .. cd redis-7006 ./bin/redis-server ./redis.conf 复制代码
修改执行权限,启动所有实例,关闭集群时只需要关闭每个实例,删除持久化文件和自动产生的
nodes.conf
文件。[root@beordie redis-cluster]# chmod u+x startall.sh [root@beordie redis-cluster]# ./startall.sh [root@beordie redis-cluster]# ps -ef | grep redis root 10008 1 0 18:45 ? 00:00:00 ./bin/redis-server 192.168.120.130:7001 [cluster] root 10010 1 0 18:45 ? 00:00:00 ./bin/redis-server 192.168.120.130:7002 [cluster] root 10018 1 0 18:45 ? 00:00:00 ./bin/redis-server 192.168.120.130:7003 [cluster] root 10023 1 0 18:45 ? 00:00:00 ./bin/redis-server 192.168.120.130:7004 [cluster] root 10028 1 0 18:45 ? 00:00:00 ./bin/redis-server 192.168.120.130:7005 [cluster] root 10033 1 0 18:45 ? 00:00:00 ./bin/redis-server 192.168.120.130:7006 [cluster] 复制代码
-
创建集群,只需要在一台机器上进行创建即可
[root@beordie bin]# ./redis-cli --cluster create 192.168.120.130:7001 192.168.120.130:7002 192.168.120.130:7003 192.168.120.130:7004 192.168.120.130:7005 192.168.120.130:7006 --cluster-replicas 1 -a 123456 >>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 192.168.120.130:7005 to 192.168.120.130:7001 Adding replica 192.168.120.130:7006 to 192.168.120.130:7002 Adding replica 192.168.120.130:7004 to 192.168.120.130:7003 复制代码
-
连接集群
[root@beordie bin]# ./redis-cli -h 192.168.120.130 -p 7001 -c # -c 是以集群的方式登录 192.168.120.130:7001> auth 123456 192.168.120.130:7001> set name zhangsan -> Redirected to slot [5798] located at 192.168.120.130:7002 复制代码
5.4、Redis-cluster信息
-
查看集群信息
192.168.120.130:7002> cluster info cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:6 cluster_my_epoch:2 cluster_stats_messages_ping_sent:263 cluster_stats_messages_pong_sent:256 cluster_stats_messages_meet_sent:2 cluster_stats_messages_sent:521 cluster_stats_messages_ping_received:253 cluster_stats_messages_pong_received:265 cluster_stats_messages_meet_received:3 cluster_stats_messages_received:521 复制代码
-
查看节点信息
192.168.120.130:7002> cluster nodes 78e1fa7a1be93c54b7c95748e386999a84398044 192.168.120.130:7005@17005 slave bf36a0e07d21247c623e3e5ee3218c437019ebca 0 1642072159000 5 connected 768202ebf2783e9ae4576950547e90a14cbc0268 192.168.120.130:7006@17006 slave 968c11987d525cdf7631192e432fd5950d04630c 0 1642072160000 6 connected 968c11987d525cdf7631192e432fd5950d04630c 192.168.120.130:7001@17001 master - 0 1642072161333 1 connected 0-5460 6bcf00a7f61a840197e7e3fad5c594cd0f44528f 192.168.120.130:7002@17002 myself,master - 0 1642072159000 2 connected 5461-10922 bf36a0e07d21247c623e3e5ee3218c437019ebca 192.168.120.130:7003@17003 master - 0 1642072160315 3 connected 10923-16383 2258831c38d3109b90201bedf665cb508df39a81 192.168.120.130:7004@17004 slave 6bcf00a7f61a840197e7e3fad5c594cd0f44528f 0 1642072162352 4 connected 复制代码
5.5、Redis-cluster连接
// JedisCluster 单例存在
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
nodes.add(new HostAndPort("192.168.120.130", 7001));
nodes.add(new HostAndPort("192.168.120.130", 7002));
nodes.add(new HostAndPort("192.168.120.130", 7003));
nodes.add(new HostAndPort("192.168.120.130", 7004));
nodes.add(new HostAndPort("192.168.120.130", 7005));
nodes.add(new HostAndPort("192.168.120.130", 7006));
JedisCluster cluster = new JedisCluster(nodes);
// 执行方法和单节点 redis 指令一一对应。
cluster.close();
复制代码