Redis - 01 五种核心数据结构应用

81 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Redis - 核心数据结构

五种数据类型

1649048647313.png

注:Redis是以键值对{key,value}形式储存的

  • 对于redis来说,所有的key(键) 都是字符串类型
  • value 可以支持上图五种数据类型

字符串String

是redis中最基本的数据类型,一个key对应一个value。

String类型是二进制安全的,意味着 redis的string可以包含任何数据。如数字,字符串,jpg图片或者序列化对象

  • 常用命令有:
序号命令及描述
1SET key value 设置指定 key 的值。
2GET key 获取指定 key 的值。
3SETNX key value 只有在 key 不存在时设置 key 的值。
4SETEX key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
5INCR key
将 key 中储存的数字值增一。
6 INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) 。
7DECR key
将 key 中储存的数字值减一。
8DECRBY key decrement
key 所储存的值减去给定的减量值(decrement) 。
9 APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
  • 应用场景

    1. 缓存:经典实用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存,mysql作为持久化层,降低mysql的读写压力;

      • 单值缓存

        SET Key Value
        GET Key
        
      • 对象缓存

        用法1:  SET  user:userId   value(json格式数据)
            	set  user:1  {"name": "zhangsan","age":"18"}
        
        
        用法2:  SET  user:userId:属性名   value(属性值)
        		set  user:1:name 		zhangsan   
                get  user:1:name
                    
        用法3:  MSET  user:1:name zhangsan   user:1:age  18
            	MGET  user:1:name    user:1:age
        
        
    2. 计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源

      命令实现:
      127.0.0.1:6379> get counter
      "2"
      127.0.0.1:6379> incr counter
      (integer) 3
          
          
      代码实现:
      String key = "code_" + shopCode;
      Long codeNum;
      //key存在则加1,key不存在则创建后加1; 最终返回value
      codeNum = codeRedisTemplate.opsForValue().increment(key, 1);
          
      
    3. session:常见方案 spring session + redis实现session共享

    4. 分布式锁:

      SETNX item:10001 true			//返回1代表获取锁成功
      SETNX item:10001 true			//返回0代表获取锁失败
      。。。执行业务操作
      DEL product:10001 				//执行完业务释放锁
          
      SET item:10001 true ex 100 nx	//防止程序意外终止导致死锁
      
          
          
      

1649051326085.png

1649050974421.png

哈希 hash

是一个Mapmap,指值本身又是一种键值对结构,如 value={{field1,value1},......fieldN,valueN}}

image.png

  • 使用:所有hash的命令都是 h 开头的 hget 、hset 、 hdel 等

    模板:
    HMSET user  {userId}:name zhangsan   {userId}:age 18
    
    实战命令:
    127.0.0.1:15>HMset user 1:name zhuangsan  1:age 18
    "OK"
    127.0.0.1:15>hmget user 1:name
     1)  "zhuangsan"
    
    

1649052163512.png

  • 应用场景: 对象缓存 - 电商购物车

    • { userId , {product , productNum} }

    1. 以用户id 为 key

    2. 商品id为 field

    3. 商品数量为 value

    • 购物车操作:

      1.添加商品
      127.0.0.1:15>hset  cart:1001 10088 1
      "1"
      127.0.0.1:15>hset book:1001 10089 1
      "1"
      
      
      2.增加数量
      127.0.0.1:15>hincrby cart:1001 10088 1
      "2"
      
      
      3.商品总数(不是sku)
      127.0.0.1:15>hlen cart:1001
      "1"
      
      
      4.删除商品
      127.0.0.1:15>hdel cart:1001 10088
      "1"
      
      
      5.获取购物车所有商品
      127.0.0.1:15>hgetall book:1001
       1)  "10089"
       2)  "1"
      
      

1649052874399.png

  • Hash结构的优缺点:
    • 优点:
      1. 同类数据归类整合储存,方便数据管理
        • 如果存储的都是比较结构化的数据,比如用户数据缓存,或者经常需要操作数据的一个或者几个, 特别是如果一个数据中如果filed比较多,但是每次只需要使用其中的一个或者少数的几个,使用hash是一个好的选择, 因为它提供了hget 和 hmget,而无需取出所有数据再在代码中处理。
        • 反之, 如果数据差异较大,操作时常常需要把所有数据都读取出来再处理,使用string 是一个好的选择。
      2. 相比String操作消耗内存和cpu更小
      3. 相比String存储更节省空间
    • 缺点:
      1. 过期功能不能使用在 field上,只能用在key上
      2. 在Redis集群架构下,不适合大规模使用

列表list

Redis 使用双端链表实现的List,是有序的,value可以重复,可以通过下标取出对应的value值,左右两边都能进行插入和删除数据。

image.png

  • List常用操作:

1649053852265.png

-- 实战:

