分布式系列之缓存设计中常见的问题

 

    缓存这个东西相信你们工做中都接触得比较多,相应的在不一样场景下也会遇到各类各样的问题。下面我列举几种可能会遇到的问题并提供一些解决建议。redis

 

一、如何把海量数据存放在缓存中并提供快速查询算法

     现实中咱们的缓存一般都是以string,map,array,list,set,tree等具体的类型或者集合存放内存中,它们的共同点都在于把元素具体内容放到内存里面。这种在元素数量小的时候是没问题。但一旦数据量过大,消耗的内存也会呈现线性增加,最终达到瓶颈,而且查询效率也可能随着元素数量增加而降低。好比list与array,没有数字下标的状况下只能是0(n)遍历,有人也许会说到map的效率不是很高吗,查询效率能够达到O(1)。但这只是理想状况而已,hash冲突大的状况下map的查询也会退化,而且map也并无解决内存消耗的问题。难道就没有办法解决这个问题吗?固然有!答案就是Bit-map和布隆过滤器!数组

     

      什么是Bit-map?缓存

       所谓的Bit-map就是用一个bit位来标记某个元素对应的Value, 而Key便是该元素或者元素通过转换(好比hash)获得的值。因为采用了Bit为单位来存储数据,所以在存储空间方面,能够大大节省。原理以下图:数据结构

  

 

      那1M的Bit-map能够表示多少数据呢,1兆字节(mb)=8388608比特(bit),也就是指咱们能够用1M的内存表示800W+的数据。。。。并发

      举个情景好比运营方给了一批100W用户ID,若是这批用户在指定时间内购买了某个商品就给用户派一张优惠劵,假设这100W的用户的userId都是64位的长整型数。那如何把这100W的用户存起来节省空间而后访问的性能也不差?个人建议是放到redis缓存里面利用redis的setbit,getbit的命令存储起来和访问,这样要比你存db要节省更多的空间而且查询速度也快~。若是这100W的用户ID分布范围比较随机,我建议是本地排好序,而后分红几段用不一样Bit-map表示~,这样就不会形成过多没必要要的空间浪费。别外本地排序也要以用Bit-map实现哦。异步

     

    什么又是布隆过滤器?函数

   布隆过滤器英语:Bloom Filter)是1970年由布隆提出的。它其实是一个很长的二进制向量和一系列随机映射函数。布隆过滤器能够用于检索一个元素是否在一个集合中。它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。高并发

  基本概念和原理:性能

  若是想判断一个元素是否是在一个集合里,通常想到的是将集合中全部元素保存起来,而后经过比较肯定。链表散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。可是随着集合中元素的增长,咱们须要的存储空间愈来愈大。同时检索速度也愈来愈慢,上述三种结构的检索时间复杂度分别为O(n),O(logn),O(n/k)

    布隆过滤器的原理是,当一个元素被加入集合时,经过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,咱们只要看看这些点是否是都是1就(大约)知道集合中有没有它了:若是这些点有任何一个0,则被检元素必定不在;若是都是1,则被检元素极可能在。这就是布隆过滤器的基本思想。

 因此说布隆过滤器底层仍是依赖Bit-map的存储原理,由于是经过散列函数来进行映射就会有冲突的可能性,当元素a,b的hash出来index是同样的时候就没法判断到底Bit-map里面存的是a仍是b。因此通常布隆过滤器是不容许删除元素的,由于真不知道删除的是哪一个元素。。。。

  布隆过滤器hash冲突性与散列函数的设计和Bit-map的大小有关,若是Bit-map过小那必然不少元素都会落到同一个下标,而且后面数量越大冲突也就越大。不过不用太担忧毕竟1M的Bit-map就能够保存800W+数据~。

   当你存储元素量不是很大的话,能够优先考虑散列表(map),数量大时布隆过滤器会不错的选择~。

  

二、高并发时缓存如何更新

    在更新缓存时咱们一般会加锁更新,为了减小锁住的资源,一般用分段锁的设计,只锁须要更新的资源。但在高并发的情景下大多数发请求都集中在一个key的时候也就是hot-key的情形下,对缓存进行更新。若是采用加锁的形式,就只能有一个线程去更新,其它的线程就只能同步阻塞,瞬间就有会大量线程hold住,甚至有可能把线程打满,这个时候系统的性能就会大打折扣。因此在高并发hot-key的情景下,加锁更新很影响性能。若是把读取和更新的操做隔离开来会怎样,以下图

 

   这种方案就是起额外的线程定时去定时去更新cache,这样读取线程就不会存在锁争的问题。这种一般cache不会设置超时时间,好比guava cache的refreshAfterWrite策略的cache就是永远不会超时的,只是每次读取的时候判断是否到了刷新周期,若是到了选取其中一个线程去更新,其它线程仍然返回旧值。因此这异步更新cache也不失为一个方法。只是要注意的控制好异步更新频率,频率过小那cache的实时性就会受影响。

 

 三、Redis OR Memcache?

      每当有人问我这个东西是用redis仍是memcache存起来时,我会建议他从下面几个方面去考虑:

       一、缓存的更新设置是怎样的?每次都get,set所有数据吗仍是部分。

       二、除了get,set还有其它操做吗?好比排序,获取前面5个元素?

       三、预计缓存的qps有多少?

       四、缓存须要持久化吗

       五、单个缓存有多大

       对于redis、memcache来讲,我以为若是是通常业务首先考虑的不该该是二者的性能问题,而是这二者提供的数据结构哪一个更适切合你当前的业务需求还有未来业务的发展。在这一点方面redis无疑是占据了绝大优点,由于redis提供了String、Hash、List、Set和Sorted Set五种数据结构,而memcache只有key-value。因此通常我是优先考虑redis的。另外在单个缓存大小方面memcache的value存储,最大为1M,若是存储的value很大,只能使用redis。刚提到为何不优先考虑性能问题呢?由于这二者的性能都不算差,而且后面都是能够横向扩展的,甚至还能够经过其它方式好比增长多层cache如local cache去提高。其实更多仍是考虑业务的维护与迭代。若是你是纯k-v操做,而且数据量很是大,并发量很是大的业务,这个时候我建议你memcache会更适合你~,若是是其它redis可能会更适合你~。

相关文章
相关标签/搜索