数据模型比较简单
对于数据库性能要求较高
不需要高度的数据一致性
以key-value形式存储,不一定遵循传统数据库的一些基本要求(非关系的、分布式的、开源的、水平可扩展的)
优点:对数据高并发读写
对海量数据的高效率存储和访问
对数据的可扩展性和高可用性
缺点:无法做太复杂的关系模型
Redis单线程:指处理我们的网络请求的时候只有一个线程来处理【文件刷盘等用的是多线程】
Redis单线程的好处:
单线程避免了不必要的上下文切换和竞争条件以及加锁释放锁操作
第一步:准备工作【解压tar包,创建Redis相关目录】
tar -zxvf redis-5.0.2.tar.gz
mkdir /opt/redis
mkdir /opt/redis/conf
mkdir /opt/redis/data
第二步:编译redis
#进入解压后的tar包执行
make
#执行结束之后进入src目录
cd src
make install PREFIX=/opt/redis
第三步:移动配置文件到conf目录
cp redis.conf /opt/redis/conf
额外配置:
vim /etc/rc.local
#加入
/opt/redis/bin/redis-server /opt/redis/conf/redis.conf
vim /opt/redis/conf/redis.conf
#修改
dir ./ ---> dir /opt/redis/data/
命令 | 说明 |
---|---|
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操作下标都是闭区间的
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 | 设置指定偏移量字符串内容 |
使用场景:存储具有一定结构化的数据
常用命令格式 | 描述 |
---|---|
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则设置 |
列表为有序、可重复结构。可指定位置插入和删除、也可从左右插入和弹出(模拟栈结构)
常用命令格式 | 描述 |
---|---|
LPUSH|RPUSH key value [value …] | 从列表的左|右插入元素 |
LINSERT key BEFORE|AFTER value newValue | 在list指定的值前|后插入新元素【时间复杂度O(N)】 |
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 | LPOP/RPOP的阻塞版,timeout为0则表示永不阻塞 |
使用技巧:
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 …] } | 返回/存储一个集合的全部成员,该集合是所有给定集合的并集 |
使用技巧
SADD = Tagging
SPOP/SRANDMEMBER = Random Item
SADD + SINTER = Social Graph【共同关注、有着相同兴趣等】
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返回分数 |
慢查询队列简介
慢查询设置
slow-max-len【慢查询队列长度,默认128】
slowlog-log-slower-than【慢查询的阙值(微秒,1000000 微秒=1 秒),默认10000(10毫秒),建议为1000(1毫秒)】
更改慢查询参数建议使用
CONFIG SET parameter value
方式,而不是更改redis.conf文件重启redis
慢查询命令
常用命令格式 | 描述 |
---|---|
slowlog get n | 获取慢查询队列一条记录 |
slowlog len | 获取慢查询队列长度 |
slowlog reset | 清空慢查询队列 |
查询结果示例
一般情况下客户端发送一条命令到Redis,Redis处理结束返回结果,即 N条命令 = N次网络时间 + N次计算时间
PipeLine将命令打包发送给客户端,Redis处理完返回结果,PipLine是一个异步处理方式,并不等待Redis返回。
Pipeline(N条命令) = 1次网络时间 + N次计算时间
/*
* 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 …]] | 取消订阅 |
常用命令格式 | 描述 |
---|---|
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 的二进制位的位置 |
实质:用String类型实现,不能取出具体值,有错误率
作用:极小的空间实现独立数量统计
常用命令格式 | 描述 |
---|---|
PFADD key element [element …] | 将任意数量的元素添加到指定的HyperLogLog |
PFCOUNT key [key …] | 计算HyperLogLog有多少值 |
PFMERGE destkey sourcekey [sourcekey …] | 将多个HyperLogLog合并为一个HyperLogLog |
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|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] | 返回指定范围内的数据 |
ZREM key member | 删除成员 |
RDB是Redis用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据)。恢复时是将快照文件直接读到内存里。
触发机制
save
手动触发,同步命令,会阻塞线程
bgsave
手动触发,fork出一个子进程,异步命令,不会阻塞线程【阻塞仅仅会发生在fork出子进程的阶段】
自动
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是一个追加写入的日志文件从而实现持久化的方式,生成的AOF文件是可识别的纯文本文件。Redis默认使用RDB持久,开启AOF持久化需要设置appendonly
为yes
AOF文件生成策略
always 不丢失数据,每次更新记录数据就进行io操作
everysec 可能会丢失1s数据,但io小
no 不启用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 |
命令
redis-cli info Persistence
可以查看统计信息aof_current_size
【当前AOF文件大小】和aof_base_size
【上次重写AOF文件大小】
命令 | RDB | AOF |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复 | 快 | 慢 |
数据安全性 | 丢数据 | 根据策略决定 |
轻重 | 重 | 轻 |
Redis 4.0 开始支持 rdb 和 aof 的混合持久化(默认开启),这样做的好处是可以结合 rdb 和 aof 的优点, 快速加载同时避免丢失过多的数据。缺点 aof 里面的 rdb 部分就是压缩格式不再是 aof 格式,可读性差。
配置开启混合持久化aof-use-rdb-preamble yes
命令开启混合持久化config set aof-use-rdb-preamble yes
开启混合持久化时,aof rewrite 的时候就直接把 rdb 的内容写到 aof 文件开头。aof 文件内容会变成如下
+------------------------+
| |
| |
| RDB |
| FORMAT |
| |
| |
| |
+------------------------+
| |
| AOF |
| FORMAT |
+------------------------+
fork操作优化
控制redis实例最大可用内存:maxmemory
子进程
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
在从机上执行slaveof masterIp masterPort
,此命令是异步。slaveof no one
结束从属关系。
修改redis配置文件
slaveof masterIp masterPort #配置主从
slave-read-only yes #从节点只读
主从状态查看
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; 就必须进行全量复制,否则执行部分复制。
以下常见的主从切换都可以使用部分复制:
当从库与主库断开时间过长导致自己的偏移量不在master_repl_offset
允许的范围之内,会触发全量复制
##############从库##############
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不一致导致丢失数据
数据结构参数优化只有优化了主机,从机未配置导致内存不一致,数据错误或丢失
配置sentinel.conf文件
#没有开启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__:hello
频道交互第一步:Sentinel选举出leader
原因:只需要一个Sentinel完成故障转移
选举:通过sentinel is-master-down-by-addr
命令都希望自己成为领导者
第二步:故障转移选举Master
slave-prority
最高的slave节点,如果存在则返回【一般不修改】对上面的slave节点执行slaveof no one
命令让其成为master节点
slaveof
命令,让它们成为master节点的slave节点,复制规则和paraller-sync
参数有关手动下线master机器
sentinel failover <masterName>
分布方式 | 特点 | 典型产品 |
---|---|---|
哈希分布 | 数据分散度高 |
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
修改redis配置文件
cluster-enable yes
cluster-config-file nodes-${port}.conf
原生安装
启动所有节点
执行redis-cli -p ${port} cluster meet ${ip} ${port}
使节点相遇
执行cluster addslots slot [slot...]
分配槽
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}
执行主从分配
集群命令安装
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
–cluster-replicas代表集群的每个主节点的从节点个数
ruby安装已废弃
cluster配置参数
cluster-enable yes
cluster-config-file nodes-${port}.conf
cluster-node-timeout 15000
cluster-require-full-coverage yes #必须集群所有节点能提供服务才提供服务
扩展集群
扩展步骤原理
扩展执行步骤
加入集群
执行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}
下线节点
迁移数据
redis-cli --cluster reshard {ip}:{port} --cluster-from {sourceNodeId} --cluster-to {targetNodeId} --cluster-slots {slotsNum}
下线节点
redis-cli --cluster del-node {ip}:{port} {shutdownNodeId}
使用cluster keyslot ${key}可查看key对应的hash值
moved重定向
ask重定向
smart客户端
实现原理:追求性能
cluster slots
初始化槽和节点映射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主要提供了以下几种批量操作方式:
批量操作必须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分配到某台机器上【不建议,大量数据会造成数据倾斜】
//使用{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");
故障发现
故障恢复
cluster-node-timeout * cluster-slave-validity-factor
取消资格cluster-slave-validity-factor
默认是10集群完整性
cluster-require-full-coverage yes
默认为yes
大多数情况下业务无法容忍,建议cluster-require-full-coverage
设置为no
PubSub广播
任意节点发布消息所有节点都会订阅到消息,消耗带宽较多。JedisCluster只会订阅任意一个节点
数据倾斜
造成的原因:
使用
redis-cli --cluster info {ip}:{port}
可以查看key、slot分布情况使用
redis-cli --cluster rebalance {ip}:{port}
进行数据平衡【慎用】
从机读写问题
在集群模式下从节点不接受任何读写请求
数据迁移
官方工具不只能从单机向集群迁移,不支持断点续传,不支持在线迁移,单线程影响速度,不建议使用官方工具
redis里面存储的过期时间,都是绝对时间点,所以如果两台机器时钟不同步,那么超过的数据会全部删除。
设置maxmemory-policy
值指定算法
更新策略 | 解释 |
---|---|
volatile-lru | 过期的键使用LRU策略剔除,没有可删除对象则退回到noeviction |
allkeys-lru | 所有键均使用LRU策略剔除,直到腾出足够空间 |
volatile-lfu | 过期的键使用LFU策略剔除,没有可删除对象则退回到noeviction |
allkeys-lfu | 所有键均使用LFU策略剔除,直到腾出足够空间 |
volatile-random | 过期的键使用随机策略剔除,没有可删除对象则退回到noeviction |
allkeys-random | 所有键均使用随机策略剔除,直到腾出足够空间 |
volatile-ttl | 剔除TTL最小的键,没有可删除对象则退回到noeviction |
noeviction | 默认策略,不做任何事,返回写错误 |
LRU【Least Recently Used】最近最少被使用
LFU【Least Frequently Used】最不常用
被动更新
当客户端方位key的时候,主动检测这些key是否过期,过期就删除
主动更新
每秒检测10次以下操作,测试随机的20个keys进行相关过期检测,删除所有的过期的keys,如果有多于25%的keys过期,重复此操作
策略 | 一致性 | 维护成本 |
---|---|---|
算法剔除 | 最差 | 低 |
被动更新 | 较差 | 低 |
主动更新 | 强 | 高 |
低一致性:最大内存和淘汰策略
高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底
缓存穿透
特点:
当缓存和数据库中都没有数据的时候,当查询Redis没有数据的时候,会继续查询数据库,数据库也没有数据,当大量查询请求发生或遭到恶意攻击时,这些访问全部透过Redis,并且数据库也没有数据,这种现象称为“缓存穿透”。
解决方案:
缓存击穿
特点:
当Redis的热点数据key失效时,大量并发查询直接打到数据库,此时数据库负载压力骤增,这种现象称为“缓存击穿”
解决方案:
1.设置key值永不过期
2.使用互斥锁,查到后就回填缓存
缓存雪崩
特点:
缓存雪崩是指在短时间内,有大量缓存同时过期,导致大量的请求直接查询数据库,从而对数据库造成了巨大的压力,严重情况下可能会导致数据库宕机的情况叫做缓存雪崩。
解决方案:
无底洞问题
现象:增加节点机器性能没提升反而下降
解决方案参考批量操作
现象:热点key缓存重建过程过长导致浪费了不必要的资源
解决方案:
互斥锁【使用redis构建锁机制】
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;
}
永不过期
方案 | 优点 | 缺点 |
---|---|---|
互斥锁 | 思路简单 | 代码复杂度增加 |
保证一致性 | 存在死锁的风险 | |
永不过期 | 基本杜绝热点key重建问题 | 不保证一致性 |
逻辑过期时间增加维护成本和内存成本 |
优点:不需要存储key,节省空间
缺点:算法判断key在集合中时,有一定的概率key其实不在集合中,且无法删除
google-guava库实现了java版的布隆过滤器
使用插件的方式部署
redis4.0之后支持使用插件的方式使用Bloom filters和cuckoo filters,redis4.0之前需手动使用代码的方式编写布隆过滤器,安装步骤参考下列链接
建议在conf配置文件中配置,不使用redis-server --loadmodule /path/to/rebloom.so
启动
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依赖
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jrebloom</artifactId>
<version>1.1.0</version>
</dependency>
Api使用
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();
}
发现BigKey
debug object {key}
查看指定key的详细信息redis-cli --bigkeys
扫描出BigKey【全表扫描,阻塞,建议从节点本地执行】BigKey删除
场景:当key非常大时,delete命令执行十分缓慢,会发生阻塞【过期bigkey也是进行删除操作也会阻塞】
redis4.0之后:可以使用unlink
命令进行后台删除,不阻塞前台
使用OBJECT IDLETIME {key}
查看key的闲置时间
过期时间不易集中
有遍历需求可以使用hscan
、sscan
、zscan
代替【这些扫描命令在field较少时COUNT参数不会生效】
必要情况下使用monitor
命令监控,注意时间不要过长
参数名 | 含义 | 默认值 | 建议 |
---|---|---|---|
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 <class> <hard limit> <soft limit> <soft seconds>
<class>:客户端类型,分为三种
(a)normal:普通客户端
(b)slave:用从节点用于复制,伪裝成客户端
(c)pubsub:发布订阅客户端 <hardlimit>:如客户使用的输出冲区大于hardlimit客户端会被立即关闭
<soft limit> 和<soft seconds>:如果客户端使用的输出缓冲区超过了 <soft limit>并且持续了<soft seconds>,客户会被立即关闭
复制缓冲区:用于slave和master断开重连时不进行全量复制保存偏移数据使用
默认为repl-backlog-size 1mb
AOF缓冲区:AOF重写期间,AOF缓冲区,没有容量限制
删除过期值
超过maxmemory使用maxmemory-policy进行控制,参见缓存更新策略
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
在配置文件中配置
#将命令更改为另一个字符串,原命令失效。如果字符串为空则表示禁用此命令
rename-command {cmd} {str}
附各种数据类型的内部编码:
附Redis云平台CacheCloud【一键部署、监控、运维、数据迁移工具等】 GitHub项目地址