金融系统性能优化之道

Posted JavaEdge.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了金融系统性能优化之道相关的知识,希望对你有一定的参考价值。

系统设计得再好,如不能及时完成业务处理也不行。为什么不同业务有不同优化需求,以及常见的优化方式和问题有哪些。

1 分析

“快”在不同的环境下有不同的定义:

  • 对互联网业务,快一般意味着吞吐量大
  • 对金融业务,快意味着延时低

为什么有这两种定义的区别?互联网业务在经济学上有个特点:边际成本(Marginal Cost)基本为零

边际成本决定业务扩张的成本,既然扩张成本低,互联网业务倾向于扩张,且大规模扩张。扩张结果就是互联网业务会有大量用户,这也决定互联网业务需解决大流量问题。

流量大为何和速度快扯上关系?

秒杀发现网页卡,半天显示不了内容。这时候你肯定抱怨网站速度慢,这是因为在解决秒杀这种大流量问题,互联网通常采用延时换吞吐量,即降低你的网页加载速度来支持更多人秒杀。虽吞吐量上去,但延时增加。虽然所有人体验都因此变更差,但至少大家还能买到东西,没有出现网站宕机。所以互联网的“快”,指服务器集群的处理能力快,能同时处理很多东西。

**金融业务的边际成本一般都很高。**金融机构在和机构用户进行对接前,双方都要做详细的客户身份识别(KYC,Know Your Customer)。对接后就是业务、财务、合规、风控等一系列的流程。因为这些流程都有一定的时间和人力物力成本,所以机构类的金融业务很难像互联网大规模扩张。

既然机构类的金融业务无法大规模扩张,金融机构就无法靠规模优势来彼此竞争,只能靠质量优势吸引客户。在金融行业,时间就是金钱,所以金融机构就会想办法把速度提高,才能帮客户省钱。这决定金融行业的“快”指的就是延时低。

互联网业务和金融业务有一个重合点,那就是普惠金融,如第三方支付、小额信贷等。普惠金融的业务特点更接近互联网业务,对快的要求也和互联网业务一样。

尽管金融业务多种多样,但我们把握住相应背景中“快”的本质定义,就能更合理地选择优化方向了。那么接下来,我们就从吞吐量和延时这两个方面,分别来看看金融系统的优化要点。

2 吞吐量优化

两种常见方法:

2.1 分库分表

吞吐量最常见的解决方式。

单个数据库表易受单机硬件处理速度限制,但拆成为多部分后,每部分都可放在不同机器处理,使用更多硬件资源。所以分库分表之后,可用大量硬件应对大流量。

切分时注意一个原则,切分完的子表需要互相独立,但也要完全穷尽(MECE,Mutually Exclusive Collectively Exhaustive)。互相独立指的是子表之间不要有任何内容的重复。完全穷尽:把原始的表切割完后,不要有任何数据丢失。即切分表时,要保证切分后的结果不多不少。

2.1.1 按hash值和主键切分

最常见的水平切分。按数据库主键切分:

  • 按主键的哈希(Hash)值来分
  • 按主键的范围(Range)来分

按哈希值来分,注意哈希函数的值域大小。一般把每个哈希值对应的数据都放在一台机器。机器数量有限,所以哈希函数的值域一般不大,如10或100。

**按哈希值切分有一个很大的优点是有一定随机性。**用户访问并不一定很随机,有可能出现某些主键范围的访问量特别集中。由于哈希值会将原值打散,所以可能将流量分散在不同机器,避免单台机器过载。

不过哈希值的随机性也带来缺点,连续访问性能差,不过互联网应用很少看到需要主键连续访问。

但金融行业有市场数据,如股票和期货的实时交易价格信息。使用时,一般会访问一个时间段内所有数据,因此在时间上需连续访问。按时间的哈希值来切分就不太适合。

2.1.2 按范围切分

优点:主键的范围连续,对于市场数据的访问很友好。

但范围连续的优点也成为缺点。范围连续可能导致访问过集中,造成单机过载。如量化分析时,可能会大量访问最近min的市场数据,按照范围划分会导致存有最近数据的服务器被大量访问。

2.1.3 分库分表的问题

将原来在一台机器上处理的事情,变成在多台机器处理。无论按哪种切分方法,都会带来多机环境的问题。

正确性

分库分表后,大家可同时更改多个表的内容。由于这些表在不同机器,网络通讯需一定时间,你很难确定别的机器上的内容是正确的。就算你验证正确,在网络消息传回来的那段时间,也可能发生变化。因此分库分表后,你需要一定分布式正确性保障,即分布式事务

