前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试遇到 Redis,我作为小白是这么被“刁难”的!|还可以学到什么(1)?

面试遇到 Redis,我作为小白是这么被“刁难”的!|还可以学到什么(1)?

作者头像
程序员小王
发布2021-03-22 12:44:35
4930
发布2021-03-22 12:44:35
举报
文章被收录于专栏:架构说

问题:万丈高楼平地起一 Redis 基础数据结 惨痛的教训

正式开始!

小白回答

我的思考:我清楚 redis 不通的对象,?️不同的的底层数据结构, 你问的是数据结构,而不是对象,因此我这样回答

redis数据结构有这些。

  • 字符串
  • 链表/压缩
  • 有序数组
  • 词典
  • 有序集合
  • 跳跃表

全部结构都回答一遍没有遗漏,然后不断回忆全部内容。

防不胜防别人点评:

  • 答思路混乱。一看就不懂,我根本听不懂。全部都列举一遍,都是记忆的,我看出你能力吗?
  • 我是想让回答这些结构吗?你直接把底层实现说了。是我期望的吗?第一步不清楚,直接第二步,好高骛远。

教训:你应该问一下数据结构是对象还是编码方式。他们问题有陷阱

有人称数据结构 就是基本的形式,最基本最傻的形式对比 容器的类型(string,vector,list map hash,优先级队列等) 这属于他们面试官认知不清楚,提问不高级 还是刻意这样做,就不清楚了。 这叫做诈唬。被忽悠了。看出自己不清楚。

后记:换个马甲就不认识了

  • 你对c++中容器 stl数据结构很清楚 例如数据结构 map,底层实现 红黑树。
  • 然后别人redis数据结构你傻掉了,不一样吗?
  • 根本原因

在认知中:红黑树是数据结构,map不是,因为map更加高级。

这一点错误理解,导致你不会主动沟通。

你能怪面试官不正规吗?

数据结构普通的类 ADT抽象,不一定是底层实现,c语言深陷太深,缘故, 遇到人工智能,其他岗位面试官你出问题了。你问清楚数据值是什么?不要自己想。

初级回答

reids 有很对数据结构,每个不通数据结构不通实现。我全部说一遍,我自己记不住呀 因此必须学会分类回答 ,然后重点说其中一个。重点自动调整,之前学容器时候 考虑每个结构使用条件。这里都给安排好了。

代码语言:javascript
复制
typedef struct redisObject {
    // 类型
    unsigned type:4;
    // 编码
    unsigned encoding:4;
    // 指向数据的指针
    void *ptr;

    // 记录对象最后一次被程序访问时间,用于计算空转时长(当前时间-lru)
    unsigned lru:22; /* lru time (relative to server.lruclock) */
    // 引用计数,用于内存回收
    int refcount;
} robj;

回答:

  • 不同的对象类型,根据存储数据多少,类型不通,采取不通的对象编码。
  • 5个基本对象(stirng,list,hash,set zset)8个编码方式。
  • 这个变化过程是redis内部实现的,用户不需要手工调整。
  • 例如:

编‍‍‍‍‍码

对象

REDIS_ENCODING_INTSET

使用整数集合实现的集合对象。

REDIS_ENCODING_HT

使用字典实现的集合对象。

REDIS_ENCODING_ZIPLIST

使用压缩列表实现的有序集合对象。

REDIS_ENCODING_SKIPLIST

使用跳跃表和字典实现的有序集合对象。5个对象类型,8个对象编码方式

5个对象类型,8个对象编码方式

代码语言:javascript
复制

# 键为字符串对象,值为字符串对象

redis> SET msg "hello world"
OK

redis> TYPE msg
string

# 键为字符串对象,值为列表对象

redis> RPUSH numbers 1 3 5
(integer) 6

redis> TYPE numbers
list

# 键为字符串对象,值为哈希对象

redis> HMSET profile name Tome age 25 career Programmer
OK

redis> TYPE profile
hash

# 键为字符串对象,值为集合对象

redis> SADD fruits apple banana cherry
(integer) 3

redis> TYPE fruits
set

# 键为字符串对象,值为有序集合对象

redis> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3

