Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all articles
Browse latest Browse all 11804

如何实现缓存系统的更新机制

$
0
0

问题是这样的:假设要你设计一个缓存系统,你如何实现缓存的更新机制!

例如某网站具有高并发访问量,为提高性能,其中的一些网页页面都是存放在缓存中的,当需要更新缓存中的页面时,缓存系统如何完成缓存的更新?

给出解决方案之前,先看看内存数据库Redis中字典的设计。

Redis 中的字典结构表示如下:

typedef struct dict {
    // 类型特定函数
    dictType *type;
    // 私有数据
    void *privdata;
    // 哈希表
    dictht ht[2];
    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */
} dict;

我们暂时不管其dict结构体中其他的成员,只关注其中的哈希表成员:

dictht ht[2];

Redis的字典底层是用hash表来实现的, 一个哈希表里面可以有多个哈希表节点, 而每个哈希表节点就保存了字典中的一个键值对,但是,但是有趣的是这里使用了两个hash表。

为什么使用两个hash表呢?

因为在真实使用场景下,我们需要扩展或者收缩hash表的规模。出于节约内存的考虑,我们不可能一次性分配一个巨大巨大的hash表,因此,只在初始时分配一个合适大小的hash表,在使用过程中,当发现碰撞率过高时,我们就需要扩大hash表的规模。同样是基于节约内存的考虑,当hash表的填充率很低时,我们可以适当的收缩hash表的规模。

使用两个hash表是为了方便进行hash表的扩展和收缩。扩展或收缩哈希表时我们需要将  ht[0] 里面的所有键值对 rehash 到  ht[1] 里面。而且这个将ht[0]里面的键值对rehash到ht[1]不是一次性、集中式地完成的, 而是分多次、渐进式地完成的。

这样做的原因在于, 如果  ht[0] 里只保存着四个键值对, 那么服务器可以在瞬间就将这些键值对全部 rehash 到  ht[1] ; 但是, 如果哈希表里保存的键值对数量不是四个, 而是四百万、四千万甚至四亿个键值对, 那么要一次性将这些键值对全部 rehash 到  ht[1] 的话, 庞大的计算量可能会导致服务器在一段时间内停止服务。

因此, 为了避免 rehash 对服务器性能造成影响, 服务器不是一次性将  ht[0] 里面的所有键值对全部 rehash 到  ht[1] , 而是分多次、渐进式地将  ht[0] 里面的键值对慢慢地 rehash 到  ht[1] 。

以下是哈希表渐进式 rehash 的详细步骤:

  1. 为  ht[1] 分配空间, 让字典同时持有  ht[0] 和  ht[1] 两个哈希表。
  2. 在字典中维持一个索引计数器变量  rehashidx , 并将它的值设置为  0 , 表示 rehash 工作正式开始。
  3. 在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将  ht[0] 哈希表在  rehashidx 索引上的所有键值对 rehash 到  ht[1] , 当 rehash 工作完成之后, 程序将  rehashidx 属性的值增一。
  4. 随着字典操作的不断执行, 最终在某个时间点上,  ht[0] 的所有键值对都会被 rehash 至  ht[1] , 这时程序将  rehashidx 属性的值设为  -1 , 表示 rehash 操作已完成。

渐进式 rehash 的好处在于它采取分而治之的方式, 将 rehash 键值对所需的计算工作均滩到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式 rehash 而带来的庞大计算量。

【PS:如果上面的描述不够清楚,可以移步这里: 渐进式rehash

====================================================

OK,看完的Redis的字典设计及其rehash机制,相信其能给我们带来灵感。现在我们再来说说 缓存系统的更新机制设计问题。

如下是一个可行的解决方案(假设我们的更新机制是每5分钟更新一次缓存):

1)设计两个缓存池,记为A、B,而A和B的内容都是从后端服务器数据库中获取到的数据。正常情况下,客户端的请求都是从缓存池A中获取缓存内容,同时,设置一个全局的变量ref用于记录当前正在访问缓存A的客户端数量,来一个客户端请求将ref值加1,响应完一个客户端请求后ref减一。

2)当缓存更新时间到时,如果ref不为0,则我们不能直接更新缓存,因为这时有客户端正在从缓存池A取数据。这里,我们可以借鉴Redis的rehash思想,更新时间到,我们将客户端的访问都引导到B缓存池,此时的缓存池A不再接受新的客户端数据请求,A的ref变量只减不增,当ref变量减少到0时,我们便可以更新A缓存池中的内容了。

【PS:初始nosql等相关知识,如有错误,欢迎指正!】

参考链接:

http://redisbook.com/en/latest/preview/dict/datastruct.html


Viewing all articles
Browse latest Browse all 11804

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>