zoukankan      html  css  js  c++  java
  • Redis基础篇

    Redis 基本数据类型

      最基本也是最常用的数据类型就是 String。set 和 get 命令就是 String 的操作命令。为什么叫 Binary-safe strings 呢?

    String 字符串

    • 存储类型
      可以用来存储字符串、整数、浮点数。
    • 操作命令
      设置多个值(批量操作,原子性)
    mset qingshan 2673 jack 666

      设置值,如果 key 存在,则不成功

    setnx qingshan
      基于此可实现分布式锁。用 del key 释放锁。但如果释放锁的操作失败了,导致其他节点永远获取不到锁,怎么办?
      加过期时间。
    set key value [expiration EX seconds|PX milliseconds][NX|XX]
    set lock1 1 EX 10 NX
      (整数)值递增/(整数)值递减
    incr qingshan 
    incrby qingshan 100
    decr qingshan 
    decrby qingshan 100
      浮点数增量
    set f 2.6 
    incrbyfloat f 7.3
      获取多个值
    mget qingshan jack
      字符串追加内容
    append qingshan good

      获取指定范围的字符

    getrange qingshan 0 8
    • 存储(实现)原理
    数据模型
      set hello word 为例,因为 Redis 是 KV 的数据库,它是通过 hashtable 实现的(我们把这个叫做外层的哈希)。所以每个键值对都会有一个 dictEntry(源码位置:dict.h),里面指向了 key 和 value 的指针。next 指向下一个 dictEntry。
    typedef struct dictEntry { 
    void *key; /* key 关键字定义 */
    union {
    void *val;
    uint64_t u64; /* value 定义 */
    int64_t s64; double d; } v;
    struct dictEntry *next; /* 指向下一个键值对节点 */ }
    dictEntry;

      key 是字符串,但是 Redis 没有直接使用 C 的字符数组,而是存储在自定义的 SDS中。value 既不是直接作为字符串存储,也不是直接存储在 SDS 中,而是存储在redisObject 中。实际上五种常用的数据类型的任何一种,都是通过 redisObject 来存储
    的。
    redisObject
      redisObject 定义在 src/server.h 文件中。

     

    可以使用 type 命令来查看对外的类型。:
    127.0.0.1:6379> type qs
    string
     
    内部编码

    127.0.0.1:6379> object encoding tom
    "int"

    字符串类型的内部编码有三种:
    1、int,存储 8 个字节的长整型(long,2^63-1)。
    2、embstr, 代表 embstr 格式的 SDS(Simple Dynamic String 简单动态字符串),存储小于 44 个字节的字符串。
    3、raw,存储大于 44 个字节的字符串(3.2 版本之前是 39 字节)。
     
    问题 1、什么是 SDS?
      Redis 中字符串的实现。
      在 3.2 以后的版本中,SDS 又有多种结构(sds.h):sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64,用于存储不同的长度的字符串,分别代表 2^5=32byte,2^8=256byte,2^16=65536byte=64KB,2^32byte=4GB。
    问题 2、为什么 Redis 要用 SDS 实现字符串?
    我们知道,C 语言本身没有字符串类型(只能用字符数组 char[]实现)。
    1、使用字符数组必须先给目标变量分配足够的空间,否则可能会溢出。
    2、如果要获取字符长度,必须遍历字符数组,时间复杂度是 O(n)。
    3、C 字符串长度的变更会对字符数组做内存重分配。
    4、通过从字符串开始到结尾碰到的第一个''来标记字符串的结束,因此不能保存图片、音频、视频、压缩文件等二进制(bytes)保存的内容,二进制不安全。 
     
    SDS 的特点:
    1、不用担心内存溢出问题,如果需要会对 SDS 进行扩容。
    2、获取字符串长度时间复杂度为 O(1),因为定义了 len 属性。
    3、通过“空间预分配”( sdsMakeRoomFor)和“惰性空间释放”,防止多
    次重分配内存。
    4、判断是否结束的标志是 len 属性(它同样以''结尾是因为这样就可以使用 C语言中函数库操作字符串的函数了),可以包含''。
    • 应用场景
    缓存
    String 类型
    例如:热点数据缓存(例如报表,明星出轨),对象缓存,全页缓存。可以提升热点数据的访问速度。
     
    数据共享分布式
    STRING 类型,因为 Redis 是分布式的独立服务,可以在多个应用之间共享
    例如:分布式 Session 
     
    分布式锁
    STRING 类型 setnx 方法,只有不存在时才能添加成功,返回 true。http://redisdoc.com/string/set.html 建议用参数的形式
     
    全局 ID
    INT 类型,INCRBY,利用原子性
     
    计数器
    INT 类型,INCR 方法
    例如:文章的阅读量,微博点赞数,允许一定的延迟,先写入 Redis 再定时同步到数据库。
     
    限流
    INT 类型,INCR 方法
    以访问者的 IP 和其他信息作为 key,访问一次增加一次计数,超过次数则返回 false。
     
    如果一个对象的 value 有多个值的时候,怎么存储?
    例如用一个 key 存储一张表的数据。
    序列化?例如 JSON/Protobuf/XML,会增加序列化和反序列化的开销,并且不能单独获取、修改一个值。 
    可以通过 key 分层的方式来实现,例如:
    mset student:1:sno GP16666 student:1:sname 沐风 student:1:company 腾讯
    获取值的时候一次获取多个值:
    mget student:1:sno student:1:sname student:1:company
    缺点:key 太长,占用的空间太多。有没有更好的方式?

    Hash 哈希

    •  存储类型

      包含键值对的无序散列表。value 只能是字符串,不能嵌套其他类型。

    同样是存储字符串,Hash 与 String 的主要区别?
    1、把所有相关的值聚集到一个 key 中,节省内存空间
    2、只使用一个 key,减少 key 冲突
    3、当需要批量获取值的时候,只需要使用一个命令,减少内存/IO/CPU 的消耗
     
    Hash 不适合的场景:
    1、Field 不能单独设置过期时间
    2、没有 bit 操作
    3、需要考虑数据量分布的问题(value 值非常大的时候,无法分布到多个节点)
    • 操作命令
    hset h1 f 6 
    hset h1 e 5
    hmset h1 a 1 b 2 c 3 d 4
      hget h1 a
      hmget h1 a b c d
      hkeys h1
      hvals h1
      hgetall h1
     
      hget exists h1
      hdel h1
      hlen h1
    • 存储(实现)原理 

      Redis 的 Hash 本身也是一个 KV 的结构,类似于 Java 中的 HashMap。外层的哈希(Redis KV 的实现)只用到了 hashtable。当存储 hash 数据类型时,我们把它叫做内层的哈希。内层的哈希底层可以使用两种数据结构实现:

    ziplist:OBJ_ENCODING_ZIPLIST(压缩列表)

    hashtable:OBJ_ENCODING_HT(哈希表) 

    127.0.0.1:6379> hset h2 f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 
    (integer) 1
    127.0.0.1:6379> hset h3 f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    (integer) 1
    127.0.0.1:6379> object encoding h2
    "ziplist"
    127.0.0.1:6379> object encoding h3
    "hashtable"
      ziplist 压缩列表
      ziplist 是一个经过特殊编码的双向链表,它不存储指向上一个链表节点和指向下一个链表节点的指针,而是存储上一个节点长度和当前节点长度,通过牺牲部分读写性能,来换取高效的内存空间利用率,是一种时间换空间的思想。只用在字段个数少,字段值
    小的场景里面。
    • 应用场景
      存储对象类型的数据
      比如对象或者一张表的数据,比 String 节省了更多 key 的空间,也更加便于集中管理。 
      购物车
    key:用户 id;field:商品 id;value:商品数量。
    +1:hincr。-1:hdecr。删除:hdel。全选:hgetall。商品数:hlen。

    List 列表

    • 存储类型
      存储有序的字符串(从左到右),元素可以重复。可以充当队列和栈的角色。
    • 操作命令

      元素增减:

    lpush queue a 
    lpush queue b c
      rpush queue d e
      lpop queue
      rpop queue
      blpop queue
      brpop queue 
      取值
    lindex queue 0
    lrange queue 0 -1

    • 存储(实现)原理
      在早期的版本中,数据量较小时用 ziplist 存储,达到临界值时转换为 linkedlist 进行存储,分别对应 OBJ_ENCODING_ZIPLIST 和 OBJ_ENCODING_LINKEDLIST 。
    3.2 版本之后,统一用 quicklist 来存储。quicklist 存储了一个双向链表,每个节点都是一个 ziplist。 
      quicklist(快速列表)是 ziplist 和 linkedlist 的结合体。quicklist.h,head 和 tail 指向双向列表的表头和表尾。
    • 应用场景
      用户消息时间线 timeline 因为 List 是有序的,可以用来做用户时间线 
      消息队列
      List 提供了两个阻塞的弹出操作:BLPOP/BRPOP,可以设置超时时间。BLPOP:BLPOP key1 timeout 移出并获取列表的第一个元素, 如果列表没有元素
    会阻塞列表直到等待超时或发现可弹出元素为止。
      BRPOP:BRPOP key1 timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
    队列:先进先出:rpush blpop,左头右尾,右边进入队列,左边出队列。栈:先进后出:rpush brpop 

     Set 集合

    • 存储类型

      String 类型的无序集合,最大存储数量 2^32-1(40 亿左右)。

    • 操作命令
    添加一个或者多个元素
    sadd myset a b c d e f g
     
      获取所有元素
    smembers myset
     
      统计元素个数
    scard myset 
     
      随机获取一个元素
    srandmember key
     
      随机弹出一个元素 
    spop myset
     
      移除一个或者多个元素
    srem myset d e f
     
      查看元素是否存在
    sismember myset a 
    • 存储(实现)原理
      Redis 用 intset 或 hashtable 存储 set。如果元素都是整数类型,就用 inset 存储。如果不是整数类型,就用 hashtable(数组+链表的存来储结构)。
    问题:KV 怎么存储 set 的元素?key 就是元素的值,value 为 null。
    如果元素个数超过 512 个,也会用 hashtable 存储。

    应用场景 

    抽奖
      随机获取元素
      spop myset
     
    点赞、签到、打卡
       
    这条微博的 ID 是 t1001,用户 ID 是 u3001。
    用 like:t1001 来维护 t1001 这条微博的所有点赞用户。
    点赞了这条微博:sadd like:t1001 u3001
    取消点赞:srem like:t1001 u3001
    是否点赞:sismember like:t1001 u3001
    点赞的所有用户:smembers like:t1001
    点赞数:scard like:t1001
    比关系型数据库简单许多。

    ZSet 有序集合

    • 存储类型

     

    sorted set,有序的 set,每个元素有个 score。
    score 相同时,按照 key 的 ASCII 码排序。
    数据结构对比:
    • 操作命令
    
    
    添加元素
    
    
    zadd myzset 10 java 20 php 30 ruby 40 cpp 50 python
    
    
    获取全部元素
    
    
    zrange myzset 0 -1 withscores
    
    
    zrevrange myzset 0 -1 withscores
    
    
    根据分值区间获取元素
    zrangebyscore myzset 20 30
    
    
    移除元素
    
    
    也可以根据 score rank 删除
    
    
    zrem myzset php cpp
    
    
    统计元素个数
    
    
    zcard myzset
    
    
    分值递增
    
    
    zincrby myzset 5 python
    
    
    根据分值统计个数
    
    
    zcount myzset 20 60
    
    
    获取元素 rank
    
    
    zrank myzset java
    
    
    获取元素 score
    
    
    zsocre myzset java
    
    
    也有倒序的 rev 操作(reverse)
     
    • 存储(实现)原理
      同时满足以下条件时使用 ziplist 编码:
       元素数量小于 128 个
       所有 member 的长度都小于 64 字节
      在 ziplist 的内部,按照 score 排序递增来存储。插入的时候要移动之后的数据。
     
      超过阈值之后,使用 skiplist+dict 存储。
     
      问题:什么是 skiplist?
      我们先来看一下有序链表:

      在这样一个链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点为止(没找到)。

    也就是说,时间复杂度为 O(n)。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。
    而二分查找法只适用于有序数组,不适用于链表。假如我们每相邻两个节点增加一个指针(或者理解为有三个元素进入了第二层),让指针指向下下个节点。 

     

      这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半(上图中是 7, 19, 26)。在插入一个数据的时候,决定要放到那一层,取决于一个算法(在 redis 中 t_zset.c 有一个 zslRandomLevel 这个方法)。

    现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数据大的节点时,再回到原来的链表中的下一层进行查找。比如,我们想查找 23,查找的路径是沿着下图中标红的指针所指向的方向进行的:
    1. 23 首先和 7 比较,再和 19 比较,比它们都大,继续向后比较。
    2. 但 23 和 26 比较的时候,比 26 要小,因此回到下面的链表(原链表),与 22比较。
    3. 23 比 22 要大,沿下面的指针继续向后和 26 比较。23 比 26 小,说明待查数据 23 在原链表中不存在 
      在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。这就是跳跃表。为什么不用 AVL 树或者红黑树?因为 skiplist 更加简洁。
     
    • 应用场景
      id 为 6001 的新闻点击数加 1:zincrby hotNews:20190926 1 n6001
      获取今天点击最多的 15 条:zrevrange hotNews:20190926 0 15 withscores
  • 相关阅读:
    rest framework 认证 权限 频率
    rest framework 视图,路由
    rest framework 序列化
    10.3 Vue 路由系统
    10.4 Vue 父子传值
    10.2 Vue 环境安装
    10.1 ES6 的新增特性以及简单语法
    Django 跨域请求处理
    20190827 On Java8 第十四章 流式编程
    20190825 On Java8 第十三章 函数式编程
  • 原文地址:https://www.cnblogs.com/talkingcat/p/13803781.html
Copyright © 2011-2022 走看看