Redis 必知必会

Hefery 的个人网站 Hefery's Personal Website 本文由博客端 http://hefery.icu 主动推送

Redis

Redis 简介

Redis:高性能(运行速度很快,并发很强,跑在内存上)的 NoSql(Not Only SQL) 系列的基于键值的非关系型数据库

特点

Redis 快的原因

Redis 支持的键值数据类型:

应用场景

主流的 NOSQL 产品

Redis 安装

Windows10

官网:https://redis.io/
    中文:http://www.redis.net.cn/
    解压:
        redis.windows.conf:配置文件
        redis-cli.exe	  :客户端
        redis-server.exe  :服务器端

CentOS7

安装:
	tar -xzf redis-3.0.7.tar.gz
	ln -s redis-3.0.7 redis
	cd redis
	make && make install

redis-server启动:
	最简启动:redis-server (默认配置)
	动态参数:redis-server --port 6380
	配置文件:redis-server configPath
		在redis目录下创建config目录,并把redis.conf复制到config目录下,更名为redis-6381.conf
		去掉conf文件的注释:cat redis-6381.conf | grep -v "#" | grep -v "^$" > redis-6381-hefery.conf
		编辑redis-6381-hefery.conf
	    启动:在redis路径下
	    	redis-server redis-6381-hefery.conf
    redis-cli客户端:
        连接:redis-cli -h 127.0.0.1 -p 7000 -a 123456
        退出:quit
    关闭: 
        停止redis-server服务: redis-server &
        redis-cli -p 6379 shutdown
    查看启动:
        ps -ef | grep redis
        ps aux | grep redis
    Windows连接Linux Redis:
    	关闭防火墙:systemctl stop firewalld
		redis.conf设置bind 0.0.0.0

Redis API

Redis 的数据结构:Redis 存储的是 key:value 格式的数据,其中 key 都是字符串,value 有不同的数据结构
字符串类型 string
哈希类型 hash : map 格式
列表类型 list : linkedlist 格式。支持重复元素
集合类型 set : 不允许重复元素
有序集合类型 sortedset:不允许重复元素,且元素有顺序

字符类型 string

结构:key : value

场景:
	缓存:热点数据、对象缓存、全页缓存
	计数器:个人主页的访问量(incr userid:pageview)
	分布式锁:setnx只有不存在才会添加成功
	数据共享:spring-session-data-redis

API:
	设置;
		不管key是否存在:set key value
		key不存在才设置:setnx key value
		key是存在才设置;set key value xx
		批量设置key:   mset key1 key2...
		设置新值返回旧值:getset key newvalue
		value追加:    append key value
		设置字符串指定下标的所有值:setrange key index value
	获取:
		单个获取:get key
		批量获取:mget key1 key2...
		字符串长度:strlen key(一个中文两个字节)
		字符串指定下标的所有值:getrange key stsrt end(index从0开始)
	删除:del key
	自增:
		整数自增:incr key(自增1)、incrby key 66(自增66)
		浮点自增:incrbyfloat key 6.6(自增6.6)
	自减:
		整数自减:decr key(自减1)、decrby key 66(自减66)
		浮点自减:decrbyfloat key 6.6(自减6.6)

哈希类型 hash

结构:key : field(n)-value(n)

场景:
	计数器:个人主页的访问量(hincrby user:1:info pageview count)(incr)
	全局ID:分库分表(incrby)
	购物车:
	点赞、签到、打卡:
		微博ID=t1001、用户ID=u3001、点赞用户:like:t1001来维护所有为这条微博点赞的用户
		微博点赞:sadd like:t1001 u3001
		取消点赞:srem like:t1001 u3001
		是否点赞:sismember like:t1001 u3001
		点赞用户:smember like:t1001 u3001
		点赞数量:scard like:t1001
	用户关注:follow 关注 fans 粉丝
		相互关注:
			sadd 1:follow 2
			sadd 2:fans 1
			sadd 1:fans 2
			sadd 2:follow 1
		我关注的人也关注她:交集
			sinter 1:follow 2:fans
		可能认识的人:
			用户1可能认识的人(差集):sdiff 2:follow 1:follow
			用户2可能认识的人(差集):sdiff 1:follow 2:follow
	排行榜:新闻ID=6001
		新闻点击数+1:zincrby hotNews:20211111 1 n6001
		今天点击最多:zrevrange hotNews:20211111 0 1 withscores


API:
	设置:
		设置hash key对应field的value:hsetnx key field value(如果field已存在则失败)
		设置hash key filed的value:hset key field value
		批量设置hash key filed的value:hmset key1 key2...
	获取:
		获取hash key filed的value:hget key field
		批量hash key filed的value:hmget key1 key2...
		获取所有hash key对应的key-filed的value:hgetall key
		获取所有hashkey对应的field:hkeys key
		获取所有hash key对应的filed的value:hvals key
		获取hash key field数量:hlen key
	删除:
		删除hash key filed:hdel key field
	自增:
		整数自增:hincr key field(自增1)、hincr key field 66(自增66)
		浮点自增:hincrbyfloat key field 6.6(自增6.6)
	自减:
		整数自减:hdecrby key field(自减1)、hdecrby key field 66(自减66)
		浮点自减:hdecrbyfloat key field 6.6(自减6.6)
	判断:
		判断hash key是否存在field:hexists key field

列表类型 list

结构:key : elements(有序;可重)

场景:
	用户消息时间线timeline:双向链表,插入有序
	消息队列:blpop、brpop,可设置超时时间(队列:rpush blpop左头右尾,右进左出  栈:rpush brpop先进后出)

API:
	设置:
		从右向list插入值:rpush key value1 value2...
		从左向list插入值:lpush key value1 value2...
		在list指定的value值前或后插入newvalue:linsert key before|after value newvalue
		设置list指定index的item为newvalue:lset key index newvalue
	弹出:
		从list左侧弹出item:lpop key
		从list右侧弹出item:rpop key
		根据count的值弹出所有value相等的item:lrem key count value
			count > 0:从左到右,弹出最多count个value相等的item
			count < 0:从右到左,弹出最多Mash.abs(count)个value相等的item
			count = 0:删除所有value相等的item
		按照index范围修剪list:itrim key start end
		lpop阻塞版本:blpop key timeout(timeout为超时时间,为0即永不阻塞)
		rpop阻塞版本:brpop key timeout(timeout为超时时间,为0即永不阻塞)
	获取:
		获取list的(start, end]的所有item:lrange key start end
			PS:a b c d e f:index从左到右0~5,从右到左-1~-6
		获取list指定index的item:lindex key index
		获取list长度:llen key

集合类型 set

结构:key : values(无序;无重)

API:
	添加:
		添加value:sadd key value(如果value已存在则失败)
	删除:
		删除value:srem key value
		从set随机弹出一个元素:spop key
	获取:
		获取set大小:scard key
		从set随机挑选count个元素:srandmember key count
		获取set所有value:smembers key
	判断;
		判断value是否在set中:sismember key(1存在)
	集合间的操作:
		差集:sdiff key1 key2
		交集:sinter key1 key2(共同关注)
		并集:sunion key1 key2

有序集合 sortedset

结构:key : [score-value](有序;无重)

场景:
	排行榜

API:
	添加:
		添加score-value:zadd key score value(score可重,value不重)
	删除:
		删除score-value:zrem key value
		删除指定排名的升序元素:zremrangebtrank key stsrt end
		删除指定分数范围内的升序元素:zremrangebyscore key minscore maxscore
	获取:
		获取value的score:zscore key value
		增加或减少value的score:zincrby key increscore value
		获取集合元素个数;zcard key
		获取指定范围(按score升序)内元素:zrange key start end
		获取指定分数范围内的升序元素:zrangebyscore key minscore maxscore
		zrevrank
		zrevrange
		zrevrangebyscore

