agent+ddd实践案例

1. 大纲

大纲

2. 业务背景(situation)

消息网关内部采用MySQL进行消息持久化,需要大量的I/O开销,因此在高并发请求下会成为系统瓶颈,亟需一种高吞吐量的替代方案。

这里主要思考了2种解决方案:

  • 寻找一种MySQL的替代方案。由于MySQL是基于B-Tree的,考虑性能提升的话,需要采用基于LSM-Tree的方案设计的数据库

    • 但是这种方案涉及业务侧比较大的改造(对于当前MVC3层结构的代码来说,因为并未对repo层进行抽象,因此替换底层存储几乎是革命性的变革)
    • B-Tree vs LSM-Tree,分别适合读多和写多的场景
  • 放弃以DB进行数据持久化的方案,转而采用ES等其他引擎。这里又可以进一步细化为2种方式,分别为

    • 代码中直接嵌入ES-Template,将数据存储到ES
    • 将数据写入log中,通过中间件将log中的信息同步至ES

    其中,第一种方案需要引入新的依赖,同时在有新租户接入时面临比较大的代码编写任务;而第二种方案仅需配置logback.xml,在有新tenant接入时,采用扩展的方式就可以很好的完成对接。

3. 设计思路(task)

在这里插入图片描述
这里需要着重考虑的一点是,写入不同的log文件时,是否可以采用对先用代码无侵入的解决方案?

答案是:javaagent

4. 重点难点(action)

4.1 logback

  • 系统引入logback.jar依赖
  • 编写logback.xml文件
    • 日志存储位置LOG_DIR
    • 日志输出格式pattern
    • 多个日志appender
    • 异步日志打印ASYNC
    • 日志类配置logger name
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定义日志文件的存储地址 -->
    <property name="LOG_DIR" value="resource/log-save"/>
    <!--
            %p:输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
            %r:输出自应用启动到输出该日志讯息所耗费的毫秒数
            %t:输出产生该日志事件的线程名
            %f:输出日志讯息所属的类别的类别名
            %c:输出日志讯息所属的类的全名
            %d:输出日志时间点的日期或时间,指定格式的方式: %d{yyyy-MM-dd HH:mm:ss}
            %l:输出日志事件的发生位置,即输出日志讯息的语句在他所在类别的第几行。
            %m:输出代码中指定的讯息,如log(message)中的message
            %n:输出一个换行符号
        -->
    <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level  %msg%n"/>

    <!--
        Appender: 设置日志信息的去向,常用的有以下几个
            ch.qos.logback.core.ConsoleAppender (控制台)
            ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新文件)
            ch.qos.logback.core.FileAppender (文件)
    -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 字符串System.out(默认)或者System.err -->
        <target>System.out</target>
        <!-- 对记录事件进行格式化 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <appender name="tenant_A" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/tenantA.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 必要节点,包含文件名及"%d"转换符,"%d"可以包含一个java.text.SimpleDateFormat指定的时间格式,默认格式是 yyyy-MM-dd -->
            <fileNamePattern>${LOG_DIR}/tenantA_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每个月滚动,如果是6,则只保存最近6个月的文件,删除之前的旧文件 -->
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!-- LevelFilter: 级别过滤器,根据日志级别进行过滤 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <!-- 用于配置符合过滤条件的操作 ACCEPT:日志会被立即处理,不再经过剩余过滤器 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 用于配置不符合过滤条件的操作 DENY:日志将立即被抛弃不再经过其他过滤器 -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="tenant_B" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/tenantB.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 必要节点,包含文件名及"%d"转换符,"%d"可以包含一个java.text.SimpleDateFormat指定的时间格式,默认格式是 yyyy-MM-dd -->
            <fileNamePattern>${LOG_DIR}/tenantB_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每个月滚动,如果是6,则只保存最近6个月的文件,删除之前的旧文件 -->
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!-- LevelFilter: 级别过滤器,根据日志级别进行过滤 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <!-- 用于配置符合过滤条件的操作 ACCEPT:日志会被立即处理,不再经过剩余过滤器 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 用于配置不符合过滤条件的操作 DENY:日志将立即被抛弃不再经过其他过滤器 -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="tenant_Default" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/tenantDefault.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 必要节点,包含文件名及"%d"转换符,"%d"可以包含一个java.text.SimpleDateFormat指定的时间格式,默认格式是 yyyy-MM-dd -->
            <fileNamePattern>${LOG_DIR}/tenantDefault_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每个月滚动,如果是6,则只保存最近6个月的文件,删除之前的旧文件 -->
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!-- LevelFilter: 级别过滤器,根据日志级别进行过滤 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <!-- 用于配置符合过滤条件的操作 ACCEPT:日志会被立即处理,不再经过剩余过滤器 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 用于配置不符合过滤条件的操作 DENY:日志将立即被抛弃不再经过其他过滤器 -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 文件 异步日志(async) -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender" >
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <neverBlock>true</neverBlock>
        <!-- 添加附加的appender,最多只能添加一个 -->
        <appender-ref ref="tenant_A" />
        <appender-ref ref="tenant_B" />
        <appender-ref ref="tenant_Default" />
    </appender>

    <!--
        也是<logger>元素,但是它是根logger。默认debug
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        <root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。
    -->
    <root level="info">
        <level>info</level>