redis> TYPE price
zset
/* Object types */
// 对象类型
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

 rewriteAppendOnlyFile(char *filename) {
   
 if (o->type == REDIS_STRING) {
                /* Emit a SET command */
                char cmd[]="*3\r\n$3\r\nSET\r\n";
                if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
                /* Key and value */
                if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
                if (rioWriteBulkObject(&aof,o) == 0) goto werr;
            } else if (o->type == REDIS_LIST) {
                if (rewriteListObject(&aof,&key,o) == 0) goto werr;
            } else if (o->type == REDIS_SET) {
                if (rewriteSetObject(&aof,&key,o) == 0) goto werr;
            } else if (o->type == REDIS_ZSET) {
                if (rewriteSortedSetObject(&aof,&key,o) == 0) goto werr;
            } else if (o->type == REDIS_HASH) {
                if (rewriteHashObject(&aof,&key,o) == 0) goto werr;
            } else {
                redisPanic("Unknown object type");
            }

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
// 对象编码
#define REDIS_ENCODING_RAW 0     /* Raw representation */
#define REDIS_ENCODING_INT 1     /* Encoded as integer */
#define REDIS_ENCODING_HT 2      /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
  • 保存
代码语言:javascript
复制
/* Save a Redis object. Returns -1 on error, 0 on success. 
 *
 * 将给定对象 o 保存到 rdb 中。
 *
 * 保存成功返回 rdb 保存该对象所需的字节数 ,失败返回 0 。
 *
 * p.s.上面原文注释所说的返回值是不正确的
 */
int rdbSaveObject(rio *rdb, robj *o) {
    int n, nwritten = 0;

    // 保存字符串对象
    if (o->type == REDIS_STRING) {
        /* Save a string value */
        if ((n = rdbSaveStringObject(rdb,o)) == -1) return -1;
        nwritten += n;

    // 保存列表对象
    } else if (o->type == REDIS_LIST) {
        /* Save a list value */
        if (o->encoding == REDIS_ENCODING_ZIPLIST) {
            size_t l = ziplistBlobLen((unsigned char*)o->ptr);

            // 以字符串对象的形式保存整个 ZIPLIST 列表
            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;
        } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
            list *list = o->ptr;
            listIter li;
            listNode *ln;

            if ((n = rdbSaveLen(rdb,listLength(list))) == -1) return -1;
            nwritten += n;

            // 遍历所有列表项
            listRewind(list,&li);
            while((ln = listNext(&li))) {
                robj *eleobj = listNodeValue(ln);
                // 以字符串对象的形式保存列表项
                if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                nwritten += n;
            }
        } else {
            redisPanic("Unknown list encoding");
        }

    // 保存集合对象
    } else if (o->type == REDIS_SET) {
        /* Save a set value */
        if (o->encoding == REDIS_ENCODING_HT) {
            dict *set = o->ptr;
            dictIterator *di = dictGetIterator(set);
            dictEntry *de;

            if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) return -1;
            nwritten += n;

            // 遍历集合成员
            while((de = dictNext(di)) != NULL) {
                robj *eleobj = dictGetKey(de);
                // 以字符串对象的方式保存成员
                if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                nwritten += n;
            }
            dictReleaseIterator(di);
        } else if (o->encoding == REDIS_ENCODING_INTSET) {
            size_t l = intsetBlobLen((intset*)o->ptr);

            // 以字符串对象的方式保存整个 INTSET 集合
            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;
        } else {
            redisPanic("Unknown set encoding");
        }

    // 保存有序集对象
    } else if (o->type == REDIS_ZSET) {
        /* Save a sorted set value */
        if (o->encoding == REDIS_ENCODING_ZIPLIST) {
            size_t l = ziplistBlobLen((unsigned char*)o->ptr);

            // 以字符串对象的形式保存整个 ZIPLIST 有序集
            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;
        } else if (o->encoding == REDIS_ENCODING_SKIPLIST) {
            zset *zs = o->ptr;
            dictIterator *di = dictGetIterator(zs->dict);
            dictEntry *de;

            if ((n = rdbSaveLen(rdb,dictSize(zs->dict))) == -1) return -1;
            nwritten += n;

            // 遍历有序集
            while((de = dictNext(di)) != NULL) {
                robj *eleobj = dictGetKey(de);
                double *score = dictGetVal(de);

                // 以字符串对象的形式保存集合成员
                if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                nwritten += n;

                // 成员分值(一个双精度浮点数)会被转换成字符串
                // 然后保存到 rdb 中
                if ((n = rdbSaveDoubleValue(rdb,*score)) == -1) return -1;
                nwritten += n;
            }
            dictReleaseIterator(di);
        } else {
            redisPanic("Unknown sorted set encoding");
        }

    // 保存哈希表
    } else if (o->type == REDIS_HASH) {

        /* Save a hash value */
        if (o->encoding == REDIS_ENCODING_ZIPLIST) {
            size_t l = ziplistBlobLen((unsigned char*)o->ptr);

            // 以字符串对象的形式保存整个 ZIPLIST 哈希表
            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;

        } else if (o->encoding == REDIS_ENCODING_HT) {
            dictIterator *di = dictGetIterator(o->ptr);
            dictEntry *de;

            if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) return -1;
            nwritten += n;

            // 迭代字典
            while((de = dictNext(di)) != NULL) {
                robj *key = dictGetKey(de);
                robj *val = dictGetVal(de);

                // 键和值都以字符串对象的形式来保存
                if ((n = rdbSaveStringObject(rdb,key)) == -1) return -1;
                nwritten += n;
                if ((n = rdbSaveStringObject(rdb,val)) == -1) return -1;
                nwritten += n;
            }
            dictReleaseIterator(di);

        } else {
            redisPanic("Unknown hash encoding");
        }

    } else {
        redisPanic("Unknown object type");
    }

    return nwritten;
}
代码语言:javascript
复制
redis> SET msg "hello wrold"
OK

