redis通过epoll方式来实现IO多路复用,将事件放到队列中,然后文件事件分派器再依序将事件分发给事件处理器。
C10K问题:假如有1w连接则基于进程/线程模型就需要创建1W个线程进行处理,如何突破单机性能是高性能网络编程要面对的问题
解决:一个进程/线程同时处理多个连接,这就是IP多路复用
重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。redis4.0使用aof-use-rdb-preamble开启混合持久化。
aofrewrite也是先写一份全量数据到新AOF文件中再追加增量只不过全量数据是都是命令。redis4.0混合持久化则是先写入旧RDB全量数据再追加增量日志,这样既可以提高aof文件的恢复速度又可以避免丢失大量数据。
rdb优点是恢复速度块,适合进行备份以及异地容灾。缺点是生成文件时间长,一旦宕机丢失数据。数据路量大可能造成停止处理客户端请求。
aof优点是灵活设置同步策略,减小宕机丢失的数据。缺点是aof文件大,恢复速度慢
当redis内存超出物理内存限制时,内存数据会和磁盘频繁进行交换swap,交换会让redis的性能急剧下降,最终导致redis不可用。
maxmemory-policy noeviction
我们可以设置maxmemory <bytes>,当数据达到限定大小后,会选择配置的策略淘汰数据
redis使用maxmemory来限制最大使用内存,当实际内存超出maxmemory时,redis提供了几种淘汰策略让用户自己决定该如何腾出新的空间继续提供读写服务。
noeviction(默认策略):拒绝写请求,放行读请求
volatile-lru:淘汰过期key,最少使用key优先被淘汰。没有设置过期时间的key不会被淘汰
volatile-ttl:淘汰过期key,剩余生存时间最短的优先被淘汰。没有设置过期时间的key不会被淘汰
volatile-random:淘汰过期key,过期key集合随机被淘汰。没有设置过期时间的key不会被淘汰
allkeys-lru:淘汰所有key,最少使用优先被淘汰
allkeys-random:淘汰所有key,随机淘汰key
如果只是拿redis做缓存,那么应该使用allkeys-xxx
如果使用redis的持久化功能,那就使用volatile-xxx,可以保证不过期的key不会被淘汰。
当某个节点超过cluster-node-timeout设置的时间时,才会认定该节点出现故障,需要进行主从切换,这样可以防止网络抖动发生频繁的主从切换。
询问的详情
How many slots do you want to move (from 1 to 16384)? 1000
What is the receiving node ID? fea53768189af3e3e4849038af13607f59ec84b0
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1: 662809cf2d5bb138912dea7fb1e452f6e0f149da
Source node #2: done
Ready to move 1000 slots.
Source nodes:
M: 662809cf2d5bb138912dea7fb1e452f6e0f149da 192.168.5.100:8001
slots:[0-5460] (5461 slots) master
1 additional replica(s)
Destination node:
M: fea53768189af3e3e4849038af13607f59ec84b0 192.168.5.100:8007
slots: (0 slots) master
Resharding plan:
Moving slot 0 from 662809cf2d5bb138912dea7fb1e452f6e0f149da
Moving slot 1 from 662809cf2d5bb138912dea7fb1e452f6e0f149da
Moving slot 2 from 662809cf2d5bb138912dea7fb1e452f6e0f149da
Moving slot 3 from 662809cf2d5bb138912dea7fb1e452f6e0f149da
.
.
.
Moving slot 996 from 662809cf2d5bb138912dea7fb1e452f6e0f149da
Moving slot 997 from 662809cf2d5bb138912dea7fb1e452f6e0f149da
Moving slot 998 from 662809cf2d5bb138912dea7fb1e452f6e0f149da
Moving slot 999 from 662809cf2d5bb138912dea7fb1e452f6e0f149da
Do you want to proceed with the proposed reshard plan (yes/no)? yes
.
.
.
Moving slot 995 from 192.168.5.100:8001 to 192.168.5.100:8007:
Moving slot 996 from 192.168.5.100:8001 to 192.168.5.100:8007:
Moving slot 997 from 192.168.5.100:8001 to 192.168.5.100:8007:
Moving slot 998 from 192.168.5.100:8001 to 192.168.5.100:8007:
Moving slot 999 from 192.168.5.100:8001 to 192.168.5.100:8007:
[root@localhost 8007]#
crc16(key) mod 16384
redis使用的是惰性删除,和定期删除,惰性删除在操作过期键才会进行删除,而定期删除则是每隔一段时间主动进行删除
支持部分重同步的数据结构有:主从偏移量,复制缓冲区,运行ID
redis通过psync实现断点续传
a) 从服务器首次复制执行psync ? -1命令执行完整重同步,否则向主服务器发送psync runid offset命令
b) 主服务器根据runid,offset判断执行何种重同步,如果返回fullresync runid offset,则执行完整重同步,如果返回+continue回复,则执行部分重同步,如果主服务返回-rr回复,表示主服务器低于redis2.8,不支持psync
实际工作经验中,热点数据问题在工作总相比雪崩更容易遇到,只是大部分时候热点不够热,都会被被提前告警解决,但这个问题一旦控制不了造成的线上问题也是足够让你年绩效垫底了。
正常情况下,Redis集群中的数据都会均匀分配到每个节点上,请求也会均匀的分布到每个分片上,但在一些特殊场景下,比如外部攻击,热点商品等,最典型的就是明星离婚啊,吃瓜群众来留言导致评论功能奔溃。这种短时间对某些key访问量过大,如果对相同key的请求汇聚到一台节点上,就会导致该节点负载过高成为瓶颈问题,导致雪崩等一系列问题。
发现问题——>分析问题——>解决问题
这个问题要看不同的业务场景。比如公司组织促销活动,那参加促销的商品肯定是有办法提前统计的,这种场景下使用“预估法”。对于突发事件,redis会自己监控热点数据
由于redis是单线的,如果一次操作的value很大,会延长整个redis集群的响应时间
我先说说多大的value算大,根据公司基础架构给出的经验值
大:string类型value>10K,集合数据类型元素个数>1000
超大:string类型value>100k,集合数据类型个数>10000
由于 Redis 是单线程运行的,如果一次操作的 value 很大会对整个 redis 的响应时间造成负面影响,所以,业务上能拆则拆,下面介绍几个典型的分拆方案
newHashKey=hashkey+(hash(field)%10000)
hset(newHashKey,field,value)
hget(newHashKey,field)
第一次线上遇到 Redis 雪崩的时候我是维护某电影线上平台(某首富家的)国内某电影平台,因为系统架构老旧,每年大年初一贺岁档电影上映,购票人达到高峰,系统就会发生因缓存失效问题导致的出票故障,当时底层是用的oracle小型机,依旧扛不住。大年初一,所有同事打开电脑被迫营业。关于缓存失效引起的类似问题,坚决要扼杀,对用户友好,对程序员友好,对老板也友好。
程度:雪崩/穿透——>击穿
redis雪崩指的是,缓存中大批热点数据过期后系统涌入大量查询请求,因为缓存失效而涌入数据库,导致数据库查询超时甚至宕机
解决:
redis穿透指的是绕过redis和数据库,大规模发起不存在key的查询请求导致系统 压力过大最后故障
解决:
try {
Object value = dao.query(key); // 从数据库中查询数据
if (value == null) {
redis.set(key, null, 20); // set null值,10ms过期时间
} else {
redis.set(key, value, 1000);
}
} catch(Exception e) {
redis.set(key, null, 20);
}
类似redis穿透,一般指一个热点key被穿透,成千上万次请求直接涌入数据库,百万QPS直接压垮数据库。
解决:
公司常用方法是使用mutex,就是说,在缓存失效时不是立即取load db,而是先使用带成功返回值的操作(setnx)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存,否则就重试整个get方法。
总体思路是:通过带返回值的操作在redis设置一个信号量,使得多线程按序查询数据库并回设内存
public String get(key) {
String value = redis.get(key);
if (value == null) { //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
} else {
return value;
}
}
APP用户打开页面都要轮询一遍未读消息。比如对于京东 APP 首页,每个用户每次进入都会查询有没有未读消息,公司用户总数是数亿级别,但是实际上并不是真的每个用户都有未读消息,对于没有未读消息的用户,也需要查询未读消息,甚至需要查询DB,实际上是比较浪费的
接口初期的设计是这样的,在Redis记录了有消息的用户userId,用户打开app,查询一次Redis,返回true代表有消息,然后查询真正的Redis消息列表或者DB等其它后续操作
当用户量逐渐变大,内存或Redis占用的空间也越来越多,有几千万、甚至几亿用户,那么内存空间会增加很多。如果是存储URL之类的字符串,消耗的内存简直是不能忍受。某些场景对所有用户轮询DB也是不可取的。
基于以上考虑,最好采用一种新得方案过滤。因为之前看过业内其他公司采用BlomFilter来实现海量数据去重过滤,各方面性能表现优异。因此考虑基于BloomFilter来实现过滤无效user从而大大减小了查询的无用功。
可以用于检索一个元素是否在一个集合中
BollomFilter检索一个元素是否在一个集合中,会出现一定的错误率,它可以告诉你某样东西一定不存在或可能存在。
实现原理:
基本思想是利用一个足够好的hash函数将一个字符串映射到二进制位数组的一位,但是不管hash函数 多么高效,还是会出现冲突。
BloomFilter有个缺点是不能删除数据,因为删除数据可能会影响到其他数据
BllomFilter十分适合海量数据去重、过滤,尤其是当检查的字符串比较大时,极大节省了内存空间
peek.allocated:redis实例运行过程中,allocator分配的内存峰值
total.allocated:allocator分配的当前内存字节数
startup.allocated:redis启动占用内存字节数
replication.backlog:redis复制积压缓冲区
clients.slaves:所有slave clinents消耗的内存字节数
clients.normal:常规客户端消耗内存字节数
overhead.total:redis额外总开销=分配总内存-数据实际占用内存
dataset.bytes:redis数据实际占用内存
dataset.percentage:redis数据占用内存占总内存百分比
keys.count:redis实例key的数量
peek.percentage:当前内存使用量与峰值时的占比
fragmentation:redis内存碎片率
LazyFree机制,延迟删除大key,降低删除操作对系统资源的占用影响
redis是单线程程序,当运行一个耗时较大的请求时,会导致所有请求排队等待,在请求处理完成之前,redis不能响应其他请求。
redis删除大的集合键就是一种比较耗时的操作。
主动删除:
a) unlink主动删除,若集合键的元素个数大于64个,会开启子线程进行异步删除
b) flushdb可添加async异步清理选项,redis清理整个实例都是异步的
被动删除:
被动删除的四种场景,每种场景都有一个配置参数
lazyfree-lazy-eviction no //当内存使用达到maxmeory,设置淘汰策略时,是否采用lazy free机制
lazyfree-lazy-expire no //针对设置过期时间的键,被redis删除时是否采用lazy free机制
lazyfree-lazy-server-del no //针对有些带有隐藏del操作,是否采用lazy free机制
slave-lazy-flush no //针对slave进行全量同步时,加载RDB文件前,会运行fushall来清理数据 是否采用lazyfree
Psync2
升级psync1解决副本重启,全量同步问题,减少同步数据和时间