通用命令

keys:
	所有的key:keys *
	key得数量:dbsize
	key的正则:keys [pattern]

exists:
	检查key是否存在:exists key

del:
	删除指定的key-value:del key1 key2...

expire:
	设置key的过期时间:expire key seconds
	查看key的剩余时间:ttl key(-2代表key已经不存在)
	去掉key的过期时间:persist key((-1代表key存在,并且没有过期时间)

type:
	查看key的类型:type key(string、hash、list、set、zset、none)

Redis 客户端

Jedis

Redis 的 java 客户端

Jedis 配置参数 参数含义 默认 建议
资源数控制
maxTotal 资源池最大连接数 8 后面讨论
maxIdle 资源池允许最大空闲连接数 8 建议 maxTotal
minIdle 资源池允许最小空闲连接数 0 后面讨论
jmxEnabled 是否开启 jmx 监控 true 建议开启
借还参数
blockWhenExhausted 当资源池用尽后,调用者是否要等待。当为 true 时,maxWaitMillis 才生效 true 建议使用默认值
maxWaitMillis 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒) -1:永不超时 不建议使用默认值
testOnBorrow 向资源池借用连接时是否做连接有效性检测(ping),无效连接会被移除 false 建议 false
testOnReturn 向资源池归还连接时是否做连接有效性检测(ping),无效连接会被移除 false 建议 false
maxTotal:根据业务而定
	命令平均执行时间0.1ms=0.001s;业务需要5w QPS;maxTotal理论值=0.001*5w=50,实际值需要更大
	业务希望Redis并发量;客户端执行命令时间;Redis资源(应用个数Nodes * maxTotal <= Redis最大连接数maxclients)

maxIdle:建议maxIdle=maxTotal
	减少创建新连接的开销
maxIdle:建议预热maxIdle
	减少第一次启动后的新连接开销

获取超时:
	原因:
		慢查询阻塞,连接池被堵住
		资源池参数配置不合理(QPS高,连接池容量小)
		连接泄露(没有close,比较难定位)

Redis 功能

慢查询

慢查询发生在Redis执行命令阶段

配置:
	slowlog-max-len:
		默认128(先进先出的队列 + 固定长度 + 保存在内存)
		不要设置过大,默认10ms,通常设置1ms
	slowlog-log-slower-than:默认1w
		1. 慢查询阈值(单位:微秒)
		2. slowing-log- slower-than=0,记录所有命令
		3. slowing-og- slower-than<0,不记录任何命令
		sawlog-log- slower-than不要设置过小,通常设置1000左右
	动态配置:
		config set slowlog-max-len 1000
		config set slowing-og- slower-than 1000

API:
	获取慢查询队列:slowlog get [n]
	获取慢查询队列长度:slowlog len
	清空慢查询队列:slowlog reset

流水线 Pipeline

Redis执行命令都是微妙级别
pipeline每次条数要控制(网络)

注意每次 pipeline携带数据量
pipeline每次只能作用在一个 Redis节点上
命令 N 个命令操作 1 次 pipeline(n 个命令)
时间 n 次网络传输时间 +n 次命令执行时间 1 次网络传输时间 +1 次命令执行时间
数据量 1 条命令 n 条命令

发布订阅

角色:
	发布者publisher:
	订阅者subscriber:
	频道channel:

模型:
	发布者(redis-cli-publisher) -> Sohutv频道(redis-server) -> 订阅者(redis-cli-subscriber)

API:
	发布消息:publish channel message
	订阅消息:subscribe [channel(n)]
	取消订阅:unsubscribe [channel(n)]

BitMap

位图:二进制

API:
	给位图指定索引设置值:setbit key offset value
	获取位图指定范围(start到end,单位字节,如果不指定就获取全部)位值为1的个数:bitcount key [start end]
	计算位图指定范围(start到end,单位字节,如果不指定就获取全部)第一个偏移量对应的值等于targetBit的位置:bitpos key targetBit [start]  [end]

HperLogLog

基于HyperLogLog算法:极小空间完成独立数量统计

API:
	向hyperloglog添加元素:fadd key element1 element2...
	计算hyperloglog的独立总数:pfcount key1 key2...
	合并多个 hyperloglog:pfmerge destkey sourcekey1 sourcekey2…

GEO

GEO(地理信息定位):存储经纬度,计算两地距离,范围计算等

API:
	增加地理位置信息:geo key longitude latitude member [longitude latitude member]
	获取地理位置信息:geopos key member [member]
	获取两个地理位置的距离;geodist key memberl member2 [unit]  unit:m(米)、km(千米)、mi(英里)、ft(尺)
	获取指定位置范围内的地理位置信息集合:
		georadius key longitude latitude radium|km|ft|mi 
			[withcoord] :返回结果中包含经纬度
			[withdist]  :返回结果中包含距离中心节点位置
			[withhash]  :返回结果中包含 geohash COUNT count:指定返回结果的数量
			[COUNT count]:指定返回结果数量
			[asc|desc]  :返回结果按照距离中心节点的距离做升序或者降序
			[store key] :将返回结果的地理位置信息保存到指定键
			[storedist key]:将返回结果距离中心节点的距离保存到指定键
		georadiusbymember key member radium|km|ft|mi
			[withcoord] :返回结果中包含经纬度
			[withdist]  :返回结果中包含距离中心节点位置
			[withhash]  :返回结果中包含 geohash COUNT count:指定返回结果的数量
			[COUNT count]:指定返回结果数量
			[asc|desc]  :返回结果按照距离中心节点的距离做升序或者降序
			[store key] :将返回结果的地理位置信息保存到指定键
			[storedist key]:将返回结果距离中心节点的距离保存到指定键

Redis 持久化

Redis 是一个内存数据库,当 redis 服务器重启,获取电脑重启,数据会丢失,可以将 redis 内存中的数据持久化保存到硬盘的文件中

Redis 持久化:Redis 所有数据保存在内存中,对数据的更新异步保存到磁盘

持久化方式:RDB:拍快照、AOF:写日志

Redis 持久化机制

1.RDB:默认方式,不需要进行配置
	在一定的间隔时间中,检测key的变化情况,保存某个时间点的全量数据,然后持久化数据
	1.编辑redis.windwos.conf文件
		#   after 900 sec (15 min) if at least 1 key changed
		save 900 1
		#   after 300 sec (5 min) if at least 10 keys changed
		save 300 10
		#   after 60 sec if at least 10000 keys changed
		save 60 10000
	2.重新启动redis服务器,并指定配置文件名称
		W:\JAVA\Redis\redis\windows-64\redis-2.8.9>redis-server.exe redis.windows.conf

2.AOF:日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据
	1. 编辑redis.windwos.conf文件
		appendonly no(关闭aof) --> appendonly yes (开启aof)
		# appendfsync always : 每一次操作都进行持久化
		appendfsync everysec : 每隔一秒进行一次持久化
		# appendfsync no	 : 不进行持久化

3.RDB-AOF混合:BGSave全量持久化,AOF增量持久化
API RDB AOF
启动优先级
体积
恢复速度
数据安全 丢数据 策略决定
轻重

RDB

触发机制:
	save(同步):通常会阻塞Redis
		redis-cli执行save命令,redis-sever创建RDB二进制文件(新的文件替换旧文件)
	bgsave(异步):不会阻塞Redis,但会fork新进程
		redis-cli执行bgsave命令,redis-sever执行fork(),创建子进程完成创建RDB二进制文件(新的文件替换旧文件)
	自动:
		#save 900 1
		#save 300 10
		#save 60 10000
		dbfilename dump-${port}.rdb		 # RDB文件名
		dir /bigdiskpath
		stop-writes-on-bgsave-error yes  # 报错,终止bgsave
		rdbcompression yes
		rdbchecksum yes