redis> OBJECT ENCODING msg
"embstr"

redis> SET story "long long long long long long ago ..."
OK

redis> OBJECT ENCODING story
"raw"

例子

http://redisbook.com/preview/object/set.html

set 编码的转换

当集合对象可以同时满足以下两个条件时, 对象使用 intset 编码:

  • 集合对象保存的所有元素都是整数值;
  • 集合对象保存的元素数量不超过 512 个;
  • 不能满足这两个条件的集合对象需要使用 hashtable
代码语言:javascript
复制
//存放的是整数类型,编码类是:intset
127.0.0.1:6379> SADD numbers 1 3 5
(integer) 3
127.0.0.1:6379> OBJECT ENCODING numbers
"intset"

//存放的是字符串类型,编码类是:hashtable,代表是无序 唯一。
127.0.0.1:6379> SADD numbers "seven"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING numbers
"hashtable"
//超过512整数,编码类是:hashtable
127.0.0.1:6379> EVAL "for i=1, 512 do redis.call('SADD', KEYS[1], i) end" 1 integers
(nil)
127.0.0.1:6379> OBJECT ENCODING integers
"intset"
127.0.0.1:6379> SADD integers 10086
(integer) 1
127.0.0.1:6379> OBJECT ENCODING integers
"hashtable"
127.0.0.1:6379>
http://redisbook.com/preview/object/set.html

有序集合对象

  1. 有序集合的编码可以是 ziplist 或者 skiplist 。

ziplist 编码的有序集合对象使用压缩列表作为底层实现

skiplist当有序集合对象可以同时满足以下两个条件时, 对象使用 ziplist 编码:

  • 有序集合保存的元素数量小于 128 个;
  • 有序集合保存的所有元素成员的长度都小于 64 字节;
  • 不能满足以上两个条件的有序集合对象将使用 skiplist 编码。
代码语言:javascript
复制
//有序集合保存的元素数量小于 128 个
127.0.0.1:6379> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
127.0.0.1:6379> object encoding price
"ziplist"
127.0.0.1:6379> type price
zset


# 对象包含了 128 个元素
redis> EVAL "for i=1, 128 do redis.call('ZADD', KEYS[1], i, i) end" 1 numbers
(nil)

redis> ZCARD numbers
(integer) 128

redis> OBJECT ENCODING numbers
"ziplist"

# 再添加一个新元素
redis> ZADD numbers 3.14 pi
(integer) 1

# 对象包含的元素数量变为 129 个
redis> ZCARD numbers
(integer) 129

# 编码已改变
redis> OBJECT ENCODING numbers
"skiplist"

# 向有序集合添加一个成员只有三字节长的元素
redis> ZADD blah 1.0 www
(integer) 1

redis> OBJECT ENCODING blah
"ziplist"

# 向有序集合添加一个成员为 66 字节长的元素
redis> ZADD blah 2.0 oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
(integer) 1

# 编码已改变
redis> OBJECT ENCODING blah
"skiplist"

总结

Redis 可以根据不同的使用场景来为一个对象设置不同的编码, 从而优化对象在某一场景下的效率。

举个例子, 在列表对象包含的元素比较少时, Redis 使用压缩列表作为列表对象的底层实现:

  • 因为压缩列表比双端链表更节约内存, 并且在元素数量较少时, 在内存中以连续块方式保存的压缩列表比起双端链表可以更快被载入到缓存中;
  • 随着列表对象包含的元素越来越多, 使用压缩列表来保存元素的优势逐渐消失时, 对象就会将底层实现从压缩列表转向功能更强、也更适合保存大量元素的双端链表上面;

内存回收

  • 引用计数(reference counting)
  • lru
