专栏首页用户1337634的专栏本地缓存同步的一个简单方案

本地缓存同步的一个简单方案

现在大部分系统使用的都是分布式缓存系统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<Integer, Model>) 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断开连接,需要捕捉异常,并重新订阅

参考

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Guava Cache最佳实践

    使用缓存一定要防止缓存占用过多的内存,导致程序OOM。需要对缓存的内存使用量进行限制,同时还需要设置过期或刷新策略。

    十毛
  • maven配置解析和最佳配置

    十毛
  • Sprint Boot Admin

    首先建立一个Spring Boot Admin Server,只需要两步,非常简单

    十毛
  • 缓存技术 2

    随着网络的发展,数据越来越多,从而导致运算压力越来越大。为了解决这一问题,就需要合理分配资源,充分利用已有资源,缓存的工作实际就是资源的合理分配。

    公众号php_pachong
  • 亿级流量峰值,如何攻破?

    许多大型互联网系统,如电商、社交、新闻等App或网站,动辄日活千万甚至上亿,每分钟的峰值流量在数十万以上,架构上如何应对如此高的流量峰值呢?

    程序IT圈
  • 关于缓存和数据库强一致的可行方案

    我们在日常工作中经常会遇到要求缓存和数据库强一致性的问题,我们平常的做法是,确保数据库插入成功,然后再更新缓存,但有时候数据库插入成功后,缓存出现问题或者缓存系...

    小程故事多
  • 针对缓存的攻击和防御

    2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒

    ydymz
  • 使用缓存技术10年了,总结了如下经验!

    一位七牛的资深架构师曾经说过这样一句话:“Nginx+业务逻辑层+数据库+缓存层+消息队列,这种模型几乎能适配绝大部分的业务场景。

    范蠡
  • 缓存穿透、击穿、雪崩什么的傻傻分不清楚?

    对于缓存,大家肯定都不陌生,不管是前端还是服务端开发,缓存几乎都是必不可少的优化方式之一。在实际生产环境中,缓存的使用规范也是一直备受重视的,如果使用的不好,很...

    Java3y
  • 缓存与数据库一致性问题深度剖析

    当我们在做数据库与缓存数据同步时,究竟更新缓存,还是删除缓存,究竟是先操作数据库,还是先操作缓存?本文带大家深度分析数据库与缓存的双写问题,以供大家参考。

    黄泽杰

扫码关注云+社区

领取腾讯云代金券