复制方式:
	全量复制:
	debug reload:
	shutdown:

RDB问题:
	耗时-O(n);耗性能-fork()消耗内存;Disk IO-IO性能
	不可控,丢失数据
API save bgsave
IO 类型 同步 异步
阻塞 阻塞 阻塞(fork 阶段)
复杂度 O(n) O(n)
优点 不会消耗额外内存 不阻塞客户端命令
缺点 阻塞客户端命令 需要 fork,消耗内存

AOF

触发机制:
	always:
		redis-cli写命令刷新缓冲区,缓冲区中每条命令fync到硬盘的AOF文件
	everysec:
		redis-cli写命令刷新缓冲区,每秒把缓冲区fync到硬盘的AOF文件
	no:
		redis-cli写命令刷新缓冲区,OS决定缓冲区中命令fync到硬盘的AOF文件

AOF重写:
	重写:
		set hello world; set hello java;  ->  set hello java
		incr counter; incr counter;       ->  set counter 2
	作用:减少硬盘占用量;加速恢复速度
	操作:
		redis-cli执行bgrewriteaof,redis-sever执行fork(),创建子进程实现AOF重写
	配置:
		auto-aof-rewrite-min-size    # AOF文件重写需要的尺寸
		auto-aof-rewrite-percentage  # AOF文件增长率
		aof_current_size             # AOF当前尺寸(单位:字节)
		aof-base-size				 # AOF上次启动和重写的尺寸(单位:字节)
		------------------------------------------------------------------
		appendonly yes
		appendfilename “appendonly-${port}.aof”
		appendfsync everysec
		dir /bigdiskpath
		no-appendfsync-on-rewrite yes
		auto-aof-rewrite-percentage 100
		auto-aof-rewrite-min-size 64mb
	自动:同时满足
		aof_current_size > auto-aof-rewrite-min-size
		(aof_current_size - aof-base-size) | (aof-base-size > auto-aof-rewrite-percentage)
API always everysec no
优点 不丢失数据 每秒一次 fsync,丢 1s 数据 不用管
缺点 IO 开销大 丢 1s 数据 不可控

开发运维问题

fork 操作
特点:
	1. 同步操作:比较快
	2. 与内存量息息相关:内存越大,耗时越长(与机型相关)
	3. 查看fork info:latest_fork_usec

改善:
	优先使用物理机或高效支持fork操作的虚拟化技术
	控制Redis实例最大可用内存Lmaxmemory
	合理配置Linux内存分配策略:vm.overcommit_memory=1
	降低fork频率:放宽AOF重写自动触发时机,不必要全量复制
子进程开销和优化
1.CPU
	开销:RDB和AOF文件生成,属于CPU密集型
	优化:不做CPU绑定,不和CPU密集型应用部署

2.内存
	开销:fork内存开销,copy-on-write会开一个副本
	优化:echo never > /sys/kernel/mm/transparent_hugepage/enabled

3.硬盘
	开销:AOF和RDB文件写入,iostat、iotop分析
	优化:
		不要和高硬盘负载服务(存储服务、消息队列)部署在一起
		no-appendfsync-on-write=yes
		根据写入量决定磁盘类型:ssd
		单机多实例持久化文件目录考虑分盘
AOF 追加阻塞
流程:
	主线程写入到AOF缓冲区,每秒同步到硬盘,对比上次fsync时间(大于2s-阻塞;小于2s-通过)
定位:
	Redis日志:Asynchronous AOF fsync is taking too long(disk is busy?)
	命令info persistence:aof_delayed_fsync:100

Redis 主从复制

主从复制

单机问题:机器故障;容量瓶颈;QPS瓶颈

作用:数据副本;拓展读性能
特点:
	一个master可以有多个slave
	一个slave只能有一个master
	数据流单向:master->slave

实现:
	salveoof:无需重启
		复制:redis-cli执行salveoof 127.0.0.1 6379命令,redis-cli-slave(6380)复制给redis-cli-master(6379)
		取消:redis-cli执行salveoof no one命令,redis-cli-slave(6380)取消复制给redis-cli-master(6379)
	配置:统一配置,需要重启
		slaveof ip port
		slave-read-only yes
# redis-master-6379.conf
daemonize yes
pidfile /var/run/redis-master-6379.pid
port 6379
logfile "master-6379.log"
#save 900 1
#save 300 10
#save 60 10000
dbfilename dump-master-6379.rdb
dir /home/hefery/Downloads/Redis/redis/data/log
appendonly yes
appendfilename "appendonly-master-6379.aof"

# redis-slave-6380.conf
daemonize yes
pidfile /var/run/redis-slave-6380.pid
port 6380
logfile "slave-6380.log"
#save 900 1
#save 300 10
#save 60 10000
dbfilename dump-slave-6380.rdb=
dir /home/hefery/Downloads/Redis/redis/data/log
slaveof 127.0.0.1 6380
appendonly yes
appendfilename "appendonly-slave-6380.aof"


# 连接master-cli,查看master信息
> redis-server redis-master-6379.conf
> redis-cli
> info replication
# 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

# 连接slave-cli,查看slave信息
> redis-server redis-slave-6380.conf
> redis-cli -p 6380 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:10
master_sync_in_progress:0
slave_repl_offset:71
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

# 连接master-cli录入key,去slave-cli查看
> redis-cli
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> exit
> redis-cli -p 6380
127.0.0.1:6380> get hello
"world"

# slave-cli不能写入key
127.0.0.1:6380> set hello hefery
(error) READONLY You can't write against a read only slave.

全量复制和部分复制

# 查看Redis的run_id
redis-cli -p 6379 info server | grep run
redis-cli -p 6380 info server | grep run

全量复制

全量复制开销;
	bgsave时间
    RDB文件网络传输时间
    从节点清空数据时间从
    节点加载RDB的时间可
    能的AOF重写时间

全量复制.png

部分复制

部分复制.png

自动故障转移

slave 宕机

slave 宕机处理.png

master 宕机

master 宕机处理.png

开发运维问题

读写分离
简述:redis-cli写数据在master,读数据在slave
问题:
	复制数据延迟
	读到过期数据
	从节点故障
主从配置不一致
1. maxmemory配置不一致,导致丢数据
2. 数据结构优化参数(hash-max-ziplist-entries),导致内存不一致
规避全量复制
第一次全量复制无法避免
解决:
	夜间低峰处理

节点运行ID不匹配
解决:
	主节点重启(运行ID变化)
	故障转移(集群/哨兵)

复制积压缓冲区不足
原因:网络中断,部分复制无法满足
解决:增大复制缓冲区配置rel_backlog_size,网络增强
规避复制风暴
单主节点复制风暴:
	问题:主节点重启,多从节点复制
	解决:更换复制拓扑

单机器复制风暴:
	机器宕机后,大量全量复制
	主节点分散多机器

Redis Sentinel 故障转移

搭建 Redis Sentinel

# 配置主节点7000
vim redis-sentinel-7000.conf
port 7000
daemonize yes
pidfile /var/run/redis-sentinel-7000.pid
logfile "redis-sentinel-7000.log"
dir /home/hefery/Downloads/Redis/redis/data/log

# 配置从节点7001+7002
sed "s/7000/7001/g" redis-sentinel-7000.conf > redis-sentinel-7001.conf
sed "s/7000/7002/g" redis-sentinel-7000.conf > redis-sentinel-7002.conf

# 声明主从
echo "slaveof 127.0.0.1 7000" >> redis-sentinel-7001.conf
echo "slaveof 127.0.0.1 7000" >> redis-sentinel-7002.conf

