<Redis开发与运维> 阅读笔记
===第一章===初识Redis===
redis 是有一种基于键值对的(key-value)的NoSQL数据库。与很多键值对数据库不同的是,redis中的值可以是:
1、string 字符串
2、hash 哈希
3、list 列表
4、set 集合
5、zset 有序集合
6、Bitmaps 位图
7、HyperLogLog
8、GEO(地址信息定位)等多种数据结构和算法组成。
redis 还可以将内存的数据以快照和日志形式保存在磁盘上。
redis 还提供了键过期,发布订阅,事务,流水线,Lua脚本等附加功能。
redis 8大特性:
1、速度快
2、基于键值对的数据结构服务器
3、丰富的功能
A. 提供键值过期功能,可以用来实现缓存
B. 提供了发布订阅服务,可以用来实现消息系统
C. 支持Lua脚本
D. 提供了简单的事务功能,能再一定程度上保证事务特性
E. 提供了流水线(Pipline)功能,这样客户端能将一批命令一次性传到redis,减少了网络的开销
4、简单稳定
5、客户端语言多
6、持久化 RDB 和 AOF
7、主从复制
8、高可用和分布式
redis从2.8版本开始提供redis sentinel,保证redis节点故障的发现和故障自动转移。从3.0开始提供了cluster。
redis可以做什么:
1、缓存
2、排行榜系统
3、计数器应用
4、社交网络
5、消息队列系统
redis-server 启动文件
redis-cli redis命令行客户端
redis-benchmark redis基准测试工具
redis-check-aof redis AOF 持久化文件检测和修复工具
redis-check-dump redis RDB 持久化文件检测和修复工具
redis-sentinel 启动redis sentinel
redis关闭三点需要注意一下:
1、redis关闭的过程:断开与客户端的连接,持久化文件生成,是一种相对优雅的关闭方式
2、除了shutdonw外,还可以通过kill -9,不但不会做持久化操作,还会造成缓存冲不会优雅关闭,极端情况会造成AOF和复制数据丢失
3、shutdown还有一个参数,代表关闭之前是否持久化 redis-cli shutdown nosave | save
redis版本规则,第二位如果是奇数,为非稳定版本,第二位为偶数,则为稳定版本。
===第二章===API的理解和使用===
简单命令:
1、keys * 遍历所有的键值
2、dbsize 查看redis中的键总数,dbsize不会遍历所有键值
3、exists key 检查键是否存在
4、del key [key...] 删除键
5、expire key seconds 键过期
expire hello 10 设置hell键10秒后过期删除
6、ttl 命令返回键的剩余过期时间,有三种返回值:
A.大于等于0的整数:键剩余的过期时间
B.-1 键没设置过期时间
C.-2 键不存在
通过ttl查看hello过期时间: ttl hello
7、键的数据结构类型
A. type key
B. 如果键不存在,则返回 none
每种数据结构都有两种以上的内部编码实现,例如list数据结构包含了linkedlist和ziplist两种内部编码,同时有些内部编码,例如ziplist,可以作为外部数据结构的内部实现。可以通过: object encoding命令查询
string 内部编码:raw、int、embstr
hash 内部编码: hashtable、ziplist
key list 内部编码: linkedlist、ziplist
set 内部编码: hashtable、intset
zset 内部编码: skiplist、ziplist
例如redis3.2提供了 quicklist,结合了 ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,对外用户根本感知不到。ziplist比较节省内存,但是在
列表元素比较多的情况下,性能会有所下降,这时候redis会根据配置选项将列表类型的内部实现转换为linkedlist。
单线程架构:
redis使用了单线程架构和IO多路复用模型来实现高性能的内存数据库服务。
redis是单线程来处理命令的,所以一条命令从客户端达到服务端不会立刻执行,所有命令都会进入一个队列中,然后逐个执行,执行顺序是不确定的。
redis单线程为什么还快?
1、纯内存访问
2、非阻塞IO,Redis使用epoll作为I/O多路复用技术的实现
3、单线程避免了线程切换和竞态产生的消耗
1000次get 耗时1.1秒
一次mget(组装了1000个键值对) 耗时0.101秒
不常用命令:
1、追加值:append key value
2、字符串长度:strlen key
3、设置并返回原值:getset key value
4、设置指定位置的字符:setrange key offeset value
5、获取部分字符串:getrange key start end
字符串类型的内部编码有3种:
·int:8个字节的长整型。
·embstr:小于等于39个字节的字符串。
·raw:大于39个字节的字符串。
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
列表类型有两个特点:
1、列表中的元素是有序的,可以通过索引下标获取某个元素等,索引从0开始计算
2、列表中的元素是可以重复的
列表的四中操作类型:
添加:rpush、lpush、linsert
查:lrange、lindex、llen
删除:lpop、rpop、lrem、ltrim
修改:lset
阻塞操作:blpop、brpop
从右边插入元素:
rpush key value [value ...]
从左边插入元素:
lpush key value [value ...]
向某个元素前或者后插入元素:
linsert key before|after pivot value
获取指定范围内的元素列表:
lrange key start end
获取列表指定索引下标的元素:
lindex key index
从列表左侧弹出元素:
lpop key
从列表右侧弹出:
rpop key
删除指定元素:
lrem key count value
lrem命令会从列表中找到等于value的元素进行删除,根据count的不同 分为三种情况:
·count>0,从左到右,删除最多count个元素。
·count<0,从右到左,删除最多count绝对值个元素。
·count=0,删除所有。
按照索引范围修剪列表:
ltrim key start end
修改指定索引下标的元素:
lset key index newValue
阻塞式弹出如下:
blpop key [key ...] timeout
brpop key [key ...] timeout
·lpush+lpop=Stack(栈)
·lpush+rpop=Queue(队列)
·lpsh+ltrim=Capped Collection(有限集合)
·lpush+brpop=Message Queue(消息队列)
集合:
一个集合最多可以存储232-1个元素。
计算元素个数:
scard key
scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用 Redis内部的变量。
ttl命令和pttl都可以查询键的剩余过期时间,但是pttl精度更高可以达到 毫秒级别,有3种返回值:
·大于等于0的整数:键剩余的过期时间(ttl是秒,pttl是毫秒)
·-1:键没有设置过期时间
·-2:键不存在
在使用Redis相关过期命令时,需要注意以下几点:
1、如果expire key的键不存在,返回结果为0
2、如果过期时间为负值,键会立即被删除,犹如使用del命令一样
3、persist命令可以将键的过期时间清除
4、对于字符串类型键,执行set命令会去掉过期时间,这个问题很容易 在开发中被忽视
5、Redis不支持二级数据结构(例如哈希、列表)内部元素的过期功 能,例如不能对列表类型的一个元素做过期时间设置
6、setex命令作为set+expire的组合,不但是原子执行,同时减少了一次 网络通讯的时间
迁移键:
1、move :
在redis内部进行迁移,例如由db1迁移至db2
2、dump+restore
A. 在源redis上,dump命令会将键值序列化,格式采用的是RDB格式
B. 在目标redis上,restore命令将上面的序列化的值进行复原,其中ttl参数代表过期时间,如果ttl=0代表没有过期时间
语法:dump key
restore key ttl value
例子:在源redis上执行dump
redis-source> set hello world
OKredis-source> dump hello
"x00x05worldx06x00x8f<Tx04%xfcNQ"
在目标redis上执行restore
redis-target> get hello
(nil)
redis-target> restore hello 0 "x00x05worldx06x00x8f<Tx04%xfcNQ"
OK
redis-target> get hello
"world"
3、migrate
语法:migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key
参数:
host:目标redis的IP地址
port:目标redis的端口
key"":在Redis3.0.6版本之前,migrate只支持迁移一个键,所以此处是要迁移的键,但Redis3.0.6版本之后支持迁移多个键,如果当前需要迁移多个键,此处为空字符串""。destination-db:目标Redis的数据库索引,例如要迁移到0号数据库,这里就写0.
timeout:迁移的超时时间(单位为毫秒)
[copy]:如果添加此选项,迁移后并不删除源键
[replace]:如果添加此选项,migrate不管目标Redis是否存在该键都会正常迁移进行数据覆盖
[keys key[key...]]:迁移多个键,例如要迁移key1、key2、key3,此处填 写“keys key1 key2 key3”
操作:
源Redis有键hello,目标Redis没有:
127.0.0.1:6379> migrate 127.0.0.1 6380 hello 0 1000
OK
源Redis和目标Redis都有键hello:
127.0.0.1:6379> get hello "world"
127.0.0.1:6380> get hello "redis"
如果migrate命令没有加replace选项会收到错误提示,如果加了replace会返回OK表明迁移成功:
127.0.0.1:6379> migrate 127.0.0.1 6379 hello 0 1000
(error) ERR Target instance replied with error: BUSYKEY Target key name already
127.0.0.1:6379> migrate 127.0.0.1 6379 hello 0 1000 replace
OK
源Redis没有键hello。如下所示,此种情况会收到nokey的提示:
127.0.0.1:6379> migrate 127.0.0.1 6380 hello 0 1000
NOKEY
Redis3.0.6版本以后迁移多个键的功能
源Redis批量添加多个键:
127.0.0.1:6379> mset key1 value1 key2 value2 key3 value3
OK
源Redis执行如下命令完成多个键的迁移:
127.0.0.1:6379> migrate 127.0.0.1 6380 "" 0 5000 keys key1 key2 key3
OK
move、dump+restore、migrate比较:
命令 作用域 原子性 支持多个键
move redis实例内部 是 否
dump+restore redis实例之间 否 否
migrate redis实例之间 是 是
migrate命名也是用于在redis实例间进行数据迁移的。migrate命令是 dump,restore,del 三个命名组合,从而简化了操作流程。
流程:
1、整个过程原子执行,在源redis上执行migrate命令即可
2、migrate命令的数据传输直接在源redis和目标redis上完成
3、目标redis完成restore后,反馈OK给源redis,源redis接收后会根据migrate对应的选项来决定是否在源redis上进行删除对应的键
遍历键:
1、keys
2、scan
删除video开头的key
redis-cli keys video* | xargs redis-cli del
渐进式遍历
scan的语法:
scan cursor [match pattern] [count number]
参数说明:
cursor:是必需参数,实际上cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。
match pattern:是可选参数,它的作用的是做模式的匹配,这点和keys的模式匹配很像。
count number:是可选参数,它的作用是表明每次要遍历的键个数,默认 值是10,此参数可以适当增大。
例如:redis有26个英文字母需要遍历
127.0.0.1:6379> scan 0 (第一部分6表示下次scan需要的cursor,第二部分是10个键)
1) "6"
2) 1) "w"
2) "i"
3) "e"
4) "x"
5) "j"
6) "q"
7) "y"
8) "u"
9) "b"
10) "o"
127.0.0.1:6379> scan 6 (这次第一部分11表示下次scan需要的cursor,直到位0,表示全部遍历完成)
1) "11"
2) 1) "h"
2) "n"
...
10) "a"
除了scan以外,Redis提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan,它们的用法和scan基本类似。
渐进式遍历可以有效的解决keys命令可能产生的阻塞问题,但是scan并非完美无瑕,如果在scan的过程中如果有键的变化(增加、删除、修改),那么遍历效果可能会碰到如下问题:新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键。
数据库管理
1、切换数据库:select dbindex (Redis默认配置中是有16个数据库)
2、flushdb/flushall 命令用于清除数据库,两者的区别的是flushdb只清除当前数据库,flushall会清除所有数据库。
===第三章===小功能大用处===
redis慢查询
1、获取慢查询日志:
slowlog get [n] n:指定条数
2、1) 1) (integer) 5 慢查询日志的标识ID
2) (integer) 1594698777 慢查询发生的时间戳
3) (integer) 264494 慢查询执行时间
4) 1) "keys" 执行的命令
2) "*"
5) "127.0.0.1:43146" 连接地址
6) ""
3、查看当前redis有多少条慢查询:
127.0.0.1:2002> SLOWLOG len
4、慢查询重置:
127.0.0.1:2002> slowlog reset
慢查询只记录命令执行时间,不包括命令排队和网络传输时间。因此客户端执命令的时间会大于命令实际执行的时间。
redis shell
redis-cli --help
每秒输出内容使用量,一共输出100次:
redis-cli -r 100 -i 1 info | grep used_memory_human
--latency:
测试客户端到目标redis的网络延迟
redis-cli -h xxxxx -p 6379 --latency
--latency-history
redis-cli -h xxxxx -p 6379 --latency-history -i 3 (-i 时间间隔,默认15S)
--latency-dist
该选项会使用统计图表的形式从控制台输出延迟统计信息
redis-benchmark详解
参数:
1、 -c:客户端的并发数,(默认50)
2、 -n<requests>:-n(num)选项代表客户端请求总量(默认是100000)
例如:
redis-benchmark -c100 -n20000
代表100各个客户端同时请求redis,一共执行20000次。
3、-q: 仅仅显示redis-benchmark的requests per second信息
4、-r: 可以向redis插入更多随机的键。-r 选项会在key,counter键上加一个12位的后缀,-r10000代表对后四位做随机处理(-r不是随机的个数)
5、-P: 代表每个请求pipeline的数据量(默认为1)
6、-k<boolean>: -k选项代表客户端是否使用keepalive,1为使用,0为不使用,默认值为1
7、-t: 对指定命令进行基准测试
8、--csv: 会将结果按照csv格式输出
Pipeline概念
将一组命令进行封装后执行
redis事务(不支持回滚)
redis提供了简单的事务功能,将一组命令放到multi和exec之间。 multi代表事务开始,exec代表事务结束。如果要结束事务,用discard命令代替exec命令即可。
watch命令:
1、redis事务执行命令,redis提供了watch命令,类似乐观锁。
2、执行步骤如下:
set key "java" (在客户端1)
watch key
multi
append key python (在客户端2)
append key jedis
exec
get key
===第四章===客户端===
客户端通信协议
1、redis客户端和服务端之间的通信协议是在TCP协议之上构建的
2、redis订制了RESP(REdis Serialization Protocol,Redis序列化协议)实现客户端与服务端的正常交互
发送命令格式:
例如:发送 set hello world
*3 参数数量为3个
参数字节数为3 5 5
$3
set
$5
hello
$5
world
有一点要注意的是,上面只是格式化显示的结果,实际的传输格式如下:
*3
$3
SET
$5
hello
$5
world
java客户端Jedis
1、直接下载jedis目标版本加入到项目中
2、使用集成工具,maven、gradle等将jedis目标版本的配置加入到项目中(通常都是用这种)
jedis的基本使用方法
下面三行代码就可以实现get功能:
# 1. 生成一个Jedis对象,这个对象负责和指定Redis实例进行通信
Jedis jedis = new Jedis("127.0.0.1", 6379);
# 2. jedis执行set操作
jedis.set("hello", "world");
# 3. jedis执行get操作, value="world"
String value = jedis.get("hello");
jedis包含了四个常用的参数:
Jedis(final String host, final int port, final int connectionTimeout, final int soTimeout)
参数说明:
host:redis实例所在机器的ip地址
port:redis实例的端口
connectionTimeout:客户端连接超时
soTimeout:客户端读写超时
jedis连接池使用方法
1、在生产环境中,一般使用连接池的方式对jedis连接进行管理,所有jedis对象预先放在池子中(jedis pool),每次连接redis只需要向池子中借,借完了再还。
2、客户端连接redis的方式是TCP协议的方式,直连的方式每次需要建立连接。
3、连接池的方式是可以预先初始化好jedis连接,所以每次只需要从连接池借即可,而借和还操作都是在本地进行的,只有少量的并发同步开销,远远小于新建TCP连接开销。
4、直连的方式无法限制jedis对象的个数,极端情况下可能会造成连接泄露
5、jedis支持pipeline特性
redis中pipeline的使用方法
redis提供了 mget、mset、却没有提供mdel,因此可以通过pipeline进行封装实现mdel模式
如下:
public void mdel(List<String> keys) {
Jedis jedis = new Jedis("127.0.0.1");
// 1)生成pipeline对象
Pipeline pipeline = jedis.pipelined();
// 2)pipeline执行命令,注意此时命令并未真正执行
for (String key : keys) {
pipeline.del(key);
}
// 3)执行命令
pipeline.sync();
}
获取redis-py
三种安装:
1、pip install redis
2、easy_install redis
3、使用源码安装:
wget https:// github.com/andymccurdy/redis-py/archive/2.10.5.zip
unzip redis-2.10.5.zip
cd redis-2.10.5
#安装redis-py
python setup.py install
redis-py的基本使用方法
使用方法
1、导入依赖库
import redis
2、生成客户端连接:需要Redis的实例IP和端口两个参数:
client = redis.StrictRedis(host='127.0.0.1', port=6379)
3、执行命令:redis-py的API保留了Redis API的原始风格
# True
client.set(key, "python-redis")
# world
client.get(key)
4、代码如下:
import redis
client = redis.StrictRedis(host='127.0.0.1', port=6379)
key = "hello"
setResult = client.set(key, "python-redis")
print setResult
value = client.get(key)
print "key:" + key + ", value:" + value
输出结果为:
True
key:hello, value:python-redis
redis-py五种数据结构的示例:
#1.string
#输出结果:True
client.set("hello","world")
#输出结果:world
client.get("hello")
#输出结果:1
client.incr("counter")
#2.hash
client.hset("myhash","f1","v1")
client.hset("myhash","f2","v2")
#输出结果:{'f1': 'v1', 'f2': 'v2'}
client.hgetall("myhash")
#3.list
client.rpush("mylist","1")
client.rpush("mylist","2")
client.rpush("mylist","3")
#输出结果:['1', '2', '3']
client.lrange("mylist", 0, -1)
#4.set
client.sadd("myset","a")
client.sadd("myset","b")
client.sadd("myset","a")
#输出结果:set(['a', 'b'])
client.smembers("myset")
#5.zset
client.zadd("myzset","99","tom")
client.zadd("myzset","66","peter")
client.zadd("myzset","33","james")
#输出结果:[('james', 33.0), ('peter', 66.0), ('tom', 99.0)]
client.zrange("myzset", 0, -1, withscores=True)
redis-py中Pipeline的使用方法
1)引入依赖,生成客户端连接:
import redis
client = redis.StrictRedis(host='127.0.0.1', port=6379)
2)生成Pipeline:注意client.pipeline包含了一个参数,如果transaction=False代表不使用事务:
pipeline = client.pipeline(transaction=False)
3)将命令封装到Pipeline中,此时命令并没有真正执行:
pipeline.set("hello","world")
pipeline.incr("counter")
4)执行Pipeline:
#[True, 3]
result = pipeline.execute()
5) 将用redis-py的Pipeline实现mdel功能
import redis
def mdel( keys ):
client = redis.StrictRedis(host='127.0.0.1', port=6379)
pipeline = client.pipeline(transaction=False)
for key in keys:
print pipeline.delete(key)
return pipeline.execute();
客户端管理
客户端API
1、client list
client list能列出与服务端相连的所有客户端连接信息
redis-cli -h 127.0.0.1 -p 4004 client list
id=5 addr=127.0.0.1:27817 fd=21 name= age=1443486 idle=0 flags=S db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=replconf
id=36 addr=127.0.0.1:10582 fd=24 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
id:客户端连接的唯一标识,这个id是随着redis的连接自增的,重启redis会为0
addr:客户端链接的ip和端口
fd:socket的文件描述符,与lsof命令结果中的fd是同一个,如果fd=-1代表当前客户端不是外部客户端,而是Redis内部的伪装客户端。
name:客户端的名字
qbuf、qbuf-free:输入缓冲区,分别代表这个缓冲区的总容量和剩余容量
redis为每个客户端分配了输入缓冲区,客户端发送的命令临时保存在输入缓冲区,redis从输入缓冲区拉取命令并执行。
obl、oll、omem:输出缓冲区,obl代表固定缓冲区的长度,oll代表动态缓冲区列表的长度,omem代表使用的字节数
age、idle:分别代表当前客户端已经连接的时间和最近一次的空闲时间
flags:S代表slave,N代表普通客户端,O代表monitor命令,
优点:client list 能精确分析每个客户端来定位问题
缺点:client list 执行速度慢(尤其是在连接数过多的情况下),频繁执行容易堵塞redis
读写超时原因:
1、读写超时设置时间过短
2、命令本身慢
3、客户端与服务端网络不正常
4、redis自身发生堵塞
找到omem非零的连接:
redis-cli client list | grep -v "omem=0"
HGETALL HLEN
===第五章===持久化===
RDB 持久化是把当前进程的数据生成快照保存到硬盘的过程。出发RDB分为两种,一种是手动触发,另一种是自动触发。
1、save命令:"阻塞"当前redis进程,直到RDB完成为止。
2、bgsave:redis进程会创建fork子进程,RDB的持久化由fork子进程完成。阻塞发生在fork阶段,一般时间很短。
自动触发RDB场景:
1、使用save m n,表示 m 秒内数据集存在 n 次修改时,自动出发bgsave
2、如果从节点执行全量复制,主节点自动执行bgsave生成RDB文件并发送给从节点
3、执行 debug reload 命令重新加载redis时,也会自动触发redis bgsave操作
4、默认情况下,执行shutdown操作,如果没有开启AOF持久化规则,自动执行bgsave
basave命令的运作流程:
1、执行bgsave命令,redis父进程判断是否存在正在执行的子进程,如RDB、AOF等,如果存在,bgsave命令直接返回。
2、父进程执行fork操作创建子进程,fork操作过程中父进程会堵塞
3、父进程fork完成后,bgsave返回 backgroud saving started信息,继续响应其他命令
4、子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行替换
5、进程发送信号给父进程表示完成,父进程更新统计信息。
AOF 持久化:以独立日志的方式记录每次写命令,主要作用是解决了redis数据持久化实时性。
AOF的工作流程:
命令写入(append)、文件同步(sync)、文件重写(rewrite)、重新加载(reload)
1、所有的写入命令会追加到缓冲区中
2、AOF根据对应策略向硬盘同步操作
3、随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
4、当Redis服务器重启时,可以加载AOF文件进行数据恢复
write操作在写入系统缓冲区后直接返回。同步硬盘操作依赖于系统调用机制。例如缓冲区页写满或者达到特定的周期。
重写机制:
重写后的AOF文件为什么可以变小?
1、进程内已经超时的数据不再写入文件
2、旧的AOF文件含有无效命令,如del key1、hdel key2,重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。
3、多条写命令可以合并为一个
AOF重写降低了文件占用空间,更小的AOF更快的被redis加载
手动触发:bgrewriteaof
自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机
auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默认为64MB
auto-aof-rewrite-percentage:表示当前AOF文件空间
重新加载流程:
1、当AOF和RDB文件同时存在时,优先加载AOF文件
2、AOF关闭或者AOF文件不存在,加载RDB文件
3、加载AOF/RDB成功后,redis启动成功
fork操作:
fork创建的子进程不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存页表。
CPU:
redis是cpu密集型服务,不要做绑定单核CPU操作。由于子进程非常消耗CPU,会和父进程产生资源竞争。
内存:
子进程通过fork操作产生,占用内存大小等同于父进程,理论上需要两倍的内存来完成持久化的工作,linux有写时机制,父子进程会共享相同的物理内存页,子进程在fork操作过程中共享进程内存快照。
===第六章===复制===
建立主从复制:slaveof
127.0.0.1:6380> slaveof 127.0.0.1 6379
主节点复制状态:
info replication
从节点复制状态:
info replication
断开复制:slaveof no one
127.0.0.1:6380> slaveof no one 断开与6379的复制关系
切换主:
127.0.0.1:6380> slaveof 127.0.0.1:6381 将6380的从切换到6381的从节点(切换操作会清空之前所有的数据)
1、断开与旧主节点复制关系
2、与新节点建立复制关系
3、删除从节点当前所有数据
4、对新主节点进行复制操作
repl-disable-tcp-nodelay参数用于控制是否关闭TCP_NODELAY,默认关闭:
1、关闭时,主节点产生的命令数据无论大小直接发送给从节点,延迟最小,但是增加消耗带宽。适用于同机房,同机架。
2、打开时,主节点会合并较小的tcp从而节省带宽,发送时间依赖于Linux内核,默认40毫秒,适用于跨机房,带宽紧张。
主从节点复制流程:
1、从节点保存主节点信息
2、主从建立socket连接
3、发送ping命令
4、权限验证
5、同步数据集
6、命令持续复制
复制偏移量:
1、参与复制的主从节点都会维护自身的复制偏移量
2、主节点在处理完写命令后,会把命令的字节长度做累加记录,统计信息在info replication中的master_repl_offset指标中
3、从节点给主节点上报自身偏移量,因此主节点也会保存从节点的复制偏移量,在info replication中的slave_repl_offset指标中
4、从节点接收到主节点的命令后,也会增加自身的偏移量
5、通过对比主从节点的复制偏移量,可以判断出主从节点数据是否一致
复制积压缓冲区:
复制积压缓冲区是保存在主节点上的一个固定定长的队列,默认是1MB,当主节点上有从节点连接时被创建,主节点响应写命令,不但会把命令发送给从节点,还会写入复制积压缓冲区,用于部分复制和复制命令丢失的数据补救。
127.0.0.1:6379> info replication
repl_backlog_active:1 // 开启复制缓冲区
repl_backlog_size:1048576 // 缓冲区最大长度
repl_backlog_first_byte_offset:7479 // 起始偏移量,计算当前缓冲区可用范围
repl_backlog_histlen:1048576 // 已保存数据的有效长度
主节点运行的ID:
127.0.0.1:6379> info server (redis重启的话,ID 会改变)
如何在不改变运行ID的情况下重启呢?
例如:hash-max-ziplist-value等,这些配置需要Redis重新加载才能优化已存在的数据,这时可以使用debug reload命令重新加载RDB并保持运行ID不变,从而有效避免不必要的全量复制
# redis-cli -p 6379 info server | grep run_id
run_id:2b2ec5f49f752f35c2b2da4d05775b5b3aaa57ca
# redis-cli debug reload
OK
# redis-cli -p 6379 info server | grep run_id
run_id:2b2ec5f49f752f35c2b2da4d05775b5b3aaa57ca
提示:
debug reload命令会阻塞当前Redis节点主线程,阻塞期间会生成本地RDB快照并清空数据之后再加载RDB文件。因此对于大数据量的主节点和无法容忍阻塞的应用场景,谨慎使用。
===第七章===redis的噩梦:阻塞
堵塞原因
内在:不合理的使用API或者数据架构,CPU饱和,持久化 阻塞等
外在:CPU竞争,内存交换,网络问题等
慢查询:
1、修改为低算法度的命令,如hgetall改为hmget,禁用keys,sort等命令
2、调整大对象,缩减大对象数据或者拆分为多个小对象,防止一次命令过多的操作数据
如何发现大对象
redis-cli -h 127.0.0.1 -p 6379 bigkeys 内部原理采用分段scan扫描
CPU饱和
CPU饱和是指将单核CPU使用率跑到100%
持久化阻塞
持久化引起主线程阻塞的操作主要有:fork阻塞,AOF刷盘阻塞,HugePage写操作阻塞
1、fork阻塞
2、AOF刷盘阻塞,主要硬盘压力引起
外在原因
CPU竞争,绑定CPU是一种优化方式,如果有RDB/AOF等操作,父子进程会产生CPU竞争,子进程重写时单核CPU通常在90%以上,因此不建议绑定CPU。
可以通过netstat-s命令获取因backlog队列溢出造成的连接拒绝统计,如下:
netstat -s | grep overflowed
===第八章===理解内存===
理解内存
参数说明:
1、used_memory:redis分配器分配的内存总量,也就是内部存储的所有数据内存占用量
2、used_memory_human:以可读的格式返回
3、used_memory_rss:从操作系统的角度显示redis进程占用的物理内存总量
4、used_memory_peak:内存使用的最大值,也就是used_memory的峰值
5、used_memory_peak_human:以可读的格式返回
6、used_memory_lua:lua 引擎消耗的内存大小
7、mem_fragmenttation_ratio:碎片率
8、mem_allocator:redis使用的分配器,默认为jemalloc
mem_fragmenttation_ratio > 1 时,说明 used_memory_rss - used_memory 多出的部分内存没有用于数据存储,而是被碎片消耗,两者差距很大,说明碎片严重。
mem_fragmenttation_ratio < 1 时,说明操作系统把redis内存交换(Swap)到硬盘导致,由于硬盘速度远慢于内存,redis性能变得很差,导致僵死。
内存消耗划分:
redis进程内存消耗主要包括:自身内存+对象内存+缓冲内存+内存碎片 (自身内存消耗非常低,可忽略)
1、对象内存:
对象内存是redis占用最大内存的一块,存储着所有的数据。每次创建键值对时,需要创建两个类型对象。key对象和value对象。
2、缓冲内存:
缓冲内存主要包括:客户端缓冲、复制积压缓冲区、AOF缓冲区
客户端缓冲:所有接入到redis服务器TCP连接的输入输出缓冲,输入缓冲无法控制,最大1G,超过将断开。输出缓冲通过client-output-buffer-limit控制。
复制积压缓冲区:
内存碎片:默认采用jemalloc,还有glibc、tcmalloc,(通过安全重启解决碎片率问题)
3、子进程内存消耗:
redis执行fork操作产生的子进程内存占用量对外表现为与父进程相同,通常需要一倍的物理内存来完成操作。linux具有写时复制技术(copy-on-write),父子进程会共享相同的物理内存页,父进程处理写请求时,会需要修改的页复出一份副本完成写操作,而子进程依然读取fork时整个父进程的快照。
内存管理:
redis内存可通过 config set maxmemory 动态修改,config rewrite
===第九章===哨兵===
取消当前sentinel节点对主节点进行监控:
sentinel remove mymaster
查看:
info sentinel
重新监控对主节点的监控:
sentinel monitor mymaster 127.0.0.1 6379 2
动态修改哨兵节点:
sentinel set
获取当然sentinel节点ip信息:
sentinel get-master-addr-by-name mymaster
添加sentinel节点:
添加sentinel monitor主节点的配置,利用sentinel-server启动即可,自动被sentinel节点发现
sentinel 切换日志参数说明:
+switch-master:切换主节点,更新主节点信息
+convert-to-slave:切换从节点
+sdown:主观下线
+reboot:重新启动了某个节点
+reset-master <instance details> 主节点被重置
+slave <instance details> 一个新的从节点被发现并关联
+failover-state-reconf-slaves <instance details> 故障转移进入 reconf-slaves状态
+slave-reconf-sent <instance details> 领导者sentinel节点命令其他从节点复制新的节点
+slave-reconf-inprog <instance details> 从节点正在重新配置主节点的slave,但是同步过程尚未完成
+slave-reconf-done <instance details> 其余从节点完成了主节点同步
+sentinel <instance details> 一个新的sentinel节点被发现并关联
+sdown <instance details> 添加对某个节点被主观下线
-sdown <instance details> 撤销对某个节点被主观下线
+odown <instance details> 添加对某一个节点客观下线
-odown <instance details> 撤销对某一个节点客观下线
+new-epoch <instance details> 当前纪元被更新
+failover-state-select-slave <instance details> 故障转移进入select-slave状态(寻找合适的从节点)
no-good-slave <instance details> 没有找到合适的从节点
selected-slave <instance details> 找到了合适的从节点
failover-state-select-slaveof-noone <instance details> 故障转移进入failver-state-select-slaveof-noone状态(对找到的从节点执行slaveof no one)
failover-end-for-timeout <instance details> 故障转移超时而终止
failover-end <instance details> 故障转移顺利完成
===第十章===集群===
常见的分区有:哈希分区 和 顺序分区
1、哈希分区:离散度好、数据分布业务无关、无法顺序访问;代表产品:redis cluster、cassandra、dynamo
2、顺序分区:离散度易倾斜、数据分布业务有关、可顺序访问;代表产品:bigtable、HBase、Hyperable
"redis cluster采用哈希分区规则,因此我们重点研究哈希分区规则"
常用的哈希分区规则有:
1、节点取余分区:根据公式hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上,扩容或收缩会导致数据重新迁移
2、一致性哈希分区:
3、虚拟槽分区:虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。比如Redis Cluster槽范围是0~16383。槽是集群内数据管理和迁移的基本单位。
redis cluster 限制:
1、key 批量操作,如mget,mset只支持相同槽内的数据,若存在于多个节点上不被支持
2、key事务支持,只支持在事务在同一个节点上,若key分布在不同节点不支持事务
3、不能将大key,如hash,list分布到不同节点上
4、不支持多数据库空间,单机支持16个,集群只有一个,即db0
5、复制结构只支持一层,不能嵌套循环
节点ID 不同于 运行ID,节点ID在加载时,会重新重用。
集群模式下的节点通过Gossip协议彼此通信
1)节点6379本地创建6380节点信息对象,并发送meet消息
2)节点6380接受到meet消息后,保存6379节点信息并回复pong消息
3)之后节点6379和6380彼此定期通过ping/pong消息进行正常的节点通信
安装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
./configure -prefix=/usr/local/ruby
make && make install
cd /usr/local/ruby
sudo cp bin/ruby /usr/local/bin
sudo cp bin/gem /usr/local/bin
安装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:
sudo cp /{redis_home}/src/redis-trib.rb /usr/local/bin
Redis集群采用P2P的Gossip(流言)协议,Gossip协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,这种方式类似流言传播.
redis cluster通信过程说明:
1、集群中的每个节点都会开通一个单独的tcp通道,端口在基础端口上加10000
2、每个节点在固定周期内通过特定规则选择几个节点发送ping消息
3、接收到ping消息的节点用pong消息作为响应
Gossip消息:
Gossip协议的主要职责就是信息交换。
常用的Gossip消息可分为:ping消息、pong消息、meet消息、fail消息等。
集群扩容:线上操作通过redis-trib.rb add-node来操作,若新节点存在数据等会拒绝,如果直接通过cluster meet 会造成线上数据错乱,线上慎用cluster meet。
迁移槽和数据:槽在迁移过程中集群可以正常提供读写服务。
redis-trib.rb reshard host:port --from <arg> --to <arg> --slots <arg> --yes --timeout <arg> --pipeline <arg>
参数说明:
host:port:必传参数,集群内任意节点地址,用来获取整个集群信息
--from:制定源节点的id,多个节点用逗号','分隔,如果是all源节点变为集群内所有主节点,
--to:需要迁移的目标节点的id,目标节点只能填写一个
--slots:需要迁移槽的总数量
--yes:当打印出reshard执行计划时,是否需要用户输入yes确认后再执行reshard
--timeout:控制每次migrate操作的超时时间,默认为60000毫秒
--pipeline:控制每次批量迁移键的数量,默认为10
迁移之后建议使用redis-trib.rb rebalance命令检查节点之间槽的均衡性,在2%误差内。
数据倾斜以下原因:
1、节点和槽分布严重不均
2、不同槽对应的键数量差异过大
3、集合对象包含大量元素
4、内存相关配置不一致
redis集群下的从节点不接受任何请求,即便有读写请求,也会重定向到主节点。
cluster failover force ,强制发起选举,不需要投票,直接变为主,复制延迟部分数据会丢失。
cluster failover takeover ,用于集群内一半以上主节点故障的场景。
议优先级:cluster failver > cluster failover force > cluster failover takeover
数据迁移:
常需要把单机Redis数据迁移到集群环境,redis-trib.rb工具提供了导入功能,用于数据从单机向集群环境迁移的场景
redis-trib.rb import host:port --from <arg> --copy --replace (redis-trib.rb import命令内部采用批量scan和migrate的方式迁移数据)
缺点:1、迁移只能从单机节点向集群环境导入数据
2、不支持在线迁移数据,迁移数据时应用方必须停写,无法平滑迁移数据
3、迁移过程中途如果出现超时等错误,不支持断点续传只能重新全量导入
4、使用单线程进行数据迁移,大数据量迁移速度过慢
迁移开源工具:redis-migrate-tool
1、支持单机、Twemproxy、Redis Cluster、RDB/AOF等多种类型的数据迁移
2、工具模拟成从节点基于复制流迁移数据,从而支持在线迁移数据,业务方不需要停写
3、采用多线程加速数据迁移过程且提供数据校验和查看迁移状态等功能
===第十二章===开发运维的陷阱==
vm.overcommit_memory
0:表示内核将检查是否有足够的可用内存,如果有足够的内存,内存申请通过,否则内存申请失败,并把错误返回给应用程序
1:表示内核允许超量使用内存直到用完为止
2:表示内核绝不过量的使用内存
获取:
cat /proc/sys/vm/overcommit_memory
设置:
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1
禁用THP
echo never > /sys/kernel/mm/transparent_hugepage/enabled
给服务设置权值,降低被OOM killer的概率:
echo {value} > /proc/${process_id}/oom_adj
for redis_pid in $(pgrep -f "redis-server")
do
echo -17 > /proc/${redis_pid}/oom_adj
done
系统tcp-backuklog查看和修改:
cat /proc/sys/net/core/somaxconn
echo 511 > /proc/sys/net/core/somaxconn
flushall和flushdb后恢复办法:
1、通过AOF文件恢复,数据被清掉后,要防止AOF重写
2、调大AOF重写参数auto-aof-rewrite-percentage和auto-aof-rewrite-min-size,让Redis不能产生AOF自动重写
3、拒绝手动bgrewriteaof
4、用AOF文件恢复,需要将flushall操作去掉,并用redis-check-aof检查
使用AOF快速恢复演练:
1、防止AOF重写,快速修改主从的auto-aof-rewrite-percentage和auto-aof-rewrite-min-size变为一个很大的值
config set auto-aof-rewrite-percentage 1000
config set auto-aof-rewrite-min-size 100000000000
2、去掉AOF文件中的flushall
3、重启redis主节点恢复数据
bigkey:
字符串类型:体现在单个value值很大,一般认为超过10KB就是bigkey
非字符串类型:哈希、列表、集合、有序集合,体现在元素个数过多
bigkey的危害:
1、内存空间不均匀
2、超时阻塞
3、网络拥塞
主动检测:scan+debug object 查找bigkey
Redis将在4.0版本支持lazy delete free的模式,那时删除bigkey不会阻塞Redis。
寻找热点key:Facebook开源的redis-faina
对于热点key优化:
1、拆分热点key,分散在不同的集群节点上
2、迁移热点key
3、本地缓存加通知机制