Redis哨兵/集群模式分析和单机/集群搭建

Redis 简介

Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志
型、Key-Value 数据库,并提供多种语言的 API。为了保证效率,数据都是缓存在内存中。

存储类型

string(字符串)
list(链表)
set(集合)
zset(sorted set --有序集合)
hash(哈希)

使用场景

在我们日常的 Java Web 开发中,无不都是使用数据库来进行数据的存储,由于一般
的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉
及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一
使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的
性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写
操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服
务宕机的严重生产问题。

为了克服上述的问题,Java Web 项目通常会引入 NoSQL 技术,这是一种基于内存
的数据库,并且提供一定的持久化功能。

Redis 是当今使用较广泛的 NoSQL,而就 Redis 技术而言,它的性能十分优越,可以支持
每秒十几万此的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配
置,原则上可以无限扩展,让更多的数据存储在内存中,更让人欣慰的是它还支持一定的
事务能力,这保证了高并发的场景下数据的安全和一致性。

高可用机制

主从同步

Redis 的主从结构可以采用一主多从,Redis 主从复制可以根据是否是全量分为全量同
步和增量同步

全量同步
Redis哨兵/集群模式分析和单机/集群搭建_第1张图片
Redis 全量复制一般发生在 Slave 初始化阶段,这时 Slave 需要将 Master 上的所有数
据都复制一份。具体步骤如下:

  1. 从服务器连接主服务器,发送 SYNC 命令;
  2. 主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区记
    录此后执行的所有写命令;
  3. 主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被
    执行的写命令;
  4. 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  5. 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  6. 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收
来自用户的读请求。

增量同步

Redis 增量复制是指 Slave 初始化后开始正常工作时主服务器发生的写操作同步到从
服务器的过程。

增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,
从服务器接收并执行收到的写命令。

同步策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需
要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增
量同步,如不成功,要求从机进行全量同步。

如果多个 Slave 断线了,需要重启的时候,因为只要 Slave 启动,就会发送 sync 请求
和主机全量同步,当多个同时出现的时候,可能会导致 Master IO 剧增宕机。

哨兵模式

Redis 的主从复制下,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主
节点,同时还要通知应用方更新主节点地址,对于很多应用场景这种故障处理的方法是无法
接受的。但是 Redis 从 2.8 开始正式提供了 Redis Sentinel(哨兵)架构来解决这个问题。

Redis Sentinel 是一个分布式架构,其中包含若干个 Sentinel 节点和Redis数据节点,
每个 Sentinel 节点会对数据节点和其余 Sentinel 节点进行监控,当它发现节点不可达时,
会对节点做下线标识。如果被标识的是主节点,它还会和其他 Sentinel 节点进行“协商”,
当大多数 Sentinel 节点都认为主节点不可达时,它们会选举出一个 Sentinel 节点来完成自
动故障转移的工作,同时会将这个变化通知给 Redis 应用方。整个过程完全是自动的,不需
要人工来介入,所以这套方案很有效地解决了 Redis 的高可用问题。

哨兵定时监控

Redis哨兵/集群模式分析和单机/集群搭建_第2张图片

  1. 每隔 10 秒,每个 Sentinel 节点会向主节点和从节点发送 info 命令获取最新的拓扑结
    构。
  2. 每隔 2 秒,每个 Sentinel 节点会向 Redis 数据节点的__sentinel__:hello 频道上发送该
    Sentinel 节点对于主节点的判断以及当前 Sentinel 节点的信息,同时每个 Sentinel 节
    点也会订阅该频道,来了解其他 Sentinel 节点以及它们对主节点的判断。
  3. 每隔 1 秒,每个 Sentinel 节点会向主节点、从节点、其余 Sentinel 节点发送一条 ping
    命令做一次心跳检测,来确认这些节点当前是否可达。

确定主节点下线

Redis哨兵/集群模式分析和单机/集群搭建_第3张图片

  1. 因为每隔一秒,每个 Sentinel 节点会向主节点、从节点、其余 Sentinel 节点发送一条
    ping 命令做一次心跳检测,当这些节点超过 down-after-milliseconds 没有进行有效
    回复,Sentinel 节点就会对该节点做失败判定,这个行为叫做主观下线。

  2. 当 Sentinel 主观下线的节点是主节点时,该 Sentinel 节点会向其他 Sentinel 节点询问
    对主节点的判断,当超过个数,那么意味着大部分的 Sentinel 节点都对这
    个主节点的下线做了同意的判定,于是该 Sentinel 节点认为主节点确实有问题,这时该
    Sentinel 节点会做出客观下线的决定。

选举哨兵领导者

Redis哨兵/集群模式分析和单机/集群搭建_第4张图片

  1. Sentinel1 最先完成客观下线,它会向其余 Sentinel 节点发送命令,请求成为领导者;
    收到命令的 Sentinel 节点如果没有同意过其他 Sentinel 节点的请求,那么就会同意
    Sentinel1 的请求,否则拒绝;如果 Sentinel1 发现自己的票数已经大于等于某个值,
    那么它将成为领导者。

故障转移

Redis哨兵/集群模式分析和单机/集群搭建_第5张图片

  1. 领导者 Sentinel 节点在从节点列表中选出一个节点作为新的主节点,选取规则是与主
    节点复制相似度最高的从节点
  2. 领导者 Sentinel 节点让剩余的从节点成为新的主节点的从节点
  3. Sentinel 节点集合会将原来的主节点更新为从节点,并保持着对其关注,当其恢复后命
    令它去复制新的主节点

集群模式

即使使用哨兵模式,redis 每个实例也是全量存储,每个 redis 存储的内容都是完整的
数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用集群模式(cluster)。
redis cluster 在设计的时候,就考虑到了去中心化,去中间件,也就是说,集群中的每个节
点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态。每个节点
都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任
意一个节点,就可以获取到其他节点的数据。

节点数据分配

Redis哨兵/集群模式分析和单机/集群搭建_第6张图片
Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽
(hash slot)的方式来分配的。redis cluster 默认分配了 16384 个 slot,当我们 set 一个
key 时,会用 CRC16 算法来取模得到所属的 slot,然后将这个 key 分到哈希槽区间的节
点上,具体算法就是:CRC16(key) % 16384。

注意的是:主节点必须大于等于三个(因为选举时需要超过半数投票),否则在创建集群时
会失败。这里,我们设置 3 个节点组成集群,分别是:A, B, C 三个节点,它们可以是一
台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽 (hash slot)的方式
来分配 16384 个 slot 的话,它们三个节点分别承担的 slot 区间是:

• 节点 A 覆盖 0-5500;
• 节点 B 覆盖 5501-11000;
• 节点 C 覆盖 11001-16383.

设置主从

Redis哨兵/集群模式分析和单机/集群搭建_第7张图片
上面那个例子里, 集群有 ABC 三个主节点, 如果这 3 个节点都没有加入从节点,如果
B 挂掉了,就无法访问整个集群了。A 和 C 的 slot 也无法访问。所以在集群建立的时候,
要为每个主节点添加从节点,当主节点宕机后,从节点变为新的主节点,当之前的主节点
重新启动后,自动变为新主节点的从节点,但是如果主从同时挂了,Redis 集群就无法继
续正确地提供服务了。

确认节点下线

每一个节点都存有这个集群所有主节点以及从节点的信息。它们之间通过互相的 ping 判断
是否节点可以连接上。如果有一半以上的节点去 ping 一个节点的时候没有回应,集群就认
为这个节点宕机了,然后去连接它的备用节点。如果某个节点和所有从节点全部挂掉,我们
集群就进入 faill 状态。
Redis哨兵/集群模式分析和单机/集群搭建_第8张图片

  1. 节点 B 的主节点宕机
  2. 节点 A 标记节点 B 为主观下线,一段时间后节点 A 通过消息把节点 B 的状态发送到其
    他节点(节点 C);
  3. 当某一节点 C 收到节点 B 的 pfail 状态时,此时有超过一半的槽主节点都标记了节点 B
    为 pfail 状态时,则标记故障节点 B 为客观下线;
  4. 向集群广播一条 pfail 消息,通知集群内的所有节点标记故障节点 B 为客观下线状态并
    立刻生效,同时通知故障节点 B 的从节点触发故障转移流程。

故障转移

  1. B 节点的从节点变更为主节点
  2. 如果 B 节点有多个从节点,需要选举一个和主节点数据最一致的从节点代替主节点

单机搭建

