整体项目介绍:
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); } } }
配置文件会规定队列的属性值,方便对应生产消费。如下
扫描二维码关注公众号,回复:
148678 查看本文章
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。