现在大部分系统使用的都是分布式缓存系统Redis。 但在一些场景下,比如缓存单元很大,单元数不多,变化很小,加载时间很长,如算法模型。 这个时候使用本地缓存比Redis的效率要高很多,但是又要保证集群中各个机器的缓存的一致性,不然就会出现请求耗时不稳定的情况,也有可能出现相同的请求不同服务器返回的结果不一致。实现思路:
本文介绍了一个简单的实现集群中同步各服务器本地缓存的方案。
- 集群各个节点通过Redis的pub/sub机制实现简单的消息队列,把缓存的变化广播给集群中所有节点。
- 因为缓存单元的数据本身很大,但是数量并不多,所以只把缓存数据的id保存在Redis的set中。
初始同步 程序启动时,一开始没有缓存任何模型数据,进入初始同步阶段。流程如下:
文章图片
初始同步
监听缓存变更事件
获取缓存事件后,并不立即操作,后续再顺序处理该事件
下面一些操作都用redis命令演示,实际项目中,使用的是jedis
redis> subscribe channel.model
获取缓存的数据id
一般从redis读取缓存的模型id列表
redis> smembers cache.models
缓存所有模型数据
根据上一步读到的id列表,缓存所有模型数据
一般是从数据库或分布式文件系统中加载模型增量更新
如果到缓存模型数据结束,有监听到缓存变更事件,则依次响应该事件
完成增量更新后,节点接入下一个阶段:广播同步广播同步 集群中的每个节点都订阅频道
channel.model
, 接收缓存变更的消息(增、删、改);也在主动变更后,往频道channel.model
发布消息来广播给其他节点。消息分为以下三种类型:- 新增缓存
一般是请求第一次到达,或者是模型生成后,收到HTTP更新消息,就会预加载模型文件。
redis> publish channel.model add:1
- 更新缓存
redis> publish channel.model update:1
- 删除缓存
不仅仅是用户逻辑触发缓存的删除,更大的可能是因为缓存策略需要删除长期不使用的缓存。
比如我们常用的Gauva Cache。设置如下:
CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(1, TimeUnit.DAYS)
.removalListener((RemovalListener) notification -> {
final RemovalCause cause = notification.getCause();
switch (cause) {
//缓存到期
case EXPIRED:
//缓存大小限制
case SIZE:
//缓存被垃圾回收
case COLLECTED:
//如果是缓存到期等原因被删除,则需要通知分布式环境下的其他机器也要删除
distCacheManager.removeFromCache(notification.getKey());
break;
//缓存显示删除(这里没有调用是避免事件循环)
case EXPLICIT:
//缓存显示替换(这了没有调用是避免事件循环)
case REPLACED:
break;
default:
log.error("there should not be [{}]", cause);
}
redis> publish channel.model delete:1
优缺点 优点:
- 实现简单:基于广泛使用的Redis,没有引入其他组件,而且实现逻辑也很简单
- 在一些极端情况下,会出现缓存的更新不及时。比如模型更新后,收到请求的进程本地更新后返回结果,因为消息是异步的,可能还没达到Redis时,进程就挂掉了。
- 当模型更新时,各个进程中缓存的模型在很短的时间内存在不一致的情况。 会影响部分用户。不过这种情况是完全可以接受的。
- 因为所有节点都订阅了同一频道
channel.model
,也会接听到自身广播的事件,所以节点在响应事件时,可以做幂等处理 - Java程序使用Jedis实现频道订阅,订阅调用是阻塞的,所以需要使用单独的线程来执行,不能阻塞主干流程
- Jedis频道订阅线程可能会与Redis断开连接,需要捕捉异常,并重新订阅
- Jedis实现Publish/Subscribe功能
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)