# 启动redis-server
redis-server redis-sentinel-7000.conf
redis-server redis-sentinel-7001.conf
redis-server redis-sentinel-7002.conf

# 查看redis-server启动情况
ps -ef | grep redis-server
root      14251      1  0 22:52 ?        00:00:00 redis-server *:7000
root      16094      1  0 22:53 ?        00:00:00 redis-server *:7001
root      16499      1  0 22:53 ?        00:00:00 redis-server *:7002
root      16686 101512  0 22:53 pts/5    00:00:00 grep --color=auto redis-server

# 查看主节点分片情况
redis-cli -p 7000 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7001,state=online,offset=169,lag=0
slave1:ip=127.0.0.1,port=7002,state=online,offset=169,lag=1
master_repl_offset:169
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:168


# 回到redis路径,Copy sentinel.conf到config路径下
cp sentinel.conf ./config/

# 精简sentinel.conf
cat sentinel.conf | grep -v "#" | grep -v "^$" > redis-sentinel-26379.conf

# 配置 redis-sentinel-26379.conf
port 26379
dir /home/hefery/Downloads/Redis/redis/data/log
daemonize yes
logfile "redis-sentinel-26379.log"
sentinel monitor mymaster 127.0.0.1 7000 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

# 启动redis-sentinel
redis-sentinel redis-sentinel-26379.conf
# 查看redis-sentinel启动情况
ps -ef | grep redis-sentinel

# redis-cli连接redis-sentinel
redis-cli -p 26379

# 查看redis-sentinel-26379.conf发生改变
> cat redis-sentinel-26379.conf
port 26379
dir "/home/hefery/Downloads/Redis/redis-3.0.7/data/log"
daemonize yes
logfile "redis-sentinel-26379.log"
sentinel monitor mymaster 127.0.0.1 7000 2
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel known-slave mymaster 127.0.0.1 7002
# Generated by CONFIG REWRITE
sentinel known-slave mymaster 127.0.0.1 7001
sentinel current-epoch 0

# 增加redis-sentinel-26380.conf+redis-sentinel-26381.conf
sed "s/26379/26380/g" redis-sentinel-26379.conf > redis-sentinel-26380.conf
sed "s/26379/26381/g" redis-sentinel-26379.conf > redis-sentinel-26381.conf

# 启动26380+26381
redis-sentinel redis-sentinel-26380.conf
redis-sentinel redis-sentinel-26381.conf
ps -ef | grep redis-sentinel

# 登录26379查看 slaves=2,sentinels=3
> info
# Server
redis_version:3.0.7
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:b32e406b52b17276
redis_mode:sentinel
os:Linux 3.10.0-1160.42.2.el7.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.8.5
process_id:33120
run_id:c40b4fa3fb2f21de80e2fab27ce29176995c6c16
tcp_port:26379
uptime_in_seconds:39
uptime_in_days:0
hz:15
lru_clock:6662430
config_file:/home/hefery/Downloads/Redis/redis-3.0.7/config/redis-sentinel-26379.conf
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster,status=ok,address=127.0.0.1:7000,slaves=2,sentinels=3

ps -ef | grep redis
root      14251      1  0 22:52 ?        00:00:01 redis-server *:7000
root      16094      1  0 22:53 ?        00:00:01 redis-server *:7001
root      16499      1  0 22:53 ?        00:00:01 redis-server *:7002
root      33120      1  0 23:25 ?        00:00:00 redis-sentinel *:26379 [sentinel]
root      33663      1  0 23:25 ?        00:00:00 redis-sentinel *:26380 [sentinel]
root      33981      1  0 23:25 ?        00:00:00 redis-sentinel *:26381 [sentinel]

Jedis 客户端测试

package com.hefery;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;

import java.security.PrivateKey;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @Author:Hefery
 * @Version:1.0.0
 * @Date:2021/10/12 23:43
 * @Description:
 */
public class RedisSentinelFailoverTest {

    private static Logger logger =  LoggerFactory.getLogger(RedisSentinelFailoverTest.class);

    public static void main(String[] args) {

        String masterName = "mymaster";
        Set<String> sentinels = new HashSet<String>();
        sentinels.add("192.168.1.13:26379");
        sentinels.add("192.168.1.13:26380");
        sentinels.add("192.168.1.13:26381");
        JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, sentinels);

        int counter = 0;

        while (true) {
            counter++;
            Jedis jedis = null;
            try {
                jedis = jedisSentinelPool.getResource();
                int index = new Random().nextInt(100000);
                String key = "k-" + index;
                String value = "v-" + index;
                jedis.set(key, value);
                if (counter % 100 == 0) {
                    logger.info("{} value is {}", key, jedis.get(key));
                }
                TimeUnit.MICROSECONDS.sleep(10);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }

    }

}

定时任务

  1. 每 10 秒每个 sentinel 对 master 和 slave 执行 info
    发现 slave 节点
    确认主从关系
  2. 每 2 秒每个 dentine 通过 master 节点的 channel 交换信息(pub/sub)
    通过_sentinel_:hell 频道交互
    交互对节点的“看法”和自身信息
  3. 每 1 秒每个 sentinel 对其他 sentinel 和 reds 执行 ping

Redis Cluster 集群

数据分布

实现:

分布形式 特点 产品
顺序分区 数据分散度高;键值分布与业务无关,无法顺序访问;支持批量操作 Redis Cluster
哈希分区 数据分散易倾斜;键值分布与业务相关,可顺序访问;支持批量操作 HBase,BigTable

哈希分区:

节点取余分区hash(key)%nodes
	3 nodehash(key)%3):[1-100] -> [3 6 9...]+[1 4...100]+[2 5...98]
	4 nodehash(key)%4):[1-100] -> [4 8...100]+[1 5...97]+[2 6...98]+[3 7...99]
	扩容
		添加单个数据迁移80%
		倍数扩容数据迁移50%
一致性哈希分区token顺时针分配
	节点伸缩只影响临近节点还是有数据迁移
	翻倍伸缩保证最小迁移数据和负载均衡
虚拟槽分区
	预设虚拟槽每个槽映射一个数据子集一般比节点数大
	良好的哈希函数CRC16
	服务端管理节点数据

搭建集群

集群架构:
	装备节点:
	meet:沟通(连通图),节点共享消息
	指派槽:给节点指派槽
	主从复制:

原生命令

装备节点
port ${port}
daemonize yes
dir /home/hefery/Downloads/Redis/redis/data/log
dbfilename "dump-cluster-${port}.rdb"
logfile "redis-cluster-${port}.log"
cluster-enabled yes
cluster-config-file redis-cluster-nodes-${port}.conf
cluster-node-timeout 15000
cluster-require-full-coverage no

sed 's/7000/7001/g' redis-cluster-7000.conf >  redis-cluster-7001.conf
sed 's/7000/7002/g' redis-cluster-7000.conf >  redis-cluster-7002.conf
sed 's/7000/7003/g' redis-cluster-7000.conf >  redis-cluster-7003.conf
sed 's/7000/7004/g' redis-cluster-7000.conf >  redis-cluster-7004.conf
sed 's/7000/7005/g' redis-cluster-7000.conf >  redis-cluster-7005.conf
redis-server redis-cluster-7000.conf
redis-server redis-cluster-7001.conf
redis-server redis-cluster-7002.conf
redis-server redis-cluster-7003.conf
redis-server redis-cluster-7004.conf
redis-server redis-cluster-7005.conf

