nginx+redis+tomcat三级缓存架构讲解

来源:互联网 发布:社会主义生产方式知乎 编辑:程序博客网 时间:2024/06/07 21:20

对于一个大型的数据缓存系统,会部署多层缓存服务来达到高并发、高可用的系统需求

  • nginx层
    对于传统的缓存系统,请求到达nginx,然后分发到对应的服务系统,然后查询redis中是否有数据,然后将结果返回,这里会花费很大的网络开销。nginx可以支持热数据的缓存,对这些数据直接缓存在nginx内存中,请求到来直接返回,不需要去发送网络请求。但是nginx内存的大小一般会很小,所以适合缓存热数据

  • redis cluster层
    这一层的数据是最完整的,大部分的离散请求都会访问到这一层。因此一定要做到高可用,可水平伸缩的模式

  • tomcat层
    tomcat中的堆也可以缓存一定的数据,但是容量也不会太大。主要是用来防止redis层大面积崩溃之后,大量数据请求直接到DB层


对于数据不同的实时性要求,我们可以采取不同的方式来修改缓存

对于实时性较高的数据(例如库存数据):

对于这种实时性很高的数据,当有修改的时候,一般是修改DB然后同时去修改redis缓存的双写模式。但是这里会设计到数据不一致的问题。

  • 最初级不一致问题及解决方案
    对于先修改数据库,再删除缓存,如果缓存删除失败,那么回导致数据库中是最新数据,缓存中是旧数据,出现不一致的情况。
    解决方法:先删除缓存,再修改数据库
  • 比较复杂的数据不一致问题
    对于高并发的请求,读写操作可能会产生不一致的问题,在写的过程中,另一个读操作重新读取到来旧数据。当然对于并发不高的系统,这种情况也基本不会遇到。
    解决方案如下:

数据库、缓存更新与读取操作进行异步串行化

将需要更新的数据,发送到jvm内部的队列中,一般会维护多个内存队列,对数据的唯一标识hash然后对内存队列数量取模,就可以保证同样的数据操作一定会在同一个队列中去。
每个队列有一个工作线程,每个工作线程串行拿到对应的操作,然后逐条执行。因此一个数据的变更,会先删除缓存,然后再去更新数据库,当还没有更新完成的时候,另一个读请求过来来,读到空的缓存,那么可以将这个缓存更新的请求发送到队列中,从而会同步等待缓存更新完成。需要注意的一点,对于缓存为空,多个读请求过来,将多个更新缓存的请求发送到队列是没有必要的,因此需要做过滤,然后可以将这些读请求hang住一个时间段,轮询去查询缓存,当前面队列中的更新缓存请求执行完成后,就可以拿到缓存数据直接返回,否则,可以让服务直接取查询DB来拿数据。

在高并发下,需要注意:

  • 对于缓存中没有该数据,也可能是数据库本身就没有。因此需要判断队列中是否有更新该数据的操作,如果没有则读请求没有必要hang住,直接返回空即可
  • 需要线上模拟压力测试,当内存队列积压过多的更新数据操作,会对最后的读操作产生于一定的延时,此时如果需要优化就需要加机器,分配到更平均的队列数量中

基于zookeeper分布式锁解决多服务缓存重建并发冲突:

对于多个缓存服务,可能存在这种情况,nginx请求到达发现都没有相应缓存数据,会发送请求给缓存服务到数据源拉取数据并写入相应tomcat/redis,同时kafka生产了消息的变更记录也需要去拉取数据并存入tomcat/redis。两者拉取数据源与缓存重建可能会出现并发冲突导致tomcat/redis中的最终数据并不是最新。


对于实时性不高的数据

对于实时性不高的数据,如果发生了变更,几分钟之后才更新到页面上,我们采取异步更新缓存的策略。缓存数据生产服务,监听一个消息队列,然后数据源服务(商品信息管理服务)发生来变更之后,就将数据变更的消息推送到消息队列(topic),缓存数据生产服务可以去消费这些变更的信息,然后根据消息的提示提取一些参数,然后调用对应的数据源服务接口拉取数据,一般是从mysql拉取,然后将数据存放到本地堆缓存和redis缓存


对于nginx层的缓存

一般来说,默认会部署多个nginx,在里面都会放一些缓存,此时缓存的命中率会非常底,因为对同样数据的请求可能被路由到多个nginx从而未能找到相应缓存而多次对redis发起请求。因此,可以采用分发层+应用层双层ngin来提升缓存命中率。

  • 分发层nginx:负责流量分发的逻辑和策略,根据自己定义的一些规则,比如根据productId进行hash,然后对后端nginx数量取模将某一个商品的访问请求固定路由到一个nginx后端服务器上去,保证了nginx只会对该商品数据从redis获取一次。之后的同样商品请求都直接走nginx缓存。
  • 后端nginx: 称之为应用服务器
阅读全文
0 1
原创粉丝点击