集成 Redis & 异步任务 - SpringBoot 2.7 .2实战基础
SpringBoot 2.7 .2实战基础 - 09 - 集成 Redis & 异步任务
1 集成Redis
《docker 安装 MySQL 和 Redis》一文已介绍如何在 Docker 中安装 Redis,本文就看看 SpringBoot 如何整合 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实战基础的更多相关文章
- 使用 Liquibase 管理数据库版本 - SpringBoot 2.7 .2 实战基础
		
优雅哥 SpringBoot 2.7 .2 实战基础 - 05 -使用 Liquibase 管理数据库版本 在企业开发中,数据库版本管理好像是一个伪命题,大多项目都是通过 Power Designer ...
 - 多环境配置 - SpringBoot 2.7.2 实战基础
		
优雅哥 SpringBoot 2.7.2 实战基础 - 06 -多环境配置 在一个项目的开发过程中,通常伴随着多套环境:本地环境 local.开发环境 dev.集成测试环境 test.用户接受测试环境 ...
 - 清晰梳理最全日志框架关系与日志配置-SpringBoot 2.7.2 实战基础
		
优雅哥 SpringBoot 2.7.2 实战基础 - 07 - 日志配置 Java 中日志相关的 jar 包非常多,log4j.log4j2.commons-logging.logback.slf4 ...
 - SpringBoot 如何集成 MyBatisPlus - SpringBoot 2.7.2实战基础
		
SpringBoot 2.7.2 学习系列,本节通过实战内容讲解如何集成 MyBatisPlus 本文在前文的基础上集成 MyBatisPlus,并创建数据库表,实现一个实体简单的 CRUD 接口. ...
 - Spring Boot从入门到精通(七)集成Redis实现Session共享
		
单点登录(SSO)是指在多个应用系统中,登录用户只需要登录验证一次就可以访问所有相互信任的应用系统,Redis Session共享是实现单点登录的一种方式.本文是通过Spring Boot框架集成Re ...
 - 集成 Spring Doc 接口文档和 knife4j-SpringBoot 2.7.2 实战基础
		
优雅哥 SpringBoot 2.7.2 实战基础 - 04 -集成 Spring Doc 接口文档和 knife4j 前面已经集成 MyBatis Plus.Druid 数据源,开发了 5 个接口. ...
 - 【springBoot】springBoot集成redis的key,value序列化的相关问题
		
使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>o ...
 - SpringBoot集成redis的key,value序列化的相关问题
		
使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>o ...
 - springboot集成redis(mybatis、分布式session)
		
安装Redis请参考:<CentOS快速安装Redis> 一.springboot集成redis并实现DB与缓存同步 1.添加redis及数据库相关依赖(pom.xml) <depe ...
 
随机推荐
- SAP Web Dynpro-消息
			
在ABAP Workbench中,您还可以创建和显示包含Dynpro应用程序最终用户信息的消息. 这些消息显示在屏幕上. 这些是用户交互消息,显示有关Web Dynpro应用程序的重要信息. 为了向用 ...
 - Windows 2008R2 IIS环境配置(靶机)
			
一.Windows 2008 R2系统安装 VMware Workstation 15安装包 链接:https://pan.baidu.com/s/11sYcZTYPqIV-pyvzo7pWLQ 提取 ...
 - TopoLVM: 基于LVM的Kubernetes本地持久化方案,容量感知,动态创建PV,轻松使用本地磁盘
			
正文 研发测试场景下,一般追求的是一键快速起环境,横向动态复制,一人一套,随起随用,用完即走.作为使用方,其不用关心实际的物理资源是怎样的,环境起在哪里,只要声明自己的使用需求即可.但作为方案构建者以 ...
 - sql-扩展sql
			
复制表结构 create table 表名 like 被复制的表名; -- 仅复制表表结构 oracle不支持 复制表结构和数据(子查询方式) CREATE TABLE 表名 [AS] SELECT ...
 - Python列表解析式的正确使用方式
			
先来逼逼两句: Python 是一种极其多样化和强大的编程语言!当需要解决一个问题时,它有着不同的方法.在本文中,将会展示列表解析式 (List Comprehension).我们将讨论如何使用它?什 ...
 - MySql查看索引以及各字段含义
			
查看表的索引: show index from userInfo(表名) show index from 数据库名.表名 查看某表某一列上的索引使用下面的SQL语句: show index from ...
 - go-zero微服务实战系列(九、极致优化秒杀性能)
			
上一篇文章中引入了消息队列对秒杀流量做削峰的处理,我们使用的是Kafka,看起来似乎工作的不错,但其实还是有很多隐患存在,如果这些隐患不优化处理掉,那么秒杀抢购活动开始后可能会出现消息堆积.消费延迟. ...
 - labview入门到出家10(进阶)——CAN通讯
			
 讲完串口,这边再讲一个labveiw工控程序中比较常用的CAN通讯吧.很久没有写过CAN通讯的程序了,网上一搜就是什么现场总线,控制器局域网总线,然后一堆复杂的协议.在这里还是一 ...
 - Map集合概述和Map常用子类
			
概述java.util.Map接口 Map<K,V> 有两个泛型 类型参数:K - 此映射所维护的键的类型V - 映射值的类型 特点:1.Map集合是双列集合,一个元素包含两个值,一个是k ...
 - 《吐血整理》保姆级系列教程-玩转Fiddler抓包教程(6)-Fiddler状态面板详解
			
1.简介 按照从上往下,从左往右的计划,今天就轮到介绍和分享Fiddler的状态面板了. 2.状态面板概览 Fiddler的状态面板概览,如下图所示: 3.状态面板详解 Fiddler底端状态栏面板详 ...