來公司第一個比較大的業(yè)務(wù)需求耿戚,便是換了新的行情提供商dxfeed,需要把所有K線的處理都重新倒騰一遍阿趁。這里說說重寫分時K線圖的一點心得膜蛔。
先交代一下dxfeed的情況:
1)股票分時數(shù)據(jù)采用訂閱的方式,可以訂閱歷史數(shù)據(jù)
2)每分鐘每只股票可能會接收到多條分時數(shù)據(jù)脖阵,也可能一條不推飞几。有些股票在開市過程中每分鐘都會有分時數(shù)據(jù),有些股票則一個交易日只有幾條數(shù)據(jù)独撇,甚至沒有屑墨,差別很大
3)推送的分時數(shù)據(jù)有延遲,從監(jiān)控獲得數(shù)據(jù)纷铣,開市期間大約在60s-120s左右(分時數(shù)據(jù)看的是趨勢卵史,這是可以忍受的)
4)每個訂閱對實時數(shù)據(jù)有qps限制,對歷史數(shù)據(jù)無qps限制(從監(jiān)控上得到的)
再交代一下股票分時K線數(shù)據(jù)的情況:
1)股票數(shù)目為8400+(我們只做美股相關(guān))
2)每只股票1個交易日的分時線包含391條數(shù)據(jù)(9:30-16:00, America/New_York)搜立,每條數(shù)據(jù)包含這一分鐘的最高價以躯、最低價、均價啄踊、昨收價忧设、漲幅、交易量颠通、VWAP等址晕,數(shù)據(jù)量在150k左右
3)每天存儲的分時數(shù)據(jù)在120w條左右
4)股票分時線如果某一分鐘沒有點,使用上一分鐘的點代替顿锰,但無交易量
接下來重點說一下這里的設(shè)計:(以AAPL為例)
1)由于分時數(shù)據(jù)有延遲谨垃,無法確定每一分鐘的最后一條數(shù)據(jù)什么時候會來到。因此開市期間并不去保存分時數(shù)據(jù)硼控。實時的分時數(shù)據(jù)存儲在redis中刘陶,計算K線時從redis中取
2)redis中分時數(shù)據(jù)的存儲格式為hash結(jié)構(gòu),每只股票一個key牢撼,為了防止當前交易日使用上個交易日的數(shù)據(jù)匙隔,因此分時數(shù)據(jù)緩存時是包含日期的,比如:TMT_AAPL0105, key為美東時間熏版,格式:HHmm纷责, value為簡單計算后的分時數(shù)據(jù)。上一個交易日的分時數(shù)據(jù)通過定時任務(wù)在指定時間清理纳决。(這里沒有對分時數(shù)據(jù)做過期設(shè)置碰逸,是因為剛開市時數(shù)據(jù)推送量及redis操作量很大,而使用hashes這個結(jié)構(gòu)阔加,是不支持直接傳入expire time的饵史,同時即使某天系統(tǒng)出什么問題,也只是多占些硬盤的問題胜榔,不會對其他造成影響胳喷,對這個操作添加監(jiān)控及報警就可以了)
3)結(jié)構(gòu)有了,如果dxfeed每次都推送數(shù)據(jù)過來時夭织,都去操作redis更新吭露,那樣redis很可能會吃不消,誰都無法曉得第三方是否會出什么問題瘋狂推送一下(事實證明尊惰,dxfeed也確實這樣有過讲竿,可能因網(wǎng)絡(luò)故障等補償推送歷史數(shù)據(jù))泥兰。接下來是緩存的的重點:在內(nèi)存中通過guava cache緩存了最近幾分鐘的所有symbol的最新數(shù)據(jù),key: 股票+HHmm(美東時間)题禀, value:一條分時數(shù)據(jù)鞋诗,每次推送分時數(shù)據(jù)過來時,根據(jù)股票及分時數(shù)據(jù)時間進行cache迈嘹,過期數(shù)據(jù)(很久以前或非最新數(shù)據(jù))或臟數(shù)據(jù)(相關(guān)數(shù)據(jù)為NaN)直接拋棄削彬,然后通過ScheduledExecutorService維護一個1分鐘1執(zhí)行的job去將最近幾分鐘的分時數(shù)據(jù)更新到redis中。這樣既解決了分時數(shù)據(jù)有延遲的問題秀仲,又保證了每分鐘的redis更新數(shù)據(jù)量融痛。
4)數(shù)據(jù)存儲結(jié)束后,就是處理分時K線的數(shù)據(jù)了神僵。當請求AAPL時雁刷,會先查看是否已經(jīng)有cache了,比如C_TT_AAPL, 如果有取出cache中數(shù)據(jù)處理后返回挑豌,沒有開始計算安券。計算會先取出上面存儲的數(shù)據(jù),即TMT_AAPL0105氓英,然后計算當前k線的時間點(開市過程中從1個點慢慢增長到391個點)侯勉,遍歷HHmm去TMT_AAPL0105中取分時數(shù)據(jù),如果有直接取出即可铝阐,如果沒有使用上一個點生成(第一個記得特殊處理一下址貌,沒有需要拿上個交易日最后一個點補),計算完后轉(zhuǎn)成json放到redis中徘键,key為計算前查詢的key: C_TT_AAPL练对,然后將結(jié)果返回。開市過程中緩存到下一分鐘開始吹害,閉市了緩存到下個交易日開始螟凭。由于這里緩存的是string,因此直接帶上過期時間它呀,之后就不用care了螺男。如此,最核心的分時K線數(shù)據(jù)絕大部分都是在操作redis纵穿,每只股票每分鐘只需要計算一次即可下隧,當然這是基于分時K線主要看的是趨勢。
5)上面說了谓媒,分時數(shù)據(jù)可以拿歷史數(shù)據(jù)淆院。這里我單獨寫了一個job,可以在給定開始時間后句惯,取出期望的歷史數(shù)據(jù)土辩,然后更新歷史分時數(shù)據(jù)支救。當實時推送出問題時,這個job可以用來在秒級獲取歷史數(shù)據(jù)來修復(fù)redis中的分時數(shù)據(jù)拷淘。實際上搂妻,這個job在故障中的表現(xiàn)遠遠超出預(yù)期,之前因為對實時那里做優(yōu)化辕棚,幾次都有點問題導(dǎo)致開市后實時數(shù)據(jù)獲取或處理有問題,這時候啟動這個拿歷史分時的job邓厕,幾秒內(nèi)就可以保證redis中的數(shù)據(jù)變成最新逝嚎,保證核心的分時圖數(shù)據(jù)一直ok。
6)說到這里详恼,分時數(shù)據(jù)還沒保存补君。這里借助上面拿歷史數(shù)據(jù)的job,在閉市后(北京時間凌晨5點以后)自動執(zhí)行昧互,更新redis中數(shù)據(jù)后開始插入數(shù)據(jù)庫挽铁。由于插入量特別大,通過guava的RateLimiter控制寫入速度在合理的qps值敞掘,慢慢更新就好啦
當然這不是全部叽掘,還有些細節(jié),比如動態(tài)增加股票玖雁,就不多說了更扁。此外,我單獨補了許多核心監(jiān)控赫冬,比如定時任務(wù)是否正常執(zhí)行浓镜、讀取及更新redis時間、開市期間推送的qps劲厌、計算分時K線平均時間膛薛、平均每分鐘推送不同股票數(shù)目等等,目前達到的結(jié)果是:docker上部署的2個4g的服務(wù)來處理所有K線圖补鼻,分時K線圖數(shù)據(jù)可以在平均10ms左右返回哄啄,大量的分時數(shù)據(jù)在低峰時間端插入,服務(wù)器在開市閉市都沒什么壓力辽幌。