Redis 高频数据类型使用详解

news2024/11/23 19:10:35

目录

一、前言

二、Redis常用数据类型

2.1 常见的数据类型

三、String 类型

3.1 String 类型简介

3.2 String常用操作命令

3.2.1 String 操作命令实践

3.3 常用业务场景

3.3.1 session共享

3.3.2 登录失败计数器

3.3.3 限流

3.3.4 多线程安全控制

四、Hash类型

4.1 hash 数据结构简介

4.2 为什么使用hash结构

4.3 hash 常用操作命令

4.3.1 常用命令操作演示

4.4 hash常用业务场景

4.4.1 对象格式存储数据

4.4.2 缓存热点数据

4.4.3 计数功能

4.4.4 数据过滤

4.4.5 电商购物车

五、List 类型

5.1 list类型简介

5.2 list类型特点

5.3 list类型常用命令

5.3.1 list命令总结

5.3.2 操作实践

5.4 list使用场景

5.4.1 实现常用的分布式数据结构

5.4.2 抢购秒杀

5.4.3 消息队列

5.4.4 排行榜

5.4.5 分页查询效果

5.4.6 流量削峰

六、Set 类型

6.1 Set简介

6.2 Set 类型常用命令

6.3 Set命令操作使用

6.3.1 常用命令操作演示

6.3.2 核心API

6.4 Set使用场景

6.4.1 用户关注、推荐模型

6.4.2 商品/用户画像标签

6.4.3 抽奖

6.4.4 点赞、收藏、喜欢数

6.4.5 统计网站的独立IP

七、SortedSet

7.1 SortedSet概述

7.1.1 SortedSet特点

7.2 常用操作命令

7.2 .1 操作命令演示

7.2 .2 核心操作API

7.3 SortedSet使用场景

7.3.1 排行榜(TOP N)

7.3.2 带权重的消息队列

7.3.3 滑动窗口限流

7.3.4 精准设定过期时间的数据

八、写在文末


一、前言

在项目开发过程中,经常会遇到各种意向不到的业景需要处理,比如统计某网站近期top10的账户访问量,再比如,需根据网站的账户活跃程度送积分刺激消费等,尽管通过数据库和程序的计算可以实现,但这种纯粹编程式的解决是否最高效的呢?是否有更合理的解决方案呢?这就是本文接下来将要介绍的,即合理使用redis的不同的数据结构,可以为问题的解决带来意想不到的效果。

二、Redis常用数据类型

redis提供了丰富的数据结构,这也是为什么这么多年来其热度始终不减的重要原因,不同的数据结构都对应着不同的使用场景,可以根据实际需要灵活的选择。

2.1 常见的数据类型

结合实际经验,redis常用的数据类型总结如下:

  • String

  • Hash

  • List

  • Set

  • SortedSet

接下来将针对每种类型进行深入的探讨。

三、String 类型

3.1 String 类型简介

String类型,也就是字符串类型,是Redis中最简单的存储类型。其value是字符串,不过根据字符串的格式不同,又可以分为3类:

  • string:普通字符串;

  • int:整数类型,可以做自增、自减操作;

  • float:浮点类型,可以做自增、自减操作;

不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m

3.2 String常用操作命令

下面列举String类型常用的操作命令

  • SET:添加或者修改已经存在的一个String类型的键值对;

  • GET:根据key获取String类型的value;

  • MSET:批量添加多个String类型的键值对;

  • MGET:根据多个key获取多个String类型的value;

  • INCR:让一个整型的key自增1;

  • INCRBY:让一个整型的key自增并指定步长,例如:incrby num 2 让num值自增2;

  • INCRBYFLOAT:让一个浮点类型的数字自增并指定步长;

  • SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行;

  • SETEX:添加一个String类型的键值对,并且指定有效期;

3.2.1 String 操作命令实践

set/get

mset/mget

SETNX

SETEX

3.3 常用业务场景

String类型数据结构简单,使用灵活,基于上述列举的常用命令,结合实际业务经验,下面归纳几种常用的业务使用场景;

3.3.1 session共享

在互联网项目中,登录是必不可少的,如果你的项目是多节点部署,怎么实现用户登录的会话共享呢?一个常见的做法是,将登录产生的关键会话信息,比如token存储在redis中,而token往往就是一个加密的字符串,使用redis的String类型的数据结构就非常合适。

3.3.2 登录失败计数器

可以利用String自增的能力,对用户登录错误次数进行记录,具体来说如下实现思路和步骤如下:

  • 登录错误一次,调用incr命令执行一次,key可以为用户的ID,value即为incr的值;

  • 当连续登录错误的次数达到特定数字,限制登录;

  • 如果未达到一定数量,在下一次登录成功,清理当前的错误记录key;

  • 而后再重新开始累计;

