Redis之基础知识总结

Redis之基础知识总结

一、支持的基本的数据类型

1.1. 五大数据类型

Redis有5个基本数据结构,stringlisthashsetzset。它们是日常开发中使用频率非常高应用最为广泛的数据结构,

  • String类型

    1. Stringredis最基本的类型,一个key对应一个value,sring类型是二进制安全的。redisstring可以包含任何数据。Sring类型是Redis最基本的数据类型,一个键最大能存储512MB

    2. Redis的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于JavaArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。

    3. 内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。

    4. 针对String的一些操作:

      127.0.0.1:6379> set name zhangsan  # 初始化字符串
      OK
      127.0.0.1:6379> get name           # 获取字符串
      "zhangsan"
      127.0.0.1:6379> strlen name        # 字符串长度
      (integer) 8
      127.0.0.1:6379> getrange name 2 4  # 获取子串 key值和开始和结束位置
      "ang"
      127.0.0.1:6379> setrange name 2 6666  # 覆盖子串  将会把子串 6666 从2位置开始覆盖
      (integer) 8
      127.0.0.1:6379> get name              # 覆盖之后的子串
      "zh6666an" 
      127.0.0.1:6379> append name 123456789    # 追加子串
      (integer) 17
      127.0.0.1:6379> get name                 # 追加之后的子串
      "zh6666an123456789"
      127.0.0.1:6379>
      
    5. 将作为计数器使用

      127.0.0.1:6379> set index 10                       # 设置 index 值为 10
      OK
      127.0.0.1:6379> get index
      "10"
      127.0.0.1:6379> incrby index 1                     # 增加一
      (integer) 11
      127.0.0.1:6379> get index                          # 增加一后为11
      "11"
      127.0.0.1:6379> decrby index 1                     # 减去一
      (integer) 10
      127.0.0.1:6379> get index                          # 减去一位10
      "10"
      127.0.0.1:6379> incr index                         # 加一
      (integer) 11
      127.0.0.1:6379> decr index                         # 减一
      (integer) 10
      127.0.0.1:6379> 
      
    6. 过期删除操作

        127.0.0.1:6379> expire index 5                     # 设置过期时间
        (integer) 1              # 1 成功      0 key不存在
        127.0.0.1:6379> ttl index                          # 获取还有多久过期
        (integer) -2             # -2 表示变量不存在, -1 表示设置过期时间  返回正常正数为还有多少秒过期
        127.0.0.1:6379> del name                           # 删除
        (integer) 1              # 删除成功返回1
        127.0.0.1:6379> get name                           # 获取   
        (nil)  # b变量不存在
        127.0.0.1:6379> 
        ```
      
      
  • list类型

    1. Redis将列表数据结构命名为list而不是array,是因为列表的存储结构用的是链表而不是数组,而且链表还是双向链表。因为它是链表,所以随机定位性能较弱,首尾插入删除性能较优。如果list的列表长度很长,使用时我们一定要关注链表相关操作的时间复杂度。

    2. 负下标 链表元素的位置使用自然数0,1,2,....n-1表示,还可以使用负数-1,-2,...-n来表示,-1表示「倒数第一」,-2表示「倒数第二」,那么-n就表示第一个元素,对应的下标为0

    3. 队列/堆栈 链表可以从表头和表尾追加和移除元素,结合使用rpush/rpop/lpush/lpop四条指令,可以将链表作为队列或堆栈使用,左向右向进行都可以

    4. list的一些操作:

      在日常应用中,列表常用来作为异步队列来使用

      127.0.0.1:6379> rpush code go java  # 将值value插入到列表key的表尾,当且仅当key存在并且是一个列表。
      (integer) 2
      127.0.0.1:6379> rpush code python php c c++
      (integer) 6
      127.0.0.1:6379> rpop code    #  移除并返回列表key的尾元素
      "c++"
      127.0.0.1:6379> rpop code
      "c"
      127.0.0.1:6379> lpop code    #  移除并返回列表key的头元素
      "go"
      127.0.0.1:6379> lpop code
      "java"
      127.0.0.1:6379> lrange code 0 -1   # 查看列表中的元素  0 开始位置, -1为最后一个位置
      1) "python"
      2) "php"
      127.0.0.1:6379> lpush code 12345   # 往表头插入数据
      (integer) 3
      127.0.0.1:6379> lrange code 0 -1   # 将12345 插入到了表头
      1) "12345"
      2) "python"
      3) "php"
      127.0.0.1:6379> lpush code java1 java2   # 插入多个值
      (integer) 6
      127.0.0.1:6379> lrange code 0 -1
      1) "java2"
      2) "java1"
      3) "rust"
      4) "12345"
      5) "python"
      6) "php"
      127.0.0.1:6379> llen code    # 获取列表的长度
      (integer) 6
      127.0.0.1:6379> lindex code 2   # 访问位置2的数据
      "rust"
      127.0.0.1:6379> lset code 1 js   # 修改位置1的数据
      OK
      127.0.0.1:6379> lrange code 0 -1
      1) "java2"
      2) "js"
      3) "rust"
      4) "12345"
      5) "python"
      6) "php"
      127.0.0.1:6379> linsert code after js js1   # 在js数据之后插入数据
      (integer) 7
      127.0.0.1:6379> lrange code 0 -1
      1) "java2"
      2) "js"
      3) "js1"
      4) "rust"
      5) "12345"
      6) "python"
      7) "php"
      127.0.0.1:6379> linsert code before js js2  # 在js数据之前插入数据
      (integer) 8
      127.0.0.1:6379> lrange code 0 -1
      1) "java2"
      2) "js2"
      3) "js"
      4) "js1"
      5) "rust"
      6) "12345"
      7) "python"
      8) "php"
      127.0.0.1:6379> lrem code 1 js   # 元素删除
      (integer) 1
      127.0.0.1:6379> lrange code 0 -1
      1) "java2"
      2) "js2"
      3) "js1"
      4) "rust"
      5) "12345"
      6) "python"
      7) "php"
      127.0.0.1:6379> ltrim code -4 -1   # 截取定长数据, 其他部分将被舍去
      OK
      127.0.0.1:6379> lrange code 0 -1
      1) "rust"
      2) "12345"
      3) "python"
      4) "php"
      127.0.0.1:6379> 
      
      
    5. 快速列表

      如果再深入一点,你会发现Redis底层存储的还不是一个简单的linkedlist,而是称之为快速链表quicklist的一个结构。首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成quicklist。因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prevnext。所以Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

  • hash类型

    1. 哈希等价于Java语言的HashMap或者是Python语言的dict,在实现结构上它使用二维结构,第一维是数组,第二维是链表,hash的内容keyvalue存放在链表中,数组里存放的是链表的头指针。通过key查找元素时,先计算keyhashcode,然后用hashcode对数组的长度进行取模定位到链表的表头,再对链表进行遍历获取到相应的value值,链表的作用就是用来将产生了「hash碰撞」的元素串起来。Java语言开发者会感到非常熟悉,因为这样的结构和HashMap是没有区别的。哈希的第一维数组的长度也是2^n

    2. 一些常规操作:

         127.0.0.1:6379> hset code name zhangsan  # 增加一个元素
           (integer) 1
           127.0.0.1:6379> hmset code lisi threeClass wangwu fourClass  # 增加多个元素
           OK
           127.0.0.1:6379> hget code name   # 通过key获取值
           "zhangsan"
           127.0.0.1:6379> hgetall code     # 获取所有的值
           1) "name"
           2) "zhangsan"
           3) "lisi"
           4) "threeClass"
           5) "wangwu"
           6) "fourClass"
           127.0.0.1:6379> hkeys code       # 获取所有的key
           1) "name"
           2) "lisi"
           3) "wangwu"
           127.0.0.1:6379> hvals code       # 获取所有的value
           1) "zhangsan"
           2) "threeClass"
           3) "fourClass"
           127.0.0.1:6379> hdel code name   # 获取key 为name的数据
           (integer) 1
           127.0.0.1:6379> hgetall code
           1) "lisi"
           2) "threeClass"
           3) "wangwu"
           4) "fourClass"
           127.0.0.1:6379> hdel code lisi wangwu   # 删除多个
           (integer) 2
           127.0.0.1:6379> hgetall code
           (empty list or set)
           127.0.0.1:6379> hexists code name       # 查看name存在, 存在返回1  不存在返回0
           (integer) 1
           127.0.0.1:6379> hexists code naaa
           (integer) 0
           
           127.0.0.1:6379> hset code name lisi    # 计数器,使用,不能使字符串
           (integer) 1
           127.0.0.1:6379> hincrby code name 1    # 加一操作,这样会报错
           (error) ERR hash value is not an integer
           127.0.0.1:6379> hset code name 1       # 将name值设置为1
           (integer) 0
           127.0.0.1:6379> hincrby code name 1    # 加一操作之后就成为 2
           (integer) 2
           127.0.0.1:6379> hgetall code
           1) "name"
           2) "2"
           127.0.0.1:6379> hdel code name         # 删除元素   
           (integer) 1
           127.0.0.1:6379> hexists code name      # 是否存在    存在1   不存在0
           (integer) 0
           127.0.0.1:6379> 
        ```
      
      3. **扩容** 当`hash`内部的元素比较拥挤时(`hash`碰撞比较频繁),就需要进行扩容。扩容需要申请新的两倍大小的数组,然后将所有的键值对重新分配到新的数组下标对应的链表中(`rehash`)。如果`hash`结构很大,比如有上百万个键值对,那么一次完整`rehash`的过程就会耗时很长。这对於单线程的`Redis`里来说有点压力山大。所以`Redis`采用了渐进式`rehash`的方案。它会同时保留两个新旧`hash`结构,在后续的定时任务以及`hash`结构的读写指令中将旧结构的元素逐渐迁移到新的结构中。这样就可以避免因扩容导致的线程卡顿现象。
      
      4. **缩容** `Redis``hash`结构不但有扩容还有缩容,从这一点出发,它要比`Java``HashMap`要厉害一些,`Java``HashMap`只有扩容。缩容的原理和扩容是一致的,只不过新的数组大小要比旧数组小一倍。
      
      
      
  • set类型

    1. Java程序员都知道HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redisset结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

    2. 一些常用的方法:

      127.0.0.1:6379> sadd index name sex age height   # 添加元素
      (integer) 4
      127.0.0.1:6379> smembers index    # 列出所有元素
      1) "sex"
      2) "name"
      3) "height"
      4) "age" 
      127.0.0.1:6379> scard index    # 统计元素
      (integer) 4
      127.0.0.1:6379> srandmember index   # 随机获取某一元素
      "age"
      127.0.0.1:6379> srem index sex  # 删除元素
      (integer) 1
      127.0.0.1:6379> spop index      # 随机删除一个元素
      "height"
      127.0.0.1:6379> sismember index sex    # 检验某一元素是否存在,0 不存在   1存在
      (integer) 0
      127.0.0.1:6379> sismember index name
      (integer) 1
      127.0.0.1:6379> 
      
      
  • sortset类型

    1. SortedSet(zset)Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。

    2. zset底层实现使用了两个数据结构,第一个是hash,第二个是跳跃列表,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。跳跃列表的目的在于给元素value排序,根据score的范围获取元素列表。

    3. 一些常用操作:

      127.0.0.1:6379> zadd info 14 age     # 增加一元素
      (integer) 1
      127.0.0.1:6379> zadd info 15 name 17 height   # 增加多个元素
      (integer) 2
      127.0.0.1:6379> zcard info                    # 获取元素个数
      (integer) 3
      127.0.0.1:6379> zrem info name                # 删除元素
      (integer) 1
      127.0.0.1:6379> zadd info 5 sex               
      (integer) 1
      127.0.0.1:6379> zincrby info 1 sex            # 加一操作
      "6"
      127.0.0.1:6379> zincrby info 3 sex            # 加三操作
      "9"
      127.0.0.1:6379> zrange info 0 -1              # 获取所有元素
      1) "lisi"
      2) "sex"
      3) "age"
      4) "height"
      127.0.0.1:6379> zrank info sex     # 指定元素的正向排名
      (integer) 1
      127.0.0.1:6379> zrevrank info sex  # zrevrank指令获取指定元素的反向排名[倒数第一名] 
      (integer) 2
      127.0.0.1:6379> zscore info sex    # 获取元素的权重
      "9"
      127.0.0.1:6379> zrange info 0 -1 withscores   # 携带权重正向输出
      1) "lisi"
      2) "7"
      3) "sex"
      4) "9"
      5) "age"
      6) "14"
      7) "height"
      8) "17"
      127.0.0.1:6379> zrevrange info 0 -1 withscores  # 携带权重反向输出
      1) "height"
      2) "17"
      3) "age"
      4) "14"
      5) "sex"
      6) "9"
      7) "lisi"
      8) "7"
      127.0.0.1:6379> zrangebyscore info -inf +inf withscores   # 通过zrangebyscore指令指定score范围获取对应的元素列表   -inf 负无穷    +inf 正无穷
      1) "lisi"
      2) "7"
      3) "sex"
      4) "9"
      5) "age"
      6) "14"
      7) "height"
      8) "17"
      127.0.0.1:6379> zrevrangebyscore info +inf -inf withscores  # 通过zrevrangebyscore指令获取倒排元素列表
      1) "height"
      2) "17"
      3) "age"
      4) "14"
      5) "sex"
      6) "9"
      7) "lisi"
      8) "7"
      127.0.0.1:6379> 
      