# 查看cluster启动情况
> ps -ef | grep redis
root       2438      1  0 23:17 ?        00:00:00 redis-server *:7000 [cluster]
root       3141      1  0 23:18 ?        00:00:00 redis-server *:7001 [cluster]
root       3395      1  0 23:18 ?        00:00:00 redis-server *:7002 [cluster]
root     114738      1  0 23:11 ?        00:00:00 redis-server *:7003 [cluster]
root     115012      1  0 23:11 ?        00:00:00 redis-server *:7004 [cluster]
root     115527      1  0 23:11 ?        00:00:00 redis-server *:7005 [cluster]
# 查看cluster info信息
> redis-cli -p 7000 cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:0
cluster_current_epoch:0
cluster_my_epoch:0
cluster_stats_messages_sent:0
cluster_stats_messages_received:0
meet
redis-cli -p 7000 cluster meet 127.0.0.1 7001
redis-cli -p 7000 cluster meet 127.0.0.1 7002
redis-cli -p 7000 cluster meet 127.0.0.1 7003
redis-cli -p 7000 cluster meet 127.0.0.1 7004
redis-cli -p 7000 cluster meet 127.0.0.1 7005

> redis-cli -p 7005 cluster nodes
528be9a68ce532a31b838724da23646834c2abf4 127.0.0.1:7003 master - 0 1634140138180 3 connected
523d5b8cfd4e32a65629ff6acd09e933966448ec 127.0.0.1:7004 master - 0 1634140139196 4 connected
6096428be6d89c2c5d43a2125a766fc88a77ba78 127.0.0.1:7001 master - 0 1634140135134 0 connected
10b0195040a76686ebb222ffa363d45bcf4a3de0 127.0.0.1:7000 master - 0 1634140137164 1 connected
326389de2601f42cc48fdd3151c4629b5c3dcdc3 127.0.0.1:7005 myself,master - 0 0 5 connected
2ac8fdc0854b74502bf0b284cf37bd8c82a06d79 127.0.0.1:7002 master - 0 1634140136148 2 connected

# 查看cluster info信息
> redis-cli -p 7000 cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:0
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_sent:2306
cluster_stats_messages_received:2306
指派槽
使用脚本:vim addslots.sh
start=$1
end=$2
port=$3
for slot in `seq ${start} ${end}`
do
   echo "slot:${slot}"
   redis-cli -p ${port} cluster addslots ${slot}
done

sh addslots.sh 0 5461 7000
sh addslots.sh 5462 10922 7001
sh addslots.sh 10923 16383 7002

# 查看cluster info信息
> redis-cli -p 7005 cluster nodes
528be9a68ce532a31b838724da23646834c2abf4 127.0.0.1:7003 master - 0 1634141761402 3 connected
523d5b8cfd4e32a65629ff6acd09e933966448ec 127.0.0.1:7004 master - 0 1634141758380 4 connected
6096428be6d89c2c5d43a2125a766fc88a77ba78 127.0.0.1:7001 master - 0 1634141763418 0 connected 5462-10922
10b0195040a76686ebb222ffa363d45bcf4a3de0 127.0.0.1:7000 master - 0 1634141760394 1 connected 0-5461
326389de2601f42cc48fdd3151c4629b5c3dcdc3 127.0.0.1:7005 myself,master - 0 0 5 connected
2ac8fdc0854b74502bf0b284cf37bd8c82a06d79 127.0.0.1:7002 master - 0 1634141762410 2 connected 10923-16383
主从复制
从:7003  主:7000
	# redis-cli -p 7003 cluster replicate ${node-id-7000}
	redis-cli -p 7003 cluster replicate 10b0195040a76686ebb222ffa363d45bcf4a3de0
从:7003  主:7000
	# redis-cli -p 7004 cluster replicate ${node-id-7001}
	redis-cli -p 7004 cluster replicate 6096428be6d89c2c5d43a2125a766fc88a77ba78
从:7003  主:7000
	# redis-cli -p 7005 cluster replicate ${node-id-7002}
	redis-cli -p 7005 cluster replicate 2ac8fdc0854b74502bf0b284cf37bd8c82a06d79

# 查看cluster info信息
> redis-cli -p 7000 cluster nodes
528be9a68ce532a31b838724da23646834c2abf4 127.0.0.1:7003 slave 10b0195040a76686ebb222ffa363d45bcf4a3de0 0 1634142192928 3 connected
6096428be6d89c2c5d43a2125a766fc88a77ba78 127.0.0.1:7001 master - 0 1634142194947 0 connected 5462-10922
2ac8fdc0854b74502bf0b284cf37bd8c82a06d79 127.0.0.1:7002 master - 0 1634142195956 2 connected 10923-16383
326389de2601f42cc48fdd3151c4629b5c3dcdc3 127.0.0.1:7005 slave 2ac8fdc0854b74502bf0b284cf37bd8c82a06d79 0 1634142193938 5 connected
523d5b8cfd4e32a65629ff6acd09e933966448ec 127.0.0.1:7004 slave 6096428be6d89c2c5d43a2125a766fc88a77ba78 0 1634142191916 4 connected
10b0195040a76686ebb222ffa363d45bcf4a3de0 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5461

# 查看cluster slot信息
> redis-cli -p 7000 cluster slots
1) 1) (integer) 5462
   2) (integer) 10922
   3) 1) "127.0.0.1"
      2) (integer) 7001
   4) 1) "127.0.0.1"
      2) (integer) 7004
2) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "127.0.0.1"
      2) (integer) 7002
   4) 1) "127.0.0.1"
      2) (integer) 7005
3) 1) (integer) 0
   2) (integer) 5461
   3) 1) "127.0.0.1"
      2) (integer) 7000
   4) 1) "127.0.0.1"
      2) (integer) 7003

Ruby

下载编译安装 Ruby
wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
tar -xvf ruby-2.3.1.tar.gz
cd ruby-2.3.1
./configure #-prefix=/usr/local/ruby 
make && make install

cd bin/usr/local/rub 
cp bin/ruby/usr/local/bin 
cp bin/gem /usr/local/bin

# 查看ruby版本
ruby -v
安装 rubygem redis
wget http://rubygems.org/downloads/redis-3.3.0.gem 
gem install -l redis-3.3.0.gem 
gem list -- check redis gem
安装 redis-trib.rb
# cp $(REDIS_ HOME)/src/redis-trib. rb /usr/local/bin

> cd Downloads/Redis/redis/src
> ./redis-trib.rb
Usage: redis-trib <command> <options> <arguments ...>

  create          host1:port1 ... hostN:portN
                  --replicas <arg>
  check           host:port
  info            host:port
  fix             host:port
                  --timeout <arg>
  reshard         host:port
                  --from <arg>
                  --to <arg>
                  --slots <arg>
                  --yes
                  --timeout <arg>
                  --pipeline <arg>
  rebalance       host:port
                  --weight <arg>
                  --auto-weights
                  --use-empty-masters
                  --timeout <arg>
                  --simulate
                  --pipeline <arg>
                  --threshold <arg>
  add-node        new_host:new_port existing_host:existing_port
                  --slave
                  --master-id <arg>
  del-node        host:port node_id
  set-timeout     host:port milliseconds
  call            host:port command arg arg .. arg
  import          host:port
                  --from <arg>
                  --copy
                  --replace
  help            (show this help)

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.
准备节点
port 7000
daemonize yes
dir /home/hefery/Downloads/Redis/redis/data/log
dbfilename "dump-cluster-ruby-7000.rdb"
logfile "redis-cluster-ruby-7000.log"
cluster-enabled yes
cluster-config-file redis-cluster-ruby-nodes-7000.conf
cluster-require-full-coverage no