代码语言:javascript
复制
typedef struct redisObject {
    // 类型
    unsigned type:4;
    // 编码
    unsigned encoding:4;
    // 指向数据的指针
    void *ptr;

    // 记录对象最后一次被程序访问时间,用于计算空转时长(当前时间-lru)
    unsigned lru:22; /* lru time (relative to server.lruclock) */
    // 引用计数,用于内存回收
    int refcount;
} robj;

如果redis配置了maxmemory和maxmemory-policy策略,则当redis内存数据达到maxmemory时,会根据maxmemory-policy配置来淘汰内存数据,以避免OOM。redis提供了以下6种淘汰策略:

  • noeviction:不执行任何淘汰策略,当达到内存限制的时候客户端执行命令会报错。
  • allkeys-lru:从所有数据范围内查找到最近最少使用的数据进行淘汰,直到有足够的内存来存放新数据。
  • volatile-lru:从所有的最近最少访问数据范围内查找设置到过期时间的数据进行淘汰,如果查找不到数据,则回退到noeviction。
  • allkeys-random:从所有数据范围内随机选择key进行删除。
  • volatile-random:从设置了过期时间的数据范围内随机选择key进行删除。
  • volatile-ttl:从设置了过期时间的数据范围内优先选择设置了TTL的key进行删除
代码语言:javascript
复制

Redis的每个slot槽里存储的key就是使用了Radix树。

现在我们就演绎下Trie树是如何浪费内存和空间的。比如下面的一组数据:

{ "deck": someValue, "did": someValue, "doe": someValue, "dog": someValue, "doge": someValue, "dogs": someValue }

我用Trie树的画法把上面的key value画出来如下:

Redis是如何实现Radix树的呢?

以下内容摘自http://mysql.taobao.org/monthly/2019/04/03/

raxNode是radix tree的核心数据结构,其结构体如下所示:

typedef struct raxNode {

uint32_t iskey:1; //是否包含key,

uint32_t isnull:1; //是否有存储value值,比如存储元数据就只有key,没有value值。value值也是存储在data中

uint32_t iscompr:1; //是否做了前缀压缩

uint32_t size:29; //该节点存储的字符个数

unsigned char data[]; //存储子节点的信息

} raxNode;

Radix和Trie树对于字符串的检索,特别是有公共前缀的场景。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。总之对于字符串的检索,Trie类树都比较适合,比如本文中的Redis的key这样的场景就非常适合。


到这里才刚刚开始

高级:新增数据结构也不能忘记

近水楼台一- GeoHash

面试题:用数据库来算附近的人(这是第二次被问了,还是通一个公司 忘记惨痛教训,后悔无用,必须在看看在看在看)

  • 地理位置Geo模块
  • https://www.cnblogs.com/wmyskxz/p/12466945.html
  • 经纬度怎么存储:

前言:针对“附近的人”这一位置服务领域的应用场景,常见的可使用PG、MySQL和MongoDB等多种DB的空间索引进行实现。

而Redis另辟蹊径,结合其有序队列zset以及geohash编码,实现了空间搜索功能,且拥有极高的运行效率。

https://zhuanlan.zhihu.com/p/90464063

https://blog.csdn.net/weixin_43246215/article/details/108041739

https://halfrost.com/go_spatial_search/

如何存储一个位置

app 界面上会显示出自己附近一个范围内可用的出租车或者共享单车。假设地图上会显示以自己为圆心,5公里为半径,这个范围内的车。如何实现呢?最直观的想法就是去数据库里面查表,计算并查询车距离用户小于等于5公里的,筛选出来,把数据返回给客户端。

这种做法比较笨,一般也不会这么做。为什么呢?因为这种做法需要对整个表里面的每一项都计算一次相对距离。太耗时了。既然数据量太大,我们就需要分而治之。那么就会想到把地图分块。这样即使每一块里面的每条数据都计算一次相对距离,也比之前全表都计算一次要快很多。

我们也都知道,现在用的比较多的数据库 MySQL、PostgreSQL 都原生支持 B+ 树。这种数据结构能高效的查询。地图分块的过程其实就是一种添加索引的过程,如果能想到一个办法,把地图上的点添加一个合适的索引,并且能够排序,那么就可以利用类似二分查找的方法进行快速查询。

