0121 spring-boot-redis的使用
redis是什么呢?redis,属于NoSQL的一种,在互联网时代,起到加速系统的作用。
redis是一种内存数据库,支持7种数据类型的存储,性能1S 10w次读写;
redis提供的简单的事务保证了高并发场景下数的一致性。
redis在2.6版本之后增加了lua支持,命令是原子性的;
本篇文章主要基于springboot的redis-starter。
HELLO, 性能利器Redis.
spring-boot-starter-redis
这个是springboot提供的redis操作工具包,底层的redis驱动使用的是lettus,而不是jedis;
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
序列化
主要通过RedisTemplate来操作redis;
当然也支持自定义序列化器,比如效率比较高的kyto序列化器;
StringRedisTemplate:key,value都是按照字符串存储的。
TypedTuple 保存集合中的有序元素;
可以查看一下StringRedisTemplate的源码:
public StringRedisTemplate() {
setKeySerializer(RedisSerializer.string());
setValueSerializer(RedisSerializer.string());
setHashKeySerializer(RedisSerializer.string());
setHashValueSerializer(RedisSerializer.string());
}
数据类型操作接口
功能 | 单个操作接口 | 批量操作接口 |
---|---|---|
有序集合 | ZSetOperations | BoundZsetOperations |
字符串 | ValueOperations | BoundValueOpetations |
集合 | SetOperations | BoundSetOperations |
列表 | ListOperations | BoundListOperations |
散列 | HashOperations | BoundHashOperations |
基数 | HyperLogLogOperations | BoundHyperLogLogOperaions |
地理位置 | GeoOperations | BoundGeoOperations |
使用代码
@Autowired
private RedisTemplate redisTemplate;
@Test
void stringRedisTest() {
final ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations
.set("key1", "value1", Duration.ofMinutes(1));
final Object value = valueOperations.get("key1");
Assert.isTrue(Objects.equals("value1", value), "set失败");
final HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.put("hash1", "f1", "v1");
hashOperations.put("hash1", "f2", "v2");
hashOperations.values("hash1").forEach(System.out::println);
}
在同一条连接中进行多次操作
- SessionCallback 高级操作对象
- RedisCallback 低级操作对象
代码中直接使用的java8的lambda表达式。
使用代码
@Test
void redisCallbackTest() {
redisTemplate.execute((RedisCallback) connection -> {
connection.set("rkey1".getBytes(), "rv1".getBytes());
connection.set("rkey2".getBytes(), "rv2".getBytes());
return null;
});
}
@Test
void sessionCallbackTest() {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
final ListOperations listOperations = operations.opsForList();
listOperations.leftPush("sk1", "sv1");
listOperations.leftPush("sk1", "sv2");
listOperations.getOperations().expire("sk1", 1, TimeUnit.MINUTES);
listOperations.range("sk1", 0, 2).forEach(System.out::println);
return 1;
}
});
}
字符串操作
最为常用的数据类型
实际情况使用的不多,现实的场景一般是放一个对象或者对象列表 转换为字符串 进行存储,取出的时候再转换为对象;
代码:
@Test
void stringTest() {
redisTemplate.opsForValue().set("stringKey1", "value1", 5, TimeUnit.MINUTES);
//字符串类型的整数,不能进行数字运算;
redisTemplate.opsForValue().set("stringKey2", "1", 5, TimeUnit.MINUTES);
//进行数字运算,增加,减少
redisTemplate.opsForValue().set("stringKey3", 1, 5, TimeUnit.MINUTES);
redisTemplate.opsForValue().increment("stringKey3",1);
redisTemplate.opsForValue().decrement("stringKey3",1);
//其它操作方法
final Long keySize = redisTemplate.opsForValue().size("stringKey1");
System.out.println(keySize);
//批量设置
Map<String,Long> map = new HashMap<>(4);
map.put("sk1",1L);
map.put("sk2",2L);
map.put("sk3",3L);
map.put("sk4",4L);
redisTemplate.opsForValue().multiSet(map);
redisTemplate.opsForValue().multiSetIfAbsent(map);
//批量获取
redisTemplate.opsForValue().multiGet(map.keySet()).forEach(System.out::println);
//getAndSet
final Object sk5Value = redisTemplate.opsForValue().getAndSet("sk5", 100);
System.out.println("sk5Value:"+sk5Value);
redisTemplate.opsForValue().append("sk5","hello redis");
System.out.println("sk5Value2:"+redisTemplate.opsForValue().get("sk5"));
//按照情况设置,可以省去了之前查询出来之后判断是否存在再操作的代码;
redisTemplate.opsForValue().setIfAbsent("sk6",1000,5,TimeUnit.MINUTES);
redisTemplate.opsForValue().setIfPresent("sk6",100,5,TimeUnit.MINUTES);
}
其它方法:
更多提供的方法需要在业务场景中多使用
列表操作
@Test
void listTest() {
stringRedisTemplate.opsForList().leftPush("lk1","lkv1");
stringRedisTemplate.opsForList().leftPushAll("lk2","lk2v1","lk2v2");
stringRedisTemplate.opsForList().leftPushAll("lk2",Arrays.asList("lk2v3","lk2v4"));
stringRedisTemplate.opsForList().leftPushIfPresent("lk3","lk3v1");
final List<String> lk2ValuesList = stringRedisTemplate.opsForList().range("lk2", 0, 3);
System.out.println(lk2ValuesList);
}
集合操作
@Test
void setTest() {
stringRedisTemplate.opsForSet().add("sk1","sk1v1","sk1v2","sk1v3");
stringRedisTemplate.opsForSet().add("sk2","sk1v1","sk2v2","sk2v3");
final Set<String> sk1 = stringRedisTemplate.opsForSet().members("sk1");
final Set<String> sk2 = stringRedisTemplate.opsForSet().members("sk2");
System.out.println("sk1: "+sk1);
System.out.println("sk2: "+sk2);
final Set<String> intersect = stringRedisTemplate.opsForSet().intersect("sk1", "sk2");
System.out.println("交集是:" + intersect);
final Set<String> union = stringRedisTemplate.opsForSet().union("sk1", "sk2");
System.out.println("并集:" + union);
final Set<String> difference = stringRedisTemplate.opsForSet().difference("sk1", "sk2");
System.out.println("差集:"+ difference);
final Long size = stringRedisTemplate.opsForSet().size("sk1");
System.out.println("size for sk1 : " + size);
stringRedisTemplate.delete("sk1");
stringRedisTemplate.delete("sk2");
}
有序集合操作
@Test
void zsetTest() {
IntStream.rangeClosed(1,100).forEach(i->{
stringRedisTemplate.opsForZSet().add("zsk1",String.valueOf(i),i*10);
});
final Set<ZSetOperations.TypedTuple<String>> typedTupleSet = IntStream.rangeClosed(1, 100).mapToObj(i -> new DefaultTypedTuple<String>(String.valueOf(i), (double) i * 11)).collect(Collectors.toSet());
stringRedisTemplate.opsForZSet().add("zsk2",typedTupleSet);
final Set<String> zsk1 = stringRedisTemplate.opsForZSet().rangeByLex("zsk1", RedisZSetCommands.Range.range().gte(20).lte(100));
System.out.println("范围内的集合:" + zsk1);
}
散列表操作
@Test
void hashTest() {
stringRedisTemplate.opsForHash().put("hashk1","k1","v1");
stringRedisTemplate.opsForHash().put("hashk1","k2","v1");
stringRedisTemplate.opsForHash().put("hashk1","k3","v1");
stringRedisTemplate.opsForHash().putIfAbsent("hashk1","k4","new V1");
final List<Object> multiGet = stringRedisTemplate.opsForHash().multiGet("hashk1", Arrays.asList("k1", "k2"));
System.out.println("一次获取多个:" + multiGet);
}
springboot中redis的配置
配置分两个部分,连接池和连接信息;下表列出必须给出的配置:
spring.redis.port=6379
spring.redis.host=localhost
spring.redis.password=
spring.redis.timeout=1000
#最小空闲连接数
spring.redis.lettuce.pool.min-idle=2
#最大空闲连接数
spring.redis.lettuce.pool.max-idle=4
#最大活跃连接数
spring.redis.lettuce.pool.max-active=8
#连接最长分配等待时间
spring.redis.lettuce.pool.max-wait=2000
#回收线程间隔毫秒数
spring.redis.lettuce.pool.time-between-eviction-runs=100
注解操作redis
配置CacheManager
spring.redis.cache.type=redis
spring.redis.cache.name=redisCache
通过注解@EnableCaching启用;
@CachePut 更新缓存
@CacheEvicat 清除缓存
@CacheAble 使用查询缓存
缓存在一个类中互相调用失效 : 基于AOP的动态代理,没有生成代理类;
package com.springbootpractice.demo.redis.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
/**
* 说明:代码方式配置缓存管理器
* @author carter
* 创建时间: 2020年01月21日 7:00 下午
**/
@Configuration
public class RedisConfig {
@Autowired private RedisTemplate redisTemplate;
@Bean
public RedisCacheManager redisCacheManager(){
RedisCacheWriter redisWrite = RedisCacheWriter.lockingRedisCacheWriter(redisTemplate.getConnectionFactory());
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration.prefixKeysWith("_demo_redis_");
configuration.entryTtl(Duration.ofMinutes(10));
configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
RedisCacheManager redisCacheManager = new RedisCacheManager(redisWrite,configuration);
return redisCacheManager;
}
}
用法
package com.springbootpractice.demo.redis.biz;
import com.springbootpractice.demo.redis.dao.entity.UserLoginExtEntity;
import com.springbootpractice.demo.redis.dao.mapper.UserLoginExtEntityMapper;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
* 说明:操作user的数据增强层
* @author carter
* 创建时间: 2020年01月21日 6:40 下午
**/
@Service
public class UserLoginExtBiz {
private final UserLoginExtEntityMapper userLoginExtEntityMapper;
public UserLoginExtBiz(UserLoginExtEntityMapper userLoginExtEntityMapper) {
this.userLoginExtEntityMapper = userLoginExtEntityMapper;
}
@Cacheable(value = "redisCache",key = "'getById:'+#id")
public UserLoginExtEntity getById(Integer id){
return userLoginExtEntityMapper.selectByPrimaryKey(id);
}
@CachePut(value = "redisCache",key = "'getById:'+#param.id")
public UserLoginExtEntity updateUserLoginExt(UserLoginExtEntity param){
userLoginExtEntityMapper.updateByPrimaryKeySelective(param);
return param;
}
@CacheEvict(value = "redisCache",key = "'getById:'+#id")
public int deleteUserLoginExt(Integer id){
return userLoginExtEntityMapper.logicalDeleteByPrimaryKey(id);
}
}
redis的特殊用法
redis中事务的用法
利用的是SessionCallback的RedisOperations 的 watch-multi-exec 连环操作;
watch: 监控某些key;
multi:开始事务;
exec: 执行事务
如果watch的key对应的值发生变化(设置为原值也是发生了变化),则会回滚事务;否则,正常的执行事务 ;
redis在执行事务的时候,要么全部执行,要么全部失败,不会被其它的redis客户端打断,保证了redis事务下数据的一致性;
@Test
void transactionTest() {
final String ttk1 = "ttk1";
stringRedisTemplate.opsForValue().set(ttk1,"ttk1v1");
final List list = stringRedisTemplate.execute(new SessionCallback<List>() {
@Override
public List execute(RedisOperations operations) throws DataAccessException {
System.out.println("监听"+ttk1);
//如果ttk1的值发生了变化,重新set一样的值也是发生了变化,则回滚事务,否则正常执行
operations.watch(ttk1);
//开启事务
System.out.println("开启事务");
operations.multi();
operations.opsForList().leftPushAll("xxx_lk1", "v1", "v2", "v3");
final List xxx_lk1 = operations.opsForList().range("xxx_lk1", 0, 2);
System.out.println(xxx_lk1);
operations.opsForSet().add("xxx_sk1", "v1", "v2", "v3");
final Set xxx_sk1 = operations.opsForSet().members("xxx_sk1");
System.out.println(xxx_sk1);
//提交事务
final List list = operations.exec();
System.out.println("提交事务");
return list;
}
});
System.out.println("执行结果:"+list);
}
批量执行redis操作
redisTemplate.executePipelined();
@Test
void pipelineTest() {
StopWatch stopWatch = new StopWatch("pipelineTest");
stopWatch.start();
final List<Object> objectList = stringRedisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (int i = 1; i <= 10000; i++) {
operations.opsForValue().set("pk" + i, "pkv" + i, 5, TimeUnit.MINUTES);
}
return null;
}
});
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
消息队列
需要定义一个一个RedisMessageListenerContainer,配置topic和监听器; 作为消费者;
通过redisTemplate.convertAndSend方法发送消息;
定义监听器
package com.springbootpractice.demo.redis.listener;
import org.springframework.data.redis.connection.Message;
import org.springframework.stereotype.Component;
/**
* 说明:redis的监听器
* @author carter
* 创建时间: 2020年01月21日 5:51 下午
**/
@Component
public class MyRedisMessageListener implements org.springframework.data.redis.connection.MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
System.out.println("MyRedisMessageListener topic:"+new String(pattern) +" 消息:"+ new String(message.getBody()));
}
}
注册监听器容器
package com.springbootpractice.demo.redis.listener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 说明:配置队列监听器,对应的主题
* @author carter
* 创建时间: 2020年01月21日 5:55 下午
**/
@Configuration
public class RedisListenerConfig {
public static final String MY_CHANNEL = "myChannel";
private final MyRedisMessageListener myRedisMessageListener;
private final MyRedisMessageListener2 myRedisMessageListener2;
private final RedisTemplate redisTemplate;
public RedisListenerConfig(MyRedisMessageListener myRedisMessageListener, MyRedisMessageListener2 myRedisMessageListener2, RedisTemplate redisTemplate) {
this.myRedisMessageListener = myRedisMessageListener;
this.myRedisMessageListener2 = myRedisMessageListener2;
this.redisTemplate = redisTemplate;
}
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer() {
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
redisMessageListenerContainer.setConnectionFactory(redisTemplate.getConnectionFactory());
final ExecutorService taskExecutor = new ThreadPoolExecutor(1,
2, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2000));
redisMessageListenerContainer.setTaskExecutor(taskExecutor);
final Topic myChannel = new ChannelTopic(MY_CHANNEL);
redisMessageListenerContainer.addMessageListener(myRedisMessageListener, myChannel);
redisMessageListenerContainer.addMessageListener(myRedisMessageListener2, myChannel);
System.out.println("注册redis的消息队列成功!");
return redisMessageListenerContainer;
}
}
测试代码
publish myChannel helloworld
- redisTemplate.convertAndSend(channel,message);
package com.springbootpractice.demo.redis.web;
import com.springbootpractice.demo.redis.listener.RedisListenerConfig;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* 说明:TODO
* @author carter
* 创建时间: 2020年01月21日 6:22 下午
**/
@RestController
public class TestController {
private final StringRedisTemplate stringRedisTemplate;
public TestController(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@GetMapping(path = "/send/{message}")
public void publishMessage(@PathVariable("message") String message){
stringRedisTemplate.convertAndSend(RedisListenerConfig.MY_CHANNEL,message);
}
}
使用lua脚本
使用的redisTemplate.execute(RedisScript,List,List);
@GetMapping(path = "/lua/{k1}/{v1}/{k2}/{v2}")
public Long publishMessage(@PathVariable("k1") String k1,@PathVariable("k2") String k2,@PathVariable("v1") String v1,@PathVariable("v2") String v2){
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(LuaScript.lua1);
redisScript.setResultType(Long.class);
return stringRedisTemplate.execute(redisScript, Arrays.asList(k1, k2), v1, v2);
}
小结
通过本篇文章,你可以学会:
- 学会使用spring-boot-redis-starter熟练的进行各种数据类型的操作;
- 学会了使用注解的方式使用redis缓存;
- redis的特殊用法,事务,消息队列,批量操作,lua脚本支持;
美女还是要给看的。
原创不易,转载请注明出处。
0121 spring-boot-redis的使用的更多相关文章
- spring boot redis缓存JedisPool使用
spring boot redis缓存JedisPool使用 添加依赖pom.xml中添加如下依赖 <!-- Spring Boot Redis --> <dependency> ...
- spring boot + redis 实现session共享
这次带来的是spring boot + redis 实现session共享的教程. 在spring boot的文档中,告诉我们添加@EnableRedisHttpSession来开启spring se ...
- Spring Boot 项目学习 (三) Spring Boot + Redis 搭建
0 引言 本文主要介绍 Spring Boot 中 Redis 的配置和基本使用. 1 配置 Redis 1. 修改pom.xml,添加Redis依赖 <!-- Spring Boot Redi ...
- Spring Boot Redis 集成配置(转)
Spring Boot Redis 集成配置 .embody{ padding:10px 10px 10px; margin:0 -20px; border-bottom:solid 1px #ede ...
- spring boot redis 缓存(cache)集成
Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...
- spring boot redis分布式锁
随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...
- 从.Net到Java学习第四篇——spring boot+redis
从.Net到Java学习系列目录 “学习java已经十天,有时也怀念当初.net的经典,让这语言将你我相连,怀念你......”接上一篇,本篇使用到的框架redis.FastJSON. 环境准备 安装 ...
- Spring Boot Redis Cluster 实战干货
添加配置信息 spring.redis: database: 0 # Redis数据库索引(默认为0) #host: 192.168.1.8 #port: 6379 password: 123456 ...
- Spring Boot + Redis
启动redis docker run --name redisServer -P -d redis redis自带客户端,启动客户端 docker run -it --link redisServer ...
- Spring boot redis自增编号控制 踩坑
近段期间,公司 接手一个订单号生成服务,规则的话已经由项目经理他们规定好了,主要是后面的四位数代表的关于当前订单号已经执行第几个了.而这里面有一个要求就是支持分布式.为了实现这个东西,刚开始我使用了r ...
随机推荐
- CF #619 div.2
序 希望,不要还有一天像今天一样糟糕. T1 three strings 笔记本的google 炸了,读题可难受了 多组测试数据 我们的想法是,用string存字符串,若 对于任意的i,a[i],b[ ...
- BSP与HAL关系(转)
板级支持包(BSP)(Board Support Package)是介于主板硬件和操作系统中驱动层程序之间的一层,一般认为它属于操作系统一部分,主要是实现对操作系统的支持,为上层的驱动程序提供访问硬件 ...
- Nginx配置HTTPS并将HTTP请求重定向到HTTPS
个人博客 地址:https://www.wenhaofan.com/a/20190702214652 在阿里云获取免费的HTTPS证书 配置HTTPS之前首先需要拥有HTTPS证书,在阿里云可以获得域 ...
- Lombok 详解
简介 lombok是一个编译级别的插件,它可以在项目编译的时候生成一些代码.通俗的说,lombok可以通过注解来标示生成getter settter等代码. 引入 创建gradle项目 compile ...
- 3、MapReduce详解与源码分析
文章目录 1 Split阶段 2 Map阶段 2.1分区 2.2排序 3 Shuffle阶段 4 Reduce阶段 1 Split阶段 首先,接到hdf文件输入,在mapreduce中的ma ...
- 题解 AT3718 【[ABC081B] Shift only】
题目传送门 分析 直接暴力. 我们可以根据题意进行模拟,使用二重循环即可. 代码讲解 定义变量\(n\)和计数数组\(cnt\),再定义数组\(a\)并输入. int a[1000000]; int ...
- gulp-css-spriter 雪碧图合并
相信做前端的同学都做过这样的事情,为优化图片,减少请求会把拿到切好的图标图片,通过ps(或者其他工具)把图片合并到一张图里面,再通过css定位把对于的样式写出来引用的html里面.gulp-css-s ...
- 简单scrapy爬虫实例
简单scrapy爬虫实例 流程分析 抓取内容:网站课程 页面:https://edu.hellobi.com 数据:课程名.课程链接及学习人数 观察页面url变化规律以及页面源代码帮助我们获取所有数据 ...
- A - Wireless Network POJ - 2236-kuangbin带你飞
A - Wireless Network POJ - 2236 Time Limit: 10000MS Memory Limit: 65536K Total Submissions: 50348 ...
- SQLyog怎么导入mysql数据库
参考链接:https://jingyan.baidu.com/article/647f0115c5ad9f7f2148a8c6.html