優惠券項目二

整體項目介紹:

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。



發佈了49 篇原創文章 · 獲贊 16 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章