Redis解读(4):Redis中HyperLongLog、布隆过滤器、限流、Geo、及Scan等进阶应用 (5)

以北京为中心,方圆 200km 以内的城市找出来 3 个,按照远近顺序排列,这个命令不会排除 北京。
当然,也可以根据经纬度来查询(将 member 换成对应的经纬度):

127.0.0.1:6379> GEORADIUS city 116.3980007200 39.9053908600 2000 km withdist withhash withcoord count 4 desc Redis Scan 1.简单介绍

scan 实际上是 keys 的一个升级版。

可以用 keys 来查询 key,在查询的过程中,可以使用通配符。keys 虽然用着还算方便,但是没有分页功能。同时因为 Redis 是单线程,所以 key 的执行会比较消耗时间,特别是当数据量大的时候,影响整个程序的运行。

为了解决 keys 存在的问题,从 Redis2.8 中开始,引入了 scan。

scan 具备 keys 的功能,但是不会阻塞线程,而且可以控制每次返回的结果数。

2.基本用法

首先准备 10000 条测试数据:

package org.taoguoguo.scan; import org.taoguoguo.redis.Redis; /** * @author taoguoguo * @description ScanTest * @website https://www.cnblogs.com/doondo * @create 2021-04-25 14:27 */ public class ScanTest { public static void main(String[] args) { Redis redis = new Redis(); redis.execute(jedis -> { for (int i = 0; i < 10000; i++) { jedis.set("k" + i, "v" + i); } }); } }

scan 命令一共提供了三个参数,第一个 cursor,第二个参数是 key,第三个参数是 limit。

cursor 实际上是指一维数组的位置索引,limit 则是遍历的一维数组个数(所以每次返回的数据大小可能不确定)。

scan 0 match k8* count 1000 3.遍历原理及渐进式 rehash机制

SCAN的遍历顺序

假设目前有三条数据:

127.0.0.1:6379> keys * 1) "key1" 2) "db_number" 3) "myKey" 127.0.0.1:6379> scan 0 match * count 1 1) "2" 2) 1) "key1" 127.0.0.1:6379> scan 2 match * count 1 1) "1" 2) 1) "myKey" 127.0.0.1:6379> scan 1 match * count 1 1) "3" 2) 1) "db_number" 127.0.0.1:6379> scan 3 match * count 1 1) "0" 2) (empty list or set) 127.0.0.1:6379>

在遍历的过程中,大家发现游标的顺序是 0 2 1 3,从十进制来看好像没有规律,但是从转为二进制,则是有规律的:

00->10->01->11

这种规律就是高位进1,传统的二进制加法,是从右往左加,这里是从左往右加。

实际上,在 Redis 中,它的具体计算流程给是这样:

将要计算的数字反转

给反转后的数字加 1

再反转

那么为什么不是按照 0、1、2、3、4...这样的顺序遍历呢?因为主要考虑到两个问题:

字典扩容

字典缩容

Redis解读(4):Redis中HyperLongLog、布隆过滤器、限流、Geo、及Scan等进阶应用

根据Scan遍历原理假如我们将要访问 110 时,发生了扩容,此时 scan 就会从 0110 开始遍历,之前已经被遍历过的元素就不会被重复遍历了

假如我们将要访问 110 时,发生缩容,此时 scan 就会从 10 开始遍历,这个时候,也会遍历到 010,但是 010 之前的不会再被遍历了。所以,在发生缩容的时候,可能返回重复的元素。

Redis一共支持5种数据结构,hash是其中的一种,在hash扩容的时候采用的是渐进式rehash的方式。要想深入理解渐进式rehash,首先要了解以下Redis中hash的数据结构。 哈希表节点 typedef struct...

哈希表节点 typedef struct dictEntry { void *key; // 键 union { void *val; uint64_t u64; int64_t s64; double d; } v; // 值 struct dictEntry *next; // 下一个节点 } dictEntry; 哈希表 /* This is our hash table structure. Every dictionary has two of this as we * implement incremental rehashing, for the old to the new table. */ typedef struct dictht { dictEntry **table; // 哈希表数组 unsigned long size; // 哈希表大小 unsigned long sizemask; // 掩码,计算索引值,size-1 unsigned long used; // 哈希表已有节点的数量 } dictht; 字典 typedef struct dict { dictType *type; // 类型特定函数 void *privdata; // 私有数据 dictht ht[2]; // 哈希表 // rehash索引 long rehashidx; /* rehashing not in progress if rehashidx == -1 */ unsigned long iterators; /* number of iterators currently running */ } dict; 特定函数 typedef struct dictType { // 计算哈希值的函数 uint64_t (*hashFunction)(const void *key); // 复制键的函数 void *(*keyDup)(void *privdata, const void *key); // 复制值的函数 void *(*valDup)(void *privdata, const void *obj); // 对比键的函数 int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 销毁键的函数 void (*keyDestructor)(void *privdata, void *key); // 销毁值的函数 void (*valDestructor)(void *privdata, void *obj); } dictType;

字典中包含一个数据结构dictht的ht数组,一般情况下字典只是用ht[0]用来存储数据,ht[1]在rehash时使用。

哈希算法原理

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzdxwg.html