sed 's/7000/7001/g' redis-cluster-ruby-7000.conf > redis-cluster-ruby-7001.conf
sed 's/7000/7002/g' redis-cluster-ruby-7000.conf > redis-cluster-ruby-7002.conf
sed 's/7000/7003/g' redis-cluster-ruby-7000.conf > redis-cluster-ruby-7003.conf
sed 's/7000/7004/g' redis-cluster-ruby-7000.conf > redis-cluster-ruby-7004.conf
sed 's/7000/7005/g' redis-cluster-ruby-7000.conf > redis-cluster-ruby-7005.conf

redis-server redis-cluster-ruby-7000.conf
redis-server redis-cluster-ruby-7001.conf
redis-server redis-cluster-ruby-7002.conf
redis-server redis-cluster-ruby-7003.conf
redis-server redis-cluster-ruby-7004.conf
redis-server redis-cluster-ruby-7005.conf

> ps -ef | grep redis-server
root      72881      1  0 01:25 ?        00:00:00 redis-server *:7000 [cluster]
root      73203      1  0 01:25 ?        00:00:00 redis-server *:7001 [cluster]
root      73436      1  0 01:25 ?        00:00:00 redis-server *:7002 [cluster]
root      73756      1  0 01:25 ?        00:00:00 redis-server *:7003 [cluster]
root      73992      1  0 01:25 ?        00:00:00 redis-server *:7004 [cluster]
root      74733      1  0 01:26 ?        00:00:00 redis-server *:7005 [cluster]

# redia路径下的redis-trib.rb
cd Downloads/Redis/redis/src
# 使用create创建nodes(选yes)
./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

集群扩容

加入集群作用:

准备新节点

port ${port}
daemonize yes
dir /home/hefery/Downloads/Redis/redis/data/log
dbfilename "dump-cluster-${port}.rdb"
logfile "redis-cluster-${port}.log"
cluster-enabled yes
cluster-config-file redis-cluster-nodes-${port}.conf
cluster-node-timeout 15000
cluster-require-full-coverage no

sed 's/7000/7006/g' redis-cluster-7000.conf >  redis-cluster-7006.conf
sed 's/7000/7007/g' redis-cluster-7000.conf >  redis-cluster-7007.conf

redis-server redis-cluster-7006.conf
redis-server redis-cluster-7007.conf

加入集群

redis-cli -p 7000 cluster meet 127.0.0.1 7006
redis-cli -p 7000 cluster meet 127.0.0.1 7007

主从复制

# 7007 replicate 7006
redis-cli -p 7007 cluster replicate 6a0d5b1f102b16ff88a0b621bc2f037798540689

redis-cli -p 7000 cluster nodes
bc125b4301176b75a20b77da37ce5a5111619ccf 127.0.0.1:7004 slave d5f6c8b25537422fde987f4c802d9fb59a07ff94 0 1634223198639 5 connected
c19b6520969c1fc4c0e50475fe73fa1a8ec54fda 127.0.0.1:7002 master - 0 1634223200660 3 connected 10923-16383
4aabd108347571d1123778bfeaddcd7a8e602194 127.0.0.1:7007 slave 6a0d5b1f102b16ff88a0b621bc2f037798540689 0 1634223199650 7 connected
6830085d334270a61508afb3c96e4f6648efb02f 127.0.0.1:7003 slave 49f14b591968ed22462b3a34262f7dcb9e6a8b92 0 1634223201670 4 connected
2163c9c61d776d5f266c1ab687f7cefc7f209feb 127.0.0.1:7005 slave c19b6520969c1fc4c0e50475fe73fa1a8ec54fda 0 1634223195612 6 connected
49f14b591968ed22462b3a34262f7dcb9e6a8b92 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
d5f6c8b25537422fde987f4c802d9fb59a07ff94 127.0.0.1:7001 master - 0 1634223197635 2 connected 5461-10922
6a0d5b1f102b16ff88a0b621bc2f037798540689 127.0.0.1:7006 master - 0 1634223199144 0 connected

迁移槽和数据

1.对目标节点发送:cluster setslot{slot} importing {source Nodeld}命令,让目标节点准备导入槽的数据
2.对于源节点发送:cluster setslot {slot} migrating {targetNodeld}命令,让源节点准备迁出槽的数据
3.源节点循环执行:cluster getkeysinslot {slot}{count}命令,毎次获取 count个属于槽的键
4.在源节点上执行:migrate {targetIP} {targetPort} key 0 {timeout}命令把指定key迁移
5.重复执行步骤3~4直到槽下所有的键数据迁移到目标节点
6.向集群内所有主节点发送 cluster setslot{slot) node {targetNodeld}命令,通知槽分配给目标节点

> ./redis-trib.rb reshard 127.0.0.1:7000
How many slots do you want to move (from 1 to 16384)? 4092
What is the receiving node ID? 6a0d5b1f102b16ff88a0b621bc2f037798540689
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1:all
Do you want to proceed with the proposed reshard plan (yes/no)? yes

# 查看槽的分配
> redis-cli -p 7000 cluster nodes
bc125b4301176b75a20b77da37ce5a5111619ccf 127.0.0.1:7004 slave d5f6c8b25537422fde987f4c802d9fb59a07ff94 0 1634223721205 5 connected
c19b6520969c1fc4c0e50475fe73fa1a8ec54fda 127.0.0.1:7002 master - 0 1634223719191 3 connected 12286-16383
4aabd108347571d1123778bfeaddcd7a8e602194 127.0.0.1:7007 slave 6a0d5b1f102b16ff88a0b621bc2f037798540689 0 1634223721205 8 connected
6830085d334270a61508afb3c96e4f6648efb02f 127.0.0.1:7003 slave 49f14b591968ed22462b3a34262f7dcb9e6a8b92 0 1634223714154 4 connected
2163c9c61d776d5f266c1ab687f7cefc7f209feb 127.0.0.1:7005 slave c19b6520969c1fc4c0e50475fe73fa1a8ec54fda 0 1634223716677 6 connected
49f14b591968ed22462b3a34262f7dcb9e6a8b92 127.0.0.1:7000 myself,master - 0 0 1 connected 1363-5460
d5f6c8b25537422fde987f4c802d9fb59a07ff94 127.0.0.1:7001 master - 0 1634223717180 2 connected 6826-10922
6a0d5b1f102b16ff88a0b621bc2f037798540689 127.0.0.1:7006 master - 0 1634223720197 8 connected 0-1362 5461-6825 10923-1228

收缩集群

# 迁移槽:7006 -> 7000
> ./redis-trib.rb reshard --from 6a0d5b1f102b16ff88a0b621bc2f037798540689 --to 49f14b591968ed22462b3a34262f7dcb9e6a8b92 --slots 1366 127.0.0.1:7006
Do you want to proceed with the proposed reshard plan (yes/no)? yes

# 迁移槽:7006 -> 7001
> ./redis-trib.rb reshard --from 6a0d5b1f102b16ff88a0b621bc2f037798540689 --to d5f6c8b25537422fde987f4c802d9fb59a07ff94 --slots 1366 127.0.0.1:7006
Do you want to proceed with the proposed reshard plan (yes/no)? yes

# 迁移槽:7006 -> 7002
> ./redis-trib.rb reshard --from 6a0d5b1f102b16ff88a0b621bc2f037798540689 --to c19b6520969c1fc4c0e50475fe73fa1a8ec54fda --slots 1366 127.0.0.1:7006

