[框架]缓存设计

[框架]缓存设计

second60  20181031

目录

[框架]缓存设计

1 . 缓存层目的

1.1缓存能够加速读写速度

1.2 降低后端负载

3 缓存的使用场景

3.1 加速请求响应,减少查数据库时间,优化高并发

3.2 开销大的复杂计算分离

3.3 通用缓存

4 缓存更新策略

4.1 LRU/LFU/FIFO算法剔除

4.2 超时删除

4.3 主动更新

4.4 总结

5 缓存粒度

6 缓存优化

6.1 缓存穿透优化

6.1.1 缓存穿透的问题

6.1.2 缓存穿透的原因

6.1.3 如何处理缓存穿透

6.2 缓存的无底洞现象

6.2.1 无底洞现象的原因

6.2.2 无底洞优化

6.3 缓存雪崩

6.3.1 缓存雪崩优化

6.4 热点key


背景:本文基于redis缓存数据库

1 . 缓存层目的

1.1缓存能够加速读写速度

通常数据都在数据库,或读取本地文件,都是在磁盘中,速度相比内存,是非常慢的。缓存是在内存中,速度比内存快N倍。

至于快多少倍,引用google 工程师Jeff Dean 首先在他关于分布式系统的ppt文档列出来的,到处被引用的很多。

1纳秒等于10亿分之一秒,= 10 ^ -9 秒 

-----------------------------------------------------------

Numbers Everyone Should Know

L1 cache reference 读取CPU的一级缓存

0.5 ns

Branch mispredict(转移、分支预测)

5 ns

L2 cache reference 读取CPU的二级缓存

7 ns

Mutex lock/unlock 互斥锁\解锁

100 ns

Main memory reference 读取内存数据

100 ns

Compress 1K bytes with Zippy 1k字节压缩

10 us

Send 2K bytes over 1 Gbps network 在1Gbps的网络上发送2k字节

20 us

Read 1 MB sequentially from memory 从内存顺序读取1MB

250 us

Round trip within same datacenter 从一个数据中心往返一次,ping一下

500 us

Disk seek  磁盘搜索

10 ms 

Read 1 MB sequentially from network 从网络上顺序读取1兆的数据

10 ms

Read 1 MB sequentially from disk 从磁盘里面读出1MB

30 ms 

Send packet CA->Netherlands->CA 一个包的一次远程访问

150 ms

我们关注一下内存 和 磁盘的访问速度, 随机访问相差 10ms/ 100ns =10 000 000 ns/100ns = 1000 00倍

总结下,速度顺度:

CPU > register >  L1 cache  >  L2 cache  >  .. >  Ln Cache > RAM(内存) > 磁盘

内存的速度远远大于磁盘,这也是缓存层产生的最主要的原因。

 

1.2 降低后端负载

加入缓存后,可以大大地减少数据库的访问,从而避免高并发对数据库来的压力,无论是读写次数都会大大地减少,同时增强了应用后台的承载能力。

 

2 缓存层成本

2.1有无缓存架构对比

左边的是无缓存的架构

右边的是有缓存架构

 

缓存架构: 即在访问存储层中间,添加一个缓存层,当业务层需要访问存储层时,会先去缓存层查找,如果查到(缓存命中)立即返回业务层,如果无缓冲(缓存未命中),才去存储层查找,如果查找(存储层命中),则写入缓存层,并返回给业务层。如果未查找到,直接通知业务层。

 

2.2 成本

添加任何功能,都是需要成本的,缓存层也一样,但添加后的用途远远大于成本,那么就是可行的。下面分析下添加缓存后的成本。

2.2.1 数据不一致

因为缓存层和存储层的数据存在一定的更新时间差,所以会在时间窗口内存在不一致性,时间窗口跟更新策略有关。

例:当业务要更新数据时,如果先更新缓存层,再更新存储层,那么在更新缓存层后,更新存储层前,会存在数据的不一致。

 

​​​​​​​2.2.2 代码维护成本

加入了缓存后,需要同时处理缓存层和存储层的逻辑,增大开发维护成本

 

​​​​​​​2.2.3 运维成本

缓存层,可以是单一模式,也可能主从模式,或者是集群模式,无论是哪种植模式,都会要部署,维护,同步,故障处理,恢复等,增加了运维成本。

 

3 缓存的使用场景

3.1 加速请求响应,减少查数据库时间,优化高并发

操作数据库,一般都是有限制的,连接数限制,网络的限制,磁盘速度的限制,即使一个简单的查询,添加了字典,数据库缓存,但速度也是比不上直接从内存中读取。

