微服务日志功能的实现rabbitmq+elasticsearch+aop

1 篇文章 0 订阅
1 篇文章 0 订阅

微服务日志



前言

最近接到一个任务需要实现微服务下的日志功能,因为和单服务不同,根据自己的经验和网上查找到的资料,我想到了3种方式来实现,第一个就是ELK微服务实现日志框架,网上基本也是用这个做的。
关于ELK的实现在下一章进行实现。
ELK框架虽然很强大,各种可视化界面非常完善,但是做日志我们任然习惯想单体服务那样来实现,应为我想到2种解决方案。
1.通过公共模块设置切面,再利用微服务的特性,使用feign调用日志模块的接口保存日志。
2.通过公共模块设置切面,再通过消息队列保存日志信息,日志模块接收消息,异步处理。
优缺点: 虽然feign这个方法能达到同步的效果,但是对于日志功能来说,对同步的需求要求低,并且微服务下模块多,记录的日志多,访问大,使用feign调用会占用较多的资源,导致服务器卡顿,因为我选用消息队列的方式来异步实现。


一、消息列表和日志模块


1.消息列队和微服务

首先写这个功能前需要了解微服务和消息队列的一些基本知识,在这里我用的是rabbitMq和springcloud alibaba

2.公共模块设置切面

创建公共模块,并在日志模块引入依赖,我使用的是注解做切面,核心切面类,代码如下:

package com.kbplus.demo.common.config;

import com.kbplus.demo.common.annotation.SystemLog;
import com.kbplus.demo.common.entity.MyLog;
import com.kbplus.demo.common.sender.ModuleRequestSender;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.UUID;

/**
 * @author kbplus
 * @date 2022-03-28
 * @blog https://blog.csdn.net/cyy9487
 */
@Order(1)
@Component
@Aspect
public class LogAspect {

    @Autowired
    private ModuleRequestSender moduleRequestSender;

    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.kbplus.demo.common.annotation.SystemLog)")
    public void doAspect() {
    }

    @Around("doAspect()")
    public Object doAround(ProceedingJoinPoint pjd) throws Throwable {

        ServletRequestAttributes attributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(attributes==null){
            return null;
        }
        HttpServletRequest request = attributes.getRequest();

        //获取方法注释
        String operation="";
        String logType="";
        Signature signature=pjd.getSignature();
        if (signature instanceof MethodSignature) {
            Class<?> targetCls=pjd.getTarget().getClass();
            MethodSignature ms= (MethodSignature)signature;
            Method targetMethod=targetCls.getDeclaredMethod(ms.getName(),ms.getParameterTypes());
            SystemLog requiredLog=targetMethod.getAnnotation(SystemLog.class);
            if(requiredLog!=null) {
                operation = requiredLog.description();
                logType = requiredLog.type().getName();
            }
        }

        List<String> params = new ArrayList<>();

        //获取参数
        Enumeration<String> enu = request.getParameterNames();
        if(enu.hasMoreElements()) {
            while (enu.hasMoreElements()) {
                String paraName = enu.nextElement();
                params.add(paraName + ": " + request.getParameter(paraName));
            }
        }else {
            Object[] args = pjd.getArgs();
            for (Object arg : args) {
                params.add(arg.toString());
            }
        }


        MyLog myLog = new MyLog();
        myLog.setId(UUID.randomUUID().toString());
        myLog.setMethodType(request.getMethod());
        myLog.setType(logType);
        myLog.setParams(params);
        myLog.setModuleName(request.getContextPath().substring(1));
        myLog.setUrl(request.getRequestURI());
        myLog.setIp(request.getRemoteAddr());
        myLog.setComment(operation);
        long startTime = System.currentTimeMillis();
        Object proceed = pjd.proceed();
        myLog.setCost(System.currentTimeMillis()-startTime);

        moduleRequestSender.send(myLog);

        return proceed;
    }
}

切面会需要的坑(切面不生效)
启动类加上包扫描

@SpringBootApplication(scanBasePackages = {"com.kbplus.demo.*"})

二、日志模块处理

创建日志模块,并添加dao-service-controller,集成elasticsearch保存和查询日志,也可以使用mongodb
作为替代。
elasticsearch分页查询核心类

package com.kbplus.demo.log.service.impl;

