本文使用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條
二、將查詢的結果轉換爲meters 26986座標系表示的距離,此即普通單位米。爲何將這一步單獨作一個嵌套查詢呢?由於ST_Transform是不走索引的,距離排序要全表掃,代價太大
三、ST_X,ST_Y等,將座標轉化爲可讀的經緯度,而不是0101000020E61000005C5E792FA2075D4026BC259C750C4440這種天文數字
如圖,查看執行計劃,使用了geom_loc_index索引進行排序,其餘條件走過濾匹配。單表300W+數據,2k+ QPS下,執行只花費了7ms(24核、32G、SATA),算得上是很是神奇了。
分佈式計算第必定律:若是不是真正須要就不要讓系統分佈式。但隨着業務擴張,DAU不斷上漲,逐漸達到百萬+,就不得不考慮可用性和擴展性了。咱們從如下幾個方面探討如何作一個可伸縮的高可用附近的XX。
系統實時性要求比較高,因此並不能經過折衷方案進行結果緩存。用戶的每次請求都須要實際的計算,這注定了CPU將成爲系統的主要爭奪點。因爲RDBMS的特性,在內存有限的環境中,IO也會成爲瓶頸,建議有條件的儘可能掛載SSD硬盤。
因爲GIS應用會有熱點問題和各類數據調整問題,傳統的sharding技術(mod、hash、random)並不能很好的工做,咱們須要自定義路由表。這種狀況下,Greenplum或者Postgresql-XL(GTM會成爲瓶頸)這類分佈式解決方案就不在考慮以內,避免陷入額外的技術陷阱和成本陷阱。
路由表可使用geohash進行分塊或者按照實際的城市區域代碼進行分片映射。使用區域代碼進行分片,會有比較好的效果,由於地理的分界線通常都是山川河流等數據不敏感的地區,但這種方式須要你有一個逆地理服務(根據經緯度查詢城市編碼),搭建成本是比較高的。
geohash就簡單的多,但會有必定的數據瑕疵,假定咱們採用的是geohash編碼(請自行解決geohash的問題,簡單來說,就是將地球上的一個區域塊,一維化爲一個固定的編碼,而後把地球切分紅這麼一羣區域塊)。使用這種方式,就能夠將熱點進行分片,一個可能的數據映射以下:
而後咱們將geohash分紅十幾個組,好比12個,那麼須要的pg實例個數就爲 12*(masterNum+slavesNum) = 36個。實例個數增加,就須要一種集羣管理方法,避免被服務癱瘓的報警叫起牀。
可使用以下的架構:
接下來咱們分析這些問題如何解決。
熱點取決於你對geohash劃分的粒度,你能夠經過掛載多個從庫或者將一批cluster進行拆分
數據庫採用standby WAL日誌進行復制,速度很快,延遲小。若是從機太多,能夠採用級聯複製方式(slave的slave)。因爲採用了單master,能夠保證一致性問題。惟一的問題是master宕機切換過程會形成寫入失敗,因此消息隊列有必要採用失敗重試的策略。案例中pg既做爲一個存儲節點,又做爲一個計算節點。若是你的應用對數據的一致性要求不是那麼高,徹底能夠將事務隔離級別設置爲"read uncommitted"
曾經考慮過使用HA或者LVS,再或者kubernetes將pg打形成一個微服務。但萬變不離其宗,這些花拳繡腿會引入額外的複雜性,遠不如簡單的自定義路由來的方便快捷,咱們引入節點權重的意義就在這裏,如某些節點由於IO等運算緩慢,就能夠下降其權重來解決。
能夠直接添加,並不影響服務,但要注意刪除操做可能會有較大的影響。
不建議這麼作,若是確實有這部分需求,建議業務低峯進行此操做
咱們採用pg的另一個緣由就是,它的數據類型很是豐富,這在使用中就顯得特別簡潔和方便。pg是一個學術派很濃的數據庫,可以試用一些最前沿功能。好比標籤就能夠用hstore或者jsonb數據類型來實現。在可預見的項目生命週期中,pg的支持足夠了
本身編寫zabbix插件、或者接入nagios,也能夠接入grafana,取決於你所使用的監控平臺。也有pgcluu等工具。
這個比較簡單,可使用腳本輪訓檢測,也可使用repmgr的主動通知功能,構造事件寫入配置中心。
下面是一個簡單的腳本例子:
更復雜的,若是PostGIS也沒法知足你的性能需求,你可能已是行業巨頭了,能夠考慮用PostGIS作數據存儲源,用Solr/ES專門提供搜索等。但目前爲止,北極熊也已經找到了它的小夥伴,多快樂啊。
參考連接: postgis: www.postgis.net/ postgresql: www.postgresql.org/docs/9.5/st… repmgr: github.com/2ndQuadrant…
更多精彩文章。
Linux五件套之類的。
更多請關注。固然也能夠關注公衆號。