Redis使用详解
一、NoSQL简介
NoSQL分类
键值(Key-Value)存储数据库:这一类数据库主要会使用到一个hash表,如Redis、Oracle BDB
列存储数据库:通常是用来应对分布式存储的海量数据,键仍然存在但是他们指向了多个列,如HBase、Riak
文档型数据库:该类型的数据模型是版本化的文档,比如JSON,允许之间进行嵌套,如MongoDB
非关系型数据库特点
数据模型比较简单
对于数据库性能要求较高
不需要高度的数据一致性
二、Redis简介
以key-value形式存储,不一定遵循传统数据库的一些基本要求(非关系的、分布式的、开源的、水平可扩展的)
优点 :对数据高并发读写
对海量数据的高效率存储和访问
对数据的可扩展性和高可用性
缺点 :无法做太复杂的关系模型
Redis单线程 :指处理我们的网络请求的时候只有一个线程来处理【文件刷盘等用的是多线程】
Redis单线程的好处 :
三、Redis的安装
第一步:准备工作【解压tar包,创建Redis相关目录】
1 2 3 4 tar -zxvf redis-5.0.2.tar.gz mkdir /opt/redis mkdir /opt/redis/conf mkdir /opt/redis/data
第二步:编译redis
1 2 3 4 5 #进入解压后的tar包执行 make #执行结束之后进入src目录 cd src make install PREFIX=/opt/redis
第三步:移动配置文件到conf目录
1 cp redis.conf /opt/redis/conf
额外配置:
1 2 3 vim /etc/rc.local #加入 /opt/redis/bin/redis-server /opt/redis/conf/redis.conf
1 2 3 vim /opt/redis/conf/redis.conf #修改 dir ./ ---> dir /opt/redis/data/
四、Redis基本通用命令
命令
说明
KEYS [pattern]
查找出匹配的key【生成环境禁止使用,数据量太大阻塞生成环境】
DBSIZE
统计key总数【使用的是redis的内部计数,并不是全部扫描,生产可用】
EXISTS key [key …]
检查key是否存在【存在返回1,不存在返回0】
DEL key [key…]
删除key【成功删除返回1,不存在此key返回0】
EXPIRE key seconds
设置过期时间
TTL key
查看剩余的过期时间【-2代表已不存在,-1代表永不过期】
PERSIST key
取消key的过期设置
TYPE key
查询key的类型
五、Redis数据类型及其使用
注意:redis操作下标都是闭区间的
字符串【String】
String的值类型可以为字符类型、数字类型、bit类型
String类型是包含很多中类型的特殊类型,并且是二进制安全的。比如序列化的对象进行存储,比如一张图片进行二进制存储,比如一个简单地字符串,数值等等。
使用场景:缓存、计数器、分布式锁等
常用命令格式
描述
{ GET key },{ MGET key [key …] }
得到String
{ SET key value },{ MSET key value [key value …] }
设置String
{ SETNX key value },{ MSETNX key value [key value …] }
如果不存在则设置
SET key value XX
如果存在则设置
SETEX key second value
设置过期时间
{ INCR key },{ INCRBY key increment }
自增操作
{ DECR key },{ DECRBY key decrement }
自减操作
GETSET key value
得到就值设置新值
APPEND key value
追加字符串
STRLEN key
得到字符串长度,内部存有计数
GETRANGE key start end
得到指定长度的value
SETRANGE key offset value
设置指定偏移量字符串内容
哈希【Hash】
使用场景:存储具有一定结构化的数据
常用命令格式
描述
HGET key field
得到key对应field的value
HSET key field value
设置key对应field的value
HDEL key field
删除key对应field的value
HEXISTS key field
判断key的field是否存在
HLEN key
获取指定key的filed数量【内部计数,生成环境可用】
HMGET key field [field …]
批量获得hash的field对应的value
HMSET key field value [field value …]
批量设置hash的field和value
HINCRBY key field increment
增加指定increment的对应key的field
HGETALL key
获取key对应所有field和value【生成环境慎用】
HVALS key
返回key对应的所有value【生成环境慎用】
HKEYS key
返回key对应的所有field【生成环境慎用】
HSETNX key field value
不存在此key对应的field则设置
列表【List】
列表为有序、可重复结构。可指定位置插入和删除、也可从左右插入和弹出(模拟栈结构)
常用命令格式
描述
**LPUSH
RPUSH** key value [value …]
LINSERT key BEFORE
AFTER value newValue
**LPOP
RPOP** key
LREM key count value
根据count值,从列表删除所有等于value的值【时间复杂度O(N)】
【count>0,从左到右删除count个】
【count<0,从右到坐删除count个】
【count=0,删除所有value相等的值】
LTRIM key start end
按照索引范围保留list,删除大链表有用【时间复杂度O(N)】
LRANGR key start end
获取列表指定索引范围内的值,数值为负则从右往左取值【时间复杂度O(N)】
LINDEX key index
获取列表指定索引的值,数值为负则从右取值【时间复杂度O(N)】
LLEN key
获取list长度【内部计数,生成环境可用】
LSET key index newValue
设置指定位置的值【时间复杂度O(N)】
**BLPOP
RLPOP** key timeout
使用技巧:
LPUSH + LPOP = Stack
LPUSH + RPOP = Queue
LPUSH + LTRIM = Capped Collection【固定容量集合】
LPUSH + BRPOP = Block Queue
集合【Set】
Set无序、无重复、有集合间操作。
常用命令格式
描述
SADD key element [member …]
向集合key添加元素
SREM key element
删除集合key中的element元素
SCARD key
查询集合元素的个数【内部计数,生成环境可用】
SISMEMBER key element
集合中是否存在element元素
SRANDMEMBER key
随机得到一个元素
SMEMBERS key
获取集合所有元素【慎用】
SPOP key
随机弹出一个元素
{ SDIFF key [key …] }、{ SDIFFSTORE destination key [key …] }
返回/存储一个集合的全部成员,该集合是所有给定集合之间的差集
{ SINTER key [key …] }、{ SINTERSTORE destination key [key …] }
返回/存储一个集合的全部成员,该集合是所有给定集合的交集
{ SUNION key [key …] }、{ SUNIONSTORE destination key [key …] }
返回/存储一个集合的全部成员,该集合是所有给定集合的并集
使用技巧
有序集合【ZSet】
ZSet有序、无重复、包含分值与元素,有集合间操作。
使用场景:排行榜
常用命令格式
描述
ZADD key score member [score member …]
向集合添加元素
ZREM key member [member …]
移除集合中的元素
ZSCORE key member
得到元素的分数
ZINCRBY key increment member
增长元素的分数
ZCARD key
获得集合中元素的个数
ZRANK key member
成员按分值递减(从小到大)排列的排名
ZRANGE key start stop [WITHSCORES]
按score值递增(从小到大)排序,WITHSCORES返回分数
ZRANGEBYSCORE key max min [WITHSCORES]
返回分数范围内数据,按score值递增(从小到大)排序,WITHSCORES返回分数
ZCOUNT key min max
统计得到分数在min和max之间的元素个数
{ ZREMRANGEBYRANK key start stop }、{ ZREMRANGEBYSCORE key min max}
按照排名/分数范围删除元素
ZREVRANK key member
成员按分值递减(从大到小)排列的排名
ZREVRANGE key start stop [WITHSCORES]
按score值递增(从大到小)排序,WITHSCORES返回分数
ZREVRANGEBYSCORE key max min [WITHSCORES]
返回分数范围内数据,按score值递增(从大到小)排序,WITHSCORES返回分数
六、Redis高级功能
慢查询日志
慢查询发生在第三阶段
客户端超时不一定是慢查询导致,但慢查询可导致客户端超时
慢查询队列简介
先进先出的队列
固定长度
长度不够时,丢弃最早记录
保存在内存中
慢查询设置
更改慢查询参数建议使用CONFIG SET parameter value
方式,而不是更改redis.conf文件重启redis
慢查询命令
常用命令格式
描述
slowlog get n
获取慢查询队列一条记录
slowlog len
获取慢查询队列长度
slowlog reset
清空慢查询队列
查询结果示例
Pipeline
一般情况下客户端发送一条命令到Redis,Redis处理结束返回结果,即 N条命令 = N次网络时间 + N次计算时间
PipeLine将命令打包发送给客户端,Redis处理完返回结果,PipLine是一个异步处理方式,并不等待Redis返回。
Pipeline(N条命令) = 1次网络时间 + N次计算时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 /* * Jest执行参考 */ long oldPipeline = Instant.now().toEpochMilli(); for (int i = 0; i < 100; i++) { Pipeline pipeline = jedis.pipelined(); for (int j = 0; j < 100; j++) { pipeline.sadd("S" + i * 100 + j, j + ""); } //pipeline.sync();异步不接受返回结果 pipeline.syncAndReturnAll(); //异步接受返回结果 } long nowPipeline = Instant.now().toEpochMilli(); System.out.println(nowPipeline - oldPipeline); /** * Spring Data Redis参考 */ // 批量插入 redisTemplate.executePipelined(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { String keyPrefix = "pipeline-"; for (int i = 0; i < 10000; i++) { String key = keyPrefix + i; connection.set(key.getBytes(), String.valueOf(i).getBytes()); } } }); // 批量获取 List<Object> data = redisTemplate.executePipelined(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { String keyPrefix = "pipeline-"; for (int i = 0; i < 10000; i++) { String key = keyPrefix + i; connection.get(key.getBytes()); } } }); data.stream().forEach(System.out::println);
发布订阅
常用命令格式
描述
PUBLISH channel message
将信息 message
发送到指定的频道 channel
SUBSCRIBE channel [channel …]
订阅给定的一个或多个频道的信息
PSUBSCRIBE pattern [pattern …]
订阅一个或多个符合给定pattern的频道
UNSUBSCRIBE [channel [channel …]]
取消订阅
Bitmap
常用命令格式
描述
SETBIT key offset value
对 key所储存的字符串值,设置或清除指定偏移量上的位(bit)
GETBIT key offset
对 key所储存的字符串值,获取指定偏移量上的位(bit)
BITCOUNT key [start] [end]
计算给定字符串中,被设置为 1的比特位的数量
BITOP operation destkey key [key …]
对一个或多个保存二进制位的字符串 key进行位元操作,并将结果保存到 destkey上
operation
可以是 AND
、 OR
、 NOT
、 XOR
这四种操作中的任意一种
BITPOS key bit [start] [end]
返回位图中第一个值为 bit
的二进制位的位置
HyperLogLog
实质:用String类型实现,不能取出具体值,有错误率
作用:极小的空间实现独立数量统计
常用命令格式
描述
PFADD key element [element …]
将任意数量的元素添加到指定的HyperLogLog
PFCOUNT key [key …]
计算HyperLogLog有多少值
PFMERGE destkey sourcekey [sourcekey …]
将多个HyperLogLog合并为一个HyperLogLog
Geo
Geo(地理信息定位):存储经纬度,计算两地距离,范围计算等
Geo是使用ZSet实现
常用命令格式
描述
GEOADD key longitude latitude member [longitude latitude member …]
longitude:经度,latitude:维度,member:标识
GEOPOS key member [member …]
返回经纬度
GEODIST key member1 member2 [unit]
返回两个给定位置之间的距离
GEORADIUS key longitude latitude radius m
km
ZREM key member
删除成员
七、Redis持久化
RDB【Redis Database】
RDB是Redis用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据)。恢复时是将快照文件直接读到内存里。
触发机制
save
手动触发,同步命令,会阻塞线程
bgsave
手动触发,fork出一个子进程,异步命令,不会阻塞线程【阻塞仅仅会发生在fork出子进程的阶段】
自动
1 2 3 save 900 1 #900秒改变1个就生成rdb文件 save 300 10 #300秒改变10个就生成rdb文件 save 60 10000 #60秒改变10000个就生成rdb文件
一般情况下建议关闭自动策略
全量复制
从节点执行全量复制操作的时候,主节点会自动触发bgsave命令生存rdb文件并发送给从节点
debug reload
在执行debug reload重新加载redis的时候,也会自动触发bgsave
shutdown
默认情况下执行shutdown命令,如果没有开启AOF持久化功能,就会自动执行bgsave
RDB配置参数
命令格式
描述
rdbcompression
压缩RDB文件,默认yes
rdbchecksum
RDB文件是否进行校验,默认yes
dbfilename dump.rdb
RDB文件名【可使用dump-端口号.rdb区分不同的redis实例】
dir ./
RDB文件存储的目录
stop-writes-on-bgsave-error
bgsave出现错误时是否停止写入,默认yes
AOF【Append-Only File】
AOF是一个追加写入的日志文件从而实现持久化的方式,生成的AOF文件是可识别的纯文本文件。Redis默认使用RDB持久,开启AOF持久化需要设置appendonly
为yes
AOF文件生成策略
AOF重写
AOF支持AOF文件重写(从内存中读取的数据,并非读取上次的AOF文件进行重写)。AOF重写可以减少硬盘占用、加速恢复速度。
AOF配置参数
命令格式
描述
appendonly
是否开启AOF,默认no
appendfilename
生成AOF文件明【可使用appendonly-端口号.aof区分不同的redis实例】
appendfsync
刷盘策略,默认everysecond
dir
保存文件的目录,默认./
no-appendfsync-on-rewrite
AOF重写过程中是否禁止append操作,默认no允许append
auto-aof-rewrite-min-size
进行AOF时文件最小尺寸,默认64mb
auto-aof-rewrite-percentage
下次进行AOF操作时的增量,默认100
aof-load-truncated
AOF文件结尾不完整,Redis重启忽略不完整记录,默认yes
命令
bgrewriteaof 手动进行AOF重写操作
aof_current_size 查看AOF当前尺寸
redis-cli info Persistence
可以查看统计信息aof_current_size
【当前AOF文件大小】和aof_base_size
【上次重写AOF文件大小】
RDB与AOF比较
命令
RDB
AOF
启动优先级
低
高
体积
小
大
恢复
快
慢
数据安全性
丢数据
根据策略决定
轻重
重
轻
混合持久化
Redis 4.0 开始支持 rdb 和 aof 的混合持久化(默认开启),这样做的好处是可以结合 rdb 和 aof 的优点, 快速加载同时避免丢失过多的数据。缺点 aof 里面的 rdb 部分就是压缩格式不再是 aof 格式,可读性差。
开启混合持久化时,aof rewrite 的时候就直接把 rdb 的内容写到 aof 文件开头。aof 文件内容会变成如下
1 2 3 4 5 6 7 8 9 10 11 12 13 +------------------------+ | | | | | RDB | | FORMAT | | | | | | | +------------------------+ | | | AOF | | FORMAT | +------------------------+
持久化相关优化
fork操作优化
控制redis实例最大可用内存:maxmemory
合理配置Linux内存分配策略:vm.overcommit_momory=1(默认0,当内存少时fork阻塞不进行)
降低fork频率:例如放宽AOF重写自动触发时机,不必要的全量复制
子进程
cpu
开销:RDB和AOF文件生成,属于CPU密集型
优化:不做CPU绑定,不和CPU密集型服务部署
内存
开销:fork内存开销,使用了linux的copy-on-write【父进程未发生改变的内存页,不进行copy-write】
优化:echo never > /sys/kernel/mm/transparent_hugepage/enabled【不分配大内存页】
硬盘
开销:AOF和RDB文件写入,可以结合iostat,iotop分析
优化:不和高硬盘负载服务部署一起,no-appendfsync-on-rewrite
设置为yes
八、Redis主从复制原理和优化
一个master可以有多个slave
一个slave只能有一个master
数据流向是单向的,master到slave
主从实现两种方式
主从状态查看info replication
全量复制和部分复制
redis4后使用psync2实现复制使redis重启也可使用部分同步,还为解决在主库故障时候从库切换为主库时候使用部分同步机制。redis从库默认开启复制积压缓冲区功能,以便从库故障切换变化master后,其他落后该从库可以从缓冲区中获取缺少的命令。该过程的实现通过两组replid、offset替换原来的master runid和offset变量实现:
第一组:master_replid和master_repl_offset
如果redis是主实例,则表示为自己的replid和复制偏移量; 如果redis是从实例,则表示为自己主实例的replid1和同步主实例的复制偏移量。
第二组:master_replid2和second_repl_offset
无论主从,都表示自己上次主实例repid1和复制偏移量;用于兄弟实例或级联复制,主库故障切换psync
判断是否使用部分复制条件:如果从库提供的master_replid与master的replid不同,且与master的replid2不同,或同步速度快于master; 就必须进行全量复制,否则执行部分复制。
以下常见的主从切换都可以使用部分复制:
一主一从发生切换,A->B 切换变成 B->A
一主多从发生切换,兄弟节点变成父子节点时
级别复制发生切换, A->B->C 切换变成 B->C->A
全量复制开销
bgsave时间
RDB文件网络时间
从节点清空数据时间
从节点加载RDB时间
可能的AOF重写时间
当从库与主库断开时间过长导致自己的偏移量不在master_repl_offset
允许的范围之内,会触发全量复制
主从相关参数配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ##############从库############## slaveof <masterip> <masterport> #设置该数据库为其他数据库的从数据库 masterauth <master-password> #主从复制中,设置连接master服务器的密码(前提master启用了认证) slave-serve-stale-data yes # 当从库同主库失去连接或者复制正在进行,从库有两种运行方式: # 1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续相应客户端的请求 # 2) 如果slave-serve-stale-data设置为no,除了INFO和SLAVOF命令之外的任何请求都会返回一个错误"SYNC with master in progress" slave-priority 100 #当主库发生宕机时候,哨兵会选择优先级最高的一个称为主库,从库优先级配置默认100,数值越小优先级越高 slave-read-only yes #从节点是否只读;默认yes只读,为了保持数据一致性,应保持默认 ##############主库############## repl-disable-tcp-nodelay no #在slave和master同步后(发送psync/sync),后续的同步是否设置成TCP_NODELAY假如设置成yes,则redis会合并小的TCP包从而节省带宽,但会增加同步延迟(40ms),造成master与slave数据不一致假如设置成no,则redis master会立即发送同步数据,没有延迟 #前者关注性能,后者关注一致性 repl-ping-slave-period 10 #从库会按照一个时间间隔向主库发送PING命令来判断主服务器是否在线,默认是10秒 repl-backlog-size 1mb #复制积压缓冲区大小设置 repl-backlog-ttl 3600 #master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。单位为秒。 min-slaves-to-write 3 min-slaves-max-lag 10 #设置某个时间断内,如果从库数量小于该某个值则不允许主机进行写操作,以上参数表示10秒内如果主库的从节点小于3个,则主库不接受写请求,min-slaves-to-write 0代表关闭此功能。
主从配置问题
maxmomory不一致导致丢失数据
数据结构参数优化只有优化了主机,从机未配置导致内存不一致,数据错误或丢失
九、Redis Sentinel
主观下线:Sentinel根据配置条件,发现redis节点达到故障标准,则此Sentinel认为此redis节点下线
客观下线:当Sentinel中认为此redis客观下线的总数达到配置阙值,则认为此节点客观下线
安装Sentinel
配置sentinel.conf文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #没有开启bind和密码的情况下,保护模式默认被开启。只接受来自环回IPv4和IPv6地址的连接。拒绝外部连接 bind 127.0.0.1 192.168.1.1 protected-mode no #端口 port 26379 #是否守护进程模式运行 daemonize no #pid以及日志文件位置 pidfile /var/run/redis-sentinel.pid logfile "" #工作目录 dir /tmp #监控的master #mymaster指此主从组的名称【Sentinel可以监控多个主从组】 #最后一个数字代码多少个sentinel主观认为此master宕机为客观宕机事实 sentinel monitor mymaster 127.0.0.1 6379 2 #当多少毫秒master不返回ping结果即认为主观宕机 sentinel down-after-milliseconds mymaster 30000 #master重新选举之后slave并发复制master数据的并发量 sentinel parallel-syncs mymaster 1 #故障转移超时时间,默认为3分钟 sentinel failover-timeout mymaster 180000 #当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会调用这个脚本 #如果脚本以“1”退出,则稍后重试执行(最多可执行10次),脚本的最长运行时间为60秒 #sentinel notification-script mymaster /var/redis/notify.sh #当master因failover而发生改变,这个脚本将被调用,通知相关的客户端关于master地址已经发生改变的信息 #sentinel client-reconfig-script mymaster /var/redis/reconfig.sh #不允许使用SENTINEL SET设置notification-script和client-reconfig-script sentinel deny-scripts-reconfig yes
Sentinel三个定时任务
每10秒每个sentinel对master和slave执行info
每2秒每个sentinel通过master节点的channel交换信息(pub/sub模式)
通过__sentinel__:hello
频道交互
交互各节点的“看法”及自身信息
每1秒每个sentinel对其他sentinel和redis节点执行ping
Master选举过程
第一步:Sentinel选举出leader
原因:只需要一个Sentinel完成故障转移
选举:通过sentinel is-master-down-by-addr
命令都希望自己成为领导者
每个做主观下线的Sentinel节点向其它Sentinel节点发送命令,要求它给自己投票
收到命令的Sentinel节点如果没有同意其它Sentinel节点发送的命令,则同意投票否则拒绝
如果该Sentinel节点发现自己的票数已经超过Sentinel半数,那么它将成为leader
如果此过程未选出leader则等待一段时间继续选举
第二步:故障转移选举Master
从slave节点中选出一个“合适”的节点作为新的master节点
选择slave-prority
最高的slave节点,如果存在则返回【一般不修改】
选择复制偏移量最大的slave节点,如果存在则返回
选择runId最小的slave节点
对上面的slave节点执行slaveof no one
命令让其成为master节点
向剩余的slave节点发送slaveof
命令,让它们成为master节点的slave节点,复制规则和paraller-sync
参数有关
更新对原来master节点配置为slave,并保存对其“关注”,当其恢复后命令它去复制新的master节点
手动下线master机器sentinel failover <masterName>
十、Redis Cluster
数据分布概论
分布方式
特点
典型产品
哈希分布
数据分散度高
Memcache
Redis Cluster
|
| 键值分布业务无关 |
| 无法顺序访问 |
| 支持批量操作 |
| 顺序分布 | 数据分散度易倾斜 |
BigTable
HBase
|
| 键值分布业务相关 |
| 可顺序访问 |
| 支持批量操作 |
哈希分布方式
节点取余分区
进行取模运算,将余数相等的放入同一节点,简单易操作,增加节点时数据偏移,导致数据的前移达到80%,翻倍扩容可以使数据迁移从80%降到50%
一致性hash分区
为系统中每个节点分配一个token,范围一般在0~2的32次方,这些token构成哈希环。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点,往往一个节点会对应多个token。加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这些数据,常用于缓存场景
虚拟哈希分区
虚拟分槽使用哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。槽数范围远远大于节点数(redisCluster槽的范围是0~16383),每一个节点负责维护一部分槽以及所映射的键值数据
基本架构
所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
节点的fail是通过集群中超过半数的master节点检测失效时才生效
客户端与redis节点直连,不需要中间proxy层.客户端连接集群中任何一个可用节点即可
redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key
安装Cluster
修改redis配置文件
1 2 cluster-enable yes cluster-config-file nodes-${port}.conf
原生安装
启动所有节点
执行redis-cli -p ${port} cluster meet ${ip} ${port}
使节点相遇
执行cluster addslots slot [slot...]
分配槽
1 2 3 4 5 6 7 8 start=$1 end=$2 port=$3 for slot in `seq ${start} ${end}` do echo "slot:${slot}" /opt/redis/bin/redis-cli -p ${port} cluster addslots ${slot} done
执行redis-cli -p ${port} cluster replicate ${nodeId}
执行主从分配
集群命令安装
cluster配置参数
1 2 3 4 cluster-enable yes cluster-config-file nodes-${port}.conf cluster-node-timeout 15000 cluster-require-full-coverage yes #必须集群所有节点能提供服务才提供服务
集群伸缩
扩展集群
扩展步骤原理
对目标节点发送cluster setslot {slot} importing {targetNodeId}命令,让目标节点准备导入槽的数据
对源节点发送cluster setslot {slot} migrating {sourceNodeId}命令,让源节点准备槽数据的导出
源节点上循环执行cluster getkeyinslot {slot} count 命令,每次获取属于这个槽中键的个数
在源节点上执行migrate {sourceIp} {sourcePort} key 0 {timeout}命令,迁移指定的key
重复执行步骤3~4直到槽下所有数据完成迁移
向集群内所有主节点发送cluster setslot {slot} node {targetNodeId}命令,通知槽分配给目标节点
扩展执行步骤
加入集群
执行redis-cli -p ${port} cluster meet ${ip} ${port}
将节点加入集群
设置主从关系
redis-cli -p ${port} cluster replicate ${nodeId}
设置主从关系
任意节点执行迁移槽命令,后续过程根据提示进行
redis-cli --cluster reshard {ip}:{port}
查看节点分配情况
redis-cli -p {prot} cluster nodes | grep master
集群缩容
迁移槽命令和扩展集群的迁移命令相同,迁移完成之后使用redis-cli cluster forget {downNodeId}
下线节点
客户端路由
使用cluster keyslot ${key}可查看key对应的hash值
moved重定向
ask重定向
smart客户端
实现原理:追求性能
从集群中选一个可运行节点,使用cluster slots
初始化槽和节点映射
将cluster slots的结果映射到本地缓存,为每个节点创建JedisPool
准备执行命令(使用CRC16计算key对应的槽,找到映射节点执行)
1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main(String[] args) { Set<HostAndPort> nodes = new HashSet<HostAndPort>(); nodes.add(new HostAndPort("192.168.1.158", 7000)); nodes.add(new HostAndPort("192.168.1.158", 7001)); nodes.add(new HostAndPort("192.168.1.158", 7002)); nodes.add(new HostAndPort("192.168.1.158", 7003)); nodes.add(new HostAndPort("192.168.1.158", 7004)); nodes.add(new HostAndPort("192.168.1.158", 7005)); JedisCluster jedisCluster = new JedisCluster(nodes); jedisCluster.set("hello", "cluster"); System.out.println(jedisCluster.get("hello")); jedisCluster.close(); }
批量操作
Redis主要提供了以下几种批量操作方式:
批量get/set(multi get/set)
管道(pipelining)
事务(transaction)
基于事务的管道(transaction in pipelining)
批量操作必须key在同一个槽,导致以上用法异常苛刻
方案一:传统的串行IO操作,也就说n个key,分n次串行操作来获取key,复杂度是o(n)
方案二:将mget操作(n个key),利用已知的hash函数算出key对应的节点,这样就可以得到一个这样的关系:Map<node, somekeys>,也就是每个节点对应的一些keys,这样将之前的o(n)的效率降低到o(node.size())
方案三:在方案二的基础上将串行取数据改为并行取数据,进一步提高效率
方案四:通过redis自带的hashtag功能,强制一批key分配到某台机器上【不建议,大量数据会造成数据倾斜】
1 2 3 //使用{user}作为key,使key统一 jedisCluster.mset("{user}1001","zhangsan","{user}1002","lisi","{user}1003","wangwu"); List<String> users = jedisCluster.mget("{user}1001","{user}1002","{user}1003");
故障转移
故障发现
通过ping/pong信息实现故障发现,当半数以上持有槽的主节点都标记某节点主观下线则为客观下线【向集群广播下线节点的fail消息】
客观下线发送通知故障节点的从节点触发故障转义流程
故障恢复
资格审查
每个从节点检查与故障主节点的断线时间
超过cluster-node-timeout * cluster-slave-validity-factor
取消资格
cluster-slave-validity-factor
默认是10
准备选举时间
最接近主节点的偏移量的从节点率先发起选举,稍后其他从节点发起选举
选举投票
替换主节点
当前从节点取消复制变为主节点(slave no one)
执行clusterDelSlot撤销故障主节点负责的槽,并执行clusterAddSlot把这些槽分配给自己
向集群广播自己的pong消息,表明已经替换了故障从节点
集群运维问题
集群完整性
cluster-require-full-coverage yes
默认为yes
集群中16384个槽全部可用:保证集群完整性
节点故障或者正在故障转移,集群不可使用
大多数情况下业务无法容忍,建议cluster-require-full-coverage
设置为no
PubSub广播
任意节点发布消息所有节点都会订阅到消息,消耗带宽较多。JedisCluster只会订阅任意一个节点
数据倾斜
造成的原因:
节点槽分配不均
不同槽对应的键值数量差异较大【可能存在hashTag】
包含bigkey
内存相关配置不一致
使用redis-cli --cluster info {ip}:{port}
可以查看key、slot分布情况
使用redis-cli --cluster rebalance {ip}:{port}
进行数据平衡【慎用】
从机读写问题
在集群模式下从节点不接受任何读写请求
命令会重定向到负责槽的主节点
readonly命令可以读取【连接级别】
数据迁移
官方工具不只能从单机向集群迁移,不支持断点续传,不支持在线迁移,单线程影响速度,不建议使用官方工具
十一、缓存设计与优化
缓存使用的成本
数据不一致:缓存层和数据层有时间窗口不一致,和更新策略有关
代码维护成本:多了一层缓存逻辑
运维成本:例如Redis Cluster
缓存更新策略
redis里面存储的过期时间,都是绝对时间点,所以如果两台机器时钟不同步,那么超过的数据会全部删除。
slaves不会独立删除数据,而是等待master给它发送删除指令的时候,再删除数据
如果slave当选为master的时候,会先淘汰keys,然后再成为master
策略
一致性
维护成本
算法剔除
最差
低
被动更新
较差
低
主动更新
强
高
低一致性:最大内存和淘汰策略
高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底
缓存穿透&缓存雪崩&无底洞
缓存穿透
特点:
当缓存和数据库中都没有数据的时候,当查询Redis没有数据的时候,会继续查询数据库,数据库也没有数据,当大量查询请求发生或遭到恶意攻击时,这些访问全部透过Redis,并且数据库也没有数据,这种现象称为“缓存穿透”。
解决方案:
缓存空对象,storage返回一个空对象,将键存储在缓存层,下次请求此键之间返回空对象
需要更多的键,建议设置过期时间
缓存层和存储层数据“短期”不一致
布隆过滤器
缓存击穿
特点:
当Redis的热点数据key失效时,大量并发查询直接打到数据库,此时数据库负载压力骤增,这种现象称为“缓存击穿”
解决方案:
1.设置key值永不过期
2.使用互斥锁,查到后就回填缓存
缓存雪崩
特点:
缓存雪崩是指在短时间内,有大量缓存同时过期,导致大量的请求直接查询数据库,从而对数据库造成了巨大的压力,严重情况下可能会导致数据库宕机的情况叫做缓存雪崩。
解决方案:
随机设置key过期时间
随机延时,让一部分查询先将数据缓存起来
设置key值永不过期
无底洞问题
现象:增加节点机器性能没提升反而下降
解决方案参考批量操作
热点key的重建优化
现象:热点key缓存重建过程过长导致浪费了不必要的资源
解决方案:
互斥锁【使用redis构建锁机制】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 String get(String key) { String value = redis.get(key); if(value == null) { String mutexKey = "mutex:key:"+key; if(redis.SetParams.setParams().ex(180).nx()) { value = db.get(key); redis.set(key,value); redis.delete(mutexKey); }else { Thread.sleep(1000); get(key); } } return value; }
永不过期
缓存层面:没有设置过期时间
功能层面:为每个value添加逻辑过期时间,但发现超过逻辑过期时间后,会使用单独的线程取构建缓存
方案
优点
缺点
互斥锁
思路简单
代码复杂度增加
保证一致性
存在死锁的风险
永不过期
基本杜绝热点key重建问题
不保证一致性
逻辑过期时间增加维护成本和内存成本
十二、Redis布隆过滤器
布隆过滤器的原理
首先需要k个hash函数,每个函数可以把key散列成为1个整数
初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中
优点:不需要存储key,节省空间
缺点:算法判断key在集合中时,有一定的概率key其实不在集合中,且无法删除
google-guava库实现了java版的布隆过滤器
Redis布隆过滤器
使用插件的方式部署
redis4.0之后支持使用插件的方式使用Bloom filters和cuckoo filters,redis4.0之前需手动使用代码的方式编写布隆过滤器,安装步骤参考下列链接
GitHub地址 使用文档
建议在conf配置文件中配置,不使用redis-server --loadmodule /path/to/rebloom.so
启动
1 loadmodule /path/to/rebloom.so
布隆过滤器命令
命令
说明
BF.RESERVE {key} {error_rate} {size}
创建布隆过滤器,error_rate为错误率,size为预期数据大小
BF.ADD {key} {item}
添加item到指定布隆过滤器
BF.MADD {key} {item} [item …]
批量添加item到指定布隆过滤器
BF.EXISTS {key} {item}
判断item是否存在与指定布隆过滤器
BF.MEXISTS {key} {item} [item …]
批量判断item是否存在与指定布隆过滤器
JavaAPI
导入maven依赖
1 2 3 4 5 <dependency> <groupId>com.redislabs</groupId> <artifactId>jrebloom</artifactId> <version>1.1.0</version> </dependency>
Api使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static void main(String[] args) { JedisPool jedisPool = new JedisPool("192.168.1.158", 6379); Client client = new Client(jedisPool); client.createFilter("main6", 10000, 0.000001); //批量插入数据 for (int i = 0; i <100; i++) { String values[] = new String[100]; for (int j = 0; j < 100; j++) { String value = "item" + (i * 100 +j); values[j] = value; } client.addMulti("main6", values); } //判断是否存在 System.out.println(client.exists("main6", "item1")); System.out.println(client.exists("main6", "a")); jedisPool.close(); }
十三、Redis开发规范
BigKey处理
发现BigKey
debug object {key}
查看指定key的详细信息
redis-cli --bigkeys
扫描出BigKey【全表扫描,阻塞,建议从节点本地执行】
BigKey删除
场景:当key非常大时,delete命令执行十分缓慢,会发生阻塞【过期bigkey也是进行删除操作也会阻塞】
redis4.0之后:可以使用unlink
命令进行后台删除,不阻塞前台
生命周期管理
命令优化
有遍历需求可以使用hscan
、sscan
、zscan
代替【这些扫描命令在field较少时COUNT参数不会生效 】
必要情况下使用monitor
命令监控,注意时间不要过长
Java客户端优化
参数名
含义
默认值
建议
testWhileIdle
是否开启空闲资源检测
false
true
timeBetweenEvictionRunsMillis
空闲资源检测周期
-1
自选,也可使用JedisPoolConfig中的默认值
minEvictiableIdleTimeMillis
资源池中资源最小空闲时间
30分钟
自选,也可使用JedisPoolConfig中的默认值
numTestsPerEvictionRun
做空闲资源检测每次的采样数
3
自选,如果设置为-1则为全部做空闲检测
maxIdle需要设置为接近maxTotal
预估maxTotal方法的例子:
一次命令时间平均耗时1ms,一个连接QPS大约1000,业务期忘的QPS时50000,理论上maxTotal=50000/1000=50
十四、内存管理
内存消耗
内存统计
执行info memory
命令可以查看内存信息
主要属性名
属性说明
used_memory
实际存储数据的内存总量
used_memory_rss
redis进程占用的总物理内存
maxmemory
最大内存
maxmemory_policy
内存剔除策略
mem_fragmentation_ratio
used_memory_rss/used_memory比值,表示内存碎片率
内存消耗划分
客户端缓冲区设置规则
client-output-buffer-limit
:客户端类型,分为三种
(a)normal:普通客户端
(b)slave:用从节点用于复制,伪裝成客户端
©pubsub:发布订阅客户端 :如客户使用的输出冲区大于hardlimit客户端会被立即关闭
和:如果客户端使用的输出缓冲区超过了 并且持续了,客户会被立即关闭
普通客户端默认:client-output-buffer-limit normal 0 0 0
salve客户端默认:client-output-buffer-limit slave 256mb 64mb 60
pubsub客户端默认:client-output-buffer-limit slave 32mb 8mb 60
复制缓冲区:用于slave和master断开重连时不进行全量复制保存偏移数据使用
默认为repl-backlog-size 1mb
AOF缓冲区:AOF重写期间,AOF缓冲区,没有容量限制
内存回收策略
删除过期值
惰性删除:访问key->expired dict->del key【先在过期表中找,发现过期删除key,返回null】
定时删除:每秒运行10次,采样删除
超过maxmemory使用maxmemory-policy进行控制,参见缓存更新策略
十五、开发运维事项
Linux内核优化
vm.overcommit_memory
Redis建议vm.overcommit_memory = 1(影响fork操作)
立即生效:
永久生效:vm.overcommit_memory = 1写入到/etc/sysctl.conf文件中
值
含义
0
表示内核将检查是否有足够可用的内存。如果有足够可用的内存,内存申请通过,否则内存申请失败,并返回错误给进程
1
表示内核允许超量使用内存直到用完为止
2
表示内存绝不过量使用,即整个系统内存不能超过swap+50%的RAM值
swappiness
值
策略
0
Linux3.5及以上:宁愿OOM Killer也不用swap
Linux3.5及以下:宁愿swap也不用OOM Killer
1
Linux3.5及以上:宁愿swap也不用OOM Killer
60
默认值
100
操作系统会主动使用swap
建议:Linux3.5以上vm.swappiness = 1,否则vm.swappiness = 0
立即生效:echo {bestValue} > /proc/sys/vm/swappiness
永久生效:vm.swappiness = {bestValue} 写入到/etc/sysctl.conf
THP(Transparent huge page)
建议禁用,Centos7在/sys/kernel/mm/transparent_hugepage/enabled
下设置为never即可
THP为大内存页时fork子线程时Copy-On-Write可能造成延迟
ulimit
建议将Redis启动用户的文件句柄限制调成10032,限制文件etc/security/limits
TCP backlog
建议将/proc/sys/net/core/somaxconn
系统TCP backlog的限制设置为与Redis一样默认511
Redis安全问题
在配置文件中配置
1 2 #将命令更改为另一个字符串,原命令失效。如果字符串为空则表示禁用此命令 rename-command {cmd} {str}
附各种数据类型的内部编码:
附Redis云平台CacheCloud【一键部署、监控、运维、数据迁移工具等】 GitHub项目地址
留言與分享