精华内容
下载资源
问答
  • 缓存设计的一些思考

    2013-12-11 21:37:11
    互联网架构中缓存无处不在,某厂牛人曾经说过:”缓存就像清凉油,哪里不舒服,抹一下就好了”。高品质的存储容量小,价格高;低品质存储容量大,价格低,缓存的目的就在于”扩充”高品质存储的容量。本文探讨缓存...

     转载:http://www.nosqlnotes.net/archives/222

     

    互联网架构中缓存无处不在,某厂牛人曾经说过:”缓存就像清凉油,哪里不舒服,抹一下就好了”。高品质的存储容量小,价格高;低品质存储容量大,价格低,缓存的目的就在于”扩充”高品质存储的容量。本文探讨缓存相关的一些问题。

    LRU替换算法

    缓存的技术点包括内存管理和替换算法。LRU是使用最多的替换算法,每次淘汰最久没有使用的元素。LRU缓存实现分为两个部分:Hash表和LRU链表,Hash表用于查找缓存中的元素,LRU链表用于淘汰。内存常以Slab的方式管理。

    上图是Memcache的内存管理示意图,Memcache以Slab方式管理内存块,从系统申请1MB大小的大块内存并划分为不同大小的Chunk,不同Slab的Chunk大小依次为80字节,80 * 1.25,80 * 1.25^2, …。向Memcache中添加item时,Memcache会根据item的大小选择合适的Chunk。

    Oceanbase最初也采用LRU算法,只是内存管理有些不同。Oceanbase向系统申请2MB大小的大块内存,插入item时直接追加到最后一个2MB内存块的尾部,当缓存的内存量太大需要回收时根据一定的策略整块回收2MB的内存,比如回收最近最少使用的item所在的2MB内存块。这样的做法虽然不是特别精确,但是内存管理简单,对于系统初期很有好处。

    缓存锁

    缓存需要操作两个数据结构:Hash表和LRU链表。多线程操作cache时需要加锁,比较直接的做法是整体加一把大锁后再操作Hash表和LRU链表。有如下的优化思路:

    1, Hash表和LRU链表使用两把不同的锁,且Hash表锁的粒度可以降低到每个Hash桶一把锁。这种做法的难点是需要处理两种数据结构不一致导致的问题,假设操作顺序为read hash -> del hash item -> del lru item -> read lru item,最后一次read lru item时item所在的内存块可能已经被回收或者重用,一般需要引入引用计数并考虑复杂的时序问题。

    2, 采用多个LRU链表以减少LRU表锁粒度。Hash表的锁冲突可以通过增加Hash桶的个数来解决,而LRU链表是一个整体,难以分解。可以将缓存的数据分成多个工作集,每个item属于某个工作集,每个工作集一个LRU链表。这样做的主要问题是可能不均衡,比如某个工作集很热,某些从整体上看比较热的数据也可能被淘汰。

    3, 牺牲LRU的精确性以减少锁。比如Mysql中的LRU算法变形,大致如下:将LRU链表分成两部分,前半部分和后半部分,如果访问的item在前半部分,什么也不做,而不是像传统的LRU算法那样将item移动到链表头部;又如Linux Page Cache中的CLOCK算法。Oceanbase目前的缓存算法也是通过牺牲精确性来减少锁。前面提到,Oceanbase缓存以2MB的内存块为单位进行淘汰,最开始采用LRU策略,每次淘汰最近最少使用的item所在的2MB内存块,然而,这样做的问题是需要维护最近最少使用的item,即每次读写缓存都需要加锁。后续我们将淘汰策略修改为:每个2MB的内存块记录一个访问次数和一个最近访问时间,每次读取item时,如果访问次数大于所有2MB内存块访问次数的平均值,更新最近访问时间;否则,将访问次数加1。根据记录的最近访问时间淘汰2MB内存块。虽然,这个算法的缓存命中率不容易评估,但是缓存读取只需要一些原子操作,不需要加锁,大大减少了锁粒度。

    4, 批量操作。缓存命中时不需要立即更新LRU链表,而是可以将命中的item保存在线程Buffer中,积累了一定数量后一次性更新LRU链表。

    LIRS思想

    Cache有两个问题:一个是前面提到的降低锁粒度,另一个是提高精准度,或者称为提高命中率。LRU在大多数情况下表现是不错的,但是有如下的问题:

    1, 顺序扫描。顺序扫描的情况下LRU没有命中情况,而且会淘汰其它将要被访问的item从而污染cache。

    2, 循环的数据集大于缓存大小。如果循环访问且数据集大于缓存大小,那么没有命中情况。

    之所以会出现上述一些比较极端的问题,是因为LRU只考虑访问时间而没有考虑访问频率,而LIRS在这方面做得比较好。LIRS将数据分为两部分:LIR(Low Inner-reference Recency)和HIR(High Inner-reference Recency),其中,LIR中的数据是热点,在较短的时间内被访问了至少两次。LIRS可以看成是一种分级思想:第一级是HIR,第二级是LIR,数据先进入到第一级,当数据在较短的时间内被访问两次时成为热点数据则进入LIR,HIR和LIR内部都采用LRU策略。这样,LIR中的数据比较稳定,解决了LRU的上述两个问题。LIRS论文中提出了一种实现方式,不过我们可以做一些变化,如可以实现两级cache,cache元素先进入第一级cache,当访问频率达到一定值(比如2)时升级到第二级,第一级和第二级均内部采用LRU进行替换。Oracle Buffer Cache中的Touch Count算法也是采用了类似的思想。

    SSD与缓存

    SSD发展很快,大有取代传统磁盘之势。SSD的发展是否会使得单机缓存变得毫无必要我们无从得知,目前,Memory + SSD + 磁盘的混合存储方案还是比较靠谱的。SSD使用可以有如下不同的模式:

    1, write-back:数据读写都走SSD,内存中的数据写入到SSD即可,另外有单独的线程定期将SSD中的数据刷到磁盘。典型的代表如Facebook Flashcache。

    2, write-through:数据写操作需要先写到磁盘,内存和SSD合在一起看成两级缓存,即cache中相对较冷的数据在SSD,相对较热的数据在内存。

    当然,随着SSD的应用,我想减少缓存锁粒度的重要性会越来越突出。

    总结&推荐资料

    到目前为止,我们在SSD,缓存相关优化的工作还是比较少的。今后的一年左右时间,我们将会投入一定的精力在系统优化上,相信到时候再来总结的时候认识会更加深刻。我想,缓存相关的优化工作首先要做的是根据需求制定一个大致的评价标准,接着使用实际数据做一些实验,最终可能会同时保留两到三种实现方式或者配置略微有所不同的缓存实现。缓存相关的推荐资料如下:

    [1] Touch Count Algorithm. http://youyus.com/wp-content/uploads/resource/Shallahamer%20TC4a.pdf

    [2] LIRS. http://portal.acm.org/citation.cfm?id=511340

     

    展开全文
  • 通用数据缓存设计方案

    千次阅读 2016-11-24 22:44:35
    随着redis等内存数据库的流行,大多数应用及系统均开始采用redis集群作为系统的缓存方案,用来减少数据库的压力,增加应用并发及响应速度。前端页面的缓存暂不做探讨,主要是针对后端服务的数据缓存。 数据缓存...

    随着redis等内存数据库的流行,大多数应用及系统均开始采用redis集群作为系统的缓存方案,用来减少数据库的压力,增加应用并发及响应速度。前端页面的缓存暂不做探讨,主要是针对后端服务的数据缓存。


    数据缓存的分类及特点

    常规涉及的数据缓存大致可以从以下三个方面进行分类:生命周期、时效性、复杂度;



    数据缓存根据生命周期分类主要分为:长期、中期、短期三类,主要作用及特点如下:



    数据缓存根据时效性主要分为异步、同步(即实时)两大类,在作用上需要根据实际的业务场景进行划分,在数据同步实现上存在较大的差异;



    数据缓存根据复杂度主要分为对象实体、组合对象等两大类,主要特点如下:



    实现难点

    涉及的大量业务数据缓存属于有实时要求的组合对象,由于组合对象是多个对象通过一定逻辑处理后得到的组合,一旦修改组合中的任一对象,都需要重建组合对象的数据缓存,数据同步实现非常困难;



    解决方案:简单对象数据缓存

    根据业务简单对象实体大致存在以下分类的数据缓存处理场景:



    解决方案:组合对象数据缓存

    组合对象处理非常复杂,可以采用业务单位为基础展开,以该业务单位更新对象实体让组合对象失效的方式实现:



    以上两种只是针对数据缓存方案的其中一种可行性探讨,通过这种方式,可以实现系统级通过缓存的框架。当然,其中需要考虑到缓存的命中率、命中频次等因素,对于命中较低的不推荐使用缓存,建议采用其他方式解决;对于部分实时性要求不高的业务场景,还可以通过消息队列等方式采用异步的方式更新数据缓存等。

    展开全文
  • 缓存设计可谓老生常谈了,早些时候都是采用memcache,现在大家更多倾向使用redis,除了知晓常用的数据存储类型,结合业务场景有针对性选择,好像其他也没有什么大的难点。 工程中引入Redis Client二方包,初始化一...

    缓存设计可谓老生常谈了,早些时候都是采用memcache,现在大家更多倾向使用redis,除了知晓常用的数据存储类型,结合业务场景有针对性选择,好像其他也没有什么大的难点。

    工程中引入Redis Client二方包,初始化一个 Bean 实例RedisTemplate ,一切搞定,so easy。

     

    如果是几十、几百并发的业务场景,缓存设计可能并不需要考虑那么多,但如果是亿级的系统呢?

     

    首先,了解缓存知识图谱

    早期的缓存用于加速 CPU 数据交换的 RAM。随着互联网的快速发展,缓存的应用更加宽泛,用于数据高速交换的存储介质都称之为缓存。

    使用缓存时,我们要关注哪些指标?缓存有哪些应用模式?以及缓存设计时有哪些 Tip 技巧?一图胜千言,如下:

    七大经典问题

    缓存在使用过程不可避免会遇到一些问题,对于高频的问题我们大概归为了 7 类。具体内容下面我们一一道来。

    1、缓存集中失效

    当业务系统查询数据时,首先会查询缓存,如果缓存中数据不存在,然后查询 DB 再将数据预热到Cache中,并返回。缓存的性能比 DB 高 50~100 倍以上。

    很多业务场景,如:秒杀商品、微博热搜排行、或者一些活动数据,都是通过跑任务方式,将 DB 数据批量、集中预热到缓存中,缓存数据有着近乎相同的过期时间

    当这批数据过期时,会一起过期,此时,对这批数据的所有请求,都会出现缓存失效,从而将压力转嫁到 DB,DB 的请求量激增,压力变大,响应开始变慢。

    那么有没有解呢?

    当然有了。

    我们可以从缓存的过期时间入口,将原来的固定过期时间,调整为过期时间=基础时间+随机时间,让缓存慢慢过期,避免瞬间全部过期,对 DB 产生过大压力。

    2、缓存穿透

    不是所有的请求都能查到数据,不论是从缓存中还是 DB 中。

    假如黑客攻击了一个论坛,用了一堆肉鸡访问一个不存在的帖子id。按照常规思路,每次都会先查缓存,缓存中没有,接着又查 DB,同样也没有,此时不会预热到 Cache 中,导致每次查询,都会cache miss

    由于 DB 的吞吐性能较差,会严重影响系统的性能,甚至影响正常用户的访问。

    解决方案:

    • 方案一:查询 DB 时,如果数据不存在,预热一个特殊空值到缓存中。这样,后续查询都会命中缓存,但是要对特殊值,解析处理。

    • 方案二:构造一个BloomFilter过滤器,初始化全量数据,当接到请求时,在BloomFilter中判断这个 key 是否存在,如果不存在,直接返回即可,无需再查询缓存和 DB

    3、缓存雪崩

    缓存雪崩是指部分缓存节点不可用,进而导致整个缓存体系甚至服务系统不可用的情况。

    分布式缓存设计一般选择一致性 Hash,当有部分节点异常时,采用 rehash 策略,即把异常节点请求平均分散到其他缓存节点。但是,当较大的流量洪峰到来时,如果大流量 key 比较集中,正好在某 1~2 个缓存节点,很容易将这些缓存节点的内存、网卡过载,缓存节点异常 Crash,然后这些异常节点下线,这些大流量 key 请求又被 rehash 到其他缓存节点,进而导致其他缓存节点也被过载 Crash,缓存异常持续扩散,最终导致整个缓存体系异常,无法对外提供服务。

    解决方案:

    • 方案一:增加实时监控,及时预警。通过机器替换、各种故障自动转移策略,快速恢复缓存对外的服务能力。

    • 方案二:缓存增加多个副本,当缓存异常时,再读取其他缓存副本。为了保证副本的可用性,尽量将多个缓存副本部署在不同机架上,降低风险。

    4、缓存热点

    对于突发事件,大量用户同时去访问热点信息,这个突发热点信息所在的缓存节点就很容易出现过载和卡顿现象,甚至 Crash,我们称之为缓存热点。

     

    这个在新浪微博经常遇到,某大 V 明星出轨、结婚、离婚,瞬间引发数百千万的吃瓜群众围观,访问同一个 key,流量集中打在一个缓存节点机器,很容易打爆网卡、带宽、CPU 的上限,最终导致缓存不可用。

    解决方案:

    • 首先找到这个热 key来,比如通过Spark实时流分析,及时发现新的热点 key。

    • 将集中化流量打散,避免一个缓存节点过载。由于只有一个 key,我们可以在 key 的后面拼上有序编号,比如key#01key#02。。。key#10多个副本,这些加工后的 key 位于多个缓存节点上。

    • 每次请求时,客户端随机访问一个即可。

    可以设计一个缓存服务治理管理后台,实时监控缓存的 SLA,并打通分布式配置中心,对于一些hot key可以快速、动态扩容。

    5、缓存大 Key

    当访问缓存时,如果 key 对应的 value 过大,读写、加载很容易超时,容易引发网络拥堵。另外缓存的字段较多时,每个字段的变更都会引发缓存数据的变更,频繁的读写,导致慢查询。如果大 key 过期被缓存淘汰失效,预热数据要花费较多的时间,也会导致慢查询。

    所以我们在设计缓存的时候,要注意缓存的粒度,既不能过大,如果过大很容易导致网络拥堵;也不能过小,如果太小,查询频率会很高,每次请求都要查询多次。

    解决方案:

    • 方案一:设置一个阈值,当 value 的长度超过阈值时,对内容启动压缩,降低 kv 的大小。

    • 方案二:评估大 key所占的比例,由于很多框架采用池化技术,如:Memcache,可以预先分配大对象空间。真正业务请求时,直接拿来即用。

    • 方案三:颗粒划分,将大 key 拆分为多个小 key,独立维护,成本会降低不少。

    • 方案四:大 key 要设置合理的过期时间,尽量不淘汰那些大 key。

    6、缓存数据一致性

    缓存是用来加速的,一般不会持久化储存。所以,一份数据通常会存在DB缓存中,由此会带来一个问题,如何保证这两者的数据一致性。另外,缓存热点问题会引入多个副本备份,也可能会发生不一致现象。

    解决方案:

    • 方案一:当缓存更新失败后,进行重试,如果重试失败,将失败的 key 写入 MQ 消息队列,通过异步任务补偿缓存,保证数据的一致性。

    • 方案二:设置一个较短的过期时间,通过自修复的方式,在缓存过期后,缓存重新加载最新的数据。

    7、数据并发竞争预热

    互联网系统典型的特点就是流量大,一旦缓存中的数据过期、或因某些原因被删除等,导致缓存中的数据为空,大量的并发线程请求(查询同一个 key)就会一起并发查询数据库,数据库的压力陡然增加。

    如果请求量非常大,全部压在数据库,可能把数据库压垮,进而导致整个系统的服务不可用。

    解决方案:

    • 方案一:引入一把全局锁,当缓存未命中时,先尝试获取全局锁,如果拿到锁,才有资格去查询DB,并将数据预热到缓存中。虽然,client 端发起的请求非常多,但是由于拿不到锁,只能处于等待状态,当缓存中的数据预热成功后,再从缓存中获取。

    为了便于理解,简单画了个流程图。这里面特别注意一个点,由于有一个并发时间差,所以会有一个二次 check 缓存是否有值的校验,防止缓存预热重复覆盖。

    • 方案二:缓存数据创建多个备份,当一个过期失效后,可以访问其他备份。

    写在最后

    缓存设计时,有很多技巧,优化手段也是千变万化,但是我们要抓住核心要素。那就是,让访问尽量命中缓存,同时保持数据的一致性。

    展开全文
  • Redis理解之缓存设计

    千次阅读 2018-04-24 15:23:39
    下面会介绍缓存使用技巧和设计方案,包含如下内容:缓存的收益和成本分析、缓存更新策略的选择和使用场景、缓存粒度控制方法、穿透问题优化、无底洞问题优化、雪崩问题优化、热点key重建优化。 1)缓存的收益和成本...

           缓存能够有效地加速应用的读写速度,同时也可以降低后端负载,对日常应用的开发至关重要。下面会介绍缓存使用技巧和设计方案,包含如下内容:缓存的收益和成本分析、缓存更新策略的选择和使用场景、缓存粒度控制方法、穿透问题优化、无底洞问题优化、雪崩问题优化、热点key重建优化。

           1)缓存的收益和成本分析

         下图左侧为客户端直接调用存储层的架构,右侧为比较典型的缓存层+存储层架构。


          下面分析一下缓存加入后带来的收益和成本。
           收益:①加速读写:因为缓存通常都是全内存的,而存储层通常读写性能不够强悍(例如MySQL),通过缓存的使用可以有效地加速读写,优化用户体验。

                    ②降低后端负载:帮助后端减少访问量和复杂计算(例如很复杂的SQL语句),在很大程度降低了后端的负载。

           成本:①数据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口跟更新策略有关。
                     ②代码维护成本:加入缓存后,需要同时处理缓存层和存储层的逻辑,增大了开发者维护代码的成本。

                     ③运维成本:以Redis Cluster为例,加入后无形中增加了运维成本。

         

         缓存的使用场景基本包含如下两种:

        ①开销大的复杂计算:以MySQL为例子,一些复杂的操作或者计算(例如大量联表操作、一些分组计算),如果不加缓存,不但无法满足高并发量,同时也会给MySQL带来巨大的负担。
       ②加速请求响应:即使查询单条后端数据足够快(例如select*from tablewhere id=),那么依然可以使用缓存,以Redis为例子,每秒可以完成数万次读写,并且提供的批量操作可以优化整个IO链的响应时间。

     

       2)缓存更新策略

           缓存中的数据会和数据源中的真实数据有一段时间窗口的不一致,需要利用某些策略进行更新,下面会介绍几种主要的缓存更新策略。

        ①LRU/LFU/FIFO算法剔除:剔除算法通常用于缓存使用量超过了预设的最大值时候,如何对现有的数据进行剔除。例如Redis使用maxmemory-policy这个配置作为内存最大值后对于数据的剔除策略。

        ②超时剔除:通过给缓存数据设置过期时间,让其在过期时间后自动删除,例如Redis提供的expire命令。如果业务可以容忍一段时间内,缓存层数据和存储层数据不一致,那么可以为其设置过期时间。在数据过期后,再从真实数据源获取数据,重新放到缓存并设置过期时间。例如一个视频的描述信息,可以容忍几分钟内数据不一致,但是涉及交易方面的业务,后果可想而知。

        ③主动更新:应用方对于数据的一致性要求高,需要在真实数据更新后,立即更新缓存数据。例如可以利用消息系统或者其他方式通知缓存更新。

                                                     三种常见更新策略的对比:


    有两个建议:①低一致性业务建议配置最大内存和淘汰策略的方式使用。
                        ②高一致性业务可以结合使用超时剔除和主动更新,这样即使主动更新出了问题,也能保证数据过期时间后删除脏数据。


      3)缓存粒度控制

           缓存粒度问题是一个容易被忽视的问题,如果使用不当,可能会造成很多无用空间的浪费,网络带宽的浪费,代码通用性较差等情况,需要综合数据通用性、空间占用比、代码维护性三点进行取舍。

         缓存比较常用的选型,缓存层选用Redis,存储层选用MySQL。



      4)穿透优化

           缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层。

          通常可以在程序中分别统计总调用数、缓存层命中数、存储层命中数,如果发现大量存储层空命中,可能就是出现了缓存穿透问题。造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题,第二,一些恶意攻击、爬虫等造成大量空命中。下面我们来看一下如何解决缓存穿透问题。

         ①缓存空对象:如图下所示,当第2步存储层不命中后,仍然将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。

         


          缓存空对象会有两个问题:第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

        ②布隆过滤器拦截

            如下图所示,在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截。例如:一个推荐系统有4亿个用户id,每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放到存储层中,但是最新的用户由于没有历史行为,就会发生缓存穿透的行为,为此可以将所有推荐数据的用户做成布隆过滤器。如果布隆过滤器认为该用户id不存在,那么就不会访问存储层,在一定程度保护了存储层。



                                              缓存空对象和布隆过滤器方案对比


     

    另:布隆过滤器简单说明:

    如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢。

    Bloom Filter 是一种空间效率很高的随机数据结构,Bloom filter 可以看做是对 bit-map 的扩展, 它的原理是:

    当一个元素被加入集合时,通过 K 个 Hash 函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:

    • 如果这些点有任何一个 0,则被检索元素一定不在
    • 如果都是 1,则被检索元素很可能在。

      

      5)无底洞优化

            为了满足业务需要可能会添加大量新的缓存节点,但是发现性能不但没有好转反而下降了。 用一句通俗的话解释就是,更多的节点不代表更高的性能,所谓“无底洞”就是说投入越多不一定产出越多。但是分布式又是不可以避免的,因为
    访问量和数据量越来越大,一个节点根本抗不住,所以如何高效地在分布式缓存中批量操作是一个难点。

            无底洞问题分析:
          ①客户端一次批量操作会涉及多次网络操作,也就意味着批量操作会随着节点的增多,耗时会不断增大。

          ②网络连接数变多,对节点的性能也有一定影响。

       

        如何在分布式条件下优化批量操作?我们来看一下常见的IO优化思路:

        ·命令本身的优化,例如优化SQL语句等。
        ·减少网络通信次数。

        ·降低接入成本,例如客户端使用长连/连接池、NIO等。

       这里我们假设命令、客户端连接已经为最优,重点讨论减少网络操作次数。下面我们将结合Redis Cluster的一些特性对四种分布式的批量操作方式进行说明。

       ①串行命令:由于n个key是比较均匀地分布在Redis Cluster的各个节点上,因此无法使用mget命令一次性获取,所以通常来讲要获取n个key的值,最简单的方法就是逐次执行n个get命令,这种操作时间复杂度较高,它的操作时间=n次网络时间+n次命令时间,网络次数是n。很显然这种方案不是最优的,但是实现起来比较简单。

       ②串行IO:Redis Cluster使用CRC16算法计算出散列值,再取对16383的余数就可以算出slot值,同时Smart客户端会保存slot和节点的对应关系,有了这两个数据就可以将属于同一个节点的key进行归档,得到每个节点的key子列表,之后对每个节点执行mget或者Pipeline操作,它的操作时间=node次网络时间+n次命令时间,网络次数是node的个数,整个过程如下图所示,很明显这种方案比第一种要好很多,但是如果节点数太多,还是有一定的性能问题。

       

      ③并行IO:此方案是将方案2中的最后一步改为多线程执行,网络次数虽然还是节点个数,但由于使用多线程网络时间变为O(1),这种方案会增加编程的复杂度。



     ④hash_tag实现:Redis Cluster的hash_tag功能,它可以将多个key强制分配到一个节点上,它的操作时间=1次网络时间+n次命令时间。

                                                        四种批量操作解决方案对比



     6)雪崩优化

            缓存雪崩:由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。

            预防和解决缓存雪崩问题,可以从以下三个方面进行着手:

            ①保证缓存层服务高可用性。如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如前面介绍过的Redis Sentinel和Redis Cluster都实现了高可用。

            ②依赖隔离组件为后端限流并降级。在实际项目中,我们需要对重要的资源(例如Redis、MySQL、HBase、外部接口)都进行隔离,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。但是线程池如何管理,比如如何关闭资源池、开启资源池、资源池阀值管理,这些做起来还是相当复杂的。

           ③提前演练。在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,在此基础上做一些预案设定。


     7)热点key重建优化

            开发人员使用“缓存+过期时间”的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:
           ·当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。

           ·重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。

            要解决这个问题也不是很复杂,但是不能为了解决这个问题给系统带来更多的麻烦,所以需要制定如下目标:

            ·减少重建缓存的次数

            ·数据尽可能一致。

            ·较少的潜在危险

         ①互斥锁:此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可,整个过程如图所示。


    下面代码使用Redis的setnx命令实现上述功能:


    1)从Redis获取数据,如果值不为空,则直接返回值;否则执行下面的2.1)和2.2)步骤。

    2.1)如果set(nx和ex)结果为true,说明此时没有其他线程重建缓存,那么当前线程执行缓存构建逻辑。

    2.2)如果set(nx和ex)结果为false,说明此时已经有其他线程正在执行构建缓存的工作,那么当前线程将休息指定时间(例如这里是50毫秒,取决于构建缓存的速度)后,重新执行函数,直到获取到数据。

      ②永远不过期

    “永远不过期”包含两层意思:
             ·从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。

             ·从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。

        从实战看,此方法有效杜绝了热点key产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致。


                                  

                                                         两种热点key的解决方法



    展开全文
  • Redis缓存设计及常见问题 缓存能够有效地加速应用的读写速度,同时也可以降低后端负载,对日常应用的开发至关重要。下面会介绍缓存使 用技巧和设计方案,包含如下内容:缓存的收益和成本分析、缓存更新策略的选择和...
  • 缓存设计可谓老生常谈了,早些时候都是采用memcache,现在大家更多倾向使用redis,除了知晓常用的数据存储类型,结合业务场景有针对性选择,好像其他也没有什么大的难点。 工程中引入Redis Client二方包,初始化一个...
  • 1.什么是系统设计?(System Design) 系统设计是一个定义系统架构、模块、接口和数据满足特定需求的过程。 比如设计一个短网址服务、评论服务、Feed流系统、抢红包系统。 微服务架构很多系统被按照业务拆分,需要...
  • 缓存设计的一些问题

    2012-02-29 12:23:27
    互联网架构中缓存无处不在,某厂牛人曾经说过:”缓存就像清凉油,哪里不舒服,抹一下就好了”。高品质的存储容量小,价格高;低品质存储容量大,价格低,缓存的目的就在于”扩充”高品质存储的容量。本文探讨缓
  • 缓存设计,LIRS,cache锁粒度

    千次阅读 2013-08-24 16:50:23
    互联网架构中缓存无处不在,某厂牛人曾经说过:”缓存就像清凉油,哪里不舒服,抹一下就好了”。高品质的存储容量小,价格高;低品质存储容量大,价格低,缓存的目的就在于”扩充”高品质存储的容量。本文探讨缓存...
  • Cache缓存设计的一些思考

    千次阅读 2013-10-06 15:45:37
    互联网架构中缓存无处不在,某厂牛人曾经说过:”缓存就像清凉油,哪里不舒服,抹一下就好了”。高品质的存储容量小,价格高;低品质存储容量大,价格低,缓存的目的就在于”扩充”高品质存储的容量。本文探讨缓存...
  • 缓存设计的一些思考 Posted by chuanhui on 2011 年 06 月 19 日Leave a comment (9)Go to comments 互联网架构中缓存无处不在,某厂牛人曾经说过:”缓存就像清凉油,哪里不舒服,抹一下就好了”...
  • 缓存设计可谓老生常谈了,早些时候都是采用memcache,现在大家更多倾向使用redis,除了知晓常用的数据存储类型,结合业务场景有针对性选择,好像其他也没有什么大的难点。 工程中引入Redis Client二方包,初始化...
  • 缓存所用的设计模式

    千次阅读 2014-11-24 16:45:33
    缓存简单的讲,缓存就是将默写资源或者数据会频繁会被使用到的数据或者资源存储在系统外,比如数据库、硬盘文件等,那么每次操作这些数据的时候都从数据库或者硬盘上去获取,速度会很慢,会造成性能问题。...
  • 前方: ...最近这几天,自己在大脑里演练过各种技术难点,解决方案,推敲了各种该解决的问题,觉的差不多了,才决定撸码。 忽然发觉,原来代码是可以写在大脑里的。 要是你看到一个员工坐着2天没写
  • java中的缓存思想与相应的设计模式

    万次阅读 2012-10-16 19:49:15
    这几天刚做完项目以后,又看起关于设计模式的书籍,在学习的过程中,频繁见到缓存,而在s2sh框架中, 也会经常使用到一级缓存与二级缓存,实现原理与实现的响应机制在此进行相应的总结: 简单的讲,缓存就是将默写...
  • 云音乐曲库缓存随着多年的实践、改善,结合曲库数据的特点,形成...缓存是系统设计中,用于提升底层系统访问能力的一种技术手段,它同样作用于云音乐的各个系统中,一种常用的缓存使用调用链路如下: 转化为时序图.
  • 集中式缓存

    千次阅读 2018-07-07 21:34:17
    这篇文章我们继续聊聊《深入分布式缓存:从原理到实践》中关于集中式缓存的部分。 集中式缓存的基本概念,我在前面的文章 本地缓存 中已经有过介绍,相对于本地缓存,集中式缓存主要有以下几个优点: 保证缓存...
  • 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),...
  • 缓存雪崩 数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机。 比如一个雪崩的简单过程: 1、redis集群大面积故障 2、缓存失效,但依然大量...
  • 51CTO也做了专门的报道,我有幸在6年前主持开发过日流量千万级别的系统,所以也在微博发表了一下自己的观点(具体详见 http://os.51cto.com/art/201111/300247.htm)不是每个人都有幸亲自参与并主持设计大型网络应用...
  • 文章目录Redis 高可用、缓存不一致、缓存雪崩缓存雪崩数据失效对数据库访问限流,返回异常码容错降级,返回异常码针对内存不足,采用 Redis 集群方案缓存击穿布隆过滤器(Bloom Filter)Redis 实现布隆过滤器高可用...
  • 缓存和数据库同步问题解决方案

    千次阅读 2020-08-01 23:07:04
    本文通过借鉴计算机系统结构上缓存设计模式,并结合互联网应用下的场景给出缓存和数据库同步问题解决方案。 首先简单来一个读缓存的场景 首先很简单 public Object read(String key){ Object da
  • web缓存

    2017-04-29 01:46:40
    一、缓存是什么? Cache ①高速缓冲存储器,其中复制了频繁使用的数据以利于快速访问。 ②位于速度相差较大的两种硬件/软件之间,用于协调两者数据传输速度差异的结构 二、缓存有哪...
  • 缓存技术概述

    2017-05-05 17:03:39
    从宏观上讲解缓存的不同层次
  • java 缓存技术

    2015-05-22 13:40:57
    java 缓存技术 装载 一、什么是缓存 1、Cache是高速缓冲存储器 一种特殊的存储器子系统,其中复制了频繁使用的数据以利于快速访问 2、凡是位于速度相差较大的两种硬件/软件之间的,用于协调两者数据传输速度...
  • 我们将活动内容总结为设计原则、技术选型和五大难点攻克。 前篇见:银行跨数据中心数据库双活架构设计设计原则及技术选型(点击标题可读) 本篇交流分享者——本次活动专家: 孔再华 民生银行 数据库架构师 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,262
精华内容 8,504
关键字:

缓存设计难点