程序员学点xx 之 Redis
概述
其实程序员也要和操作系统打交道, 比如最常见的,部署自己电脑上的开发环境.
当然有时某些牛人, 觉得运维或基础部门的同事不够给力, 亲自上手部署服务器或线上环境,这种情况也是存在的.综上所述, 程序员和运维接触的东西是一致的, 只是涉及群集或动作原理上会差一点点.
我的目的,就是花点时间把这些运维的细碎知识梳理一下, 保证被人问起来完全不虚.
群集
单独的redis大家应该都会部署了, 下载源码包, 编译一下就成. 或者使用docker 命令 pull 一下.
下面来说群集 CLUSTER
一般 CLUSTER 习惯布6台: 3主 3从
如果单台会部署的话,其实也很简单
修改一下 redis.conf, 把监听端口, 节点等选项打开就行了
主要涉及下列项目:
- bind
- daemonize
- requirepass
- masterauth
- logfile
- cluster-enabled
- cluster-config-file
- cluster-node-timeout
- dbfilename
- appendfilename
修改完成后, 把6台redis 启动起来, 登录进redis
/root/docker_redis_cluster/redis-3.2.11/src/redis-cli -h 127.0.0.1 -p 6379
auth 123456
info replication
然后用meet命令把其他群集加入进来即可
CLUSTER MEET 172.17.0.3 6379
CLUSTER MEET 172.17.0.4 6379
...
CLUSTER NODES
需要分配下槽位
redis-cli -p 6379 cluster addslots {0..5461}
redis-cli -p 6380 cluster addslots {5462..10922}
redis-cli -p 6381 cluster addslots {10923..16383}
CLUSTER NODES
分配的3台就是主了
登录另外3台服务器, 分别把主添加给自己, 集群就完成了
CLUSTER NODES
CLUSTER REPLICATE 760e4d0039c5ac13d04aa4791c9e6dc28544d7c7
# 分别登录从机,加入主服务器的id
茶歇
昨天搞定了redis的集群, 本来是没什么问题了, 但看完关于矫情的记述后我觉得, 一定会有人跳出来:
“low货,人家redis集群都是用集群脚本安装的.”
作为一个9012年的猿, 虽然yann觉得可以举100个理由证明集群脚本的不方便性, 但脚本本身还是可以了解一下的.
脚本安装
这里所说的脚本是叫做 redis-trib 的rb脚本.
虽然100个理由比较难, 但少数不用的理由还是举的出来的:
- 需要ruby环境
- 对容器的支持不好
- 不支持密码
下面逐条开撕:
需要ruby环境
yum install ruby
# 或 apt-get install ruby
gem install redis
这里还会有一个坑:
gem时系统会报ruby的版本太低
至于如何使用rvm修改版本就是另一篇文了
对于洁癖人士 (比如yann)来说, 无端在系统里安装ruby是不可接受的.
况且还有衍生问题,后面会叙述.
命令参数
cd /usr/local/src/redis-3.2.11/src
./redis-trib.rb help
create
check
info
fix
reshard
创建集群
./redis-trib.rb create --replicas 1 127.0.0.1:6479 127.0.0.1:6480 127.0.0.1:6481 127.0.0.1:6482 127.0.0.1:6483 127.0.0.1:6484
ps. 如果需要指定服务器为从库, 只能先添加3台, 再通过新增节点指定其对应主库
不支持密码
第二点理由爆发了.
redis-trib.rb 会报无法连接集群, 需要修改client.rb文件
修改源码文件后, 脚本连接上了集群,继续其他操作
测试集群
./redis-trib.rb check 127.0.0.1:6479
查看信息
./redis-trib.rb info 127.0.0.1:6479
平衡节点
根据权重分配, 比较有用的功能之一
./redis-trib.rb rebalance 127.0.0.1:6479
删除节点
只能删除没有分配slot的节点
./redis-trib.rb del-node 127.0.0.1:6480
添加节点
加主库注意添加的服务器在前, 加从库需要主库id
./redis-trib.rb add-node 127.0.0.1:6488 127.0.0.1:6479
./redis-trib.rb add-node --slave --master-id 77c2a2d5e96d14a4c5b5614cb68ad27d40530f4b 127.0.0.1:6480 127.0.0.1:6479
踩坑
其实上面的操作统统没有完成.
原因很简单, yann使用容器搭的redis
会有2个ip, 容器外ip和容器内ip.
使用容器内ip脚本运行不起来, 而使用容器外ip的话,脚本完成不了. 原因请自己想一下.
到这一步,算掉到坑里出不来了, 虽然在容器内部安装ruby可以解决, 但容器是精简系统, redis文件也不全…
redis-trib.rb 对容器的支持不好
茶歇
面试别人还是很有技巧的,比如:
- 用容器搭一个redis出来
- 你知道redis-trib.rb文件么
- 如何用redis-trib.rb和刚才搭的redis做一个集群出来
答案是做不出来, 原因请看昨天的巨坑.
生产操作
搞定了群集之后就可以做一些具体的工作了, 例如:
- 槽位迁移
- 再平衡
- 单机迁移到群集
槽位迁移
为什么要进行槽位迁移呢?
当然是为了集群的扩容/缩容啊.
redis的槽位其实是很重要的概念.
槽位不分配掉, 集群不能使用,
存在槽位的节点不能删除,
槽位只分配在主节点上…
曹魏: 说我么?
没有, 快滚…
在线迁移, 用来完成集群的在线横向扩容和缩容
./redis-trib.rb reshard 127.0.0.1:6479# 检查之后会出现交互信息, 询问迁移多少槽位,到哪个节点之类
参数迁移
生产中常用的方式
./redis-trib.rb reshard --from 7fa64d250b595d8ac21a42477af5ac8c07c35d83 --to 5476787f31fa375fda6bb32676a969c8b8adfbc2 --slots 10 127.0.0.1:6479
再平衡
新加入节点后, 槽位变的不平衡,可以用 rebalance 处理.
同样有密码的问题, 修改rb文件或配置文件上取消密码看各人爱好.
./redis-trib.rb rebalance 127.0.0.1:6479
单机迁移到集群
常用来处理历史问题,
当年需求急, 单枪匹马上线了12345
redis-trib.rb import--from ip:port:id # 源 单机--copy ip:port # 集群
密码问题,同样需要修改文件源码:
vi redis-trib.rb
承前
三天都在说使用集群的相关的知识, 今天来说明一下集群的工作原理.
虽然都是官方提供的东西:
Redis Cluster采用无中心节点方式实现,无需proxy代理
客户端直接与redis集群的所有节点连接
根据hash算法计算出key对应的slot
在slot对应的Redis上执行命令
以CAP角度来看
Redis Cluster 属于AP
Availability&Partition-Tolerancy
可用并分区容错
安全加固
redis 是系统中的漏洞大户
yann某次不能关机又需要root权限,就是从它这里拿的
私有环境操作, 请勿模仿
总之, 常见的加固如下:
- 开启redis密码认证
- 禁止使用root用户启动
- 修改默认6379端口
- 限制redis 配置文件访问权限
- 禁用或者重命名危险命令
- 禁止监听在公网
- 打开保护模式
开启redis密码认证
vi redis.conf中 requirepass# 配置强密码
禁止使用root用户启动
useradd -s /sbin/nolog -M redis sudo -u redis /<redis-server-path>/redis-server /<configpath>/redis.conf
修改默认6379端口
vi redis.conf中 port# 80,81,82的兄弟们
限制redis 配置文件访问权限
chmod 600 /<filepath>/redis.conf
禁用或者重命名危险命令
这条倒不是必要的, 毕竟不方便
vi redis.conf中 rename-command DEL ""# 或rename-command FLUSHALL joYAPNXRPmca
禁止监听在公网
vi redis.conf中 bind 127.0.0.1 # 或本地ip
打开保护模式
必要, 没有密码和bind只能本地访问
vi redis.conf中 protected-mode yes
疑问
之前yann有个疑问, 不知道大家会不会有类似的怀疑:
REDIS有主从和哨兵模式了, 要CLUSTER干嘛.
这个问题今天会解决
持久化相关命令
参考mysql备份机制
rdb相当于物理备份, aof 相当于 逻辑备份
vi redis.conf
dbfilename
appendfilename
# 主要参数及上下方类似名字的各种设置参数
从库换主
做法: 把从库清空然后从新主库完整同步一份数据再进行续传.
重做流程
- 主库bgsave自身数据到磁盘
- 主库发送rdb文件到从库
- 从库开始加载
- 加载完毕开始续传,同时开始提供服务
现实数据
一但数据量超过20GB, 复制时间超过20分钟
多台从库, 时间会倍增. 同时需要考虑网卡瓶颈
使用内存限制
vi redis.conf
maxmemory
从库扩容
同样问题, 从库扩容也会遇到.
当遇到流量暴增,应急性扩容, 以上扩容同样需要20分以上
如果仍然接受数据, 中断时间过长同步缓冲区被覆盖,重新同步
持久化进程阻塞Redis主线程, 20G内存耗时约为750ms
话题
都知道Redis很快, 但是有多快呢.
QPS测试
环境
i5 CPU 8GB 内存
REDIS测试工具
cd src
./redis-benchmark -n 1000000 -t set,get -q
# SET: 48936.32 requests per second
# GET: 51290.85 requests per second
REDIS 的大致数据约5w
MYSQL测试工具
sysbench
url -s https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh | sudo bash
sudo apt -y install sysbench
mysql -uroot -p -e"create database benchmark"
sysbench /usr/share/sysbench/oltp_read_write.lua --mysql-user=root --mysql-password=root --mysql-db=benchmark --tables=10 --table-size=1000000 --events=100000000 --report-interval=10 --threads=4 --time=300 prepare
sysbench /usr/share/sysbench/oltp_read_write.lua --mysql-user=root --mysql-password=root --mysql-db=benchmark --tables=10 --table-size=1000000 --events=100000000 --report-interval=10 --threads=4 --time=300 run
因为使用的阿里云RDS, 这个操作我没有做
相同配置服务器反馈是4000左右
单线程REDIS
数据都在内存, 单线程去操作效率最高
多线程存在上下文的切换
一次CPU上下文的切换约 1500ns
从内存中读取 1MB 的连续数据,耗时大约为 250us
假设多线程读取了1000次,光切换耗时1500ns * 1000 = 1500us
多线程REDIS
Redis 6 引入的多线程 IO 特性
优化方向
提高网络 IO 性能,使用 DPDK
动用多个核心运行多线程, 仅用在处理网络数据和协议解析
实现原理
- 主线程接收建连请求,读事件放队列处理
- 主线程将连接分配给其他 IO 线程,然后主线程等待
- IO 线程将请求数据读取并解析
- 主线程执行命令并清空队列
性能对比
vi redis.conf
io-threads 4
io-threads-do-reads yes
# 仅记录, 拿到后测试一下, 需要gcc 5.0+
redis-benchmark -h 192.168.0.49 -a foobared -t set,get -n 1000000 -r 100000000 --threads 4 -d ${datasize} -c 256
周报
Redis也看了一周了, 每天测试,记笔记, 总结…
感觉可以暂停一下了, 明天会有新的东西.
以下以Redis的最后一部分
缓存雪崩
所谓缓存雪崩就是缓存挂掉了,全部请求都跑去找数据库了
这里有2个前提:
- 业务流量大
- 系统没有降级限流设置措施
先说流量, Mysql 5.7 默认最大连接数200, 通常会配置成2000, 加上只读库可以看作4000. 加上普遍连接池运作, 一般可以抗一段时间, 坚持到redis拉起来.
再说限流, SpringCloud和 Dubbo 都可以有 Hystrix, 比较核心的业务, 建议配置起来.
如何解决缓存雪崩?
基于系统的故障总是好解决的
Redis集群也好, 对其本身的监控或对Mysql的监控也好, 都可提醒有故障了, 去处理.
难的是逻辑上的故障
比如写死过期时间, 结果同时全部过期,
再比如新功能上线直接没缓存. 需要缓存预热等.
缓存穿透
缓存穿透就很简单了, 就是一直请求不存在的参数.
结果 Redis就没东西提供, 直接交给数据库处理了.
同样和数量有关, 少量够不成危害. 而大量的话, redis命中率偏低, 直接会有报警出来.
解决方式也有二种:
要么布隆过滤不合理参数, 要么干脆缓存空对象, 使用哪种方式, 就看自己方便了.
REDIS与MYSQL 一致性
缓存什么都好, 就是一致性让人头疼. 不光Redis和Mysql, 浏览器的缓存, 甚至CDN, 一切缓存相关的问题都让人头疼.
读操作
如果数据在缓存里边有,那么直接取缓存返回。
如果缓存里没有, 先去查询数据库,然后将结果写到缓存中。最后将数据返回给请求。
写操作
先更新数据库, 再操作缓存失败.
优雅一点的做法是, 在MySQL端定义CRUD触发器,或在Redis端解析binlog, 进行操作.
更现实的做法是, 用消息队列保证更新操作能够完成, 甚至对同样数据的请求放在一个队列里, 当然也可能全部卡住.
更新缓存
这里没有涉及更新缓存, 事实上定时更新也好, 逻辑判断有效性也好, 都是基于业务特性来决定的.
缓存算法
FIFO 先进先出
LRU 近期最少使用, 使用时间差异
LFU 最不经常使用, 次数差异
注: Redis 4.0 才有LFU.
总结
从1号到现在, 蜻蜓点水写了很多. 大部分都关注于坑和生产应用, 对日常使用Redis的记录比较少.
没办法, 时间还是不够. 就算强力驱动着自己还是浅尝辄止. 任何一个应用都可以静下心来揣摩一个月, 但是没有时间.
现在只能拼命吃一点, 以求遇到的时候不要没有思路. 其他只能后续研究了. 共勉!
本文由博客一文多发平台 OpenWrite 发布!
最新内容欢迎关注公众号: