redis整合springboot学习笔记
pom引入依赖
需要同时引入spring-boot-starter-data-redis和commons-pool2这2个依赖;
spring-boot-starter-data-redis是官方封装的redis操作依赖,
commons-pool2是redis需要的连接池,不引入这个会导致启动报错.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.5.0</version>
</dependency>
配置springboot项目内的application.yml或application.properties
spring:
redis:
# ip
host: localhost
# 端口6379
port: 6379
#密码,没有密码则不配置这一项
password:
#指定使用redis 16个库中的哪一个,不配置的话,默认配置为0
database: 0
lettuce:
pool:
min-idle: 0 #连接池最新空闲时间
max-wait: -1ms #最大等待时间
max-active: 8 #最大活跃时间
max-idle: 8 #最大空闲时间
shutdown-timeout: 100ms #连接池关闭超时时间
timeout: 1000ms #redis连接超时时间
使用spring-boot-starter-data-redis内置的StringRedisTemplate
要在需要使用redis的地方注入StringRedisTemplate,如
@Slf4j
@Component
@Validated
public class RedisTool {
private StringRedisTemplate stringRedisTemplate;
@Autowired
public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
private ObjectMapper objectMapper;
@Autowired
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
}
StringRedisTemplate 是springDataRedis提供的一个模板类,可以让我们更方便的操作key值类型为字符串的Redis Key 。
字符串类型是Redis最基础的数据结构,字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。
StringRedisTemplate 内提供的常用的方法如下。
判断key是否存在
public boolean hasKey(@NotNull String strK) {
Boolean hasKey = stringRedisTemplate.hasKey(strK);
if (hasKey != null && hasKey) {
return true;
} else {
log.warn("不存在redis key:{}",strK);
return false;
}
}
删除key
//删除1个key
public void deleteOneKeyIfExist(@NotNull String key) {
if (this.hasKey(key)) {
Boolean delete = stringRedisTemplate.delete(key);
log.info("删除了key:{},删除结果:{}",key, delete);
}
}
//删除多个key
public void deleteBatchKeyIfExist(Set<String> keySet) {
for (String k : keySet) {
this.deleteOneKeyIfExist(k);
}
}
添加key
//添加固定的key
public void setKV(@NotNull String strK, String plainStrV) {
stringRedisTemplate.opsForValue().set(strK, plainStrV);
}
//添加带超时时间的key,超时时间到期后key会自动删除
public void setKVTimeoutSeconds(@NotNull String strK, String plainStrV, long seconds) {
stringRedisTemplate.opsForValue().set(strK, plainStrV, seconds, TimeUnit.SECONDS);
}
//序列化后 进行set key
public boolean setKeyAndObjValue(@NotNull String strK, @NotNull Object objV) {
try {
String s = objectMapper.writeValueAsString(objV);
log.warn("jackson序列化:{}", s);
this.setKV(strK, s);
return true;
} catch (JsonProcessingException e) {
e.printStackTrace();
log.error("jackson序列化失败", e);
return false;
}
}
public boolean setKeyAndObjValueByTimeoutSeconds(@NotNull String strK, @NotNull Object objV, long seconds) {
try {
String s = objectMapper.writeValueAsString(objV);
log.warn("jackson序列化:{}", s);
this.setKVTimeoutSeconds(strK, s, seconds);
return true;
} catch (JsonProcessingException e) {
e.printStackTrace();
log.error("jackson序列化失败", e);
return false;
}
}
查询key
//获取key的字符串值
public String getPlainStrV(@NotNull String srtKey) {
if (!this.hasKey(srtKey)) {
return null;
}
return stringRedisTemplate.opsForValue().get(srtKey);
}
//获取key的对象值
public <T> T getJavaObjByJsonCache(@NotNull String srtKey, Class<T> valueType) {
if (!this.hasKey(srtKey)) {
return null;
}
String str = stringRedisTemplate.opsForValue().get(srtKey);
if (StrUtil.isBlank(str)) {
return null;
}
try {
log.warn("反序列化json str:{} 为Java obj type:{}", str,valueType);
return objectMapper.readValue(str, valueType);
} catch (IOException e) {
e.printStackTrace();
log.error("json str 反序列化失败", e);
return null;
}
}
随机获取一个key
//随机取一个 redis key名
public String getRandomKey(){
return stringRedisTemplate.randomKey();
}
加1/减1/替换-操作
stringRedisTemplate.opsForValue().set("wfew", "233");
//+1和减1操作 (key不存在时,创建这个key,并设置为指定的值;key存在时,直接设置key值加1或减1)
//+1操作
stringRedisTemplate.boundValueOps("twfew").increment(1); //key不存在,存入一个key值1
//-1操作
stringRedisTemplate.boundValueOps("wfew").decrement(1); //值变为232
//替换操作 设置新值,同时返回旧值 (key不存在依然可以操作)
String oldVal = stringRedisTemplate.boundValueOps("kwfew").getAndSet("500"); System.out.println(oldVal);//不存在旧值,返回null
设置几秒过期
//设置几秒过期
stringRedisTemplate.opsForValue().set("gb23f", "gwr23", 100, TimeUnit.SECONDS);
查询当前key还剩几秒过期
Long gb23f = stringRedisTemplate.getExpire("gb23f", TimeUnit.SECONDS);
System.out.println(gb23f);
覆盖key值并设置过期时间
//已存在的固定key,会被设置新值同时设置过期时间
stringRedisTemplate.boundValueOps("wfew").set("300", 60, TimeUnit.SECONDS);
append拼接key值
stringRedisTemplate.opsForValue().set("th718", "718");
//append拼接key值
stringRedisTemplate.boundValueOps("th718").append("23");
//打印71823
System.out.println(stringRedisTemplate.opsForValue().get("th718"));
获取旧值,同时set新值
//获取旧值,同时set新值,若旧值不存在会返回null
String th718 = stringRedisTemplate.opsForValue().getAndSet("th718", "202307");
//会打印 71823
System.out.println(th718);
仅在键不存在时设置key
//setIfAbsent-仅在键不存在时设置key, 相当于redis中的setnx命令
// key存在则不变,key不存在则执行set;
// 返回true表示key不存在,set了一个新key
// 返回false表示key已存在,不进行set
//适合场景:并发环境下的分布式锁--(多个进程/线程尝试设置同一个key,set成功则表示获取到锁,set失败表示没有获取到锁)
//setIfAbsent是一个原子性操作,分布式环境下需要注意并发问题和过期时间的处理
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("th720", "kwerq2");
System.err.println(aBoolean);
批量获取key值
//批量获取key值
List<String> stringList = stringRedisTemplate.opsForValue().multiGet(
ListUtil.toList("th718", "th719", "th720","th721"));
//某个key不存在,则值获取为null
//[202307, 202307170, kwerq, null]
System.err.println(stringList);
操作list结构
redis list 中的元素可以重复 ,且list元素是有序的.
redis list 的结构类似于队列, 提供了lpush左入队,rpop右出队 相关的方法, 但不建议使用redis的List作为队列使用.
redis列表的使用场景包括消息队列、发布/订阅、实时排行榜、历史记录、消息通知系统、简单队列等场景
push 向list中添加元素
//right push 操作 可以创建key并向右顺序插入元素,或者在已有list的末尾追加元素
stringRedisTemplate.opsForList().rightPush("lst1", "hjew");
stringRedisTemplate.opsForList().rightPush("lst1", "bswgrg");
stringRedisTemplate.opsForList().rightPush("lst1", "hjew");
stringRedisTemplate.opsForList().rightPush("lst1", "kgwefqz");
// left push 操作, 可以创建key并向左插入元素,或者在已有list的第一个元素之前追加元素
stringRedisTemplate.opsForList().leftPush("lst1", "gtw1e");
stringRedisTemplate.opsForList().leftPush("lst1", "nkqwe1");
pop 弹出list中的元素
stringRedisTemplate.opsForList().rightPush("lst2", "v1");
stringRedisTemplate.opsForList().rightPush("lst2", "v2");
stringRedisTemplate.opsForList().leftPush("lst2", "zisj");
//从 lst2 列表 中最后一个元素开始,弹出1个元素
//[从右往左删除元素 (或删除列表中最后一个元素)]
stringRedisTemplate.opsForList().rightPop("lst2");
//从左往右删除元素 (或删除列表中第一个元素)
stringRedisTemplate.opsForList().leftPop("lst2");
仅在键不存在时向list添加元素
// 若 lst3 这个key存在,则 向左插入1个元素 (lst3不存在则不会执行)
stringRedisTemplate.opsForList().leftPushIfPresent("lst3", "nkqwe1");
//创建lst3并向右 插入1个元素
stringRedisTemplate.opsForList().rightPush("lst3", "grt2r3");
// 若 lst3 这个key存在,则 向右 插入1个元素
stringRedisTemplate.opsForList().rightPushIfPresent("lst3", "hwerg");
根据索引来查询/更新/删除list中的元素
// 获取lst1 列表中 索引为0的元素
System.err.println(stringRedisTemplate.opsForList().index("lst1", 0));
// 对 lst1 列表中的 索引为 2的元素 ,设置为 e3; (若lst1不存在,会报错 ERR no such key)
stringRedisTemplate.opsForList().set("lst1", 2, "e3");
// 仅当 列表 lst3 中 索引为1的元素 为 hwerg 时,才执行删除 这个元素
System.err.println(stringRedisTemplate.opsForList().remove("lst3", 1, "hwerg"));
对list里的元素进行范围查询
// 范围查询 ,获取 lst1 列表中 ,索引从1到4的元素
List<String> rangeList = stringRedisTemplate.opsForList().range("lst1", 1, 4);
System.err.println(rangeList);
操作set结构
redis 的Set是一个无序的、唯一的数据集合。
无序性:集合中的元素没有固定顺序,每次获取的元素顺序可能不同
唯一性:集合中的元素是唯一的,不允许重复
添加元素:sadd 命令
删除元素: srem命令
查找元素: sismember命令
获取元素的数量: scard命令
获取所有元素: smembers命令
set集合间的操作: sinter求交集 \sunion 求并集\ sdiff 求差集
set集合的实际使用场景有:
去重:把java集合放入redis set,实现去重
共同好友/共同关注 : 2个集合求交集可以实现
标签或分类:用set集合存在对象的标签或分类,方便快速查找
排他性操作:通过求差集,实现对范围外的用户进行排他性操作
在springDataRedis里提供了一下对redisSet的操作方法如下
@Test
public void testSet() {
//创建一个无序集合,并写入数据 (set中的元素不能重复, 若values里的元素有重复的,写入时会自动去重)
stringRedisTemplate.opsForSet().add("sk1", "reg23r", "r23rf", "kuy54rt", "hn5t4t","reg23r");
//判断set中是否存在某个元素
System.out.println(
stringRedisTemplate.opsForSet().isMember("sk1", "reg23r"));
//根据key获取set中的元素
System.out.println(
stringRedisTemplate.opsForSet().members("sk1"));
//从1个set中随机取3个元素
System.out.println(stringRedisTemplate.opsForSet().distinctRandomMembers("sk1", 3));
//从1个set中随机取1个元素
System.out.println(stringRedisTemplate.opsForSet().randomMember("sk1"));
//从1个set中删除 某些元素
stringRedisTemplate.opsForSet().remove("sk1", "ga34rerw", "greg23");
stringRedisTemplate.opsForSet().add("sk2", "plf23e", "r23rf");
System.out.println(
stringRedisTemplate.opsForSet().members("sk2"));
// sk1 集合 减去 sk2 集合
System.err.println(stringRedisTemplate.opsForSet().difference("sk1", "sk2"));
// sk2 减 sk1
System.err.println(stringRedisTemplate.opsForSet().difference("sk2", "sk1"));
// 2个set集合相减,结果存到新的set中
stringRedisTemplate.opsForSet().differenceAndStore("sk1", "sk2", "sk3");
//两个set集合求交集
System.err.println(stringRedisTemplate.opsForSet().intersect("sk1", "sk2"));
//两个set集合求交集, 结果存到新的set中
stringRedisTemplate.opsForSet().intersectAndStore("sk1", "sk2", "sk5");
//2个set集合 求并集
System.err.println(stringRedisTemplate.opsForSet().union("sk1", "sk2"));
//sk1和sk2 求并集,然后结果存到sk8里
stringRedisTemplate.opsForSet().unionAndStore("sk1", "sk2", "sk8");
//移动1个元素 到1个新的set
//把sk1里的元素hn5t4t,移动到sk6
System.out.println(stringRedisTemplate.opsForSet().move("sk1", "hn5t4t", "sk6"));
//获取1个set的元素数目
System.err.println(stringRedisTemplate.opsForSet().size("sk1"));
}
操作zset结构
zset是redis里的的有序集合,是一种允许存储多个元素并对每个元素设置一个score分数的数据结构
zset的特点:
排序性: zset 数据最终存储的顺序是按照score得分从低到高排序,如果score相同再按成员的字典顺序排序。
唯一性: zset中的元素是唯一的,不允许重复
添加元素和分数的命令: zadd
删除元素的命令: zrem
获取元素的分数: zscore
统计zset元素数目: zcard
根据分数范围和排名获取成员: zrangebyscore / zrank
集合间的操作: zinterstore 求交集 /zunionstore 求并集
zset的使用场景:
排行榜:根据得分进行排名/获取排行
范围查询: 根据得分进行范围查询
任务优先级: 可以用得分来表示任务优先级 或 权重 ,进行按优先级/权重 的调度
社交网络: 可以用zset存用户之间的关系和相关指标,如共同关注的人、好友推荐等
springDataRedis提供的常用的对zset的操作如下
@Test
public void testZset() {
redisTool.deleteBatchKeyIfExist(Stream.of("zs1", "zs2", "zs3").collect(Collectors.toSet()));
// 创建zs1 集合 , 同时写入 元素值 及其 得分;
// (redis zset 数据最终存储的顺序是按照score得分从低到高排序)
stringRedisTemplate.opsForZSet().add("zs1", "v01", 0.3);
stringRedisTemplate.opsForZSet().add("zs1", "v03", 0.5);
stringRedisTemplate.opsForZSet().add("zs1", "v07", 0.2);
stringRedisTemplate.opsForZSet().add("zs1", "v09", 0.2);
stringRedisTemplate.opsForZSet().add("zs1", "v12", 0.1);
// 统计分数为0.1到0.3 的元素数目
//这里打印结果是 4
System.out.println(stringRedisTemplate.opsForZSet().count("zs1", 0.1, 0.3));
// 获取 zset中 索引从 1到3 的集合
// 由于zset 数据最终存储的顺序是按照score得分从低到高排序,
// 所以 索引1-3的数据 是 [v07, v09, v01]
Set<String> rangeSet = stringRedisTemplate.opsForZSet().range("zs1", 1, 3);
System.out.println(rangeSet);
//对索引1-3的数据 倒序排序
Set<String> zs12 = stringRedisTemplate.opsForZSet().reverseRange("zs1", 1, 3);
//[v01, v09, v07]
System.err.println(zs12);
//按score分数范围 查询元素集合
//查询score在0.1到0.3 的元素集合
Set<String> zs1 = stringRedisTemplate.opsForZSet().rangeByScore("zs1", 0.1, 0.3);
//[v12, v07, v09, v01]
System.out.println(zs1);
//score在0.1到0.3 的元素 倒序排序
Set<String> zs13 = stringRedisTemplate.opsForZSet().reverseRangeByScore("zs1", 0.1, 0.3);
//[v01, v09, v07, v12]
System.out.println(zs13);
//查询某个元素的排行(即zet中某个元素的索引)
Long zs11 = stringRedisTemplate.opsForZSet().rank("zs1", "v03");
//v03的索引是4 ,这里会打印 4
System.out.println(zs11);
//查询某个元素排倒数第几 (即倒序索引)
Long aLong = stringRedisTemplate.opsForZSet().reverseRank("zs1", "v03");
System.out.println(aLong);
//重新设置某元素的score得分
//incrementScore 加分 (在原有基础上增加多少得分)
Double aDouble = stringRedisTemplate.opsForZSet().incrementScore("zs1", "v09", 0.40);
//0.2 +0.40 = 0.6
//打印结果是 0.6
System.out.println(aDouble);
//再次打印zset 索引0-5的元素
Set<String> zs14 = stringRedisTemplate.opsForZSet().range("zs1", 0, 5);
//[v12, v07, v01, v03, v09]
System.out.println(zs14);
}
操作hash结构
redis里的 hash用于存储 键值对的集合, 需要注意的是 hash 元素中的键和值 都是字符串类型
redis里的 hash 提供了类似于 字典或关联数组的功能,可以通过键 快速访问和修改对应的值
redis 哈希的特点:
存储结构: 类似于java里的map ,以键值对形式存储数据
键的唯一性: hash中的 hashKey是唯一的,不允许重复
添加键值对命令: hset
获取值命令: hget
删除键值对命令: hdel
获取所有键值对: hgetall
获取所有键和值: hkeys / hvals
统计键值对数量: hlen
redis hash结构的使用场景包括:
对象存储:hash可以表示对象,其中对象的字段存储为hash的键值对
用户属性存储: 可以将用户的属性存储在hash中,通过键来快速获取用户的相应属性
数据库模型存储: 可以用hash存储关系数据库表的行记录,其中每行的字段对应hash的键值对
springDataRedis提供的常用的对hash的操作如下
@Test
public void testhash() {
redisTool.deleteBatchKeyIfExist(Stream.of("htb1", "htb2", "htb3").collect(Collectors.toSet()));
HashOperations<String, Object, Object> sooHashOperations = stringRedisTemplate.opsForHash();
//put方法: 对某个redis key里添加键值对 ,键是hashKey ,值是value
sooHashOperations.put("htb1", "hk001", "er21r12r");
sooHashOperations.put("htb1", "hk002", "3rge");
sooHashOperations.put("htb1", "hk003", "tgr");
sooHashOperations.put("htb1", "hk004", "er21r12r");
sooHashOperations.put("htb1", "hk005", "erg23");
sooHashOperations.put("htb2", "hk201", "er21r12r");
sooHashOperations.put("htb2", "hk202", "ret234t");
sooHashOperations.put("htb2", "hk203", "gr34r");
sooHashOperations.put("htb2", "hk204", "n818gr34r");
//size方法: 统计hash中的键值对数量
Long htb11 = sooHashOperations.size("htb1");
//5
System.out.println(htb11);
Map<String, Object> htb3 = new HashMap<>();
htb3.put("ewfeqf", "r32r23r");
htb3.put("hethrt", "n1498h4");
//putAll方法: 可以直接把java里的 HashMap 放入redis hash中缓存
sooHashOperations.putAll("htb3", htb3);
//hasKey 方法: 判断某个redis key里 是否存在键名为 hk001的元素
//true
System.err.println(sooHashOperations.hasKey("htb1", "hk001"));
//entries方法: 查询redis key名为 htb1 的所有键值对
Map<Object, Object> htb1 = sooHashOperations.entries("htb1");
//{hk001=er21r12r, hk002=3rge, hk003=tgr, hk004=er21r12r, hk005=erg23}
System.out.println(htb1);
//false
System.err.println(htb1.containsKey("hk203"));
//multiGet方法: 同时查询多个hashKey
List<Object> objects = sooHashOperations.multiGet("htb1", ListUtil.toList("hk001", "hk002"));
//[er21r12r, 3rge]
System.err.println(objects);
//get方法: 获取某个redis key里的键名为 hk001的元素值
Object o = sooHashOperations.get("htb1", "hk001");
//er21r12r
System.out.println(o);
//delete方法: 删除某个redis key里的 某些键值对 , hashKeys表示可以传入多个键名
//打印结果为2 ,表示成功删除了2个hashKey
System.err.println(sooHashOperations.delete("htb2", "hk201", "hk202"));
//keys方法: 获取redis key名为 htb2 的hash键值对里的 key键集合
Set<Object> keysForhtb2 = sooHashOperations.keys("htb2");
//[hk203, hk204]
System.out.println(keysForhtb2);
//values方法: 获取redis key名为 htb2 的hash键值对里的 value值集合
List<Object> htb2 = sooHashOperations.values("htb2");
//[gr34r, n818gr34r]
System.out.println(htb2);
}
操作geo数据结构
redis geo 是一种地理位置存储结构
redis 3.2版本开始有了geo这个数据结构,可以存储/查询地理位置信息
redis geo的常用命令:
添加地理位置:
geoadd g1 93.21 26.32 shanghai;geoadd g1 87.12 35.23 beijing;
获取地理位置:
geopos g1 shanghai;
计算距离:
geodist g1 shanghai beijing km;
删除地理位置: zrem
获取某个坐标点 半径多少km的范围内的成员:
georadius g1 88.12 30.13 1500 km;
获取某个标识点 半径多少km的范围内的成员:
georadiusbymember g1 beijing 2000 km;
redis geo的使用场景包括:
附近的人:可以根据用户的地理位置信息,查询附近的其他用户
地理位置搜索: 根据经纬度信息,搜索特定地点附近的商店、餐厅等
路径规划: 根据地理位置信息,计算两个地点之间的距离和最短路径
springDataRedis提供的常用的对geo的操作如下
@Test
public void testGeo() {
redisTool.deleteBatchKeyIfExist(Stream.of("g1", "g2", "g3").collect(Collectors.toSet()));
//添加地理位置
//向key名为g1的geo集合里添加一个Point坐标经纬度 和对应的member地理标识名
stringRedisTemplate.opsForGeo().add("g1", new Point(23.12, 31.23), "m1");
stringRedisTemplate.opsForGeo().add("g1", new Point(93.21, 26.32), "shanghai");
stringRedisTemplate.opsForGeo().add("g1", new Point(87.12, 35.23), "beijing");
//获取多个地理位置
List<Point> position = stringRedisTemplate.opsForGeo()
.position("g1", "m1","shanghai");
//[Point [x=23.120001, y=31.229999], Point [x=93.210001, y=26.320001]]
System.out.println(position);
//计算距离
Distance distance = stringRedisTemplate.opsForGeo()
.distance("g1", "shanghai", "beijing",
RedisGeoCommands.DistanceUnit.KILOMETERS);
if (distance!=null){
//1148.6266km
System.out.println(distance.getValue() + distance.getUnit());
}
//获取某个坐标点 半径多少km的范围内的成员
GeoResults<RedisGeoCommands.GeoLocation<String>> g1radius = stringRedisTemplate.opsForGeo()
.radius("g1",
new Circle(
new Point(88.12, 30.13),
new Distance(1500, RedisGeoCommands.DistanceUnit.KILOMETERS)
)
);
System.out.println(g1radius);
//获取某个标识点 半径多少km的范围内的成员
GeoResults<RedisGeoCommands.GeoLocation<String>> radius = stringRedisTemplate.opsForGeo()
.radius("g1",
"beijing",
new Distance(2000, RedisGeoCommands.DistanceUnit.KILOMETERS));
//GeoResults: [averageDistance: 0.0, results: GeoResult [content: RedisGeoCommands.GeoLocation(name=beijing, point=null), distance: 0.0, ],GeoResult [content: RedisGeoCommands.GeoLocation(name=shanghai, point=null), distance: 0.0, ]]
System.out.println(radius);
if (radius!=null && radius.getContent().size()>0){
for (GeoResult<RedisGeoCommands.GeoLocation<String>> geoLocationGeoResult : radius.getContent()) {
//beijing
//shanghai
System.out.println(geoLocationGeoResult.getContent().getName());
}
}
//删除多个地理标识的位置
Long count = stringRedisTemplate.opsForGeo().remove("g1", "m1","beijing");
// 打印2 ,表示成功删除了2个地理标识的位置
System.out.println(count);
}
通过redis中的pub/sub实现消息发布和订阅
定义RedisPubSubCfg
package cn.test.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@Configuration
public class RedisPubSubCfg {
private final RedisConnectionFactory redisConnectionFactory;
@Autowired
public RedisPubSubCfg(RedisConnectionFactory redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory;
}
/**
* 将消息监听器绑定到消息容器
* @return RedisMessageListenerContainer
*/
@Bean
public RedisMessageListenerContainer messageListenerContainer(){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
//------分开订阅监听,使用自定义的消息监听器SubscribeListener-------
//按名称匹配
container.addMessageListener(new CitySub(),new ChannelTopic("cityTopic"));
//按模式匹配
container.addMessageListener(new TestSub(),new PatternTopic("/aaa/*"));
return container;
}
}
自定义消息监听器
package cn.test.redis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
@Slf4j
public class TestSub implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
String s = new String(pattern);
log.info("redis sub pattern: "+s);
String channelReal = new String(message.getChannel());
log.info("real sub pattern: "+channelReal);
String body = new String(message.getBody());
log.info("TestSub 订阅到的消息: "+body);
}
}
package cn.test.redis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
@Slf4j
public class CitySub implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
String body = new String(message.getBody());
String channel = new String(message.getChannel());
System.err.println("Topic 名称: "+channel);
String patternStr = new String(pattern);
System.err.println("Topic 模式: "+patternStr);
log.info("CitySub 订阅到的消息: {}",body);
}
}
发布消息
springDataRedis中的stringRedisTemplate提供了convertAndSend方法可以发送消息。
@Test
public void tstPublish() {
String s = null;
try {
s = objectMapper.writeValueAsString(new CityInfo("aaa", 117.17, 31.52));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//发送消息,topic为 cityTopic
stringRedisTemplate.convertAndSend("cityTopic",
s
);
//发送消息,topic为 /aaa/123
stringRedisTemplate.convertAndSend("/aaa/123",
s
);
}
2023-07-19 19:32:42.003 INFO 1128 --- [erContainer-357] cn.test.redis.TestSub : redis sub pattern: /aaa/*
2023-07-19 19:32:42.003 INFO 1128 --- [erContainer-357] cn.test.redis.TestSub : real sub pattern: /aaa/123
2023-07-19 19:32:42.003 INFO 1128 --- [erContainer-357] cn.test.redis.TestSub : TestSub 订阅到的消息: {"city":"hefei1689755562001","longitude":117.17,"latitude":31.52}
设置key过期监听
1.需要先配置 redis.conf ,开启key过期监听功能
notify-keyspace-events Ex
2.在springboot redis配置中,订阅key过期事件并发布pub/sub消息
@Configuration
public class RedisPubSubCfg {
private final RedisConnectionFactory redisConnectionFactory;
@Autowired
public RedisPubSubCfg(RedisConnectionFactory redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory;
}
/**
* 将消息监听器绑定到消息容器
* @return RedisMessageListenerContainer
*/
@Bean
public RedisMessageListenerContainer messageListenerContainer(){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
//------分开订阅监听,使用自定义的消息监听器SubscribeListener-------
//按名称匹配
container.addMessageListener(new CitySub(),new ChannelTopic("cityTopic"));
//按模式匹配
container.addMessageListener(new TestSub(),new PatternTopic("/aaa/*"));
//监听key过期事件,并发布一个消息
//监听key过期事件 ,需要在redis.conf里开启配置 notify-keyspace-events Ex
//然后需要在springDataRedis里订阅__keyevent@*__:expired 的模式消息
container.addMessageListener(new ExpSub() ,new PatternTopic("__keyevent@*__:expired"));
return container;
}
}
package cn.test.redis.exp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
@Slf4j
public class ExpSub implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
String channelReal = new String(message.getChannel());
log.info("real sub pattern: "+channelReal);
String body = new String(message.getBody());
log.info("过期的redisKey名: "+body);
}
}
2023-07-19 19:32:41.090 INFO 1128 --- [erContainer-356] cn.test.redis.exp.ExpSub : real sub pattern: __keyevent@0__:expired
2023-07-19 19:32:41.090 INFO 1128 --- [erContainer-356] cn.test.redis.exp.ExpSub : 过期的redis key名: ewr3w1689755556001