大key如何发现及治理?
发现
redis4.0之前
- redis-cli --bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。
优点是可以在线扫描(用scan命令通过游标遍历键空间并且在生产上可以通过对slave执行该命令),不阻塞服务;缺点是信息较少,内容不够精确。扫描结果中只有string类型是以字节长度为衡量标准的。List、set、zset等都是以元素个数作为衡量标准,元素个数多不能说明占用内存就一定多。
自定义的扫描脚本,通过strlen、hlen、scard等命令获取字节大小或者元素个数。这样获取的到和上述1缺点一样,不能获取到除string类型外的具体字节长度等内存信息。
debug object key命令。可以查看某个key序列化后的长度,每次只能查找单个key的信息。(元素个数较多的数据结构,debug object执行速度比较慢,存在阻塞Redis的可能,所以不推荐)。
通过了解rdb文件结果,通过工具解析rdb文件,分析rdb文件,来获取大key,网上的redis-rdb-tools,godis-cli-bigkey等。redis实例上执行bgsave,然后对dump出来的rdb文件进行分析,找到其中的大KEY。
redis4.0之后
memory usage key命令
采用抽样估算整个key的内存大小(比如hash类型有100个field,只抽取5个(默认值)来估算整个的内存)样本的数量决定了key的内存大小的准确性和计算成本,样本越大,循环次数越多,计算结果更精确,性能消耗也越多。可以通过SAMPLES参数设置样本大小
我们可以通过脚本在集群低峰时扫描Redis,循环去获取所有key的内存大小。
治理
redis monitor
-
客户端通过执行MONITOR命令可以将自己变为一个监视器,实时地接受并打印出服务器当前处理的命令请求的相关信息,如下图
此时,当其他客户端向服务器发送一条命令请求时,服务器除了会处理这条命令请求之外,还会将这条命令请求的信息发送给所有监视器:
2.原理
- redisServer维护一个monitors的链表,记录自己的监视器,每次收到MONITOR命令之后,只需将客户端追加到表尾即可
- 向监视器传播命令
call函数中有对于监视器命令传播,将命令打包为协议,发送给监视器
详情参考:https://www.jianshu.com/p/36420adb344b
本地缓存 - 通知机制怎么实现的?
Redis键通知机制
自从redis2.8以后出了一个新特性,Keyspace Notifications 称为“键空间通知”。
这个特性大概是,凡是实现了Redis的Pub/Sub的客户端,只需要订阅相应Channel,就可以获得对Key操作的一些事件,从而可以处理一些业务。
比如:
- 当你del一个key时,就可以触发一个del事件通知。
- 一个key的失效时间到了,就会触发expire事件通知。
- 对一个库所有key操作,都可以获取通知。
注意事项:
1)因为 Redis 目前的订阅与发布功能采取的是发送即忘(fire and forget)策略, 所以是不可靠的,并不能确保消息送达。
2)Redis Pub/Sub 是一种并不可靠地消息机制,他不会做信息的存储,只是在线转发,那么肯定也没有ack确认机制,另外只有订阅段监听时才会转发!所以Keyspace Notification 也不是可靠地通知系统
本地缓存的应用
键通知机制通过发布订阅实现,具体步骤如下:
1.修改配置:键空间通知功能耗费CPU,默认关闭,需要修改配置文件redis.conf或 操作CONFIG SET命令,设置notify-keyspace-events选项,来启用或关闭该功能(耗费cpu)。
2.对Redis实例进行发布订阅,指定监听类和监听事件类型(键删除,过期);
3.监听类继承JedisPubSub,实现相应操作(清除本地缓存,保证一致性);
4.客户端进行操作,以触发订阅事件发生。
参数:
代码实现:
import java.util.List;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisPubSub;
public class Subscriber {
public static void main(String[] args) {
JedisPool pool = new JedisPool(new JedisPoolConfig(), "127.0.0.1", 3069);
Jedis jedis = pool.getResource();
config(jedis);
jedis.psubscribe(new KeyExpiredListener(), "__key*__:*");
}
private static void config(Jedis jedis){
String parameter = "notify-keyspace-events";
List<String> notify = jedis.configGet(parameter);
if(notify.get(1).equals("")){
jedis.configSet(parameter, "KA");//参数意义见上图。
}
}
}
class KeyExpiredListener extends JedisPubSub {
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
System.out.println("onPSubscribe " + pattern + " " + subscribedChannels);
}
// 取得订阅的消息后的处理 (key过期可以删除本地缓存)
@Override
public void onPMessage(String pattern, String channel, String message) {
// 删除本地缓存
}
}
附:redis文件事件原理:
Redis中的文件事件关注网络IO,用于处理 Redis 服务器和客户端之间的网络IO。
Redis基于Reactor模式开发了自己的网络事件处理器,也就是文件事件处理器。文件事件处理器使用IO多路复用技术,同时监听多个套接字,并为套接字关联不同的事件处理函数。当套接字的可读或者可写事件触发时,就会调用相应的事件处理函数。
如下图所示,文件事件处理器有四个组成部分,它们分别是套接字、I/O多路复用程序、文件事件分派器以及事件处理器,如下图所示:
文件事件每当一个套接字准备好执行 accept、read、write和 close 等操作时,就会产生一个文件事件。因为 Redis 通常会连接多个套接字,所以多个文件事件有可能并发的出现。
I/O多路复用程序负责监听多个套接字,并向文件事件派发器传递那些产生了事件的套接字。
尽管多个文件事件可能会并发地出现,但I/O多路复用程序总是会将所有产生的套接字都放到同一个队列(也就是后文中描述的aeEventLoop的就绪事件表)里边,然后文件事件处理器会以有序、同步、单个套接字的方式处理该队列中的套接字,也就是处理就绪的文件事件。
所以,一次 Redis 客户端与服务器进行连接并且发送命令的过程如上图所示。
1.客户端向服务端发起建立 socket 连接的请求,那么监听套接字将产生 AE_READABLE 事件,触发连接应答处理器执行。处理器会对客户端的连接请求进行应答,然后创建客户端套接字,以及客户端状态,并将客户端套接字的 AE_READABLE 事件与命令请求处理器关联。
2.客户端建立连接后,向服务器发送命令,那么客户端套接字将产生 AE_READABLE 事件,触发命令请求处理器执行,处理器读取客户端命令,然后传递给相关程序去执行。
3.执行命令获得相应的命令回复,为了将命令回复传递给客户端,服务器将客户端套接字的 AE_WRITEABLE 事件与命令回复处理器关联。当客户端试图读取命令回复时,客户端套接字产生 AE_WRITEABLE 事件,触发命令回复处理器将命令回复全部写入到套接字中。
具体源码参考:https://www.cnblogs.com/jabnih/p/4746138.html
SDK端统计分析热Key
说明:
1.业务方应用发起执行命令;
2.sdk首先判断是否是热key,是的话从热key模块中获取信息,否则执行正常redis命令执行流程,并异步上报命令信息到热key分析系统,对于expire,del命令也会上报到分析系统,分析系统会通知sdk中的热key模块,执行对应的删除操作,到达数据的一致性;
3.热key分析系统:
- 不断收集来自各个应用的sdk端的命令信息
- 自身维护一个热key列表,每隔5秒分析一次当前命令产生的热key,并下发给对应的应用程序。
- 当分析系统收到key失效的命令时,立即下发失效通知到sdk端热key模块,保持数据的一致性。
以上就是整个sdk端发现处理热key的大概思路。
Encache和guava cache的区别
guava cache
GuavaCache 提供了一般我们使用缓存所需要的几乎所有的功能,主要有:
- 自动将entry节点加载进缓存结构中;
- 当缓存的数据已经超过预先设置的最大值时,使用LRU算法移除一些数据;
- 具备根据entry节点上次被访问或者写入的时间来计算过期机制
(无读写回收expireAfterAccess,无更新回收expireAfterWrite,上次操作时间后再刷新refreshAfterAccess); - 缓存的key被封装在WeakReference引用内;
- 缓存的value被封装在WeakReference或者SoftReference引用内;
- 移除entry节点,可以触发监听器通知事件(同步,异步);
- 统计:统计缓存使用过程中命中率/异常率/未命中率等数据。
Guava Cache其核心数据结构大体上和ConcurrentHashMap一致,具体细节上会有些区别。功能上,ConcurrentMap会一直保存所有添加的元素,直到显式地移除.相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素.在某些场景下,尽管它不回收元素,也是很有用的,因为它会自动加载缓存.
Guava Cache 官方推荐的使用场景:
1.愿意消耗一些内存空间来提升速度;
2.能够预计某些key会被查询一次以上;
3.缓存中存放的数据总量不会超出内存容量(Guava Cache是单个应用运行时的本地缓存)。
Ehcache
特点:
- 提供内存和磁盘存储,缓存在内存和磁盘存储可以伸缩到数G;
- 过期策略(LRU、LFU和FIFO缓存淘汰算),也可以自定义,每个Cache的存活时间都是可以设置和控制的
- 动态、运行时缓存配置,存活时间、空闲时间、内存和磁盘存放缓存的最大数目都是可以在运行时修改的。
- 持久化,在VM重启后,持久化到磁盘的存储可以复原数据。
- 缓存管理器监听器,可以监听Removed/Put/Updated/Expired 事件
- JMX功能,可以监控和管理如下的MBean:CacheManager、Cache、CacheConfiguration、CacheStatistics
- 分布式缓存,通过RMI、JGroups或JMS进行的异步或同步的缓存复制
- 多种配置模式,配置文件、声明式配置、编程式配置,甚至通过指定构造器的参数来完成配置
[图片上传中...(image.png-451bd3-1575192572195-0)]