延时

分布式事务需要至少两次网络沟通,这也决定分库分表方案的最低延时。对个人用户,网络延时带来问题不大,但对高频交易相关机构,会延时过高。

容灾

机器不可能一直在线,一定会出问题,只是时间早晚问题。如果你学过概率论就会更理解这点,机器数目越多,出问题概率越高,所以分库分表一定要考虑集群容灾。

容量限制

分库分表过程一旦完成就很难再调整分库数量。因此有经验的架构师在最开始会做一些看起来“超前”准备。比如说如分10个库就够,可能你分20或50个。但互联网应用的增长速度谁都说不准,很可能爆发增长,这时候依然会出现集群整体容量不足。

2.2 MQ

核心思想:将流量先写入MQ,然后服务器按固定速度处理MQ内的消息。这样就算是峰值流量进来了,也不会服务器过载。

消息系统是很常见的一种处理峰值流量的架构。秒杀一般分为三个阶段。第一个阶段准备期,支付系统流量和平常一样,无需特殊准备。

第二阶段秒杀开始之后的几min。这时从系统监控上可以看到一个很高的流量峰值。这峰值是电商系统丢给支付系统的流量,代表理论上最高的并发量。

支付系统的流量并不会直接处理,而是会写入MQ。消息队列也有个消息写入速度的上限,但这上限很高,通常不会成为瓶颈。

支付系统尽最大能力从消息系统拉取要处理的消息。这处理速度有上限,一般是分库分表后所有机器的处理能力。和消息系统不同,支付系统处理速度低于电商系统丢过来的峰值流量。所以电商系统丢过来的流量很高,但时间很短。支付系统处理速度慢,时间也长。

第三阶段秒杀结束后。这时候系统流量会慢慢恢复到秒杀开始之前的情况,一切回归正常。

中间第二阶段比喻成削峰填谷

除削峰填谷,还可继续优化吗?要结合业务。虽然你在零点秒杀,但货品需过一段时间才能送到你手。等你确认收货后,商家才能收到你的钱。所以从你付钱到商家收钱中间有很长一段时间,这就给我们进一步优化空间。

在秒杀时,钱不是从买家账号直接打到卖家账号,而是先打到中间账号,即担保账号。所以我们在处理秒杀时,只需处理买家账号到中间账号的流量问题。

买家账号到中间账号还可进一步切分。支付系统角度,只要买家账号能正确扣款,中间账号稍微延迟点打款没问题。中间账号的打款就算丢了也问题不大。支付系统在每天晚上日切时进行所有账户对账。如果中间账号的打款丢了,会通过补账方式把钱再补回来。即中间账号可异步处理。

首先,在秒杀开始时,即秒杀第二阶段,支付系统只处理买家账号,将对中间账号的处理暂时搁置,减少一半的账号操作。到第三阶段,再慢慢恢复对中间账号的处理。

这时候系统多第四阶段,即快递送货之后打款给卖家的阶段:

3 延时优化

吞吐量优化是系统能力横向扩展,宏观资源调配。而延时是系统能力纵向扩展,微观资源调配,因此需不同解决方案。

3.1 单机优化

提高单机性能有一个反直觉的解决方案:单线程处理。为什么单线程可以有这样高的处理速度呢?

谈到多线程优势,常提到可用到计算机的多个CPU或者多个核,因此有更多的计算资源,因此可以处理更多的事情。听起来有道理,但假设计算之间不抢占资源。多线程处理时,计算机操作系统会进行线程调度。线程调度需更新操作系统内的核心数据结构及更新CPU上的各种缓存,这过程需消耗时间。所以虽然多线程能用到更多资源,但准备资源本身就消耗资源。

这就是为什么单线程可比多线程更快。这只是一种可能性,为能真正超过多线程,还是要做些处理。

把你的线程绑定到某块CPU。如Linux操作系统有个C函数 sched_setaffinity把你的程序绑定到指定的CPU

默认情况下绑定到CPU指的是你的程序只会在这块CPU上运行,不会跑到其他的CPU。尽管其他程序还是可能会过来抢你的这块CPU,但是你的程序绑定到CPU之后,还是会运行得更快。

绑定CPU还有一些优化空间。Linux内核启动时有个 isolcpus 的启动选项。这个选项可以将一块CPU独立出来,这样任何程序都不可以使用这块CPU了。唯一可以使用这块CPU的方式是你将进程绑定到这块CPU,这样你就能真正独占这块CPU了。

