億級「附近的人」,打通「特殊服務」通道

本文使用postgis實現了數億活躍用戶的附近xx功能。好比,搖一搖。更多功能關注小姐姐味道微信公衆號:xjjdog.html

假如動物們也用GPS,忽然有那麼一天北極的公北極熊有點衝動,想刷一下附近有沒有母熊。要求距離越近越好,不是澳大利亞動物園那隻,也不是格陵蘭島上被囚禁的那羣呆企鵝,要是有點共同的嗜好就再好不過了。這種應用場景如何解決?mysql

一個基於LBS的社交應用或者電商應用,或多或少的包含一些地理信息,如經緯度(lat、lng)。如何在既定的時限內響應用戶的請求,如何低成本的存儲這些數據,是LBS應用最關鍵的問題。咱們以附近的人爲例,看一下如何去作一個生產級別的應用。ios

方案

你可能已經瞭解到,目前有多種方法能夠實現這樣的功能,如solr、es、mongodb、redis等scheme free的數據庫,也有使用mysql+geohash來實現這些功能的。git

爲何不用geohash將問題一緯化呢?github

由於這種作法沒法準確計算距離,並且擴展性和維護性都是問題redis

爲何不用solr、es、mysql、sphinx呢?sql

由於這幾位都是gis函數庫的閹割版,多個維度查詢會有問題,優化困難mongodb

爲何不用mongodb數據庫

由於mongodb會隨數據量的增長在地理位置查詢時性能會急劇降低,而pg是線性的json

爲何不用redis geo呢?

redis數據所有放在內存中,不支持排序。有誰用在生產環境中了,請告訴我...

本文采用postgis方案,相比較其餘方案,開發人員對SQL都比較熟悉。技術選擇上,你選擇了最優,你就節約了時間和成本,人生苦短,做爲使用者不必在一些半成品上浪費時間。postgresql自己是最優秀的開源RDBMS,postgis是功能最多、最成熟的開源gis數據庫。GIS方面,支持:

一、空間數據類型,包括:點(POINT)、線(LINESTRING)、多邊形(POLYGON)、多點(MULTIPOINT)、 多線(MULTILINESTRING)、多多邊形(MULTIPOLYGON)和集合對象集(GEOMETRYCOLLECTION)

二、空間分析函數,包括:面積(Area)、長度(Length)和距離(Distance)

三、元數據以及函數,包括:GEOMETRY_COLUMNS和SPATIAL_REF_SYS

四、二元謂詞,包括:Contains、Within、Overlaps和Touches

五、空間操做符,包括:Union和Difference

實現/單機

咱們首先看下單機版的附近的人: 首先,安裝之。以centos7爲例。

echo 'exclude=postgresql*' >> /etc/yum.repos.d/CentOS-Base.repo
cat /etc/readhat-release
sudo rpm -ivh http://yum.postgresql.org/9.5/redhat/rhel-7-x86_64/pgdg-centos95-9.5-2.noarch.rpm
yum install postgresql95 postgresql95-server postgresql95-libs postgresql95-contrib postgresql95-devel
 
 service postgresql-9.5 initdb
 chkconfig postgresql-9.5 on
 service postgresql-9.5 restart
複製代碼

Postgis的依賴比較多,因爲CentOS默認是有pg源的,要首先排除它,安裝專用源。 基本數據結構以下:

CREATE EXTENSION postgis;

drop table if exists nearby_user;
create table nearby_user(
userid varchar,
sex varchar,
age int,
loc geometry,
updateTime timestamp
); 

CREATE INDEX geom_loc_index ON nearby_user USING GIST(loc);
CREATE INDEX geom_time_index ON nearby_user USING BTREE(updateTime);
CREATE UNIQUE INDEX userid_index ON nearby_user  USING BTREE(userid);
複製代碼

有三個比較重要的點

一、 經過create extension語句建立postgis插件,每一個庫只能建立一次

