【Redis】redis异步消息队列+Spring自定义注解+AOP方式实现系统日志持久化
说明:
SSM项目中的每一个请求都需要进行日志记录操作。一般操作做的思路是:使用springAOP思想,对指定的方法进行拦截。拼装日志信息实体,然后持久化到数据库中。可是仔细想一下会发现:每次的客户端的每一次请求,服务器都会处理两件事情。一个是正常的业务操作;另一个就是我们额外要做的日志数据记录。这样的话,每次请求的“效率”就变得收到影响了,换句话说就是“耦合”了。明明一个请求是干一件特定的事情,你却又给我加上一部分东西。而且这一次请求是必须在额外做的事情做完才能返回。面向切面 编程就是为了“解耦”的。所以想到了日志持久化这个动作使用异步处理方式,不当误真正的请求效率。(这一段写的可能有点luan,大家先将就着看)。
分析:
① 异步消息队列中有【消费者】和【生产者两个角色】。生产者负责产生消息,并放入队列中。消费者负责监听队列,一旦队列中有新的消息了,取出后根据消息的类型选择对应的业务处理操作。
② 消费者在这里是在系统启动的时候,启动一个线程,对redis指定的key进行监听。使用redis的指令brpop阻塞指令进行监听对应的list。
环境:
jdk1.8、maven、idea、jedis3.2、mysql数据库
代码:
自定义注解:
/**
* 自定义系统日志注解
* @author 魏正迪
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SysLog {
/**
* 操作描述
* @return
*/
String value() default ""; /**
* 日志类型
* @return
*/
short type();
}
AOP切面
/**
* @author wzd
* @data 2018/03/06
* 系统日志切面
*/
@Component
@Aspect
public class LogAspect { @Autowired
private ILogService logService;
@Autowired
private JedisClientPool jedisClientPool;
/**
* 自动注册当前线程的request对象
*/
@Autowired
private HttpServletRequest request; /**
* 日志的切点
*/
@Pointcut("@annotation(top.oldwei.common.annotation.SysLog)")
public void logPoint(){ } /**
* 日志采用环绕通知来进行处理
* @param point
* @return
* @throws Throwable
*/
@Around("logPoint()")
public Object around(ProceedingJoinPoint point)throws Throwable{
// 执行方法之前
UserEntity currentUser = ShiroUtils.getUserEntity();
long start = SystemClock.now();
Object result = point.proceed();
long end = SystemClock.now();
saveSysLog(point,end-start,currentUser);
return result;
} /**
* 保存日志操作
* @param point
* @param time
* @param userEntity
*/
private void saveSysLog(ProceedingJoinPoint point,long time ,UserEntity userEntity){
try{
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
LogEntity logEntity = new LogEntity();
logEntity.setId(IdWorker.getId());
SysLog syslog = method.getAnnotation(SysLog.class);
if(StringUtils.checkValNotNull(syslog)){
// 注解的value
logEntity.setOperation(syslog.value());
// 注解的type
logEntity.setType(syslog.type());
}
// 调用的方法
logEntity.setMethod(point.getTarget().getClass().getName()+"."+method.getName()+"()");
logEntity.setIp(IpUtils.getIpAddr(request));
logEntity.setTime(time);
// 请求参数
Object [] args = point.getArgs();
try{
logEntity.setParams(JSON.toJSON(args[0]).toString());
}catch (Exception e){}
if(StringUtils.checkValNotNull(userEntity)){
// 创建人
logEntity.setCreateByCode(userEntity.getUserCode());
logEntity.setCreateByName(userEntity.getUserName());
}else{
// 登录操作时,方法执行后才能获取用户信息
userEntity = ShiroUtils.getUserEntity();
if(StringUtils.checkValNotNull(userEntity)){
logEntity.setCreateByCode(userEntity.getUserCode());
logEntity.setCreateByName(userEntity.getUserName());
}else{
logEntity.setCreateByCode("");
logEntity.setCreateByName("");
}
}
logEntity.setCreateDate(new Date());
// 使用redis异步队列方式进行保存日志
//logService.save(logEntity);
TaskEntity taskEntity = new TaskEntity();
taskEntity.setTaskType(TaskType.LOG_TASK);
taskEntity.setData(JSON.toJSONString(logEntity));
jedisClientPool.lpush(JedisConstants.AYSC_TASK_KEY,JSON.toJSONString(taskEntity));
taskEntity.setTaskType(TaskType.MAIL_TASK);
jedisClientPool.lpush(JedisConstants.AYSC_TASK_KEY,JSON.toJSONString(taskEntity));
}catch (Exception e){
e.printStackTrace();
}
}
}
消息实体类
/**
* 任务实体类
* @author wzd
* @date 2018/04/01
*/
public class TaskEntity implements Serializable {
/**
* 任务的唯一性编码
*/
private Long id;
/**
* 任务类型,通过类型找到对应任务处理器进行处理
*/
private TaskType taskType;
/**
* 需要传输的数据 json格式的
*/
private String data; public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public TaskType getTaskType() {
return taskType;
} public void setTaskType(TaskType taskType) {
this.taskType = taskType;
} public String getData() {
return data;
} public void setData(String data) {
this.data = data;
}
}
消费者:启动注册消费者任务处理器多个。监听队列,取出任务根据任务类型选择对应的 任务处理器进行相应处理。
/**
* redis 队列消费者
* 容器启动时加载并启动相应的线程,进行阻塞读取redis
* 对应的任务队列。根据任务的类型选择对应的任务处理器进行处理。
* @author wzd
* @data 2018/04/01
*/
@Component
public class TaskConstomer implements InitializingBean, ApplicationContextAware {
/**
* spring上下文
*/
private ApplicationContext applicationContext;
/**
* 加载所有的任务处理器
*/
private Map<TaskType, List<TaskHandler>> config = new HashMap<>();
/**
* redis操作
*/
@Autowired
private JedisClientPool jedisClientPool; @Override
public void afterPropertiesSet() throws Exception {
// 获取系统所有实现TaskHandler的任务处理器
Map<String,TaskHandler> handlers = applicationContext.getBeansOfType(TaskHandler.class);
if(StringUtils.checkValNotNull(handlers)){
for(Map.Entry<String,TaskHandler> entry:handlers.entrySet()){
List<TaskType> supportTaskTypes = entry.getValue().getTaskType();
for(TaskType taskType:supportTaskTypes){
if(!config.containsKey(taskType)){
config.put(taskType,new ArrayList<TaskHandler>());
}
config.get(taskType).add(entry.getValue());
}
}
}
// 启动线程
// 构建线程池 ExecutorService executorService = Executors.newCachedThreadPool();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true){
List<String> task = jedisClientPool.brpop(10, JedisConstants.AYSC_TASK_KEY);
if(StringUtils.checkValNotNull(task) && task.size()>1 ){
TaskEntity entity = JSON.parseObject(task.get(1),TaskEntity.class);
if(config.containsKey(entity.getTaskType())){
for(TaskHandler handler:config.get(entity.getTaskType())){
handler.doTask(entity);
}
}
}
}
}
});
thread.start(); } @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
任务处理器接口:
/**
* @author wzd
* 异步任务通用接口
*/
public interface TaskHandler {
/**
* 执行任务
* @param taskEntity
*/
void doTask(TaskEntity taskEntity); /**
* 任务类型
*
* @return
*/
default List<TaskType> getTaskType(){
return new ArrayList<>();
}
}
日志任务
/**
* 日志处理任务
* @author wzd
*/
@Component
public class LogTaskHandler implements TaskHandler { @Autowired
private ILogService logService; @Override
public void doTask(TaskEntity taskEntity) {
try{
LogEntity logEntity = JSON.parseObject(taskEntity.getData(),LogEntity.class);
logService.save(logEntity);
}catch (Exception e){}
} @Override
public List<TaskType> getTaskType() {
return Arrays.asList(TaskType.LOG_TASK);
}
}
发送邮件任务
/**
* @author wzd
* 发送短信的异步队列任务
*/
@Component
public class MailTaskHandler implements TaskHandler{ @Autowired
private MailMessageHandler mailMessageHandler; @Override
public void doTask(TaskEntity taskEntity) {
// 进行发送短信的业务逻辑
try{
mailMessageHandler.doSend(null);
}catch (Exception e){
e.printStackTrace();
}
} @Override
public List<TaskType> getTaskType() {
return Arrays.asList(TaskType.MAIL_TASK);
}
}
、、、、、
其他的任务实现接口即可。
特殊说明:以上代码需要重构的地方很多,仅给大家参考思路。也欢迎指正
【Redis】redis异步消息队列+Spring自定义注解+AOP方式实现系统日志持久化的更多相关文章
- SpringCloud微服务实战——搭建企业级开发框架(三十九):使用Redis分布式锁(Redisson)+自定义注解+AOP实现微服务重复请求控制
通常我们可以在前端通过防抖和节流来解决短时间内请求重复提交的问题,如果因网络问题.Nginx重试机制.微服务Feign重试机制或者用户故意绕过前端防抖和节流设置,直接频繁发起请求,都会导致系统防重 ...
- ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存
基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...
- Spring Cloud(7):事件驱动(Stream)分布式缓存(Redis)及消息队列(Kafka)
分布式缓存(Redis)及消息队列(Kafka) 设想一种情况,服务A频繁的调用服务B的数据,但是服务B的数据更新的并不频繁. 实际上,这种情况并不少见,大多数情况,用户的操作更多的是查询.如果我们缓 ...
- php和redis怎么实现消息队列
把瞬间服务器的请求处理换成异步处理,缓解服务器的压力,实现数据顺序排列获取.本文主要和大家分享php和redis如何实现消息队列,希望能帮助到大家. redis实现消息队列步骤如下: 1).redis ...
- Redis 学习笔记(六)Redis 如何实现消息队列
一.消息队列 消息队列(Messeage Queue,MQ)是在分布式系统架构中常用的一种中间件技术,从字面表述看,是一个存储消息的队列,所以它一般用于给 MQ 中间的两个组件提供通信服务. 1.1 ...
- Redis+php-resque实现消息队列
服务器硬件配置 Dell PowerEdge R310英特尔单路机架式服务器 Intel Xeon Processor X3430 2.4GHz, 8MB Cache 8GB内存(2 x 4GB) ...
- 如何使用NODEJS+REDIS开发一个消息队列
作者: RobanLee 原创文章,转载请注明: 萝卜李 http://www.robanlee.com MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应 ...
- Delayer 基于 Redis 的延迟消息队列中间件
Delayer 基于 Redis 的延迟消息队列中间件,采用 Golang 开发,支持 PHP.Golang 等多种语言客户端. 参考 有赞延迟队列设计 中的部分设计,优化后实现. 项目链接:http ...
- 八.利用springAMQP实现异步消息队列的日志管理
经过前段时间的学习和铺垫,已经对spring amqp有了大概的了解.俗话说学以致用,今天就利用springAMQP来完成一个日志管理模块.大概的需求是这样的:系统中有很多地方需要记录操作日志,比如登 ...
随机推荐
- 「CTSC 2013」组合子逻辑
Tag 堆,贪心 Description 给出一个数列 \(n\) 个数,一开始有一个括号包含 \([1,n]\),你需要加一些括号,使得每个括号(包括一开始的)所包含的元素个数 \(\leq\) 这 ...
- Pytorch编程记录
搭建网络的方式: 1.用sequential方式搭建,只能适用于线性网络 2.用forward和init方式搭建
- 使用Webpack构建多页面程序
使用webpack搭建单页面程序十分常见,但在实际开发中我们可能还会有开发多页面程序的需求,因此我研究了一下如何使用webpack搭建多页面程序. 原理 将每个页面所在的文件夹都看作是一个单独的单页面 ...
- HarmonyOS三方件开发指南(14)-Glide组件功能介绍
<HarmonyOS三方件开发指南>系列文章合集 引言 在实际应用开发中,会用到大量图片处理,如:网络图片.本地图片.应用资源.二进制流.Uri对象等,虽然官方提供了PixelMap进行图 ...
- 通过 ASM 库生成和修改 class 文件
在 JVM中 Class 文件分析 主要详细讲解了Class文件的格式,并且在上一篇文章中做了总结. 众所周知,JVM 在运行时, 加载并执行class文件, 这个class文件基本上都是由我们所写的 ...
- 13 条高效实用的 JavaScript 单行代码
JavaScript可以实现很多令人惊奇的事! 从复杂的框架到处理API,有太多的东西可以学习. 甚至,仅用一行代码,它也能完成一些很棒的工作. 不信?那么请看这13条JavaScript单行代码,用 ...
- (十二)struts2的类型转换
所有的MVC框架,都属于表现层的解决方案,都需要负责收集用户请求参数,并将请求参数传给应用的控制器组件. 这时问题出现了,所有的请求参数都是字符串类型数据,因此MVC框架必须具备将这些字符串请求参数转 ...
- 一个诡异的MySQL查询超时问题,居然隐藏着存在了两年的BUG
这一周线上碰到一个诡异的BUG. 线上有个定时任务,这个任务需要查询一个表几天范围内的一些数据做一些处理,每隔十分钟执行一次,直至成功. 通过日志发现,从凌晨5:26分开始到5:56任务执行了三次,三 ...
- RE.从单链表开始的数据结构生活(bushi
单链表 单链表中节点的定义 typedef struct LNode{ int data;//数据域 struct LNode *next;//定义一个同类型的指针,指向该节点的后继节点 }LNode ...
- jasypt-spring-boot提示Failed to bind properties
1 问题描述 在Spring Boot中使用jasypt-spring-boot进行加密,但是提示: Description: Failed to bind properties under 'spr ...