Loading

多级缓存(Nginx,OpenResty,Redis,Caffine,Canel)

架构

实际开发中往往使用多级缓存架构,如下图

img

  1. Java应用使用Caffine等缓存技术在JVM中缓存数据库的数据
  2. Redis(集群)做Java应用的缓存
  3. OpenResty(集群)做Redis(以及Java应用)的缓存
  4. 用户本地缓存

这种多级缓存结构可以大大的减少数据库甚至Web服务器的压力,因为大部分请求都被前面的缓存处理好了。但这也给实现数据一致性带来了一些挑战。

本篇文章不会事无巨细的介绍搭建的全过程,只介绍其中需要特别注意的地方,我这是看黑马程序员的网课学的,你想看全过程就去这里

OpenResty

OpenResty是用于Nginx平台上的一款Web服务器,可以使用lua语言进行业务逻辑的开发,emmm,最近正在研究的好多东西都用到lua。

OpenResty官方提供了Docker镜像所以可以很方便的使用Docker来搞它,下面是总体架构中的完整dockercompose文件中OpenResty的部分:

# docker-compose.yaml
  nginx_inst1:
    image: openresty/openresty:1.21.4.1-bullseye-fat
    ports:
      - 8080:8080
    volumes:
      - ./nginx/nginx-openresty-cluster.conf:/usr/local/openresty/nginx/conf/nginx.conf
      - ./html/:/usr/local/openresty/nginx/html/
      - ./lua/item.lua:/usr/local/openresty/nginx/lua/item.lua
      - ./lua/common.lua:/usr/local/openresty/lualib/common.lua

OpenResty在整个的架构中连接着redis和tomcat的业务集群,**它们持有着一份本地缓存,它们的查询路径是本地缓存->Redis->Tomcat

img

OpenResty本地缓存

由于要搭建OpenResty集群,所以每个集群中都有一个本地缓存,本地缓存通过下面的指令创建:

# nginx/nginx-openresty-cluster.conf
# shared_dict, 150m大小
lua_shared_dict   item_cache 150m;

这样的话,集群中每个实例持有一个本地缓存,但它们不是共享的,如果你不想在其中保存冗余数据或降低缓存命中率的话,请将前台nginx反向代理的负载均衡算法设置成根据请求url进行hash,这样可以保证相同的url的缓存都被保存到同一个OpenResty节点上。

# nginx/nginx-front.conf
upstream nginx-cluster {
   hash $request_uri;
   server nginx_inst1:8080;
   # ... more server ...
}

lua编程

在进行OpenResty的lua编程前,需要在配置文件的http作用域下导入lualib和clib

# nginx/nginx-openresty-cluster.conf
lua_package_path  "/usr/local/openresty/lualib/?.lua";
lua_package_cpath "/usr/local/openresty/lualib/?.so";

然后,对于想由lua处理的请求,通过content_by_lua_file指令调用对应的lua文件

server {
   listen 8080;
   # 正常处理的url(反向代理到tomcat集群)
   location /item {
      proxy_pass http://tomcatserver;
   }
   # 通过lua文件处理的url
   location ~ /api/item/(\d+) {
      default_type application/json;
      content_by_lua_file lua/item.lua;
   }
}

编写item.lua,它其中要干这么几件事:

  1. 获取用户要查找的itemid,即url/api/item/(\d+)中的路径参数
  2. 根据此id获取本地缓存
  3. 若失败获取redis缓存
  4. 若失败转发到tomcat业务集群
  5. 将结果保存到本地缓存

代码会在后面放出

OpenResty本地缓存过期

在OpenResty中可以设置本地缓存的过期时间,可以根据业务对数据一致性的强弱不同来设置不同的时间,甚至不保存本地缓存。

不知道OpenResty能不能和各种MQ或Canal整合,如果可以,通过消息队列进行数据更改通知也是一条路子。

Tomcat集群

Redis数据预热

在我们的例子中,Tomcat和OpenResty都并不直接将数据添加到Redis(你当然可以根据你的业务做出不同的决策),那Redis中的缓存是怎么来的呢?

  1. 当数据发生增删改时,Tomcat会向Redis中操作
  2. Tomcat启动时会做数据预热,即将数据库数据全部保存到Redis中

一般情况下,负责数据分析的团队会分析出热点数据,然后我们只需要将热点数据保存到Redis中即可,这里我们直接将所有数据都导入进Redis作为演示

@Component
public class RedisCacheHandler implements InitializingBean {
   // ....

    private void serializeAndSaveToRedis(String key, Object o) throws JsonProcessingException {
        String json = mapper.writeValueAsString(o);
        template.opsForValue().set(key, json);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        for (Item item : itemService.list()) serializeAndSaveToRedis("item:"+item.getId(), item);
        for (ItemStock item : stockService.list()) serializeAndSaveToRedis("stock:"+item.getId(), item);
    }
}

Canal监听MySQL的binlog变化

Canal是阿里出品的一个可以监听MySQL变化并异步通知给其它人的工具。

它用起来有点像消息队列,它的实现原理是伪装成MySQL的从节点并连接到MySQL服务器,这时,MySQL会在binlog发生改变时(发生增删改)将这个改动通知给从节点,这样Canal就实现了数据库增删改的监听

Canal提供了各种语言的客户端,以监听Canal的消息,所以Canal就是个在数据库和应用间提供监听功能的中间件

配置Canal时要注意的

  1. MySQL必须开启binlog
  2. binlog的类型是ROW

一个巨大的坑

当我完成了所有Canal的配置时,Canal和MySQL似乎能正确连接,并且Java客户端也能监听到Canal的部分消息。但它监听到的消息里只有TRANSACTIONBEGINTRANSACTIONEND,即它只监听到了事务的开启和关闭,并没有监听到行的改变。

我当时怀疑就是哪里的表名配置错了,但我检查了几个小时也没有发现任何问题,后来我看到了这样一篇文章:

里面说是一个转义字符引起的,我尝试把它去掉,就一切正常了

-canal.instance.filter.regex=hcache\\..*
+canal.instance.filter.regex=hcache\..*

代码

YHaoNan/hierarchy_cache

posted @ 2022-08-28 11:26  yudoge  阅读(510)  评论(0编辑  收藏  举报