基于 dockers 的容器化搭建,前提主机安装 docker

搜索镜像

docker search redis
Redis哨兵/集群模式分析和单机/集群搭建_第9张图片

下载镜像

docker pull redis

查看镜像

docker images
在这里插入图片描述

启动容器

docker run --name redis1 -p 6380:6379 -d redis:latest redis-server --appendonly yes --protected-mode no

appendonly yes: 开启持久化

protected-mode no:其他 host 可以访问

Redis 操作

进入容器:docker exec -it redis1 /bin/bash

/usr/local/bin/redis-cli

info

127.0.0.1:6379> info
查询 redis 状态

#Server
redis_version:5.0.5 版本
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:f5cc35eb8e511133
redis_mode:standalone 单机
os:Linux 5.1.3-1.el7.elrepo.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:8.3.0
process_id:1
run_id:ebd5ff30cb280f08b9d12ccd7e9a967f115b0026
tcp_port:6379
uptime_in_seconds:1316
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:3149326
executable:/data/redis-server
config_file:
#Replication
role:master 主节点
connected_slaves:0 从节点数
master_replid:015f9edecb6436d3a7cbe12402549d04c7e7a0b4
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
#Cluster
cluster_enabled:0

keys

127.0.0.1:6379> keys *
查询所有的 key
Redis哨兵/集群模式分析和单机/集群搭建_第10张图片

type

查询 key 的类型
127.0.0.1:6379> type PRODUCT_ITEM:146
string
127.0.0.1:6379> type CART:85
hash

get

查询 string 类型的 key 值

127.0.0.1:6379> get PRODUCT_ITEM:146
“{“productId”:146,“salePrice”:1050.00,“productName”:“65 inch HD 3D 4K led
TV”,“productType”:1,“subTitle”:“55 65 inch HD 3D 4K led TV Android Full smart wifi
curved 1080P LED
TV”,“limitNum”:1,“num”:1,“productImageBig”:“http://172.16.23.195:7777/img/4abaed
dc40c2496fb54c83aabdefa7d6.jpg”,“detail”:”\u003cp align\u003d\“left\” data-spm-
anchor-id\u003d\“a2g0o.detail.1000023.i2.709852c7uVSDdx\”\u003e\u003cimg
class\u003d\“wscnph\”
src\u003d\“http://172.16.23.195:7777/img/e6681881bbb844f2812f365a6b5a3e62.jpg\”
/\u003e\u003cimg class\u003d\“wscnph\”
src\u003d\“http://172.16.23.195:7777/img/d1f909f582824dc5bae88050f6514787.jpg\”
/\u003e\u003cimg class\u003d\“wscnph\”
src\u003d\“http://172.16.23.195:7777/img/ae6899d0e6fb40089f3183cbf85d9b7a.jpg\”
/\u003e\u003c/p\u003e",“productImageSmall”:[“http://172.16.23.195:7777/img/4abae
ddc40c2496fb54c83aabdefa7d6.jpg”,“http://172.16.23.195:7777/img/5eed68adbe014a15b
d9280b117e93ae6.jpg”],“weight”:29.40,“promotion”:0,“cid”:1257,“itemPriceDtoList”:[{
“itemPriceId”:493…

hkeys

查询 hash 类型的 key 的 field

127.0.0.1:6379> hkeys WISH:85
1)“176”
2)“192”

hget

查询 hash 类型 key 的某个 field 的值

127.0.0.1:6379> hget WISH:85 176
“{“productId”:176,“salePrice”:900.99,“productType”:1,“limitNum”:98,“checked”:“1”,
“productName”:“Huawei P30
Pro”,“productImg”:“http://172.16.23.195:7777/img/e3458352965942d99ab50e95d8c1fd8
d.jpg”,“itemPriceId”:0}”

del

删除某个 key

127.0.0.1:6379> del WISH:85
(integer) 1

flushall

删除所有 key 值

set

插入 String 类型的 key 值

127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> get a
“1”

hset

插入 hash 类型的 key 值

127.0.0.1:6379> hset b bb 2
(integer) 1
127.0.0.1:6379> hget b bb
“2”

Cluster 集群搭建

3 主 3 从,端口分别为 6380-6385

创建 docker 内部网络(如果全部搭建在不同宿主机则略过)