下面给出该业务实现的伪代码,具体细节可以结合实际情况继续斟酌;

public String login(User user) {
        String userId = user.getId();
        if(!user.getUserName().equals("jerry") && !user.getPassWord().equals("123456")){
            if (redisTemplate.hasKey(userId)) {
                long failCount = (long)redisTemplate.opsForValue().get(userId);
                if(failCount < 3){
                    //如果密码不正确,登录失败,同时记录错误次数的值
                    redisTemplate.opsForValue().increment(userId,1);
                    return "登录失败";
                }
                return "登录失败错误次数超过3次,账户将会被锁定";
            }
            //首次登录失败
            redisTemplate.opsForValue().increment(userId,1);
            return "登录失败";
        }

        //TODO 执行登录业务 ...

        //如果登录失败的key值存在,则删除
        if(redisTemplate.opsForValue().get(user) != null){
            redisTemplate.delete(userId);
        }
        return "login success";
    }

3.3.3 限流

在某些场景下,如果系统识别到了某些IP或账户出现异常而频繁刷接口,针对这样的账户或IP可以进行限流,一个比较常见的场景就是发送短信验证码,为了避免用户恶意刷短信,通常的做法是限制1分钟内只允许发一次,大致的思路如下:

  • 第一次提交发送短信请求,后台给当前手机号推送一个验证码,同时后台以IP或用户ID为key,在redis中记录一条数据,并设定key的有效期为1分钟;

  • 用户输入账户信息以及正确的短信验证码提交请求到后台,验证通过,删除redis中的key;

  • 第二步提交请求时,如果验证码输入失败提交到后台,登录失败,此时用户再次点击发送验证码请求,后台检测到redis中存在当前用户ID的key存在,给出异常提示,一般为发送短信过于频繁;

  • 一分钟之后,redis中的key过期,用户则可以再次提交发送短信验证码请求;

如下为一段核心伪代码,有兴趣的同学可以对细节进行斟酌完善

public static final String VERIFY_CODE = "login:verify_code:";

    public String getSmsVerifyCode(String userId,String phone) {
        String smsVerifyCode = getSmsVerifyCode();
        String smsCodeKey = VERIFY_CODE + ":" +userId;
        Object existedSmsCode = redisTemplate.opsForValue().get(smsCodeKey);
        //如果验证码已经存在
        if (Objects.nonNull(existedSmsCode)) {
            Long expireTime = "从redis中获取当前key的过期时间";
            //剩余时间
            long lastTime = "总时间" - expireTime;
            //三分钟内验证码有效,1分钟到3分钟之间,用户可以继续输入验证码,也可以重新获取验证码,新的验证码将覆盖旧的
            if(lastTime > 60 && expireTime >0){
                //调用第三方平台发短信,只有短信发送成功了,才能将短信验证码保存到redis
                System.out.println("此处调用短信发送逻辑......");
                redisTemplate.opsForValue().set(smsCodeKey, smsVerifyCode, "总的过期时间", TimeUnit.SECONDS);
            }
            //一分钟之内不得多次获取验证码
            if(lastTime < 60){
                throw new RuntimeException("操作过于频繁,请一分钟之后再次点击发送");
            }
        }else {
            System.out.println("此处调用短信发送逻辑......");
            redisTemplate.opsForValue().set(smsCodeKey, smsVerifyCode, "总的过期时间", TimeUnit.SECONDS);
        }
        return smsVerifyCode;
    }

    /**
     * 随机获取6位短信数字验证码
     *
     * @return
     */
    public static String getSmsVerifyCode() {
        Random random = new Random();
        String code = "";
        for (int i = 0; i < 6; i++) {
            int rand = random.nextInt(10);
            code += rand;
        }
        return code;
    }

3.3.4 多线程安全控制

利用setnx 命令的原子性特点,在多线程并发场景下,作为一种锁进行使用。在redis的相关分布式锁解决方案或SDK中,其底层就利用了setnc的特性实现了锁机制。

四、Hash类型

4.1 hash 数据结构简介

Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。通俗来说,hash是一个键值对(key - value)集合,常用于存储对象数据,类似于Java中的Map<String,Object>。

可以采用这样的命名方式(hash格式):对象类别和ID构成键名,使用字段表示对象的属性,而字段值则存储属性值。

如下为hash类型存储数据的示例格式:

在redis中的存储格式如下: 

4.2 为什么使用hash结构

