• Redis-五种数据类型


    Redis基本特性

    a) 非关系型的键值对数据库,可以根据键以O(1) 的时间复杂度取出或插入关联值

    b) Redis 的数据是存在内存中的

    c) 键值对中键的类型可以是字符串,整型,浮点型等,且键是唯一的

    d) 键值对中的值类型可以是string,hash,list,set,sorted set 等

    e) Redis 内置了复制,磁盘持久化,LUA脚本,事务,SSL, ACLs,客户端缓存,客户端代理等功能(6.0新特性)

    f) 通过Redis 哨兵和Redis Cluster 模式提供高可用性

    Redis应用场景

    a) 计数器 可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。

    b) 分布式ID生成 利用自增特性,一次请求一个大一点的步长如 incr 2000 ,缓存在本地使用,用完再请求。

    c) 海量数据统计 位图(bitmap):存储是否参过某次活动,是否已读谋篇文章,用户是否为会员, 日活统计。

    d) 会话缓存 可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。

    e) 分布式队列/阻塞队列 List 是一个双向链表,可以通过 lpush/rpushrpop/lpop 写入和读取消息。可以通过使用brpop/blpop 来实现阻塞队列。

    f) 分布式锁实现 在分布式场景下,无法使用基于进程的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁。

    g) 热点数据存储 最新评论,最新文章列表,使用list 存储,ltrim取出热点数据,删除老数据。

    h) 社交类需求 Set 可以实现交集,从而实现共同好友等功能,Set通过求差集,可以进行好友推荐,文章推荐。

    i) 排行榜 sorted_set可以实现有序性操作,从而实现排行榜等功能。

    j) 延迟队列 使用sorted_set,使用 【当前时间戳 + 需要延迟的时长】做score, 消息内容作为元素,调用zadd来生产消息,消费者使用zrangbyscore获取当前时间之前的数据做轮询处理。消费完再删除任务 rem key member

    Redis 的key 都是字符串(SDS, simple dynamic string)类型,命令由客户端发送给服务端都会转换为字节流的形式,虽然看起来可能是数字、浮点数、或者字符串多种类型

    1. > set 1 a // 数字key
    2. > get 1
    3. > set 0.5 b // 浮点key
    4. > get 0.5
    5. > set a c // 字符串key
    6. > get a
    7. 以上key 发送到服务端最终都是以字节流的形式

    SDS 的特点:

    a)二进制安全的数据结构,安全体现在不会丢数据(c:char data[]="g\0ao",C 语言以"\0" 作为字符串结尾标记,业务数据存在"\0"时存在安全问题)

    b)内存预分配机制,防止频繁扩容产生的内存分配问题

    c)兼容C语言的函数库

    扩容示例:

    1. redis:
    2. sds:
    3. free: 0
    4. len: 3
    5. char buf[]="gao" -> "gao123"
    6. len: 3
    7. addlen: 3
    8. (len + addlen) * 2 = 12 //扩容,(现有长度+需要增加长度)*2
    9. // 成倍扩容,当长度len=1024,再扩容每次增加1M,(所以使用 setbit 设置大点的空间,防止频繁扩容)
    10. append,setbit
    11. sds:
    12. free: 6
    13. len: 6
    14. char buf[]="gao123"

    设计思想:

    1. K-V:map -> dict,
    2. 数据库:海量数据的存储
    3. 1、数组:O(1) 随机访问,下表
    4. 2、链表:O(n) 头结点,遍历
    5. 3、数:log(n) 优化的比较好的场景,二分查找
    6. hash(key) 任意数据进行随机散列,并且把hash 值转换为一个自然数[大]
    7. 创建一个大的数组:arr[4]
    8. hash(key) -> 自然数[大] % 4 = [0, arr.size-1], redis 就是这么干的
    9. 1、任意相同的输入,一定能得到相同的输出
    10. 2、不同的输入,可能得到相同的输出
    11. 把现实中无限的数据放到有限的集合中,肯定会产生hash 冲突
    12. (k1,v1),(k2,v2),(k3,v3)
    13. hash(k1) % 4 = 0
    14. hash(k2) % 4 = 1
    15. arr[0]->(k1,v1,next->null)
    16. arr[1]->(k3,v3,next->k2)(k2,v2,next->null)
    17. 产生hash 冲突时,redis 使用的是头插法
    18. redis 是比较基础的语言,没有那么多高级特性,没有那么多工具可以用,是redis 作者自己实现的
    19. 通过链表法来解决碰撞,java 中的hashMap 要复杂得多
    20. key:string
    21. value:string,hash,list,set,sorted set

    Redis之String 类型数据结构直通车

    Redis6.0 多线程,但是最终执行用户请求是在单线程中进行的,渐进式扩容很有必要

    有趣命令:

    1. > type [key] //查看key的类型
    2. string
    3. > object encoding 100 //key 所对应的值,在redis 底层是一个什么样的编码格式
    4. "int"
    5. > object encoding str
    6. "embstr"
    7. > object encoding 0.1
    8. "int"
    9. int / embstr 都是redis 对内存方面的优化

    Redis求模小优化:

    1. 任意数 % 2^n //对CPU 不友好,累除法
    2. 任意数 & (2^n - 1) //一次位运算

    Redis 对于值是string 类型的底层编码结构分析:

    1. //type [key],查看key 的类型
    2. //object encoding [key],查值在底层的编码格式
    3. //redis 中所有对象的封装
    4. typedef struct redisObject {    
    5. unsigned type:4; //约束客户端命令,当前类型。位域的语法,占4个bit 位
    6. unsigned encoding:4; //编码格式,同样占4个bit 位
    7. unsigned lru:LRU_BITS; //24个bit 位,也就是3 个字节
    8. int refcount; //引用计数法,4个字节
    9. void *ptr; //数据真正存放的位置,8个字节
    10. } robj;
    11. redisObject 占16个字节的空间
    12. cache line:64字节,64 - 16 = 48 字节,肯定可以利用起来
    13. 48个字节使用sdshdr8的数据结构存储,因为48在数据范围 2^5 ~ 2^8-1 之间
    14. struct __attribute__ ((__packed__)) sdshdr8 {  // 表示 2^5 ~ 2^8-1 的数据    
    15. uint8_t len; /* used */    
    16. uint8_t alloc; /* excluding the header and null terminator */    
    17. unsigned char flags; /* 3 lsb of type, 5 unused bits */    
    18. char buf[];
    19. };
    20. sds本身消耗4个字节,其中len、alloc、flags各占一个字节,另外'\0'占一个字节,因为兼容C语言的函数库
    21. 最终:48 - 4 = 44 字节,用来存放实际的数据

    对于string 类型的value 有结论如下:

    1. value 值是string 类型的底层编码结构:
    2. 1、value值的长度小于等于20,尝试转换成数字转换成功,底层使用int 编码格式
    3. 2、value值不满足条件1,且长度小于等于44redis 底层使用embstr 编码结构
    4. 3、value值不满足1和2条件,使用row 编码结构也就是sds 的数据结构,简单动态字符串

    扩展分析:

    1. 亿级日活的统计:
    2. bit 的特点,除了0 就是1,所以可以用1个bit 位表示登陆状态
    3. 像这样1个字节就可以表示8个用户
    4. 1 1 0 1 0 1 0 1 1 1 0 1 0 0 1 0 1 -- 登陆状态
    5. offset:0 1 2 3 4 5 6 7 8 9 ...
    6. 命令:setbit key offset 0|1
    7. 日期可以作为一个key 使用,“2022-5-26”
    8. 变种:userId 是数字时,可以作为偏移量使用
    9. setbit a-bit-map userId 1
    10. setbit a-bit-map userId 1
    11. getbit a-bit-map 8
    12. userId:
    13. bit: 0/1 (默认值是0)

    简单测试:

    1. > setbit login_05_26 100 1
    2. > setbit login_05_26 100 1
    3. > setbit login_05_26 100 1
    4. > getbit login_05_26 100 //获取日期5月26日用户100的活跃状态,id特别大可以减固定值优化
    5. > setbit login_05_26 100 0 //恢复状态
    6. > getbit login_05_26 100
    7. > STRLEN login_05_26 //长度13 字节。100/8bit = 12.5 所以分配13个字节
    8. > type login_05_26 //string 类型的数据
    9. > get login_05_26 //可以执行命令,获取的数据是内存中的存储,没有实际意义
    10. > setbit login_05_26 100 1
    11. > setbit login_05_26 101 1
    12. > setbit login_05_26 102 1
    13. > setbit login_05_26 103 1
    14. > BITCOUNT login_05_26 //统计bit位是1的个数:4
    15. > strlen login_05_26 //13个字节
    16. > BITCOUNT login_05_26 0 12 //一共13个字节,从索引0开始,所以是0和12。可以统计一部分数据根据索引
    17. string 表示数据最大是512M,索引位是2^32-1。最多可表示这么多的用户

    另一个扩展分析:

    1. login_05_26: 0 1 0 1 0 1 1 1 0 1 0 1 1 0 0 1 1
    2. login_05_27: 1 1 0 1 0 1 0 1 1 1 0 1 0 0 1 0 1
    3. 连续登陆情况:按位与再统计
    4. > BITOP and login_05_26-27 login_05_26 login_05_27 //按位与操作,结果存到login_05_26-27中
    5. > BITCOUNT login_05_26-27 //再做一次bitcount可以得到连续登陆的结果
    6. login_05_24: 0 1 0 1 0 1 1 1 0 1 0 1 1 0 0 1 1
    7. login_05_25: 1 1 0 1 0 1 0 1 1 1 0 1 0 0 1 0 1
    8. login_05_26: 0 1 0 1 0 1 1 1 0 1 0 1 1 0 0 1 1
    9. login_05_27: 1 1 0 1 0 1 0 1 1 1 0 1 0 0 1 0 1
    10. login_05_28: 0 1 0 1 0 1 1 1 0 1 0 1 1 0 0 1 1
    11. login_05_29: 1 1 0 1 0 1 0 1 1 1 0 1 0 0 1 0 1
    12. login_05_30: 0 1 0 1 0 1 1 1 0 1 0 1 1 0 0 1 1
    13. 周活:按位或,有一天登陆就行
    14. > BITOP or login_05_26-27-active login_05_26 login_05_27 //两天内有一天登陆就行
    15. > BITCOUNT login_05_26-27-active
    16. redis 源码有判断只能是0或1,按位与 按位或,效率非常高
    17. 汉明重量 按位操作 有兴趣的同学可以了解

    List常用API

    1. /> help @list
    2. LPUSH key element [element ...]
    3. RPOP key
    4. RPUSH key element [element ...]
    5. LPOP key
    6. BLPOP key [key ...] timeout
    7. BRPOP key [key ...] timeout
    8. BRPOPLPUSH source destination timeout
    9. RPOPLPUSH source destination
    10. LINDEX key index
    11. LLEN key
    12. LINSERT key BEFORE|AFTER pivot element
    13. LRANGE key start stop
    14. LREM key count element
    15. LSET key index element
    16. LTRIM key start stop

    List是一个有序(按加入的时序排序)的数据结构,Redis采用quicklist(双端链表) 和 ziplist 作为List的底层实现。

    可以通过设置每个ziplist的最大容量,quicklist的数据压缩范围,提升数据存取效率

    1. list-max-ziplist-size -2 // 单个ziplist节点最大能存储 8kb ,超过则进行分裂,将数据存储在新的ziplist节点中
    2. list-compress-depth 1 // 0 代表所有节点,都不进行压缩,1, 代表从头节点往后走一个,尾节点往前走一个不用压缩,其他的全部压缩,2,3,4 ... 以此类推

    Redis - quicklist 数据结构:

    Redis - ziplist 数据结构:

    Hash常用API

    1. /> help @hash
    2. HSET key field value [field value ...]
    3. HGET key field
    4. HMGET key field [field ...]
    5. HKEYS key
    6. HGETALL key
    7. HVALS key
    8. HEXISTS key field
    9. HDEL key field [field ...]
    10. HINCRBY key field increment
    11. HINCRBYFLOAT key field increment
    12. HLEN key
    13. HSCAN key cursor [MATCH pattern] [COUNT count]
    14. HSETNX key field value
    15. HSTRLEN key field

    Hash 数据结构底层实现为一个字典( dict ),也是RedisBb用来存储K-V的数据结构,当数据量比较小,或者单个元素比较小时,底层用ziplist存储,数据大小和元素数量阈值可以通过如下参数设置。

    1. hash-max-ziplist-entries 512 // ziplist 元素个数超过 512 ,将改为hashtable编码
    2. hash-max-ziplist-value 64 // 单个元素大小超过 64 byte时,将改为hashtable编码

    Redis - hash 数据结构:

    Set常用API

    1. /> help @set
    2. SADD key member [member ...]
    3. SCARD key
    4. SISMEMBER key member
    5. SPOP key [count]
    6. SDIFF key [key ...]
    7. SINTER key [key ...]
    8. SUNION key [key ...]
    9. SMEMBERS key
    10. SRANDMEMBER key [count]
    11. SREM key member [member ...]
    12. SMOVE source destination member
    13. SUNIONSTORE destination key [key ...]
    14. SDIFFSTORE destination key [key ...]
    15. SINTERSTORE destination key [key ...]
    16. SSCAN key cursor [MATCH pattern] [COUNT count]

    Set 为无序的,自动去重的集合数据类型,Set 数据结构底层实现为一个value 为 null 的 字典( dict ),当数据可以用整形表示时,Set集合将被编码为intset数据结构。两个条件任意满足时 Set将用hashtable存储数据。1, 元素个数大于 set-max-intset-entries , 2 , 元素无法用整形表示

    set-max-intset-entries 512       // intset 能存储的最大元素个数,超过则用hashtable编码

    Redis - set 数据结构:

    ZSet常用API

    1. /> help @sorted_set
    2. ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
    3. ZCARD key
    4. ZCOUNT key min max
    5. ZINCRBY key increment member
    6. ZRANGE key start stop [WITHSCORES]
    7. ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
    8. ZRANK key member
    9. ZREM key member [member ...]
    10. ZREMRANGEBYRANK key start stop
    11. ZREMRANGEBYSCORE key min max
    12. ZREVRANGE key start stop [WITHSCORES]
    13. ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
    14. ZREVRANK key member
    15. ZSCAN key cursor [MATCH pattern] [COUNT count]
    16. ZSCORE key member

    ZSet 为有序的,自动去重的集合数据类型,ZSet 数据结构底层实现为 字典(dict) + 跳表(skiplist) ,当数据比较少时,用ziplist编码结构存储。

    1. zset-max-ziplist-entries 128 // 元素个数超过128 ,将用skiplist编码
    2. zset-max-ziplist-value 64 // 单个元素大小超过 64 byte, 将用 skiplist编码

    Redis - zset 数据结构:

    Redis - skiplist 数据结构:

  • 相关阅读:
    【华为机试真题 Python实现】比较两个版本号的大小
    【面试普通人VS高手系列】请说一下你对分布式锁的理解,以及分布式锁的实现
    day7【代码随想录】移除链表元素
    【nginx】Nginx配置:
    VScode配置Ros环境
    链表面试题-刷题
    RCNN 目标检测网络学习笔记 (附代码)
    【红队】要想加入红队,需要做好哪些准备?
    9.WPF资源
    java并发编程:synchronized同步方法
  • 原文地址:https://blog.csdn.net/weixin_58482311/article/details/134541594