counter计数器

一、为什么要做计数器:

在唯品会的工作的时候,为了更好的推动系统向智慧供应链发展,理性分析供应商和商助对产品的使用频率,追溯服务历史调用情况,于是,开发了一款轻量级计数器,供各团队使用。

二、难点:

1、调用量大

2、各团队需要保证极端情况下,计数器服务不可用时,不影响各团队业务正常运行

3、需要使用简单

4、个别需要统一业务日志

5、不能有额外的定时器一直轮询

三、优势:

1、简单   @LogCounter  对应的类或者方法上 即可开启计数器功能

2、可配置    日志记录可选择开启和关闭、可选择记录到本地、可选择记录到远程服务器

3、统一日志  开启日志功能后,即可对方法的入参和出参进行记录

4、采用了线程池隔离

5、设计了漏斗策略

四、代码:

      https://gitee.com/sunrisexq/counter

      

package com.javaxiaobang.counter.aspect.log;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 功    能:日志注解标签
 * 作    者:java潇邦
 * 时    间:2018-05-19
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface LogCounter {

    String counterName() default "";//计数器名称

    boolean useLogFile() default true;//true时记录log日志

    boolean useRemote() default true;//true时记录counter

    boolean useLocal() default false;//true时开启本地counter

}
package com.javaxiaobang.counter.aspect.log;

import com.alibaba.fastjson.JSON;
import com.javaxiaobang.counter.exception.CounterException;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 功    能:日志AOP
 * 作    者:java潇邦
 * 时    间:2018-05-19
 */
@Order
@Aspect
@Component("commonLogCounterAop")
public class LogCounterAop {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    @Autowired
    private LogCounterLocalBusiness logCounterLocalBusiness;
    @Autowired
    private LogCounterRemoteBusiness logCounterRemoteBusiness;

    //切入点
    @Pointcut("@annotation(com.javaxiaobang.counter.aspect.log.LogCounter)")
    public void anyMethod() {
    }

    //方法执行之前
    @Before("anyMethod()")
    public void before(final JoinPoint joinPoint) {
        try {
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            if (method != null && method.isAnnotationPresent(LogCounter.class)) {
                String methodName = method.getName();
                final LogCounter aspect = method.getAnnotation(LogCounter.class);
                if (aspect.useLogFile()) {
                    logger.info("{}{}方法的参数值:{}", aspect.counterName(), methodName, JSON.toJSONString(joinPoint.getArgs()));
                }
                final String counterName = StringUtils.isBlank(aspect.counterName()) ? methodName : aspect.counterName();
                if (aspect.useLocal()) {
                    pool.submit(new Runnable() {
                        @Override
                        public void run() {
                            logCounterLocalBusiness.execute(counterName);
                        }
                    });
                }
                if (aspect.useRemote()) {
                    pool.submit(new Runnable() {
                        @Override
                        public void run() {
                            logCounterRemoteBusiness.execute(counterName);
                        }
                    });
                }
            }
        } catch (Exception e) {
            logger.debug("before切面异常:", e);
        }
    }

    //方法执行之后
    @AfterReturning(pointcut = "anyMethod()", returning = "retVal")
    public void after(JoinPoint joinPoint, Object retVal) {
        try {
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            if (method != null) {
                String methodName = method.getName();
                LogCounter aspect = method.getAnnotation(LogCounter.class);
                if (aspect != null && aspect.useLogFile()) {
                    logger.info("{}{}方法的返回值:{}", aspect.counterName(), methodName, JSON.toJSONString(retVal));
                }
            }
        } catch (Exception e) {
            logger.debug("after切面异常:", e);
        }
    }

    //方法执行之后异常日志
    @AfterThrowing(pointcut = "anyMethod()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) throws CounterException {
        //不需要再记录ServiceException,因为在service异常切面中已经记录过
        if (e instanceof CounterException) {
            throw (CounterException) e;
        } else {
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            if (method != null) {
                String methodName = method.getName();
                LogCounter aspect = method.getAnnotation(LogCounter.class);
                logger.warn("{}{}方法异常:", aspect.counterName(), methodName, e);
                throw new CounterException();
            }
        }
    }


}
package com.javaxiaobang.counter.aspect.log;

import com.javaxiaobang.counter.util.LogCounterUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 功    能:远程计数器-调用远程服务保存数据
 * 作    者:java潇邦
 * 时    间:2018-05-19
 */
@Component("logCounterRemoteBusiness")
public class LogCounterRemoteBusiness {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static ReentrantLock lock = new ReentrantLock();

