中间件之Redis数据类型的基本命令及应用场景

一、Redis定位与特性

1.SQL与NoSQL

在绝大多数情况,我们会优先考虑使用关系型数据库来存储业务数据,比如SQLServer、Oracle、MySQL等等。

关系型数据库特点

  • 它以表格的形式,基于行存储数据,是一个二维模式
  • 它存储的是结构化数据,数据存储有固定的模式(schema),数据需要适应表结构
  • 表与表之间存在关联关系(Relationship)
  • 大部分关系型数据库都支持SQL的操作。支持复杂的关联查询
  • 通过支持事务来提供严格或者实时的数据一致性

关系型数据库限制

  • 扩容只能垂直扩展,比如磁盘限制了数据的存储,就需要扩大磁盘容量,通过堆硬件的方式,不支持动态的扩缩容。水平扩容需要复杂的技术来实现,比如分库分表
  • 表结构修改困难,因此存储的数据库格式也受到限制
  • 关系型数据库通常会把数据持久化到磁盘,在高并发和高数据量的情况下,基于磁盘的读写压力比较大

非关系型数据库特点

  • 存储非结构化数据,比如文本、图片、音频、视频等等
  • 表与表之间没有关联,可扩展性强
  • 保证数据的最终一致性,遵循BASE理论。(Basically Available基本可用、Soft-state软状态、EventuallyConsistent最终一致性)
  • 支持海量数据的存储和高并发的高效读写
  • 支持分布式,能够堆数据进行分片存储,扩缩容简单

2. Redis特性

  1. 为什么把数据存放在内存中?
    1. 内存的速度更快
    2. 减少计算的时间,减轻数据库压力
  2. 如果是用内存的数据结构作为缓存,为什么不用HashMap或者Memcached?
    1. 丰富的数据类型
    2. 支持多种编程语言
    3. 功能丰富:持久化机制、内存淘汰策略、事务、发布订阅、pipeline、lua
    4. 支持集群、分布式

二、Redis数据类型及应用

Redis包括5种基本数据类型:String、Hash、List、Set、Zset。以及使用比较少的其他三种数据类型:BitMaps、Hyperloglogs、Geo等

1.String

存储类型
String可以用来存储INT(整数)、float(单精度浮点数)、String(字符串)

操作命令

# 获取指定范围的字符
getrange key1 0 1
# 获取value长度
strlen key1
# 字符串追加
append key1 good
# 设置多个值
mset key1 1 key2 2
# 获取多个值
mget key1 key2
#设置值,如果key存在,则不成功
setnx key1 1
#多参数,实现分布式锁,加过期时间,加nx属性
set key value[expiration EX seconds|PX milliseconds][NX|XX]
set key1 value1 EX 10 NX
# 整数值递增(值不存在得到1)
incr key1
incrby key1 100 # 一次增100
# 整数值递减
decr key2
decr by key2 100
# 浮点数增量
set myfloat 1.1
incrbyfloat myfloat 2.5

