博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一次线上小问题的思考
阅读量:6256 次
发布时间:2019-06-22

本文共 3022 字,大约阅读时间需要 10 分钟。

hot3.png

    tn-零售平台事业部-底层研发部-A组    

    今天,技术经理让我处理一个问题:app首页的产品列表,第9个,第11个,两个产品一模一样。

    先讲一下项目的架构:

232049_uxpI_1380557.png

    注:每个箭头都有相反方向的对应箭头,我懒,不画了。

    portal负责app首页、频道页的大部分服务端功能,主要用于聚合信息,cms是中间层,调用搜索,获取产品信息。ror是搜索项目,具体处理请求,通过solr获取数据。portal,cms我都做过一部分,ror没做过,具体细节不清楚,只知道是操作solr的。

    一个请求的处理过程是这样: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。

      可以看出,这是多个微服务组成的系统,各个应用间通过restful方式相互调用。好处是解耦,坏处是重复造轮子,资源利用率不高,前前后后多少个缓存?

        在谈谈我是怎么解决技术经理的问题的。首先,app是分页,每页10条,向portal请求产品列表的,第一页的第9个,第二页的第1个,两个产品完全一样!我看了看m站,没有出现问题,有可能php的缓存把它保护了。再看看cms,先是 cms不走缓存,发现app相同的产品排在第11个(第2页第一个),然后cms走缓存,app相同的产品排在第9个。于是,我大概知道原因了。portal缓存了第1页的老数据,缓存了第2页的新数据。我删了portal的产品列表的缓存,问题解决了。

        我告诉经理解决了,问题的原因是缓存不一致,方案是删缓存。经理让我想想有没有更好的方法。我想了想,将思考写入此文。

        首先,缓存不一致,是因为前前后后有很多缓存,每个缓存的存活时间是1小时。所以会出现缓存不一致的问题,这本质上是分布式一致性的问题。(那就有了分布式锁,分布式一致性)。

        那么,就有两种思路:1,能不能只有一套缓存,portal,cms共用一套缓存。(我只能管到这两个项目,其他的我也不知道,也管不了,也没有权限操作)那就不存在缓存不一致了。2,可以保留多套缓存,但能不能提供  缓存更新通知 缓存同步\异步刷新?

        思路一:

        思路一是简单粗暴的,还节省了缓存服务器,真省钱。前面提到,我们是微服务,各应用间通过restful接口实现调用,这种方式有好处——业务逻辑封装在应用内部,外部调用方不可见(高内聚),A系统出了问题,不会太多的影响B系统(低耦合)。坏处是每次调用需要一次http链接,建立链接是耗时的,还存在网络故障、延时的风险,所以每个应用自己有一套缓存,避免每次都要调接口,提升性能(所以就有了缓存不一致问题,即——分布式一致性问题,关于分布式cap,详见)。那么如何做到只有一套缓存呢?见下图:

002216_IZhD_1380557.png

        如图所示,大概都画清楚了。四个黄条分表代表  过滤器-控制器-业务层-数据层。图中,缓存放在了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,详见。当cms缓存失效、更新时,通过消息机制jms通知portal删除或跟新缓存,这是异步的。如果对一致性要求不极致,这样就可以了,改造成本也小,加个mq就行了(如果要求更低,什么都不用改,等缓存自己失效,现在就是这样的)。如果要求强一致性,就要引入分布式事务,可以基于mysql,redis,或者zookeeper。个人建议基于curator,采用  分布式锁+分布式事务,         

 

转载于:https://my.oschina.net/u/1380557/blog/893648

你可能感兴趣的文章
Andrew Ng 的 Machine Learning 课程学习 (week2) Linear Regression
查看>>
Windows下安装MongoDB
查看>>
MyBatis在insert插入操作时返回主键ID的配置
查看>>
使用eclipse的todo标签管理任务
查看>>
iOS 开发自定义一个提示框
查看>>
CentOS6.5加域
查看>>
互联网+时代,移动互联网安全怎么自我防范呢?
查看>>
图片上传预览功能
查看>>
org.apache.log4j.Logger 详解
查看>>
Tiny Linux -- tce-load
查看>>
Android 中自定义控件和属性(attr.xml,declare-styleable,TypedArray)的方法和使用
查看>>
vue中get和post请求
查看>>
2015-2016 ACM-ICPC, NEERC, Southern Subregional Contest A Email Aliases(模拟STL vector+map)
查看>>
Mr. Frog’s Game
查看>>
3.4可靠数据传输的原理
查看>>
多媒体通信-3-30-2018
查看>>
【Spring Boot&&Spring Cloud系列】Spring Boot中使用数据库之MySql
查看>>
【Spring Boot && Spring Cloud系列】那些Spring Boot中踩过的坑
查看>>
对XX系统的可用性和易用性改良
查看>>
大数据如何解决人工智能对文本挖掘的挑战
查看>>