import com.kbplus.demo.common.entity.Page;
import com.kbplus.demo.common.entity.PageRequest;
import com.kbplus.demo.log.dao.LogDao;
import com.kbplus.demo.log.model.entity.MyLog;
import com.kbplus.demo.log.model.query.LogQuery;
import com.kbplus.demo.log.service.LogService;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * @author kbplus
 * @date 2022-03-28
 * @blog https://blog.csdn.net/cyy9487
 */
@Service
public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao dao;

    @Override
    public void save(MyLog myLog) {
        myLog.setRecordTime(new Date());
        dao.save(myLog);
    }

    @Override
    public Page<MyLog> getAllPage(LogQuery logQuery, PageRequest pageRequest){


        Pageable pageable = org.springframework.data.domain.PageRequest.of(pageRequest.getPage()-1,pageRequest.getSize(), Sort.by(Sort.Direction.DESC,"recordTime"));

        BoolQueryBuilder queryBuilder= QueryBuilders.boolQuery();

        MatchQueryBuilder matchQueryBuilder1;
        MatchQueryBuilder matchQueryBuilder2;

        if(StringUtils.isNotEmpty(logQuery.getModuleName())) {
            matchQueryBuilder1 = new MatchQueryBuilder("moduleName", logQuery.getModuleName());
            queryBuilder.must(matchQueryBuilder1);
        }
        if(StringUtils.isNotEmpty(logQuery.getType())) {
            matchQueryBuilder2 = new MatchQueryBuilder("type", logQuery.getType());
            queryBuilder.must(matchQueryBuilder2);
        }

        org.springframework.data.domain.Page<MyLog> search = dao.search(queryBuilder,pageable);
        Page<MyLog> page = new Page<>(pageRequest.getPage(),pageRequest.getSize());
        page.setTotal(search.getTotalElements());
        page.setCurrent(search.getNumber()+1);
        page.setRecords(search.getContent());

        return page;
    }

}

MQ配置

2个配置,一个在公共模块,一个在日志模块

package com.kbplus.demo.common.config;

import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;

/**
 * @author kbplus
 * @date 2022-03-28
 * @blog https://blog.csdn.net/cyy9487
 */
@Configuration
public class DirectRabbitConfig implements RabbitListenerConfigurer {

    @Bean
    public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
        return rabbitTemplate;
    }

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory());
    }

    @Bean
    MessageHandlerMethodFactory messageHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
        messageHandlerMethodFactory.setMessageConverter(consumerJackson2MessageConverter());
        return messageHandlerMethodFactory;
    }

    @Bean
    public MappingJackson2MessageConverter consumerJackson2MessageConverter() {
        return new MappingJackson2MessageConverter();
    }

}

日志模块:

package com.kbplus.demo.log.common.configuration;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
/**
 * @author kbplus
 * @date 2022-03-28
 * @blog https://blog.csdn.net/cyy9487
 */
@Configuration
public class LogDirectRabbitConfig {

    /**
     * 日志队列
     */
    @Bean
    public Queue createModuleRequestQueue() {
        return new Queue("log",true);
    }

    @Bean
    public DirectExchange createModuleRequestExchange() { return new DirectExchange("log_exchange",true,false); }

    @Bean
    public Binding ModuleRequestBindingDirect() {
        return BindingBuilder.bind(createModuleRequestQueue()).
                to(createModuleRequestExchange()).
                with("log_request_key");
    }

}

生产在切面类,切指定注解切面并生产消息
生产者-日志模块:

package com.kbplus.demo.log.recevier;

import com.kbplus.demo.log.model.entity.MyLog;
import com.kbplus.demo.log.service.LogService;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
/**
 * @author kbplus
 * @date 2022-03-28
 * @blog https://blog.csdn.net/cyy9487
 */
@Slf4j
@Component
public class ModuleReceiver {

    @Autowired
    private LogService logService;

    /**
     * 日志收集处理
     *
     */
    @RabbitListener(queues = "log")
    @Transactional(rollbackFor = Exception.class)
    public void connectBase(MyLog myLog, Channel channel, Message message) throws IOException {

        try {
            logService.save(myLog);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            e.printStackTrace();
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


总结

以上就是今天要讲的内容。代码详细源码:微服务日志功能的实现rabbitmq+elasticsearch+aop

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值