jFinal统计接口访问次数并写入库中

在拦截器里存储接口访问次数:

(两个小问题: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();
		
	}

}


数据库存储样例:

image.png

评论区

JFinal

2018-06-03 22:09

ExecutorService 与 AtomicLongMap 配合的使用性能会非常高,这个是亮点

有几个小建议:
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 判断是非常划算的

JFinal

2018-06-03 22:09

我自己也点赞收藏一个,ExecutorService 与 AtomicLongMap 配合确实是个高招

laofa

2019-11-09 23:35

apiCallCountsAtomicLongMap.getAndAdd(key, -apiCallCountsAtomicLongMap.get(key)) 这个要怎么解

KevinQWW

2019-11-15 15:49

@laofa 获取内存中的当前值返回,内存中的值减掉返回的值(归零)。