大型网站架构系列之五,缓存策略设计概要

时间:2022-06-08 21:55:35
上篇对疯狂代码缓存配置进行了概要的设计,可能说的有点模糊了,有几个朋友发了几个问题探讨了下,这里有必要先澄清一个问题,和常见的缓存策略不同,我们的缓存策略将重点放在更新策略而不是只读策略上。只读缓存以及共性缓存策略性质实现的难度并不大,我们要解决的是非共性缓存,并发更新缓存,可扩充性缓存,分布式缓存更新运算的问题,而对于共性的东西的话我们可以很轻松的实现,而不必做太多的运算。
1 G0 C  x4 G4 V9 T; s # Y4 Y( J7 h! G. p7 C
试想一个问题,对于一个多用户的并发的系统,如果对每个用户都维护一份缓存策略还要保证更新的及时性以及处理的必要性来说的话,我们很难想到一个有效的处理机制来维护每份(每用户)缓存的副本的,缓存的存储性质也决定了做分布式缓存策略处理的难度和分布式通讯更新的的难度,我们也很难尝试对于一些访问量很小且少有共性的页面实现有效的缓存命中率,比如某某用户的博客。 % |% @/ }- N, A6 r7 P. L# J# B7 d
, ]! Z; \9 c  P8 I* B) L
简单的总结了一下关于缓存策略讨论的重点
6 Q% R& W/ B' U2 w/ m$ M# _' Q8 bA.     基于海量非共性数据的缓存策略 3 E4 H9 t- ]) _* G3 l5 g8 X
B.      基于数据缓存级别并发更新的缓存策略 # F; O2 B! F1 f5 O  H
C.     基于数据并发存储的缓存策略
, L6 c2 c; [' U6 H6 R* ?% `D.     基于分布式的缓存策略
9 }/ z1 H+ X2 [2 C' cE.基于搜索的缓存策略
" L4 D+ x0 g) E6 m0 `% [; H
! _, \0 N: Q+ ^0 [我们这里不再赘谈关于页面静态化以及类似的问题,静态化的情况非常适合在系统初期,用户的基数并不算很大的情况下实现,而在涉及集群的情况下,静态化的实现成本,IO成本,维护成本,扩充成本以及更新成本会远远的超出缓存策略的成本,当然我们也会有一套建立在缓存基础上的静态化处理方案,这些放在以后再谈。我们的目的是要建立一个可伸缩,便于维护扩展的缓存策略,下面就具体问题进行分析。
1 G( v" y* w% E, C7 |2 k' U 2 y: m1 @, }# G6 n) v0 j" k2 S8 f/ q
对于问题A:
5 c4 j0 ~& \- n5 O# Y- D- o) v* L常见的博客系统就是一个最好的例子,每个用户的首页都是相对个性的数据,共性的地方不多,以常见的处理方案来说的话,我们可能需要维护每个用户访问的缓存副本,而对于一些访问量极小的博客站点来说的话这种方式无疑会造成巨大的浪费。
1 ^6 ^3 d& U7 U* P. v% r " {% u, ]$ m5 A
对于大量非共性的数据缓存来说,几个处理方案: 1 [: q7 N+ q) Y, y6 F! p
1)      量化缓存目标并分配相应的缓存权值。(权值分级)   Q, q; u: e) K
目的很简单,只缓存有效的数据。首先抽取活跃用户,以及高访问量用户,将数据进行分组分权制缓存(对于交友型的SNS系统来说,我们称之为美女效应) ! W4 z) t7 S8 ^2 \
2)      非连接持久性的缓存保持(临时的持久性) # n- ^8 b; y& b7 o: H7 h6 V8 M
珍惜并有效利用数据查询,将未被缓存命中时的查询或者无权值的数据持久化保存(序列化存贮静态存贮等),当缓存未被命中时优先取得持久化数据而非数据查询。可以理解为临时数据存贮,或者临时存贮于子服务器的某个位置。
! c( Q7 a0 w0 C3)  基于数据更新的缓存清除(一次性使用)   e% V3 L3 K7 v
当持久性缓存保持失效(依赖数据发生修改),直接删除临时数据(缓存只在访问时被激活并储存,一旦修改或者失效,我们立刻抛弃)。
5 q/ m! P" X4 v1 L* y4)缓存更新代理规则
! {* e) A) Z, |由另外的线程进行维护,并维护线程的有效性,最大限度的分离主程序对无效缓存以及临时持久性缓存数据的清理 - i; U8 H6 Q  [, R4 ~; N" \

& Z! G  Z9 }6 x: ^) H
/ Z6 ~1 I# j) Z2 m( }对于问题B: # V1 ^0 W3 K- I4 N( V; v
在小型缓存策略中,缓存处理对于整个应用程序对于每个请求来说都是唯一的,可操作的和非物理存储的。而在并发更新的过程中,一个小小的并发更新就会很现实的清空所有的缓存池,造成缓存命中率奇低而初始化率奇高而起不到缓存策略应有的作用。
0 u; ~% R: p' h  A
3 P! y. x8 j. W8 f0 v! F# L 在这种情况下,处理方案也和A.4中提到的方案是一样的,由独立的缓存更新进程来处理,对于应用程序中所有涉及缓存更新的请求由专门的更新代理来执行。这个处理方案相对简单,不再赘述。 : d& ~3 l/ f- v- v

8 `- n6 g; N0 q$ ^7 I, _2 D对于问题C: / T+ r9 Y" E' r, Y* t
上篇已经提到关于并发数据更新会带来的问题也就是数据库的I/O响应,超时,死锁,以及线程的阻塞问题。我们用一个写入缓存来处理这个方案,其实这个并非传统意义上的读缓存,姑且命名为写缓存吧,我们可以形象的理解为类似硬盘缓冲区的问题。这里处理的操作稍微有点多了,还要涉及只读缓存的更新的问题了。
' j9 a& ?& B, w5 f' C) h) I ) o9 k! W& j+ G
根据系统的不同,我们需要分析处理的角度也不同,我们以常见的webgame为例来简单介绍一下处理机制,这里有两种常见的情况 - Z3 b: ^. z/ d$ J
1)对于webgame的最终用户玩家来说,每个在线用户的数据是非共性的(问题A),而在一个战斗场景下,每组数据时刻都在变化之中,如果我们对数据的变化采用数据库日志记录的形式保存的情况显然对Database的压力很大,而我们需要记录的仅仅是战斗的结果,战斗的过程我们完全没有必要进行保存,这个时候我们就用写入缓存来执行相应的数据操作。这个处理很简单,用服务器变量的形式就能解决他。 ; D0 {/ i  S# z' t$ T& l
2)对于webgame的服务器角色来说,如果战斗场景的用户量非常多,而数据更新非常大的情况下,我们采用方法1中的处理也可能力不从心,这个时候我们可以将缓存来进一步的抽象,在某个时间段内(比如3分钟),维护一个唯一的缓存对象,所有的数据操作都在这个时间段来被缓存进程来记录,来更新。而由另外的一个进程来进行异步的定时的数据保存操作。
1 q% u( k1 M% k) U8 ]+ U- E
9 {' p+ l& j% E' t# f8 k8 f对于问题D
1 v5 @, u3 q& H! a) ~        这个是比较常见的分布式缓存服务器组了,而对缓存服务器来说其实要解决的问题就是服务器间之间互相通讯的问题,并保证数据一致性的问题。那么我们的有四个处理规则: & d- }4 q& Q& D
1) 数据缓存应该被有效的分组并索引
1 p2 D- m2 C9 ]' \目标是实现数据耦合的成都降到最低,甚至没有耦合。比如以用户ID为分割的数据缓存分布,或者以文章分类为分割的缓存分布
* e! w: H# F$ r% F# f6 U( `2) 数据缓存应该被有效的更新
5 V, h1 b3 j) F0 {, U: S) \如果数据被有效的分组完成后,这个就是问题C.2的方案了,和C.2不同的是,因为缓存组可能未必在一组服务器中,可能涉及缓存和DATABASE数据通讯延迟的问题。这个时候要保证缓存服务器被即时的传递到databse,那么需要另外的一个缓存检测进程来完成这项工作(数据完整性检查,并备份两个缓存段的数据)
$ q/ I' w+ H# w3) 缓存服务器间的数据完整性 ) {6 k( _% P+ G# N- w/ z( m
对于无法分组的数据,比如时间段内的用户认证数据和资料数据,我们需要保证两组数据同步,最好的处理方法就是清除相应的缓存段,让它在下次使用的时候初始化 / Y! k- \* a9 ]- w
4) 缓存服务器间的连通性
& a1 O& {; n9 K5 |4 w这个取决于物理线路,如果缓存服务器在天南地北的话,我们还需要一个队列进程来进行同步和数据矫正,我们称之为缓存路由。
# H3 r7 w6 q6 s9 Y5 f! F# j5 F! I
2 ^6 F8 H2 y# A! w$ Q; m 对于问题E
" w* ]$ z9 {! [; s  T' F: q' q在分布式缓存的情况下,多条件搜索往往涉及多个缓存服务器,处理起来笔者尚未有一套完善的出来方案。笔者用的是敷衍原则和集成原则了
* f' w! X! w& n0 J敷衍原则: % C' d! {2 F- I$ n+ T- ^$ w
对于搜索型的数据来说,很多情况下并不是非常重要,我们的搜索结果完全可以晚一会提供给用户,允许搜索的数据有10分钟或者更长时间的延迟。 ( D9 W2 [; K/ G. y
集成原则
+ a% Z; f) l' V' c. q, n0 v. C将搜索字段和表整合出来,用独立的只读查询服务器来分担负荷

转自--疯狂代码