一、引言
在電商物流領域我們會涉及到地址,其中包括了基礎的四級地址和使用者填寫的地址。四級地址在整個從下單到收貨的業務流程中都會用到,因此設計的時候要考慮如何最大限度地提高QPS。使用者地址在下單的時候讓使用者填寫或者選擇,然後存在交易訂單和物流訂單上,後續的流程一般不會變,如果使用者需要修改地址,直接變更交易訂單和物流訂單的地址資訊即可,因此設計的時候主要考慮滿足各種使用者地址場景。
二、物流地址資料模型設計
1、dvc_division表
描述:四級地址表
表結構:
欄位名稱 | 欄位型別 | 是否可以為空 | 描述 |
id | bigint | 否 | 主鍵 |
name | varchar(128) | 否 | 名稱 |
code | varchar(32) | 否 | 編碼 |
level | int | 否 | 層級:1、2、3、4 |
parent_code | varchar(32) | 是 | 上級地址code |
country | varchar(16) | 是 | 預設CN |
language | varchar(16) | 是 | 預設ZH_CN |
status | int | 否 | 1正常,2廢棄 |
post_code | varchar(16) | 是 | 郵編 |
longititude | varchar(32) | 是 | 經度 |
latitude | varchar(32) | 是 | 維度 |
version | int | 否 | 版本號 |
feature | varchar(1024) | 是 | 擴充套件欄位 |
gmt_created | datetime | 否 | 建立時間 |
gmt_modified | datetime | 否 | 修改時間 |
上面的表結構以一種一維的角度描述了四級地址,可能不太直觀,下面用一個例子來說明四級地址是怎麼組織的:
四級地址第一級:
省份編碼兩位,從1開始到34代表34個省級行政區(23省+4直轄市+5自治區+2特別行政區),以浙江為例為8
四級地址第二級:
城市編碼兩位,從01開始遞增,省份+城市構成二級地址,以杭州為例就是801
四級地址第三級:
區域編碼兩位,從01開始遞增,省份+城市+區域構成三級地址,以杭州西湖區為例就是80103
四級地址第四級:
街道編碼兩位,從01開始遞增,省份+城市+區域+街道構成四級地址,以浙江省杭州市西湖區古蕩街道為例就是8010301
上面的每一級地址都通過parent_code關聯上一級地址,因此只要知道任意一級地址,都可以把整個四級地址都查出來。
2、user_address表
描述:使用者地址表
表結構
欄位名稱 | 欄位型別 | 是否可以為空 | 描述 |
id | bigint | 否 | 主鍵 |
user_id | bigint | 否 | 使用者ID |
user_name | varcahr(64) | 是 | 使用者名稱稱 |
shop_id | bigint | 是 | 店鋪ID |
user_type | int | 否 | 使用者型別:1買家,2賣家 |
telephone | varchar(16) | 是 | 手機號 |
phone | varchar(16) | 是 | 座機號 |
country | varchar(16) | 是 | 國家,預設CN |
province | varchar(16) | 是 | 省份名稱 |
province_code | varchar(32) | 是 | 省份code |
city | varchar(32) | 是 | 城市名稱 |
city_code | varchar(32) | 是 | 城市code |
area | varchar(32) | 是 | 地區名稱 |
area_code | varchar(32) | 是 | 地區code |
street | varchar(32) | 是 | 街道名稱 |
street_code | varchar(32) | 是 | 街道code |
detail_address | varchar(1024) | 否 | 詳細地址 |
address_code | varchar(32) | 否 | 最小code |
is_default | int | 否 | 是否預設,1預設,2非預設 |
language | varchar(16) | 是 | 語言,預設ZH_CN |
post_code | varchar(16) | 是 | 郵編 |
version | int | 否 | 版本號 |
feature | varchar(1024) | 是 | 擴充套件欄位 |
gmt_created | datetime | 否 | 建立時間 |
gmt_modified | datetime | 否 | 修改時間 |
索引:
user_id普通索引
三、四級地址庫高併發設計
四級地址庫的使用場景是查詢非常多,修改和建立幾乎沒有,因此我們首先想到的是使用快取。對於快取,有基於redis的集中式快取,也有基於JVM的本地快取,考慮到四級地址庫的使用場景,基於redis的集中式快取在後期不一定能支撐巨大的查詢量,因此我們從一開始就選擇基於JVM的本地快取,下面是對本地快取的技術選型:
1、基於Guava Cache+定時任務
該策略使用guava cache做本地快取,由於guava cache本質上是KV資料,因此針對不同的查詢場景,需要構建不同的快取,然後通過elastic-job定時(比如每天凌晨)將資料庫資料重新整理到快取。這種策略編碼實現比較簡單,但是無法適應複雜場景的查詢,而且隨著查詢場景的增多,記憶體資料會越來越大。
2、基於H2記憶體資料庫
該策略基於H2記憶體資料庫+Mybatis實現了複雜查詢場景,同時又保證了效能。但是,由於H2資料庫的資料是在啟動的時候從檔案載入的,執行期無法變更,因此,每次地址庫更新,都需要更新啟動時的資料檔案。
3、最終決策
考慮到四級地址資料一年也更新不了幾次,我們最終選擇了方案二:基於H2記憶體資料庫的快取。我們把H2資料檔案、H2資料庫SQL、Mybatis介面都封裝在了一個client中打包出去,外部系統直接依賴這個client包就可以獲得四級地址庫的能力。當地址庫資料更新,我們會重新打包client,其他應用只要升級這個client包就可以獲得最新的四級地址資料。
四、基於經緯度的使用者地址查詢
使用者在下單的時候,需要填寫收貨地址,普通的填寫方式通過四級地址一級一級往下填,使用者體驗比較繁瑣。為了提升使用者體驗,我們可以根據使用者的經緯度直接匹配出四級地址。
我們使用Redis GEO API進行地址與經緯度的對映,整體架構如下:
我們首先將使用者地址經緯度對映到四級地址code中,存入redis,前端應用根經緯度從redis獲取四級地址code,然後根據code查詢四級地址詳細資訊。由於資料儲存在redis,為了防止快取被逐出或者redis被重啟,每天凌晨使用定時任務重新整理redis的資料。
我們使用redis geo API主要使用兩個命令:
1.GEOADD
命令:GEOADD key longitude latitude member [longitude latitude member ...]
命令描述:將指定的地理空間位置(緯度、經度、名稱)新增到指定的key中。
返回值:新增到sorted set元素的數目,但不包括已更新score的元素。
2.GEORADIUS
命令:GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
命令描述:
以給定的經緯度為中心, 返回鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的所有位置元素。
範圍可以使用以下其中一個單位:
- m 表示單位為米。
- km 表示單位為千米。
- mi 表示單位為英里。
- ft 表示單位為英尺。
五、總結
通過上面的介紹,我們基本介紹完了一個物流地址系統的關鍵技術要點,接下來的一篇文章會把物流詳情和物流服務的能力一併介紹,然後進入我們的重頭戲:如何構建一個具有良好擴充套件性的物流產品服務層。
更多文章歡迎訪問 http://www.apexyun.com/
聯絡郵箱:public@space-explore.com
(未經同意,請勿轉載)