    private static String domain = LogCounterUtil.getDomain();

    private static long tryLockTime = 2;//尝试2秒获取锁的时间

    private static long refreshTime = 30000L;//30秒刷新一次

    private static ConcurrentHashMap<String, AtomicLong> countMap = new ConcurrentHashMap<String, AtomicLong>(32);//并发计数

    private static ConcurrentHashMap<String, Long> timeMap = new ConcurrentHashMap<String, Long>(32);//并发计时

    private static long detailRefreshTime = 10 * 60 * 1000L;//默认10分钟刷新数据

    private static ConcurrentHashMap<String, AtomicLong> detailCountMap = new ConcurrentHashMap<String, AtomicLong>(32);//并发计数

    private static ConcurrentHashMap<String, Long> detailTimeMap = new ConcurrentHashMap<String, Long>(32);//并发计时

//    private CounterServiceHelper.CounterServiceClient client = new CounterServiceHelper.CounterServiceClient();//唯品会counter服务-对应两张表

    /**
     * 计数器逻辑处理
     */
    public void execute(String counterName) {
        commonMethod("total", counterName, refreshTime, countMap, timeMap);//计数器头表
        commonMethod("detail", counterName, detailRefreshTime, detailCountMap, detailTimeMap);//计数器明细表
    }

    private void commonMethod(String type, String counterName, long refreshTime, ConcurrentHashMap<String, AtomicLong> countMap, ConcurrentHashMap<String, Long> timeMap) {
        try {
            String key = counterName + domain;//唯一key
            AtomicLong atomicLong = countMap.get(key);
            if (null == atomicLong) {//原子计数器
                synchronized (lock) {//并发控制
                    atomicLong = countMap.get(key);
                    if (null == atomicLong) {
                        countMap.put(key, new AtomicLong(1));
                        timeMap.put(key, System.currentTimeMillis());
                        return;
                    }
                }
            }
            atomicLong.incrementAndGet();//原子计数器加1

            boolean flag = (System.currentTimeMillis() - timeMap.get(key) > refreshTime) && (atomicLong.get() > 0);//超过N秒 并且 次数大于0
            if (flag) {//满足条件的漏斗
                if (lock.tryLock(tryLockTime, TimeUnit.SECONDS)) {
                    try {
                        flag = (System.currentTimeMillis() - timeMap.get(key) > refreshTime) && (atomicLong.get() > 0);//避免错误更新
                        if (flag) {
                            long counter = countMap.get(key).get();
                            timeMap.put(key, System.currentTimeMillis());//重新计时
                            atomicLong.set(0);
                            countMap.put(key, atomicLong);//重新计数
                            if ("detail".equals(type)) {
                                incrementDetailCounter(counterName, domain, counter, refreshTime);
                            } else {
                                incrementCounter(counterName, domain, counter);
                            }
                        }
                    } finally {
                        lock.unlock();
                    }
                } else {
                    logger.warn("2秒内没有获取到锁则放弃,下次再调用远程保存数据的服务:{}", key);
                }
            }
        } catch (Exception e) {
            logger.debug("remote计数器异常:", e);
        }
    }

    /**
     * 保存到counter_info头表
     */
    public void incrementCounter(String counterName, String domain, long counter) {
        //调用远程方法
//        try {
//            CounterInfo counterInfo = new CounterInfo();
//            counterInfo.setCounterName(counterName);
//            counterInfo.setDomain(domain);
//            counterInfo.setCounter(counter);
//            client.incrementCounter(counterInfo);
//        } catch (Exception e) {
//            logger.debug("incrementCounter:", e);
//        }
    }

    /**
     * 保存到counter_detail明细表
     */
    public void incrementDetailCounter(String counterName, String domain, long counter, long interval) {
        //调用远程方法
//        try {
//            CounterInfo counterInfo = new CounterInfo();
//            counterInfo.setCounterName(counterName);
//            counterInfo.setDomain(domain);
//            counterInfo.setCounter(counter);
//            counterInfo.setInterval(interval);
//            client.incrementDetailCounter(counterInfo);
//        } catch (Exception e) {
//            logger.debug("incrementDetailCounter:", e);
//        }
    }

    public static long getRefreshTime() {
        return refreshTime;
    }

    public static void setRefreshTime(long refreshTime) {
        LogCounterRemoteBusiness.refreshTime = refreshTime;
    }

    public static long getDetailRefreshTime() {
        return detailRefreshTime;
    }

