目录
- Redis实践
-
- [1. Redis监控](#1. Redis监控)
-
- [1.1 系统自带监控](#1.1 系统自带监控)
-
- [(1) Info](#(1) Info)
- [(2) Monitor](#(2) Monitor)
- [(3) latency](#(3) latency)
- [(4) Intrinsic latency](#(4) Intrinsic latency)
- [(5) Latency monitor](#(5) Latency monitor)
- [(6) Slow log](#(6) Slow log)
- [1.2 Prometheus-Redis-exporter 监控 多实例监控部署](#1.2 Prometheus-Redis-exporter 监控 多实例监控部署)
- [1.3 redis-stat(作业)](#1.3 redis-stat(作业))
- 2、Redis-benchmark压测
-
- [2.1 介绍](#2.1 介绍)
- [2.2 参数](#2.2 参数)
- [2.3 模拟](#2.3 模拟)
- [2.4 结果分析](#2.4 结果分析)
- [3. Redis应用问题解决](#3. Redis应用问题解决)
-
- [3.1 缓存穿透](#3.1 缓存穿透)
- [3.2 缓存击穿](#3.2 缓存击穿)
- [3.3 缓存雪崩](#3.3 缓存雪崩)
- [3.4 MySQL与Redis双写一致性方案](#3.4 MySQL与Redis双写一致性方案)
- 4、Redis运维问题及优化案例
-
- [(1) 数据类型选择不合理](#(1) 数据类型选择不合理)
- [(2) 过期key订阅](#(2) 过期key订阅)
- [(3) 大key问题](#(3) 大key问题)
- [(4) 数据倾斜](#(4) 数据倾斜)
- [(5) 流量倾斜](#(5) 流量倾斜)
- [(5) redis慢日志](#(5) redis慢日志)
- [(6) 波动的响应延迟](#(6) 波动的响应延迟)
- [(7) 删除数据后,内存占用率无法下降](#(7) 删除数据后,内存占用率无法下降)
- [(8) 脑裂](#(8) 脑裂)
- [5. redis shake](#5. redis shake)
-
- [5.1 RedisShake能做什么](#5.1 RedisShake能做什么)
- [5.2 RedisShake基本原理](#5.2 RedisShake基本原理)
-
- [(1) 功能](#(1) 功能)
- [(2) 基本同步模式](#(2) 基本同步模式)
- [(3) sync模式数据流图](#(3) sync模式数据流图)
- [5.3 数据同步示例](#5.3 数据同步示例)
-
- [1. 下载, 解压](#1. 下载, 解压)
- [2. 配置文件](#2. 配置文件)
- [3. 恢复](#3. 恢复)
- [4. 数据同步](#4. 数据同步)
- [1. 创建&编写配置文件](#1. 创建&编写配置文件)
- 2、启动
- 3、验证数据是否迁移完成
- 4、新增数据验证cluster中是否存在
- [5.4 redis-shake注意事项](#5.4 redis-shake注意事项)
- 6、Redis备份恢复
- [7. Redis企业使用规范](#7. Redis企业使用规范)
- [8. cachedoud](#8. cachedoud)
-
- [(1) CacheCloud 是什么?](#(1) CacheCloud 是什么?)
- [(2) 部署](#(2) 部署)
- [(3) 配置修改](#(3) 配置修改)
- [(4) 添加机器](#(4) 添加机器)
- [(5) 导入应用](#(5) 导入应用)
Redis实践
1. Redis监控
1.1 系统自带监控
(1) Info
bash
redis-cli info
# 获取所有信息
> info
# 获取内存相关信息,info 可以一次性获取所有的信息,也可以按块取信息
> info memory
# 获取复制相关信息
> info replication
# Info 指令显示的信息非常繁多,分为 9 大块,每个块都有非常多的参数,这 9 个块分别是:
#(如果想要了解所有的参数细节,请参考阅读:https://redis.io/commands/info)
- Server 服务器运行的环境参数
- Clients 客户端相关信息
- Memory 服务器运行内存统计数据
- Persistence 持久化信息
- Stats 通用统计数据
- Replication 主从复制相关信息
- CPU CPU 使用情况
- Cluster 集群信息
- KeySpace 键值对统计数量信息
比较重要的参数指标
instantaneous_ops_per_sec:789 # ops_per_sec: operations per second, 也就是每秒操作数
connected_clients:124 #正在连接的客户端数量,client list列出所有的客户端连接
rejected_connections:0 #因为超出最大连接数限制而被拒绝的客户端连接次数
maxclients:10000 #最大连接数
maxmemory_human:100.00M #redis设置的最大内存
used_memory_human:827.46K #内存分配器(jemalloc)从操作系统分配的内存总量
used_memory_rss_human:3.61M #操作系统看到的内存占用,top命令看到的内存
used_memory_peak_human:829.41K #Redis 内存消耗的峰值
repl_backlog_size:1048576 # 复制积压缓冲区大小
sync_partial_err:0 #半同步失败次数
配合linux下的watch命令,实时监测redis的内存开销和吞吐量,用作流量
bash
watch -n 1 -d "redis-cli -p 6400 info |grep -e connected_clients -e used_memory_human -e used_memory_peak_human -e instantaneous -e evicted_keys"
(2) Monitor
bash
#monitor的开启,会很大程度影响redis本身的性能。监测redis运行过程中的每个命令的信息。
redis-cli -p 6400 monitor
(3) latency
bash
#ping操作,检测server和client间的网络延时,一般都是1ms以内
redis-cli --latency
(4) Intrinsic latency
bash
#redis的延时基准(命令本身的耗时),运行命令
redis-cli --intrinsic-latency 100
(5) Latency monitor
bash
#redis运行过程中,可以对超出某个时间阈值的命令进行监控。开启监测的方式如下:
CONFIG SET latency-monitor-threshold 100 #ms
#监测系统运行过程中fork时间大于10ms的fork latency。
latency history fork
(6) Slow log
bash
#开启记录latency大于100ms的命令和最大保留1000条slowlog
CONFIG SET slowlog-log-slower-than 100 #单位微妙,执行时间大于该值的query才会被记录,默认10ms
CONFIG SET slowlog-max-len 1000 #慢查询最大的条数,超过则最早的被删除(FIFO队列)
#查看slowlog总条数
127.0.0.1:6379> SLOWLOG_LEN
(integer) 4
#读取
/*
1)日志的标识 id
2)发生的时间戳
3)命令耗时
4)执行的命令和参数
*/
127.0.0.1:6379> SLOWLOG GET
1)1)(integer)1 #唯一性(unique)的日志标识符,只有在Redis服务器重启的时候才会重置,可以避免对日志的重复处理(比如邮件通知)
2)(integer)1559237232 #被记录命令的执行时间点,以Unix时间戳格式表示
3)(integer)13 #命令耗时,以微秒为单位
4)1)"set" #执行的命令,以数组的形式排列
2)"aaa"
3)"bbb"
1.2 Prometheus-Redis-exporter 监控 多实例监控部署
-
官网地址
https://grafana.com/oss/prometheus/exporters/redis-exporter/?tab=installation
-
介绍
grafana官方出品,promethus-redis指标采集+grafana面板
-
下载地址
bash
wget https://github.com/oliver006/redis_exporter/releases/download/v1.50.0/redis_exporter-v1.50.0.linux-amd64.tar.gz
tar zxvf redis_exporter-v1.50.0.linux-amd64.tar.gz
mv redis_exporter-v1.50.0.linux-amd64 redis_exporter
cd redis_exporter
./redis_exporter --help
# -namespace string #指标名字
# -redis.addr string #redis地址,多实例监控参数
# -redis.password string #redis密码监控
# -redis.user string #6.0版本ACL特性
# -web.listen-address string #默认端口 9121
# -web.telometry-path string #指标暴露地址,默认 /redis-metrics,即 http://192.168.9.78:9090/metrics
- 多实例配置redis-exporter 监控步骤
bash
# 1.安装promethus+grafana(略)
# 2.启动exporter
nohup ./redis_exporter -redis.addr= _web.listen-address:9121 & #观察指标
curl http://192.168.9.78:9121/metrics
# 3.配置文件,prometheus.yml中加入job
cat prometheus.yml
- job_name: 'redis_exporter_targets'
static_configs:
- targets:
- redis://192.168.9.78:6400
- redis://192.168.9.78:6410
- redis://192.168.9.78:6411
metrics_path: /scrape
relabel_configs:
- source_labels: [_address_]
target_label: __param_target
- source_labels: [_param_target]
target_label: instance
- target_label: __address_
replacement: 192.168.9.78:9121
- job_name: 'redis_exporter'
static_configs:
- targets:
- 192.168.9.78:9121
##热加载prometheus
curl -X POST 192.168.9.78:9090/-/reload
##查看指标
http://192.168.9.78:9090/targets
# 4. 配置dashboard, 导入Dashboard
# 地址:https://grafana.com/oss/prometheus/exporters/redis-exporter/?tab=dashboards
# 在grafana中导入json模板
1.3 redis-stat(作业)
- 轻量级redis服务监控运行,基于INFO信息,比monitor(影响性能)要友好
- 分为终端命令行和可视化web界面两种
- 最佳场景:压测,实时监控
使用步骤
网址: https://github.com/junegunn/redis-stat/
- 安装
bash
# 安装ruby
sudo yum install ruby
sudo yum install ruby-devel
# gem update --system -- 更新到最新版
# 移除默认镜像地址并更换国内镜像地址,
gem sources --remove https://rubygems.org -a https://gems.ruby-china.com
# 更换国内镜像地址 可选镜像
gem sources -a https://gems.ruby-china.com
gem sources --add https://mirrors.tuna.tsinghua.edu.cn/rubygems/
# 查看
gem sources -l
# gem安装
gem install redis-stat
# 源码方式
wget https://github.com/junegunn/redis-stat/releases
- 使用
bash
#黑屏命令行监控
./redis-stat 192.168.9.78:6400 192.168.9.78:6411 5
#web图形监控
./redis-stat 192.168.9.78:6400 192.168.9.78:6411 --server=9000 5 --daemon
# 访问地址: http://192.168.9.78:9000/
使用java版本
bash
# 使用java版本,下载jar包
# https://github.com/junegunn/redis-stat/releases/download/0.4.14/redis-stat-0.4.14.jar
# 命令行监控,执行jar包
java -jar redis-stat-0.4.14.jar --server 192.168.9.78:6400 192.168.9.78:6411 5
# 使用web页面监控,
java -jar redis-stat-0.4.14.jar 192.168.9.78:6400 192.168.9.78:6411 5 --server=9000
# 访问地址: http://192.168.9.78:9000/
Dashboard示例
time us sy cl bcl mem rss keys cmd/s exp/s evt/s hit%/s hit/s mis/s ad/ca
00:30:55 0 0 1 0 1.74MB 12.9MB 12 0.20 0 0 - 0 0 1.49MB
...
Instance information示例
redis_version 6.2.12
redis_mode standalone
process_id 93226
uptime_in_seconds 389061
uptime_in_days 4
role master
connected_slaves 0
ad_enabled 1
2、Redis-benchmark压测
2.1 介绍
redis-benchmark是Redis自带的基准性能测试工具
redis-benchmark测试cluster集群实例时需要加 --cluster 参数
2.2 参数
bash
-h <hostname> 指定服务器主机名(默认 127.0.0.1)
-p <port> 指定服务器端口(默认 6379)
-s <socket> 指定服务器 socket
-a <password> Redis 认证密码
-c <clients> 指定并发连接数(默认 50)
-n <requests> 指定请求数(默认 100000)
-d <size> 以字节的形式指定 SET/GET 值的数据大小(默认 2)
--dbnum <db> 选择指定的数据库号(默认 0)
-k <boolean> l=keep alive 长链接, 0=reconnect(默认 1)每次请求重新连接
-r <keyspacelen> SET/GET/INCR 使用随机 key,SADD 使用随机值
-P <numreq> 通过管道传输 <numreq> 请求,代表每个请求pipeline的数据量(默认为1)
-q 仅仅显示redis-benchmark的requests per second信息,
-t <tests> 可以对指定命令进行基准测试 例如: -t get,set
--csv 以 CSV 格式输出
2.3 模拟
50个客户端同时请求Redis,总共1万个请求,
bash
/usr/local/redis/bin/redis-benchmark -p 6400 -p 10 -c 50 -d 100 -n 10000 -t get
2.4 结果分析
bash
# redis-benchmark -p 6400 -p 10 -c 50 -d 100 -n 10000 -t get
===== MSET (10 keys) ====
10000 requests completed in 0.13 seconds #总共1万次,0.13秒完成
50 parallel clients #50并发
100 bytes payload #每个请求100字节
keep alive: 1
97.81% <= 1 milliseconds #97.81%的命令执行时间小于1毫秒
99.23% <= 2 milliseconds
100.00% <= 2 milliseconds
Summary:
throughput summary: 476190.47 requests per second #每秒可以处理476190.47次get请求
latency summary (msec):
avg min p50 p95 p99 max
0.845 0.232 0.791 1.127 2.335 2.455
3. Redis应用问题解决
① 缓存穿透:大量请求根本不存在的key
② 缓存雪崩:redis中大量key集体过期
③ 缓存击穿:redis中一个热点key过期(大量用户访问该热点key,但是热点key过期)
④ mysql与redis双写不一致:
3.1 缓存穿透
场景描述
缓存穿透:是指查询一个根本不存在的数据,缓存层和存储层都不会命中
访问 → 请求web服务 → 访问缓存获取不存在数据 → 数据库查询不存在数据 → 无法同步缓存
解决方案
- 缓存空对象
- 布隆过滤器拦截
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
将所有可能存在的数据信给到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。 - 对前端查询条件加密,比如商品id,使用md5加密,无规律,前端无法模拟;
- 小批量数据设置可访问的名单(白名单) :
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。 - 进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
3.2 缓存击穿
场景描述
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回收到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
访问 → 请求web服务 → 访问缓存 key过期 → 瞬时访问量过大
解决方案
key可能会在某些时间点被超高并发地访问,是一种非常"热点"的数据。这个时候,需要考虑一个问题:缓存被"击穿"的问题。
解决问题:
- 预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
- 设置热点数据永远不过期
- 使用互斥锁
3.3 缓存雪崩
场景描述
大量热点key同时过期 或者 缓存服务故障,导致请求无法命中缓存,而是直接打到DB,这个时候大并发的请求可能会瞬间把后端DB压垮。
缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key
正常访问:访问 → 请求web服务 → 分发到该服务器 → 访问缓存 → 定期更新数据
缓存失效瞬间:访问 → 请求web服务 → 分发到该服务器 → 访问缓存 → 数据批量过期 → 更新失效
解决方案
缓存失效时的雪崩效应对底层系统的冲击非常可怕!
解决方案:
- 构建多级缓存架构:nginx缓存 + 本地缓存 + redis缓存
- 考虑用队列或着锁的方式,保证缓存单线程写,但这种方案可能会影响并发量
- 双key策略,主key设置过期时间,备key不设置过期时间,当主key失效时,直接返回备key值。
- 避免给大量的数据设置相同的过期时间
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。 - 服务降级
- 业务系统中实现服务熔断或请求限流机制
3.4 MySQL与Redis双写一致性方案
(1)一致性
我们在这里说的一致性,是redis和MySQL数据的一致性。
一致性有三种模型:
- 强一致性
- 弱一致性
- 最终一致性
(2)缓存模式
- Cache-Aside Pattern (旁路缓存模式)
- Read-Through/Write through(读取透写模式)
- Write behind (写后模式)
Cache-Aside(旁路缓存)
-
Cache-Aside读流程
- 读的时候,先读缓存,缓存命中的话,直接返回数据
- 缓存没有命中的话,就去读数据库,从数据库取出数据,放入缓存后,同时返回响应
-
Cache-Aside写流程
- 更新数据库
- 删除缓存。
如果写请求更新数据库后异常了,没有删除缓存,就会造成缓存和数据库里的不一致。
Read-Through/Write-Through(读写穿透)
-
Read-Through读流程
- 从缓存读取数据,读到直接返回
- 如果读取不到的话,从数据库加载,写入缓存后,再返回响应。
Read-Through实际只是在Cache-Aside之上进行了一层封装,它会让程序代码变得更简洁,同时也减少数据源上的负载。
-
Write-Through 写流程
Write-Through模式下,当发生写请求时,也是由 缓存抽象层 完成数据源和缓存数据的更新。
Write behind (异步缓存写入)
Write-Behind模式下,应用程序在写入数据时首先将数据写入缓存,然后异步地将数据写入数据源。
相同:都是由Cache Provider来负责缓存和数据库的读写
不同: Read/Write Through是同步更新缓存和数据的,Write Behind则是只更新缓存,不直接更新数据库,通过批量异步的方式来更新数据库。
这种方式下,缓存和数据库的一致性很强,对一致性要求高的系统要谨慎使用。但是它适合频繁写的场景,MySQL的InnoDB Buffer Pool机制就使用到这种模式。
(3)不一致问题分析
A. 先写数据库再更新缓存不一致问题
更新缓存相对于删除缓存的劣势:
- 如果写入的缓存值,是经过复杂计算才得到的话,更新缓存频率高的话,就浪费性能了;
- 在写数据库场景多、读数据场景少的情况下,数据很多时候还没被读取到,又被更新了,这也浪费了性能。
B. 先删缓存,读的时候再写不一致问题
缓存和数据库的数据不一致。缓存保存的是老数据,数据库保存的是新数据。
(4)不一致问题解决
缓存延时双删
写请求 → 删除缓存 → 更新数据库 → 删除缓存
删除缓存重试机制
写请求 → 应用程序 → 1.更新数据库 → 数据库
→ 2.删除缓存失败 → 缓存
→ 3.删除失败的key → 消息队列 → 4.要删除的key拉出来 → 5.重试删除操作
读取binlog异步删除缓存
binlog → canal → mq ACK机制确认更新消息,删除缓存
4、Redis运维问题及优化案例
(1) 数据类型选择不合理
-
选择合适的类型
当一个可以聚合的内容分开存储时,总共占用了(64+57+61)=182字节,改为hash存储时,占用了98字节。
-
压缩列表的条件:"列表中元素数量<512个" && "列表中所有字符串对象都不足64字节"
示例:
bash
local0>set user:1000:login "login"
"OK"
local1>set user:1000:quota "2000"
"OK"
local2>set user:1000:info "xxx"
"OK"
local3>set user:1000 login "login" quota "2000" info "xxx"
"3"
local4>memory usage user:1000:login
"64"
local5>memory usage user:1000:quota
"57"
local6>memory usage user:1000:info
"61"
local7>memory usage user:1000
"98"
(2) 过期key订阅
现象 :
监听过期事件处理业务,如订单30分钟过期,因为程序没有再访问master,没有触发删除,导致系统订单一直未关闭,库存未释放。
redis内部机制 :
redis产生expired的时间为过期key被删除的时候,而不是ttl变为0的时候。
代码在:notifyKeyspaceEvent (NOTIFY_EXPIRED, "expired", key.db->id);
__keyspace@db__:key expired__keyevent@db__:expired key
redis订阅过期事件是订阅的__keyevent@db__:expired。
结论 :
当一个键过期时,Redis并不会主动通知订阅者。redis产生expired的事件为过期key被删除的时候,而不是ttl变为0的时候。而删除是由两种过期键删除策略完成的,一种是定期删除,另一种是惰性删除。会在适当的时候自动删除过期的键,也有可能不会触发删除。这就导致无法收到过期事件通知。从而引发生产事故。
(3) 大key问题
key过大会引起以下的问题:
- 占用网络IO和cpu,阻塞,比如有的key里存储十几MB的数据,多次查询,会阻塞其他的操作请求;
- 内存挤占过多,甚至会把未失效的清掉,比如自己用zset做的限流、数据没有及时删除,导致占用内存过多;
- del删除也会阻塞(使用异步删除unlink)
如何发现大key?
- 实时检测:通过redis-cli --bigkeys检测
- 离线检测:通过redis-rdb-tools工具对备份的rdb进行分析
redis-rdb-tools工具
redis-rdb-tools 是一个 python 的解析 rdb 文件的工具,在分析内存的时候,我们主要用它生成内存快照。
-
生成rdb文件
-
分析rdb文件,并将结果写入到csv文件中
bashrdb -c memory dump.rdb > dump-rdb.csv输出字段说明:
database : key在redis的db type : key类型 key : key值 size_in_bytes : key的内存大小(byte) encoding : value的存储编码形式 num_elements : key中的value的个数 len_largest_element : key中的value的长度 expiry : key过期时间 -
'直接分析csv文件获取大key' or '将csv文件的数据导入到mysql数据库进行分析'
解决并避免
- 异步删除
- 选择合适的数据库
- 选择合适的数据结构
(4) 数据倾斜
现象 :
redis cluster大量的key路由到其中一个master节点,导致该节点cpu、内存、qps升高
数据倾斜的原因:
- 大key的出现
- 数据的写入机制,一般都是hash后不均匀出现的
- 数据迁移或扩容
解决:
- 排查集群中是否存在大key,对bigkey拆分;
- 将数据量大的slot迁移,迁移一部分到其他master节点;
- 热点数据,利用分片算法的特性,对key进行打散处理
(5) 流量倾斜
流量倾斜一般是指redis集群中某个节点的流量远远大于平均流量值。
原因:
- 实例上存在热点数据
如何解决避免
- 只读的热点数据:热点数据复制多份,每一个数据副本的 key 中增加一个随机前缀,让其被映射到不同的 Slot 中
- 有读有写热点数据:升配
(5) redis慢日志
我们在分析redis变慢的时候,除了看cpu,看io,还有一种情况,就是啥都没问题,就是慢。那我们可以看看redis的慢日志。
redis的慢日志是redis提供的一个简单的慢命令统计记录功能,它会把命令执行时间超过slowlog-log-slower-than纪录到一个列表中,该列表通过slowlog-max-len控制长度。
配置:
bash
- 设置20000微妙
config set slowlog-log-slower-than 20000
- 设置保留1000条
config set slowlog-max-len 1000
config rewrite
示例:
bash
[root@OPS-9-78 ~]# redis-cli -p 6400
# 设置20000微妙
127.0.0.1:6400> config set slowlog-log-slower-than 20000
OK
# 设置保留1000条
127.0.0.1:6400> config set slowlog-max-len 1000
OK
127.0.0.1:6400> config rewrite
OK
# 查看慢日志
127.0.0.1:6400> slowlog get 1
1) 1) (integer) 1
2) (integer) 1686971278
3) (integer) 108129
4) 1) "config"
2) "rewrite"
5) "127.0.0.1:49489"
6) ""
# 查看日志列表的长度
127.0.0.1:6400> slowlog len
(integer) 2
# slowlyg 重置
127.0.0.1:6400> slowlog reset
OK
127.0.0.1:6400> slowlog len
(integer) 0
(6) 波动的响应延迟
redis客户端访问redis经过了网络,loop排队,命令执行,这三块都有可能导致redis server的响应延迟。
我们这节重点分析下loop排队中的延迟。
怎么查看延迟?
第一种方式 :
Redis 2.6版本的时候,引入了一个看门狗(watchdog)工具,这个工具可以用于诊断redis的延迟问题。
watchdog 只是用来调试程序的,会阻塞server,耗时会比较高,生产不要使用。
bash
-- 设置延迟间隔时间
127.0.0.1:6400> CONFIG SET watchdog-period 300
OK
-- 关闭watchdog
127.0.0.1:6400> CONFIG SET watchdog-period 0
第二种方式
redis从 2.8.7 版本开始,redis-cli 命令提供了 -intrinsic-latency 选项,可以用来监测和统计测试期间内的最大延迟,这个延迟可以作为 Redis 的基线性能
bash
[root@OPS-9-78 ~]# redis-cli -p 6400 --intrinsic-latency 120
Max latency so far: 1 microseconds.
Max latency so far: 7 microseconds.
Max latency so far: 41 microseconds.
Max latency so far: 42 microseconds.
Max latency so far: 49 microseconds.
Max latency so far: 98 microseconds.
Max latency so far: 341 microseconds.
Max latency so far: 818 microseconds.
Max latency so far: 1244 microseconds.
Max latency so far: 1621 microseconds.
1621249297 total runs (avg latency: 0.0740 microseconds / 74.02 nanoseconds per run).
Worst run took 21900x longer than the average latency.
cpu架构对redis性能的影响
redis是典型的cpu密集型应用,cpu的性能对redis的影响还是比较大的。
- 1个cpu处理器一般有多个运行核心,一个运行核心称为一个物理核,每个物理核都可以运行程序
- 每个物理核拥有私有的一级缓存(Level 1 cache 包括一级指令缓存和一级数据缓存)访问延迟在10ns,以及私有的二级缓存(Level 2 cache 访问延迟在100ns)
- 一个CPU有一个三级缓存,在一个cpu中的不同的物理核会共享一个共同的三级缓存(Level 3 cache)
- 每个物理核通常会运行两个逻辑核(超线程)
- 应用程序在1个cpu内访问叫本地内存访问,切到另一个cpu上运行,需要调用原来的数据叫远端内存访问
多核CPU对redis的性能影响
当redis运行的时候,cpu进行上下文切换时,应用程序由cpu的A物理核切换到B物理核,应用程序的运行时间信息需要从L3或从内存中被重新加载到B物理核的L1、L2缓存中,就会导致redis的延迟增加。(虽然是微秒级,但是在流量高峰会出现毛刺)
多核cpu下的优化
既然这样,那我们就想办法让redis运行在一个物理核上,就可以避免因cpu切换带来的延迟问题。
Note: 绑定以后主线程、子进程、后台线程共享使用一个物理核,如果能将子进程和后台线程单独绑定物理最好(需要修改源码)
幸好linux给我们提供了一个命令 taskset
taskset命令用于设置进程(或线程)的处理器亲和性(Processor Affinity),可以将进程(或线程)绑定到特定的一个或多个CPU上去执行,而不允许将进程(或线程)调度到其他的CPU上。
bash
#先通过lscpu查看下核的编号(一定要注意NUM架构下cpu核的编号方法)
[root@OPS-9-78 6400]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 1
Core(s) per socket: 1
座: 4
NUMA 节点: 1
厂商 ID: GenuineIntel
CPU 系列: 6
型号: 79
型号名称: Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz
步进: 1
CPU MHz: 2199.998
BogoMIPS: 4399.99
超管理器厂商: VMware
虚拟化类型: 完全
L1d 缓存: 32K
L1i 缓存: 32K
L2 缓存: 256K
L3 缓存: 25600K
NUMA 节点0 CPU(s): 0-3,8-11 # NUMA架构下节点0是一个物理核,第一个逻辑核的cpu编号是0到3,第二个逻辑核的cpu编号是8-11
NUMA 节点1 CPU(s): 4-7,12-15 # NUMA架构下节点1是一个物理核,第一个逻辑核的cpu编号是4到7,第二个逻辑核的cpu编号是12-15
# 将 redis绑定到一个物理核上的两个逻辑核上,
taskset -c 0,8 ./redis-server
内存交换
swap对于操作系统来比较重要,当物理内存不足时,可以将一部分内存页进行swap操作,已解燃眉之急。swap的空间是由磁盘提供。
内存交换 (swap) 对redis来说是致命的,redis保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把redis使用的部分内存换到硬盘,由于内存与磁盘的读写速度是数量级的差异,会导致发生内存交换后的redis性能急剧下降。
检查内存交换
bash
# 查询redis的进程id
ps -ef|grep redis
# 根据进程号查询内存交换信息
cat /proc/12875/smaps |grep Swap
Swap: 0 kB
Swap: 0 kB
Swap: 4 kB
Swap: 0 kB
Swap: 0 kB
屏蔽swap
bash
# 配置swap
echo 0 > /proc/sys/vm/swappiness
| 值 | 策略 |
|---|---|
| 0 | Linux3.5 以及以上:宁愿用 OOM killer 也不用 swap |
| Linux3.4 以及更早:宁愿用 swap 也不用 OOM killer | |
| 1 | Linux3.5 以及以上:宁愿用 swap 也不用 OOM killer |
| 60 | 默认值 |
| 100 | 操作系统会主动地使用 swap |
内存大页
linux内核从2.6.38开始支持内存大页机制。该机制支持2MB大小的内存页分配,而常规的内存页分配是按4kb来的。
为什么内存大页会影响延迟呢?这就要说到fork机制了。我们都知道fork以后,一旦数据要修改,并不会在原来的内存上修改,而是copy一份,然后再进行修改。
如果采用内存大页,即使客户端请求只修改200B的数据,redis也需要copy2MB的大页,如果是4kb的页,只会copy 4kb。这在高并发的时候就能体现出来。
bash
# 关闭系统大页
echo never /sys/kernel/mm/transparent_hugepage/enabled
(7) 删除数据后,内存占用率无法下降
现象 :
删除redis数据后,用top统计时,发现redis占用了很多内存
原因 :
内存碎片
如何清理?
bash
# 相关配置参数
-- 开启自动内存清理
config set activedefrag yes
-- 内存碎片的字节数达到100mb,开始清理
config set active-defrag-ignore-bytes 100mb
-- 内存碎片空间占操作系统分配给redis的总空间比例达到10%时,开始清理
config set active-defrag-threshold-lower 10
-- 表示自动清理过程所用cpu时间的比例不低于25%
active-defrag-cycle-min 25
-- 表示自动清理过程所用cpu时间的比例不高于75%,一旦超过,就停止清理,以避免影响redis
active-defrag-cycle-max 75
(8) 脑裂
Redis脑裂(Split-Brain)是指在Redis集群中发生网络分区(网络故障、网络延迟等)导致集群节点之间无法正常通信,从而导致集群分裂成多个独立的子集,每个子集都认为自己的主节点。
脑裂可能导致以下问题:
- 数据冲突
- 数据丢失
- 服务不可用
如何避免
在主库上设置
旧版本(3.2及以下)
min-slaves-to-write 3
min-slaves-max-lag 10
新版本(4.0及以上)
min-replicas-to-write 3 最少从库数量
min-replicas-max-lag 10 从库最大延迟时间
任一条件不满足,主库将不再接收请求
5. redis shake
5.1 RedisShake能做什么
redis-shake是一个用于在两个redis之间同步数据的工具,满足用户非常灵活的同步、迁移需求。
GitHub地址:https://github.com/alibaba/RedisShake
作用&应用场景
- 数据迁移&同步
- 版本变更
- 架构变更
- 容灾
- 多活
数据迁移
支持code,支持云下到云上,云上到云上,云上到云下,其他云到阿里云等链路,灵活构建混合云场景~
Dowhat
版本升级、版本降级
Redis2.8 --> Redis 4.0
Redis4.0 --> Redis 5.0
Redis2.8 --> Redis 5.0
架构变更
单节点 主从版 集群版
m分片 --> n分片
容灾
维持超过半数的节点
机房断点,着火,地震,链路损坏...
5.2 RedisShake基本原理
(1) 功能
RedisShake的主要功能有解析、恢复、备份、同步。
功能 :
恢复restore:将RDB文件恢复到目的redis数据库。
备份dump:将源redis的全量数据通过RDB文件备份起来。
解析decode:对RDB文件进行读取,并以json格式解析存储
同步sync:支持源redis和目的redis的数据同步,支持全量和增量数据的迁移,以下主要介绍同步sync。
(2) 基本同步模式
redis-shake的基本原理就是模拟一个从节点加入源redis集群,首先进行全量拉取并回放,然后进行增量的拉取(通过psync命令)。
- 支持的Redis形态 :
- Standalone:单源拉取,主从版/单节点
- Sentinel:从Sentinel获取地址并拉取
- Cluster:开源Cluster模式
- Proxy:从Proxy拉取
| 源redis节点 | 0. psync |
|---|---|
| 1. 发送RDB | |
| 2. 发送增量命令 |
| redis-shake | 1. 写入RDB |
|---|---|
| 2. 写入增量命令 |
| 目的redis节点 | 1. 发送RDB |
|---|---|
| 2. 发送增量命令 |
(3) sync模式数据流图
5.3 数据同步示例
1. 下载, 解压
bash
wget https://github.com/tair-opensource/RedisShake/releases/download/v3.1.11/redis-shake-linux-amd64.tar.gz
cd /data
tar zxvf redis-shake-linux-amd64.tar.gz -C redis-shake/
cd redis-shake/
2. 配置文件
restore.toml scan.toml sync.toml
3. 恢复
bash
./redis-shake -conf=conf/restore.toml &
4. 数据同步
bash
./redis-shake -conf=conf/sync.toml &
示例: 将一个单实例中的数据迁移至cluster中
步骤流程 :
创建xx.toml配置文件 --> 编写toml配置文件同步规则 --> 执行redis-shake进程 --> 核对数据同步情况
1. 创建&编写配置文件
bash
$ cat /data/redis-shake/conf/sync-6400tocluster.toml
type = "sync"
[source]
version = 6.2 # redis version, such as 2.8, 4.0, 5.0, 6.0, 6.2, 7.0, ...
address = "127.0.0.1:6400"
username = "" # keep empty if not using ACL
password = "" # keep empty if no authentication is required
tls = false
elasticache_psync = "" # using when source is ElasticCache. ref: https://github.com/alibaba/RedisShake/issues/373
[target]
type = "cluster" # "standalone" or "cluster"
version = 6.2 # redis version, such as 2.8, 4.0, 5.0, 6.0, 6.2, 7.0, ...
# When the target is a cluster, write the address of one of the nodes.
# redis-shake will obtain other nodes through the `cluster nodes` command.
address = "127.0.0.1:6510"
username = "" # keep empty if not using ACL
password = "" # keep empty if no authentication is required
说明
- 1.x和2.x版本过滤
(1) 如果需要迁移全部的key,则 filter.db = 空,如下:
filter.db =
(2) 如果需要迁移某几个key,则分号分隔,如下:
filter.key = aaattt;hello
(3) 迁移:Abc.Route前缀的key、aaa前缀的key
filter.key = Abc.Route;aaa - 3.x版本过滤
使用lua脚本自定义过滤规则。Redis-shake可以用下面的命令启动:
./bin/redis-shake sync.toml filter/xxx.lua
2、启动
bash
./redis-shake conf/sync-6400tocluster.toml
3、验证数据是否迁移完成
2023-06-17 19:05:28 INF send RDB finished. address=[127.0.0.1:6400], repl-stream-db=[0]
4、新增数据验证cluster中是否存在
bash
[root@OPS-9-78 ~]# redis-cli -p 6400 set qq 20230617
OK
[root@OPS-9-78 ~]# redis-cli -c -p 6510 get qq
"20230617"
注意 :
clusterA -> clusterB: 将一个clusterA (3节点) 中的数据迁移至clusterB中,需要把A集群当做3个单机实例,然后部署3个 redis-shake 进行数据同步。
5.4 redis-shake注意事项
- 如果目标库的数据淘汰策略(maxmemory-policy)配置为noeviction以外的值,可能导致目标库的数据与源库不一致
- 如果源库中的某些Key使用了过期(expire)机制,由于可能存在Key已过期但未被及时删除的情形,所以在目标库中查看(如通过info命令)到的Key数量会比源库的Key数量少
6、Redis备份恢复
备份
python
def rdb_baK(self, pwd):
cmd_baK_redis="{recmd} -h {host} -p {port} -a '{pwd}' --rdb {file}" .format(recmd=recmd, host=self.domain_ip, port=self.port, pwd=pwd, file=self.rdb_file_path)
(status, output) = commands.getstatusoutput(cmd_baK_redis)
log.info(output)
if status == 0:
log.error(output)
return False,"backup fail"
return True, "backup successful"
恢复
python
def restore_rdb(self):
#获取集群当天的rdb文件
if not self.cmdutil.is_exists(self.rdb_dir):
msg = "rdb_dir={} is not exist.".format(self.rdb_dir)
self.send_mg.send_fail(tag, self.id, msg, self.title)
return
rdb_files_path_list = self.get_rdb_files(self.rdb_dir)
rdb_files_path_str = rdb_files_path_list[0]
if len(rdb_files_path_list) > 1:
rdb_files_path_str = ";".join(rdb_files_path_list)
log.info(rdb_files_path_str)
#1.修改配置中的数据
sta_m = self.modify_config(rdb_files_path_str)
log.info(sta_m)
if not sta_m:
msg = "( )修改失败".format(self.shakeconf)
self.send_mq.send_fail(tag, self.id, msg, self.title)
return
#2.执行恢复的命令
cmd_shake = "../redis-shake -conf=conf/rdb_().conf -type=restore&".format(str(self.restorePort))
log.info("cmd_shake=%s", cmd_shake)
os.popen(cmd_shake)
log.info("Redis data is recovering, please wait...........")
#3.判断恢复是否完成
sta, msg = self.is_rdb_success()
log.info(msg)
#生成恢复用的配置文件
def modify_configelf, rdb_files_path_str):
tar_host="{}:{}", format(self.restoreHost, str(self.restorePort))
log.info("target.address=%s", tar_host)
log.info("source.rdb.input=%s", rdb_files_path_str)
target_address = "target.address = {}".format(tar_host)
rdb_input = "source.rdb.input = {}".format(rdb_files_path_str)
cmd_host = "sed -i 's#'target.address.*#%s#g' %s" % (target_address, self.shakeconf)
cmd_rdb = "sed -i 's#'source.rdb.input.*#%s#g' %s" % (rdb_input, self.shakeconf)
log.info(cmd_host)
log.info(cmd_rdb)
status1, output1, msg1 = self.command(cmd_host)
if not status1:
log.error(output1)
return False
status2, output2, msg2 = self.command(cmd_rdb)
if not status2:
log.error(output2)
return False
return True
7. Redis企业使用规范
开发规范
-
key名设计
- 建议 :可读性和可管理性
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:idugc:video:1 - 建议 :简洁性
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
user::{uid}:friends:messages:{mid}简化为u::{uid}:fr:m:{mid} - 强制 :不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符
- 建议 :可读性和可管理性
-
value设计
-
强制 :拒绝bigkey(防止网卡流量、慢查询) #最重要
string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
反例:一个包含200万个元素的list。
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查),查找方法和删除方法 -
推荐 :选择适合的数据类型。
例如:实体类型(要合理控制和使用数据结构内存储码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)
反例:bashset user::{id}:name tom set user::{id}:age 19 set user::{id}:favor football正例:
bashhmset user::{id} name tom age 19 favor football -
推荐 :控制key的生命周期,redis不是垃圾桶。
建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
-
命令使用
-
推荐 O(N)命令关注N的数量
例如
hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。 -
推荐 :禁用命令
禁止线上使用
keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scanf的方式渐进式处理。 -
推荐 合理使用select
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
-
推荐 使用批量操作提高效率
原生命令:例如
mget、mset。非原生命令:可以使用pipeline提高效率。
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
注意两者不同:
- 原生是原子操作,pipeline是非原子操作。
- pipeline可以打包不同的命令,原生做不到。
- pipeline需要客户端和服务端同时支持。
-
建议 Redis事务功能较弱,不建议过多使用
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)
-
建议 Redis集群版本在使用Lua上有特殊要求:
- 所有key都应设由 KEYS 数据来传递,redis.call/pcall 里面调用的redis命令,key的位置,必须是 KEYS array,否则直接返回error,"-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS arrayrn"
- 所有key,必须在1个slot上,否则直接返回error,"-ERR eval/evalsha command keys must in same slotrn"
-
建议 必要情况下使用monitor命令时,要注意不要长时间使用。
客户端使用
-
推荐
避免多个应用使用一个Redis实例
正例:不相干的业务拆分,公共数据做服务化。
-
推荐
使用带有连接池的数据库,可以有效控制连接,同时提高效率,标准使用方式:
-
建议
高并发下建议客户端添加熔断功能(例如netflix hystrix)
-
推荐
设置合理的密码,如有必要可以使用ssL加密访问(阿里云Redis支持)
-
建议
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
默认策略是volatile-lru,即超过最大内存后,在过期中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
其他策略如下:
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
- allkeys-random:随机删除所有键,直到腾出足够空间为止。
- volatile-random:随机删除过期键,直到腾出足够空间为止。
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
- noeviction:不会删除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
删除bigkey
redis 4.0已经支持key的异步删除,欢迎使用。
- Hash删除:hscan + hdel
- List删除:ltrim
- Set删除:sscan + srem
- SortedSet删除:zscan + zrem
8. cachedoud
(1) CacheCloud 是什么?
CacheCloud是一个Redis云管理平台,支持Redis多种架构(Standalone、Sentinel、Cluster)高效管理、有效降低大规模redis运维成本,提升资源管控能力和利用率。平台提供快速搭建/迁移,运维管理,弹性伸缩,统计监控,客户端整合接入等功能。
(2) 部署
获取运行war包
CacheCloud 支持两种部署方式
- 源码编译 后运行
- 直接下载war包运行
初始化数据库
创建一个cachecloud的数据库
执行项目中sql目录下对应版本的sql文件,我部署的是3.2,执行的3.2.sql。
修改配置文件
在项目的src/main/resources目录下,有几个配置文件
- application-local.yml
- application-online.yml
- application-open.yml
- application-test.yml
这几个配置文件为不同环境的配置。我使用的local环境,就配置了application-local.yml
主要配置是mysql和redis的配置
yaml
cachecloud:
primary:
url: jdbc:mysql://xxxx:3306/cachecloud_open?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&connectTimeout=3000&socketTimeout=10000&serverTimezone=Asia/Shanghai
user: root
password: xxxxxx
initialPoolSize: 1
maxPoolSize: 3
redis: #配置cachecloud-web需要的redis,用户存储任务流log
main:
host: xxxx
port: 6379
password: 123456
启动项目
bash
#启动web工程,通过-Dspring.profiles.active=local 指定启动环境
nohup java -jar cachecloud-web.war -Dspring.profiles.active=local &
项目启动后直接ip:端口访问,我的ip是192.168.9.78,账户名和密码是admin、admin
http://192.168.9.78:8080/manage/login
(3) 配置修改
CacheCloud管理界面功能
- 全局统计
- client统计
- server统计
- 工单审批
- 应用运维
- 实例运维
- 应用导入
- 数据迁移
- 诊断工具
- 机器管理
- 报警配置
- 系统配置
- 资源管理
配置修改
填写配置
ssh授权方式(*): 密码
机器ssh用户名(*): root
机器ssh密码(*): 111111
公钥用户名(*): cachecloud-open
密钥路径(*): /opt/ssh/id_rsa
机器ssh端口(*): 22
cachecloud-admin用户名(*): admin
cachecloud-admin密码(*): admin
(4) 添加机器
机器管理界面
机器ID: 机器ID:多台机器分隔 注:多台机器用:分隔
机器: 默认机器
内存: 机器内存(单位G) G
CPU: 机器CPU接收 核
disk: 机器磁盘空间G G
是否虚拟: 否
操作系统: centos
主机ID: 宿主机ID: 多台机器分隔 注:多台机器用:分隔
机器信息: Redis机器(默认) 机器类型:
(5) 导入应用
导入应用主要的是把分散在各个地方的redis导入到cacheCloud平台,注意,不是接管,而是在cacheCloud中创建一个新的redis应用服务,将原来的数据迁移过来。
应用导入流程
- 确认导入配置
- 创建Redis版本
- 新建应用
- 数据迁移
- 应用导入完成
导入前准备:
- 为应用创建版本
- 部署应用
- 新老实例redis数据迁移
- 导入完成
应用创建状态:
-
- 应用部署异常,请【修复】或【重新部署】
从【获得申请】->【导入应用】填写相关申请信息
应用导入申请
源:Redis实例信息
存储类型: Redis-standalone
实例详情(*): 192.168.1.150:6379
redis密码: 123456
redis密码,如果没有则为空
实例格式说明
每行格式都是: ip:port(例如:10.10.xx.xx:6379)
1. standalone类型:
master:ip:masterPort
2. sentinel类型:
master_name:master
sentinel:ip1:sentinelPort1
sentinel:ip2:sentinelPort2
sentinel:ip3:sentinelPort3
3. cluster类型:
master:ip1:masterPort1
master:ip2:masterPort2
master:ip3:masterPort3
目标:应用信息
应用名称(*): show-home-local
如:cachecloud-js-online,全局唯一
应用描述(*): 演示信息
不超过128个字符,可以包含中文
项目负责人(*): 琳达【linda@qq.com】
存储种类: Redis-standalone ✔ 内存总量(*): 1
Redis部署版本: redis-5.0.9 ✔ 其他: 格式:redis-x.x.x
测试: 否 ✔ 是否有数据备份: 是 ✔
是否需要持久化: 是 ✔ 是否需要slave: 是 ✔
预估QPS(*): 800 预估条目数量:(*): 100000
客户端机房:(*): local (192.168.1.*)
内存报警阀值(*): 90
例如:内存使用率超过90%报警,请填写90(大于100以上则不报警)
客户端连接数报警阀值(*): 2000
例如:如客户端连接数超过2000报警,填写2000
检查格式 提交申请
申请完以后,在我的工单里出现一条导入任务
管理员审批完以后,就会部署迁移了,对是迁移,创建一个新的节点迁移进来
应用部署信息示例
应用部署 ID:1
| 应用Id | 1 | 应用名称 | show-home-local |
|-----------|------|-------------|-----------------|
| 存储种类 | RedisStandalone | 内存申请详情 | |
| 是否需要路由器 | 是 | 是否有后续数据源 | 有 |
| 是否测试 | 否 | 是否需要持久化 | 是 |
| 预估QPS | 800 | 预估条目数量 | 100000 |
| 客户端机房信息 | local | Redis版本 | redis-5.0.9 |
| 申请安装Redis模块 | | 淘汰策略 | |
应用基础信息
| 应用级别 | B | ✔ |
|-------------|------|-------------|
| Redis版本 | redis-5.0.9 | ✔ |
| Redis密码: | 2651080c6814a449d62da69a12f962b6 | □设置自定义密码 |
Redis模块信息申请安装模块
选择安装Redis模块:
应用部署信息
| 应用类型*: | Redis-Standalont | maxMemory*: | 512 |