在使用redis做缓存时,使用String类型的结构尽管也能满足多数场景,甚至可以将对象进行json序列化之后以String类型格式存储,但是当程序中涉及到修改对象属性的操作时,使用String类型修改字段属性时就比较麻烦了。总结来说,使用hash可以帮助开发者解决如下问题:

  • 可以像Java那样存储对象数据,并能较方便的修改对象中的属性值;

  • hash结构可以针对对象中的每个字段独立存储,即针对单个字段进行增删改查;

  • 将具有同一类规则的数据放到redis中的一个数据容器里,便于查找数据;

  • 使用hash节省内存。在hash类型中,一个key可以对应多个多个field,一个field对应一个value。相较于每个字段都单独存储成string类型来说,更能节约内存。

4.3 hash 常用操作命令

hash常用的操作命令如下 :

HSET key field value:添加或者修改hash类型key的field的值;

HGET key field:获取一个hash类型key的field的值;

HMSET:批量添加多个hash类型key的field的值;

HMGET:批量获取多个hash类型key的field的值;

HGETALL:获取一个hash类型的key中的所有的field和value;

HKEYS:获取一个hash类型的key中的所有的field;

HVALS:获取一个hash类型的key中的所有的value;

HINCRBY:让一个hash类型key的字段值自增并指定步长;

HSETNX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行

4.3.1 常用命令操作演示

hset/hget

redis中存储的数据格式如下,这个与Java中的对象存储格式是不是很像;

通过命令获取上述添加的对象key;

HMSET/HMGET

使用该命令可以批量为一个对象的key存储多个属性值

 

HGETALL

获取一个hash类型的key中的所有的field和value

HKEYS/HVALS

类似于遍历map中的所有key和value

HINCRBY

对hash中的某个字段进行增长,类似于String类型中针对key的字段值自增,可以指定步长

4.4 hash常用业务场景

4.4.1 对象格式存储数据

在程序中对查询的对象数据进行缓存,尽管可以使用String结构对对象进行json序列化再存储,但是后续想要对某个属性进行修改的时候,并不是很方便,所以这种情况下可以考虑使用hash存储,其结构与Java的对象类似,修改属性值很方便。对应的代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SingleApp.class)
public class RedisHashTest {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    //单个设置
    @Test
    public void saveSingle(){
        redisTemplate.opsForHash().put("user:002","name","jike");
        redisTemplate.opsForHash().put("user:002","age",21);
        redisTemplate.opsForHash().put("user:002","city","广州");
        System.out.println("保存成功");
    }

    @Test
    public void getSingle(){
        Object id = redisTemplate.opsForHash().get("user:001", "id");
        Object name = redisTemplate.opsForHash().get("user:001", "name");
        System.out.println("id:" + id);
        System.out.println("name:"+ name);
    }

    //批量设置多个值
    @Test
    public void saveHashMulti(){
        Map<String,Object> userMap = new HashMap();
        userMap.put("id","001");
        userMap.put("name","jerry");
        userMap.put("age","19");
        redisTemplate.opsForHash().putAll("user:001",userMap);
        System.out.println("保存成功");
    }

    //获取多个值
    @Test
    public void getMultiVal(){
        List<Object> objects = redisTemplate.opsForHash().multiGet("user:001", Arrays.asList("id", "name"));
        objects.forEach(item ->{
            System.out.println(item);
        });
    }

}

对应到redis中的存储格式如下,是不是和对象存储很类似

4.4.2 缓存热点数据

针对下面一些场景,可以有选择的将高频访问并且字段值变动较小的热点数据存储在hash类型的结构中:

  • 用户信息:用户基本信息,如用户名、密码、邮箱、手机号码等常用字段;

  • 订单数据:订单基本信息,如订单号、下单时间、订单状态、配送地址等,在高并发抢购秒杀中可以考虑使用;

  • 商品信息:商品相关信息,如商品名称、价格、库存等;

  • 配置信息:应用配置信息,比如中间件的连接配置信息,缓存配置信息,网关配置信息等;

  • 统计信息:存储应用程序中计算之后的统计信息,比如网站某用户访问量、用户活跃度、用户购买次数、购买订单数量等;

4.4.3 计数功能

系统中在很多地方可能都会涉及到针对账户的某些行为的统计计数,比如某个账户访问电商网站中的商品详情的次数,下单的次数,购买的次数等等。redis的hash结构中提供了针对计数的功能,因此可以基于这个功能进行实现。伪代码如下:

//1、接口请求;
//2、取出redis中的hash对应的key;
//3、校验key是否存在;
//4、存在key,取出统计值对应的属性字段,执行incr的功能;
//5、返回最新的incr的数值进行展示

4.4.4 数据过滤

比如针对那些恶意刷请求的账户,一旦被系统捕捉到之后,可以将该账户信息存放在hash中,而后系统一旦再次捕捉到相同的账户恶意请求,将会被过滤或给出相关的风险告警。

4.4.5 电商购物车

比如在电商购物车中,为了加快页面的性能,可以将账户的购物车信息的关键数据放在hash结构中存储,如下为一个使用hash存储的购物车商品数据的格式和实现思路;

