3

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

 2 years ago
source link: https://blog.51cto.com/u_15773567/5658970
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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都执行完后才会返回结果。而且输出的日志顺序是固定的:

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

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实战基础_spring_02

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK