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);
}