应用场景

  • 缓存
    Stirng类型,缓存热点数据。比如网站首页、微博热搜、报表数据等。可以显著提升热点数据的访问速度
  • 分布式数据共享
    因为Redis是分布式的独立服务,可以在多个应用之间共享。
  • 分布式锁
    String类型的setnx方法,只有不存在时才能添加成功,返回true。
    public  Boolean getLock(String key,String value,long timeoutSeconds) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, timeoutSeconds, TimeUnit.SECONDS);
    }
    for (int i = 0; i < 5; i++) {
        Boolean lock = redisUtils.getLock("lockKey", "lockValue", 3);
        System.out.println("第" + (i + 1) + "次获取锁,状态:" + lock);
        Thread.sleep(1000);
    }
    //第1次获取锁,状态:true
    //第2次获取锁,状态:false
    //第3次获取锁,状态:false
    //第4次获取锁,状态:true
    //第5次获取锁,状态:false
    
  • 全局id
    INT类型,INCRBY,利用原子性
    public Object getIdTest() throws Exception {
        int cpuNum = Runtime.getRuntime().availableProcessors();// 获取处理器数量
        int threadNum = cpuNum * 2 + 1;// 根据cpu数量,计算出合理的线程并发数
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                threadNum - 1,
                threadNum,
                Integer.MAX_VALUE,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>(Integer.MAX_VALUE),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                        super.rejectedExecution(r, e);
                    }
                }
        );
        long start = System.currentTimeMillis();
        int count = 100000;
        CountDownLatch countDownLatch = new CountDownLatch(count);
        for (int i = 0; i < count; i++) {
            threadPoolExecutor.execute(() -> {
                Long id = redisTemplate.opsForValue().increment("id");
                map.merge(id, 1, Integer::sum);
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        Set<Long> longs = map.keySet();
        for (Long aLong : longs) {
            if (map.get(aLong) > 1) {
                throw new Exception("出现重复id:" + aLong + ",出现次数:" + map.get(aLong));
            }
        }
        long end = System.currentTimeMillis();
        long result = end - start;
        return "执行完毕,耗时:" + result + "ms";//执行完毕,耗时:2965ms
    }
    
  • 计数器
    INT类型,INCR方法
    例如:文章的阅读量、微博点赞数、允许一定的延迟,先写入Redis再定时同步到数据库
  • 限流
    INT类型,INCR方法
    以访问者的IP和其他信息作为key,访问一次增加一次计数,超过次数则返回false。
        public Object limitingTest() {
        Random ra =new Random();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            ThreadPoolUtils.execute(()->{
                int random = ra.nextInt(10) + 1;
                for (int j = 1; j <= 10; j++) {
                    Long increment = redisTemplate.opsForValue().increment("user_" + finalI);
                    if (Integer.parseInt(String.valueOf(increment)) >= random) {
                        System.out.println("user_" + finalI + "获取访问限制次数为:" + random + ",已经访问次数为:"+increment+",当前访问次数:" + j + ",访问收到限制!");
                    }
                }
            });
        }
        return "";
    }
    
  • 总结:利用redis本身的特性,和String内存的存储内容,以及提供的操作方法,我们可以用来达到很多的业务目的

2.Hash哈希

Hash用来存储多个无序的键值对。最大存储数量2^32-1(40亿左右)
在这里插入图片描述

注:

  • 这个Hash和Redis的存储结构的Hash不是一个东西,那个是外层hash,现在说的是内层hash。
  • Hash的value只能是字符串,不能嵌套其他类型,比如hash或者list。

Hash和String的主要区别

  1. hash把所有相关的值聚集在一个key中,节省内存空间
  2. 只使用一个key,减少key冲突
  3. 当需要批量获取值的时候,只需要一个命令,减少内存/IO/CPU的消耗

Hash不适合使用的场景

  1. Field不能单独设置过期时间,Field的过期时间依赖于key,key过期,整条过期。
  2. 需要考虑数据量分布的问题(field非常多的时候,无法分布到多个节点)

操作命令

# 单个设置field
hset h1 field1 1
hset h1 field2 2
# 一次设置多个field
hmset h2 field1 1 field2 2 field3 3 field4 4

# 获取某个key中的某个field
hget h1 field1
##批量获取某个key中的多个field值
hmget h1 field1 field2 field3
# 获取key中的所有字段
hkeys h1
# 获取key中的所有字段的值
hvals h1

# 获取key中的字段长度
hlen h1
# 删除key中的某个field,可传入多个
hdel h1 field1 filed2

RedisTemplate中封装的方法
在这里插入图片描述

应用场景
存储对象类型的数据,比如一个对象或者一张表的数据,比String节省了更多key的空间,也更加便于集中管理

  • 购物车
    key:用户id;field:商品id、商品数量
    商品数量+1:hincr
    商品数量-1:hdecr
    删除商品:hdel key field1
    全选:hgetall
    商品数量:hlen

3.List

存储类型
存储有序的字符串(从左到右),元素可以重复。最大存储数量2^32-1(40亿左右)

3.2版本之后,用quicklist(此对象中标明了头部和尾部、以及count元素数量和len节点长度)来存储。quicklist存储了一个双向链表,每个节点quicklistNode都是一个ziplist,所以是ziplist和linkedlist的结合体。
quicklist是一个数组+链表的结构

在这里插入图片描述

操作命令

  • 元素增减
    # 列表的左侧塞入一个值 (left)头部
    lpush myqueue a # 1 a
    # 列表塞入多个值 (left)
    lpush myqueue b c d # 4 d c b a
    # 列表右侧塞入多个值(right)尾部
    rpush myqueue h #5 d c b a h
    # 列表左侧弹出一个值
    lpop myqueue # d		c b a h
    # 列表右侧弹出一个值
    rpop myqueue # h		c b a
    
  • 取值
    # 取出索引位置的value
    lindex myqueue 0 # c
    lrange myqueue 0 -1 # c b a
    

RedisTemplate中封装的方法
在这里插入图片描述

应用场景
list主要用在存储有序内容的场景。

  • 列表
    例如用户的消息列表、网站的公告列表、活动列表、博客的文章列表、评论列表等等。思路:存储所有字段,使用lrange queue start end命令取出一页,按顺序显示。
  • 队列/栈
    List可以用作分布式环境的队列/栈使用。
    List提供了两个阻塞的弹出操作:BLPOP/BRPOP,可以设置超时时间(单位:秒)
    # 头部弹出
    blpop queue1 queue2... timeout
    #尾部弹出
    brpop queue1 queue2... timeout
    
    BLPOP:移除并获取列表的第一个元素,如果列表没有元素会阻塞队列直到等待超时或发现可弹出元素位置。
    BRPOP:移除列表的最后一个元素,如果列表没有元素会阻塞队列直到等待超时或发现可弹出元素位置。

4.Set

存储类型
Set存储String类型的无序集合,最大存储数量2^32-1。Redis用intset或hashtable存储set。如果元素都是整数类型,就用inset存储,如果不是整数类型或元素个数超过512个(可配置),就用hashtable(数组+链表)
在这里插入图片描述
操作命令

# 添加一个或者多个元素
sadd myset a b c d e f g # 7
# 获取所有元素
smembers myset # 无序展示
# 统计元素个数
scard myset # 7
# 随机获取一个元素【传入count可以随机获取count个元素】
srandmember myset [count]
# 随机弹出一个元素,从set中删除【传入count可以随机获取count个元素】
spop myset [count]
# 移除一个或者多个元素
srem myset a b c
//查看元素是否存在
sismember myset a

RedisTemplate中封装的方法
在这里插入图片描述

应用场景

  • 抽奖
    随机获取n个(不传为1)元素:srandmember myset n
    随机移除n个(不传为1)元素:spop myset n

  • 点赞、打卡、签到
    如一条微博id为wb001,用户id是u001,u002,u003...
    点赞这条微博:sadd wb001 u001 u002…
    u001取消点赞:srem wb001 u001
    u001是否点赞:sismember wb001 u001
    点赞的所有用户:smembers wb001
    点赞数:scard wb001

  • 商品标签
    如用i5001和i5002来维护两件商品的所有标签
    sadd i5001 画面清晰
    sadd i5001 流畅

    sadd i5002 流畅
    sadd i5002 真彩显示屏
    获取差集:sdiff i5001 i5002
    #(画面清晰、真彩显示屏)
    获取交集:sinter i5001 i5002
    #(流畅)
    获取并集
    #(画面清晰、流畅、真彩显示屏)

  • 用户关注、推荐模型
    相互关注、我关注的人也关注了他、可能认识的人

5.Zset

存储类型
sorted set 存储有序的元素。每个元素有个score,按照score的从小到大排序。score相同的,按照key的ASCII码排序。
在这里插入图片描述

跳表
在这里插入图片描述
比如上面组数据,我要查找一个分值为14的数据(流程看红线)

  1. 首先在最外层找到7,发现比7大,继续找,发现没有了
  2. 然后就降级,从7找到25,发现比7大,比25小,则在此范围内继续降级
  3. 找到比7大,比15小,继续降级
  4. 找到比7大比11大,则往后找,找到比11大,比15小,说明此zset中没有分值为14的数据

数据结构对比

数据结构是否允许重复元素是否有序有序实现方式
列表List索引下标
集合Set
有序集合Zset分值score

操作命令

# 添加元素
zadd mzset 10 java 20 python 30 php
# 获取全部元素 正序 [携带分数]
zrange mzset 0 -1 withscores
# 获取全部元素 倒序 [携带分数]
zrevrange mzset 0 -1 withscores
# 根据分值区间获取区间内分值的key
zrangebyscore mzset 10 20 # java python
#移除元素,也可以根据score rank删除
zrem mzset php # 1
# 统计元素个数
zcard mzset # 2
# 分值递增
zincrby mzset 5 java # 15
# 根据分值统计个数
zcount mzset 10 20 # 2
# 获取元素rank(等级,排在第一个的就是0,根据分数依次递增,每次+1)
zrank mzset java # 0
# 获取元素score
zscore mzset java # 15

RedisTemplate中封装的方法
redisTemplate.opsForZSet();
在这里插入图片描述
应用场景
顺序会动态变化的列表

  • 排行榜
    例如百度热搜,微博热榜。
    在Zset:hotNews中有一个id为9000的新闻用户每点击一次加1:zincrby hotNews 1 9000
    获取今天点击最多的15条新闻:zervrange hotNews 0 15 withscores

三、应用场景总结

场景优点
缓存提升数据访问速度,减轻数据库压力
共享数据分布式环境下的数据存储共享
全局ID分布式全局ID的生成方案(分库分表)
分布式锁进程间共享数据的原子操作保证
在线用户统计和计数zset
队列、栈跨进程、分布式的队列、栈
消息队列异步解耦的消息机制
服务注册与发现RPC通信机制的服务协调中心(Dubbo)
购物车hash
新浪/Twitter用户消息时间线列表list
抽奖逻辑(礼物、转发)Set
点赞、签到、打卡Set
商品标签Set
用户关注模型、商品推荐模型set
电商产品筛选set
排行榜zset
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程大帅气

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值