<!--        <appender-ref ref="STDOUT"/>-->
        <appender-ref ref="ASYNC"/>
        <appender-ref ref="tenant_A"/>
        <appender-ref ref="tenant_B"/>
        <appender-ref ref="tenant_Default"/>
    </root>

    <logger name="com.example.logback.domain.factory.DefaultLogger" level="warn" additivity="false">
        <level value="warn"/>
        <appender-ref ref="tenant_Default"/>
    </logger>

    <logger name="com.example.logback.domain.factory.TenantALogger" level="warn" additivity="false">
        <level value="warn"/>
        <appender-ref ref="tenant_A"/>
    </logger>

    <logger name="com.example.logback.domain.factory.TenantBLogger" level="warn" additivity="false">
        <level value="warn"/>
        <appender-ref ref="tenant_B"/>
    </logger>

</configuration>

4.2 DDD

4.2.1 编写消息写入log的代码

  • DDD层级划分
    在这里插入图片描述
  • 代码层级划分
    在这里插入图片描述
  • UML图
    在这里插入图片描述
    • UML类图知识点回顾
      • 强弱关系:依赖 < 关联 < 聚合 < 组合
      • 依赖
        • 表示方式:虚线箭头
        • 解释说明:对象A作为对象B方法的一个参数,则对象B依赖于对象A
      • 关联
        • 表示方式:实线箭头
        • 解释说明:对象A作为对象B的一个属性,则对象B依赖于对象A
      • 聚合
        • 表示方式:空心菱形加实线
        • 解释说明:弱的拥有关系,has a的一种情形,两者不需要有相同的生命周期
      • 组合
        • 表示方式:实心菱形加实线
        • 解释说明:强的拥有关系,contains a的一种情形,两者是严格的整体与部分的关系
    • 代码说明
      • 入口是fileController中的logSave和logEventSave,其中logSave方法用于模拟正常的日志存储、logEventSave方法用来模拟消息送达后的事件触发日志存储。
      • fileController中的传参分为三种类型,分别是commend、query、event。分别对应于写请求、读请求和事件请求。
      • 事件请求是指,将原本串行化执行的指令修改为监听事件触发。在Spring中可以直接使用Spring Event机制。该机制通过编写ApplicationEvent、ApplicationListener并交由ApplicationEventPublisher进行事件发布,完成全部流程。使用监听器模式处理事件请求可以很好的实现逻辑解耦,以遵循单一职责原则。
      • 具体Logger对象实例的构造,采用了策略模式实现,通过传递参数中的属性,在LoggerPolicyContext中进行判断后构造。

4.3 javaagent

这部分很多,参考我的另一篇文章:
https://www.yuque.com/docs/share/205ed300-cb08-4929-8cb4-7d61631fd152?# 《2022-02-18【agent代理】》

4.4 filebeat接入

一波三折的一次实践。
首先,晒出最终的filebeat配置:

###################### Filebeat Configuration Example #########################

# This file is an example configuration file highlighting only the most common
# options. The filebeat.full.yml file from the same directory contains all the
# supported options with more comments. You can use it as a reference.
#
# You can find the full configuration reference here:
# https://www.elastic.co/guide/en/beats/filebeat/index.html

#=========================== Filebeat prospectors =============================

filebeat.inputs:

# Each - is a prospector. Most options can be set at the prospector level, so
# you can use different prospectors for various configurations.
# Below are the prospector specific configurations.

- type: log
  enabled: true
  # Paths that should be crawled and fetched. Glob based paths.
  paths:
    - /home/admin/koms/log2/error.log
  json.keys_under_root: true
  json.overwrite_keys: true
  tags: ["error"]


- type: log
  enabled: true
  # Paths that should be crawled and fetched. Glob based paths.
  paths: 
    - /home/logback/resource/log-save/tenantA.log
  json.keys_under_root: true
  json.overwrite_keys: true
  tags: ["tenantA"]


- type: log
  enabled: true
  # Paths that should be crawled and fetched. Glob based paths.
  paths: 
    - /home/logback/resource/log-save/tenantB.log
  json.keys_under_root: true
  json.overwrite_keys: true
  tags: ["tenantB"]


- type: log
  enabled: true
  # Paths that should be crawled and fetched. Glob based paths.
  paths: 
    - /home/logback/resource/log-save/tenantDefault.log
  json.keys_under_root: true
  json.overwrite_keys: true
  tags: ["tenantDefault"]

#    - /home/admin/koms/log2/info.log
    #- c:\programdata\elasticsearch\logs\*

  # Exclude lines. A list of regular expressions to match. It drops the lines that are
  # matching any regular expression from the list.
  #exclude_lines: ["^DBG"]

  # Include lines. A list of regular expressions to match. It exports the lines that are
  # matching any regular expression from the list.
  #include_lines: ["^ERR", "^WARN", "^INFO"]

  # Exclude files. A list of regular expressions to match. Filebeat drops the files that
  # are matching any regular expression from the list. By default, no files are dropped.
  #exclude_files: [".gz$"]

  # Optional additional fields. These field can be freely picked
  # to add additional information to the crawled log files for filtering
  #fields:
  #  level: debug
  #  review: 1

  ### Multiline options

  # Mutiline can be used for log messages spanning multiple lines. This is common
  # for Java Stack Traces or C-Line Continuation

  # The regexp Pattern that has to be matched. The example pattern matches all lines starting with [
 # multiline.pattern: '^\[[0-9]{4}-[0-9]{2}-[0-9]{2}'

  # Defines if the pattern set under pattern should be negated or not. Default is false.
 # multiline.negate: true

  # Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
  # that was (not) matched before or after or as long as a pattern is not matched based on negate.
  # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
 # multiline.match: after

 # multiline.max_lines: 2000
#================================ General =====================================

# The name of the shipper that publishes the network data. It can be used to group
# all the transactions sent by a single shipper in the web interface.
#name:

# The tags of the shipper are included in their own field with each
# transaction published.
#tags: ["service-X", "web-tier"]

# Optional fields that you can specify to add additional information to the
# output.
#fields:
#  env: staging

#================================ Outputs =====================================

# Configure what outputs to use when sending the data collected by the beat.
# Multiple outputs may be used.

#-------------------------- Elasticsearch output ------------------------------
output.elasticsearch:
  # Array of hosts to connect to.
 
  hosts: ["xxxx:9200"]
  indices:
    - index: "filebeat-error-%{+yyyy.MM.dd}"
      when.contains:
        tags: "error"
    - index: "tenanta-%{+yyyy.MM.dd}"
      when.contains:
        tags: "tenantA"
    - index: "tenantb-%{+yyyy.MM.dd}"
      when.contains:
        tags: "tenantB"
    - index: "tenantdefault-%{+yyyy.MM.dd}"
      when.contains:
        tags: "tenantDefault"
  # Optional protocol and basic auth credentials.
  #protocol: "https"
  #username: "elastic"
  #password: "123456"

#----------------------------- Logstash output --------------------------------
#output.logstash:
  # The Logstash hosts
  #hosts: ["localhost:5044"]

  # Optional SSL. By default is off.
  # List of root certificates for HTTPS server verifications
  #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]

  # Certificate for SSL client authentication
  #ssl.certificate: "/etc/pki/client/cert.pem"

  # Client Certificate Key
  #ssl.key: "/etc/pki/client/cert.key"

#================================ Logging =====================================

# Sets log level. The default log level is info.
# Available log levels are: critical, error, warning, info, debug
#logging.level: debug

# At debug level, you can selectively enable logging only for some components.
# To enable all selectors use ["*"]. Examples of other selectors are "beat",
# "publish", "service".
#logging.selectors: ["*"]

一开始启动时一直报错下面这个错误,检索到的资料都说是配置文件写法问题。

Exiting: No modules or prospectors enabled and configuration reloading disabled. What files do you want me to watch?

最终发现,并不是写法问题,而是由于filebeat版本过低导致的。上面的写法需要filebeat-6.x,而测试环境使用的还是5.x版本。

果断升级了新版本后,启动成功。

在filebeat向es创建索引的过程中,还出现了一些问题,那就是es中的索引不能有大写字母,所以修改了一下配置文件的index字段信息。

按照上述方法得到的es查询日志如下,不是很直观,因此需要format后进行便捷的查询

在这里插入图片描述

4.5 ES同步

  • filebeat将数据同步至es中
  • es层面的查询,采用kibana提供的sense组件实现
    在这里插入图片描述

5. 代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值