当数据库可能会成为整体框架的瓶颈时,就要考虑是否添加缓存层,减轻存储层的压力,提高效率。

 

3.2 开销大的复杂计算分离

1. 如果操作数据库的计算非常复杂,非常耗性能,可以考虑把复杂的计算用缓存来处理,减少数据库的负担。比如:一个SQL关联很多表,很多分组等计算,可以通过缓存层来计算并缓存结果等

2. 如果业务逻辑涉及的处理非常耗时,比如:计算排行榜,可能要从所有数据中选出TOP100数据,是非常耗时的,或者做一个实时排行榜,可以直接用redis的sorted_set实现,效率非常快。

 

3.3 通用缓存

1. 在分布式框架中,要缓存公共数据,多个进程或服务共用等,考虑用缓存,避免用网络直接发送读写。

2. 在缓存中,一般都是统一格式,如redis中统一用json等,避免多服务序列化问题。

 

4 缓存更新策略

缓存中的数据和存储层中的数据可能存在不一致问题,所以需要某些策略进行更新或删除。

 

4.1 LRU/LFU/FIFO算法剔除

当缓存数据量大于预设的缓存大小时,需要对一些数据进行删除处理。

通常方法有:

LRU - 最近最久未使用优先删除

LFU - 最不经常使用优先删除

FIFO - 先进先出方法删除

 

4.2 超时删除

给缓存数据设置一个过期时间,在过期时间后,系统自动删除过期数据。

 

4.3 主动更新

对数据一致性要求高,需要在操作后,立即更新数据到缓存和存储层。

 

4.4 总结

低一致性:建议配置最大内存和淘汰策略

高一致性业务:使用超时剔除和主动更新

 

5 缓存粒度

添加了缓存层,缓存是在内存当中,所以他的容量是一定的。但相对于,数据库而言,可能数据量非常大,不可能全部加载到缓存层。(当然数据小的时候可以)因此,要考虑缓存内容维度的问题,也叫缓存粒度。

 

比如:很大的数据表,当缓存时,你是缓存整条记录,还是只缓存用到的某几个字段。

select  * from user  where id={id}

 

全部缓存到redis中

set  user:{id} ‘select * from user where id={id}’

 

缓存重要的列

set user:{id} ‘select uid,username,..... from user where id = {id}’

 

数据类型

通用性

占用空间(内存+网络)

代码维护

全部缓存

简单

部份缓存

复杂

 

6 缓存优化

6.1 缓存穿透优化

什么是缓存穿透?

缓存穿透指查询一个不存在的数据,缓存层和存储层都不命中(穿透)。

 

6.1.1 缓存穿透的问题

缓存穿透,意味着很次还是要去存储层查找,失去了缓存层的意义,同时加大了后端的负载(即要查缓存层,又要查偏存储层),如果出现大量的穿透, 甚至可能造成存储层瓶颈或宕机。

 

6.1.2 缓存穿透的原因

1. 自身业务代码出现问题

正常来说不应该出现穿透存储层问题。大部份数据有效性检查,都应在逻辑入口处检查并处理完,当然也有个别情况。所以业务出现不正常的批量穿透,应当查出问题原因。

 

2.恶意攻击/爬虫等大量空命中。

某些接口被恶意循环攻击,一般出现这问题,业务代码是一方面,别一方面,应该从入口处来限制或禁用攻击。

 

6.1.3 如何处理缓存穿透

1 对自身业务逻辑的入口限制

当出现缓存命中率低,存储命中率低时,先对出问题的业务进行查找出原因,然后对入口出进行限制。如果限制不了,再用下面的方法。

 

2 缓存空对象

当缓存未命中,存储层也未命中时,我们把空对象(可能是一个查询id),缓存到缓存层,下次再缓存中查找时,直接返回空对象。避免了再查存储层。

缓存空对象产生的问题:

  1. 空对象做了缓存,占用了内存空间(如果是攻击,问题更严重),解决方法就是设置过期时间。
  2. 缓存层和存储层会有一段时间数据不一致,可能会对业务有影响。但可以代码主动存储来避免。

 

