1. 数据存储层
我们介绍过,金融交易系统具有海量数据的特点,但是也并发每种类型的数据都是海量的,而且不同数据对于数据的延时要求不同,即使对于同一种类型的数据,由于使用场景的不同,也会对数据的延时性要求有很大的差异。
基于上述的特点,造成了金融数据的存储方式也存在很对的差别(待完善)。
数据名称 | 使用场景 | 延时要求 | 数据量 | 存储方式 |
---|---|---|---|---|
3年内行情 | 亚秒 | 70亿条/15T | Ignite | |
3年内行情 | 曲线拟合 | 秒级 | 50亿条/8T | Cassandra |
3年内行情 | 策略验证/回测 | 分钟级 | 50亿条/8T | Cassandra |
3年外行情 | 存档 | -- | 千亿条 | Hive |
报价 | 做市报价 | 亚秒 | 亿条/3T | Ignite |
监控数据 | 监控大屏 | 分钟级 | Hive | |
风控数据 | 风控数据展示 | 秒级 | Hive/Mysql | |
利率曲线 | 亚秒 | Ignite | ||
收益率曲线 | 亚秒 | Ignite | ||
市场参考数据 | 亚秒 | Ignite | ||
市场交易数据 | < 3ms | Ignite | ||
权限数据 | < 1ms | 本地缓存:caffine |
从上表可以看到,由于数据访问时的延时要求不同,数据量的不同,消息体大小的不同,我们需要分类采用不同的存储方案。
2. 哪些场景需要缓存
对于需要亚秒级响应的数据需要进行缓存。
另外,交易系统之外的应用如果需要访问,则需要提供网关服务。外部应用主要的请求行情数据和交易数据,例如请求交易数据来完成清算工作。
外部服务请求的数据比较集中,如果让外部请求直接穿透,会造成交易系统的负载过高,因此,会将请求的结果进行缓存,避免请求频繁到达交易系统的应用。
3. 缓存中间件选型
redis是业界比较常用的缓存中间件,单节点吞吐量能达到3-4万/s,延时在100-200us左右。而且,搭建集群环境后,可以通过横向扩展来提高吞吐量,并提高数据的存储能力,具有高扩展性。但是,基于key-value模式的redis,有很多场景无法满足。
3.1 BigKey问题
由于 redis 只使用单核,所以平均每一个核上 redis 在存储小数据时具有优势。而在 100k 以上的数据中,redis的性能会严重受损。但是,金融交易系统中,大消息体的消息普遍存在,例如,excel报价数据,一个消息体的大小就在3-5M。
3.2 消息过滤问题
redis是基于key-value模式的缓存,只能通过key来进行消息的查询,无法建立索引来进行更精细的过滤查询。
以行情为例,根据交易所,产品分类,产品子类,行情来源等字段进行联合筛选的情况普遍存在。有人会说,那么将这些字段按一定的规则生成一个key来进行消息的缓存不久解决了吗?这一方面会导致key的字段过长,而且会导致查询过滤的组合严重受限,不利于扩展。
3.3 多级缓存问题
redis没有本地缓存的概念,如果需要配合本地缓存来实现多级缓存,那么必须选择另一种本地缓存的缓存框架,导致架构体系变得复杂。
3.4 数据丢失问题
redis是无法保证数据完全不丢失的。在交易系统中,有些场景严格限制延时性,比如涉及交易的行情数据要求延时在3ms以下,而行情数据的处理过程中涉及填充市场参考数据,如果参考数据在缓存中没有,就需要穿透到数据库进行查询,延时性就很可能达不到了。为了保证行情数据的延时性能达标,必须保证市场参考数据在缓存中不过期,不丢失。
3.5 字段读写问题
redis对于value是作为一个整体进行操作的,value不能在细化处理了。在交易系统中,很多缓存对象非常大,字段数有时候达到成千多万,如果每次都全量读取,或者全量替换,会造成网络和CPU(序列化/反序列化)的瓶颈。
基于上述原因,我们并没有采用redis来作为缓存框架,而是采用了ignite来作为缓存中间件。
对比项 | Redis | Ignite |
---|---|---|
吞吐量 | 3-4w | 3-4w |
延时 | 100-200us | 100-200us |
支持建立索引,按索引进行过滤查询 | N | Y |
多线程处理 | N | Y |
支持near cache | N | Y |
数据丢失 | Y | N |
按字段读写 | N | Y |
GC(存在一定的性能风险) | N | Y |
4. 缓存面临的问题及解决方案
4.1 高性能
(1)极高性能要求的数据不使用分布式缓存
如权限数据(延时小于1ms),直接使用本地缓存。
(2)预加载
对于交易时间段会使用到的数据进行预加载,并且对于热点数据不设置过期时间。比如市场参考数据,它的变更频率非常低,有的甚至几年都不会变动,我们只需要在每天的非交易时间段预先加载好即可。
(3)冷热分离
针对交易频繁的产品(如债券)和交易不频繁的产品(如黄金)分不同的缓存topic,设置不同的缓存参数,来达到最高的性能。
4.2 数据一致性
(1)不追求一致性
对于行情而言,根据使用场景的不同,存储在了hive,cassandra和ignit中,但我们不需要保持三者间的一致性。这是由于行情在实时变动,而且三种存储介质存储的行情根据业务需要而不同,延时性要求也不同,即使对于同一行情,不同时间切面上的数值也不要求相同。
(2)要求强一致性的数据不使用分布式缓存
如权限数据,直接使用本地缓存。
(3)交易期间不会变动的数据,提前加载
比如债券统计数据,TOPN的数据,这些数据都是通过定时任务根据T-1的数据进行复杂计算后得到的结果,只需要在本天交易时间开始前计算好,然后预加载到缓存中即可,不需要考虑数据一致性,中途不会有数据的更新操作(新增,修改,删除)。
(4)多级缓存间的数据一致性
Ignite自带near cache(就是本地缓存)功能,ignite保证了near cache和ignite分布式缓存间的数据一致性。
(5)ignite与mysql,cassandra,hive间的数据一致性
例如市场参考数据,市场交易数据,利率曲线等,需要保证多个存储介质间的数据一致性。鉴于金融交易系统已经使用了MQ,且MQ的功能比较强大,我们的做法如下:
1)更新数据先做用于缓存(缓存系统本身高可用);
2)更新数据写入MQ;
3)通过订阅MQ更新其他数据存储介质。
这样可以让更新数据尽快的进入缓存,并尽可能快的到达消费方,为了编码方便,在调用缓存接口时,可以通过简单的注解来指定需要同步到哪些存储介质即可,框架会完成消息的后续处理。
4.3 HotKey
如行情,在写操作上,会存在峰值达到5万/s的情况;在读操作上,交易频繁的产品可能达到6万/s,而且交易频繁的产品并不完全固定,很难预测。
(1)过滤存储产品种类
只针对业务上需要的产品种类,且对访问有延时要求的产品种类进行缓存,粒度越细越好,这样可以尽可能的减少对缓存的冲击。
(2)冷热分离
对于访问特别频繁的产品和访问不太频繁的产品分不同的topic存储,配置不同的缓存参数,降低访问压力,并分类调优。
(3)本地缓存 + 多备份
通过本地缓存和多个缓存备份来达到分散压力的效果。
(4)限流
对于预测外的高并发访问,很可能拖垮缓存系统,因此需要限流来兜底。
4.4 BigKey
对于聚合行情和excel报价容易造成BigKey问题。对于聚合行情而言,如果聚合的要素越多,就会带上越多的信息,导致消息体变大。excel报价是由于会将多组报价一起合并,涉及的产品阅读,就会导致报价消息体不断增大。
(1)压缩
对需要存储的消息进行序列化,然后进行压缩;当然,对于需要检索的索引还需要保留。
(2)delta vs snapshot
如果消息体特别大,我们只存储一份snapshot,然后单独为每一次涉及变动的字段记录delta,使用时将snapshot和delta进行合并得到最新的snapshot。
(3)拆分存储文章来源:https://www.toymoban.com/news/detail-834137.html
例如聚合行情,将聚合行情的多种信息拆分,使用时查询后简单合并数据即可。文章来源地址https://www.toymoban.com/news/detail-834137.html
到了这里,关于[缓存] - 3.金融交易系统缓存架构设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!