整体项目介绍:
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。