设为首页 加入收藏

TOP

分布式缓存架构设计(二)
2019-09-17 17:44:54 】 浏览:71
Tags:分布式 架构 设计
一些列表数据,并对这些数据进行排序和分页。

例如我们想获取点赞最多的评论,或者最新的评论,然后列出来,一页一页的翻下去。

在这种情况下,缓存里面的数据结构和数据库里面完全不一样。

如果完全使用数据库进行实现,则按照某种条件将所有的行查询出来,然后按照某个字段进行排序,然后进行分页,一页一页的展示。

但是当数据量比较大的时候,这种方式往往成为瓶颈,首先涉及的数据库行数比较多,而且排序也是个很慢的活,尽管可能有索引,分页也是翻页到最后,越是慢。

在缓存里面,就没必要每行一个key了,而是可以使用Redis的列表方式进行存储,当然列表的长短是有限制的,肯定放不下数据库里面这么多,但是大家会发现其实对于所有的列表,用户往往没有耐心看个十页八页的,例如百度上搜个东西,也是有排序和分页的,但是你每次都往后翻了吗,每页就十条,就算是十页,或者一百页,也就一千条数据,如果保持ID的话,完全放的下。

如果已经排好序,放在Redis里面,那取出列表,翻页就非常快了。

可以后台有一个线程,异步的初始化和刷新缓存,在缓存里面保存一个时间戳,当有更新的时候,刷新时间戳,异步任务发现时间戳改变了,就刷新缓存。

场景3:计数缓存
计数对于数据库来讲,是一个非常繁重的工作,需要查询大量的行,最后得出计数的结论,当数据改变的时候,需要重新刷一遍,非常影响性能。

因此可以有一个计数服务,后端是一个缓存,将计数作为结果放在缓存里面,当数据有改变的时候,调用计数服务增加或者减少计数,而非通过异步数据库count来更新缓存。

计数服务可以使用Redis进行单个计数,或者hash表进行批量计数

场景4:重构维度缓存
有时候数据库里面保持的数据的维度是为了写入方便,而非为了查询方便的,然而同时查询过程,也需要处理高并发,因而需要为了查询方便,将数据重新以另一个维度存储一遍,或者说将多给数据库的内容聚合一下,再存储一遍,从而不用每次查询的时候都重新聚合,如果还是放在数据库,比较难维护,放在缓存就好一些。

例如一个商品的所有的帖子和帖子的用户,以及一个用户发表过的所有的帖子就是属于两个维度。

这需要写入一个维度的时候,同时异步通知,更新缓存中的另一个维度。

在这种场景下,数据量相对比较大,因而单纯用内存缓存memcached或者redis难以支撑,往往会选择使用levelDB进行存储,如果levelDB的性能跟不上,可以考虑在levelDB之前,再来一层memcached。

场景5:较大的详情内容数据缓存
对于评论的详情,或者帖子的详细内容,属于非结构化的,而且内容比较大,因而使用memcached比较好。

五、缓存三大矛盾问题

1、缓存实时性和一致性问题:当有了写入后咋办?

虽然使用了缓存,大家心里都有一个预期,就是实时性和一致性得不到完全的保证,毕竟数据保存了多份,数据库一份,缓存中一份,当数据库中因写入而产生了新的数据,往往缓存是不会和数据库操作放在一个事务里面的,如何将新的数据更新到缓存里面,什么时候更新到缓存里面,不同的策略不一样。

从用户体验角度,当然是越实时越好,用户体验越流畅,完全从这个角度出发,就应该有了写入,马上废弃缓存,触发一次数据库的读取,从而更新缓存。但是这和第三个问题,高并发就矛盾了,如果所有的都实时从数据库里面读取,高并发场景下,数据库往往受不了。

2、缓存的穿透问题:当没有读到咋办?

为什么会出现缓存读取不到的情况呢?

第一:可能读取的是冷数据,原来从来没有访问过,所以需要到数据库里面查询一下,然后放入缓存,再返回给客户。

