引言

本地缓存是一种常用的缓存手段,在客户端会经常使用这种手段来提升效率,但服务端在分布式环境下它出现的频率并不高,原因是本地缓存无法做到多台服务器数据共享,因此我们会采用redis这种分布式缓存,然而在某些情况下我们还是要使用的话怎么办呢?网上基本查不到有人去做这种分布式环境使用本地缓存,又要实时更新的方法,我这边项目中就需要这样。

项目背景

我这边有个评论检测的项目,其中有个敏感词检测的功能,这个功能需要一次性拿出所有敏感词,对输入的评论进行检测,敏感词有几万个,因此如果把敏感词放到redis,每次都取出所有的敏感词显然不太合适,因为我考虑放到本地缓存,但同时我们敏感词是需要实时更新的,后台设置以后要立马生效,所以我就弄了一套分布式环境更新本地缓存的方法。

功能实现

一. 第一个考虑的问题,我们要怎么让本地缓存知道敏感词更新了呢,肯定是通知,如何通知?

  • 我第一反应是消息推送,通过MQ这些消息队列来推送,RabbitMq有一种工作模式叫做fanout广播模式,它可以把消息分发给所有bind到此exchange的queue,但是这个依赖rabbitmq,如果公司没装就无法实现。
  • 后来我想到可以通过调用接口来通知消息(首先说明一下,我这个更新敏感词的操作是在后台管理,代码和评论检测的代码不是在一起的,是两套系统),通过接口调用的方式来更新本地缓存,问题就在于,调用接口是一次性的,nginx只会转发到一个节点上,所以现在要做的就是怎么调用所有节点的通知接口。我这边想到了微服务的注册机制,springcloud中每一个微服务都是由注册中心管理,我也可以做一个注册中心,把所有节点的IP注册进去,然后通过IP+端口来调用接口

所以最后的做法就是弄一个注册中心,我选择了redis作为注册中心,简单粗暴,效率又高。具体做法就是:

  • 应用启动时把IP注册到redis
    @PostConstruct
    public void addIpToRedis() {
        String key = XHStringUtil.generateRedisKey(XHConstant.IP);
        String ip = IPUtil.getLocalIpByNetcard();
        redisTemplate.opsForSet().add(key, ip);
    }

  • 应用关闭时把IP从redis剔除
    @PreDestroy
    public void removeIpFromRedis() {
        String key = XHStringUtil.generateRedisKey(XHConstant.IP);
        String ip = IPUtil.getLocalIpByNetcard();
        redisTemplate.opsForSet().remove(key, ip);
    }

当然只是这样还是不够,因为万一机房断电,进程被人误杀,这种都可能导致这个注册中心的数据不正确,不及时,所以还需要给注册中心加一个心跳检测:

  • 可以做个简单的维护注册中心的功能,就是每隔5分钟把本地的IP加入注册中心,而在调用方,只要调用节点接口失败,就把节点的IP从注册中心剔除
  • 也可以做个复杂点的心跳检测,就是专门写一个用于心跳的接口,每隔几秒调用一次,只要失败达到累计次数就剔除IP

至此,分布式环境更新本地缓存就已经实现了,简单又实用.其实还有一种比较麻烦的更新本地缓存方法,就是监控binlog,但是由于比较复杂,而且只能局限于mysql,就不展开了


最近我把这个升级了一下,大体情况就是做了个sdk,使用的时候只需要接入sdk,使用几个注解即可完成本地缓存的更新,更新方式可以选择Redis、Kafka、RabbitMQ或者上面的接口方式。