在拦截器里存储接口访问次数:
(两个小问题:1. 统计计数的并行化方案:线程池选择/其他并行化方法;2. 没有统计接口的响应时间,在现有方法下如何直接添加,必须在额外添加一个线程安全的Map进行计算吗?)
package com.highmall.core.interceptor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.google.common.util.concurrent.AtomicLongMap; import com.jfinal.aop.Interceptor; import com.jfinal.aop.Invocation; import com.jfinal.plugin.activerecord.Db; /** * * @author qiww * */ public class ModuleStatisticsInterceptor implements Interceptor { ExecutorService saveAPICountsToDB = Executors.newFixedThreadPool(100);//newSingleThreadExecutor(); public static AtomicLongMap<String> apiCallCountsAtomicLongMap = AtomicLongMap.create(); /** * 统计接口调用次数 */ @Override public void intercept(Invocation inv) { //lambda表达式 saveAPICountsToDB.execute(() -> { String controllerPath = inv.getControllerKey(); String key = controllerPath.substring(1, controllerPath.length()) + ":" + inv.getMethodName(); apiCallCountsAtomicLongMap.incrementAndGet(key); }); inv.invoke(); } public static void saveAPICallCountsToDB(){ for (String key : apiCallCountsAtomicLongMap.asMap().keySet()) { if (apiCallCountsAtomicLongMap.get(key) <= 0) { continue; } String controllerName = key.split(":")[0]; String methodName = key.split(":")[1]; int n = Db.update("UPDATE yw_module_statistics SET count = count + ? WHERE controllerName = ? AND methodName = ? LIMIT 1", apiCallCountsAtomicLongMap.getAndAdd(key, -apiCallCountsAtomicLongMap.get(key)), controllerName, methodName); if (n == 0) { //只有首次访问命中 Db.update("INSERT INTO yw_module_statistics(controllerName, methodName, count, state) values(?,?, 1, 1)"); } } } }
周期性的存储次数到数据库中:
package com.highmall.core.quartz; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import com.highmall.core.interceptor.ModuleStatisticsInterceptor; import com.jfinal.kit.LogKit; /** * 将接口访问次数写入数据库 * @author qiww * */ public class Task_SaveAPICallCountsToDB implements Job { @Override public void execute(JobExecutionContext arg0) throws JobExecutionException { LogKit.info("将接口访问次数写入数据库"); ModuleStatisticsInterceptor.saveAPICallCountsToDB(); } }
数据库存储样例:
有几个小建议:
1:saveAPICountsToDB.execute 中使用 lambda 会更简洁,省好几行代码
2:saveAPICallCountsToDB 中的 findFirst 方法中使用 sql 要添加一个 limit 1 性能才更好
3:saveAPICallCountsToDB 中的逻辑可以改进一下,性能会更高:
int n = Db.update("UPDATE yw_module_statistics SET count = count + ? WHERE controllerName = ? AND methodName = ? LIMIT 1", ...)
if (n == 0) {
Db.update("INSERT INTO....);
}
上面的逻辑是,默认一上来就执行 update ,得到的返回值 n 如果是 0,证明记录不存在,这个时间再执行 insert into
这样做的原因是,绝大部分情况下都会命中前一个 update,只有 action 在第一次被请求时才会命中后面的 insert into,避免掉了每次的 findFirts 判断是非常划算的