集成 Redis & 异步任务 - SpringBoot 2.7 .2实战基础

Posted 小二上酒8

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了集成 Redis & 异步任务 - SpringBoot 2.7 .2实战基础相关的知识,希望对你有一定的参考价值。

SpringBoot 2.7 .2实战基础 - 09 - 集成 Redis & 异步任务

1 集成Redis

SpringBoot 提供了整合 Redis 的 starter,使用非常简单。

1.1 添加依赖

在 pom.xml 中添加 redis 的 starter:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

1.2 配置 Redis

修改 application.yml 文件,添加 Redis 的配置:

spring:
redis:
host: 127.0.0.1
port: 6379
username:
password:
timeout: 5000
jedis:
pool:
max-active: 3
max-idle: 3
min-idle: 1
max-wait: -1

1.3 添加配置

在 ​​com.yygnb.demo.config​​ 中创建 ​​RedisConfig​​ ,处理一些中文乱码问题。

​com.yygnb.demo.config.RedisConfig​​ :

@Configuration
public class RedisConfig

private final RedisTemplate redisTemplate;

public RedisConfig(RedisTemplate redisTemplate)
this.redisTemplate = redisTemplate;


/**
* 解决redis插入中文乱码
* @return
*/
@Bean
public RedisTemplate<Serializable, Object> redisTemplateInit()
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);

GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate;


1.4 封装 Redis 操作

可封装一些 Redis 的常见操作。

​com.yygnb.demo.utils.RedisUtils​​ :

@RequiredArgsConstructor
@Component
public class RedisUtils

private final RedisTemplate redisTemplate;

/**
* 指定缓存失效时间
* @param key
* @param time 单位 秒
*/
public void expire(Serializable key, long time)
if (time > 0)
redisTemplate.expire(key, time, TimeUnit.SECONDS);



/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key)
return redisTemplate.getExpire(key, TimeUnit.SECONDS);


/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key)
try
return redisTemplate.hasKey(key);
catch (Exception e)
e.printStackTrace();
return false;



/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key)
if (key != null && key.length > 0)
if (key.length == 1)
redisTemplate.delete(key[0]);
else
redisTemplate.delete(CollectionUtils.arrayToList(key));




/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key)
return key == null ? null : redisTemplate.opsForValue().get(key);


/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value)
try
redisTemplate.opsForValue().set(key, value);
return true;
catch (Exception e)
e.printStackTrace();
return false;



/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Serializable value, long time)
try
if (time > 0)
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
else
set(key, value);

return true;
catch (Exception e)
e.printStackTrace();
return false;


操作太多,这里简单放了几个常见的方法。

1\\. 5 测试

新建 controller 测试 Redis 的操作。

​com.yygnb.demo.controller.RedisDemoController​​ :

@Tag(name = "Redis测试接口")
@RequiredArgsConstructor
@RestController
@RequestMapping("/redis")
public class RedisDemoController

private final RedisUtils redisUtils;

@Operation(summary = "存值")
@PostMapping()
public void save(@RequestBody Map<String, Object> map)
Set<String> keys = map.keySet();
for (String key : keys)
redisUtils.set(key, map.get(key));



@Operation(summary = "取值")
@GetMapping()
public Object get(String key)
return redisUtils.get(key);

2 异步任务

异步任务在后端开发中很常见,如生成报表,前端调用后端一个接口,如果数据量较大,后端生成报表会非常耗时,这时候如果是同步任务,等后端报表已经生成后,估计已经请求超时了。通常情况下,后端触发一个异步任务,成功触发任务后,后端就返回前端,无需等待报表生成成功。类似场景如同时给用户发送邮件和短信。

2.1 准备工作

(1) 两个 Service

分别编写模拟发送邮件和短信的 Service,这里只是演示使用,故 Service 省略了接口定义,直接编写实现类:

​com.yygnb.demo.service.impl.EmailService​​ :

@Slf4j
@Service
public class EmailService