二、建立一個gis類型字段,支持POINT、POLYGON等多種數據類型,咱們後續的排序和計算都將使用此字段

三、爲loc字段建立空間索引(GIST索引),能夠進行排序、計算距離等

如圖,咱們要查詢某個用戶最近N天附近的人,根據距離有近到遠進行排序,查詢第一頁,每頁25條

一、使用planar degrees 4326座標系計算兩個點之間的距離(Point(x,y))

二、將查詢的結果轉換爲meters 26986座標系表示的距離,此即普通單位米。爲何將這一步單獨作一個嵌套查詢呢?由於ST_Transform是不走索引的,距離排序要全表掃,代價太大

三、ST_X,ST_Y等,將座標轉化爲可讀的經緯度,而不是0101000020E61000005C5E792FA2075D4026BC259C750C4440這種天文數字

如圖,查看執行計劃,使用了geom_loc_index索引進行排序,其餘條件走過濾匹配。單表300W+數據,2k+ QPS下,執行只花費了7ms(24核、32G、SATA),算得上是很是神奇了。

實現/集羣

分佈式計算第必定律:若是不是真正須要就不要讓系統分佈式。但隨着業務擴張,DAU不斷上漲,逐漸達到百萬+,就不得不考慮可用性和擴展性了。咱們從如下幾個方面探討如何作一個可伸縮的高可用附近的XX。

需求

  • 要求較高的實時性,不作緩存,讀取和寫入都比較頻繁(1w+ TPS/s)
  • 可以按照查詢距離進行排序,可以分頁
  • 支持除位置意外的其餘條件過濾(如年齡,性別,用戶標籤等)
  • 支持GIS其餘擴展功能,如三維、區塊包含查詢
  • 要求大部分查詢可以在100ms內返回,部分長尾請求不超過1s
  • 要求支持集羣環境基本的failover、SLB功能

分析

系統實時性要求比較高,因此並不能經過折衷方案進行結果緩存。用戶的每次請求都須要實際的計算,這注定了CPU將成爲系統的主要爭奪點。因爲RDBMS的特性,在內存有限的環境中,IO也會成爲瓶頸,建議有條件的儘可能掛載SSD硬盤。

因爲GIS應用會有熱點問題和各類數據調整問題,傳統的sharding技術(mod、hash、random)並不能很好的工做,咱們須要自定義路由表。這種狀況下,Greenplum或者Postgresql-XL(GTM會成爲瓶頸)這類分佈式解決方案就不在考慮以內,避免陷入額外的技術陷阱和成本陷阱。

路由表可使用geohash進行分塊或者按照實際的城市區域代碼進行分片映射。使用區域代碼進行分片,會有比較好的效果,由於地理的分界線通常都是山川河流等數據不敏感的地區,但這種方式須要你有一個逆地理服務(根據經緯度查詢城市編碼),搭建成本是比較高的。

geohash就簡單的多,但會有必定的數據瑕疵,假定咱們採用的是geohash編碼(請自行解決geohash的問題,簡單來說,就是將地球上的一個區域塊,一維化爲一個固定的編碼,而後把地球切分紅這麼一羣區域塊)。使用這種方式,就能夠將熱點進行分片,一個可能的數據映射以下:

每一組機器有一臺master,N臺slave經過WAL日誌進行復制。每一個geohash塊屬於一組或多組機器,都有一個標識來代表節點的權重,以及是否可用。

而後咱們將geohash分紅十幾個組,好比12個,那麼須要的pg實例個數就爲 12*(masterNum+slavesNum) = 36個。實例個數增加,就須要一種集羣管理方法,避免被服務癱瘓的報警叫起牀。

架構

可使用以下的架構:

