基本的業務需求可分爲兩大部分,第一部分是手機端間隔必定時間上報一次位置信息,第二部分是後臺系統能夠實時看看手機設備當前所在的位置,並繪製軌跡。node
總之就是用戶安裝了此應用,就至關於給本身裝上了一個跟蹤器,所到之處,都將有所記錄。nginx
咱們先來保守計算一組數據,假定用戶基數爲10萬,每隔5秒上報一次位置信息,而這5秒期間,地圖SDK大概會給出2次定位數據,由此得出,一次上報的瞬時峯值大概是20萬條數據,一天將會達到300多萬的數據。sql
這樣的QPS已經算是很高了,固然,絕大多數狀況下是不會觸頂的。數據庫
另一方面,後臺系統在查詢的時候,也面臨着一個巨大的挑戰:如何從海量的數據中找尋符合條件的那一部分?服務器
查詢的條件無非是圍繞三個維度展開的:時間、對象和區域。網絡
相似「查詢某塊地理位置柵欄內的全部用戶在一段時間內的位置信息」,這就是一個典型的查詢業務需求,涵蓋了三個維度的條件。架構
固然,還有一些距離的測算和排序的需求也是在所不免的。併發
綜上所述,咱們要解決兩個難點:負載均衡
一、上報用戶的實時位置信息; 二、處理海量數據的讀寫請求。框架
以上兩個難點是任何LBS應用不得不攻克的,這是支撐業務發展的關鍵點。
咱們能夠將整個請求連接進行拆解,分別用對應的策略解決這部分的難題:
一、客戶端收集數據。這一部分有四個基本要求:準實時,不丟數據,低功耗,高效率。
二、網關限流。主要爲了不涌入大量無效請求,消耗系統資源,拖跨服務器。
三、負載均衡。爲了應對不斷增加的訪問量,服務實例必須支持橫向擴展,多個實例之間按照必定的策略進行負載均衡。
四、異步化讀寫數據。須要藉助中間件,起到削峯的做用,這一部分的設計會重點闡述。
五、數據存儲層高可用。採集的數據不要求強一致性,依據CAP理論,知足AP兩個條件便可。解決辦法一般是集羣化,避免單點故障,進而實現程序上的讀寫分離策略。
接下來是針對上述五大部分在技術實現上的考量。
目前客戶端是蘋果機和安卓機,將來極可能會接入友商的車載OS。
在網絡情況良好的時候,能夠實時採集,實時上報,假若網絡情況很差,也不會丟數據,採用MMAP實現本地緩衝區。固然,爲了上報效率,綜合考慮仍是按批次上傳,過於頻繁的上報也會導致耗電量攀升。
網關限流和負載均衡交給了nginx,學習和開發成本低,重要的是效果很好,而且可以達到服務橫向擴展的目的。
咱們使用了nginx基於ip地址的限流策略,同一個ip在1秒鐘內最多容許3個請求,burst=5,爲了應對忽然的流量的爆發,還有一個nodelay參數,咱們選擇不加了,意味着請求隊列和緩衝區都被塞滿以後,直接返回503錯誤碼,再也不等待了。
目前,是1個master進程,5個worker子進程,此外,超時時間、最大鏈接數以及緩衝區的設置值得斟酌,服務器性能好的話,能夠設置得高一些。負載均衡的策略不是基於權重的設置,而是使用的最少鏈接 (least_conn),由於在我看來,每一個服務實例都是能夠被同等對待的,哪臺空閒,就優先請求哪臺。若是非要在此基礎上設置一個weight,那就把性能高的服務器設置成較高的權重。
爲了應對大量的位置上報請求,必然須要引入消息隊列。由於技術團隊一直在使用RabbitMQ,而且一番壓測後,表現依然堅挺,因此成爲了咱們的不二選擇。
此外,爲了減輕存儲層的壓力,在數據落盤前有一個緩衝機制,是典型的主從雙Buffer緩衝池,避免與存儲層頻繁交互,佔用過多connection,提升了存儲層的工做效率,可是緩衝池Flush的時候,會帶來一次「IO尖刺」,能夠調整緩衝池的Threshold,把「IO尖刺」控制在一個合理的範圍內。
最後,也是最引人關注的是存儲層的選型。咱們面臨的選擇比較多,咱們預研了4種有可能的方案:MySQL,Redis,PostGIS,MongoDB。
MySQL的方案有兩條路可走,第一是使用純SQL進行計算,很明顯這條路子受限於數據量,隨着數據量的增大,計算量劇增,系統性能急劇降低。第二是使用MySQL的Spatial Indexes,可是這類空間索引是MySQL5.7版本引入的,不巧的是,咱們用的是阿里雲RDS,MySQL版本是5.6.16,不得不放棄MySQL的方案。
基於Redis GeoHash的方案,在性能方面是沒任何問題的,可是有一個重大的缺憾,就是很是受限於距離查詢,而沒法方便的匹配另外的附加屬性,也就是上述三維需求(時間、對象、區域)中,只能知足區域查詢的需求,若想進一步過濾,還須要藉助其餘的存儲技術。
留下來的PostGIS和MongoDB是如今比較通用的解決方案。
PostGIS的專業性很高,是地圖服務商的首選,對OGC的標準支持得很是全面,提供的函數庫也十分豐富。固然,MongoDB也不賴,3.0版本引入的WiredTiger存儲引擎,給MongoDB增色了很多,性能和併發控制都有了較大幅度的提高。
單就LBS應用來講,二者均可以很好的知足各方面的業務需求,性能也不是咱們首要考慮的因素,由於二者的可擴展性都很強,不容易觸及瓶頸。最後咱們選擇了MongoDB,是由於技術團隊對此接受程度更高,MongoDB原本就是公司技術棧中的一員。PostGIS相對來講學習成本較高,由於Postgresql是一種關係型數據庫,若是隻接入普通的數據類型,其實至關因而維護了另外一種MySQL,徹底不必,可是若是想用上GIS相關數據類型,現有的ORM框架支持得並不友好,開發效率低,維護成本高。
出於對用戶體驗的考慮,不能由於讀寫速度慢讓用戶傻傻的等待,這類問題有一個通用的解決方案:異步化。
異步化的寫請求很好實現,如前述所說,經過消息中間件來解決此問題,對於客戶端的位置上報請求,不須要強一致性,能肯定數據被塞入消息隊列中便可。
除此以外,還在消息隊列和存儲層之間加入了雙Buffer緩衝池,用以增長flush的效率。
上圖中的Buffer-1和Buffer-2共同組成一個循環隊列,每次只有一個Buffer用了緩衝數據。圖中的Flush Point表示每一個Buffer達到必定的閾值(Threshold)後,將會觸發數據落盤,而且會在此刻切換Buffer。圖中所示的閾值是50%,能夠根據實際狀況上下調整。
值得注意的是,在Flush Point,應該是先切換Buffer,再flush數據,也就是在flush的同時,還能接收上報的位置信息。若是要等待flush完畢再切換,將會致使流量過渡不平滑,出現一段時間的隊列擁塞。
接下來要解決大量讀請求,應對這種問題,有一個不二法門就是:拆。
很是相似「分表」的思想,可是在這個場景下,沒有傳統意義上「分表」所帶來的諸多反作用。
「分表」有一個規則,好比按Hash(user_id)進行「橫向分表」,按照經常使用字段進行「縱向分表」。受到Hadoop關於資源管理的啓發,在此位置採集系統中,使用了「元數據」來代替分表規則,這裏的「元數據」至關於NameNode,是管理數據的數據,將散落在不一樣位置的數據集中化管理。
如上圖所示,按城市級別進行分庫,每一個城市的位置數據相對獨立,體如今客戶端的功能就是「切換城市」。「分表」意即分紅多份Collection,以一天爲最小粒度,由於一個城市的當天數據一般是「最熱」的,衆多查詢請求都須要這份數據,分頁,聚合,排序等需求都不是問題了,所以一天一個Collection是比較合理的切分。固然,若是一天的數據量過少,能夠適當延長數據切分的週期。
那麼,若是查詢請求的時間段跨天了,該怎麼辦呢?
這時候,「元數據」就發揮威力了。
查詢請求分爲兩部分,第一個是定位元數據,就是在meta data中找尋符合條件的全部Collection(s),爲了儘量縮小查詢範圍,須要指定必定的時間區段,這個也是有現實意義的。全部的位置數據被分爲了三種等級:Hot,Cold,Freeze,Hot等級一般是指當天的數據,讀寫最頻繁的部分;Cold等級被界定爲過去一個月內的數據,應對的各種查詢和分析的需求;Freeze等級是已歸檔的數據,沒有特殊狀況不會被解凍。
經過時間區段拿到N個Collection(s)後,附加上其餘的查詢條件,循環N次,成功循環一次,就渲染一次獲取到的數據,直到循環結束。
上述N>1的業務場景只可能出如今後臺管理系統,也就是移動端不會出現跨天的業務需求,提供的API都是針對Hot級別的數據。若是一旦出現了這種狀況,能夠限制時間區段,好比只能獲取兩天內的數據。
本文詳細闡述了手機定位採集系統的設計,整個系統的複雜性集中在了數據的存儲和呈現,我在此文中都給出了相應的解決方案。除此以外,整個採集系統還會有其餘的功能模塊,諸如日誌採集與分析,實時監控等,限於篇幅,再也不逐一敘述了。
如下是擴展閱讀,對於理解整個系統會有所幫助,建議閱讀之。
掃描下方二維碼,進入原創乾貨,搞「技」聖地。