第二:可能数据因为有了写入,被实时的从缓存中删除了,就如第一个问题中描述的那样,为了保证实时性,当数据库中的数据更新了之后,马上删除缓存中的数据,导致这个时候的读取读不到,需要到数据库里面查询后,放入缓存,再返回给客户。

第三:可能是缓存实效了,每个缓存数据都会有实效时间,过了一段时间没有被访问,就会失效,这个时候数据就访问不到了,需要访问数据库后,再放入缓存。

第四:数据被换出,由于缓存内存是有限的,当使用快满了的时候,就会使用类似LRU策略,将不经常使用的数据换出,所以也要访问数据库。

第五:后端确实也没有,应用访问缓存没有,于是查询数据库,结果数据库里面也没有,只好返回客户为空,但是尴尬的是,每次出现这种情况的时候,都会面临着一次数据库的访问,纯属浪费资源,常用的方法是,讲这个key对应的结果为空的事实也进行缓存,这样缓存可以命中,但是命中后告诉客户端没有,减少了数据库的压力。

无论哪种原因导致的读取缓存读不到的情况,该怎么办?是个策略问题。

一种是同步访问数据库后,放入缓存,再返回给客户,这样实时性最好,但是给数据库的压力也最大。

另一种方式就是异步的访问数据库,暂且返回客户一个fallback值,然后同时触发一个异步更新,这样下次就有了,这样数据库压力小很多,但是用户就访问不到实时的数据了。

3、缓存对数据库高并发访问:都来访问数据库咋办?
我们本来使用缓存,是来拦截直接访问数据库请求的,从而保证数据库大本营永远处于健康的状态。但是如果一遇到不命中,就访问数据库的话,平时没有什么问题,但是大促情况下,数据库是受不了的。

一种情况是多个客户端,并发状态下,都不命中了,于是并发的都来访问数据库,其实只需要访问一次就好,这种情况可以通过加锁,只有一个到后端来实现。

另外就是即便采取了上述的策略,依然并发量非常大,后端的数据库依然受不了,则需要通过降低实时性,将缓存拦在数据库前面,暂且撑住,来解决。

六、解决缓存三大矛盾的刷新策略

1、实时策略
所谓的实时策略,是平时缓存使用的最常用的策略,也是保持实时性最好的策略。

读取的过程,应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。如果命中,应用程序从cache中取数据,取到后返回。

写入的过程,把数据存到数据库中,成功后,再让缓存失效,失效后下次读取的时候,会被写入缓存。那为什么不直接写缓存呢?因为如果两个线程同时更新数据库,一个将数据库改为10,一个将数据库改为20,数据库有自己的事务机制,可以保证如果20是后提交的,数据库里面改为20,但是回过头来写入缓存的时候就没有事务了,如果改为20的线程先更新缓存,改为10的线程后更新缓存,于是就会长时间出现缓存中是10,但是数据库中是20的现象。

这种方式实时性好,用户体验好,是默认应该使用的策略。

2、异步策略
所谓异步策略,就是当读取的时候读不到的时候,不直接访问数据库,而是返回一个fallback数据,然后往消息队列里面放入一个数据加载的事件,在背后有一个任务,收到事件后,会异步的读取数据库,由于有队列的作用,可以实现消峰,缓冲对数据库的访问,甚至可以将多个队列中的任务合并请求,合并更新缓存,提高了效率。

当更新的时候,异步策略总是先更新数据库和缓存中的一个,然后异步的更新另一个。

一是先更新数据库,然后异步更新缓存。当数据库更新后,同样生成一个异步消息,放入消息队列中,等待背后的任务通过消息进行缓存更新,同样可以实现消峰和任务合并。缺点就是实时性比较差,估计要过一段时间才能看到更新,好处是数据持久性可以得到保证。

一是先更新缓存,然后异步更新数据库。这种方式读取和写入都用缓存,将缓存完全挡在了数据库的前面,把缓存当成了数据库在用。所以一般会使用
首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇使用Consul做服务发现的若干姿势 下一篇dubbo+zipkin调用链监控(二)

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目