如何使用 Redis 實現 陪玩原始碼中“附近的人” 這一功能?
前言
操作命令
GEOADD
GEOADD key longitude latitude member [longitude latitude member ...]
/* GEOADD key long lat name [long2 lat2 name2 ... longN latN nameN] */void geoaddCommand(client *c) {//引數校驗 /* Check arguments number for sanity. */ if ((c->argc - 2) % 3 != 0) { /* Need an odd number of arguments if we got this far... */ addReplyError(c, "syntax error. Try GEOADD key [x1] [y1] [name1] " "[x2] [y2] [name2] ... "); return; }//引數提取Redis int elements = (c->argc - 2) / 3; int argc = 2+elements*2; /* ZADD key score ele ... */ robj **argv = zcalloc(argc*sizeof(robj*)); argv[0] = createRawStringObject("zadd",4); argv[1] = c->argv[1]; /* key */ incrRefCount(argv[1]);//引數遍歷+轉換 /* Create the argument vector to call ZADD in order to add all * the score,value pairs to the requested zset, where score is actually * an encoded version of lat,long. */ int i; for (i = 0; i < elements; i++) { double xy[2]; //提取經緯度 if (extractLongLatOrReply(c, (c->argv+2)+(i*3),xy) == C_ERR) { for (i = 0; i < argc; i++) if (argv[i]) decrRefCount(argv[i]); zfree(argv); return; } //將經緯度轉換為52位的geohash作為分值 & 提取物件名稱 /* Turn the coordinates into the score of the element. */ GeoHashBits hash; geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash); GeoHashFix52Bits bits = geohashAlign52Bits(hash); robj *score = createObject(OBJ_STRING, sdsfromlonglong(bits)); robj *val = c->argv[2 + i * 3 + 2]; //設定有序集合的物件元素名稱和分值 argv[2+i*2] = score; argv[3+i*2] = val; incrRefCount(val); }//呼叫zadd命令,儲存轉化好的物件 /* Finally call ZADD that will do the work for us. */ replaceClientCommandVector(c,argc,argv); zaddCommand(c);}
GEORADIUS
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] [STORE key] [STORedisT key]
[ ["member1", distance1, [longitude1, latitude1]] ["member2", distance2, [longitude2, latitude2]]]
/* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC] * [COUNT count] [STORE key] [STORedisT key] * GEORADIUSBYMEMBER key member radius unit ... options ... */void georadiusGeneric(client *c, int flags) { robj *key = c->argv[1]; robj *storekey = NULL; int stoRedist = 0; /* 0 for STORE, 1 for STORedisT. *///根據key獲取有序集合 robj *zobj = NULL; if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == NULL || checkType(c, zobj, OBJ_ZSET)) { return; }//根據使用者輸入(經緯度/member)確認中心點經緯度 int base_args; double xy[2] = { 0 }; if (flags & RADIUS_COORDS) { …… }//獲取查詢範圍距離 double radius_meters = 0, conversion = 1; if ((radius_meters = extractDistanceOrReply(c, c->argv + base_args - 2, &conversion)) < 0) { return; }//獲取可選引數 (withdist、withhash、withcoords、sort、count) int withdist = 0, withhash = 0, withcoords = 0; int sort = SORT_NONE; long long count = 0; if (c->argc > base_args) { ... ... }//獲取 STORE 和 STORedisT 引數 if (storekey && (withdist || withhash || withcoords)) { addReplyError(c, "STORE option in GEORADIUS is not compatible with " "WITHDIST, WITHHASH and WITHCOORDS options"); return; }//設定排序 if (count != 0 && sort == SORT_NONE) sort = SORT_ASC;//利用中心點和半徑計算目標區域範圍 GeoHashRadius georadius = geohashGetAreasByRadiusWGS84(xy[0], xy[1], radius_meters);//對中心點及其周圍8個geohash網格區域進行查詢,找出範圍內元素物件 geoArray *ga = geoArrayCreate(); membersOfAllNeighbors(zobj, georadius, xy[0], xy[1], radius_meters, ga);//未匹配返空 /* If no matching results, the user gets an empty reply. */ if (ga->used == 0 && storekey == NULL) { addReplyNull(c); geoArrayFree(ga); return; }//一些返回值的設定和返回 …… geoArrayFree(ga);}
// geohash_helper.cGeoHashRadius geohashGetAreasByRadiusWGS84(double longitude, double latitude, double radius_meters) { return geohashGetAreasByRadius(longitude, latitude, radius_meters);}//返回能夠覆蓋目標區域範圍的9個geohashBoxGeoHashRadius geohashGetAreasByRadius(double longitude, double latitude, double radius_meters) {//一些引數設定 GeoHashRange long_range, lat_range; GeoHashRadius radius; GeoHashBits hash; GeoHashNeighbors neighbors; GeoHashArea area; double min_lon, max_lon, min_lat, max_lat; double bounds[4]; int steps;//計算目標區域外接矩形的經緯度範圍(目標區域為:以目標經緯度為中心,半徑為指定距離的圓) geohashBoundingBox(longitude, latitude, radius_meters, bounds); min_lon = bounds[0]; min_lat = bounds[1]; max_lon = bounds[2]; max_lat = bounds[3];//根據目標區域中心點緯度和半徑,計算帶查詢的9個搜尋框的geohash精度(位)//這裡用到latitude主要是針對極地的情況對精度進行了一些調整(緯度越高,位數越小) steps = geohashEstimateStepsByRadius(radius_meters,latitude);//設定經緯度最大最小值:-180<=longitude<=180, -85<=latitude<=85 geohashGetCoordRange(&long_range,&lat_range); //將待查經緯度按指定精度(steps)編碼成geohash值 geohashEncode(&long_range,&lat_range,longitude,latitude,steps,&hash); //將geohash值在8個方向上進行擴充,確定周圍8個Box(neighbors) geohashNeighbors(&hash,&neighbors); //根據hash值確定area經緯度範圍 geohashDecode(long_range,lat_range,hash,&area);//一些特殊情況處理 ……//構建並返回結果 radius.hash = hash; radius.neighbors = neighbors; radius.area = area; return radius;}對中心點及其周圍8個geohash網格區域進行查詢:// geo.c//在9個hashBox中獲取想要的元素int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lon, double lat, double radius, geoArray *ga) { GeoHashBits neighbors[9]; unsigned int i, count = 0, last_processed = 0; int debugmsg = 0;//獲取9個搜尋hashBox neighbors[0] = n.hash; …… neighbors[8] = n.neighbors.south_west;//在每個hashBox中搜尋目標點 for (i = 0; i < sizeof(neighbors) / sizeof(*neighbors); i++) { if (HASHISZERO(neighbors[i])) { if (debugmsg) D("neighbors[%d] is zero",i); continue; } //剔除可能的重複hashBox (搜尋半徑>5000KM時可能出現) if (last_processed && neighbors[i].bits == neighbors[last_processed].bits && neighbors[i].step == neighbors[last_processed].step) { continue; } //搜尋hashBox中滿足條件的物件 count += membersOfGeoHashBox(zobj, neighbors[i], ga, lon, lat, radius); last_processed = i; } return count;}int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lon, double lat, double radius) {//獲取hashBox內的最大、最小geohash值(52位) GeoHashFix52Bits min, max; scoresOfGeoHashBox(hash,&min,&max);//根據最大、最小geohash值篩選zobj集合中滿足條件的點 return geoGetPointsInRange(zobj, min, max, lon, lat, radius, ga);}int geoGetPointsInRange(robj *zobj, double min, double max, double lon, double lat, double radius, geoArray *ga) {//搜尋Range的引數邊界設定(即9個hashBox其中一個的邊界範圍) zrangespec range = { .min = min, .max = max, .minex = 0, .maxex = 1 }; size_t origincount = ga->used; sds member;//搜尋集合zobj可能有ZIPLIST和SKIPLIST兩種編碼方式,這裡以SKIPLIST為例,邏輯是一樣的 if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { …… } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = zobj->ptr; zskiplist *zsl = zs->zsl; zskiplistNode *ln; //獲取在hashBox範圍內的首個元素(跳錶資料結構,效率可比擬於二叉查詢樹),沒有則返0 if ((ln = zslFirstInRange(zsl, &range)) == NULL) { /* Nothing exists starting at our min. No results. */ return 0; } //從首個元素開始遍歷集合 while (ln) { sds ele = ln->ele; //遍歷元素超出range範圍則break /* Abort when the node is no longer in range. */ if (!zslValueLteMax(ln->score, &range)) break; //元素校驗(計算元素與中心點的距離) ele = sdsdup(ele); if (geoAppendIfWithinRadius(ga,lon,lat,radius,ln->score,ele) == C_ERR) sdsfree(ele); ln = ln->level[0].forward; } } return ga->used - origincount;}int geoAppendIfWithinRadius(geoArray *ga, double lon, double lat, double radius, double score, sds member) { double distance, xy[2];//解碼錯誤, 返回error if (!decodeGeohash(score,xy)) return C_ERR; /* Can't decode. *///最終距離校驗(計算球面距離distance看是否小於radius) if (!geohashGetDistanceIfInRadiusWGS84(lon,lat, xy[0], xy[1], radius, &distance)) { return C_ERR; }//構建並返回滿足條件的元素 geoPoint *gp = geoArrayAppend(ga); gp->longitude = xy[0]; gp->latitude = xy[1]; gp->dist = distance; gp->member = member; gp->score = score; return C_OK;}
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2795230/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Golang 實現 Redis(9): 使用GeoHash 搜尋附近的人GolangRedis
- 用 Go + Redis 實現陪玩平臺原始碼中的分散式鎖GoRedis原始碼分散式
- 遊戲陪玩app原始碼開發,常用的倒數計時功能如何實現?遊戲APP原始碼
- 如何在遊戲陪玩系統原始碼中實現“刮刮樂”效果?遊戲原始碼
- 如何在遊戲陪玩app原始碼中實現簡訊驗證碼登入?遊戲APP原始碼
- 陪玩系統原始碼開發,H5頁面中呼叫支付功能的實現原始碼H5
- 如何實現遊戲陪玩系統原始碼前端效能監控?遊戲原始碼前端
- 通過經緯度計算距離實現附近、附近的人等功能
- 如何在遊戲陪玩系統原始碼中聊天室內實現一個禮物系統?遊戲原始碼
- 遊戲陪玩原始碼的移動端適配,應該如何實現?遊戲原始碼
- 如何開發陪玩系統原始碼的列表頁面,相關實現程式碼原始碼
- 陪玩平臺原始碼中的排序演算法,插入排序的實現原始碼排序演算法
- 陪玩系統原始碼中陣列去重的實現程式碼,簡單卻重要原始碼陣列
- 陪玩系統原始碼中mysql資料庫備份還原的實現程式碼原始碼MySql資料庫
- Redis 中的 set 和 sorted set 如何使用,原始碼實現分析Redis原始碼
- 陪玩系統原始碼實現音訊編碼的相關步驟原始碼音訊
- 遊戲陪玩系統原始碼中不同排序演算法的實現方式遊戲原始碼排序演算法
- 語音陪玩原始碼如何做到不卡頓?原始碼
- 使用者互動聊天,看陪玩平臺原始碼怎麼透過html實現原始碼HTML
- 陪玩平臺原始碼實現類似手機懸浮按鈕,需要如何做?原始碼
- 遊戲陪玩系統原始碼中懶載入的實現方式有哪幾種?遊戲原始碼
- 遊戲陪玩系統原始碼開發,如何實現圖片和動畫的優化?遊戲原始碼動畫優化
- Spring Boot + Redis 解決陪玩平臺原始碼重複提交問題Spring BootRedis原始碼
- 遊戲陪玩系統原始碼的許可權設計,如何基於位運算實現?遊戲原始碼
- 遊戲陪玩原始碼的登入方式,簡訊驗證碼登入的實現遊戲原始碼
- Spring Boot 2 實戰:利用Redis的Geo功能實現查詢附近的位置Spring BootRedis
- 遊戲陪玩app原始碼的可靠訊息最終一致性方案的實現遊戲APP原始碼
- 陪玩app原始碼,加密演算法中金鑰生成和讀取一覽APP原始碼加密演算法
- 遊戲陪玩平臺原始碼開發,依賴收集和觸發的實現遊戲原始碼
- [Redis原始碼閱讀]實現一個redis命令--nonzerodecrRedis原始碼
- 如何實現遊戲陪玩系統中語音的錄製與播放?遊戲
- 如何用分散式鎖解決陪玩平臺原始碼中的併發問題?分散式原始碼
- 從比心APP原始碼的成功,分析陪玩系統原始碼應該如何開發APP原始碼
- 使用redis實現互粉功能Redis
- 遊戲陪玩app開發,前端實現一個輪詢需要如何做?遊戲APP前端
- Netty 原始碼中對 Redis 協議的實現Netty原始碼Redis協議
- 陪玩原始碼,與時間、日期相關的程式碼分析原始碼
- 在遊戲陪玩原始碼開發中,兩種清空陣列的方式遊戲原始碼陣列