public void sendEmail(String msg)
log.info("开始发送邮件: ", msg);
int i = new Random().nextInt(5);
try
Thread.sleep(i * 1000);
log.info("邮件发送成功");
catch (InterruptedException e)
log.error("邮件发送失败");
e.printStackTrace();


发送短信的 Service 与邮件 service 类似。

​com.yygnb.demo.service.impl.SmsService​​ :

@Slf4j
@Service
public class SmsService

public void sendSms(String msg)
log.info("开始发送短信: ", msg);
int i = new Random().nextInt(5);
try
Thread.sleep(i * 1000);
log.info("短信发送成功");
catch (InterruptedException e)
log.error("短信发送失败");
e.printStackTrace();


(2) 接口开发

创建 DemoService,在 DemoService 中调用上面两个 Service:

@Slf4j
@RequiredArgsConstructor
@Service
public class DemoServiceImpl implements DemoService

private final EmailService emailService;
private final SmsService smsService;

@Override
public void send(String msg)
log.info("发别发送短信和邮件");
smsService.sendSms(msg);
emailService.sendEmail(msg);
log.info("Demo Service 结束");

在 DemoController 中添加接口:

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("demo")
public class DemoController

private final DemoService demoService;

@GetMapping("async")
public void asyncDemo(String msg)
demoService.send(msg);

(3) 运行

请求该接口:

http://localhost:9099/demo/async?msg=hello

由于现在是同步执行,需要短信和邮件两个service都执行完后才会返回结果。而且输出的日志顺序是固定的:

集成

2.2 异步任务

接下来进行异步任务的改造。

(1) @EnableAsync

首先在启动类上添加注解 ​​@EnableAsync​​ 开启异步任务。

@EnableAsync
@MapperScan("com.yygnb.demo.mapper")
@SpringBootApplication
public class DemoApplication
...
(2)@Async

在需要异步执行的方法上添加注解 ​​@Async​​ 。在 ​​sendSms​​ 和 ​​sendEmail​​ 两个方法上添加该注解:

...
@Async
public void sendEmail(String msg)
...

...
(3)注意事项

调用异步任务的方法与异步任务的方法,不能在同一个 Service 中,即上面的 demo中,send 方法与 sendSms 不能在同一个 Service中。

2.3 自定义线程池

异步任务本质上是在子线程中运行的。可以自定义线程池。

(1)定义配置的实体类

创建线程池配置的实体类:

​com.yygnb.demo.config.ThreadPoolInfo​​ :

@Data
@Component
@ConfigurationProperties(prefix = "thread-pool")
public class ThreadPoolInfo

private int corePoolSize = 1;

private int maxPoolSize = Integer.MAX_VALUE;

private int keepAliveSeconds = 60;

private int queueCapacity = Integer.MAX_VALUE;

private String threadNamePrefix = "thread-";
(2)配置类

​com.yygnb.demo.config.AsyncConfig​​ :

@RequiredArgsConstructor
@Configuration
public class AsyncConfig

private final ThreadPoolInfo info;

@Bean("asyncExecutor")
public Executor asyncExecutor()
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(info.getCorePoolSize());
executor.setMaxPoolSize(info.getMaxPoolSize());
executor.setQueueCapacity(info.getQueueCapacity());
executor.setThreadNamePrefix(info.getThreadNamePrefix());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;

(3)配置 YML

在 application.yml 中配置线程池:

thread-pool:
core-pool-size: 3
max-pool-size: 5
thread-name-prefix: yyg-async-

重启服务,访问上面的接口,日志如下:

集成

以上是关于集成 Redis & 异步任务 - SpringBoot 2.7 .2实战基础的主要内容,如果未能解决你的问题,请参考以下文章

python3+celery+redis实现异步任务

Celery+python+redis异步执行定时任务

Django使用Celery加redis执行异步任务

flaskcelery+redis 实现定时任务和异步——

flaskcelery+redis 实现定时任务和异步—— 时间篇

Django设置异步任务