# 查看7006槽以及迁移完毕
> redis-cli -p 7000 cluster nodes
bc125b4301176b75a20b77da37ce5a5111619ccf 127.0.0.1:7004 slave d5f6c8b25537422fde987f4c802d9fb59a07ff94 0 1634224663138 10 connected
c19b6520969c1fc4c0e50475fe73fa1a8ec54fda 127.0.0.1:7002 master - 0 1634224664646 11 connected 10927-16383
4aabd108347571d1123778bfeaddcd7a8e602194 127.0.0.1:7007 slave c19b6520969c1fc4c0e50475fe73fa1a8ec54fda 0 1634224665149 11 connected
6830085d334270a61508afb3c96e4f6648efb02f 127.0.0.1:7003 slave 49f14b591968ed22462b3a34262f7dcb9e6a8b92 0 1634224663640 9 connected
2163c9c61d776d5f266c1ab687f7cefc7f209feb 127.0.0.1:7005 slave c19b6520969c1fc4c0e50475fe73fa1a8ec54fda 0 1634224662132 11 connected
49f14b591968ed22462b3a34262f7dcb9e6a8b92 127.0.0.1:7000 myself,master - 0 0 9 connected 0-5463
d5f6c8b25537422fde987f4c802d9fb59a07ff94 127.0.0.1:7001 master - 0 1634224666154 10 connected 5464-10926
6a0d5b1f102b16ff88a0b621bc2f037798540689 127.0.0.1:7006 master - 0 1634224664143 8 connected

# 下线:7006+7007
> ./redis-trib.rb del-node 127.0.0.1:7000 6a0d5b1f102b16ff88a0b621bc2f037798540689
>>> Removing node 6a0d5b1f102b16ff88a0b621bc2f037798540689 from cluster 127.0.0.1:7000
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.
> ./redis-trib.rb del-node 127.0.0.1:7000 4aabd108347571d1123778bfeaddcd7a8e602194
>>> Removing node 4aabd108347571d1123778bfeaddcd7a8e602194 from cluster 127.0.0.1:7000
>>> Sending CLUSTER FO 

客户端路由

moved 重定向:槽已经确定迁移
ask 重定向:槽还在迁
smart 客户端:JedisCluster

原理:

开发运维问题

集群完整性

cluster-require-full-coverage:
	集群中16384个槽全部可用:保证集群完整性
	节点故障或者正在故障转移:(error) CLUSTERDOWN The cluster is down
	建议:cluster-require-full-coverage no

带宽消耗

避免“大”集群:避免多业务使用一个集群,大业务可以多集群
cluster-node-timeout:带宽和故障转移速度的均衡
	cluster-node-timeout=15000,ping/pong带宽为25Mb 
	cluster-node-timeout=20000,ping/pong带宽低于15Mb
尽量均匀分配到多机器上:保证高可用和带宽

Pub/Sub 广播

问题:publish在集群每个节点广播:加重带宽
解决:单独“走”一套 Redis Sentinel

数据倾斜

数据倾斜:内存不均
	节点和槽分配不均:
		redis-trib.rb info ip:port  查看节点、槽、键值分布
		redis-trib.rb rebalance ip:port  进行均衡(谨慎使用)
	不同槽对应键值数量差异较大:
		CRC16正常情况下比较均匀
		可能存在hash_tag
		cluster countkeysinslot {slot}  获取槽对应键值个数
	包含bigkey:
		bigley:例如大字符串、几百万的元素的hash、set等
		从节点:redis-cl- bigkeys
		优化:优化数据结构
	内存相关配置不一致:
		hash-max-ziplist-value, set-max-intset-entriesg
		优化:定期“检査”配置一致性

请求倾斜:热点
	热点key:重要的key或bigkey
	优化:
        避免 bigley
        热键不要用hash_tag
        当一致性不高时,可以用本地缓存+MQ

读写分离

只读连接:集群模式的从节点不接受任何读写请求
	- 重定向到负责槽的主节点
	- readonly命令可以读:连接级别命令
读写分离:更加复杂
    同样的问题:复制延迟、读取过期数据、从节点故障
    修改客户端:cluster slaves {nodeId}

数据迁移

离线/在线迁移
    官方迁移工具:redis- trib. rb import
    只能从单机迁移到集群
    不支持在线迁移:source需要停写
    不支持断点续传
    单线程迁移:影响速度
在线迁移:
    唯品会 redis-migrate-tool
    豌豆荚:redis-port

缓存的使用与设计

缓存好处:

缓存成本:

使用场景:

缓存更新策略

LRU/LFU/FIFO 算法剔除:例如 maxmemory-policy
超时剔除:例如 expire
主动更新:开发控制生命周期

建议:
低一致性:最大内存和淘汰策略
高一致性 ∶ 超时剔除和主动更新结合,最大內存和淘汰策略兜底

策略 一致性 维护成本
LRU/LFU/FIFO 算法剔除 最差
超时剔除 较差
主动更新

缓存粒度控制

缓存粒度控制角度:
通用性:全量属性更好
占用空间:部分属性更好
代码维护:表面上全量属性更好

缓存穿透优化

缓存穿透问题:大量请求不命中 request -> cache(miss) -> storage(miss) -> request(return null)
原因:
业务代码自身问题
恶意攻击、爬虫等等
发现:
业务的相应时间
业务本身问题
相关指标:总调用数、缓存层命中数、存储层命中数

解决缓存空对象:request -> cache(miss) -> storage(miss) -> cache(return null) + request(return null)

问题:
需要更多的键
缓存层和存储层数据“短期”不一致
解决:
布隆过滤器拦截

缓存雪崩优化

由于 cache 服务承载大量请求,当 cache 服务异常/脱机,流量直接压向后端组件例如 DB,造成级联故障

优化:Redis Cluster、Redis Sentinel、主从漂移
保证缓存高可用性:个别节点、个别机器、甚至是机房,例如 Redis Cluster、Redis Sentinel、VIP
依赖隔离组件为后端限流:依赖隔离组件——线程池/信号量隔离组件 HYSTRIX
提前演练:例如压力测试

无底洞问题优化

问题描述:
2010 年, Facebook 有了 3000 个 Memcache 节点
发现问题:“加”机器性能没能提升,反而下降

问题关键:
更多的机器不代表更高的性能
批量接口需求(mget, mset 等)
数据增长与水平扩展需求

优化:
IO 优化:
命令本身优化:例如慢查询 keys、 hgetall bigkey
减少网络通信次数
降低接入成本 ∶ 例如客户端长连接/连接池、NIO 等
批量优化的方法:
串行 mget
串行 IO
并行 IO
hash_tag

方案 优点 缺点 网络 IO
串行 mget 编程简单,少量 keys 满足需求 大量 keys 请求延迟严重 O(keys)
串行 IO 编程简单,少量节点满足需求 大量 node 延迟严重 O(node)
并行 IO 利用并行特性,延迟取决于最慢的节点 编程复杂,超时定位问题难 O(max_slow(node))
hash_tag 性能最高 读写增加 tag 维护成本,tag 分布易出现数据倾斜 O(1)

热点 key 重建优化

可题描述:热点 key+ 较长的重建时间

目标:
减少重缓存的次数
数据尽可能一致
减少潜在危险

解决:
互斥锁:
永远不过期:
缓存层面:没有设置过期时间没有用 expire
功能层面:为每个 value 添加逻辑过期时间,但发现超过逻辑过期时间后,会使用单独的线程去构建缓存

方案 优点 缺点
互斥锁 思路简单,保证一致性 代码复杂度增加,存在死锁的风险
永远不过期 基本杜绝热点 key 重建问题 不保证一致性,逻辑过期时间增加维护成本和内存成本

基于 Redis 的分布式布隆过滤器

布隆过滤器原理

实现原理:一个很长的二进制向量和若干个哈希函数

布隆过滤器构建:
参数:m 个二进制向量,n 个预备数据,k 个 hash 函数

布隆过滤器误差率

肯定存在误差:恰好都命中了
直观因素:m/n 的比率,hash 函数的个数
m/n 与误差率成反比,k 与误差率成反比

Redis 开发规范

Key 名设计

可读性和管理性:以业务名(或数据库名)为前缀(防止key冲突),用冒号分割可
	如:业务名表名id,如:ugc:video:1

