精华内容
下载资源
问答
  • .net core 微服务日志落盘设计 ... 目录1、设计目标2、日志流程3、串联请求事务3.1 请求ID3.2 处理服务器、服务3.3 处理接口名3.4 日志的发生时间3.5 接口返回状态码4、记录结构5...
    原文: .net core 微服务之日志落盘设计

    1、设计目标

    1. 对各个微服务的访问进行请求追踪,注重排查开发、线上问题
    2. 消息队列发送、多服务落盘,事后分析
    3. 日志分析
    4. 性能优化

    2、日志流程

    前端Api网关MQXX微服务api请求1条代理层访问时间日志发送RPC请求2条api访问时间日志,N条微服务间调用时间日志1条api访问日志,N条业务自定义日志返回结果1条代理层访问时间日志api请求结果前端Api网关MQXX微服务

    3、串联请求事务

    3.1 请求ID

    请求id:唯一标识一个Api请求链路。

    为了分析前端的一条Api请求,需要在Api网关请求时产生一个guid,标识api的请求,并按照调用次序传递给微服务,微服务间可以互相调用,因此请求id按照调用次序依次传递。

    3.2 处理服务器、服务

    请求链路会穿越不同的服务器以及不同的服务,因此,需要记录服务器的IP或名称,服务的名称,这样在分析问题时可以快速找到故障点。

    3.3 处理接口名

    api的入口是唯一接口,但穿越不同节点后,实际执行接口会随着调用而改变,因此接口名需要被记录下来。

    3.4 日志的发生时间

    可提供时间索引,可按照时间进行分区分表等

    3.5 接口返回状态码

    状态码简单判断接口是否工作正常,有无错误,错误描述等

    4、记录结构

    在这里插入图片描述

    5、RabbitMq队列

    在这里插入图片描述

    6、落盘

    日志落盘采用RabbitMq的拉模式,考虑到日志的规模,如果采用推模式,很可能导致落盘阻塞,因此这里采用拉取模式,以落盘介质的流速为限制。
    落盘可以采用多个微服务进行拉取,这样可以保证某个落盘服务故障后,仍然可以继续落盘。
    落盘采用一个线程拉取队列,并存储在内存队列中,三个不同的落库线程,从内存队列中拉取并落库。

     public static void Init()
            {
                _event = new AutoResetEvent(false);
                _queue = new ConcurrentQueue<LogBase>();
    
                new Thread(SaveLogInfo){IsBackground = true}.Start();
                new Thread(SaveLogStat) { IsBackground = true }.Start();
                new Thread(SaveLogBussiness) { IsBackground = true }.Start();
                new Thread(SaveLogToDb) { IsBackground = true }.Start();
            }     
    

    落库失败,可以降级到文件落盘。

    7、性能优化

    考虑到写库的性能限制,可以批量写库,使用insert value value批量方式能极大提高落库速度。

    8、简单统计

    可以快速分析api接口的访问次数以及响应的平均时间。
    在这里插入图片描述


    在此我向大家推荐一个微服务架构学习交流群。交流学习群号:864759589 里面会分享一些资深架构师录制的视频录像:高并发、高性能、分布式、微服务架构的原理,分布式架构等这些成为架构师必备的知识体系。
    在这里插入图片描述


    引用链接

    1. 口袋代码仓库
    2. 在线计算器
    3. 本节源码:github
    posted on 2019-01-18 10:23 NET未来之路 阅读( ...) 评论( ...) 编辑 收藏

    转载于:https://www.cnblogs.com/lonelyxmas/p/10286277.html

    展开全文
  • 3.2 处理服务器、服务 3.3 处理接口名 3.4 日志的发生时间 3.5 接口返回状态码 4、记录结构 5、RabbitMq队列 6、落盘 7、性能优化 8、简单统计 引用链接 1、设计目标 对各个微服务的访问进行...

     

    1、设计目标

    1. 对各个微服务的访问进行请求追踪,注重排查开发、线上问题
    2. 消息队列发送、多服务落盘,事后分析
    3. 日志分析
    4. 性能优化

    2、日志流程

     

    3、串联请求事务

    3.1 请求ID

    请求id:唯一标识一个Api请求链路。

    为了分析前端的一条Api请求,需要在Api网关请求时产生一个guid,标识api的请求,并按照调用次序传递给微服务,微服务间可以互相调用,因此请求id按照调用次序依次传递。

    3.2 处理服务器、服务

    请求链路会穿越不同的服务器以及不同的服务,因此,需要记录服务器的IP或名称,服务的名称,这样在分析问题时可以快速找到故障点。

    3.3 处理接口名

    api的入口是唯一接口,但穿越不同节点后,实际执行接口会随着调用而改变,因此接口名需要被记录下来。

    3.4 日志的发生时间

    可提供时间索引,可按照时间进行分区分表等

    3.5 接口返回状态码

    状态码简单判断接口是否工作正常,有无错误,错误描述等

    4、记录结构

    在这里插入图片描述

    5、RabbitMq队列

    在这里插入图片描述

    6、落盘

    日志落盘采用RabbitMq的拉模式,考虑到日志的规模,如果采用推模式,很可能导致落盘阻塞,因此这里采用拉取模式,以落盘介质的流速为限制。
    落盘可以采用多个微服务进行拉取,这样可以保证某个落盘服务故障后,仍然可以继续落盘。
    落盘采用一个线程拉取队列,并存储在内存队列中,三个不同的落库线程,从内存队列中拉取并落库。

     public static void Init() { _event = new AutoResetEvent(false); _queue = new ConcurrentQueue<LogBase>(); new Thread(SaveLogInfo){IsBackground = true}.Start(); new Thread(SaveLogStat) { IsBackground = true }.Start(); new Thread(SaveLogBussiness) { IsBackground = true }.Start(); new Thread(SaveLogToDb) { IsBackground = true }.Start(); } 

    落库失败,可以降级到文件落盘。

    7、性能优化

    考虑到写库的性能限制,可以批量写库,使用insert value value批量方式能极大提高落库速度。

    8、简单统计

    可以快速分析api接口的访问次数以及响应的平均时间。
    在这里插入图片描述

    转载:https://www.cnblogs.com/lonelyxmas/p/10286277.html

    转载于:https://www.cnblogs.com/liliuguang/p/10788622.html

    展开全文
  • 微服务应用日志组件封装

    千次阅读 2018-05-18 09:56:00
    随着微服务设计理念在系统的应用,业务的调用链越来越复杂,日志信息越来越大,从快速增长的日志数据流提取出所需的信息,并将其与其他相关联的事件进行关联,将变得越加困难。ELK+KafKa作为微服务应用日志...

    微服务应用日志组件定制

    随着微服务等设计理念在系统中的应用,业务的调用链越来越复杂,日志信息越来越大,从快速增长的日志数据流中提取出所需的信息,并将其与其他相关联的事件进行关联,将变得越加困难。ELK+KafKa作为微服务应用日志中心解决方案是业界常规办法,前提是应用日志输出的规范化,日志组件就是为了完成这一目标。

    微服务应用日志格式规划

    经过公司技术人员充分讨论和分析,确认了应用日志输出格式如下:

    |时间|日志级别|线程名|代码发生行|同步调用ID|客户系统节点|服务系统节点|客户系统名称|服务系统名称|日志类型|全局流水号|定制化字段^日志信息|
    • 时间:记录日志产生时间;
    • 日志级别:FATAL、ERROR、WARN、INFO、DEBUG;
    • 线程名:执行操作线程名称;
    • 代码发生行:日志事件发生在代码中位置;
    • 同步调用ID:用于同步调用链分析、可以关联ZipKin、Pinpoint等工具关联排查问题;
    • 客户系统节点:表示服务-客户端系统节点的标识,可以为IP或者DockerID;
    • 服务系统节点:表示服务-服务端系统节点的标识,可以为IP或者DockerID;
    • 客户系统名称:表示服务-客户端系统的标识,可以为系统简称和全称;
    • 服务系统名称:表示服务-服务端系统的标识,可以为系统简称和全称;
    • 全局流水号:贯穿一次业务流程的全局流水号;
    • 日志类型:枚举,REQ-接口请求报文,RESP-接口响应报文,BIZ-通用日志;
    • INFO:定制化字段(格式采用 关键字段=值,关键字段2=值) ^ 业务信息(记录详细日志信息)。

    举个栗子:

    |2018-03-28 14:26:21.001|INFO|demo-service-woker:share-d-3[share]-thread-1|com.xxxx.xxx.service.interceptor.ParamCheckInterceptor:59:doInvoke|4507080825331412517041832|127.0.0.1:50344|127.0.0.1:20015|demo-a|demo-b|20170428142618325183APP|BIZ|phoneNumber=185****1234, woAccountId=110000008592138^参数及权限校验拦截,验证通过.|

    日志组件实现方案

    Java技术平台日志输出组件主要有java.util.logging、log4j、log4j2、logback、slf4j。我司目前使用的log4j2+slf4j组合作为应用日志输出组件,为了实现上述日志格式输出,主要有两种方式在组件上实现封装。应用采用SpringBoot+Restful作为主框架

    Log4J的MDC方案

    MDC[http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html]是为每个线程建立一个独立的存储空间,开发人员可以根据需要把信息存入其中。MDC使用Map机制来存储信息,信息以key/value对的形式存储在Map中。
    使用Servlet Filter或者AOP在服务接收到请求时,获取需要的值填充MDC。在log4j2的配置中就可以使用%X{key}打印MDC中的内容,从而识别出同一次请求中的log。

    Log4j2.X版本
    MdcLogfilter类
    public class MdcLogfilter implements Filter {
    
        public static final String TRACE_ID = "traceId";
    
        public static final String CLIENT_ADDR = "clientAddr";
    
        public static final String SERVER_ADDR = "serverAddr";
    
        public static final String CLIENT_SYS_NAME = "clientSysName";
    
        public static final String SERVER_SYS_NAME = "serverSysName";
    
        public static final String TRANS_ID = "transId";
    
        public static final String LOG_TYPE = LogTypeEnum.BIZ.getKey();
    
        private String systemName;
    
        public MdcLogfilter( String systemName ) {
            this.systemName = systemName;
        }
    
        @Override
        public void init( FilterConfig filterConfig ) throws ServletException {
        }
    
        @Override
        public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException {
            try {
                insertIntoMDC(request);
                chain.doFilter(request, response);
            } finally {
                ThreadContext.clearAll();
            }
        }
    
        @Override
        public void destroy() {
        }
    
        protected void insertIntoMDC( ServletRequest request ) {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            String traceId = httpRequest.getHeader(TRACE_ID);
            String clientAddr = httpRequest.getRemoteAddr();
            String serverAddr = getSreverAddr();
            String clientSysName = httpRequest.getHeader(CLIENT_SYS_NAME);
            String serverSysName = systemName;
            String transId = (String) httpRequest.getAttribute(TRANS_ID);
            ThreadContext.put(TRACE_ID, traceId);
            ThreadContext.put(CLIENT_ADDR, clientAddr);
            ThreadContext.put(SERVER_ADDR, serverAddr);
            ThreadContext.put(CLIENT_SYS_NAME, clientSysName);
            ThreadContext.put(SERVER_SYS_NAME, serverSysName);
            ThreadContext.put(TRANS_ID, transId);
            ThreadContext.put(LOG_TYPE, LogTypeEnum.REQ.getKey());
        }
    
        private String getServerAddr() {
            return null;
        }
    
    }
    
    MDCLogAspect类
    @Aspect
    public class MDCLogAspect {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(EpayLogAspect.class);
    
        private static long STIME = LogUtil.getNowTimeInMillis();
    
        private static String REQUESTURL = "url";
    
        @Pointcut("execution(public * com.demo..*.*.controller.*.*(..))")
        public void log() {
        }
    
        @Before("log()")
        public void deBefore( JoinPoint joinPoint ) throws Throwable {
            STIME = LogUtil.getNowTimeInMillis();
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            Map<String, String[]> params = request.getParameterMap();
            String queryString = "";
            for (String key : params.keySet()) {
                String[] values = params.get(key);
                for (int i = 0; i < values.length; i++) {
                    String value = values[i];
                    queryString += key + "=" + value + "&";
                }
            }
    
            if (!StringUtils.isEmpty(queryString)){
                queryString = queryString.substring(0, queryString.length() - 1);
            }
            REQUESTURL = request.getRequestURL().toString();
    
            LOGGER.info("url:{},req:{}", REQUESTURL, queryString);
            MDC.put(LogPreKeyConstants.LOGTYPE, LogTypeEnum.BIZ.getKey());
        }
    
        @AfterReturning(returning = "ret", pointcut = "log()")
        public void doAfterReturning( Object ret ) throws Throwable {
            MDC.put(LogPreKeyConstants.LOGTYPE, LogTypeEnum.RESP.getKey());
            LOGGER.info("url:{},resp:{}", REQUESTURL, ret);
            MDC.remove(LogPreKeyConstants.LOGTYPE);
            MDC.put(LogPreKeyConstants.LOGTYPE, LogTypeEnum.BIZ.getKey());
        }
    
        //后置异常通知
        @AfterThrowing("log()")
        public void throwss( JoinPoint jp ) {
            String costtime = LogUtil.getNowTimeInMillis() - STIME + "ms";
            LOGGER.info("url:{},executetime:{}", REQUESTURL, costtime);
        }
    
        //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
        @After("log()")
        public void after( JoinPoint jp ) {
            String costtime = LogUtil.getNowTimeInMillis() - STIME + "ms";
            LOGGER.info("url:{},executetime:{}", REQUESTURL, costtime);
        }
    }
    
    Log4j1.X版本

    log4j1.x版本,ThreadContext类换成MDC即可

    log4j配置使用MDC

    在log4j配置中,使用mdc:(log4j 1.x 与 2.x中都可以使用此配置)

    <PatternLayout pattern="|%d{yyyy-MM-dd HH:mm:ss.SSS}|%5p|%5t|%4c:%L|%X{traceid}|%X{clientaddr}|%X{serveraddr}|%X{cientsysname}|%X{serversysname}|%X{transid}|%X{logType}|%m|%n" />
    Springboot使用

    实现定制化log-start,应用依赖即可。

    Log4J的适配器封装方案

    使用适配器模式封装log4j接口,使用AOP在服务接收到请求时,获取需要的值填充本地线程副本中,打印日志时候拼接输出到日志。

    Log4j2.X/Log4j1.X版本
    LogContextHolder
    public class AdapterLogContextHolder {
    
        private static final ThreadLocal<AdapterLogContext> LOG_CONTEXT = new ThreadLocal<>();
    
        private static AdapterLogContextHolder context = new AdapterLogContextHolder();
    
        private AdapterLogContextHolder() {
        }
    
        public static AdapterLogContextHolder getInstance() {
            return context;
        }
    
        public AdapterLogContext getLogContext() {
            AdapterLogContext context = LOG_CONTEXT.get();
    
            if (context == null) {
                context = new AdapterLogContext();
                LOG_CONTEXT.set(context);
            }
            return context;
        }
    
        public void setLogContext( AdapterLogContext context ) {
            LOG_CONTEXT.set(context);
        }
    
        public void removeLogContext() {
            LOG_CONTEXT.remove();
        }
    
        public String getTraceID() {
            return getLogContext().getTraceID();
        }
    
        public void setTraceID( String id ) {
            getLogContext().setTraceID(id);
        }
    }
    
    
    AdapterLogContext
    public class AdapterLogContext implements Serializable, Cloneable {
    
        private static final long serialVersionUID = 6126191683350551062L;
    
        private String traceID = "traceID";
    
        private long callStartTimes;
    
        private String transID = "transId";
    
        private String clientAddr = "clientAddr";
    
        private String serverAddr = "serverAddr";
    
        private String clientSysName = "clientSysName";
    
        private String serverSysName = "serverSysName";
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
        //get、set .....
    }
    LoggerAdapter
    public class LoggerAdapter {
    
        final static String FQCN = LoggerAdapter.class.getName();
    
        private static LoggerAdapter loggerAdapter;
    
        private static volatile HashMap<String, LoggerAdapter> loggerxMap = null;
    
        protected Logger logger = null;
    
        private LoggerAdapter( Logger logger ) {
            this.logger = logger;
        }
    
        public static LoggerAdapter getLogger( Class<?> classObject ) {
    
            if (null == classObject) {
                throw new IllegalArgumentException();
            }
    
            if (loggerxMap == null) {
                synchronized (Loggerx.class) {
                    if (loggerxMap == null) {
                        loggerxMap = new HashMap<>();
                        return getLoggerAdapter(classObject);
                    }
                }
            }
    
            LoggerAdapter loggerAdapter = loggerxMap.get(classObject.getSimpleName());
            if (loggerAdapter == null) {
                return getLoggerAdapter(classObject);
            } else {
                return loggerAdapter;
            }
        }
    
        private static LoggerAdapter getLoggerAdapter( Class<?> classObject ) {
            Logger logger = LogManager.getLogger(classObject);
            if (logger == null) {
                return null;
            }
    
            LoggerAdapter loggerAdapter = new LoggerAdapter(logger);
            loggerxMap.put(classObject.getSimpleName(), loggerAdapter);
    
            return loggerAdapter;
        }
    
        public void info( LogType logType, Object message ) {
            String msgFormatted = msgFormat(logType, message);
            logger.log(FQCN, Level.INFO, msgFormatted, null);
        }
    
        public void info( LogType logType, Map<String, String> custMsg, Object message ) {
            String msgFormatted = msgFormat(logType, custMsg, message);
            logger.log(FQCN, Level.INFO, msgFormatted, null);
        }
    
        public void error( LogType logType, Object message ) {
            String msgFormatted = msgFormat(logType, message);
            logger.log(FQCN, Level.ERROR, msgFormatted, null);
        }
    
        public void error( LogType logType, Map<String, String> custMsg, Object message ) {
            String msgFormatted = msgFormat(logType, custMsg, message);
            logger.log(FQCN, Level.ERROR, msgFormatted, null);
        }
    
        public void error( LogType logType, Object message, Throwable t ) {
            String msgFormatted = msgFormat(logType, message);
            logger.log(FQCN, Level.ERROR, msgFormatted, t);
        }
    
        public void error( LogType logType, Map<String, String> custMsg, Object message, Throwable t ) {
            String msgFormatted = msgFormat(logType, custMsg, message);
            logger.log(FQCN, Level.ERROR, msgFormatted, t);
        }
    
        public void warn( LogType logType, Object message ) {
            String msgFormatted = msgFormat(logType, message);
            logger.log(FQCN, Level.WARN, msgFormatted, null);
        }
    
        public void warn( LogType logType, Map<String, String> custMsg, Object message ) {
            String msgFormatted = msgFormat(logType, custMsg, message);
            logger.log(FQCN, Level.WARN, msgFormatted, null);
        }
    
        public void debug( LogType logType, Object message ) {
            String msgFormatted = msgFormat(logType, message);
            logger.log(FQCN, Level.DEBUG, msgFormatted, null);
        }
    
        protected String msgFormat( LogType logType, Object message ) {
            StringBuilder sb = new StringBuilder();
            AdapterLogContext logContext = AdapterLogContextHolder.getInstance().getLogContext();
            sb.append(logContext.getTraceID());
            sb.append("|").append(logContext.getClientAddr());
            sb.append("|").append(logContext.getServerAddr());
            sb.append("|").append(logContext.getClientSysName());
            sb.append("|").append(logContext.getServerAddr());
            sb.append("|").append(logContext.getTransID());
            sb.append("|").append(logType);
            sb.append("|").append("custmsg=null");
            sb.append(message != null ? "^" + message.toString().replaceAll("\r|\n", "").replaceAll("\\|", "#") + "|" : "");
            return sb.toString();
        }
    
        protected String msgFormat( LogType logType, Map<String, String> custMsgMap, Object message ) {
            if (custMsgMap == null || custMsgMap.isEmpty()) {
                return msgFormat(logType, message);
            } else {
                StringBuilder sb = new StringBuilder();
                AdapterLogContext logContext = AdapterLogContextHolder.getInstance().getLogContext();
                sb.append("|").append(logContext.getClientAddr());
                sb.append("|").append(logContext.getServerAddr());
                sb.append("|").append(logContext.getClientSysName());
                sb.append("|").append(logContext.getServerAddr());
                sb.append("|").append(logContext.getTransID());
                sb.append("|").append(logType);
                sb.append("|");
                for (Map.Entry<String, String> entry : custMsgMap.entrySet()) {
                    sb.append(entry.getKey() + "=" + entry.getValue() + ",");
                }
                sb.deleteCharAt(sb.length() - 1);
                sb.append(message != null ? "^" + message.toString().replaceAll("\r|\n", "") + "|" : "");
                return sb.toString();
            }
        }
    }
    
    AOP切片类LoggerAdapter
    public class ClientContextBeforeAdvice implements MethodBeforeAdvice {
    
        @Override
        public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
            try {
                if (arg1.length == 0 || !(arg1[0] instanceof BaseRequest)) {
                    return;
                }
    
                AdapterLogContext logContext = AdapterLogContextHolder.getInstance().getLogContext()
    
                //获取logContext各个属性值
                BaseRequest request = (BaseRequest) arg1[0];
                request.setLogContext(logContext);
            } catch (Exception e) {
                LOGGER.error(LogType.EX, "", e);
                throw e;
            }
        }
    }
    
    //其他AOP切片
    log4j配置使用适配器模式

    在log4j配置中,log4j 1.x 与 2.x中都可以使用正常配置

    Springboot使用

    实现定制化log-start,应用依赖即可。

    MDC方式与Adapter方式对比

    两种对比,MDC优点在于不改变开发人员使用log4j方法,引入不需要更改业务代码,Adapter优势在于使用AOP和ThreadLocal方式更方便定制化。我司目前使用MDC方案.

    展开全文
  • 转载本文需注明出处:微信公众号EAWorld,违者必究。引言:日志向来都是运维以及开发人员最关心的问题。运维人员可以及时的通过相关日志信息发现系统隐患、系统故障并及时安排...

    640?wx_fmt=png

    转载本文需注明出处:微信公众号EAWorld,违者必究。

    引言:


    日志向来都是运维以及开发人员最关心的问题。运维人员可以及时的通过相关日志信息发现系统隐患、系统故障并及时安排人员处理解决问题。开发人员解决问题离不开日志信息的协助定位。没有日志就相当于没有了眼睛,失去了方向。


    微服务日渐火热,享受微服务架构带来的种种好处的同时也要承担起由微服务带来的种种困扰。日志管理就是其中一个。微服务有一个很大的特色:分布式。 分布式部署的结果就导致日志信息散落在各地,这样就给日志的采集存储带来了一定的挑战:


    • 如何及时采集日志信息?

    • 如何将日志信息进行汇总?

    • 汇总之后如何对日志信息进行检索分析?


    这篇文章将探讨一下日志管理的相关问题。


    目录:


    一、日志的重要性和复杂性

    二、基于微服务的日志中心架构设计

    三、日志中心的流程与实现

    四、日志中心的关键配置

    五、总结



    、日志的重要性和复杂性


    要说管日志,在管日志之前有一个先决条件,我们需要知道日志是什么,能做什么,有什么用。援引百度百科的话,它是记录系统操作事件的记录信息。


    在日志文件内部记录了当前系统的各项生命体征,就像我们在医院体检过后拿到的体检单,上面反应了我们的肝功能、肾功能、血常规等的具体指标。日志文件在应用系统中的作用就如同体检单,它反应了系统的健康状态、系统的操作事件、系统的变更状况。


    640?wx_fmt=png


    日志在系统中扮演着监护人的身份,它是保障高可靠服务的基础,记录了系统的一举一动。运维层面、业务层面、安全层面都有日志的身影,系统监控、异常处理、安全、审计等都离不开日志的协助。


    日志种类繁杂,一个健壮的系统可能会有着各种各样的日志信息。


    640?wx_fmt=png


    如此复杂多样的日志,是否要一网打尽?哪些又是我们所需要的?这都是我们在设计日志中心架构时需要考虑的问题。

     

    二、基于微服务的

    日志中心架构设计


    日志中心是微服务生态中不可或缺的一环,是监控的二当家。在此分享一下我们的产品级设计实践,了解一下,在基于微服务的架构,日志中心在技术架构中所处的位置是怎样的,以及如何部署。


    640?wx_fmt=png

    在这一设计中,微服务结构由以下几部分组成:


    • 域:一个域是一套注册中心、配置中心、业务监控中心、网关等组成的生态圈。一个域内有可以有多个系统。

    • 系统:一个系统内部可以部署多个应用。

    • 应用:轻耦合的微服务应用。


    图片中并没有日志中心这四个关键字,因为他是由多个独立组件共同协同构成的。这些组件分别是Filebeat、Kafka、Logstash、Elastic search,他们共同构成了日志中心。 

    640?wx_fmt=png

    我们经过考量研究确定了一套适用于当下微服务架构日志管理流程。


    1. 日志选取    ----    确定选择哪些日志记录分析

    2. 日志采集    ----    filebeat轻巧做收集

    3. 日志缓冲    ----    kafka缓存本地做缓冲

    4. 日志筛选    ----    logstash筛选过滤

    5. 日志存储    ----    elasticsearch建索引入库

    6. 日志检索    ----    利用elasticsearch本身的检索功能 

    7. 日志展现    ----    参考kibana风格实现日志数据可视化


    在传统的ELK上替换了Logstash做日志采集的部分采取Filebeat,在日志存储前多了kafka缓冲和logstash筛选。这一套流程在保障功能完备同时提高性能、尽可能做到轻量部署。

     

    三、日志中心的流程与实现


    选择:以业务场景为原则


    日志内容复杂多样,如何去收集有价值的日志是我们重点关注的。日志的价值其实是取决于业务操作的,不同的业务场景下相同类型的日志的价值会截然不同。


    根据以往的业务实践,结合企业级的一些业务需求,我们选定关注以下几类日志。


    • 跟踪日志【trace.log】 Server引擎的调试日志,用于系统维护人员定位系统运行问题使用。

    • 系统日志【system.log】 大粒度的引擎运行的入口、出口的日志,用于调用栈分析,可以进行性能分析使用。

    • 部署日志【deploy.log】 记录系统启动、停止、构件包部署、集群通知等信息的日志。

    • 引擎日志【engine.log】 细粒度的引擎运行日志,可以打印上下文数据,用于定位业务问题。  

    • 构件包日志【contribution.log】 构件包记录的业务日志(使用基础构件库的日志输出API写日志)


    通过以上几种日志,我们可以在分析问题时明确我们要查找的位置,通过分类缩小查找范围提高效率。


    采集(Filebeat:侧重考虑轻量级

           

    微服务应用会分布在各个域内的各个系统。应用的日志也就相对应的产生在各个域内的各个系统。进行日志管理首先要做好日志的采集工作。日志采集工作我们选择Elastic Stack中的Filebeat。


    640?wx_fmt=png


    • Filebeat是一个开源的文件采集器,基于go语言开发,不需要java环境,它是对logstash的重构产物。Filebeat对环境依赖很低比较亲民。                             


    • Filebeat是一个轻量的采集器,最新版的Filebeat体积大约是20M左右,而logstash有近百兆。Filebeat更利于部署实施,减轻宿主压力。   

                                             

    • 之前曾有对filebeat和logstash的测试对比,在采集日志方面,filebeat比logstash花费更少CPU和内存。Filebeat在日志采集方面有更好的性能表现。 


    Filebeat和应用是挂钩的,因为我们需要知道针对每个落点去收集日志信息,所以轻量其实是我们考量的主要因素。


    Filebeat会有一个或者多个的叫做Prospector的探测器,可以实时监听指定的文件或者制定的文件目录的变更状况,将变更状况及时的传递到下一层---Spooler处理


    Filebeat还有一个特性我们放到日志筛选那里进行介绍,它是定位来源的关键。


    这两点刚好满足我们实现日志的实时采集的需求。通过Filebeat动态的将新增的日志进行及时存储取样。至此如何采集日志信息的问题已经可以完美解决。


    缓冲(Kafka):吞吐量大、易扩展、上限高

            

    在日志存储之前,我们引入了一个组件--- Kafka,作为日志缓冲层。 Kafka起一个缓冲的作用,避免高峰期应用对ES的冲击。造成由于ES瓶颈问题而引发数据丢失问题。同时它也起到了数据汇总的功能。


    选用kafka来做日志缓冲有几个优点:

    • 吞吐量大,一个topic可以拆分为多个partition。建议这几个partition在不同的磁盘中。

    • 分布式系统易拓展。

    • Kafka的性能主要取决于它对磁盘的连续读写,它的性能很大程度上是取决于磁盘处理的能力。可以说只要磁盘性能允许,它就可以无限制的接收来自Filebeat的日志信息,从而实现一个缓冲的作用。


    640?wx_fmt=png640?wx_fmt=png


    筛选(Logstash提前埋点、方便定位

           

    日志信息经过filebeat、kafka等工具的收集传递,给日志事件加了很多附加信息。利用Logstash实现二次处理,可在filter里进行过滤或处理。


    我们在Filebeat收集信息的时候通过将同一个Server上的日志信息发送到同一个Kafka的topic中来实现日志的汇总,这个topic名称就是server的关键信息。在更细粒度的层面上你也可以将每一个App的信息都当作一个topic来进行汇总。Kafka中通过Filebeat接受到的日志信息中包含了一个标识---日志是从哪里来的。Logstash的作用就是在日志汇入ES之前,通过标识将对应的日志信息进行二次筛选汇总处理,输送给ES,为之后的搜索提供依据。方便我们清楚的定位问题。


    640?wx_fmt=png

     

    存储(ES):方便扩展、上手简单


    Elastic 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。

     

    640?wx_fmt=png选用ElasticSearch的原因主要为:可分布式的部署,方便拓展;处理海量数据可以应对多种需求;强大的搜索功能,基于Lucene可以实现快速搜索;活跃的开发社区,资料多、上手简单。

     

    检索(ES):分门别类


    Elasticsearch本身就是一个强大的搜索引擎,支持通过系统、应用、应用实例分组、应用实例IP、关键字、日志级别、时间区间来检索所需要的日志信息。

     

    640?wx_fmt=png


    展现(Kibana):简单配置、清晰明了


    查看密密麻麻的日志信息的时候,常常会有一种晕眩感。需要通过精简提炼日志信息,对日志信息进行整合分析,以图表的形式将日志信息进行展示。在展示的过程中我们可以借鉴和吸收Kibana在日志可视化方面的努力,实现日志的可视化处理,只需通过简单的配置就可以看到对某一个服务或者某一个应用的清晰的可视化的日志分析结果。    


    640?wx_fmt=png


    (注:图片摘自互联网

    https://blog.csdn.net/my_bai/article/details/68062701 服务调用折线图)



    四、日志中心的关键配置


    在此就不再赘述关于各个组件的安装方法了,大家可以很轻松的从网络上找到相关教程。我们在这里介绍一些关键的配置,确保大家在安装过程中关键环节少走一些弯路。


    Filebeat的关键配置

     

    640?wx_fmt=png

    640?wx_fmt=png


    • enabled配置为true,表示打开日志采集能力。

    • 在path中配置文件路径或者是文件目录位置,支持多级搜索。

    对于EOS的应用我们是这样做的:统一输出在/opt/apps/logs/目录下,如:/opt/apps/logs/应用ID/应用ID_system.log, /opt/apps/logs/应用ID/应用ID_trace.log

    • 在output.kafka中配置输出对象。建议以自己的主机名作为topic名字,同一台机器上收取的各种日志会发送到kafka的同一个队列。


    Kafka以及Zookeeper关键配置

     

    640?wx_fmt=png


    640?wx_fmt=png


    • zookeeper.connect为zookeeper的集群地址


    640?wx_fmt=png


    • server.1,server.2, server.3,

      注意和/opt/zookeeper/myid的内容里的数字一致用来标识server。


    Logstash的关键配置

     

    640?wx_fmt=png


    • Input 配置数据源输入

    • Filter 将收取的源文件路径做截取,获取的文件名作为appid并将其存储在es数据中。作为后期筛选的标志。

    • Output 配置数据源输出,输出信息到elasticsearch中


    Elasticsearch关键配置

     

    640?wx_fmt=png


    • network.host 配置可访问es的地址,开发环境中配置为0.0.0.0代表允许任意一个ip访问,在生产环境中需配置为特定的ip,限制访问。

    五、总结


    日志中心在微服务架构中起到了至关重要的作用,它是微服务监控的一个非常重要的切入点,本文以我们的团队技术实践为蓝本阐述了其设计、实现与关键配置,并详细阐述了日志管理的7个关键步骤和一些考量原则。


    首先,在日志的选择上,要以业务场景为原则;选择之后的采集环节,可侧重考虑轻量级的Filebeat;在采集之后,选用吞吐量大的Kafka避免系统瓶颈造成的数据丢失;在存储之前,采用Logstash提前标识,便于问题的定位 ;充分利用ES和Kibana的功能来实现存储、检索和展现。


    采用这样的架构和原则,我们可以通过日志中心提供对日志的实时采集、分析、展示能力。并通过日志中心可以及时了解系统性能、用户行为,保障微服务的高可靠运行。

     

    备注:本文的所有设计和实现的相关实践,来源普元最新的微服务架构平台EOS 8。


                     前方高能

                                     前方高能预警

              前方高能

                                                 前方高能预警


    粉丝福利:在文末以留言方式参与本文相关内容讨论或抒发见解,小编会在评论中选取点赞数最多的3位幸运朋友,每人赠送《分布式服务架构:原理、设计与实战》、《Spring Cloud与Docker微服务架构实战》二选一书籍

    截止时间:6月19日10:00


                                                                                                         

    640?wx_fmt=png关于作者:赵瑞栋,普元SOA&云计算部门java工程师,从事Eclipse插件开发,参与普元EOS8 Platform开发,现主要参与EOS8微服务管理平台开发工作。

                 

    640?wx_fmt=png关于EAWorld:微服务,DevOps,数据治理,移动架构原创  技术分享,长按二维码关注


    640?wx_fmt=jpeg


    展开全文
  • 微服务中日志管理 — ELK

    万次阅读 2019-01-22 15:41:15
    需要提供在众多服务中查看分布的完整事务日志和分布式调试的能力。 实际上,挑战在于微服务是相互隔离的,它们不共享公共数据库和日志文件。随着微服务数量的增加以及我们使用自动化持续集成工具实现...
  • 微服务场景下,日志模块如何设计? 二、问题分析 日志模块设计目前想到如上两种 方案一:每个微服务提供者 集成日志模块以及相关的库表(如图上方案一),这种集成方式以及使用方式区别不大, 但是意味着存在 1....
  • 3.ElasticSearch:Elasticsearch 是基于 JSON 的分布式搜索和分析引擎,专为实现水平扩展、高可靠性和管理便捷性而设计。 4.Kibana:Kibana 能够以图表的形式呈现数据,并且具有可扩展的用户界面,供您全方位配置和...
  • 文章目录目录微服务架构的问题如何拆分服务服务间如何通信微服务框架API 网关配置中心服务中心通信中间件熔断、服务降级、限流Service Mesh文档微服务治理监控链路跟踪日志分析 微服务架构的问题 微服务架构服务...
  • 微服务日志收集方案

    2020-11-27 14:22:55
    微服务日志收集方案背景设计思路架构图搭建流程 背景 公司内部使用的是springcloud,随着业务逐步扩大,微服务数据也逐步增多,运行平台不仅涉及到腾讯云,超融合,微服务散落在不同的机器上,并且都是部署在docker...
  • 在我们进行微服务架构设计和改造过程,一个不可避免的问题是如何确定服务边界、如何进行服务识别,微服务的划分粒度究竟如何确认。我们可能会听到,服务既不能太大,也不能太小,当然这是一个笼统的概念。那么,...
  • 众所周知,微服务运行在多个主机上。为了满足某个业务需求,我们可能需要与运行在不同机器上的多个服务进行通信。因此,微服务生成的日志分布在多个主机上。作为一个开发人员或管理员...
  • 微服务体系结构固有的分布式系统,有太多相互影响的移动部件,并且可能以无法预测的方式失败。 可观察性 是涉及测量,收集和分析来自系统的各种诊断信号的活动。 这些信号可能包括度量,跟踪,日志,事件,...
  • 良好的日志记录可以提供丰富的日志数据,便于在调试时发现问题,从而大大提高编码效率。 记录器提供的自动化信息越多越好,日志信息也需要以简洁的方式呈现,便于找到重要的数据。 日志需求: 无需修改业务代码即可...
  • 同时,由于各种原因,跨进程的服务调用失败时,运维人员希望能够通过查看日志和查看服务之间的调用关系来定位问题,而Spring cloud sleuth组件正是为了解决微服务跟踪的组件。 sleuth的原理介绍可以参考这篇文章: ...
  • 在分布式架构下,单体应用被拆分为多个微服务,为了保证微服务的单一职责和合理拆分,“高内聚、松耦合”是最宝贵的设计原则。 通俗点讲,高内聚就是把相关的行为聚集在一起,把不相关的行为放在别处,如果你要修改...
  • 对于有关微服务和API网关的所有嗡嗡声,发现细节可能令人惊讶地困难。 我想起了西德尼·哈里斯(Sidney Harris)的动画片,其中提出了一个复杂的数学公式的第一步, 然后出现了奇迹 ,光辉的解决方案的突然出现促使...
  • 微服务平台也是我目前正在参与的,还在研发过程的平台产品,平台是以SpringCloud为基础,结合了普元多年来对企业应用的理解和产品的设计经验,逐步孵化的一个微服务应用平台。 一、微服务架构演进过程
  • 我想起了西德尼·哈里斯(Sidney Harris)的那幅漫画,该漫画提出了一个复杂的数学公式的第一步, 然后出现了奇迹 ,光辉的解决方案的突然出现促使观察者评论说,也许我们应该在第二步更加明确。 由于这些模式...
  • 微服务的集成架构设计

    千次阅读 2018-04-13 17:12:35
    微服务集成框架的模式 微服务已经在架构界流行起来了,但在实践,难免需要利用其它软件厂商系统的能力,同时也没有办法一步到位把企业内的所有系统都改造成微服务架构的系统,所以系统集成仍然是 一个非常重要的...
  • 一、微服务设计 二、技术选型 三、关键实现 四、服务注册 五、服务发现 目标 一、微服务包括那些内容 二、为什么会出现服务注册和服务发现 三、了解关键技术实现原理 一、微服务设计 1、包括那些...
  • 1. 微服务的4个设计原则和19个解决方案 1 2. 微服务应用4个设计原则 1 2.1. AKF拆分原则 2 2.2. 前后端分离 2 2.3. 无状态服务 2 2.4. Restful通信风格 2 3. 微服务平台的19个落地实践 3 4. 前言微服务要素-...
  • 微服务中的Sidecar设计模式解析

    千次阅读 2018-08-10 02:54:14
    它降低了与微服务架构相关的复杂性,并提供了许多功能,如负载平衡、服务发现、流量管理、熔断、遥测、故障注入等。阅读我之前的博客能够了解 Service Mesh 的概念,为什么云原生应用需要它以及它受欢迎的原...
  • 一般采用的都是基于时下比较流行SpringCloud和Dubbo的分布式的微服务的架构模式,虽然模块间能够独立部署了,但是模块间的还是强依赖关系,每次改动都需要重新发版上线,产品迭代速度又快,就造成了每次上线心里都唱...
  • 微服务的数据库设计

    千次阅读 2019-10-21 17:48:08
    微服务设计的一个关键是数据库设计,基本原则是每个服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。它是基于下面三个原因。 优化服务接口:微服务之间的接口越小越好,最好只有服务调用接口...
  • 微服务

    2018-08-12 10:35:16
    一、微服务介绍 ...很好的诠释了这一解释(2 pizza 团队最早是亚马逊 CEO Bezos提出来的,意思是说单个服务设计,所有参与人从设计、开发、测试、运维所有人加起来 只需要2个披萨就够了 )。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 41,523
精华内容 16,609
关键字:

微服务中日志服务设计