缓存不是系统架构的必选项,只有在遇到性能瓶颈的业务场景,才可能需要引入缓存。引入缓存时,要从“大处着眼、小处着手”,即首先要从宏观考虑整体的缓存场景、缓存层次及缓存(更新/同步)策略,然后从局部考虑选择合适的缓存组件及使用方式(数据结构、分布、部署),并指定缓存系统的SLA,最后在系统运行过程中,要对缓存系统进行监控报警,还要根据业务发展、访问规模的变化,不断的对缓存架构进行优化及演进。
缓存引入前的考量
缓存引入前,首先要分析业务的应用规模、访问量级。对于规模不大的小系统或系统发展初始阶段,数据量和访问量不大,引入缓存并不能带来明显的性能提升,反而会增加开发、运维的复杂性。另外对于单条数据尺寸比较大的业务,如图片系统,虽然访问量可能较大,但更佳爱的选择可能是分布式文件系统而非缓存。而对于具有一定规模的常规业务系统(特别是互联网系统),数据规模、访问量较大,且持续快速增加,要保证服务的稳定性,在突发流量下也能快速响应用户请求,就需要把频繁读写的数据从磁盘访问变为内存访问,也就是说需要引入缓存。
其次还要分析系统架构,在系统架构中合适的模块、层次引入缓存,并考虑各个缓存系统之间的更新方式、一致性保障策略等,让缓存系统和其他架构层(如持久层、分布式文件层)有机结合。
缓存组件的选择
缓存的种类很多,我们实际使用时,需要根据缓存位置(系统前后端)、待存数据类型、访问方式、内存效率等情况来选择最适合的缓存组件。本小节接下来将主要探讨在应用层后端如何选择分布式缓存组件。
一般业务系统中,大部分数据都是简单KV数据类型,如前述微博Feed系统中的feed content、feed列表、用户信息等。这些简单类型数据只需要进行set、get、delete操作,不需要在缓存端做计算操作,最适合以memcached作为缓存组件。
其次对于需要部分获取、事务型变更、缓存端计算的集合类数据,拥有丰富数据结构和访问接口的Redis也许会更适合。Redis还支持以主从(master-slave)方式进行数据备份,支持数据的持久化,可以将内存中的数据保持在磁盘,重启时再次加载使用。因磁盘缓存(diskstore)方式的性能问题,Redis数据基本只适合保存在内存中,由此带来的问题是:在某些业务场景,如果待缓存的数据量特别大,而数据的访问量不太大或者有冷热区分,也必须将所有数据全部放在内存中,缓存成本(特别是机器成本)会特别高。如果业务遇到这种场景,可以考虑用pika、ssdb等其他缓存组件。pika、ssdb都兼容Redis协议,同时采用多线程方案,支持持久化和复制,单个缓存实例可以缓存数百G的数据,其中少部分的热数据存放内存,大部分温热数据或冷数据都可以放在磁盘,从而很好的降低缓存成本。
对前面讲到的这些后端常用的缓存组件,可以参考下表进行选择。
缓存组件 | 数据类型 | 访问方式 | 数据容量(单实例) | 同步 | 内存效率 |
---|---|---|---|---|---|
Memcached | 简单KV | GET SET DEL等常规接口 | 100GB以下 | Client多写 | 一般 |
Redis | 丰富 | 更丰富的常规接口、事务更新等 | 30GB以下 | 主从复制 | 一般 |
Pika/ssdb | 较丰富,部分Redis数据结构不支持 | 较丰富 | 数百GB以上 | 主从复制 | 一般 |
最后,对于对存储效率、访问性能等有高要求的业务场景,结合业务特性进行缓存组件的定制化设计与开发,也是一个很好的选择。
总之,缓存组件的选型要考虑数据模型、访问方式、缓存成本甚至开发人员的知识结构,从而进行因地制宜的取舍,不要盲目引入不熟悉、不活跃、不成熟的缓存组件,否则中途频繁调整缓存方案,会给开发进度、运维成本带来较大的挑战。
缓存架构的设计
确定缓存组件后,首先需要考虑结合业务场景,设计业务数据对应的缓存结构及缓存容量规划。如微博在用Memcached缓存feed content时,最初采用json、xml格式进行缓存,后来又改为更紧促的PB(protocol buffer)结构;而对feed content的容量规划设计,则包含了content的平均size、缓存数据量、峰值读写QPS、命中率、过期时间、平均穿透加载时间等。
其次要结合缓存组件特点,设计缓存的读写策略、分布策略、过期策略等。如在海量数据、大并发访问场景下使用Redis时,要考虑如何在主从之间进行读写分离,要考虑key的hash算法、分布策略,以使数据分散和请求访问更均衡,同时还要考虑如何让冷数据/过期数据更快从内存剔除(如主动删除冷数据、低峰scan清理过期数据),让更多的热数据常驻内存,从而确保缓存有持续的高命中率。
最后还要从开发、运维等角度,设计缓存的一致性、高可用性方案。如微博在memcached对feed vector进行更新时,如果该vector不存在则不更新,待有请求时再从持久层获取最新数据,如果该vector存在,则通过MAIN层的CAS、HA层/L1层的set来进行更新和一致性保证。又如微博对Redis采用DNS方式进行主从的访问及运维,如果是master故障,运维系统可以根据策略快速选择新的master,并将其他所有slave指向新的master,并更新DNS,确保Redis缓存的快速恢复;而对于slave故障,则直接将其摘除slave DNS即可,访问基本不受影响。
缓存系统的监控及演进
在系统运行过程中,需要对缓存系统进行实时监控报警,在缓存组件出现故障或无法满足(突发流量)需求时,进行修复及快速扩展。监控可以采用集中探测的方式,也可以采用分布式汇报的方式,具体探测方案需要根据监控系统的特点进行确定。
另外,随着业务发展和访问规模的变化,缓存架构也需要不断进行优化及演进。如在微博发展过程中,单个核心数据的访问量从万级别增加到十万、百万级别,同时突发事件可能在短时间带来30%以上的访问流量。微博为此在memcached先后引入Main-HA、L1-Main-HA多层结构,以确保缓存系统持续的高可用性。