    public static void setDetailRefreshTime(long detailRefreshTime) {
        LogCounterRemoteBusiness.detailRefreshTime = detailRefreshTime;
    }

    public static void setDomain(String domain) {
        LogCounterRemoteBusiness.domain = domain;
    }

    public static String getDomain() {
        return domain;
    }


}
package com.javaxiaobang.counter.aspect.log;

import com.javaxiaobang.counter.aspect.delay.LogCounterDelayInterface;
import com.javaxiaobang.counter.util.LogCounterUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 功    能:本地计数器-实现LogCounterDelayInterface接口-保存到本地数据库
 * 作    者:java潇邦
 * 时    间:2018-05-19
 */
@Component("logCounterLocalBusiness")
public class LogCounterLocalBusiness {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static ReentrantLock lock = new ReentrantLock();

    private static String domain = LogCounterUtil.getDomain();

    private static long tryLockTime = 2;//尝试2秒获取锁的时间

    private static long refreshTime = 30000L;//30秒刷新一次

    private static ConcurrentHashMap<String, AtomicLong> countMap = new ConcurrentHashMap<String, AtomicLong>(32);//并发计数

    private static ConcurrentHashMap<String, Long> timeMap = new ConcurrentHashMap<String, Long>(32);//并发计时

    private static long detailRefreshTime = 10 * 60 * 1000L;//默认10分钟刷新数据

    private static ConcurrentHashMap<String, AtomicLong> detailCountMap = new ConcurrentHashMap<String, AtomicLong>(32);//并发计数

    private static ConcurrentHashMap<String, Long> detailTimeMap = new ConcurrentHashMap<String, Long>(32);//并发计时

    //懒加载
    @Autowired(required = false)
    private LogCounterDelayInterface logCounterDelayInterface;

    /**
     * 计数器逻辑处理
     */
    public void execute(String counterName) {
        commonMethod("total", counterName, refreshTime, countMap, timeMap);
        commonMethod("detail", counterName, detailRefreshTime, detailCountMap, detailTimeMap);
    }

    private void commonMethod(String type, String counterName, long refreshTime, ConcurrentHashMap<String, AtomicLong> countMap, ConcurrentHashMap<String, Long> timeMap) {
        try {
            String key = counterName + domain;//唯一key
            AtomicLong atomicLong = countMap.get(key);
            if (null == atomicLong) {//原子计数器
                synchronized (lock) {//并发控制
                    atomicLong = countMap.get(key);
                    if (null == atomicLong) {
                        countMap.put(key, new AtomicLong(1));
                        timeMap.put(key, System.currentTimeMillis());
                        return;
                    }
                }
            }
            atomicLong.incrementAndGet();//原子计数器加1

            boolean flag = (System.currentTimeMillis() - timeMap.get(key) > refreshTime) && (atomicLong.get() > 0);//超过N秒 并且 次数大于0
            if (flag) {//满足条件的漏斗
                if (lock.tryLock(tryLockTime, TimeUnit.SECONDS)) {
                    try {
                        flag = (System.currentTimeMillis() - timeMap.get(key) > refreshTime) && (atomicLong.get() > 0);//避免错误更新
                        if (flag) {
                            long counter = countMap.get(key).get();
                            timeMap.put(key, System.currentTimeMillis());//重新计时
                            atomicLong.set(0);
                            countMap.put(key, atomicLong);//重新计数
                            if ("detail".equals(type)) {
                                logCounterDelayInterface.incrementDetailCounter(counterName, domain, counter, refreshTime);
                            } else {
                                logCounterDelayInterface.incrementCounter(counterName, domain, counter);
                            }
                        }
                    } finally {
                        lock.unlock();
                    }
                } else {
                    logger.warn("2秒内没有获取到锁则放弃,下次再调用保存数据的服务:{}", key);
                }
            }
        } catch (Exception e) {
            logger.info("local计数器异常:", e);
        }
    }

    public static void setDomain(String domain) {
        LogCounterLocalBusiness.domain = domain;
    }

    public static String getDomain() {
        return domain;
    }

    public static long getRefreshTime() {
        return refreshTime;
    }

    public static void setRefreshTime(long refreshTime) {
        LogCounterLocalBusiness.refreshTime = refreshTime;
    }

    public static long getDetailRefreshTime() {
        return detailRefreshTime;
    }

    public static void setDetailRefreshTime(long detailRefreshTime) {
        LogCounterLocalBusiness.detailRefreshTime = detailRefreshTime;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值