对于高并发架构,毫无疑问缓存是最重要的一环,对于大量的高并发,可以采使用三层缓存架构来实现nginx+redis+ehcache。
Nginx

对于中间件nginx常使用来做流量的分发,同时nginx本身也有自己的缓存(容量有限),我们可以使用来缓存热点数据,让使用户的请求直接走缓存并返回,减少流向服务器的流量。
1.模板引擎
通常我们可以配合用freemaker/velocity等模板引擎来抗住大量的请求:
2.双层nginx来提升缓存命中率
对于部署多个nginx而言,假如不加入少量数据的路由策略,那么可能导致每个nginx的缓存命中率很低。因而可以部署双层nginx:
Redis
使用户的请求,在nginx没有缓存相应的数据,那么会进入到redis缓存中,redis可以做到全量数据的缓存,通过水平扩展能够提升并发、高可使用的能力。
1.持久化机制
将redis内存中的数据持久化到磁盘中,而后可以定期将磁盘文件上传至S3(AWS)或者者ODPS(阿里云)等少量云存储服务上去。
假如同时用RDB和AOF两种持久化机制,那么在redis重启的时候,会用AOF来重新构建数据,由于AOF中的数据更加完整,建议将两种持久化机制都开启,使用AO F来保证数据不丢失,作为数据恢复的第一选择;使用RDB来作不同程度的冷备,在AOF文件都丢失或者损坏不可使用的时候来快速进行数据的恢复。
实战踩坑:对于想从RDB恢复数据,同时AOF开关也是打开的,一直无法正常恢复,由于每次都会优先从AOF获取数据(假如临时关闭AOF,即可以正常恢复)。此时首先中止redis,而后关闭AOF,拷贝RDB到相应目录,启动redis之后热修改配置参数redis config set appendonly yes,此时会自动生成一个当前内存数据的AOF文件,而后再次中止redis,打开AOF配置,再次启动数据就正常启动。
2.redis集群
ehcache
tomcat jvm堆内存缓存,主要是抗redis出现大规模灾难。假如redis出现了大规模的宕机,导致nginx大量流量直接涌入数据生产服务,那么最后的tomcat堆内存缓存也可以解决部分请求,避免所有请求都直接流向DB
缓存数据升级策略
对时效性要求高的缓存数据,当发生变更的时候,直接采取数据库和redis缓存双写的方案,让缓存时效性最高。
对时效性不高的数据,当发生变更之后,采取MQ异步通知的方式,通过数据生产服务来监听MQ消息,而后异步去拉取服务的数据升级tomcat jvm缓存和redis缓存,对于nginx本地缓存过期之后即可以从redis中拉取新的数据并升级到nginx本地。
经典的缓存+数据库读写的模式
cache aside pattern
数据库和redis缓存双写不一致的问题
1.最初级的缓存不一致问题以及处理方案
问题:
假如先修改数据库再删除缓存,那么当缓存删除失败来,那么会导致数据库中是最新数据,缓存中仍旧是旧数据,造成数据不一致。
处理方案:
可以先删除缓存,再修改数据库,假如删除缓存成功但是数据库修改失败,那么数据库中是旧数据,缓存是空不会出现不一致。
2.比较复杂的数据不一致问题分析
问题:
问题:对于数据发生来变更,先删除缓存,而后去修改数据库,此时数据库中的数据还没有修改成功,并发的读请求到来去读缓存发现是空,进而去数据库查询到此时的旧数据放到缓存中,而后之前对数据库数据的修改成功来,就会造成数据不一致。
处理方案:
将数据库与缓存升级与读取操作进行异步串行化。当升级数据的时候,根据数据的唯一标识,将升级数据操作路由到一个jvm内部的队列中,一个队列对应一个工作线程,线程串行拿到队列中的操作一条一条地执行。当执行队列中的升级数据操作,删除缓存,而后去升级数据库,此时还没有完成升级的时候过来一个读请求,读到了空的缓存那么可以先将缓存升级的请求发送至路由之后的队列中,此时会在队列积压,而后同步等待缓存升级完成,一个队列中多个相同数据缓存升级请求串在一起是没有意义的,因而可以做过滤解决。等待前面的升级数据操作完成数据库操作之后,才会去执行下一个缓存升级的操作,此时会从数据库中读取最新的数据,而后写入缓存中,假如请求还在等待时间范围内,不断轮询发现可以取到缓存中值即可以直接返回(此时可能会有对这个缓存数据的多个请求正在这样解决);假如请求等待事件超过肯定时长,那么这一次的请求直接读取数据库中的旧值
对于这种解决方式需要注意少量问题:
1.读请求长时阻塞
因为读请求进行来非常轻度的异步化,所以对超时的问题需要格外注意,超过超时时间会直接查询DB,解决不好会对DB造成压力,因而需要测试系统高峰期QPS来调整机器数以及对应机器上的队列数最终决定正当的请求等待超时时间
2.多实例部署的请求路由
可能这个服务会部署多个实例,那么必需保证对应的请求都通过nginx服务器路由到相同的服务实例上
3.热点数据的路由导师请求的倾斜
由于只有在商品数据升级的时候才会清空缓存,而后才会导致读写并发,所以升级频率不是太高的话,这个问题的影响并不是特别大,但是确实可能某些机器的负载会高少量
分布式缓存重建并发冲突处理方案
对于缓存生产服务,可能部署在多台机器,当redis和ehcache对应的缓存数据都过期不存在时,此时可能nginx过来的请求和kafka监听的请求同时到达,导致两者最终都去拉取数据并且存入redis中,因而可能产生并发冲突的问题,可以采使用redis或者者zookeeper相似的分布式锁来处理,让请求的被动缓存重建与监听主动的缓存重建操作避免并发的冲突,当存入缓存的时候通过比照时间字段废弃掉旧的数据,保存最新的数据到缓存
缓存冷启动以及缓存预热处理方案
当系统第一次启动,大量请求涌入,此时的缓存为空,可能会导致DB崩溃,进而让系统不可使用,同样当redis所有缓存数据异常丢失,也会导致该问题。因而,可以提前放入数据到redis避免上述冷启动的问题,当然也不可能是全量数据,可以根据相似于当天的具体访问情况,实时统计出访问频率较高的热数据,这里热数据也比较多,需要多个服务并行的分布式去读写到redis中(所以要基于zk分布式锁)。
通过nginx+lua将访问流量上报至kafka中,storm从kafka中消费数据,实时统计处每个商品的访问次数,访问次数基于LRU(apache commons collections LRUMap)内存数据结构的存储方案,用LRUMap去存放是由于内存中的性能高,没有外部依赖,每个storm task启动的时候基于zk分布式锁将自己的id写入zk同一个节点中,每个storm task负责完成自己这里的热数据的统计,每隔一段时间就遍历一下这个map,而后维护一个前1000的数据list,而后去升级这个list,最后开启一个后端线程,每隔一段时间比方一分钟都将排名的前1000的热数据list同步到zk中去,存储到这个storm task对应的一个znode中去。
部署多个实例的服务,每次启动的时候就会去拿到上述维护的storm task id列表的节点数据,而后根据taskid,一个一个去尝试获取taskid对应的znode的zk分布式锁,假如能够获取到分布式锁,再去获取taskid status的锁进而查询预热状态,假如没有被预热过,那么就将这个taskid对应的热数据list取出来,从而从DB中查询出来写入缓存中,假如taskid分布式锁获取失败,快速抛错进行下一次循环获取下一个taskid的分布式锁就可,此时就是多个服务实例基于zk分布式锁做协调并行的进行缓存的预热。
缓存热点导致系统不可使用处理方案
对于瞬间大量的相同数据的请求涌入,可能导致该数据经过hash策略之后对应的应使用层nginx被压垮,假如请求继续就会影响至其余的nginx,最终导致所有nginx出现异常整个系统变得不可使用。
基于nginx+lua+storm的热点缓存的流量分发策略自动降级来处理上述问题的出现,可以设定访问次数大于后95%平均值n倍的数据为热点,在storm中直接发送http请求到流量分发的nginx上去,使其存入本地缓存,而后storm还会将热点对应的完整缓存数据没发送到所有的应使用nginx服务器上去,并直接存放到本地缓存。对于流量分发nginx,访问对应的数据,假如发现是热点标识就立即做流量分发策略的降级,对同一个数据的访问从hash到一台应使用层nginx降级成为分发至所有的应使用层nginx。storm需要保存上一次识别出来的热点List,并同当前计算出来的热点list做比照,假如已经不是热点数据,则发送对应的http请求至流量分发nginx中来取消对应数据的热点标识
缓存雪崩处理方案
redis集群彻底崩溃,缓存服务大量对redis的请求等待,占使用资源,随后缓存服务大量的请求进入源头服务去查询DB,使DB压力过大崩溃,此时对源头服务的请求也大量等待占使用资源,缓存服务大量的资源一律耗费在访问redis和源服务无果,最后使自身无法提供服务,最终会导致整个网站崩溃。
事先处理方案:
搭建一套高可使用架构的redis cluster集群,主从架构、一主多从,一旦主节点宕机,从节点自动跟上,并且最好用双机房部署集群。
事中处理方案:
部署一层ehcache缓存,在redis一律实现情况下能够抗住部分压力;对redis cluster的访问做资源隔离,避免所有资源都等待,对redis cluster的访问失败时的情况去部署对应的熔断策略,部署redis cluster的降级策略;对源服务访问的限流以及资源隔离。
事后处理方案:
redis数据做了备份可以直接恢复,重启redis就可;redis数据彻底失败来或者者数据过旧,可以快速缓存预热,而后让redis重新启动。而后因为资源隔离的half-open策略发现redis已经能够正常访问,那么所有的请求将自动恢复。
缓存穿透处理方案
对于在多级缓存中都没有对应的数据,并且DB也没有查询到数据,此时大量的请求都会直接到达DB,导致DB承载高并发的问题。处理缓存穿透的问题可以对DB也没有的数据返回一个空标识的数据,进而保存到各级缓存中,由于有对数据修改的异步监听,所以当数据有升级,新的数据会被升级到缓存汇中。
nginx缓存失效导致redis压力倍增
可以在nginx本地,设置缓存数据的时候随机缓存的有效期,避免同一时刻缓存都失效而大量请求直接进入redis
这个过程值得我们去深入学习和思考。假如对你有帮助请动动小手关注下吧!
最后:大家可以关注我私信我:“资料”就可领取Java架构,Dubbo、Redis 、Netty、zookeeper Spring cloud、分布式、高并发、性能调优、微服务 等架构技术的视频资料。