五、List 类型

5.1 list类型简介

List类型与Java中的LinkedList类似,可看做是一个双向链表的结构,既可以支持正向检索和也可以支持反向检索,使用的时候可以类比LinkedList使用。

5.2 list类型特点

  • list类型用来存储多个有序的字符串,列表当中每个字符看做一个元素,内部的数据可以重复;

  • 一个列表当中可以存储有一个或者多个元素,redis的list支持存储2^32次方-1个元素;

  • list中的元素有序,redis可以从列表的两端进行插入(pubsh)和弹出(pop)元素,支持读取指定范围的元素集,或者读取指定下标的元素等操作;

  • redis列表是一种比较灵活的链表数据结构,它可以充当队列或者栈的角色;

  • 由于其结构与链表类似,插入数据和删除数据较快,数据检索时性能一般;

5.3 list类型常用命令

5.3.1 list命令总结

LPUSH key element ...

向列表左侧插入一个或多个元素;

LPOP key

移除并返回列表左侧的第一个元素,没有则返回nil;

RPUSH key element ...

向列表右侧插入一个或多个元素

RPOP key

移除并返回列表右侧的第一个元素

LRANGE key star end

返回一段角标范围内的所有元素

BLPOP和BRPOP

与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil

5.3.2 操作实践

lpush

从展示的数据结构效果来看,使用lpush的效果如下:

rpush

这个正好与lpush相反的效果

LPOP  

从list列表中左边取出元素,以上述list1为例,从左到右的元素依次为:p5,p4,p3,p2,p1,设想到使用该命令时,p5元素将首先被取出,没有元素的时候将会返回 nil,RPOP与之相反;

LRANGE

取出指定下标范围的数据

BLPOP

类似于一个阻塞队列的效果,会阻塞等待从list中获取元素,没有元素返回nil,如下,使用BLPOP从list3中等待获取元素,显然list3目前并不存在,也没有元素,所以执行这个命令之后,可以看的阻塞的效果,第三个参数为阻塞等待的时间,即可以不用无限的等待下去;

此时在另一个客户端给list3添加一条元素,看到如下效果

5.4 list使用场景

5.4.1 实现常用的分布式数据结构

结合上述的操作实践,利用list的特点,可以实现如下常用几种数据结构

  • Stack(栈) : LPUSH + LPOP;

  • Queue(队列):LPUSH + RPOP;

  • Blocking MQ(阻塞队列) : LPUSH + BRPOP;

通过下面的代码以及输出结果可以清晰的看到栈的效果

    @Test
    public void saveList(){
        redisTemplate.opsForList().leftPush("user1","jerry");
        redisTemplate.opsForList().leftPush("user1","mike");
        redisTemplate.opsForList().leftPush("user1","hang");

        System.out.println("插入3个元素,分别为:jerry,mike,hang");
        System.out.println("============");
        List<Object> user1 = redisTemplate.opsForList().range("user1", 0, 3);
        user1.forEach(item ->{
            System.out.println("依次取出当前的元素:" + item);
        });
        System.out.println("============");

        Object user11 = redisTemplate.opsForList().leftPop("user1");
        Object user12 = redisTemplate.opsForList().leftPop("user1");
        Object user13 = redisTemplate.opsForList().leftPop("user1");
        System.out.println("取出的元素依次为:" + user11 + "," + user12 + "," + user13);
    }

5.4.2 抢购秒杀

了解抢购秒杀业务场景的同学应该对此不陌生,在针对某种具体的商品抢购正式开始之前,需要定好本次参与抢购的商品数量,如果抢购时直接操作数据库,这个性能是低效的,所以可以考虑使用redis的list结构,将参与抢购的商品ID列表提前初始化到list中,抢购的时候,商品直接从list中取出即可。主要实现思路如下:

1、参与抢购的商品打散放入list;

2、开始抢购时调用pop命令从list中取出;

3、将抢购成功的用户以及商品ID记录写入到数据库;

4、后续的其他业务处理;

下面是一段核心的关于抢购的代码,可供参考,第一段为将参与抢购的商品预存到list中,第二段为抢购的逻辑

    @Resource
    private RedisTemplate redisTemplate;

    @Scheduled(cron = "0/5 * * * * ?")
    public void startSecKill(){
        List<PromotionSecKill> list  = promotionSecKillMapper.findUnstartSecKill();
        for(PromotionSecKill ps : list){
            System.out.println(ps.getPsId() + "秒杀活动启动");
            //删掉以前重复的活动任务缓存
            redisTemplate.delete("seckill:count:" + ps.getPsId());
            /**
             * 有多少库存商品,则初始化几个list对象
             * 实际业务中,可能是拿出部分商品参与秒杀活动,通过后台的界面进行设置
             */
            for(int i = 0 ; i < ps.getPsCount() ; i++){
                redisTemplate.opsForList().rightPush("seckill:count:" + ps.getPsId() , ps.getGoodsId());
            }
            ps.setStatus(1);
        }
    }

抢购逻辑

    @Resource
    private RedisTemplate redisTemplate;

    public void processSecKill(Long psId, String userid, Integer num) throws SecKillException {
        PromotionSecKill ps = promotionSecKillMapper.findById(psId);
        if (ps == null) {
            throw new SecKillException("秒杀活动不存在");
        }
        if (ps.getStatus() == 0) {
            throw new SecKillException("秒杀活动未开始");
        } else if (ps.getStatus() == 2) {
            throw new SecKillException("秒杀活动已结束");
        }
        Integer goodsId = (Integer) redisTemplate.opsForList().leftPop("seckill:count:" + ps.getPsId());
        if (goodsId != null) {
            //判断是否已经抢购过
            boolean isExisted = redisTemplate.opsForSet().isMember("seckill:users:" + ps.getPsId(), userid);
            if (!isExisted) {
                System.out.println("抢到商品啦,快去下单吧");
                redisTemplate.opsForSet().add("seckill:users:" + ps.getPsId(), userid);
            }else{
                redisTemplate.opsForList().rightPush("seckill:count:" + ps.getPsId(), ps.getGoodsId());
                throw new SecKillException("抱歉,您已经参加过此活动,请勿重复抢购!");
            }
        } else {
            throw new SecKillException("抱歉,该商品已被抢光,下次再来吧!");
        }
    }

5.4.3 消息队列

利用redis的BLPOP的特性,可以实现消息队列,生产者将消息推送到list中,而消费者通过BLOP阻塞式的从;list中获取消息进行消费。

5.4.4 排行榜

利用list的数据有序性以及支持索引访问元素的特性,提前将排好序的元素存放到list中,比如玩家的积分,个人学习时长等,然后取出的数据就可以从高到低排序实现一个排行榜的效果。

5.4.5 分页查询效果

基于list支持索引访问元素,以及可以通过range访问元素的特点,可以实现数据的分页,当然这样的数据通常是高频查询的热点数据的ID或唯一性元素。

5.4.6 流量削峰

在业务高峰期,如果系统的服务器处理请求的能力有限,可以考虑将请求全部放到list中,然后开启多个线程来处理后续请求,以减轻服务器压力,可以用来处理一些高并发场景。

六、Set 类型

6.1 Set简介

redis集合(set)类型和list列表类型类似,都可用来存储多个字符串元素的集合。但是和list不同的是set集合当中不允许重复的元素。而且set集合当中元素是没有顺序的,不存在元素下标。

set类型是使用哈希表构造的,因此复杂度是O(1),它支持集合内的增删改查,并且支持多个集合间的交集、并集、差集操作。与Java中的set可以对比理解使用,可以利用这些集合操作,解决开发过程很多数据集合间的问题。

6.2 Set 类型常用命令

Set常用的操作命令总结如下:

  • SADD key member ... :向set中添加一个或多个元素;

  • SREM key member ... : 移除set中的指定元素;

  • SCARD key: 返回set中元素的个数;

  • SISMEMBER key member:判断一个元素是否存在于set中;

  • SMEMBERS:获取set中的所有元素;

  • SINTER key1 key2 ... :求key1与key2的交集;

  • SDIFF key1 key2 ... :求key1与key2的差集;

  • SUNION key1 key2 ..:求key1和key2的并集;

6.3 Set命令操作使用

6.3.1 常用命令操作演示

sadd/smembers

添加元素并查看元素

可以看到,set中存储元素是无序的

SREM

删除set中的元素

SCARD

返回元素个数

SISMEMBER

判断某个元素是否在set中

SINTER key1 key2

求两个set的交集

6.3.2 核心API

如下是使用redisTemplate操作set的相关API

    @Test
    public void opeSet(){
        redisTemplate.opsForSet().add("set1","a","b","c","d","e");
        redisTemplate.opsForSet().add("set2","d","e","f","j");

        Set<Object> set1 = redisTemplate.opsForSet().members("set1");
        set1.forEach(item ->{
            System.out.println("item :" + item);
        });

        //是否set的元素
        Boolean member = redisTemplate.opsForSet().isMember("set1", "a");
        System.out.println(member);

        //求交集
        Set<Object> difference = redisTemplate.opsForSet().difference("set1", "set2");
        difference.forEach(item ->{
            System.out.println(item);
        });

    }

6.4 Set使用场景

6.4.1 用户关注、推荐模型

比如在社交类网站中,我的关注、我的粉丝,类似这样的操作使用Set就是不错的选择。