测试 :15>rpush list no1
"1"
测试 :15>rpush list no2
"2"
测试 :15>lpush list no3
"3"
测试 :15>lpop list
"no3"
测试 :15>rpop list
"no2"


1649054117761.png


  • 列表使用技巧

    • 先进后出: lpush + lpop = Stack(栈)
    • 先进后出: lpush+rpop=Queue(队列)
    • lpush+ltrim = Capped Collection(有限集合)
    • lpush + brpop = Blocking MQ(阻塞队列)
  • **List应用场景:**微博消息和微信公众号消息

    • 我关注了 大V,大V的新动态,就会推送到我的缓存List中
    • 微博、微信公众号都有关注人的时间轴;最新更新的在最前面
    • 有人发布微博,用 lpush加入时间轴
    • 展示新的列表信息,使用 LRANGE命令

集合set

  • 集合类型也是用来保存多个字符串的元素,但和列表不同的是集合中
    1. 不允许有重复的元素,
    2. 集合中的元素是无序的,不能通过索引下标获取元素,
    3. 支持集合间的操作,可以取多个集合取交集、并集、差集。

image.png

1649054682108.png 使用:命令都是以s开头的 sset 、srem、scard、smembers、sismember

测试 :15>SADD SET M1
"1"
测试 :15>SADD SET M1
"0"
测试 :15>SADD SET M2
"1"
测试 :15>SADD SET [1,2,3]
"1"
测试 :15>SADD SET [1,2,3]
"0"
测试 :15>SREM SET M2
"1"
测试 :15>SMEMBERS SET
 1)  "M1"
 2)  "[1,2,3]"
测试 :15>SCARD SET
"2"
测试 :15>SISMEMBER SET M2
"0"
测试 :15>SISMEMBER SET M1
"1"
测试 :15>SRANDMEMBER SET 1
 1)  "[1,2,3]"
测试 :15>SRANDMEMBER SET 1
 1)  "M1"
测试 :15>SRANDMEMBER SET 1
 1)  "[1,2,3]"
测试 :15>SRANDMEMBER SET 1
 1)  "M1"
测试 :15>SPOP SET 1
 1)  "[1,2,3]"
测试 :15>SMEMBERS SET
 1)  "M1"
测试 :15>
  • SET 应用场景:

    • 微信抽奖小程序

1649055053755.png

  • 微信微博点赞,收藏,标签

1649055065387.png

Set数据结构的集合操作: 交集、并集、差集

1649055220244.png

测试 :15>SMEMBERS SET
 1)  "M1"
测试 :15>SADD SET2 M1
"1"
测试 :15>SADD SET2 M2
"1"

测试 :15>SINTER SET SET2
 1)  "M1"
测试 :15>SUNION SET SET2
 1)  "M2"
 2)  "M1"
测试 :15>SDIFF SET2 SET
 1)  "M2"

重点:集合操作交并差

  • 集合操作实现微博微信关注模式

1649055447730.png

  • 集合操作实现电商商品筛选(最好还是用ES做,这里只提供一个redis解决思路)

1649055460648.png

有序集合 zset

有序集合和集合有着必然的联系,保留了集合不能有重复成员的特性,区别是,有序集合中的元素是可以排序的,它给每个元素设置一个分数,作为排序的依据。

(有序集合中的元素不可以重复,但是score 分数 可以重复,就和一个班里的同学学号不能重复,但考试成绩可以相同)。

image.png

使用: 有序集合的命令都是 以 z 开头 zadd 、 zrange、 zscore

1649055819824.png

测试 :15>zadd myzset 44 m4
"1"
测试 :15>zadd myzset 55 m5
"1"
测试 :15>zadd myzset 66 m6  77 m7
"2"
测试 :15>zrange myzset 0 -1
 1)  "m4"
 2)  "m5"
 3)  "m6"
 4)  "m7"
 5)  "m2"
 6)  "m1"
 7)  "m3"
测试 :15>zscore myzset m2
"99"
测试 :15>
  • 有序集合应用场景:
    • 排行榜: 有序集合经典使用场景。
    • 例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。

1649056165481.png

其他高级命令:

keys * :全量遍历键;当redis数据量比较大时,性能比较差,要避免使用;

测试 :15>keys *
 1)  "SET"
 2)  "cart:1001"
 3)  "list"
 4)  "myzset"
 5)  "SET2"
 6)  "user"
 7)  "book:1001"
    

scan:渐进式遍历键

clipboard.png

todo 单线程与高性能 todo

  • redis 6 以前的版本,严格来说也是多线程, 只不过执行用户命令的请求时单线程模型,还有一些线程用来执行后台任务, 比如 unlink 删除 大key,rdb持久化等。

  • redis 6.0 提供了多线程的读写IO, 但是最终执行用户命令的线程依然是单线程的,这样,就没有多线程数据的竞争关系,依然很高效。