優化背景
1:數據統計類型的項目,用戶量多、業務數據量大、計算邏輯複雜。
4:項目開發工作臨近完成,不宜大改動。
方案
1:針對背景第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 {
//緩存名稱,用於Redis Key的命名。
String cacheName();
//過期時間配置 以秒爲單位,示例(1天):3600*24
int expire();
}
AOP緩存(關鍵)
/**
部份命名空間因爲脫敏,幹掉了。
**/
@Aspect
@Component
public class RedisProcess {
private static final Logger logger = LoggerFactory.getLogger(RedisProcess.class);
@Resource
RedisTemplate<String, Object> redisTemplate;
//可以直接取toString的類型集合。
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[i];
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:定義了緩存的Key格式(方面後期Redis操作)
2:定義過期時間
好的,大功告成。
測試結果:
第一次
[ DEBUG] [2020-03-24 14:56:19] 攔截命中***
[ INFO ] [2020-03-24 14:56:19] - Redis緩存AOP處理所用時間:11
[ INFO ] [2020-03-24 14:56:19] 沒有從Redis查到數據
[ INFO ] [2020-03-24 14:56:19] 執行業務方法
[ INFO ] [2020-03-24 14:56:25] Redis緩存
第二次
[ DEBUG] [2020-03-24 14:59:47] 攔截命中***
[ INFO ] [2020-03-24 14:59:47] 從Redis中查到了數據
[ INFO ] [2020-03-24 14:59:47] - 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] - REDIS的VALUE值:{“data”:[’’],“msg”:“獲取成功”,“result”:1}
PS:
1:第一次沒有命中緩存,耗時6秒
2:第二次命中緩存,耗時小於1秒
總結
1:性能提示顯著,應對多併發輕鬆自如。
2:使用切面編程,業務代碼沒有任何改動,緩存沒有景響到原來的開發進度。