《docker 安裝 MySQL 和 Redis》一文已介紹如何在 Docker 中安裝 Redis,本文就看看 SpringBoot 如何整合 Redis。SpringBoot 提供了整合 Redis 的 starter,使用非常簡單。
在 pom.xml 中新增 redis 的 starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
修改 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
在 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;
}
}
可封裝一些 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;
}
}
}
操作太多,這裡簡單放了幾個常見的方法。
新建 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);
}
}
非同步任務在後端開發中很常見,如生成報表,前端呼叫後端一個介面,如果資料量較大,後端生成報表會非常耗時,這時候如果是同步任務,等後端報表已經生成後,估計已經請求超時了。通常情況下,後端觸發一個非同步任務,成功觸發任務後,後端就返回前端,無需等待報表生成成功。類似場景如同時給使用者傳送郵件和簡訊。
分別編寫模擬傳送郵件和簡訊的 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();
}
}
}
建立 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);
}
}
請求該介面:
http://localhost:9099/demo/async?msg=hello
由於現在是同步執行,需要簡訊和郵件兩個service都執行完後才會返回結果。而且輸出的紀錄檔順序是固定的:
接下來進行非同步任務的改造。
首先在啟動類上新增註解 @EnableAsync
開啟非同步任務。
@EnableAsync
@MapperScan("com.yygnb.demo.mapper")
@SpringBootApplication
public class DemoApplication {
...
}
在需要非同步執行的方法上新增註解 @Async
。在 sendSms
和 sendEmail
兩個方法上新增該註解:
...
@Async
public void sendEmail(String msg) {
...
}
...
呼叫非同步任務的方法與非同步任務的方法,不能在同一個 Service 中,即上面的 demo中,send 方法與 sendSms 不能在同一個 Service中。
非同步任務本質上是在子執行緒中執行的。可以自定義執行緒池。
建立執行緒池設定的實體類:
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-";
}
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;
}
}
在 application.yml 中設定執行緒池:
thread-pool:
core-pool-size: 3
max-pool-size: 5
thread-name-prefix: yyg-async-
重啟服務,存取上面的介面,紀錄檔如下:
感謝你花費寶貴的時間閱讀本文,如果本文給了你一點點幫助或者啟發,還請三連支援一下,點贊、關注、收藏,作者會持續與大家分享更多幹貨