华为架构师整理Redis数据结构的大厂最佳实践( 四 )


直接基于set将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于JVM内存里的HashSet进行去重,但若你的某个系统部署在多台机器呢?就需要基于redis进行全局的set去重 。
可基于set玩交集、并集、差集操作,比如交集:

  • 把两个人的粉丝列表整一个交集,看看俩人的共同好友
  • 把两个大v的粉丝都放在两个set中,对两个set做交集
全局这种计算开销也大 。
  • 记录唯一的事物
    比如想知道访问某个博客的IP地址,不要重复的IP,这种情况只需要在每次处理一个请求时简单的使用SADD命令就可以了,可确保不会插入重复IP
  • 表示关系
    你可以使用Redis创建一个标签系统,每个标签使用一个Set表示 。然后你可以使用SADD命令把具有特定标签的所有对象的所有ID放在表示这个标签的Set中如果你想要知道同时拥有三个不同标签的对象,那么使用SINTER
  • 可使用SPOP 或 SRANDMEMBER 命令从集合中随机的提取元素 。
Hash/Map一般可将结构化的数据,比如一个对象(前提是这个对象未嵌套其他的对象)给缓存在redis里,然后每次读写缓存的时候,即可直接操作hash里的某个字段 。
key=150value=https://www.isolves.com/it/sjk/Redis/2021-11-01/{“id”: 150,“name”: “zhangsan”,“age”: 20}hash类的数据结构,主要存放一些对象,把一些简单的对象给缓存起来,后续操作的时候,可直接仅修改该对象中的某字段的值 。
value=https://www.isolves.com/it/sjk/Redis/2021-11-01/{“id”: 150,“name”: “zhangsan”,“age”: 21}因为Redis本身是一个K.V存储结构,Hash结构可理解为subkey - subvalue
这里面的subkey - subvalue只能是
  • 整型
  • 浮点型
  • 字符串
因为Map的 value 可表示整型和浮点型,因此Map也可以使用hincrby 对某个field的value值做自增操作 。
内存数据结构hash有HashTable 和 ziplist 两种实现 。对于数据量较小的hash,使用ziplist 实现 。
HashTable 实现HashTable在Redis 中分为3 层,自底向上分别是:
  • dictEntry:管理一个field - value 对,保留同一桶中相邻元素的指针,以此维护Hash 桶中的内部链
  • dictht:维护Hash表的所有桶链
  • dict:当dictht需要扩容/缩容时,用户管理dictht的迁移
dict是Hash表存储的顶层结构
// 哈希表(字典)数据结构,Redis 的所有键值对都会存储在这里 。其中包含两个哈希表 。typedef struct dict {// 哈希表的类型,包括哈希函数,比较函数,键值的内存释放函数dictType *type;// 存储一些额外的数据void *privdata;// 两个哈希表dictht ht[2];// 哈希表重置下标,指定的是哈希数组的数组下标int rehashidx; /* rehashing not in progress if rehashidx == -1 */// 绑定到哈希表的迭代器个数int iterators; /* number of iterators currently running */} dict;Hash表的核心结构是dictht,它的table 字段维护着 Hash 桶,桶(bucket)是一个数组,数组的元素指向桶中的第一个元素(dictEntry) 。
typedef struct dictht {//槽位数组dictEntry **table;//槽位数组长度unsigned long size;//用于计算索引的掩码unsigned long sizemask;//真正存储的键值对数量unsigned long used; } dictht;结构图
华为架构师整理Redis数据结构的大厂最佳实践

文章插图
 
Hash表使用【链地址法】解决Hash冲突 。当一个 bucket 中的 Entry 很多时,Hash表的插入性能会下降,此时就需要增加bucket的个数来减少Hash冲突 。
Hash表扩容和大多数Hash表实现一样,Redis引入负载因子判定是否需要增加bucket个数:
负载因子 = Hash表中已有元素 / bucket数量扩容后,bucket 数量是原先2倍 。目前有2 个阀值:
  • 小于1 时一定不扩容
  • 大于5 时一定扩容
  • 在1 ~ 5 之间时,Redis 如果没有进行bgsave/bdrewrite 操作时则会扩容
  • 当key - value 对减少时,低于0.1时会进行缩容 。缩容之后,bucket的个数是原先的0.5倍
ziplist 实现这里的 ziplist 和List#ziplist的实现类似,都是通过Entry 存放元素 。
不同的是,Map#ziplist的Entry个数总是2的整数倍: