综合中间件Redisson(二)常见功能组件实战


前面已经Springboot整合了Redisson的项目,下面将以 布隆过滤器发布-订阅主题特有的延迟队列

布隆过滤器

前面已经说过了,布隆过滤器的底层算法主要有两大部分
第一部分
精心设计并构造K个哈希函数,以及构造长度为N的位数组,设置初始值为0
第二部分
判断一个元素是否在集合中,同样把这个元素经过K个哈希函数,得出K个哈希值,判断对应的下标是否都为1,如果是,则元素大概率是存在的,如果不是,就一定不在集合中。
在这里插入图片描述
好在Redisson提供了布隆过滤器组件,开发者可以直接拿来就用(开箱即用)
这里我们以实际环境中的典型业务场景,“判断一个元素是否存在于一个大数据量的集合中”,这个集合用布隆过滤器构造一个10万或者100万的集合,然后分别查询指定的元素是否在该集合中,将检测结果输出。

基本数据类型过滤

    @Test
    public void test2() throws Exception {
        // 定义缓存中的key
        String key = "myBloomFilterData";
        // 定义一千万的数据
        Long total = 1_000_0L;
        // 创建布隆过滤器
        RBloomFilter<Integer> rBloomFilter = redissonClient.getBloomFilter(key);
        rBloomFilter.tryInit(total,0.01F);
        for (int i = 0; i < total; i++) {
             rBloomFilter.add(i);
        }

        // 开始测试特定的元素是否在布隆过滤器中
        log.info("布隆过滤器是否包含1:{}",rBloomFilter.contains(1));
        log.info("布隆过滤器是否包含-11:{}",rBloomFilter.contains(-11));
        log.info("布隆过滤器是否包含100000000:{}",rBloomFilter.contains(100000000));
        log.info("布隆过滤器是否包含10:{}",rBloomFilter.contains(10));
    }

引用型数据过滤

package com.learn.boot.dto;


import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

import java.io.Serializable;

@Data
@ToString
@EqualsAndHashCode
public class BloomDTO  implements Serializable {
    private Integer id; //id
    private String msg; //描述信息
    //空构造方法
    public BloomDTO() {
    }
    //包含所有字段的构造方法
    public BloomDTO (Integer id, String msg) {
        this.id = id;
        this.msg = msg;
    }
}



    @RequestMapping(value = "/rediss",method = RequestMethod.POST)
    public void test3() throws Exception {
        // 定义Redis中的Key
        final String key="myBloomFilterDataV4";
        // 创建布隆过滤器组件
        RBloomFilter<BloomDTO> bloomFilter=redissonClient.getBloomFilter(key);
        // 初始化布隆过滤器,预计统计元素数量为1000,期望误差率为0.01
        bloomFilter.tryInit(1000, 0.01);
        BloomDTO dto1=new BloomDTO(1,"1");
        BloomDTO dto2=new BloomDTO(10,"10");
        BloomDTO dto3=new BloomDTO(100,"100");
        BloomDTO dto4=new BloomDTO(1000,"1000");
        BloomDTO dto5=new BloomDTO(10000,"10000");
        bloomFilter.add(dto1);
        bloomFilter.add(dto2);
        bloomFilter.add(dto3);
        bloomFilter.add(dto4);
        bloomFilter.add(dto5);
        bloomFilter.add(dto5);

        // 开始测试特定的元素是否在布隆过滤器中
        log.info("该布隆过滤器是否包含数据(1,\"2\"):{}",bloomFilter.contains (new BloomDTO(1,"a")));
        log.info("该布隆过滤器是否包含数据(2,\"2\"):{}",bloomFilter.contains(new BloomDTO(2,"b")));
        log.info("该布隆过滤器是否包含数据(3,\"3\"):{}",bloomFilter.contains(new BloomDTO(3,"c")));
        log.info("该布隆过滤器是否包含数据(4,\"2\"):{}",bloomFilter.contains(new BloomDTO(4,"d")));
    }

发布订阅主题

前面已经说了是以主题作为消息传递对象的
在这里插入图片描述
下面我们用之前的场景,用户登录成功写日志

数据库设计

CREATE TABLE 'sys_log' (
 'id' int(11) NOT NULL AUTO_INCREMENT,
 'user_id' int(11) DEFAULT '0' COMMENT '用户id',
 'module' varchar(255) DEFAULT NULL COMMENT '所属操作模块',
 'data' varchar(5000) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '操作数据',
 'memo' varchar(500) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '备注',
 'create_time' datetime DEFAULT NULL COMMENT '创建时间',
 PRIMARY KEY ('id')
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='日志记录表';

代码实现

package com.learn.boot.controller;

import com.learn.boot.dto.BloomDTO;
import com.learn.boot.dto.UserLoginDto;
import com.learn.boot.model.SysLog;
import com.learn.boot.resultVo.ResultVo;
import com.learn.boot.service.SysLogService;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.redisson.api.listener.MessageListener;
import org.redisson.client.codec.StringCodec;
import org.redisson.codec.SerializationCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.Date;

@RestController
public class RedissonController {
    private static final Logger log = LoggerFactory.getLogger(RedissonController.class);
    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private SysLogService sysLogService;

    @RequestMapping(value = "/rediss",method = RequestMethod.POST)
    public void test3() throws Exception {
        // 定义Redis中的Key
        final String key="myBloomFilterDataV4";
        // 创建布隆过滤器组件
        RBloomFilter<BloomDTO> bloomFilter = redissonClient.getBloomFilter(key);
        // 初始化布隆过滤器,预计统计元素数量为1000,期望误差率为0.01
        bloomFilter.tryInit(1000, 0.01);
        BloomDTO dto1 = new BloomDTO(1,"1");
        BloomDTO dto2 = new BloomDTO(10,"10");
        BloomDTO dto3 = new BloomDTO(100,"100");
        BloomDTO dto4 = new BloomDTO(1000,"1000");
        BloomDTO dto5 = new BloomDTO(10000,"10000");
        bloomFilter.add(dto1);
        bloomFilter.add(dto2);
        bloomFilter.add(dto3);
        bloomFilter.add(dto4);
        bloomFilter.add(dto5);
        bloomFilter.add(dto5);

        // 开始测试特定的元素是否在布隆过滤器中
        log.info("该布隆过滤器是否包含数据(1,\"2\"):{}",bloomFilter.contains (new BloomDTO(1,"a")));
        log.info("该布隆过滤器是否包含数据(2,\"2\"):{}",bloomFilter.contains(new BloomDTO(2,"b")));
        log.info("该布隆过滤器是否包含数据(3,\"3\"):{}",bloomFilter.contains(new BloomDTO(3,"c")));
        log.info("该布隆过滤器是否包含数据(4,\"2\"):{}",bloomFilter.contains(new BloomDTO(4,"d")));
    }

    @RequestMapping(value = "/redissLog",method = RequestMethod.POST)
    public ResultVo sendMsgForRedisson(@RequestBody UserLoginDto dto) {
        try {
            String key = "redisson:sendTopic";
            // 这里不去校验用户的真实性
            log.info("用户登录成功,发送主题");
            // 根据key从Redisson拿到RTopic,RTopic去异步发送主题
            RTopic rTopic = redissonClient.getTopic(key,new SerializationCodec());
            rTopic.publish(dto);
            return ResultVo.success("登录成功");
        }catch (Exception ex) {
            log.info("异常",ex);
            return null;
        }
    }


}

package com.learn.boot.config;

import com.learn.boot.dto.UserLoginDto;
import com.learn.boot.model.SysLog;
import com.learn.boot.service.SysLogService;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.redisson.api.listener.MessageListener;
import org.redisson.codec.SerializationCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Date;

@Component
@Order(0)
public class UserLoginSubscriber implements ApplicationRunner {
    private static final Logger log = LoggerFactory.getLogger(UserLoginSubscriber.class);

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private SysLogService sysLogService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 初始化监听主题
        try {
            RTopic rTopic = redissonClient.getTopic("redisson:sendTopic",new SerializationCodec());
            rTopic.addListener(UserLoginDto.class, new MessageListener<UserLoginDto>() {
                @Override
                public void onMessage(CharSequence charSequence, UserLoginDto userLoginDto) {
                    log.info("监听到用户登录成功后的消息");
                    if (userLoginDto != null) {
                        SysLog sysLog = new SysLog();
                        sysLog.setUserId(userLoginDto.getUserId());
                        sysLog.setCreateTime(new Date());
                        sysLog.setModule("用户登录");
                        sysLog.setData("用户登录成功");
                        try {
                            sysLogService.recordLog(sysLog);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
        }catch (Exception ex) {
            log.error("订阅消息异常",ex);
        }
    }
}

数据类型映射之Map

在Redisson中Map被称为RMap,这个组件实现了Java对象中的ConcurrentMap接口和Map接口,这两个接口实现的功能RMap都能实现,并且自身也实现了丰富的Redisson特有的操作方法
添加元素

    @Test
    public void testAdd() {
        String key = "redissonRmap";

        RMapDto rMapDto1 = new RMapDto(1,"a");
        RMapDto rMapDto2 = new RMapDto(2,"a");
        RMapDto rMapDto3 = new RMapDto(3,"a");
        RMapDto rMapDto4 = new RMapDto(4,"a");
        RMapDto rMapDto5 = new RMapDto(5,"a");
        RMapDto rMapDto6 = new RMapDto(6,"a");
        RMapDto rMapDto7 = new RMapDto(7,"a");
        RMapDto rMapDto8 = new RMapDto(8,"a");
        RMap<String,Object> rMap = redissonClient.getMap(key);
        // 正常添加元素
        rMap.put(String.valueOf(rMapDto1.getId()),rMapDto1);

        // 异步的方式添加元素
        rMap.putAsync(String.valueOf(rMapDto2.getId()),rMapDto2);

        // 添加元素之前判断元素是否存在,如果不存在才添加,否则不添加
        rMap.putIfAbsent(String.valueOf(rMapDto3.getId()),rMapDto3);

        // 添加元素之前判断元素是否存在,如果不存在才添加,否则不添加(异步)
        rMap.putIfAbsentAsync(String.valueOf(rMapDto4.getId()),rMapDto4);

        // 以下是快速的方式
        // 快速添加元素
        rMap.fastPut(String.valueOf(rMapDto5.getId()),rMapDto5);

        // 快速异步的方式添加
        rMap.fastPutAsync(String.valueOf(rMapDto6.getId()),rMapDto6);

        // 快速添加方式(判断是否存在)
        rMap.fastPutIfAbsent(String.valueOf(rMapDto7.getId()),rMapDto7);

        // 快速添加元素并且判断是否存在(异步)
        rMap.fastPutIfAbsentAsync(String.valueOf(rMapDto8.getId()),rMapDto8);

        log.info("--------------Rmap新增完成");
    }

在这里插入图片描述
获取元素和删除元素操作


    @Test
    public void testRedissonGet() {
        log.info("从映射数据类型map中获取元素----");
        String key = "redissonRmap";

        // 从redis中获取map
        RMap<String,Object> rMap = redissonClient.getMap(key);

        // 获取所有的key集合
        Set<String> ids = rMap.keySet();
        Map<String,Object> map = rMap.getAll(ids);
        log.info("元素列表:{}",map);

        // 删除指定元素id
        String removeId = "1";
        rMap.remove(removeId);
        // 重新赋值为Map
        map = rMap.getAll(rMap.keySet());

        // 删除指定的元素ID集合
        String[] removeIds = new String[]{"2","3","4"};
        rMap.fastRemove(removeIds);
        map = rMap.getAll(rMap.keySet());
        log.info("移除{}后的元素集合为{}",removeIds,map);
    }

开源中间件redisson还为RMap提供了一系列不同功能特性的数据类型,这些数据类型按照特性分成三大类
1.元素淘汰:允许针对一个映射中的每个元素单独设定有效时间和最常闲置时间。
2.本地缓存:指的是可以将部分数据保存到本地,从而将数据提取数据提高最多45倍。所有本地同名本地缓存公用 一个发布-订阅话题,所有更新和过期消息都通过该话题共享。仅适用于Redis的集群环境,该映射结构也叫集群分布式映射
3.数据分片:可以使得单一的映射结构突破Redis自身的容量限制,让其可以随着容量增大而增大,并且在扩容的同时读写性能和元素淘汰的处理能力随之呈线性增长
在这里插入图片描述

元素淘汰

    @Test
    public void testRMapCache() throws Exception {
        // 定义储存与缓存中间件的Redis的Key
        String key = "myRedissonMapCache";
        // 获取RMapCache
        RMapCache<Integer, RMapDto> rMapCache = redissonClient.getMapCache(key);
        // 构造实例
        RMapDto rMapDto1 = new RMapDto(1, "a");
        RMapDto rMapDto2 = new RMapDto(2, "a");
        RMapDto rMapDto3 = new RMapDto(3, "a");
        RMapDto rMapDto4 = new RMapDto(4, "a");
        RMapDto rMapDto5 = new RMapDto(5, "a");
        RMapDto rMapDto6 = new RMapDto(6, "a");
        RMapDto rMapDto7 = new RMapDto(7, "a");
        RMapDto rMapDto8 = new RMapDto(8, "a");
        // 元素淘汰
        rMapCache.putIfAbsent(rMapDto1.getId(), rMapDto1);
        // 设置有效时间10秒
        rMapCache.putIfAbsent(rMapDto2.getId(), rMapDto2, 20, TimeUnit.SECONDS);
        // 设置有效时间20秒
        rMapCache.putIfAbsent(rMapDto3.getId(), rMapDto3, 10, TimeUnit.SECONDS);
        Set<Integer> keySet = rMapCache.keySet();
        Map<Integer, RMapDto> map = rMapCache.getAll(keySet);

        // 等待5秒查看数据元素
        Thread.sleep(5000L);
        map = rMapCache.getAll(rMapCache.keySet());
        log.info("等待5秒元素为{}", map);

        // 等待15miao秒查看映射元素
        Thread.sleep(15000L);
        map = rMapCache.getAll(rMapCache.keySet());
        log.info("等待15秒元素为{}", map);
    }

本地缓存

    /**
     * 采用分组的 形式使用 本地缓存
     * @throws Exception
     */
    @Test
    public void testRLocalCachedMap() throws Exception {
        // 定义储存与缓存中间件的Redis的Key
        String key = "myRedissonRLocalCachedMap";
        RMapDto rMapDto1 = new RMapDto(1, "a");
        RMapDto rMapDto2 = new RMapDto(1, "b");
        RMapDto rMapDto3 = new RMapDto(1, "c");
        RMapDto rMapDto4 = new RMapDto(4, "d");
        RMapDto rMapDto5 = new RMapDto(5, "e");
        RMapDto rMapDto6 = new RMapDto(6, "f");
        RMapDto rMapDto7 = new RMapDto(7, "g");
        RMapDto rMapDto8 = new RMapDto(8, "h");
        List<RMapDto> list = new ArrayList<>(16);
        list.add(rMapDto1);
        list.add(rMapDto2);
        list.add(rMapDto3);
        list.add(rMapDto4);
        list.add(rMapDto5);
        list.add(rMapDto6);
        list.add(rMapDto7);
        list.add(rMapDto8);
        Map<Integer, List<RMapDto>> map = list.stream().collect(Collectors.groupingBy(RMapDto::getId));
        // 获取RMapCache
        RLocalCachedMap<Integer, List<RMapDto>> rLocalCachedMap = redissonClient.getLocalCachedMap(key,LocalCachedMapOptions.defaults());
        // 本地缓存加入分组结果
        log.info("分组结果加入本地缓存");
        rLocalCachedMap.putAll(map);
        // 再从本地缓存里边取数据
        List<RMapDto> result = rLocalCachedMap.get(1);
        log.info("取出id为1的所有元素",result);
    }

**剩下的数据分片,可以自行去官网查找 **

数据类型集合之Set

Redisson的功能组件RSet则主要是实现了Java中的Set,该功能组件可以保证集合中的每一个元素不重复,并且还提供了相应的有序集合SortedSet,计分功能排序组件ScoredSortedSet以及字典排序集合功能组件LexSortedSet等
SortedSet

    @Test
    public void testSortedSet() {
        String key = "myRedissonSortedSet";

        //创建对象实例
        RSetDTO dto1 = new RSetDTO(1,"N1",20,10.0D);
        RSetDTO dto2 = new RSetDTO(2,"N2",18,2.0D);
        RSetDTO dto3 = new RSetDTO(3,"N3",21,8.0D);
        RSetDTO dto4 = new RSetDTO(4,"N4",19,6.0D);
        RSetDTO dto5 = new RSetDTO(5,"N5",22,1.0D);
        // 获取有序集合实例
        RSortedSet<RSetDTO> setDTOS = redissonClient.getSortedSet(key);
        // 设置排序规则
        setDTOS.trySetComparator(new RSetComparator());
        // 将对象元素添加到集合中
        setDTOS.add(dto1);
        setDTOS.add(dto2);
        setDTOS.add(dto3);
        setDTOS.add(dto4);
        setDTOS.add(dto5);
        //查看此时有序集合的元素列表
        Collection<RSetDTO> result =  setDTOS.readAll();
        log.info("此时有序集合的元素列表:{} ",result);

    }
package com.learn.boot.comparator;

import com.learn.boot.dto.RSetDTO;

import java.util.Comparator;

/**
 *  //集合RSet数据组件的自定排序
 * @author Administrator
 */

public class RSetComparator implements Comparator<RSetDTO> {
    /**
     * 自定义排序的逻辑
     * @param o1 待比较的数据元素1
     * @param o2 待比较的数据元素2
     */
    @Override
    public int compare(RSetDTO o1, RSetDTO o2) {
        //表示后添加的数据元素如果age更大,则排得越前
        return o2.getAge().compareTo(o1.getAge());
    }
}


    @Test
    public void testSortedSetScoredSortedSet() {
        String key = "myRedissonScoredSortedSet";
        // 创建对象实例
        RSetDTO dto4 = new RSetDTO(1,"N1",10.0D);
        RSetDTO dto3 = new RSetDTO(2,"N2",2.0D);
        RSetDTO dto2 = new RSetDTO(3,"N3",8.0D);
        RSetDTO dto1 = new RSetDTO(4,"N4",6.0D);
        // 定义得分排序集合ScoredSortedSet实例
        RScoredSortedSet<RSetDTO> rScoredSortedSet = redissonClient.getScoredSortedSet(key);
        // 往得分排序集合ScoredSortedSet添加对象元素
        rScoredSortedSet.add(dto1.getScore(),dto1);
        rScoredSortedSet.add(dto2.getScore(),dto2);
        rScoredSortedSet.add(dto3.getScore(),dto3);
        rScoredSortedSet.add(dto4.getScore(),dto4);
        // 查看此时得分排序集合ScoredSortedSet的元素列表
        // 可以通过SortOrder指定读取出的元素是正序还是倒序
        Collection<RSetDTO> result = rScoredSortedSet.readAll();

        log.info("此时得分排序集合ScoredSortedSet的元素列表-从小到大:{} ", result);
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值