缓存使用规范

前言:有赞开始制定自己的技术规范,应邀请,笔者负责“缓存使用规范”的制定,摘去部分保密内容,分享给大家。

一、为什么要使用缓存

1、业务视角

根据谷歌的一项研究,假如一个网站在3秒钟或更短时间内没有加载成功,会有 53% 的手机用户会离开。因此在提高应用程序的速度和性能上,每一毫秒都很重要,这直接影响了业务的留存率、转化率。

2、技术视角

大型网站的特点有高并发、大流量、用户分布广泛、网络情况复杂等,缓存正是系统优化的重要技术手段之一。它能够降低系统延迟,缓解数据库压力,提高系统的整体可用性、并发性、可伸缩性等。

二、缓存的使用场景

①读多写少的场景,如配置信息、商品信息、用户信息等。

②不追求数据强一致性的场景,大多数业务场景能接受短时间的不一致,但账户金额操作等重要数据的场景不建议使用。

三、缓存的使用模式

1、Cache-Aside

即业务代码围绕着Cache写,是由业务代码直接维护缓存,一般使用这种方式。

2、Cache-As-SoR

所有的操作都是对Cache进行,然后再委托给SoR进行真实的读/写。即业务代码中只看到Cache的操作,看不到关于SoR(system of record)相关的代码。共有三种实现方式:

  • Read-Through,业务代码首先调用Cache,如果Cache不命中,由Cache回源到SoR,而不是业务代码(即由Cache读SoR)。Guava Cache和Ehcache 3.x都支持该模式。
  • Write-Through,被称为穿透写模式/直写模式。业务代码首先调用Cache写(新增/修改)数据,然后由Cache负责写缓存和写SoR,而不是业务代码。Ehcache 3.x支持。
  • Write-Behind,也叫Write-Back,我们称之为回写模式。不同于Write-Through是同步写SoR和Cache,Write-Behind是异步写。异步之后可以实现批量写、合并写、延时和限流。

四、多级缓存

1、什么是多级缓存

多级缓存,是指在整个系统架构的不同系统层级进行数据缓存,以提升访问效率,这是应用最广的方案之一。一般分为接入层缓存、应用层缓存,应用层缓存又分为分布式缓存、本地缓存,本地缓存一般又分为堆内缓存、堆外缓存、磁盘缓存等。

2、接入层缓存

接入网关层,可以使用OpenResty可以做一些缓存前置的功能,查询到缓存结果后直接返回,无需走到应用层。

3、本地缓存

在商家大促场景下,极少数热点数据可能造成服务器压力过大,导致服务器性能、吞吐量、带宽达到极限,出现响应慢或者拒绝服务的情况,这肯定是不允许的。常用的本地缓存有:

  • 堆内缓存;最快,没有序列化/反序列化,但GC暂停时间会变长。支持的开源组件:Guava Cache、Ehcache 3.x、MapDB。
  • 堆外缓存;比堆内缓存慢,需要序列化/反序列化,可以减少GC暂停时间,支持的开源组件:Ehcache 3.x、MapDB。
  • 磁盘缓存;比堆外缓存慢,但支持的存储量最大。支持的开源组件:Ehcache 3.x、MapDB。

有赞内部TMC组件提供了应用层热点探测、本地缓存等能力,可以直接接入。

4、分布式缓存

建议直接使用KVDS(有赞内部自研KV组件)

原有使用codis的业务,建议尽快迁移到KVDS。

(1)key名设计

①可读性;key命名需要方便管理,并防止冲突,建议采用风格“字段1(应用):字段2(业务):字段3(特征):primarykey”的格式进行命名。

示例:

account:user:info:xxxxx


③不要包含特殊字符;如空格、换行、单双引号以及其他转义字符。②简洁性;保证语义的前提下,控制key的长度,单词较多时可以采用缩写,当key较多时,内存占用也不容忽视。

(2)value设计

①选择适合的数据类型。绝大部分场景String类型可以满足,注意节省内存和性能之间的平衡。

②value值越小越好,建议不超过1KB。value越大,服务端能承受qps越低,请求耗时越大。当value大小超过1MB时,读写性能将严重受影响。string类型控制在10KB以内,hash、list、set、zset元素个数不要超过2000。

③value值较大的场景,建议参考下文”常见问题-bigkey“的方案进行优化。

(3)TTL

①缓存场景必须设定TTL,保证命中率的前提下,越短越好。

②建议 TTL < 3小时,具体可结合业务场景设置。

(4)超时设置

普通kv接口:单key请求超时时间建议设置100ms左右,批量请求超时时间建议设置200ms以上。
特殊数据结构接口:超时时间建议设置500ms左右。

(5)超时处理

写超时:理论上除非服务端压力过大丢弃请求,否则所有写请求在服务端都会执行成功,只是没有返回结果给客户端,除非业务逻辑依赖数据的强一致性,否则不需要重试,可以交由下一个业务请求写入缓存。
读超时:重试不要超过3次。

(6)命令使用

①O(N)命令关注N的数量;例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。

②使用批量操作提高效率,例如mget、mset。注意控制一次批量操作的元素个数(例如100以内,实际也和元素字节数有关)。

③合理使用select;redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。

④Redis事务功能较弱,不建议使用;Redis的事务功能较弱(不支持回滚),而且要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。

⑤禁止线上使用keys、flushall、flushdb等

五、常见问题

1、缓存一致性

一般只有查询的操作才可以写入缓存,任何对数据修改的操作不要主动刷新写缓存,而是删除缓存。具体的缓存删除、写入机制可以根据业务特性进行调整,但一定要确保数据的一致性。

2、bigkey

①review业务场景的使用姿势是否合理,业务上有没有优化的余地。

②对Value进行压缩处理(压缩后的大小能够接受的情况下可以采用这种方案)。

③维度化缓存,将Value拆分为多个小Value,按需获取,客户端再进行查询、聚合。

④考虑使用多线程缓存,如Memcached来缓存大Value。(有赞KV是多线程,但同样不建议使用大key,优先考虑上述的三种方式,避免大key)

注意:禁止使用大key。如果出现bigkey的情况,应及时和运维沟通,交由较专业的技术同学处理,禁止直接del命令删除。

3、缓存热点

如果业务场景可能存在缓存访问量过大造成热点的场景,建议直接接入有赞内部TMC组件:http://paas.qima-inc.com/docs/tmc/

4、缓存穿透

NULL值也存入缓存。应用内部可以考虑构建一个抽象的缓存对象,数据不是直接存储到缓存,而是放入缓存对象后再存储进缓存。

5、缓存击穿

①双重检查锁创建缓存(根据业务场景,可以选择分布式锁,也可以考虑本地锁)

③后台主动预刷入缓存

6、缓存雪崩

①pass组会保障缓存机器实例的高可用性。

②后台主动预热刷入缓存,如大促活动等需要预先给大商家刷入缓存、过期前预刷取等。

③针对可能出现大量缓存集中失效的场景,缓存过期时间随机打散。

你可能感兴趣的:(系统架构,分布式)