13 | GEO是什么?还可以定义新的数据类型吗?

156 阅读4分钟

0.基本概念

  • 数据结构

    • 基本类型

      • String
      • List
      • Hash
      • Set
      • Sorted Set
    • 扩展数据类型

      • Bitmap
      • HyperLogLog
      • GEO
    • 自定义数据类型

1.面向 LBS 应用的 GEO 数据类型

  • 位置信息服务(Location-Based Service,LBS),GEO 就非常适合应用在 LBS 服务的场景中\

  • 如果使用hash记录一系列车辆 ID 和经纬度的对应关系

    • 不支持范围查询(因为元素是无序的)
  • 如果使用Sorted Set,key 就是 Sorted Set 中的元素,而 value 则是元素的权重分数

  • 使用GeoHash

2.GeoHash 的编码方法

  • 目标:将一组经纬度转换成float,作为score分数

  • 核心原理:当我们要对一组经纬度进行 GeoHash 编码时,我们要先对经度和纬度分别编码,然后再把经纬度各自的编码组合成一个最终编码

  • 具体编码原理

    • 经度范围是[-180,180]\

    • GeoHash 编码会把一个经度值编码成一个 N 位的二进制值,我们来对经度范围[-180,180]做 N 次的二分区操作\

      • 进行第一次二分区时,经度范围[-180,180]会被分成两个子区间:[-180,0) 和[0,180]

      • 如果是落在左分区,我们就用 0 表示

      • 如果落在右分区,就用 1 表示

      • 每做完一次二分区,我们就可以得到 1 位编码值\

      • 做完 N 次的二分区后,经度值就可以用一个 N bit 的数来表示了\

      • 组合规则:最终编码值的偶数位上依次是经度的编码值,奇数位上依次是纬度的编码值(2N位)1110011101

    • 使用 GeoHash 编码后,完成物理地址分区(可以很快识别那些元素在模块中)

\

3.如何操作 GEO 类型?

  • GEOADD 和 GEORADIUS\

    • GEOADD:用于把一组经纬度信息和相对应的一个 ID 记录到 GEO 类型集合中\

    • GEORADIUS 命令:会根据输入的经纬度位置,查找以这个经纬度为中心的一定范围内的其他元素(半径可以自行定义)\

    • GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10 5km内,最近的,10个

  • 可以很轻松操作经纬度

4.如何自定义数据类型?

  • Redis 的基本对象结构 RedisObject(dictentry是必备的)

    • 元数据

      • type 值类型,5大基本类型\

      • encoding 值的编码方式 底层数据结构:例如 SDS、压缩列表、哈希表、跳表等\

      • lru 记录了这个对象最后一次被访问的时间,用于淘汰过期的键值对\

      • refcount 记录了对象的引用计数

    • *ptr指向数据的指针\

  • 定义了新的数据类型后,也只要在 RedisObject 中设置好新类型的 type 和 encoding,再用*ptr指向新类型的实现\

  • 创建NewTypeObject 的新数据类型

    • 定义新数据类型底层结构

    • 在 RedisObject 的 type 属性中,增加这个新类型的定义\

      • Redis 的 server.h 文件中\
    • 开发新类型的创建和释放函数\

      • 数据类型的创建和释放函数都定义在了XXXobject.c 文件中\

      • 释放函数zfree

    • 开发新类型的命令操作\

      • 在 t_newtype.c 文件中增加命令操作的实现\

      • 在 server.h 文件中,声明我们已经实现的命令,以便在 server.c 文件引用这个命令\

      • 在 server.c 文件中的 redisCommandTable 里面,把新增命令和实现函数关联起来\

第一步:

//单向列表
struct NewTypeObject {     struct NewTypeNode *head;      size_t len;  }NewTypeObject;
 struct NewTypeNode {     long value;     struct NewTypeNode *next; }; 

第二步:

#define OBJ_STRING 0    /* String object. */ 
#define OBJ_LIST 1      /* List object. */ 
#define OBJ_SET 2       /* Set object. */ 
#define OBJ_ZSET 3      /* Sorted set object. */#define OBJ_NEWTYPE 7 

第三步:

robj *createNewTypeObject(void){    
   NewTypeObject *h = newtypeNew();     
   robj *o = createObject(OBJ_NEWTYPE,h);    
   return o; 
}
//需要自定义的内置函数newtypeNew 为新数据类型初始化内存结构
//位置:newtypeNew 函数定义在名为 t_newtype.c 的文件中(类似接口实现)
//t_string.c 和 t_list.c 分别对应 String 和 List 类型
NewTypeObject *newtypeNew(void){     
   NewTypeObject *n = zmalloc(sizeof(*n));//使用zmalloc分配内存,以便写入数据     
   n->head = NULL;     
   n->len = 0;     
   return n; 
} 
//通用的内置函数createObject
robj *createObject(int type, void *ptr) {     
   robj *o = zmalloc(sizeof(*o));     
   o->type = type;     
   o->ptr = ptr;     
   ...     
   return o; 
} 

第四步

//声明插入操作
void ntinsertCommand(client *c){ //基于客户端传递的参数,实现在NewTypeObject链表头插入元素} 
//引用命令 void ntinsertCommand(client *c) 
//绑定指令和函数 struct redisCommand redisCommandTable[] = {  
... 
{"ntinsert",ntinsertCommand,2,"m",...} 
} 

5.小结

  • GEO 类型使用 GeoHash 编码方法实现了经纬度到 Sorted Set 中元素权重分数的转换,这其中的两个关键机制就是对二维地图做区间划分,以及对区间进行编码。一组经纬度落在某个区间后,就用区间的编码值来表示,并把编码值作为 Sorted Set 元素的权重分数\

  • 扩展类型

    • 基于现有的数据类型,通过数据编码或是实现新的操作的方式,来实现扩展数据类型

      • geohash\

      • Bitmap\

    • 开发自定义的数据类型,具体的操作是增加新数据类型的定义,实现创建和释放函数,实现新数据类型支持的命令操作