docker network create redis-cluster-net

配置

mkdir redis-cluster
cd redis-cluster
创建 redis-cluster.tmpl 配置文件

vi redis-cluster.tmpl
port ${PORT}
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.16.23.126
cluster-announce-port P O R T c l u s t e r − a n n o u n c e − b u s − p o r t 1 {PORT} cluster-announce-bus-port 1 PORTclusterannouncebusport1{PORT}
appendonly yes
for port in seq 6380 6385; do mkdir -p ./KaTeX parse error: Expected 'EOF', got '&' at position 13: {port}/conf &̲& PORT={port} envsubst
< ./redis-cluster.tmpl > ./${port}/conf/redis.conf; done

启动容器

注意 ip 和路径

同一宿主机

for port in seq 6380 6385; do docker run -d -ti -p p o r t : {port}: port:{port} -p
1 p o r t : 1 {port}:1 port:1{port} -v /root/redis-
cluster/ p o r t / c o n f / r e d i s . c o n f : / u s r / l o c a l / e t c / r e d i s / r e d i s . c o n f − − r e s t a r t a l w a y s − − n a m e r e d i s − {port}/conf/redis.conf:/usr/local/etc/redis/redis.conf --restart always -- name redis- port/conf/redis.conf:/usr/local/etc/redis/redis.confrestartalwaysnameredis{port} --net redis-cluster-net --sysctl net.core.somaxconn=1024
redis redis-server /usr/local/etc/redis/redis.conf; done

不同宿主机

for port in seq 6380 6385; do docker run -d -ti -p p o r t : {port}: port:{port} -p
1 p o r t : 1 {port}:1 port:1{port} -v /root/redis-
cluster/ p o r t / c o n f / r e d i s . c o n f : / u s r / l o c a l / e t c / r e d i s / r e d i s . c o n f − − r e s t a r t a l w a y s − − n a m e r e d i s − {port}/conf/redis.conf:/usr/local/etc/redis/redis.conf --restart always -- name redis- port/conf/redis.conf:/usr/local/etc/redis/redis.confrestartalwaysnameredis{port} --sysctl net.core.somaxconn=1024 redis redis-server
/usr/local/etc/redis/redis.conf; done

安装 ruby 镜像 (5.0 以下版本)

docker search ruby在这里插入图片描述
docker pull ruby

构建 redis-trib 镜像 (5.0 以下版本)

下载对应 redis 版本的源码(https://redis.io/)

找到文件/src/redis-trib.rb

编写 dockerfile(最好在新文件夹中创建,redis-trib.rb 拷贝到同一文件夹下)

vi Dockerfile

FROM ruby:latest
MAINTAINER hong.liang
RUN gem install redis
RUN mkdir /redis
WORKDIR /redis
ADD ./redis-trib.rb /redis/redis-trib.rb

进入 Dockerfile 所在目录下

docker build -t redis-trib .

组建集群 (5.0 以下版本)

docker run -it --rm redis-trib ruby redis-trib.rb create --replicas 1 172.16.23.126:6380
172.16.23.126:6381 172.16.23.126:6382 172.16.23.126:6383 172.16.23.126:6384
172.16.23.126:6385

组建集群(5.0 以上版本)

登陆任意一台 redis

docker exec -it redis-6380 bash

/usr/local/bin/redis-cli --cluster create 172.16.23.126:6380 172.16.23.126:6381
172.16.23.126:6382 172.16.23.126:6383 172.16.23.126:6384 172.16.23.126:6385 --cluster-
replicas 1

输入 yes
Redis哨兵/集群模式分析和单机/集群搭建_第11张图片

cluster 高可用场景测试

登陆任意一台 redis

docker exec -it redis-6380 bash

查询集群状态

redis-cli --cluster check 172.16.23.126:6382
redis-cli --cluster info 172.16.23.126:6382
172.16.23.126:6382 (5a5b176f…) -> 0 keys | 5461 slots | 1 slaves.
172.16.23.126:6381 (94b6caee…) -> 0 keys | 5462 slots | 1 slaves.
172.16.23.126:6380 (cf0bc8f2…) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.

插入数据

登陆 6380

redis-cli -c -h 172.16.23.126 -p 6380
172.16.23.126:6380> hset b bb 2
(integer) 1
172.16.23.126:6380> set c 2
-> Redirected to slot [7365] located at 172.16.23.126:6381
OK
放到了 6381
172.16.23.126:6380> set a 1
-> Redirected to slot [15495] located at 172.16.23.126:6382
OK
放到了 6382
172.16.23.126:6380> keys *
1)“b”
172.16.23.126:6381> keys *
1)“c”

