众所周知,在项目的开发中,合理使用缓存是提高服务性能的一大利器,本篇文章就来介绍一下我所在项目中如何使用缓存的一个案例。
我们的项目是由多个微服务所组成,业务是保险,我所负责的模块是出单,在压测的过程中,发现当所有服务启动好之后,第一次出单的时间存在耗时较长的情况,通过sleuth分析了一下各个服务之间的调用链,针对第一单,发现出单接口中存在调用其他接口做查询和逻辑处理,在第一次调用后会将结果缓存,那么以后的调用基本是直接走缓存,不会去和数据库交互,减少了数据库创建和关闭连接之类的耗时。此时,我发现针对首单慢的问题,主要是因为一些接口返回固定的值,第一次没有通过缓存获取而导致的耗时较长的问题。
为了解决首单耗时较长的问题,我采用了缓存预热的方案,那就是在服务启动的时候进行缓存预热,这样首单中一些接口的调用也是会通过缓存来取值,肯定是可以减少耗时,提高接口的性能,缩短出单的时间。
缓存预热
具体的相关代码如下所示:
package com.xxx.xxx.xx.xxx.service.inner.impl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.List;
import static java.util.stream.Collectors.toList;
/**
* xxx 服务启动时 加载缓存 缓存项为vvv的内容
* @author xxx
*/
@Order(4)
@Component
@Slf4j
public class InitMarketCacheService implements CommandLineRunner {
@Value("#{'${zz.xx.goodsId}'.split(',')}")
private List<String> goodsIdArray;
@Value("#{'${zz.xx.planId}'.split(',')}")
private List<String> planIdArray;
@Value("#{'${zz.xx.productId}'.split(',')}")
private List<String> productIdArray;
@Autowired
private AxxService axxShareService;
@Autowired
private BxxShareService bxxShareService;
@Override
public void run(String... args) throws Exception {
List<Long> goodsIdList = goodsIdArray.stream().map(Long::valueOf).collect(toList());
List<Long> goodsPlanIdList = planIdArray.stream().map(Long::valueOf).collect(toList());
List<Long> productIdList = productIdArray.stream().map(Long::valueOf).collect(toList());
log.warn("begin init xxservice cache");
try {
for (Long goodsId:goodsIdList){
AxxService.queryGoods(goodsId);
}
for (Long planId:goodsPlanIdList){
AxxService.queryPlanAndPackage(planId);
}
for (Long productId:productIdList){
BxxShareService.detail(productId);
}
}catch (Exception e){
log.error("cache warm up failed",e);
}finally {
log.warn("end init xxx service cache");
}
}
}
CommandLineRunner
源码如下:
package org.springframework.boot;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
public interface CommandLineRunner {
void run(String... args) throws Exception;
}
ApplicationRunner
源码如下:
package org.springframework.boot;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}
CommandLineRunner 和 ApplicationRunner 的
CommandLineRunner
中run
方法的参数是一个可变参数列表ApplicationRunner
中run
方法的参数是一个ApplicationArguments
类型的参数
interface
run()
方法,返回值为void
Bean
在spring boot
启动后会自动调用Bean
实现接口,但是调用的顺序可以使用@Order
注解来指定,其中value
属性的值越小,则会优先被调用实现这两个接口的类,在执行run
方法的时候默认是通过主线程的,如果在服务调用的时候,某个服务异常,那么该服务就会启动不起来,为了不影响服务的正常启动,我们可以采用try...catch
的方法对执行run
时的异常进行捕获,这样就不会影响服务的正常启动。