tn-零售平台事业部-底层研发部-A组 php
今天,技术经理让我处理一个问题:app首页的产品列表,第9个,第11个,两个产品如出一辙。html
先讲一下项目的架构:mysql
注:每一个箭头都有相反方向的对应箭头,我懒,不画了。redis
portal负责app首页、频道页的大部分服务端功能,主要用于聚合信息,cms是中间层,调用搜索,获取产品信息。ror是搜索项目,具体处理请求,经过solr获取数据。portal,cms我都作过一部分,ror没作过,具体细节不清楚,只知道是操做solr的。sql
一个请求的处理过程是这样:APP send a request to portal, and portal query data from cache(redis) firstly, if get something from cache, send response to app, if not, send request to cms, then get data from cms. When cms get a request from portal, it query data from cache, if get nothing, send a request to ror and get data from it. m站有本身的服务端,是php的。php也是先查缓存, 再查portal。apache
能够看出,这是多个微服务组成的系统,各个应用间经过restful方式相互调用。好处是解耦,坏处是重复造轮子,资源利用率不高,前先后后多少个缓存?缓存
在谈谈我是怎么解决技术经理的问题的。首先,app是分页,每页10条,向portal请求产品列表的,第一页的第9个,第二页的第1个,两个产品彻底同样!我看了看m站,没有出现问题,有可能php的缓存把它保护了。再看看cms,先是 cms不走缓存,发现app相同的产品排在第11个(第2页第一个),而后cms走缓存,app相同的产品排在第9个。因而,我大概知道缘由了。portal缓存了第1页的老数据,缓存了第2页的新数据。我删了portal的产品列表的缓存,问题解决了。服务器
我告诉经理解决了,问题的缘由是缓存不一致,方案是删缓存。经理让我想一想有没有更好的方法。我想了想,将思考写入此文。restful
首先,缓存不一致,是由于前先后后有不少缓存,每一个缓存的存活时间是1小时。因此会出现缓存不一致的问题,这本质上是分布式一致性的问题。(那就有了分布式锁,分布式一致性)。网络
那么,就有两种思路:1,能不能只有一套缓存,portal,cms共用一套缓存。(我只能管到这两个项目,其余的我也不知道,也管不了,也没有权限操做)那就不存在缓存不一致了。2,能够保留多套缓存,但能不能提供 缓存更新通知 缓存同步\异步刷新?
思路一:
思路一是简单粗暴的,还节省了缓存服务器,真省钱。前面提到,咱们是微服务,各应用间经过restful接口实现调用,这种方式有好处——业务逻辑封装在应用内部,外部调用方不可见(高内聚),A系统出了问题,不会太多的影响B系统(低耦合)。坏处是每次调用须要一次http连接,创建连接是耗时的,还存在网络故障、延时的风险,因此每一个应用本身有一套缓存,避免每次都要调接口,提高性能(因此就有了缓存不一致问题,即——分布式一致性问题,关于分布式cap,详见http://www.cnblogs.com/bangerlee/p/5328888.html)。那么如何作到只有一套缓存呢?见下图:
如图所示,大概都画清楚了。四个黄条分表表明 过滤器-控制器-业务层-数据层。图中,缓存放在了dao以后,其实,也能够放在filter-controller之间,controller-business之间,business-dao之间。
先讲dao以后。他要保证的核心,是确保:只有cms能往缓存写数据,portal,以及其余任何应用,都没有写权限!你能想象,cms刚写完,portal就把它改了,删了吗?另外,因为如今用的是redis,是单线程操做的,不用过多考虑并发场景下的异常状况,memcache等多线程操做的cache就复杂了。除了保证,缓存只有cms能写,还要考虑portal从缓存没有读到数据的情形。是缓存过时了?仍是缓存里原本就不应存在此次请求对应的数据?针对这个问题,有两种思路:
1 没有获取到缓存时,直接调用cms的restful接口,cms自己不读缓存(portal就没读到,cms就不必读了),而是直接从后台获取数据,若是有,返回给portal,能够在返回给portal以前,同步/异步写到缓存里。若是并发量大,可使用redis setnx语句,避免屡次写同一个key,也能够将写任务提交到任务队列,提升性能。若是cms没有从后台获取到数据,最好返回一个无害的默认值(降级),并设置缓存,记录请求的上下文到日志,便于往后分析。为何呢?防止缓存击穿。假设有这样一个流程:恶意用户用一个恶意入参请求portal,portal从缓存没有取到数据,调用cms,cms调用ror,也没取到,也没设一个默认值到缓存,就返回了。这样,恶意用户重复这个流程100万次,portal会取100万次缓存,调100万次cms,调100万次ror,ror调100万次solr,这会产生灾难性的后果!若是在缓存设个默认值,时间不用长,5分钟,30分钟,避免缓存击穿形成的严重后果。(时间不能长,是由于可能存入大量无害的默认信息,失效时间长了,影响缓存的容量以及性能。另外,恶意用户短期内发现恶意请求不起破坏性效果,就极有可能换个恶意请求了)。固然,也能够采用防刷、限流的方式,熔断、降级,甚至封号,加黑名单。。。
2 portal始终只从缓存读,没读到就直接返回null或无害的默认值。这就要cms保证缓存的完整性、及时性、有效性。cms必须将全量的数据存入缓存(只有这样,才知足portal只从缓存取数据的基本条件)。缓存时间能够为永久,或者短期缓存。若是缓存时间短,如1h,则能够作个定时任务,如半小时,定时更新全量的缓存,要保证半小时更新完全部缓存,这只能尽可能保证数据的及时性,有效性。若是缓存永久,必须有一个通讯机制,让cms知道要更新哪些数据,如mq等。另外,更新缓存要考虑数据备份,防止由于异常,致使缓存出问题。
缓存也可放在filter-controller之间,这里离用户最近,性能也最好。但这就至关于把business层处理完的数据存入了cache,这违反了只有cms有写权限的原则!解决方法是将portal,cms两个项目合并。但这就不是微服务了。缓存放在其余地方也是同理。
以上是一套缓存的分析,下面分析多套缓存的方案。
多套缓存,即保持现有架构不变。那就要解决多个缓存之间的不一致问题——本质上是分布式一致性的问题。关于分布式cap,详见http://www.cnblogs.com/bangerlee/p/5328888.html。当cms缓存失效、更新时,经过消息机制jms通知portal删除或跟新缓存,这是异步的。若是对一致性要求不极致,这样就能够了,改形成本也小,加个mq就好了(若是要求更低,什么都不用改,等缓存本身失效,如今就是这样的)。若是要求强一致性,就要引入分布式事务,能够基于mysql,redis,或者zookeeper。我的建议基于curator,采用 分布式锁+分布式事务, http://ifeve.com/zookeeper-leader/ http://curator.apache.org/ http://zookeeper.apache.org