项目背景
1:用户群体大,
2:业务计算量大,计算逻辑复杂。
现状态
1:主面页面,嵌套多个报表,部分报表加载时间长。
2:项目已经临近开发完成,准备移交测试。
3:如果压力测试必然会死得很难看。
方案
针对目前的情况,我准备了一个优化方案
1:使用Redis把复用性高的结果缓存起来。
2:使用 注解+AOP的技术,达到代码修改最小化。
我用思维导致整理了整个设计的思维。
代码实现
Redis配置
spring.redis.host=172.26.175.74
spring.redis.port=6379
spring.redis.password=admin
#0:不启用 1:启用
report.redis.enable=1
注解
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCashe {
//缓存名称
String cacheName();
//过期时间 以秒为单位,示例(1天):3600*60*24
int expire();
}
AOP缓存(关键)
/**
部份命名空间因为脱敏,干掉了。
**/
@Aspect
@Component
public class RedisProcess {
private static final Logger logger = LoggerFactory.getLogger(RedisProcess.class);
@Resource
RedisTemplate<String, Object> redisTemplate;
//可以直接取otString的类型集合。
private static final List<String> typeList;
static {
ArrayList<String> rs =new ArrayList<>();
rs.add(String.class.getTypeName());
rs.add(int.class.getTypeName());
rs.add(Integer.class.getTypeName());
rs.add(double.class.getTypeName());
rs.add(BigDecimal.class.getTypeName());
rs.add(boolean.class.getTypeName());
rs.add(byte.class.getTypeName());
rs.add(Date.class.getTypeName());
rs.add(Long.class.getTypeName());
typeList= rs;
}
/**
* 拦截所有元注解RedisCache注解的方法
*/
@Pointcut("@annotation(com.config.RedisCashe)")
public void pointcutMethod() {
logger.debug("************拦截命中***************");
}
@Value("${report.redis.enable}")
private int reportCacheEnable ;
/**
* 流程
* 1:检查是否有缓存,有缓存直接返回
* 2:执行业务
* 3:结果缓存
* 4:返回结果
* @param joinPoint
* @return
*/
@Around("pointcutMethod()")
public RestResult around(ProceedingJoinPoint joinPoint) {
//前置:从Redis里获取缓存
//先获取目标方法参数
logger.debug("************拦截命中***************");
long startTime = System.currentTimeMillis();
Object cdn = joinPoint.getArgs()[0];
String key = getKey(joinPoint);
if (reportCacheEnable==1 && redisTemplate.hasKey(key)) {
Object obj = redisTemplate.opsForValue().get(key);
logger.info("Redis的KEY值:" + key);
logger.info("**********从Redis中查到了数据**********");
return (RestResult) obj;
}
logger.info("Redis缓存命中耗时:" + (System.currentTimeMillis() - startTime));
logger.info("**********没有从Redis查到数据**********");
try {
logger.info("**********执行业务方法**********");
RestResult rs = (RestResult) joinPoint.proceed();
logger.info("**********Redis缓存**********");
if(reportCacheEnable==1)
{
RedisCashe cacheCfg =getCacheConfig(joinPoint);
redisTemplate.opsForValue().set(key,rs,cacheCfg.expire(), TimeUnit.SECONDS);
}
logger.info("Redis缓存未命中耗时:" + (System.currentTimeMillis() - startTime));
return rs;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
private RedisCashe getCacheConfig(ProceedingJoinPoint joinPoint)
{
String methodName =joinPoint.getSignature().getName();
RedisCashe rs ;
Method[] methods =joinPoint.getTarget().getClass().getMethods();
for(Method m :methods)
{
if(methodName.equals(m.getName()) )
{
Annotation[] annotations =m.getDeclaredAnnotations();
for(Annotation a: annotations)
if(RedisCashe.class.isInstance(a))
{
rs = ( RedisCashe) a;
return rs;
}
}
}
return null;
}
//定义Key的格式,示例:RptCacheName:getDphuDatePassPrc@2018-01-01@1@abc
private String getKey(ProceedingJoinPoint joinPoint)
{
Object[] args = joinPoint.getArgs();
RedisCashe cacheCfg =getCacheConfig(joinPoint);
String key="RptCacheName:"+cacheCfg.cacheName();
for(int i=0;i< args.length;i++)
{
Object o=args[0];
String oType =o.getClass().getTypeName();
if(typeList.contains(oType))
{
key+="@"+ o.toString();
}
else
{
//类对象的使用JSON转换
key+="@"+ new Gson().toJson(o);
}
}
return key;
}
}
业务调用(部分)
@Service("dphuRptBiz")
public class DphuRptBiz implements IDphuRptBiz {
@Autowired
DphuRptMapper mapper;
@Override
@RedisCashe( cacheName = "getDphuDatePassPrc", expire = 3600 * 60 * 24)
public RestResult<List<DphuInputResult>> getDphuDatePassPrc(DphuInput inp) {
inp.setResult(new ArrayList<DphuInputResult>());
RestResult<List<DphuInputResult>> rs=new RestResult<>();
rs.setResult(1);
mapper.getDphuDatePassPassRpt(inp);
rs.setMsg("获取成功");
rs.setData(inp.getResult());
return rs;
}
@Override
@RedisCashe( cacheName = "getDphuWorstPrc", expire = 3600 * 60 * 24)
public RestResult<List<DphuWorstInputResult>> getDphuWorstPrc(Date vCurrentDate, String vSumaryType, String vGroupBy, String vWhere, String vTop) {
DphuWorstInput inp = new DphuWorstInput();
inp.setvCurrentDate(vCurrentDate);
inp.setvSumaryType(vSumaryType);
inp.setvGroupBy(vGroupBy);
inp.setvWhere(vWhere);
inp.setvTop(vTop);
inp.setResult(new ArrayList<DphuWorstInputResult>());
RestResult<List<DphuWorstInputResult>> rs=new RestResult<>();
rs.setResult(1);
mapper.getDphuWorstRpt(inp);
rs.setMsg("获取成功");
rs.setData(inp.getResult());
return rs;
}
}
关键是注解: @RedisCashe( cacheName = “getDphuDatePassPrc”, expire = 3600 * 60 * 24)
1:定义了缓存头(方面后期Redis操作)
2:定义过期时间
好上,以上大功告成。
测试结果:
第一次
[ DEBUG] [2020-03-24 14:56:19] com.ly.mp.busicen.mec.config.RedisProcess [60] - 拦截命中***
[ INFO ] [2020-03-24 14:56:19] com.ly.mp.busicen.mec.config.RedisProcess [71] - Redis缓存AOP处理所用时间:11
[ INFO ] [2020-03-24 14:56:19] com.ly.mp.busicen.mec.config.RedisProcess [73] - 没有从Redis查到数据
[ INFO ] [2020-03-24 14:56:19] com.ly.mp.busicen.mec.config.RedisProcess [75] - 执行业务方法
[ INFO ] [2020-03-24 14:56:25] com.ly.mp.busicen.mec.config.RedisProcess [78] - Redis缓存
第二次
[ DEBUG] [2020-03-24 14:59:47] com.ly.mp.busicen.mec.config.RedisProcess [60] - 拦截命中***
[ INFO ] [2020-03-24 14:59:47] com.ly.mp.busicen.mec.config.RedisProcess [66] - 从Redis中查到了数据
[ INFO ] [2020-03-24 14:59:47] com.ly.mp.busicen.mec.config.RedisProcess [67] - Redis的KEY值:Base{rptName=‘getDphuDatePassPrc’, rptCondition={“result”:[],“vCurDate”:“2020-01-01 00:00:00”,“vGroupBy”:“factory_code”,“vSumaryType”:“10”,“vTop”:“4”,“vWhere”:" 1=1 "}}
[ INFO ] [2020-03-24 14:59:47] com.ly.mp.busicen.mec.config.RedisProcess [68] - REDIS的VALUE值:{“data”:[’’],“msg”:“获取成功”,“result”:1}
测试结果
1:第一次没有命中缓存,耗时6秒
2:第二次命中缓存,耗时小于1秒
总结
1:性能的提升,统计页面秒开了,感觉到这就是技术的力量。
2:代码改动小,原来的代码保持原样,只需注解就可以。
大功告功,接下来让团队成员照着改。