埋点?什么是埋点?一觉醒来感觉天塌了,听到这个需求的时候脑子一片空白,后来网上搜索了一下才知道,埋点按照我的理解就是记录用户的行为,以方便后续处理分析用户的访问数据喜好。埋点业务尽可能是异步解耦,即对主流程业务无影响。
埋点的设计思路:
代码埋点、可视化埋点、无埋点(全埋点)
代码埋点:每一个埋点都需要手动去添加代码,要是更新埋点方案,可能都需要改代码,技术人员的累趴下(成本比较大,而且代码容易堆积)
· 可视化埋点:这个就是前端的活了,在页面上设计点击事件,比如:按钮、页面滚动......(我一个java后端不是很了解,大家可以上网搜一下)
无埋点(全埋点):本篇文章就是采用的无埋点的设计,这个设计方案不会修改原始业务代码,采用自定义注解 + AOP + @Async进行实现。
多的不说少的不唠咱们直接怼代码(本人亲自设计,保证能实现埋点功能):
自定义一个注解接口:
/**
* @author YanChengHao
* @data 2024/09/11
* 自定义注解 作用:事件追踪【埋点】
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventTracking {
/**
* 事件
*/
public String event() default "";
/**
* 每次访问的次数
*/
public int count() default 1;
}
实现AOP切面拦截:
package com.zhiwei.rent.config.eventTracking;
import com.zhiwei.rent.service.eventTracking.EventTrackingService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author YanChengHao
* @data 2024/09/11
*/
@Aspect
@Component
public class EventTrackingAspect {
@Autowired
private EventTrackingService eventTrackingService;
@Around("@annotation(eventTracking)")
public Object jointPotin(ProceedingJoinPoint joinPoint, EventTracking eventTracking) throws Throwable {
// 统计方法耗时
long begin = System.currentTimeMillis();
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - begin;
// 耗时:大于等于 1000ms 为 s 单位 ,小于 1000ms 还是为 ms 单位
String timeConsuming = logExecutionTime(duration);
// 进行业务处理 将 eventTracking 信息添入数据库进行记录
eventTrackingService.insertEventTracking(eventTracking.event(),eventTracking.count(),timeConsuming);
return result;
}
// 耗时单位
private String logExecutionTime(long duration) {
if (duration >= 1000) {
double seconds = duration / 1000.0;
return String.format("%.2f s", seconds);
} else {
return String.format("%d ms", duration);
}
}
}
切面类里面有一行代码是插入数据库进行记录的
异步:
@Async("ThreadEventPoolExecutor")
public void insertEventTracking(String event,int count, String timeConsuming) {
// 是否存在事件 如果不存在则插入 如果存在则记录次数加1
EventTracking eventTracking = eventTrackingMapper.findByEvent(event);
if(ObjectUtil.isEmpty(eventTracking)){
// 不存在则去添加数据
int result =eventTrackingMapper.insertEventTracking(event, count, timeConsuming);
log.info(result > 0 ? "事件追踪信息添加成功" : "事件追踪信息添加失败");
}else {
// 存在则去修改数据
int result = eventTrackingMapper.updateEventTracking(count, timeConsuming, eventTracking.getId());
log.info(result > 0 ? "事件追踪信息更新成功" : "事件追踪信息更新失败");
}
}
注解: @Async("ThreadEventPoolExecutor") ThreadEventPoolExecutor是配置的线程池
@Bean(name = "ThreadEventPoolExecutor", destroyMethod = "shutdown")
public ThreadPoolExecutor systemCheckPoolExecutorService() {
return new ThreadPoolExecutor(1, 4, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(10),
new ThreadFactoryBuilder().setNameFormat("default-executor-%d").build(),
(r, executor) -> log.error("system pool is full! "));
}
1:核心线程数
4:最大线程数
new LinkedBlockingQueue<Runnable>(10):阻塞队列的线程数
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
到这里的话已经接近尾声了 最后一步:在对应的方法上添加该注解:
@EventTracking(event = "浏览商品详情页", count = 1)
public ResData<ProductVo> detail(@RequestParam("id") String idTxt)
数据库展示:
注:本人使用jmeter测试,一秒10000访问量数据是不会丢失的