天下武功唯快不破国觉,那么對于交易系統(tǒng)更是如此,如何達到這個快痕寓,可以提升單機性能针饥,提高運算速度和效率丁眼, 可以橫向集群擴展增加吞吐量和并行能力昭殉。盡可能將數(shù)據(jù)放在內存, 在內存中實現(xiàn)計算是一種行之有效的方法挪丢,IMDG(In Memory Data Grid), 是此篇我們將要探討的內容。
概述
需求
對于一個交易系統(tǒng)惠啄,在交易時段撵渡,需要非常精確快速提供下面幾點服務:
- 保存和計算所有用戶的資金信息
- 可用現(xiàn)金
- 占用保證金
- 賬戶浮動盈虧
- 實時聚合計算死嗦,規(guī)則引擎觸發(fā)
- 保存和計算用戶持倉
- 倉位變更
- 實時浮動盈虧計算
- 實時消息推送
- 用戶在線狀態(tài)
- 訂閱價格信息
- 持倉倉位信息
基本條件
我們需要這樣一個工具(或者服務)提供:
- 緩存
- 內存中索引
- 高可用性
- 易擴展性
- 落地方案
- 流式計算
- 內存運算
當建立一個大型Java應用時越除,引起性能問題大部分是延遲,在一個分布式Java系統(tǒng)中引起延遲的原因可能有:
- 從磁盤上加裝數(shù)據(jù)的IO延遲
- 跨網絡加裝數(shù)據(jù)的IO延遲
- 在分布式鎖上的資源爭奪
- 垃圾回收引起的暫停(在JAVA 應用中這個尤為重要)
典型Ping時間是:本地機器是57μs翼雀;局域網是300 μs狼渊;從倫敦到紐約是100ms;對于1Gb網絡坦弟,網絡數(shù)據(jù)傳輸是每秒 25MB – 30MB酿傍。對于10GB網絡是每秒250MB – 350MB驱入。使用SATA 3.0接口的SSD硬盤數(shù)據(jù)傳輸是每秒500-600MB亏较。如果你有1G以上數(shù)據(jù)需要處理,磁盤延遲會嚴重影響應用性能遵岩。
硬件上最低延遲是內存巡通,典型的內存緩存是每秒3-5 GB宴凉,能夠隨著CPU擴展。如果你有兩個處理器丧靡,你就能每秒10GB籽暇,如果有4CPU就能獲得 20GB. 有一個內存基準測試稱為STREAM 是測試許多計算機的內存吞吐量图仓,一些在大量CPU幫助下能夠實現(xiàn)每秒TB級別的吞吐量救崔。
在內存中存放和管理數(shù)據(jù)是降低延遲最有效的方法之一捏顺,現(xiàn)如今內存的價格大大的下降幅骄,現(xiàn)在幾十G的上T的服務器非常常見本今, 使得這樣的操作方法的以可行冠息; 內存中保證高可用性, 勢必涉及partition 和 replication, 分布式計算躏碳、backup散怖, 再平衡等等镇眷, 和外圍語言的交互欠动,借用ignite 圖大概這個樣子:
IMDG 提供一個完全基于內存的架構, 理論上可能給你應用提升幾倍或者幾十倍的性能提升铆遭, 可以將上T的數(shù)據(jù)載入內存, 能夠很好滿足現(xiàn)如今大數(shù)據(jù)處理的需求啼肩。
IMDG, 直觀可以認為是一個分布式的hash map, 你按照key 存儲數(shù)據(jù)衙伶, 和傳統(tǒng)的系統(tǒng)不一樣矢劲,key, value 必須是一個string 或者byte數(shù)組, 你可以直接用你領域對象作為key 和 value躺同,不需要序列反序列化這些對象將給你的業(yè)務處理流程帶來大量的編輯蹋艺, 使用和操作這里map 就和你處理本地的hash map 一樣,這個特征也是IMDG, 和IMDB(In-Memory Database)區(qū)分民效, 省去ORM不僅僅省去很多不必要的麻煩涛救,也會有性能上的提升检吆。
IMDG和其他一些產品, 比如NoSql, IMDB, NewSql 數(shù)據(jù)庫交煞,一個現(xiàn)在的區(qū)別就是使用集群, IMDG可以按照partition將數(shù)據(jù)replicate 和均勻的分布在你網絡節(jié)點中斟或, 所以理論上網絡節(jié)點越多萝挤,能夠存儲的數(shù)據(jù)就越多怜珍。在IMDG 使用需要注意點,應該讓你的運算盡量靠近數(shù)據(jù)--類似于Map-Reduce, Hadoop 中思想今豆, 因為在網絡世界中呆躲,移動計算比移動數(shù)據(jù)更省事捶索,當整個網絡拓撲圖穩(wěn)定后,大部分時間數(shù)據(jù)是待在固定的節(jié)點箩祥, 只有在加入肆氓,或者有節(jié)點退出情況下谢揪,產生re-partition 才會導致數(shù)據(jù)在節(jié)點的移動。
在我們的運用中有一個必不可少的步驟,就是數(shù)據(jù)持久化患民,將數(shù)據(jù)存儲到外部界質匹颤,下圖的ignite使用方法,這里也可以實現(xiàn)自己的cache storage, 比如通過write/read through保存到數(shù)據(jù)庫印蓖。
分布式計算是通過以并行的方式執(zhí)行來獲得高性能赦肃、低延遲和線性可擴展。在集群內的多臺計算機上執(zhí)行分布式計算和數(shù)據(jù)處理船侧。分布式并行處理是基于在任何集群節(jié)點集合上進行計算和執(zhí)行然后將結果返回實現(xiàn)的镜撩。
上面是根據(jù)我們業(yè)務場景琐鲁, 需要基于內存的解決方案人灼, 最終選擇 apache ignite作為IMDG解決方案投放, 當然業(yè)界有很多其他的IMDG 產品比如:
- Hazelcast
- Gemfire
- Coherence 當年還是tangosol 用過
- Ehcache
- Terracotta
實踐
PNL
PNL分基本兩種: 一種是浮動灸芳, 一種是結算好的;結算好的比較好理解冯遂, 一個買賣周期結束蛤肌,錢已落袋就是最終的盈虧批狱,這個是固定不變的赔硫,另外一種是浮動爪膊, 在每個價格跳動都要計算一次, 結算好的PNL在實現(xiàn)方法上面沒有太大的挑戰(zhàn)僵芹, 同時他的TPS 也相對平緩拇派, 但是對于浮動PNL凿跳, 特別在像外匯交易中控嗜,計算的強度非常的大, 幾乎每秒都有3~5個以上的報價曾掂, 同時如果用戶有大量的持倉珠洗。 對系統(tǒng)有運算能力有巨大的挑戰(zhàn)若专。
首先要做的就是將用戶的持倉信息載入內存膊爪,分布于集群中, 對于計算的頻次沛豌,也有有區(qū)分, 這個根據(jù)不用用戶的持倉比(占用保證金率)赃额, 對于占比高的(70%以上)的當然需要更頻繁的計算加派, 對于低比例比如小于30%,其實這樣的倉位非常安全爬早,適當擴大計算的頻次風險比較低哼丈, 當然也要根據(jù)價格波動的區(qū)間, 價格波動非常大時候筛严, 需要整體的計算一遍醉旦。
一旦訂單match后會通過ESB(Kafka,Redis) 推送到PNL節(jié)點,由于用戶的持倉是多節(jié)點集群桨啃,在replication 模式下保持3個backup; 每個節(jié)點有自己的primary partition, 同時使用Kafka 在publish 時使用同樣的partition 算法分發(fā)到Kafka topic partition中,這樣保證每個集群中節(jié)點只consume 自己primary partition 上面的數(shù)據(jù)照瘾, 大大減少了數(shù)據(jù)在節(jié)點間的re-balance; 但是這里需要自己監(jiān)聽持倉cache 上面的EVTS_CACHE_REBALANCE 消息匈棘; 這會額外增加節(jié)點間的通訊量, 但是基于析命,一般集群拓撲圖穩(wěn)定后很少變動主卫, 這點損耗還是值得的。
浮動PNL能否正確無誤的計算出來鹃愤, 對于下游一系列的模塊正常運作非常重要簇搅。
規(guī)則引擎
在整個交易系統(tǒng)中, 涉及規(guī)則的地方非常多软吐, 簡單比如什么時候margin call? Stop 條件的觸發(fā)瘩将, 在開市時間需要實時的觸發(fā), 有兩種方法凹耙, 一種是被動姿现,定時Job去掃描; 另外一種是主動在變動數(shù)據(jù)上面加上監(jiān)聽條件 reactive callback 回來肖抱。 對于margin call,的觸發(fā)可以使用Job 定時比如1分鐘去掃描备典; 而對于stop 規(guī)則 需要使用continuous query 這樣的功能在價格和變動后,立刻執(zhí)行虐沥。
微服務
對熊经, 我們這里也用到了時髦的微服務泽艘,這里也是借助ignite 的服務網格來注冊和發(fā)現(xiàn)和部署服務欲险, 只不過我們這里用到 DDD 開發(fā)模式镐依, 對于一個聚合根的變動,原則在一個時間點只能在一臺機一個線程中處理天试,所以這里需要做些額外的處理槐壳, ignite 可以根據(jù)NodeFilter 條件來選擇用集群中的某個節(jié)點上的服務, 因為可能這個節(jié)點有你服務依賴的數(shù)據(jù)喜每, 而對于我們的DDD务唐,這個是個必要條件, 必須在每個固定的節(jié)點带兜,除非這個節(jié)點從primary 角色退出枫笛; 這里涉及到微服務發(fā)現(xiàn)的方式, 可以參考Pattern: Service registry刚照。
我們的做法是使用Client 發(fā)現(xiàn)服務的方式枪眉,在分兩種接癌, 一種是偶爾需要定位某個服務類似ad-hoc, 現(xiàn)用現(xiàn)查,另外一種是和某項服務有長期的關聯(lián)炮叶, 比如一個book 和我們portfolio 管理模塊, 這個時候需要注冊服務提供節(jié)點網絡拓撲圖改變listener拾积,隨時保持保持最新更新抹蚀, 或者退而求其次,設置一個重試機制郭变,在錯誤達到一個benchmark, 再重新定位服務颜价。
風控
風險監(jiān)控, 包含margin call, 某些交易規(guī)則觸發(fā)诉濒, 如頭寸大小是否超過設定benchmark, 這里借助ignite continuous query + redis , 以 Realtime 處理周伦。
風險還包括 fault detection 這些部分以Near realtime處理,可以借助 ELK循诉、和NEO4J 類似工具來完成横辆。
Market 數(shù)據(jù)
市場數(shù)據(jù), 涉及 realtime 和 history 數(shù)據(jù)茄猫; 其實按照價格傳播的快慢可以分幾大類型價格:
- 匹配中心狈蚤, 這個是最新最快價格放在節(jié)點的本地 hashmap 中
- 內部計算的價格,通過redis 發(fā)送給PNL划纽, Account 節(jié)點
- 歷史數(shù)據(jù)脆侮,比如分時,小時這個是最后價格勇劣,通過KAFKA 到Market 節(jié)點進行聚合和存儲
在考察了一堆的時序數(shù)據(jù)庫靖避,比如最近當紅炸子雞InfluxDB,但是如何處理OHLC潭枣,特別在你有10來個價格檔位情況下沒有看到合適的處理方法,同時InfluxDB 開源不支持集群了幻捏。 通過簡單的評估盆犁, 比如平均有200產品同時開市,每個每秒平均報價5次篡九, 有10種K線聚合情況下谐岁, 每秒其實也就 200x5x10 ~= 10000 次操作, 如果完全在內存中還是綽綽有余榛臼,DONE伊佃!
所以很多時候解決方案,沒有你想像那么復雜沛善, 計算機沒有你想像那么快航揉,也沒有你想像那么不堪,只有試過方知可不可金刁。
Market 數(shù)據(jù)部分同時涉及帅涂,報價往客戶端實時發(fā)送, 這里涉及內容比較多胀葱,下篇再說漠秋。
上面涉及的各模塊之間的交互,其實不完全借助IMDG完成抵屿,KAFKA在內部消息通道中占有很大一塊比重庆锦,將分獨立篇幅敘述。
參考
- 為什么要使用數(shù)據(jù)網格Data Grid
- 開源數(shù)據(jù)網格平臺Infinispan訪談
- In-Memory Data Grids
- From Cache To In-Memory Data Grids
- In-Memory Database vs. In-Memory Data Grid: Revisited
- in-memory data grid
- In-Memory Data Grid: Explained
- Apache Ignite(V2.3.0)中文開發(fā)手冊
- Top 15 In Memory Data Grid Platform
- apache ignite 是什么東西轧葛,最近勢頭很猛搂抒?
GoXTX 下一代交易平臺技術供應商
GoXTX one-stop solution for neXT generation eXchange