源码位置:redis/src/geo.c
转自:
Redis源码剖析之GEO——Redis是如何高效检索地理位置的?_xindoo-CSDN博客Redis源码剖析之跳表(skiplist)_xindoo-CSDN博客
上文中花了大量篇幅讲解了geohash的实现,其实看到这里,你基本上已经理解了redis中的geohash的实现了。本质上redis中的geo就是对geohash的封装,具体geohash相关的代码就不给大家列了(可自行查阅),就给大家介绍下redis geo里的大体流程。
首先,可能大家最好奇的是geohash在redis中是怎么存储的,从geoadd命令的实现可以一窥端倪。
/* GEOADD key [CH] [NX|XX] long lat name [long2 lat2 name2 ... longN latN nameN] */ void geoaddCommand(client *c) { int xx = 0, nx = 0, longidx = 2; int i; /* 解析可选参数 */ while (longidx < c->argc) { char *opt = c->argv[longidx]->ptr; if (!strcasecmp(opt,"nx")) nx = 1;//判断字符串是否相等 else if (!strcasecmp(opt,"xx")) xx = 1; else if (!strcasecmp(opt,"ch")) {} else break; longidx++; } if ((c->argc - longidx) % 3 || (xx && nx)) { /* 解析所有的经纬度值和member,并对其个数做校验 */ addReplyErrorObject(c,shared.syntaxerr); return; } /* 构建zadd的参数数组 */ int elements = (c->argc - longidx) / 3; int argc = longidx+elements*2; /* ZADD key [CH] [NX|XX] score ele ... */ robj **argv = zcalloc(argc*sizeof(robj*)); argv[0] = createRawStringObject("zadd",4); for (i = 1; i < longidx; i++) { argv[i] = c->argv[i]; incrRefCount(argv[i]); } /* 以3个参数为一组,将所有的经纬度和member信息从参数列表里解析出来,并放到zadd的参数数组中 */ for (i = 0; i < elements; i++) { double xy[2]; if (extractLongLatOrReply(c, (c->argv+longidx)+(i*3),xy) == C_ERR) { for (i = 0; i < argc; i++) if (argv[i]) decrRefCount(argv[i]); zfree(argv); return; } /* 将经纬度坐标转化成score信息 */ 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[longidx + i * 3 + 2]; argv[longidx+i*2] = score; argv[longidx+1+i*2] = val; incrRefCount(val); } /* 转化成zadd命令所需要的参数格式*/ replaceClientCommandVector(c,argc,argv); zaddCommand(c); }
原来geo的存储只是zset包了一层壳(是不是有点小失望),关于zset的具体实现可以参考我之前写的文章redis中skiplist的实现。
我们再来详细看下georadius的大体执行流程(代码偏长,故删除大量细节代码)。
void georadiusGeneric(client *c, int srcKeyIndex, int flags) { robj *storekey = NULL; int storedist = 0; /* 0 for STORE, 1 for STOREDIST. */ /* 根据key找找到对应的zojb */ robj *zobj = NULL; if ((zobj = lookupKeyReadOrReply(c, c->argv[srcKeyIndex], shared.emptyarray)) == NULL || checkType(c, zobj, OBJ_ZSET)) { return; } /* 解析请求中的经纬度值 */ int base_args; GeoShape shape = {0}; if (flags & RADIUS_COORDS) { /* * 各种必选参数的解析,省略细节代码,主要是解析坐标点信息和半径 */ } /* 解析所有的可选参数. */ int withdist = 0, withhash = 0, withcoords = 0; int frommember = 0, fromloc = 0, byradius = 0, bybox = 0; int sort = SORT_NONE; int any = 0; /* any=1 means a limited search, stop as soon as enough results were found. */ long long count = 0; /* Max number of results to return. 0 means unlimited. */ if (c->argc > base_args) { /* * 各种可选参数的解析,省略细节代码 */ } /* Get all neighbor geohash boxes for our radius search * 获取到要查找范围内所有的9个geo邻域 */ GeoHashRadius georadius = geohashCalculateAreasByShapeWGS84(&shape); /* 创建geoArray存储结果列表 */ geoArray *ga = geoArrayCreate(); /* 扫描9个区域中是否有满足条的点,有就放到geoArray中 */ membersOfAllNeighbors(zobj, georadius, &shape, ga, any ? count : 0); /* 如果没有匹配结果,返回空对象 */ if (ga->used == 0 && storekey == NULL) { addReply(c,shared.emptyarray); geoArrayFree(ga); return; } long result_length = ga->used; long returned_items = (count == 0 || result_length < count) ? result_length : count; long option_length = 0; /* * 后续一些参数逻辑,比如处理排序,存储…… */ // 释放geoArray占用的空间 geoArrayFree(ga); }
上述代码删减了大量细节,有兴趣的同学可以自行查阅。不过可以看出georadius的整体流程非常清晰。