问题就来了,地图上的点是二维的,有经度和纬度,这如何索引呢?如果只针对其中的一个维度,经度或者纬度进行搜索,那搜出来一遍以后还要进行二次搜索。那要是更高维度呢?三维。可能有人会说可以设置维度的优先级,比如拼接一个联合键,那在三维空间中,x,y,z 谁的优先级高呢?设置优先级好像并不是很合理。

  • 关系数据库(元素 id,经度 x,纬度 y)
  • GeoHash算法将二维的经纬度数据映射到一维的整数
  • GeoHash算法会继续对这个整数做一次 base32编码,变成一个字符串
  • 在 Redis 里面,经纬度使用 52 位的整数进行编码, 放进了 zset 里面, zset 的 value 是元素的 key, score 是 GeoHash 的 52 位整数值

首先在每个geohash网格中的geohash值都是连续的,有固定范围。

所以只要找出有序集合中,处在该范围的位置对象即可。

以下是有序集合的跳表数据结构

其拥有类似二叉查找树的查询效率,操作平均时间复杂性为O(log(N))。且最底层的所有元素都以链表的形式按序排列。

所以在查询时,只要找到集合中处在目标geohash网格中的第一个值,后续依次对比即可,不用多次查找。

基础知识不牢固呀:

查找--排序---索引(tree ,hash,quicklist)

同类问题:

我不知道mysql 查询那个记录,我怎么做加锁查询。索引是全部记录做排序

我不知道用户查询那个位置,我怎么对全部位置进行排序!地理位置本身有序的,我怎么存储起来呀。每次都都计算,性能不一定高效

查找 -有序数组 --平衡二叉树-- 跳跃表(考虑元素采用什么结构存储很重要)

代码语言:javascript
复制
//01 geoadd key 经度 纬度 地理位置
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing 121.47 32.23 shanghai
(integer) 2
//02 geopos命令:从key里返回所有给定位置元素的位置(经度和纬度) 
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
  
//03 georadius命令:以给定的经纬度为中心,找出某一半径内的元素 
127.0.0.1:6379> georadius china:city 116 39  1000 km
1) "shanghai"
2) "beijing"

//04 geohash命令:返回一个或多个位置元素的Geohash字符串
127.0.0.1:6379> geohash china:city beijing
1) "wx4fbxxfke0"
Geohash字符串查询
http://geohash.org/wx4fbxxfke0


127.0.0.1:6379> debug object china:city
Value at:0x7f2ad2c285c0 refcount:1
 encoding:ziplist 
serializedlength:51 
lru:4668404 
lru_seconds_idle:5768

Redis另辟蹊径,结合其有序队列zset以及geohash编码, 实现了空间搜索功能,且拥有极高的运行效率。

代码语言:javascript
复制
/* GEOADD key long lat name [long2 lat2 name2 ... longN latN nameN] */
void geoaddCommand(client *c) {
  

    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;
        }

        /* Turn the coordinates into the score of the element. */
        GeoHashBits hash;
        geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash);
        GeoHashFix52Bits bits = geohashAlign52Bits(hash);
        //score设置
        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);
    }

    /* Finally call ZADD that will do the work for us. */
    replaceClientCommandVector(c,argc,argv);
    zaddCommand)//zset存储
}

部署说明

在一个地图应用中,车的数据、餐馆的数据、人的数据可能会有百万千万条,如果使用 RedisGeo 数据结构,它们将 全部放在一个 zset 集合中。

Redis 的集群环境中,集合可能会从一个节点迁移到另一个节点,如果单个 key 的数据过大,会对集群的迁移工作造成较大的影响【一个key 只能在一个slot,这里说对大key 数据进行拆分 才会从一个节点到另外一个节点。平时一个key就在一个节点,不会移动的。】

在集群环境中单个 key 对应的数据量不宜超过 1M,否则会导致集群迁移出现卡顿现象,影响线上服务的正常运行。

所以,这里建议 Geo 的数据使用 单独的 Redis 实例部署,不使用集群环境。

如果数据量过亿甚至更大,就需要对 Geo 数据进行拆分,按国家拆分、按省拆分,按市拆分,在人口特大城市甚至可以按区拆分。这样就可以显著降低单个 zset 集合的大小。

节衣缩食一一位图

千呼万唤-流(streams)

https://zhuanlan.zhihu.com/p/40854446

节衣缩食一一前缀tree

耗时:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Offer多多 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题:万丈高楼平地起一 Redis 基础数据结 惨痛的教训
    • 小白回答
      • 后记:换个马甲就不认识了
        • 初级回答
        • 5个对象类型,8个对象编码方式
        • 例子
          • set 编码的转换
            • 有序集合对象
            • 总结
            • 内存回收
            • 高级:新增数据结构也不能忘记
              • 近水楼台一- GeoHash
                • 节衣缩食一一位图
                  • 千呼万唤-流(streams)
                    • 节衣缩食一一前缀tree
                    相关产品与服务
                    云数据库 Redis
                    腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档