目录
一、对缓存中间件的诉求
1.1 我们为什么需要缓存中间件
我们一直使用关系型数据库作为我们几乎是唯一的数据存储方案。关系型数据库在对复杂结构的数据的组织上、持久性和一致性控制上有巨大的优势。但磁盘数据库无论如何进行查询优化,速度上终究无法和内存读写相提并论。而随着客户数据量越来越大、并发量越来越高、客户场景越来越复杂,我们对数据访问效率的要求也在提高。此时,引入缓存中间件成了我们一定要考虑的事情。
在2022年,一听到缓存中间件,我们首先想到的依然是redis。但我们团队长期以来并没有充分地利用起redis提高系统性能,依然大量依赖于关系型数据库处理数据的存储和读写。为提高系统整体性能,尝试引入新的缓存中间件解决我们的问题。
1.2 缓存的分类
我将缓存解决方案划分为两大类别:弱势缓存和强势缓存。
1.1.1 弱势缓存
第一类,是以Redis为首的弱势缓存。这类缓存强调的是极高的读性能和写性能,一般作为高并发场景下,高速的应用服务和较低速的磁盘数据库之间的缓存。
弱势缓存为提高读写效率,舍弃了强一致性,追求最终一致性,数据结构简单,因此,基于弱势缓存设计的应用系统,通常以磁盘关系型数据库的数据为准,缓存中更倾向于存储一些相对静态稳定的基础数据,用于辅助关系型数据库。对于数据的更新模式,也更偏向于追加,而不是大并发下的频繁更新,应用系统不会完全信任缓存中的数据。这种效率优先,对数据准确性要求不高的方向与如今的互联网行业(尤其是toC领域)的需求十分契合。
1.1.2 强势缓存
而对于业务数据模型较复杂,对数据实时性和准确性要求较高的金融行业、企服行业,更需要的是一个强一致性的数据存储,在引入缓存之前,关系型数据库承担了这一角色。
业务特性导致我们几乎无法接受为了读写效率牺牲数据准确性。复杂业务场景下,我们需要一个强一致性和高实时性的、对数据结构有更强的表达能力的,能描述部分逻辑表达的内存数据库。能满足这些要求的产品,我称其为强势缓存。因为这些特性可以让内存数据库作为我们数据的基准,而让关系型数据库作为一个持久化的备份,进一步降低磁盘的访问率,提高内存的存在感,让一个完整业务流程中的数据流转可以在内存中可靠地完成。
二、什么是Apache Geode
Apache Geode正是满足我给出的强势缓存定义的一款内存数据库产品。它是商业内存数据网格Geofirm的开源版本,已经在金融支付领域和12306等大型订购网站中经受住了考验。
2.1.2 服务发现
Apache Geode提供了定位器(Locator)进程,为参与缓存的所有成员(Client、Server、其它的Locator)提供其他成员的发现和负载均衡。Locator既可以和其他Geode进程部署在一起,也可以独立部署,独立部署可以更好地保证定位器的可靠性和可用性(因为一旦一起的Geode进程挂了,这个Locator也很难幸免于难)。可以部署多个Locator共同起作用,client连接时可以选择连接到哪个Locator上。
Client可以配置连接到哪个Server服务器,但更合理的配置方式是连接到某个Locator上,由这个Locator为Client分配一个负载较低的Server,Client启动后只会和Locator沟通一次,在获知被分配到的Server的IP和端口之后,每次读写都会直接连接到Server上。
2.1.3 数据存储形式和区域
市面上大部分的内存数据存储,都将数据按键值对的格式进行存放,Geode也是如此。但与Redis等简单的KV不同,Geode将KV数据们按数据区域(Region)进行组织。对于不同的区域可以单独配置(如是否分区或需要副本)。
数据区域可以类比于关系型数据库中的表的概念,是一系列结构相同的数据结构的集合。实际上,在实现上数据区域就是一个ConcurrentMap<K, V>,其键就是一条数据的唯一性标识(类型任意,只要重写了equals和hashcode以便于Region确认键的唯一),其值是一个表达完整数据概念的对象,这样其实也让一条数据中按类的成员又划分出了列的概念。基于这种类似关系型数据库的存储模式,Geode提供了一种类似于SQL的查询语言,称为OQL,并支持多区域查询(类似于连表查询)。
下面是一个OQL查询的小例子:
class DictPlatform implements DataSerializable {
short platformId;
String name;
String status;
}
class TestServiceImpl {
public void query() {
String queryString = "SELECT dp.platformId, dp.name FROM /dict_platform dp WHERE dp.status >= 0;"
QueryService queryService = cache.getQueryService();
Query query = queryService.newQuery(queryString);
SelectResults results = (SelectResults)query.execute();
DictPlatform p = (DictPlatform)results.iterator().next();
}
}
2.1.4 数据量的控制和热点数据
Geode有两种模式控制内存中的数据规模,持久化和失效。但无论如何,热点数据都通过最近最少使用(LRU)算法来判断。
驱逐(Eviction) 和 到期(Expiration) 是可配置的两种数据失效的方式,总的来说,失效就是值当数据满足某些条件时,就从内存中删除掉。
- 驱逐
驱逐是指当内存中的数据量到达一定阈值时,将LRU队尾的数据销毁。这个阈值可以是条数或占用的内存大小。驱逐和溢出并不是互斥的,溢出实际上是一种不会丢失数据的驱逐。 - 到期
到期是指按时间销毁掉冷数据,到期有两种计算方式,一种是从数据创建或更新开始计算,被称为TTL(Time to live),另一种是上次被访问开始计算,被称为Idle timeout。第二种更适用于热点数据的存储,第一种更适合于业务要求的定时失效的数据。