二、简述机制原理

Redis使用的是NIO中的多路IO复用的机制,能够非常好的支持并发,从而保持线程安全。

Redis单线程,也就是底层采用一个线程维护多个不同的客户端io操作。

但是NIO在不同的操作系统上实现的方式有所不同,在我们windows操作系统使用select实现轮训时间复杂度是为o(n),而且还存在空轮训的情况,效率非常低, 其次是默认对我们轮训的数据有一定限制,所以支持上万的tcp连接是非常难。

linux操作系统采用epoll实现事件驱动回调,不会存在空轮训的情况,只对活跃的 socket连接实现主动回调这样在性能上有大大的提升,所以时间复杂度是为o(1)

nginxredis都能够非常高支持高并发,最终都是linux中的IO多路复用机制epoll, Redis底层采用nio epoll实现。

三、使用场景

  • Token令牌的生成
  • 短信验证码Code
  • 缓存查询数据
  • 网页计数器
  • 分布式锁
  • 延迟操作

四、 SpringBoot整合

4.1. 依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 需要引入pools,否则会报类无法找到 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

4.2. 配置文件
# Redis服务器地址
spring.redis.host=192.168.252.135
# Redis数据库索引
spring.redis.database=0
# Redis密码
spring.redis.password=123456
# 端口
spring.redis.port=6379
# 连接池最大连接数,负值无限制
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间 负值无限制
spring.redis.lettuce.pool.max-wait=-1ms
# 关机超时时间
spring.redis.lettuce.shutdown-timeout=100ms
# 最小空闲连接数
spring.redis.lettuce.pool.min-idle=0
4.3. 使用例子
@RestController
public class RedisController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @GetMapping("/getData")
    public String load(@RequestParam("name") String name) {
        stringRedisTemplate.opsForValue().set("name", name, TimeUnit.SECONDS.toSeconds(60));
        return "success";
    }

}
4.4. Redis客户端区别
  • Jedis:是RedisJava实现客户端,提供了比较全面的Redis命令的支持,

  • Redisson:实现了分布式和可扩展的Java数据结构。

  • Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

总体来说:优先使用Lettuce,如果需要分布式锁,分布式集合等分布式的高级特性,添加Redisson结合使用,因为Redisson本身对字符串的操作支持很差。

文章第二部分的Redis数据类型参考文章地址:https://juejin.im/post/5b53ee7e5188251aaa2d2e16

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章