查询数据

172.16.23.126:6381> get a
-> Redirected to slot [15495] located at 172.16.23.126:6382
“1”
172.16.23.126:6381> hget b bb
-> Redirected to slot [3300] located at 172.16.23.126:6380
“2”

主从复制

退出,登陆 6383
redis-cli -c -h 172.16.23.126 -p 6383
172.16.23.126:6383> keys *
1)“a”
在这里插入图片描述
复制了 6382,是正确的

故障转移

主节点下线,从节点代替

停掉主节点 6380

docker stop redis-6380

检查集群信息

redis-cli --cluster info 172.16.23.126:6382
Could not connect to Redis at 172.16.23.126:6380: Connection refused
172.16.23.126:6382 (5a5b176f…) -> 1 keys | 5461 slots | 1 slaves.
172.16.23.126:6381 (94b6caee…) -> 1 keys | 5462 slots | 1 slaves.
172.16.23.126:6384 (d2f3e6f1…) -> 1 keys | 5461 slots | 0 slaves.
[OK] 3 keys in 3 masters.
0.0 keys per slot on average.

6384 从节点变更为主节点
查询属于 6380 节点的数据 b,

172.16.23.126:6383> hget b bb
-> Redirected to slot [3300] located at 172.16.23.126:6384
“2”

在 6384 中可以查到

恢复节点

启动 6380

docker start redis-6380

root@76cbd3379368:/data# redis-cli --cluster info 172.16.23.126:6382
172.16.23.126:6382 (5a5b176f…) -> 1 keys | 5461 slots | 1 slaves.
172.16.23.126:6381 (94b6caee…) -> 1 keys | 5462 slots | 1 slaves.
172.16.23.126:6384 (d2f3e6f1…) -> 1 keys | 5461 slots | 1 slaves.

6380 恢复为 6384 的从节点

JAVA API 操作

单机模式

样例为 springboot 下整合 redis,将商品信息缓存到 redis 中

pom 依赖


	org.springframework.boot
	spring-boot-starter-data-redis

application.yml 配置

spring:
	#redis 配置
	redis:
		host: 172.16.23.195
		port: 6379
		timeout: 1000
		database: 0
		pool:
 			max-active: 10
			max-idle: 8
			min-idle: 2
			max-wait: 100

Java 代码

通过装配 redistemplate 访问 redis

@Autowired
RedisTemplate redisTemplate;

先查询缓存中是否有该商品信息,如果有则读取

//查询缓存
try {
	//有缓存则读取
	String json = redisTemplate.opsForValue().get(PRODUCT_ITEM + ":" + id);
	if (json != null) {
		ProductDet productDet = new Gson().fromJson(json,
		ProductDet.class);
		log .info("读取了商品" + id + "详情缓存");
		//重置商品缓存时间
		redisTemplate.expire(PRODUCT_ITEM + ":" + id, ITEM_EXPIRE, TimeUnit. SECONDS );
		return productDet;
	}
}
catch (Exception e) {
	e.printStackTrace();
}

如果没有则查询数据库,将结果塞入

//无缓存 把结果添加至缓存
try {
	redisTemplate.opsForValue().set(PRODUCT_ITEM + ":" + id, new Gson().toJson(productDet));
	//设置过期时间
	redisTemplate.expire(PRODUCT_ITEM + ":" + id, ITEM_EXPIRE, TimeUnit. SECONDS );
 	log .info("添加了商品" + id + "详情缓存");
}
catch (Exception e) {
	e.printStackTrace();
}

集群模式

application.yml 配置

增加 cluster 的配置

redis:
	cluster:
	#(普通集群,不使用则不用开启)以逗号分隔的“主机:端口”对列表进行引导。
	nodes:
172.16.23.126:6380,172.16.23.126:6381,172.16.23.126:6382,172.16.23.126:6383,172.16.23.12
6:6384,172.16.23.126:6385

你可能感兴趣的:(中间件)