3 布隆过滤器拦截(https://en.wikipedia.org/wiki/Bloom_filter)

一开始,大家都可能不知道啥叫布隆过滤器,其实就是布隆想出来的一个想法。就是预先把所有的key存储起来,当作一个过滤器,过滤器有的就通行,没有的就不存在。

 

布隆过滤器怎么存?

可以用位的方式,每位表示一个id,存储到一段预分配的内存中。可以定时更新布隆过滤器,也可以实时更新。

如:利用Redis的Bitmaps实现布隆过滤器

​​​​​​​

适用于数据命中不高,数据相对固定,实时性代的场景,代码维护复杂,但缓存空间小。

 

 

解决缓存穿透

适用场景

维护成本

缓存空对象

数据命中不高

数据频繁变化实时性高

代码简单

需过多缓存空间

数据不一致

布隆过滤器

数据命中不高

数据相对固定实时性低

代码维护复杂

缓存空间小

6.2 缓存的无底洞现象

什么是缓存的无底洞呢?

当缓存节点达到一定量后,继续增加,会发现性能不但没有好转反而降了,称为无底洞现象。(主要针对集群场景)

 

6.2.1 无底洞现象的原因

在集群环境中,key都是分散到各个节点,但由于数据量和访问量的增长,大量节点做水平扩容,导致键值分布到更多不同的节点,当批量操作时,可能从不同节点上获取信息。

在单节点情况下,时间 = 一次网络往返 + 一次存取的时间

但多节点情况存取的时候,时间 = 多次网络往返 + 多次存取时间

由上面分析,无底洞现象,存在于集群多节点环境,批量操作中。涉及节点越多,效率就会越慢。节点多不代表高性能,投入越多不一定产出越多,也就是所谓的无底洞。

 

6.2.2 无底洞优化

从上面可以看出,优化无非两方面:网络往返次数和节点操作次数。减少网络往返次数和节点操作次数,可以提高效率。

 

结合redis cluster说明

 

1. 串行命令(多次get, mget)

假如要得到n个key,分别在m个节点上,那么

操作时间 = n*网络往返 + n *命令时间

缺点:效率最差

 

2. 串行IO

假如要得到n个key, 分别在m个分点上,那么,可以先计算出key分别在哪几个节点上。再用mget或pipeline操作。

操作时间 = node次网络往返 + n次命令时间

缺点:效率差

比1优.

 

3. 并行IO

改用多线程执行,网络操作时间为O(1)

操作时间= 1次最慢节点网络返回 + n次命令

缺点:代码复杂

 

4. hast_tag实现

采用hash_tag,把多个key存储在一个节点上,取数据时,相当于在单节点操作。

操作时间 = 1次网络 + n次命令时间

优点:效率最好

缺点:可能造成分布不均问题

 

6.2.3 方案对比

方案

优点

缺点

网络IO

串行命令

编程简单

如果keys少,性能可满足

大量keys请求延迟严重

O(keys)

串行IO

编程简单

少量节点,性能能满足

大量node延迟严重

O(nodes)

并行IO

利用并行特性,延迟取决非于最慢的决点

编程复杂

多线程,定位难

O(max_slow(nodes))

hash_tag

性能最高

业务维护成本较高

容易数据倾斜

 

6.3 缓存雪崩

什么是缓存雪崩?

由于缓存层承载着大量的请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务(如很多key同时过期,缓存服务失效),于是所有请求都会到达存储层,存储层调用暴增,可能会造成存储层宕机情况。称为缓存雪崩!!指缓存层失效后,流量会像奔逃的野牛一样,打向后端存储。

6.3.1 缓存雪崩优化

1 保证缓存层的高可用性。

缓存层宕机,那么是非常可怕的,一定要避免。如:机房宕掉,网络不络问题等。

redis中提供了sentinel(哨兵) -可以临视和切换主从

提供了cluster(集群)-水平扩展和故障迁移

 

2 依赖隔离组件为后端限流并降级。

当故障出现时,可以降级采用替代方案处理。

当故障出现时,可以限流处理,限定最大处理的峰值。即使业务处理慢,也不会使整个业务不可用。

 

3  提前演练

在项目上线前,演练缓存层宕掉后,后端的负载以及可能出现的问题,在此基础上做一些预案。(A方案不可用时采用B计划)

 

 

6.4 热点key

热点key 即是一个使用非常频繁的key, 并发量非常大。

通常会用 缓存 + 过期时间 的策略来加速数据的读写,又保证数据的定期更新。

 

6.4.1 热点key问题

当一个热点key某一时间失效期间,如果大量并发,将会造成后端负载的一时瓶颈甚至崩溃。

重建热点key又不能在短时间内完成,可能一个复杂的计算,耗时上分数。

 

6.4.2 热点key解决

减少重建缓存的次数

数据尽可能一致

 

1  重建热点key时,加互斥锁

2  永久热点key,永不过期

a 从缓存层看,无过期时间

b 从功能层看,key过期了,会重建。但重建过程中,会数据不一致。

c 独立逻辑去更新过期的key,不是业务里去重建。

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值