user1 : 我的粉丝:

sadd user1:fans user2 sadd user1:fans user4 sadd user1:fans user5 ...

user2 : 我关注的人:

sadd user2:follow user1 sadd user2:follow user3 sadd user2:follow user5 sadd user2:follow user7 ...

基于上述的数据,可以分别求 user1 和 user2 可能认识的人,这就是粉丝或关注推荐

6.4.2 商品/用户画像标签

在电商或团购类网站中,经常看到某些商品的详情下面,被打上了很多标签,比如某款水果类商品,口感好,新鲜,发货快等等,可以考虑使用set来保存标签信息;

sadd tags:productId "口感好"

sadd tags:productId "新鲜"

sadd tags:productId "发货快

6.4.3 抽奖

利用set中的 SRANDMEMBER 结合 SPOP 的操作,可以实现抽奖的功能,即从set集合中每次随机得到指定数量的元素,实现思路如下:

sadd random user1 user2 user3 user4 user5 user6 user7 user8 user9

srandmember random 2 #随机取出指定数量的元素,不删除

spop random 2 #随机取出指定数量的元素,删除元素

抽完一次时,被抽过的人还可以继续参与抽奖,使用srandmember ;

抽完一次时,被抽过的人从set中剔除,不能继续参与抽奖,使用spop ;

6.4.4 点赞、收藏、喜欢数

在某些社交app、微博或短视频类app中,经常看到的那些点赞、收藏或喜欢的功能,可以基于set来实现,实现思路如下:

点赞

sadd like:视频ID {userId}

取消点赞

srem like:视频ID {userId}

用户是否点赞

sismember like:视频ID {userId}

点赞的用户列表

smembers like:视频ID

获取点赞的用户数量

scard like:视频ID

6.4.5 统计网站的独立IP

利用set集合当中元素的唯一性,可以快速实时统计访问网站的独立IP。

七、SortedSet

7.1 SortedSet概述

SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可基于score属性对元素做排序,其底层的实现是一个跳表(SkipList)加 hash表。

有序集合可以利用分数进行从小到大的排序。虽然有序集合的成员是唯一的,但是分数(score)却可以重复。就比如在一个班中,学生的学号是唯一的,但是每科成绩却是可以一样的,redis可以利用有序集合存储学生成绩快速做成绩排名功能。

7.1.1 SortedSet特点

SortedSet具备如下特性:

  • 可排序;

  • 元素不重复;

  • 查询速度快;

7.2 常用操作命令

SortedSet常用操作命令如下:

  • ZADD key score member:添加一个或多个元素到sorted set ,如果已存在则更新其score值;

  • ZREM key member:删除sorted set中的一个指定元素;

  • ZSCORE key member : 获取sorted set中的指定元素的score值;

  • ZRANK key member:获取sorted set 中的指定元素的排名;

  • ZCARD key:获取sorted set中的元素个数;

  • ZCOUNT key min max:统计score值在给定范围内的所有元素的个数;

  • ZINCRBY key increment member:让sorted set中的指定元素自增,步长为指定的increment值;

  • ZRANGE key min max:按照score排序后,获取指定排名范围内的元素;

  • ZRANGEBYSCORE key min max:按照score排序后,获取指定score范围内的元素;

  • ZDIFF、ZINTER、ZUNION:求差集、交集、并集;

注意:默认情况下使用SortedSet时,集合中元素排名都是升序,如要降序则在命令后添加REV;

7.2 .1 操作命令演示

ZADD

添加一个或多个元素到SortedSet

从添加的数据来看,SortedSet中的元素是有序的,即按照分数进行了升序排序

ZSCORE key member

获取指定元素的score值

ZRANK key member

获取指定元素的排名

7.2 .2 核心操作API

如下列举了常用的API操作,当然远不止这些,可以在此基础上继续操作实践

    @Test
    public void opeZSet(){
        //添加元素
        redisTemplate.opsForZSet().add("urank","user1",99);
        redisTemplate.opsForZSet().add("urank","user2",88);
        redisTemplate.opsForZSet().add("urank","user3",95);
        redisTemplate.opsForZSet().add("urank","user4",79);
        redisTemplate.opsForZSet().add("urank","user5",82);

        //获取某个元素的分值
        Double score = redisTemplate.opsForZSet().score("urank", "user1");
        System.out.println(score);

        //统计排名0~3的元素
        Set<Object> users = redisTemplate.opsForZSet().range("urank", 0, 3);
        users.forEach(item ->{
            System.out.println(item);
        });

        //获取某元素的排名
        Long rank = redisTemplate.opsForZSet().rank("urank", "user5");
        System.out.println(rank);

    }

7.3 SortedSet使用场景

7.3.1 排行榜(TOP N)

