-
结构图
-
-
redis通用命令
-
keys #时间复杂度o(n) 查询所有的key
-
Exists key #时间复杂度o(1)判断key是否存在
-
DBsize #时间复杂度o(1) 判断redis的大小
-
Expire #设置过期时间 stt 查询过期时间,presite 去掉过期时间
-
-
string 命令
- Get #获取key’对应的value
- Set set key value #设置key value的值, 不管key是否存在都设置
- setnx key #不存在才设置
- Set key value xx #存在才设置,相当于更新操作
- Del #删除key
-
整形操作的api
1.incr #key自增1,如果key不存在,自增后get(key)=1
2.decr #key自减1,如果key不存在,自减后get(key)=-1
3.incrby #key自增k,如果key不存在,自增后get(key)=k
4.decrby #key自减k,如果key不存在,自减后get(key)=-k
3.批量操作命令
1. mget key1 key2 key3 #批量获取key 原子操作
2.mset ket1 value1 key2 value2 key3 value3...... #批量设置key value
Getset key newvalue #给key设置一个新的value 返回旧的值
Append key value #将vlaue值追加到旧的value后面
Strlen key #返回字符串的长度(注意中文(中文占用字节不同,所以返回)) 时间复杂度为o(1) 在字符串内部维护一个长度,不需要计算
-
Getrange key start end #获取字符串指定下标的所有值
Setrange key index value #设置指定下标所对应新的值
-
hash 命令
- Hget #获取hash key 对应的field的value值
- Hset #设置hash key 对应的field的value值
- Hdel #删除hash key 对应的field的value值
- Hexists key field #判断hash key是否存在field
- Hlen key #获取 hash key field的数量
-
Hmget key field1 field2 field3 field4..... #获取hash key的 一批field的值 O(N)
Hmset key field1 value1 field2 value2 field3 values3 field4 values4.。。#设置一批field的value值O(N)
Hgetall key #返回所有的key 对应的field 和value值 O(N)
Hvals key #返回所有的key 对应的value值 O(N)
Hkeys key #返回key对应的field O(N)
Hsetnx key field value #不存在才设置
Hincrby key field #自增
hincrbyFloat key field floatcount #自增浮点型数据
-
列表list 命令
- rpush key value1 value2 value3 #从列表右插入值o(1-n) rpush list c b a c b a
- lpush key value1 value2 value3 #从列表左插入值o(1-n) lpush list c b a a b c
- Linsert key before | after value newValue #在list指定的值前|后插入value o(n)
- Lpop key #从左边弹出一个元素 o(1)
- Rpop key #从右边弹出一个元素 o(1)
- Lrem key count value #根据count的值删除所有value相等的值 o(n)
-
当count>0 从左到右删除最多count个value相等的项
当count<0 从右到左删除最多count个value相等的项
当count=0 删除所有value相等的项
-
- ltrim key start end #按照索引范围修剪列表
- Lrange key start end #(包含end) 获取列表指定范围的所有项
- Lindex key index #获取列表指定索引的项
- Llen key #获取列表长度(列表内部维护长度,不用遍历去算)
- Lset key index newvalue #设置列表指定索引的值
- Blpop key timeout #左边弹出阻塞版本 timeout为超时时间,timeout=0为永不阻塞
- Brpop key timeout #右边弹出阻塞版本 timeout为超时时间,timeout=0为永不阻塞
- Lpush + lpop #=stack栈(相当于栈) 先进后出
- Lpush+ rpop #=queue 队列(相当于队列)
- Lpush+ltrim #=固定大小的列表
- Lpush+brpop #=消息队列
-
集合set 命令
-
集合内的操作命令(时间复杂度o(1))
1.sadd key element #向集合内添加元素(如果元素存在则添加失败)
2.srem key element #删除集合中的元素
3.scard key #计算集合的大小
4.sismember key element #判断element 是否在元素中存在
5.srandmember key count #从集合中随机挑选count个元素(元素没消失)
6.smember #获取结婚中的所有元素(元素无序的)
7.spop #从集合中随机弹出一个元素(元素已经消失)
- 集合间的操作命令(时间复杂度o(1))
- sdiff key1 key2 #差集
- Sinter key1 key2 #交集
- Sunion key1 key2 #并集
- Sdiff|Sinter|Sunion +store key #将集合结果保存到key中,方便后面使用
-
-
有序集合zset 命令
-
集合内的操作命令(时间复杂度o(1))
1.zadd key score element #(可以是多对) 向集合中添加score和element score可以重复 o(log n)
2.zrem key element #删除一个元素(可以是多个)
3.zscore key element #获取元素分数
4.zincrby key score element #增加或减少元素的分数
5.zcard key #返回集合元素的总个数
6.zrank key element #获取元素的排名
7.zrange key start end (withscores) # 根据一定范围遍历 升序排列(根据分值)
9.zrangebyscore key mixscore maxscore withscores # 返回指定分数范围内的升序元素(根据分值)
10.zcount key minscore maxscore #返回有序集合内在指定分数范围内的个数
11.zremrangebyrank key start end #删除指定排名内的升序元素
12.zremrangescore key minscore maxscore # 删除指定分数内的升序元素
13.zrevrank #获取集合元素 降序排名
14.zrevrange #获取集合元素 降序
2.集合操作
- Zunionstore #取并集
- Zinterstore #取交集
-
-
Java通过jdes链接redis的2中方式
- jedis直连
-
public class RedisTest {
public static void main(String[] args) {
Jedis jedis = null;
try {
jedis = new Jedis("192.168.253.129", 6379);
System.out.println(jedis.ping());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
-
- 连接池链接
-
public class RedisTest {
public static void main(String[] args) {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(50);
poolConfig.setMaxIdle(50);
poolConfig.setMaxWaitMillis(1000);
JedisPool jedisPool = new JedisPool(poolConfig, "192.168.253.129", 6379);
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
for (int i = 0; i < 10; i++) {
Pipeline pipeline = jedis.pipelined();
for (int j = i * 1000; j < (i + 1) * 1000; j++) {
pipeline.hset("hashkey" + j, "field" + j, "value" + j);
}
pipeline.syncAndReturnAll();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
-
- jedis直连
-
慢查询
- 生命周期
- 发送命令----排队等待(单线程)----执行命令------返回结果
-
说明:1.慢查询发生在生命周期第三阶段(执行命令阶段)
2.客户端超时不一定是慢查询,慢查询是客户端超时的一 种可能
- 两个配置
- slowlog-max-len(队列长度 list)
-
慢查询是一个先进先出的队列(第三步)
是固定长度
是保存在内存中的(断电重启会丢失,重置了)
-
- slowlog-max-slower-than慢查询阀值(单位:微秒)
-
当查询时间在什么时间内,将他定义为慢查询
slowlog-max-slower-than=0 记录所有命令(通常没意义)
查询确切的执行时间时用该命令
slowlog-max-slower-than<0不记录任何命令
-
- slowlog-max-len(队列长度 list)
- 配置方法
- config get slowlog-max-len=128
- config get slowlog-max-slower-than=10000 10毫秒
-
在第一次配置时,修改配置文件,然后重启电脑
-
- 动态配置
-
config get slowlog-max-len=128
config get slowlog-max-slower-than=10000 10毫秒
-
- 慢查询命令
- slowlog get[n]: 获取慢查询队列 (有多少个队列)
- Slowlog len :获取慢查询队列长度 (有多少在排队)
- Slowlog reset: 清空慢查询队列
- 使用经验
-
config get slowlog-max-len=? 不要设置过小,默认128,通常设置为1000
config get slowlog-max-slower-than=10000 不要设置过大,默认10ms,设置为1ms
定期持久化慢查询到数据库
-
- 生命周期
-
pipeline流水线功能
-
解释:将批量操作打包,按顺序执行-返回
1次pipeline(n条命令)=1次请求时间+n次命令时间
注意:redis命令执行时间时微秒级别
Pipeline每次条数要控制(网络时间比较长)
Pipeline的操作是非原子性的,在service 是将多个拆分成小的命令执行的,返回的结果是顺序的。
1.Jedis-对pipeline的支持
JedisPool jedisPool = new JedisPool("192.168.1.9", 6379);
/* 操作Redis */
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
Pipeline p = jedis.pipelined();
/* 插入多条数据 */
for(Integer i = 0; i < 100000; i++) {
p.set(i.toString(), i.toString());
}
}
- 建议
- 注意每次使用pipeline 携带的数量(不要太多)
- pipeline 每次只能作用在一个redis的节点上
- 批量操作和pipeline的区别(原子和非原子)
-
-
发布订阅
- api命令
- publish channel(频道) message #发布-- 发布一个信息到频道,返回订阅者的个数
- subscride [channel ] #订阅 --一个或多个,收到订阅的消息
- unsubscride [channel ] # 取消订阅--一个或多个
- api命令
-
Bitmap--位图
-
Redis可以直接操作位
- Api
- setbit key offset(偏移量:下标:第几个数) value(只能是0和1) #给位图指定索引设置值
- Setbit key offset #获取位图指定索引的值
- Bitcount key [start end] #获取位图指定范围(start end 单位为字节,如果不指定就是获取全部)位值为1的个数
-
-
Hyperloglog 极小空间完成独立数量统计,本质还是字符串
- Api
-
Pfadd key element [element......] #向htperloglog添加元素,可以多个
pfcount key[key......] #计算htperloglog的独立总数
Pfmerge destkey sourcekey [sourcekey...] #合并多个htperloglog
-
- 使用经验
-
是否能容忍错误(错误率0.81%),是否需要单条数据(不能取出单条数据)
-
- Api
-
Geo,地理信息定位,存储经纬度,计算两地距离,范围计算等等
- Api
-
Geoadd key longitude(精度) latitude(纬度) member(可以是地名) [longitude latitude member] #增加地理位置
Geopos key member [member...] #获取地理位置信息
Geodist key member 1 member 2 #获取两个地理位置的距离(unit) m米,km 千米,mi 英里,ft 英尺
-
- 使用
-
Type geokey #=zset geo是使用zset实现的
Zrem key member #没有删除api,可以用zset的api删除
-
- Api
-
redis持久化的取舍和选择
- 持久化:redis所有的数据保存在内存中,对数据的更新将异步的保存到磁盘上。
-
redis持久化RDB
- RDB就是redis将内存的数据以快照的方式保存到硬盘上rdb(二进制文件),当断电重启时,读取rdb文件来恢复redis缓存。
- 同时我们的主从复制也可以用这种方式--是一种复制媒介
- 触发机制--三种方式:生成rdb文件的方式
- save命令:同步命令,需要排队吗,是阻塞的
-
文件策略:存在老的rdb文件,新的替换老文件
-
- Bgsave命令:异步命令,执行完返回ok,在后台单独启动线程执行。
- 自动:在某些条件达到的时候自动触发:可以自己设置
-
Save 900 1 达成条件执行 900秒更新1条数据执行rdb
Save 300 10达成条件执行 300秒更新10条数据执行rdb
Save 60 10000达成条件执行60秒更新10000条数据执行rdb
-
-
常用配置以及默认值,名称
-
Dbfinename dump.rdb #rdb文件名称,执行时生成的默认文件名称
Dir ./文件地址
-
Stop-writes-on-bgsave-error yes #这个参数设置为yes,保存rdb出现错误是否停止写入
-
Rdbcompression yes #是否采用压缩格式保存,默认也是压缩
-
Rdbchecksum yes #对文件检验
-
最佳配置:
通常用 dump-${prot}.rdb #dump加端口号来命名 ,文件的命名格式
Dir /自定义地址
Stop-writes-on-bgsave-error yes
Rdbcompression yes
Rdbchecksum yes
-
-
- save命令:同步命令,需要排队吗,是阻塞的
- 触发方式
-
全量复制--没设置也生成rdb文件 主从复制生成
-
debug reload 这种方式会生成rdb文件
-
shutdown 会生成rdb
-
-
redis持久化AOF
-
什么是aop:日志文件,执行一条命令就在aof中记录这条命令,格式是按照aof的格式保存,基本上是实时的。
- 三种策略:aof 配置的三种属性
- always #redis--写入缓存---(每条命令都写入)再写入磁盘(aof文件中) 确定io开销大
- Everyset #redis--写入缓存---(每秒写入)再写入磁盘(aof文件中)有可能丢失一秒的数据,默认1秒,可配置
- No #redis--写入缓存---(根据操作系统决定(我们不管))再写入磁盘(aof文件中) 不可控
- Aop重写:就是把过期的,重复的,可优化的命令重写
-
好处:减少硬盘占用量,加快恢复速度
-
实现的两种方式:
- Bgrewriteaof #开子线程重写,将内存的数据重写
- Aof重写配置:提供两个配置实现aof自动重写
-
配置名:
auto-aof-rewrite-min-size aof文件重写需要的尺寸(当文件多大时才需要重写文件)
Auto-aof-rewrite-percentage aof文件增长率(比如aof100兆,下一次重写100%,就是200兆时重写)
-
统计名:
aof-current-size aof当前尺寸(单位字节)
Aof-base-size aof上次启动和重写的尺寸(单位字节)
-
自动触发机制:
aof-current-size >auto-aof-rewrite-min-size
aof-current-size ---减去--Aof-base-size/Aof-base-size >Auto-aof-rewrite-percentage
-
- 配置
- appendonly yes #表示使用aof功能(默认no)
- Appendfilename appendonly-${prot}.aof #文件名称
- Appendfsync everyset #每秒写入一次aof
- Dir /文件地址
- No-Appendfsync-on-rewrite yes #重写时 是否要做,不做
- Aof-load-truncated #aof文件有错误,是否忽略错误
- Auto-aof-rewrite-percentage 100 #增长率
- auto-aof-rewrite-min-size 64mb #大小
- rdb最佳策略
1.“关” 关掉rdb(全量复制需要用到,关不掉)
2.集中管理, 大量数据的操作有用
-
aof最佳策略
-
“开”:主要是缓存和存储
-
aof重写集中管理.
-
选择everysed 策略
-
-
-
-
常规操作的问题
- fork 操作
-
同步操作
与内存量息息相关;内存越大,耗时越久
Info:latest_fork_usec 查询上次fork的时间
-
-
改善:
- 使用高效支持fork的物理机或者虚拟技术
- 控制redis实例最大可用内存:maxmmory
- 合理分配linux内存分配策略:vm.overcommit_memory=1 默认为0
- 降低fork的频率
- 子进程开销和优化
- cpu开销,Rdb和aof文件的生成,属于cpu密集型开销
- 优化:不做cpu绑定,不要把进程和一个cpu绑定,不和cpu密集型(多业务)部署在一起
- 内存:Fork内存开销 copy_on_werite
- 硬盘:Rdb和aof 写入硬盘的开销,
- 优化:不和高负载程序部署到一起
- aof阻塞定位
- 命令的方式:Info persistence
-
结果:aof_delayed_fsync:100 (100)是每次的结果累加,不能知道具体的是哪一次
- fork 操作
-
Redis复制的原理和优化
-
作用:将数据备份,主节点怠机,从节点继续提供服务,扩展读的性能(读写分离)
-
主从复制:一主一从,一主多从
-
一个master可以有多个slave
-
一个slave只有一个master
数据流向是单向的,从master流向slave
-
-
主从复制的配置:两种方式
-
Slave ip地址 端口号 如:slave192.160.0.1 6379 该命令:清空以前的所有数据,复制命令是异步的。
Slave no one 断开复制,注意:不会清空以前的数据
- 配置:在启动之前配置 slave ip port Slave-read-only yes 只做读的操作
- redis的runid:每次启动都会随机生成一个id,作为redis的标识,关闭就没了。
- 复制偏移量:
-
全量复制:用bgsave,生成可rdb文件,生成rdb文件形成期间的写命令的文件---清空从节点数据然后复制rdb文件,和写命令文件,同步成从节点的数据。
- 部分复制:从节点将runid和偏移量传给主节点,然后主节点在buffer判断偏移量是否在buffer期间,在的话,就将偏移量之后的数据同步到从节点。
- 故障处理: master 怠机,和slave怠机
-
-
开发运维遇见的问题:
- 读写分离:将流量分摊到从节点
- 读到过期数据(3.2已解决)
- 从节点故障
- 问题:
- 复制数据的延迟
- 读到过期数据(3.2版本已解决)
- 从节点故障
- 配置不一致:
-
maxmemory 不一致:丢失数据
-
数据结构优化参数()导致内存不一致
-
-
规避全量复制:
-
第一次全量复制(无可避免) 避免方法:memory 设置小一点
-
配置复制时间-在夜间,低峰期的时候做复制
-
-
节点runid不匹配:
-
主节点重启(runid发生变化)
-
故障转移如:哨兵或者集群
-
-
复制积压缓存区不足:
-
缓冲区太小,无法满足部分复制
-
增大缓冲区配置:rel_backlog_size
-
-
规避复制风暴:
-
主节点重启,从节点复制-----解决:更改拓扑结构
- 单机器复制风暴:解决:将master部署在多个机器
-
- 读写分离:将流量分摊到从节点
-
-
Redis sentienl
-
架构:
-
客户端 从sentienl 获取master和slave 而不关心真正的master和slave。
-
-
故障转移:
-
首先多个sentienl选出一个sentienl老大,让他来执行,然后将其中一个slave变成master,其他的slave来连接,如果原来的master好了,就将它变成slave。同时sentienl也会告知客户端该链接谁,每套sentienl可以监控多套 master-slave 可通过 master-name配置。
-
-
Sentienl的主要配置:
-
port ${port} 端口
-
dir /opt/soft/redis/data 工作目录
-
logfile ”${port}.log” 日志文件
-
sentienl monitor mymaster 192.168.0.1 7000 2 设置sentienl,2表示几个sentienl发现故障了才处理
-
sentienl down-after-milliseconds mymaster 30000 确认master故障时间
-
sentienl parallel-syncs-mymaster 1 复制是并行的,1表示一次只有一个
-
sentienl failover-timeout mymaster 180000 故障转移时间
-
-
客户端实现高可用:
-
流程:获取所有的Sentienl 节点集合,找到一个可以链接的节点,获取master的端口和地址(Sentienl 内部是通过发布订阅的模式实现的),然后重新链接。
-
Jedis实现:
jSentinelPool=new JedisSentinelPool(masterName,set(节点list),gPoolConfig);
-
-
三个定时任务:
-
每10秒Sentienl 对master和slave做info操作,发现slave节点,并确认主从信息。
- 每2秒Sentienl 通过master节点的channel交换信息(pub/sub发布订阅),通过_Sentienl_:hello交换频道信息
-
对节点的看法和自身的信息,来达到一个共识。
-
- 每隔1秒 Sentienl 对其他Sentienl 和redis执行ping的操作对后面的故障检测做基础。
-
主观下线: 每个Sentienl节点对redis节点失败的偏见:规定时间内Sentienl 单个节点的失败
客观下线:所有Sentienl 对redis节点的失败打成共识(超过设置的个数)
-
- 领导者选举:
-
通过Sentienl is-master-down-by-addr命令都希望成为领导者。
- 每个做主观下线的Sentienl向其他Sentienl 发送命令,希望自己成为领导者
-
收到命令得Sentienl ,如果没有同意其他Sentienl 的命令,则同意否则拒绝。
- 如果Sentienl 节点发现自己的票数已经超过节点集合的半数且超过quorum(设置((节点数/2)+1)) ,那么他就成为领导者
- 如果多个Sentienl 成为领导者,则重新选举。
-
- 故障转移:
-
从slave节点选择一个合适的slave节点成为新的master节点
- 对上面选出的slave节点执行slaveof no one 命令使其成为新的额master节点
- 向剩余的slave节点发送命令,让他们成为新的master节点的slave节点,并复制规则和parallel-syncs参数设置
- 将原master设置成slave,并保持对他的关注,当该节点恢复后,让他去复制新的master的数据
-
- 选择合适的slave节点:
-
选择slave-priority(优先级高)最高的节点,有就返回,没有就继续。
-
选择偏移量更大的节点(偏移量大,数据越完整),存在返回,不存在继续。
-
选择runid最小的slave节点(启动最早的节点)
-
-
-
Redis cluster
-
Redis cluster 集群:
-
Cluster特性:主从复制,高可用的,分片的
-
数据分区:顺序分区和哈希分区
-
-
哈希分区主要有:
- 节点取余法(hash(key)%nodes)
- 优点:数据分布均匀,支持批量操作
-
缺点:节点伸缩(增加减少节点),80%的数据发生偏移
优化:多倍扩容(双倍扩容迁移率50%左右)依次递减
- 一致性哈希算法:
- 有一个圆的闭环是token(0-2)32次方,每个key顺时针去找离他最近的节点存储。客服端分片
-
优点:数据伸缩(添加节点) 数据偏移少,只影响邻近节点,是取余算法的优化
缺点:不能将数据迁移到新节点
优化:翻倍扩容(负载均衡)
- 虚拟槽算法:
-
优点:
缺点:
优化:
节点:cluster-enabled yes 是否以集群来操作
Meet: meet操作让所有节点共享消息
-
- 节点取余法(hash(key)%nodes)
-
两种安装方式:原生命令得安装(理解架构),官方工具安装rudy
-
原生命令安装:
- 配置开启节点
-
Port ${port} 设置端口
Daemonize yes 是否以守护进程的方式启动
Dir /路径
Dbfinename dump-${[port}.rdb 设置rdb文件的名称
Logfine ${port}.log 设置日志文件的名称
cluster-enabled yes 是cluster节点(和普通的节点区分)
Cluster-config-file nodes-${port}.conf 添加cluster的配置文件
Redis-server nodes-${port}.conf 启动
-
-
节点的主要配置:
-
cluster-enabled yes 是cluster节点(和普通的节点区分)
Cluster-node-timeout 15000(毫秒)故障转移,节点超时
Cluster-config-file nodes-${port}.conf 集群节点的配置
Cluster-require-full-coverage yes(要配置为no) 集群内所有的节点都是正确的才提供服务。
-
- Meet:
-
Cluster meet ip port
Redis-cli -h ${ip} -p ${port} Cluster meet ${ip} ${port}
Redis-cli -h 33.1 -p 8001 Cluster meet 71.1 3009
Redis-cli -h 33.1 -p 8001 Cluster meet 91.1 4002
之后通过关系,所有的节点都互相通信
-
- 指派槽:
-
Cluster addslots slot[slot1,slot2....] 分配槽
Redis-cli -h 33.1 -p 8001 Cluster addslots {0..5461}
-
- 主从复制:命令
-
Cluster-replicate node-id node-id表示节点id
Redis-cli -h 33.1 -p 8001 Cluster-replicate ${node-id port(端口)} 33.1去复制后面的数据
-
- 配置开启节点
-
-
-
集群伸缩
- 集群伸缩:槽和数据在节点之间移动
- Moved重定向:槽已经迁移
-
发送命令(set hello)到任意节点---计算槽和节点---判断是否指向本身----指向本身则返回---不指向本身则返回槽和节点---然后----需要二次写逻辑--重定向槽返回。
-
- ask重定向:不确定槽位置
-
解决的问题,在做转移的槽之间发生,如访问源节点但是数据已经迁移到了目标节点,就会返回一个ask,然后ask重定向到目标节点,然后返回。
-
- 故障转移:
- 主关下线:节点1向节点2发送ping节点2返回pang,节点1记录返回时间,若没反回,则触发定时任务,当超过时间之后就认为节点2主关下线
- 客观下线:当半数持有槽的主节点都标记某节点主关下线。
-
流程:计算有效下线报告数量---是否大于持有槽的节点的一半,小于--退出,大于-更新为客观下线---向集群广播下线节点的消息
-
故障恢复:
- 检查资格:
-
每个从节点和故障主节点断开链接的资格
超过cluster-node-timeout(15秒)*cluster-slave-validily-factor(默认10) (150秒)取消资格
-
- 准备选举时间:
-
偏移量较大(复制数据完整的)的 给与少的时间,增大几率成为主节点
-
-
选举投票:
-
主节点投票--主节点个数/(2+1)
-
-
替换主节点
-
当前送节点取消复制变成主节点(slave no one)
执行clusterdelslot撤销故障主节点的槽,并执行clusteraddslot把这些槽分配给自己
向集群pong自己的消息,表明已经替换主节点
-
-
集群的完整性:
- Cluster-require-full-coverage yes
-
问题:
-
集群中16384个槽必须全部在线;保证集群完整性
-
节点故障或者故障正在转移:会返回down(不可用)
优化:大多数业务无法容忍,所以该设置设置为no
-
-
带宽消耗:Redis cluseter 节点之间会做一些通信,或心跳反应,会带来带宽消耗。
- redis官方建议设置1000个节点
- Ping/pong消息
-
不容忽视的带宽消耗
- 消息发送频率: 节点发现与其他节点最后通信时间超过cluster-node-timeout/2会直接发送ping消息--会消耗带宽
-
消息数据量:slots槽数组(2kb空间)和整个集群1/10的状态数据(10个节点数据约为1kb)
- 节点部署的机器规模:集群分布的机器越多且每台机器划分的节点数越均匀,则集群内可用带宽越高
-
优化:
- 避免大集群:避免多个业务使用一个集群,大业务可以多集群
- Cluster-node-timeout 带宽和故障转移的均衡
- 尽量部署多的机器上,保证高可用和带宽
-
Pub/sub广播:
-
问题:1.publish在集群每个节点广播,加重带宽
-
优化:单独 走一个 redis sentitenl
-
-
集群倾斜:
-
数据倾斜:内存不均
- 节点和槽分配不均(一般不常见)
-
redis-trib.rb info ip:port 查看节点,槽,键值分布
- redis-trib.rb rebalance ip:port 进行均衡(谨慎使用--会涉及数据迁移--客户端的问题)
-
- 不同槽对应键值数量差异大
-
可能存在hash_tag问题(主要原因)
-
Cluster countkeysinslot{slot} 获取槽对应键值个数
-
-
包含bigkey:
-
比如大的字符串,几百万元素的hash,set
-
查找bigkey:从节点:redis-cli --bigkeys
-
优化:数据结构(二次hash,拆分成小的字符串)
-
- 内存相关配置不一致(不常见)
-
优化:配置的一致性
-
- 节点和槽分配不均(一般不常见)
-
-
请求倾斜;热点问题
- 热点key:重要的key或者bigkeys
-
优化:避免bigkey
- 避免使用hash_tag
- 当一致性性不高时,可以使用本地缓存+mq
-
- 热点key:重要的key或者bigkeys
-
数据迁移:
-
官方工具:redis-trib.rb import
-
只能单机迁移到集群
- 不支持在线迁移:source需要停写
- 不支持断点续传
- 单线程迁移,影响数度
-
-
在线迁移:常用工具
-
redis-migrate-tool 工具
-
redis-port
-
-
-
集群和单机的对比:
-
集群限制:
-
key批量操作有限,mget,mset必须在一个slot里面
-
Key事务和lua支持有限,操作的key必须在一个节点
- Key是数据分区的最小粒度:不支持bigkey分区
- 不支持多个数据库,集群模式下只有一个数据库
-
复制只支持一层,不支持树形结构
-
-
-
思考:
- redis cluster:满足容量和性能的扩展性,----很多系统不需要
- 大多数时间客户端性能会降低
- 很多命令无法跨节点使用:mget,mset,flush
- Lua和事务无法跨节点使用
-
客服端服务端维护复杂
-
很多场景redis sentienl 已经满足
- redis cluster:满足容量和性能的扩展性,----很多系统不需要
-
集群总结:
-
redis cluster数据分区采用虚拟槽方式(16384个槽),每一个节点负责一部分槽和相关数据,实现数据和请求的负载均衡。
- 搭建集群分4个步骤:准备节点,节点握手,分配槽,复制
-
Redis-trib.rb工具用于快速搭建集群(还有扩容,节点检测等功能)
-
- 集群伸缩通过在节点之间移动槽和相关数据来实现
-
扩容时通过槽迁移计划将槽从源节点迁移到新节点
收缩时要将下线节点的槽前移到其他节点,在通过 cluster forget命令让集群内所有节点忘记下线的节点
-
- 使用smart客户端操作集群达到通信效率的最大化,客户端内部负责计算维护键--槽--节点的映射,用于快速定位到目标节点
- 集群自动故障转移过程分为故障发现和节点恢复,节点下线分为主关下线和客观下线,当半数主节点认为故障节点主关下线时标记他为客观下线状态,送节点负责对客观下线的主节点触发故障恢复流程,保障集群的可用性。
-
- 检查资格:
-
缓存使用和优化
-
缓存的收益:
-
加速读写
-
降低后端负载
-
- 缓存的成本:
- 数据不一致:缓存层和数据层时间窗口不一致,和更新策略有关
- 代码维护成本,多了一层缓存逻辑
-
运维成本
- 缓存的使用场景
- 降低后端负载:高消耗的sql:join结果集/分组结果统计
-
加速请求响应:利用redis。。。优化io响应时间
- 大量写合并为批量写:计数器先累加在写db
- 缓存更新策略:
- lru/lfu/fifo算法剔除,列如maxmemory-policy(内存的最大值) -----优势:设置简单
-
超时剔除:过期时间expire(问题--缓存时数据更新了)
- 主动更新:开发-控制生命周期
-
建议:低一致性,最大内存和淘汰策略
-
-
高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底。(建议使用)
- 缓存粒度问题:
- 通用性:全量属性最好。
- 占用空间:部分属性
- 代码维护:表面上全面属性好,实际生产中,不适用
- 缓存穿透问题:
-
缓存穿透就是:大量请求不命中,比如一个不存在的key去访问,先访问缓存,再访问数据库,大量的请求,就会一直重复这个过程。最后都会2次。
-
产生原因:
- 业务代码原因
- 恶意攻击,爬虫等
-
怎么发现:
- 响应时间
- 业务本身问题
-
相关指数,总调用数,缓存层命中数,存储层命中数
-
怎么解决:
- 缓存空对象,如果查出来为空就存在缓存中,设置过期时间什么的,问题:1.需要更多的键(占用空间)2.缓存层和存储层短期数据不一致。
- 布隆过滤器拦截:再访问缓存前,再过滤一层,实现复杂
- 无底洞问题优化:
-
就是加机器,性能没有提升,反而下降了
-
问题:
-
更多的机器!=更多的性能
- 批量需求mget,mset
- 数据增长和水平扩展矛盾
-
- 优化:
- 命令本身优化:如慢查询等
- 减少网络通信
-
降低接入层本:如:客户端长链接/连接池,nio等
-
-
热点key重建问题:
-
问题:
-
热点key+较长的重建时间:就是大量的请求会导致大量的去查询数据库,重建缓存
-
-
三个目标:
-
减少缓存重建的时间
-
数据尽可能一致
-
减少潜在风险
-
-
解决办法:
- 互斥锁:就是把查询数据库和重建的过程锁起来。
-
问题:代码复杂,可能出现死锁
有点:保证一致性,思路简单
-
-
永远不过期:
-
缓存层面:没有为key设置过期时间
功能层面:为每个value设置逻辑过期时间,发现超过过期时间,会使用单独的线程构建缓存
-
优化:基本杜绝热点key重建问题
问题:数据不一致,逻辑过期时间增加维护成本和内存成本。
-
- 互斥锁:就是把查询数据库和重建的过程锁起来。
-
-
-
-
Redis云平台cachecloud
-
1.redis规模化困扰:
1.发布构建繁琐,私搭乱建
2.节点和机器等运维成本
3.监控报警比较初级
Cachecloud简介:
- 一键开启redis(standalone,sentienl,cluster)
- 机器,应用,实例监控,报警
- 客户端:透明使用,性能上报
- 可视化运维:配置,扩容,failover,机器应用,实例上下线
- 已存在的redis直接接入和数据迁移
- https://github.com/sohutv/cachecloud 开源地址
-
redis过期策略
在使用redis做缓存的时候,我们常常会设置过期时间。那么redis是如何清理这些过期的数据呢?
定期删除: redis每100ms就会随机
抽查删除过期的数据。但是这种方法有时候会留下大量过期但没有被抽查到的过期数据,白白浪费内存。
- 惰性删除: 惰性删除此时就派上用场了,当用户获取数据时,redis会先检查该数据有没有过期,如果过期就删除。
听上去定期删除+惰性删除好像很完美的样子,but过期的数据用户又没有及时访问,那么内存中还是会存在大量的过期数据。此时应该采用redis内存淘汰机制。
- noeviction:内存不足以写入新数据的时候会直接报错。
- allKeys-lru:内存不足以写入新数据时候,移除最近最少使用的key。
- allKeys-random: 内存不足以写入新数据时,随机移除key。
- volatile-lru: 内存不足以写入新数据时,在设置了过期时间的key当中移除最近最少使用的key。
- volatile-random: 内存不足以写入新数据时,在设置了过期时间的key中,随即移除key。
- volatile-ttl: 内存不足以写入新数据时,在设置了过期时间的key当中移除最先过期的key。
上面六种你可以这么记:
- 不移除直接报错: noeviction。
- 在所有key中移除: 1.allKeys-lru 2. allKeys-random
- 在设置了过期时间的key中移除: 1. volatile-lru 2. volatile-random 3.volatile-ttl
一般常用allKeys-lru