- Location Service提供用戶位置服務,可使用簡單的KV數據庫進行保存,目的是能夠隨時查看到用戶的位置信息

  • 用戶的位置更新,最好打到Queue裏進行緩衝。這種模式有不少好處,好比你能夠訂閱一份數據專門去作用戶的軌跡服務
  • PgRouter 將經緯度轉化爲geohash,根據路由表信息,定位到pg集羣中的一批節點,進行查詢計算
  • 節點的啓停、主從關係,使用repmgr進行管理。Master故障Slave可以自動提權
  • PgMonitor 是一組腳本,可以監控節點的存活狀態和主從關係,而後將存活信息更新到Zookeeper或者Etcd中,固然也能夠是consul。
  • PgRouter監聽到節點變化,會重建內存路由表信息,隔離故障節點

問題解決

接下來咱們分析這些問題如何解決。

熱點問題如何解決,如何應對突發流量?

熱點取決於你對geohash劃分的粒度,你能夠經過掛載多個從庫或者將一批cluster進行拆分

複製的效率和一致性如何解決?

數據庫採用standby WAL日誌進行復制,速度很快,延遲小。若是從機太多,能夠採用級聯複製方式(slave的slave)。因爲採用了單master,能夠保證一致性問題。惟一的問題是master宕機切換過程會形成寫入失敗,因此消息隊列有必要採用失敗重試的策略。案例中pg既做爲一個存儲節點,又做爲一個計算節點。若是你的應用對數據的一致性要求不是那麼高,徹底能夠將事務隔離級別設置爲"read uncommitted"

負載均衡放在哪一個層面去作?

曾經考慮過使用HA或者LVS,再或者kubernetes將pg打形成一個微服務。但萬變不離其宗,這些花拳繡腿會引入額外的複雜性,遠不如簡單的自定義路由來的方便快捷,咱們引入節點權重的意義就在這裏,如某些節點由於IO等運算緩慢,就能夠下降其權重來解決。

迭代過程須要變動scheme,postgis如何動態添加某個字段?

能夠直接添加,並不影響服務,但要注意刪除操做可能會有較大的影響。

如何動態添加刪除索引?

不建議這麼作,若是確實有這部分需求,建議業務低峯進行此操做

如何實現如QQ中用戶標籤的過濾?好比查詢一批擁有"逗逼"標籤的人

咱們採用pg的另一個緣由就是,它的數據類型很是豐富,這在使用中就顯得特別簡潔和方便。pg是一個學術派很濃的數據庫,可以試用一些最前沿功能。好比標籤就能夠用hstore或者jsonb數據類型來實現。在可預見的項目生命週期中,pg的支持足夠了

如何去作監控?

本身編寫zabbix插件、或者接入nagios,也能夠接入grafana,取決於你所使用的監控平臺。也有pgcluu等工具。

如何監控節點的上下線?

這個比較簡單,可使用腳本輪訓檢測,也可使用repmgr的主動通知功能,構造事件寫入配置中心。

下面是一個簡單的腳本例子:

END

更復雜的,若是PostGIS也沒法知足你的性能需求,你可能已是行業巨頭了,能夠考慮用PostGIS作數據存儲源,用Solr/ES專門提供搜索等。但目前爲止,北極熊也已經找到了它的小夥伴,多快樂啊。

參考連接: postgis: www.postgis.net/ postgresql: www.postgresql.org/docs/9.5/st… repmgr: github.com/2ndQuadrant…

更多精彩文章。

《微服務不是所有,只是特定領域的子集》

《「分庫分表" ?選型和流程要慎重,不然會失控》

這麼多監控組件,總有一款適合你

《Linux生產環境上,最經常使用的一套「vim「技巧》

《使用Netty,咱們到底在開發些什麼?》

Linux五件套之類的。

《Linux之《荒島餘生》(一)準備篇》

《Linux之《荒島餘生》(二)CPU篇》

《Linux之《荒島餘生》(三)內存篇》

《Linux之《荒島餘生》(四)I/O篇》

《Linux之《荒島餘生》(五)網絡篇》

更多請關注。固然也能夠關注公衆號。

相關文章
相關標籤/搜索