简洁性:保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视(reds3:39字节embstr)
	如:user:{uid}:friends:messages:{mid} -> u:{uid}:fr:m:{mid}

不要包含特殊字符:反例:包含空格、换行、单双引号以及其他转义字符

Value 设计

bigley
	强制:
        1.string类型控制在10KB以内
        2.hashlistsetzset元素个数不要超过5000
	反例:一个包含几百万个元素的listhash等,一个巨大的json字符串
	危害:
        1.网络阻塞
        2.Redis阻塞:慢查询,hgetalllrangezgrange
        3.集群节点数据不均衡
        4.频繁序列化:应用服务器CPU消耗
        	反序列消耗:
				Redis客户端本身不负责序列化
				应用频繁序列化和反序列化 bigley:本地缓存或 Redis缓存
	发现:
		1.应用异常
		2.redis-ci --bigkeys
		3.scan + debug object
		4.主动报警:网络流量监控、客户端监控
		5.内核热点key问题优化
	删除:
		阻塞:注意隐性删除(过期、 rename等
		Reds4.0lazy delete unlink命令
	优化:
		优化数据结构:例如二级拆分
		物理隔离或者万兆网卡:不是治标方案
		命令优化:例如hgetall --> hmget、scan
		报警和定期优化

Redis 面试

对于 Redis 的理解

Redis 是完全开源免费的一个高性能的 key-value 数据库

特点:

Redis 优势

为什么要使用 Redis

安全,解决高并发

为什么 Redis 需要把所有数据放到内存中?

Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。在内存越来越便宜,redis 会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值

一个 Redis 实例最多能存放多少的 keys?

理论上 Redis 可以处理多达 232 的 keys,并且在实际中进行了测试,每个实例至少存放了 2 亿 5 千万的 keys。我们正在测试一些较大的值。任何 list、set、和 sorted set 都可以放 232 个元素。换句话说,Redis 的存储极限是系统中的可用内存值

为什么要用 Redis,相比于如 Memcached,MongoDB 有什么优势

Memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型
Redis 的速度比 Memcached 快
Redis 可以持久化其数据
存储方式 Memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis 有部份存在硬盘上,这样能保证数据的持久性

Redis 怎么做到多路 IO 复用

Redis 的原子性实现

对于 Redis 而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行
Redis 的操作之所以是原子性的,是因为 Redis 是单线程的
Redis 本身提供的所有 API 都是原子操作,Redis 中的事务其实是要保证批量操作的原子性

Redis 在项目中进行怎么样的使用,项目中 redis 主要存储什么

热点数据和冷数据

热点数据,缓存才有价值
对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存

寿星列表、导航信息都存在一个特点,就是信息修改频率不高,读取通常非常高的场景

对于热点数据,比如我们的某 IM 产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次

数据更新前至少读取两次,缓存才有意义,如果缓存还没有起作用就失效了,那就没有太大价值了

存在修改频率很高,但是又不得不考虑缓存的场景,比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到 Redis 缓存,减少数据库压力

Redis 的单线程为什么也能这么快?

完全基于内存
单线程避免不必要的上下文切换、CPU 消耗
采用了非阻塞 I/O 多路复用机制

Redis 底层数据结构?详细说 string 底层的 sds

讲一下 zset 跳表?

Redis 常用数据结构,常用的指令

string(字符串),hash(哈希),list(列表),set(集合)及 zsetsorted set(有序集合)

实际项目中比较常用的是 string,hash

Redis 中高级用户,还需要加上下面几种数据结构 HyperLogLog、Geo、Pub/Sub

Redis Module,像 BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了

一个字符串类型的值能存储最大容量是多少?

512M

Redis 数据过期

Redis 的过期策略以及内存淘汰机制

定期删除 + 惰性删除

为什么不用定时删除策略?
定时删除,用一个定时器来负责监视 key,过期则自动删除。虽然内存及时释放,但是十分消耗 CPU 资源。在大并发请求下,CPU 要将时间应用在处理请求,而不是删除 key,因此没有采用这一策略

定期删除 + 惰性删除是如何工作的呢?
定期删除,redis 默认每个 100ms 检查,是否有过期的 key,有过期 key 则删除。需要说明的是,redis 不是每个 100ms 将所有的 key 检查一次,而是随机抽取进行检查(如果每隔 100ms,全部 key 进行检查,redis 岂不是卡死)。因此,如果只采用定期删除策略,会导致很多 key 到时间没有删除。于是,惰性删除派上用场。也就是说在你获取某个 key 的时候,redis 会检查一下,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除

采用定期删除 + 惰性删除就没其他问题了么?
不是的,如果定期删除没删除 key。然后你也没即时去请求 key,也就是说惰性删除也没生效。这样,redis 的内存会越来越高。那么就应该
采用内存淘汰机制

Redis 的回收策略(淘汰策略)

同:MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据

Redis 过期键删除策略

Redis key 的过期时间和永久有效分别怎么设置?

EXPIRE 和 PERSIST 命令

Redis 持久化机制

Redis 支持持久化,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当 Redis 重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的

实现:单独创建 fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。

RDB:Redis 默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即 Snapshot 快照存储,对应产生的数据文件为 dump.rdb,通过配置文件中的 save 参数来定义快照的周期

AOF:Redis 会将每一个收到的写命令都通过 Write 函数追加到文件最后,类似于 MySQL 的 binlog。当 Redis 重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当两种方式同时开启时,数据恢复 Redis 会优先选择 AOF 恢复

缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级

缓存雪崩

问题:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库 CPU 和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃

解决:大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开

缓存穿透

问题:用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题

解决:采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴

缓存更新

问题:系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据

解决:数据量不大,可以在项目启动的时候自动进行加载;定时刷新缓存

缓存预热

除了缓存服务器自带的缓存失效策略之外(Redis 默认的有 6 中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰

解决:

缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级

降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)

Redis 事务

Redis 事务功能通过 MULTI、EXEC、DISCARD 和 WATCH 四个原语实现
Redis 会将一个事务中的所有命令序列化,然后按顺序执行

Redis 集群有了解么

Redis 集群,为什么是 16384 个槽,槽分配的好处

https://zhuanlan.zhihu.com/p/435994841

Redis 集群会有写操作丢失吗?

Redis 并不能保证数据的强一致性

Redis 集群之间是如何复制的?

异步复制

Redis 集群最大节点个数是多少?

16384 个

怎么测试 Redis 的连通性?

使用 ping 命令

Redis 哈希槽

Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽

Redis 的编码方式
Redis 的 zset 底层,skiplist 和红黑树,缓存击穿
Redis 发布订阅和直接用 List 的区别
Redis 发布订阅和消息中间件的区别
Redis 两种高可用方案介绍

Redis 缓存到了过期时间是怎么删除缓存的?

了解 Redis 的哨兵模式吗?

Redis 怎么保证和数据库一致性

数据进行改是直接改 Redis 还是删除 Redis?如果直接修改为什么不行?(线程安全)

为什么要有第一次的删除 Redis 数据

Redis 分布式锁

先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放

如果在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了,那会怎么样?
set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和 expire 合成一条指令来用

Redis 基本数据结构,每个数据结构的组成
Redis 的使用场景,Redis 集群方案,Redis 持久化原理

Redis 可以当做数据库吗?为什么不能?Redis 持久化优势劣势?关闭 rdb 如何主从复制?

Redis 常见性能问题

Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内
尽量避免在压力很大的主库上增加从库
主从复制不要用图状结构,用单向链表结构更为稳定,即: Master <- Slave1 <- Slave2 <-Slave3

  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    320 引用 • 602 回帖 • 1 关注
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    252 引用 • 244 回帖 • 550 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...