优惠券项目二

整体项目介绍:

https://blog.csdn.net/wenjieyatou/article/details/80190886

优惠券项目一介绍:

https://blog.csdn.net/wenjieyatou/article/details/80191083

下面我们来看一下分支二做了哪些方面的优化。

分支1.2

1:加入操作日志
目的:跟踪热点数据,查询日志快速跟踪应用程序中的慢查询或慢操作,为后面的优化奠定基础
2:加入异常日志
目的:快速的获取线程的异常问题,通过日志中的数据能快速修改

3:采用技术通过aop和rabbitmq中间件来做,这样减少由于日志问题给程序带来的效率问题

1:加入操作日志  目的:跟踪热点数据,查询日志快速跟踪应用程序中的慢查询或慢操作,为后面的优化奠定基础

以下代码是操作日志实体。

package com.peiyu.mem.domian.entity;

import java.util.Date;

/**
 * Created by Administrator on 2016/12/19.
 */
public class ActionLog {
    /**
     * 操作记录
     */
    private Long id;
    /**
     * 商家id
     */
    private Long vendorId;
    /**
     * 会员编号
     */
    private String memNo;
    /**
     * 类名
     */
    private String className;
    /**
     * 方法类别(0:添加,1删除,2修改,3查询)
     */
    private int methodType;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 方法参数
     */
    private String methodParam;
    /**
     * 参数值
     */
    private String paramValue;
    /**
     * 方法效率(单位毫秒)
     */
    private Long operationTime;
    /**
     * 操作时间
     */
    private Date createDate;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getVendorId() {
        return vendorId;
    }

    public void setVendorId(Long vendorId) {
        this.vendorId = vendorId;
    }

    public String getMemNo() {
        return memNo;
    }

    public void setMemNo(String memNo) {
        this.memNo = memNo;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public int getMethodType() {
        return methodType;
    }

    public void setMethodType(int methodType) {
        this.methodType = methodType;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getMethodParam() {
        return methodParam;
    }

    public void setMethodParam(String methodParam) {
        this.methodParam = methodParam;
    }

    public String getParamValue() {
        return paramValue;
    }

    public void setParamValue(String paramValue) {
        this.paramValue = paramValue;
    }

    public Long getOperationTime() {
        return operationTime;
    }

    public void setOperationTime(Long operationTime) {
        this.operationTime = operationTime;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }
}

操作日志主要是通过spring的aop切面的形式记录,以下代码是切面的定义:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="actionLogService" class="com.peiyu.mem.service.impl.ActionLogServiceImpl"/>
    <bean id="abnormalLogService" class="com.peiyu.mem.service.impl.AbnormalLogServiceImpl"/>
    <aop:config>
        <aop:aspect ref="actionLogService">
            <aop:pointcut id="insertActionPointCut" expression="execution(* com.peiyu.mem.service.*.insert*(..)) or
             execution(* com.peiyu.mem.manager.*.insert*(..)) orexecution(* com.peiyu.mem.dao.*.insert*(..))"/>
            <aop:around method="insertActionLog" pointcut-ref="insertActionPointCut"/>
        </aop:aspect>
        <aop:aspect ref="actionLogService">
            <aop:pointcut id="deleteActionPointCut" expression="execution(* com.peiyu.mem.service.*.delete*(..)) or
            execution(* com.peiyu.mem.manager.*.delete*(..))or execution(* com.peiyu.mem.dao.*.delete*(..))"/>
            <aop:around method="deleteActionLog" pointcut-ref="deleteActionPointCut"/>
        </aop:aspect>
        <aop:aspect ref="actionLogService">
            <aop:pointcut id="updateActionPointCut" expression="execution(* com.peiyu.mem.service.*.update*(..)) or
            execution(* com.peiyu.mem.dao.*.update*(..)) or execution(* com.peiyu.mem.manager.*.update*(..))"/>
            <aop:around method="updateActionLog" pointcut-ref="updateActionPointCut"/>
        </aop:aspect>
        <aop:aspect ref="actionLogService">
            <aop:pointcut id="getActionPointCut" expression="execution(* com.peiyu.mem.service.*.get*(..)) or
             execution(* com.peiyu.mem.service.CouponService.consumeSendCoupon(..)) or
             execution(* com.peiyu.mem.manager.*.get*(..)) or execution(* com.peiyu.mem.dao.*.get*(..))"/>
            <aop:around method="getActionLog" pointcut-ref="getActionPointCut"/>
        </aop:aspect>
        <aop:aspect ref="abnormalLogService">
            <aop:pointcut id="abnormalLog" expression="execution(* com.peiyu.mem.service.*.*(..)) or
            execution(* com.peiyu.mem.dao.*.*(..))"/>
            <aop:after-throwing method="saveAbnormalLog" throwing="e" pointcut-ref="abnormalLog"/>
        </aop:aspect>
    </aop:config>
</beans>

经过定义后 无非就是aop切面拦截操作,然后将操作插入到数据库。其中Service模块调用DAO模块去实现持久化。

Service模块的代码参考如下:

package com.peiyu.mem.service.impl;

import com.migr.common.util.JsonUtil;
import com.migr.common.util.StringUtils;
import com.peiyu.mem.domian.entity.ActionLog;
import com.peiyu.mem.domian.entity.Member;
import com.peiyu.mem.rabbitmq.produces.MqSenderHandler;
import com.peiyu.mem.service.ActionLogService;
import com.peiyu.mem.utils.ParamUtils;
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.collections.CollectionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Date;

/**
 * Created by Administrator on 2016/12/20.
 */
@Service
public class ActionLogServiceImpl implements ActionLogService {
//日志的插入为了不浪费系统效率 不与用户服务争夺资源,采用消息队列的形式。
    @Autowired
    private MqSenderHandler senderHandler;

    @Override
    public Object insertActionLog(ProceedingJoinPoint joinPoint) throws ClassNotFoundException, NotFoundException {
        return actionLog(joinPoint, 0);
    }

    @Override
    public Object deleteActionLog(ProceedingJoinPoint joinPoint) throws ClassNotFoundException, NotFoundException {
        return actionLog(joinPoint, 1);
    }

    @Override
    public Object updateActionLog(ProceedingJoinPoint joinPoint) throws ClassNotFoundException, NotFoundException {
        return actionLog(joinPoint, 2);
    }

    @Override
    public Object getActionLog(ProceedingJoinPoint joinPoint) throws ClassNotFoundException, NotFoundException {
        return actionLog(joinPoint, 3);
    }


    /**
     * 公共处理操作日志
     *
     * @param joinPoint
     * @param type
     * @return
     */
    protected Object actionLog(ProceedingJoinPoint joinPoint, int type) throws ClassNotFoundException, NotFoundException {
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        StringBuilder methodParam = new StringBuilder();
        StringBuilder pavamValues = new StringBuilder();
        if (!className.contains("dao")) {
            String classType = joinPoint.getTarget().getClass().getName();
            Class<?> clazz = Class.forName(classType);
            String clazzName = clazz.getName();
            String[] methodParams;
            methodParams = ParamUtils.getPavamsName(clazz, clazzName, methodName);
            if (methodParams != null && methodParams.length > 0) {
                for (String str : methodParams) {
                    methodParam.append(str + ",");
                }
            }
        }
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            for (Object obj : args) {
                String typeName = obj.getClass().getName();
                if (ParamUtils.isBasicType(typeName)) {
                    pavamValues.append(obj + ",");
                } else {
                    pavamValues.append(ParamUtils.getFieldsValue(obj) + ",");
                }
            }
        }
        ActionLog actionLog = new ActionLog();
        actionLog.setVendorId(0l);
        actionLog.setMemNo("0");
        actionLog.setClassName(className);
        actionLog.setMethodName(methodName);
        actionLog.setMethodType(type);
        actionLog.setCreateDate(new Date());
        actionLog.setMethodParam(methodParam.toString());
        actionLog.setParamValue(pavamValues.toString());
        long start = System.currentTimeMillis();
        Object obj = null;
        try {
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        long end = System.currentTimeMillis();
        actionLog.setOperationTime(end - start);
        String data = JsonUtil.g.toJson(actionLog);
        senderHandler.sendMessage("spring.actionLog.queueKey", data);
        return obj;
    }
}

以下代码相当于向消息队列发送消息:即担当任务的生产者。会持有队列去保存消息,然后等待消费端处理。

package com.peiyu.mem.rabbitmq.produces;

import org.apache.log4j.Logger;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created by Administrator on 2016/12/8.
 */
@Component
public class MqSenderHandler {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    private Logger log = Logger.getLogger(MqSenderHandler.class);

    /**
     * 发送信息
     *
     * @param messageInfo
     */
    public void sendMessage(String routingKey,Object messageInfo) {
        try {
            rabbitTemplate.convertAndSend(routingKey,messageInfo);
        } catch (Exception e) {
            log.error("发送消息失败"+e);
        }
    }
}

配置文件会规定队列的属性值,方便对应生产消费。如下

rabbit.hosts=localhost
rabbit.port=5672
rabbit.username=guest
rabbit.password=guest
rabbit.virtualHost=/
rabbit.exchange.direct=spring.exchange.direct
#rabbitmq队列设置
#制券相关属性
rabbit.makeCoupons.queue=spring.makeCoupons.queue
rabbit.makeCoupons.routingKey=spring.makeCoupons.queueKey
#操作日志相关属性
rabbit.actionLog.queue=spring.actionLog.queue
rabbit.actionLog.routingKey=spring.actionLog.queueKey
#异常日志相关属性
rabbit.abnormalLog.queue=spring.abnormalLog.queue
rabbit.abnormalLog.routingKey=spring.abnormalLog.queueKey
#更新券状态相关属性
rabbit.updateCouponState.queue=spring.updateCouponState.queue
rabbit.updateCouponState.routingKey=spring.updateCouponState.queueKey

消息队列的配置参考代码,方法绑定等:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <import resource="spring-rabbitmq-share.xml"/>
    <import resource="spring-dao-rabbitmq.xml"/>
    <context:component-scan base-package="com.peiyu.mem.rabbitmq.consumers"/>
    <!--制券-->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="makeCouponsHandler1" method="onMessage" queues="spring.makeCoupons.queue"/>
    </rabbit:listener-container>
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="makeCouponsHandler2" method="onMessage" queues="spring.makeCoupons.queue"/>
    </rabbit:listener-container>
    <!--操作日志-->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="actionLogHandler1" method="onMessage" queues="spring.actionLog.queue"/>
    </rabbit:listener-container>
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="actionLogHandler2" method="onMessage" queues="spring.actionLog.queue"/>
    </rabbit:listener-container>
    <!--异常日志-->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="abnormalLogHandler1" method="onMessage" queues="spring.abnormalLog.queue"/>
    </rabbit:listener-container>
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="abnormalLogHandler2" method="onMessage" queues="spring.abnormalLog.queue"/>
    </rabbit:listener-container>
    <!--更新券状态-->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="updateCouponStateHandler1" method="onMessage" queues="spring.updateCouponState.queue"/>
    </rabbit:listener-container>
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="updateCouponStateHandler2" method="onMessage" queues="spring.updateCouponState.queue"/>
    </rabbit:listener-container>
</beans>

消息队列的消费端会持有两个服务器,避免一个挂掉风险。处理逻辑是:

package com.peiyu.mem.rabbitmq.consumers;

import com.migr.common.util.JsonUtil;
import com.migr.common.util.StringUtils;
import com.peiyu.mem.dao.ActionLogDao;
import com.peiyu.mem.domian.entity.ActionLog;
import com.peiyu.mem.rabbitmq.Gson2JsonMessageConverter;
import com.rabbitmq.client.Channel;
import org.apache.log4j.Logger;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created by Administrator on 2016/12/19.
 */
@Component
public class ActionLogHandler1 implements ChannelAwareMessageListener {
    private Logger log = Logger.getLogger(ActionLogHandler1.class);
    @Autowired
    private ActionLogDao actionLogDao;
    @Autowired
    private Gson2JsonMessageConverter jsonMessageConverter;

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        try {
            channel.basicQos(1);
            if (message == null || message.getBody() == null) {
                return;
            }
            String data = jsonMessageConverter.fromMessage(message).toString();
            if (StringUtils.isNotBlank(data)) {
                ActionLog actionLog = JsonUtil.g.fromJson(data, ActionLog.class);
                actionLogDao.insert(actionLog);
            }
        } catch (Exception e) {
            log.error("操作日志异常" + e);
        }
    }
}
2:加入异常日志

目的:快速的获取线程的异常问题,通过日志中的数据能快速修改

这部分的逻辑和加入操作日志一样。spring切面的配置,消息队列的配置参考上面代码。实体代码如下:

package com.peiyu.mem.domian.entity;

import java.util.Date;

/**
 * Created by Administrator on 2016/12/19.
 * 异常日志
 */
public class AbnormalLog {
    /**
     * 操作记录
     */
    private Long id;
    /**
     * 商家id
     */
    private Long vendorId;
    /**
     * 会员编号
     */
    private String memNo;
    /**
     * 类名
     */
    private String className;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 方法参数
     */
    private String methodParam;
    /**
     * 参数值
     */
    private String paramValue;
    /**
     * 异常信息
     */
    private String abnormalInfo;
    /**
     * 方法效率(单位毫秒)
     */
    private Long operationTime;
    /**
     * 操作时间
     */
    private Date createDate;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getVendorId() {
        return vendorId;
    }