在很多网站都有排行的功能,比如新闻类网站可以按照阅读数或点赞量最高的排到最前面,游戏网站中根据个人玩家积分进行排名等,类似的场景还有很多,都可以基于SortedSet中的分数来实现。

7.3.2 带权重的消息队列

使用SortedSet可以实现一个带权重的消息队列,具体来说,将不同的消息赋予不同的权重存入SortedSet,消费消息时,可以优先获取权重高的消息进行处理;

7.3.3 滑动窗口限流

将score作为时间戳,可统计最近一段时间内的成员数量,实现滑动窗口限流。

7.3.4 精准设定过期时间的数据

可以把sorted set中score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis中的过期数据,你完全可以把 Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。

八、写在文末

关于redis的常用数据类型,可以说在日常的工作中随处可见,系统学习并深入掌握这些不同数据结构的使用,在面对复杂多变的业务场景时可以更好的为开发人员提供高效的解决方案,加快问题的解决效率,同时也是作为一个优秀的开发工程师必备的技能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/848715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++笔记】C++启航之为C语言填坑的语法

【C笔记】C启航之为C语言填坑的语法 一、命名空间1、为什么要引入命名空间&#xff1f;2、命名空间的基本用法3、展开命名空间4、命名空间的套娃5、命名空间的自动合并 二、缺省参数1、为什么要引入缺省参数&#xff1f;2、缺省参数的基本用法3、缺省的参数必须从右向左4、缺省…

为什么我们需要加快推进数字孪生技术?

数字孪生技术以其强大的潜力和应用前景&#xff0c;引起了各行各业的广泛关注和热切期待。那么&#xff0c;究竟为什么要加快推进数字孪生技术呢&#xff1f; 首先&#xff0c;数字孪生技术能够实现现实世界与虚拟世界的无缝连接&#xff0c;为各行业带来了前所未有的创新机遇…

Vue电商项目--VUE插件的使用及原理

图片懒加载 图片懒加载&#xff0c;就是图片延迟加载。只加载页面可视区域上的图片&#xff0c;等滚动到页面下面时&#xff0c;再加载对应视口上的图片 而在vue中有一个插件 vue-lazyload - npm (npmjs.com) npm i vue-lazyload 去使用他&#xff0c;这里我们引入了一张图片…

(九)人工智能应用--深度学习原理与实战--前馈神经网络实现MNST手写数字识别

目标: 识别手写体的数字,如图所示: 学习内容: 1、掌握MNIST数据集的加载和查看方法 2、熟练掌握Keras建立前馈神经网络的步骤【重点】 3、掌握模型的编译及拟合方法的使用,理解参数含义【重点】 4、掌握模型的评估方法 5、掌握模型的预测方法 6、掌握自定义图片的处理与预测 …

十分钟掌握使用 SolidJS 构建全栈 CRUD 应用程序

我们可以开始讨论 SolidJS&#xff0c;说它比React更好&#xff0c;但没有必要做这种比较。SolidJS只是众多前端框架之一&#xff0c;旨在在Web上快速创建数据驱动。那么&#xff0c;我们为什么要突出这个新孩子呢&#xff1f; 首先&#xff0c;我们不能忽视SolidJS不使用虚拟…

嗅探抓包工具,解决线上偶现问题来不及抓包的情况阅读目录

目录 背景 实现思路 具体实现 Python 抓包 总结 资料获取方法 背景 测试群里经常看到客户端的同学反馈发现了偶现Bug&#xff0c;但是来不及抓包&#xff0c;最后不了了之&#xff0c;最近出现得比较频繁&#xff0c;所以写个小脚本解决这个问题。 实现思路 之前写过一个…

免费实用的日记应用:Day One for Mac中文版

Day One for Mac是一款运行在Mac平台上的日记软件&#xff0c;你可以使用Day One for mac通过快速菜单栏条目、提醒系统和鼓舞人心的信息来编写更多内容&#xff0c;day one mac版还支持Dropbox同步功能&#xff0c;想要day one mac中文免费版的朋友赶紧来试试吧&#xff01; …

IPC之一:使用匿名管道进行父子进程间通信的例子

IPC 是 Linux 编程中一个重要的概念&#xff0c;IPC 有多种方式&#xff0c;本文主要介绍匿名管道(又称管道、半双工管道)&#xff0c;尽管很多人在编程中使用过管道&#xff0c;但一些特殊的用法还是鲜有文章涉及&#xff0c;本文给出了多个具体的实例&#xff0c;每个实例均附…

Leetcode.1316 不同的循环子字符串

题目链接 Leetcode.1316 不同的循环子字符串 rating : 1837 题目描述 给你一个字符串 text &#xff0c;请你返回满足下述条件的 不同 非空子字符串的数目&#xff1a; 可以写成某个字符串与其自身相连接的形式&#xff08;即&#xff0c;可以写为 a a&#xff0c;其中 a 是…

