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

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

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

     现实中我们的缓存通常都是以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),数量大时布隆过滤器会不错的选择~。

  

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

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

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

 3、Redis OR Memcache?

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

       1、缓存的更新设置是怎样的?每次都get,set全部数据吗还是部分。

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

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

       4、缓存需要持久化吗

       5、单个缓存有多大

       对于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可能会更适合你~。

     

原文地址:https://www.cnblogs.com/linlinismine/p/9356141.html

时间: 2024-07-30 18:22:16

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

数据库设计中常见表结构的设计技巧(转)

一.树型关系的数据表 不少程序员在进行数据库设计的时候都遇到过树型关系的数据,例如常见的类别表,即一个大类,下面有若干个子类,某些子类又有子类这样的情况.当类别不确定,用户希望可以在任意类别下添加新的子类,或者删除某个类别和其下的所有子类,而且预计以后其数量会逐步增长,此时我们就会考虑用一个数据表来保存这些数据.按照教科书上的教导,第二类程序员大概会设计出类似这样的数据表结构: 类别表_1(Type_table_1) 名称 类型 约束条件 说明 type_id int 无重复 类别标识,主键 t

设计中常见的11种误区

设计师们经常会创作一些不合时宜的作品,他们将设计与艺术混为一谈,持有幸运.灵感和个人表达的想法.让我们一起来细数一些常见的设计误区. 理解这些误区 设计并不复杂,就是制作产品.由于设计师的设计内容十分广泛,从物品.信息设计,到动作.构图设计,等等,这使得很难对设计进行明确的定义.毫无疑问,关于设计存在许多不同的判断.理念与误区,且在高效地产出作品方面存在一些分歧.这些误解会阻碍我们创作出好的设计. 需要证据证实误区存在吗?看看设计师们放在公文包里的设计作品吧!纵使外观美丽养眼,设计本身却残破不堪

数据库设计中常见表结构分析

一.树型关系的数据表 不少程序员在进行数据库设计的时候都遇到过树型关系的数据,例如常见的类别表,即一个大类,下面有若干个子类,某些子类又有子类这样的情况.当类别不确定,用户希望可以在任意类别下添加新的子类,或者删除某个类别和其下的所有子类,而且预计以后其数量会逐步增长,此时我们就会考虑用一个数据表来保存这些数据. 设计结构: 名称 类型 约束条件 说明 type_id int 无重复 类别标识,主键 type_name char(50) 不允许为空 类型名称,不允许重复 type_father

架构设计中常见的语义耦合类型的总结

语义耦合是隐性的,不易察觉的耦合类型 ,是导致代码重构.调试.修改复杂度急剧增加的主要原因. 1,操作顺序耦合 使用一个对象,需要先调用Init(),之后才能调用DoAnything().这种顺序耦合,即使在文档中remark也是极为不优雅的做法. 2,全局参数传递 模块A修改了某个全局参数g_val,模块B读取该值.模块B必须知道模块A已经对该参数赋值. 3,业务封装不够紧密 模块A向模块B传一个参数,模块B根据该参数选择对应的操作.模块A必须知道与业务相关的所有的操作类型.对于模块A,仅传递

缓存设计(cache-design)

分布式缓存设计 目前常见的缓存方案都是分层缓存,通常可以分为以下几层: 1.1NG本地缓存,命中的话直接返回 1.2 NG没有命中时则需要查询分布式缓存,如redis 1.3 如果分布式缓存没有命中则需要回源到Tomcat在本地堆进行查询,命中之后异步写回redis 1.4以上都没有命中那就只有从DB或者是数据源进行查询,并写回到redis 缓存更新原子性 在写回到redis的时候如果是Tomcat集群, 多个进程同时写那很有可能出现脏数据,这时就会出现更新原子性的问题, 可以有以下解决方案:

大型网站架构系列:缓存在分布式系统中的应用(三)

本文是<缓存在分布式系统中的应用>第三篇文章. 上次主要给大家分享了,缓存在分布式系统中的应用,主要从不同的场景,介绍了CDN,反向代理,分布式缓存,本地缓存的常规架构和基本原理. 因为时间关于,原计划分享<缓存常见问题>的内容,没有讲.本次主要针对缓存的常见个问题,做一个介绍.主要有以下议题: 一.分享大纲 分享大纲 数据一致性 缓存高可用 缓存雪崩 缓存穿透 参考资料 分享总结 二.数据一致性 缓存是在数据持久化之前的一个节点,主要是将热点数据放到离用户最近或访问速度更快的介质

后端分布式系列:分布式存储-HDFS 与 GFS 的设计差异

「后端分布式系列」前面关于 HDFS 的一些文章介绍了它的整体架构和一些关键部件的设计实现要点. 我们知道 HDFS 最早是根据 GFS(Google File System)的论文概念模型来设计实现的. 然后呢,我就去把 GFS 的原始论文找出来仔细看了遍,GFS 的整体架构图如下: HDFS 参照了它所以大部分架构设计概念是类似的,比如 HDFS NameNode 相当于 GFS Master,HDFS DataNode 相当于 GFS chunkserver. 但还有些细节不同的地方,所以

【游戏开发】浅谈游戏开发中常见的设计原则

俗话说得好:“设计模式,常读常新~”.的确,每读一遍设计模式都会有些新的体会和收获.马三不才,才读了两遍设计模式(还有一遍是在学校学的),属于菜鸟级别的.这次准备把阅读设计模式的想法记录下来,并且把设计模式应用在Unity游戏开发上,做些小案例. 什么是设计模式 每一种模式都在说明某种一再出现的问题,并描述解决方法的核心,之后让你能够举一反三,从而解决数个类似的问题.每一种设计模式除了按照“面向对象的设计原则”加以分析设计之外,还满足:”解决一再出现的问题“.”解决问题的方案和问题核心的关键点“

CYQ.Data V5 分布式自动化缓存设计介绍(二)

前言: 最近一段时间,开始了<IT连>创业,所以精力和写的文章多数是在分享创业的过程. 而关于本人三大框架CYQ.Data.Aries.Taurus.MVC的相关文章,基本都很少写了. 但框架的维护升级,还是时不时的在进行中的,这点从开源的Github上的代码提交时间上就可以看出来了. 毕竟<IT连>的后台WebAPI,用的是Taurus.MVC,后台系统管理用的是Aries. 不过今天,就不写创业相关的文章了,先分享篇技术类的文章. CYQ.Data 分布式自动缓存 之前写过一篇