    public void setVendorId(Long vendorId) {
        this.vendorId = vendorId;
    }

    public String getMemNo() {
        return memNo;
    }

    public void setMemNo(String memNo) {
        this.memNo = memNo;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getMethodParam() {
        return methodParam;
    }

    public void setMethodParam(String methodParam) {
        this.methodParam = methodParam;
    }

    public String getParamValue() {
        return paramValue;
    }

    public void setParamValue(String paramValue) {
        this.paramValue = paramValue;
    }

    public Long getOperationTime() {
        return operationTime;
    }

    public void setOperationTime(Long operationTime) {
        this.operationTime = operationTime;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public String getAbnormalInfo() {
        return abnormalInfo;
    }

    public void setAbnormalInfo(String abnormalInfo) {
        this.abnormalInfo = abnormalInfo;
    }
}
package com.peiyu.mem.service;

import javassist.NotFoundException;
import org.aspectj.lang.JoinPoint;

/**
 * Created by Administrator on 2016/12/21.
 */
public interface AbnormalLogService {
    /**
     *记录异常日志
     * @param joinPoint
     */
    void saveAbnormalLog(JoinPoint joinPoint,Exception e) throws ClassNotFoundException, NotFoundException;

}
package com.peiyu.mem.rabbitmq.consumers;

import com.migr.common.util.JsonUtil;
import com.migr.common.util.StringUtils;
import com.peiyu.mem.dao.AbnormalLogDao;
import com.peiyu.mem.domian.entity.AbnormalLog;
import com.peiyu.mem.rabbitmq.Gson2JsonMessageConverter;
import com.rabbitmq.client.Channel;
import org.apache.log4j.Logger;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created by Administrator on 2016/12/22.
 */
@Component
public class AbnormalLogHandler2 implements ChannelAwareMessageListener {
    private Logger log = Logger.getLogger(AbnormalLogHandler2.class);
    @Autowired
    private AbnormalLogDao abnormalLogDao;
    @Autowired
    private Gson2JsonMessageConverter jsonMessageConverter;

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        try {
            channel.basicQos(1);
            if (message == null || message.getBody() == null) {
                return;
            }
            String data = jsonMessageConverter.fromMessage(message).toString();
            if (StringUtils.isNotBlank(data)) {
                AbnormalLog abnormalLog = JsonUtil.g.fromJson(data, AbnormalLog.class);
                abnormalLogDao.insert(abnormalLog);
            }
        } catch (Exception e) {
            log.error("操作日志异常" + e);
        }
    }
}
3:采用技术通过aop和rabbitmq中间件来做,这样减少由于日志问题给程序带来的效率问题。具体描述请参考1。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值