服务器感染了LockBit 3.0勒索病毒,如何确保数据文件完整恢复?

引言&#xff1a; 在数字时代&#xff0c;恶意软件的威胁变得愈发严峻&#xff0c;而LockBit 3.0勒索病毒则是其中的顶尖恶势力之一。其先进的加密技术和毫不留情的勒索手段&#xff0c;使无数人蒙受损失。然而&#xff0c;我们不应束手无策。本文91数据恢复将带您深入了解Loc…

AndroidStudio通过Profiler查找内存泄漏

Fragment内存泄漏&#xff1a; AndroidStudio --> Profiler --> 勾选 show nearest Gc root only&#xff0c;然后查看非weakreference的引用&#xff08;weakreference是不会导致内存泄漏的&#xff09;&#xff0c;往下就能找自己项目里写的代码&#xff0c;一般此处…

旷视科技AIoT软硬一体化走向深处,生态和大模型成为“两翼”?

齐奏AI交响曲的当下&#xff0c;赛道玩家各自精彩。其中&#xff0c;被称作AI四小龙的商汤科技、云从科技、依图科技、旷视科技已成长为业内标杆&#xff0c;并积极追赶新浪潮。无论是涌向二级市场还是布局最新风口大模型&#xff0c;AI四小龙谁都不甘其后。 以深耕AIoT软硬一…

ASCP系列电气防火限流式保护器在养老院的应用-安科瑞黄安南

摘要&#xff1a;2020年&#xff0c;我国65岁及以上老年人口数量为1.91亿&#xff0c;老龄化率达到13.5%。总体来看&#xff0c;大部分省市的养老机构数量还较少。养老设施的建设与民生息息相关&#xff0c;养老院的电气安全也非常重要。如果发生电气火灾&#xff0c;对于行动不…

【多模态】24、开放词汇学习到底是什么?

文章目录 一、什么是开放词汇学习二、开放词汇学习的测评和数据集三、开放词汇目标检测3.1 Region-Aware Training3.2 Pseudo-Labeling3.3 Knowledge Distillation-Based3.4 Transfer Learning-Based3.5 总结3.6 效果 参考论文&#xff1a;A Survey on Open-Vocabulary Detecti…

Vue3 事件处理简单应用

去官网学习→事件处理 | Vue.js 运行示例&#xff1a; 代码&#xff1a;HelloWorld.vue <template><div class"hello"><h1>Vue 事件处理</h1><button v-on:click"numb 1">点击加1-----{{ numb }}</button><br/&…

独家揭秘Linux内核栈:内核态的奇妙之处和与用户态的差异

理解Linux内核栈可以从以下几个方面来考虑&#xff1a;内核态与用户态&#xff1a;在阅读Linux内核及相关资料时&#xff0c;需要明确它所描述的是内核态还是用户态的内容。这有助于理解所讨论的是在哪个执行环境下进行的操作。进程与线程的描述&#xff1a;用户态的进程和线程…

Yield Guild Games:社区更新 — 2023 年第二季度

本文重点介绍了 Yield Guild Games (YGG) 2023 年第二季度社区更新中涵盖的关键主题&#xff0c;包括公会发展计划 (GAP) 第 3 季的总结、YGG 领导团队的新成员以及 YGG 的最新消息地区公会网络和广泛的游戏合作伙伴生态系统。 在 YGG 品牌焕然一新的基础上&#xff0c;第二季…

ArcGIS Pro基础:【按顺序编号】工具实现属性字段的编号自动赋值

本次介绍一个字段的自动排序编号赋值工具&#xff0c;基于arcgis 的字段计算器工具也可以实现类似功能&#xff0c;但是需要自己写一段代码实现&#xff0c; 相对而言不是很方便。 如下所示&#xff0c;该工具就是【编辑】下的【属性】下的【按顺序编号】工具。 其操作方法是…

Openlayers实战:右键点击,弹出feature信息

鼠标作为一个重要的交互触发手段,不但有左点击,还有右点击。 Openlayers开发的项目中,我们取消鼠标右键默认菜单,右击后获取到的feature的信息值。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhuanlan),还是大剑师兰特(CSDN) * @此源代码版权归大剑师兰特所有,可…

Spring系列四:AOP切面编程

文章目录 &#x1f497;AOP-官方文档&#x1f35d;AOP 讲解&#x1f35d;AOP APIs &#x1f497;动态代理&#x1f35d;初始动态代理&#x1f35d;动态代理深入&#x1f35d;AOP问题提出&#x1f4d7;使用土方法解决&#x1f4d7; 对土方法解耦-开发最简单的AOP类&#x1f4d7;…