访问内存时,注意CPU并非直接访问内存,而是通过CPU缓存来访问。机器加载缓存时会一次性加载一小段内存,这也决定**内存的顺序访问会比乱序访问速度更快。**进行金融风险计算时,会用到多维数组,这时需根据算法访问顺序合理组织数组的位置。

内存另一个需要注意的是C语言分配内存需要一定时间,而且这时间长度还随机。所以如你的程序需频繁分配内存或对延时敏感,最好自己实现内存池。

文件系统。事件溯源由于顺序写文件,可达非常高的写速度,所以如你的程序也能顺序写文件,尽量按顺序写。

如一定要随机写,mmap 会将文件映射到进程的内存页表。这样在C程序里就能像访问内存一样访问文件。这就减少用户进程和操作系统之间来回拷贝数据的开销,节省时间。

3.2 网络优化

一台机器的各个组成部分相对来说还是比较稳定的,所以单机优化都有一些可以复用的优化手段。而网络则是非常不确定的一个环境,优化的手段需要结合实际情况来看。在这里我们重点看看Linux上比较有用的几个函数,它们可以解决网络消息处理的问题。

C10K:有没有可能让一台机器支撑一万并发。用进程或线程的方法达不到,所以就要 epoll 。Linux独有函数,可同时监听大量网络链接。当网络链接变得可读写,会通知你的程序。你就不需要同时等待所有网络链接,只需等待这个函数的通知。epoll还做内核数据结构优化,就算网络链接特别多,也能高效工作。但 epoll 只告诉你网络是否可读写,你还是要自己写代码读写网络。由于每次读写网络都会调用内核函数,这样会造成大量用户态和内核态切换,浪费很多计算资源。

咋解决这问题?2018年Linux内核新增 io_uring,解决用户态切换过多问题。在写程序时准备个队列,记录所有你想要做的读写操作,同时也包含你预先分配的读写内存。

接着你将这个队列一股脑交给内核。内核会先做 epoll ,检查哪些网络链接可开始读写。然后内核多做一步,帮你处理网络数据。

如果你的操作是写网络,会把你内存的数据写出去。如你的操作是读操作的话,会把数据读到你预先分配的内存。内核操作完之后会把这些操作的状态记录在另一个列表里,返回给你的用户态进程。


架构上来说,io_uring 替你把 epoll 和之后的读写操作在内核态批量处理,同时用户进程和内核共享数据页表,这样既节省了状态切换开销,也节省了数据拷贝开销。

目前有一些网络操作频繁的应用正在实验这种新技术。不过相对于已经存在近10年的 epoll 来说,io_uring 从文档和工具上来说都还不太成熟,所以你要做好挑战的准备。

总结

如何优化金融系统。首先我们分析了为什么金融系统会有吞吐量和延时这两个优化的方向。普惠金融和互联网业务类似,面向大众,对系统吞吐量要求非常高。机构金融专业性特别强,对延时要求非常高。

吞吐量优化:分库分表和使用消息队列。分库分表有按哈希值和范围这两种不同的划分方式。这两种划分方式都有各自的优缺点,但是它们都有正确性、延时、容灾和容量限制这四个问题。我会在第三个模块讲解应该如何解决这些问题。

消息队列的作用是对流量做削峰填谷。我们用秒杀场景下的支付系统为例,讲解了在碰到问题时应该如何分析业务规律,应该如何利用业务规律的特点来优化系统架构。

延时优化的一些常见方法。延时优化要从单机优化开始,优化对CPU、内存和文件这些资源使用。网络优化主要是通过 epoll 来减少在高并发情况下的线程开销,同时用io_uring来进一步减少网络操作的用户态和内核态的切换开销。

普惠金融的架构是从宏观层面解决架构的横向扩张问题,是互联网云计算的标准使用场景。机构金融是从微观层面解决架构的纵向扩张问题,需要对用户进程、操作系统和硬件做特别的优化和控制,因此非常不适合云计算的解决方案。知道这些区别之后,你还要根据具体业务进行相应的优化和选择。

以上是关于金融系统性能优化之道的主要内容,如果未能解决你的问题,请参考以下文章

从优化性能到应对峰值流量:微博缓存服务化的设计与实践

百万并发下的 Nginx 性能优化之道

spring+ehcache实战--性能优化之道

工程之道,深度学习推理性能业界最佳优化实践

Spark Streaming性能优化: 如何在生产环境下应对流数据峰值巨变

金融云原生漫谈|中小银行破局之道:云原生架构转型全攻略