当前位置: 首页 > news >正文

Filebeat 轻量级日志采集实践:安装、配置、多行合并、JSON 解析与字段处理

Filebeat 轻量级日志采集器实战指南

适用版本:Filebeat 8.2.2 + Elasticsearch 8.2.2 / Logstash 8.2.2
部署目标:从服务器采集日志并发送至 Elasticsearch 或 Logstash


一、Beats 简介

Beats 是 Elastic 开源的轻量级数据采集平台,专为高效、低资源消耗地收集各类数据而设计。它由多个专用采集器组成,适用于大规模分布式环境下的日志、指标、网络流量等数据采集。

官方 Beats 组件一览

Beats 组件用途
Filebeat采集日志文件(如 Nginx、系统日志、应用日志)
Metricbeat采集系统/服务指标(CPU、内存、MySQL、Redis 等)
Packetbeat采集网络流量数据(抓包分析)
Winlogbeat采集 Windows 事件日志(Event Log)
Auditbeat采集 Linux 审计日志和文件完整性监控
Heartbeat监测服务可用性(Ping、HTTP、TCP)

🔗 官网地址:https://www.elastic.co/cn/beats


二、Filebeat 概述

Filebeat 是 Beats 家族中最常用的组件,专为日志文件采集设计。它具有以下特点:

  • 轻量级:资源占用极低,适合部署在大量边缘服务器。
  • 可靠:通过文件状态记录(registry)确保日志不丢失。
  • 灵活输出:支持直接发送到 Elasticsearch、Logstash,或通过 Kafka、Redis 中转。
  • 模块化:内置 Nginx、Apache、MySQL、System 等常见日志模块,开箱即用。
  • 安全支持:支持 TLS 加密、身份认证,适配 ES 8.x 安全机制。

📌 典型应用场景

  • 收集 Nginx/Apache 访问日志、错误日志
  • 采集 Java 应用日志(如 Spring Boot)
  • 收集系统日志(/var/log/messages, secure 等)
  • 多服务器日志集中到 ELK/EFK 平台

🔗 Filebeat 官网:https://www.elastic.co/cn/beats/filebeat


三、部署 Filebeat 8.2.2(二进制方式)

⚠️ 注意:本文使用 Filebeat 8.2.2,与你的 Elasticsearch 8.2.2 版本严格匹配。

1. 下载 Filebeat 8.2.2

wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.2.2-linux-x86_64.tar.gz

🔗 下载地址:https://www.elastic.co/downloads/beats/filebeat

2. 解压安装包

tar -xzf filebeat-8.2.2-linux-x86_64.tar.gz -C /data

建议创建软链接便于管理:

ln -s /data/filebeat-8.2.2-linux-x86_64 /data/filebeat

3. 创建配置目录

mkdir -p /data/filebeat/config

四、实战案例:典型配置场景

📁 所有配置文件建议存放于 /data/filebeat/config 目录


案例 1:标准输入 → 控制台输出(调试用)

用途:快速验证 Filebeat 是否正常工作。

# config/input-stdin.yaml
# 指定filebeat的数据源为stdin
filebeat.inputs:# 定义一个输入源列表(支持多个输入)- type: stdin        # 输入类型:标准输入(即键盘输入)enabled: true      # 是否启用此输入源:true 表示启用,false 表示禁用# 指定filebeat的输出源console
output.console:pretty: true         # 是否格式化输出:true 表示以美观、易读的 JSON 格式打印日志# 包含换行和缩进,便于调试查看结构# 若为 false,则输出紧凑的单行 JSON

启动命令

/data/filebeat/filebeat -e -c config/input-stdin.yaml

💡 输入任意文本,观察输出的 JSON 结构,重点关注 message 字段。


案例 2:采集普通日志文件input插件之log

用途:采集单行日志,如应用日志、系统日志。

# config/input-log.yaml
# 指定filebeat的数据源为log
filebeat.inputs:
- type: logenabled: truepaths:- /var/log/myapp/app.log# 指定filebeat的输出源console
output.console:pretty: true

🔍 工作机制

  • Filebeat 按行读取日志
  • 通过 registry 文件记录采集位置(inode + offset)
  • 默认路径:/data/filebeat/data/registry/filebeat/log.json,该文件中记录了:采集的文件名称,inode,偏移量(offset)以及采集的时间戳等。
  • 重启后自动从中断处继续采集

📌 示例数据

# 创建目录
sudo mkdir -p /var/log/myapp# 写入示例日志
tee /var/log/myapp/app.log > /dev/null << 'EOF'
2025-08-04 10:00:00 INFO  [web-server] User login attempt from IP=192.168.1.100, UID=usr-78321
2025-08-04 10:00:02 DEBUG [auth-service] Validating token for user: alice@example.com
2025-08-04 10:00:03 INFO  [web-server] Login successful for user=alice@example.com, duration=125ms
2025-08-04 10:01:10 WARN  [payment-gateway] Payment timeout for order=ord-99213, retrying...
2025-08-04 10:01:15 ERROR [db-connector] Failed to query user profile: connection refused, host=db-primary, error_code=5003
2025-08-04 10:02:00 INFO  [cache-service] Redis connection restored, resuming operations
2025-08-04 10:02:30 DEBUG [api-gateway] Request received: method=GET, path=/api/v1/user/profile, from=192.168.1.105
2025-08-04 10:02:31 INFO  [api-gateway] Response sent: status=200, duration=15ms
2025-08-04 10:03:00 WARN  [file-uploader] Upload size exceeds limit: file=report.pdf, size=52MB, limit=50MB
2025-08-04 10:03:45 ERROR [email-service] SMTP server unreachable: host=mail.company.com, port=587, retry=2
EOF

案例 3:多行日志合并(按行数)count类型案例

用途:适用于固定格式的多行日志(如每条日志占 4 行)。

# config/multiline-count.yaml
filebeat.inputs:
- type: logenabled: truepaths:- /tmp/data.log# 指定多行匹配模式multiline:# 指定多行匹配的类型type: countcount_lines: 4  # 每 4 行合并为一个事件# 指定filebeat的输出源console
output.console:pretty: true

📌 示例数据 /tmp/data.log

[INFO] User login attempt
User: alice
IP: 192.168.1.100
Time: 2025-08-04T10:00:00Z[ERROR] Database connection failed
Service: db-service
Error: timeout
Retry: 3

案例 4:多行日志合并(正则匹配)pattern类型案例

用途:识别日志起始行,适用于 JSON、异常栈等结构化日志。

# config/multiline-pattern.yaml
filebeat.inputs:
- type: logenabled: truepaths:- /tmp/app-logs.jsonmultiline:type: patternpattern: ^\{                # 匹配以 "{" 开头的行negate: true                # 非起始行视为延续match: after                # 将后续行附加到上一行# 指定filebeat的输出源console
output.console:pretty: true

📌 示例数据 /tmp/app-logs.json

{"level": "INFO","msg": "Application started","timestamp": "2025-08-04T10:00:00Z"
}
{"level": "ERROR","msg": "Failed to connect","error": "connection timeout","service": "auth-service"
}

案例 5:添加标签与自定义字段

用途:为日志添加上下文信息,便于后续过滤和分析。

# config/enrich-fields.yaml
# Filebeat 配置:演示如何为采集的日志添加标签(tags)和自定义字段(fields)
# 并使用处理器(processors)过滤不需要的日志# 定义日志输入源列表
filebeat.inputs:- type: log                # 输入类型:从日志文件读取enabled: true            # 是否启用此输入源(true=启用,false=禁用)# 为该输入源添加标签,用于后续分类或路由tags: ["web", "production"]# 自定义字段:添加业务上下文信息fields:app: "user-service"    # 应用名称env: "prod"            # 环境:生产环境team: "backend"        # 负责团队# 控制自定义字段是否提升到 JSON 根层级# false:字段放在 fields 对象下,如:{"fields": {"app": "user-service", ...}}# true:字段直接放在根层级,如:{"app": "user-service", ...}fields_under_root: false# 指定要采集的日志文件路径(支持通配符)paths:- /var/log/user-service/*.log# 第二个输入源:采集数据库错误日志- type: logenabled: truetags: ["db", "critical"]           # 标签:数据库、关键级别fields:database: "mysql"                # 数据库类型role: "master"                   # 角色:主库# 将自定义字段提升到根层级,便于在 Kibana 中直接查询fields_under_root: truepaths:- /var/log/mysql/error.log      # 采集 MySQL 错误日志# 处理器(processors):在发送前对日志事件进行处理
processors:# drop_event.when:条件性丢弃事件- drop_event.when:# 使用正则表达式匹配 message 字段regexp:# 如果 message 字段以 "DEBUG:" 开头,则丢弃该日志message: "^DEBUG:"# 注:此处理器会作用于所有输入源的日志# 即:无论是 user-service 还是 mysql 的日志,只要以 DEBUG: 开头都会被过滤掉
# 指定filebeat的输出源console
output.console:pretty: true
📄 日志数据示例
1. /var/log/user-service/access.log 示例内容
2025-08-04 10:00:00 INFO  User login: id=usr-1001, ip=192.168.1.100
2025-08-04 10:00:05 DEBUG Request processed: path=/api/profile, duration=12ms
2025-08-04 10:00:10 WARN  Session expired for user: usr-1001
2025-08-04 10:00:15 ERROR Failed to update profile: DB connection timeout
DEBUG: Entering function validate_token()
2025-08-04 10:00:20 INFO  Password changed successfully for usr-1001

⚠️ 注意:以 DEBUG: 开头的日志将被 drop_event 处理器过滤掉,不会发送出去


2. /var/log/mysql/error.log 示例内容
2025-08-04 10:00:01 [ERROR] Failed to connect to primary: host=db-master, error=Connection refused
2025-08-04 10:00:30 [Warning] Slow query detected: SELECT * FROM users WHERE active=1, duration=2.3s
DEBUG: Executing query plan for JOIN operation
2025-08-04 10:01:00 [ERROR] InnoDB: Unable to allocate memory for buffer pool
2025-08-04 10:01:15 [Note] Slave I/O thread: Connected to master

⚠️ 同样,DEBUG: 开头的日志会被自动丢弃。


🧪 Filebeat 输出示例(控制台)

假设你配置了 output.console: { pretty: true },运行后你会看到类似以下结构:

示例 1:来自 user-service 的日志(fields 在子对象中)
{"@timestamp": "2025-08-04T10:00:00.000Z","message": "2025-08-04 10:00:00 INFO  User login: id=usr-1001, ip=192.168.1.100","tags": ["web", "production"],"fields": {"app": "user-service","env": "prod","team": "backend"},"log": { "file": { "path": "/var/log/user-service/access.log" } },"input": { "type": "log" }
}
示例 2:来自 MySQL 的日志(fields 提升到根层级)
{"@timestamp": "2025-08-04T10:00:01.000Z","message": "2025-08-04 10:00:01 [ERROR] Failed to connect to primary: host=db-master, error=Connection refused","tags": ["db", "critical"],"database": "mysql","role": "master","log": { "file": { "path": "/var/log/mysql/error.log" } },"input": { "type": "log" }
}

✅ 总结:这个配置的作用
功能说明
多输入源同时采集应用日志和数据库日志
打标签(tags)便于在 Kibana 中按 webdbcritical 等分类
加字段(fields)添加 appenvdatabase 等元数据
字段层级控制fields_under_root 决定字段是否在根层级
日志过滤使用 drop_event 删除调试日志,减少无效数据传输
  • 参考案例:

https://www.elastic.co/guide/en/beats/filebeat/8.2/filebeat-input-log.html#filebeat-input-log-common-options
https://www.elastic.co/guide/en/beats/filebeat/8.2/filtering-and-enhancing-data.html


案例 6:解析嵌套 JSON 日志(字符串内 JSON)

用途:将日志行中某个字段(如 payload)包含的 JSON 字符串 解析为结构化字段。

🔍 常见场景:API 网关、消息队列日志、审计日志中常出现 "data": "{\"key\": \"value\"}" 这类嵌套结构。

# config/json-parse.yaml
# Filebeat 配置:解析日志中嵌套的 JSON 字符串
# 作者:You are Qwen, created by Alibaba Cloud. You are a helpful assistant.filebeat.inputs:# 定义日志输入源- type: log                # 输入类型:从文件读取日志enabled: true            # 启用此输入# 指定要采集的日志文件路径paths:- /tmp/nested-data.log  # 示例路径,需确保文件存在# 配置 JSON 解析规则(适用于整个日志行是 JSON 的情况)json.keys_under_root: true    # 将解析出的字段提升到 JSON 根层级# 例如:payload 中的 method、path 直接成为顶级字段json.add_error_key: true      # 如果 JSON 解析失败,添加 "error.message" 字段标记错误# 便于排查问题,不会导致日志丢失# 指定输出目标为控制台(调试用)
output.console:pretty: true                  # 格式化输出 JSON,便于阅读

📄 示例日志文件:/tmp/nested-data.log
{ "source": "api-gateway", "timestamp": "2025-08-04T10:00:00Z", "payload": "{\"method\": \"POST\", \"path\": \"/login\", \"status\": 200, \"user_id\": \"usr-1001\"}" }
{ "source": "message-queue", "timestamp": "2025-08-04T10:00:05Z", "payload": "{\"event\": \"order_created\", \"order_id\": \"ord-88123\", \"amount\": 99.99}" }
{ "source": "audit-log", "timestamp": "2025-08-04T10:00:10Z", "payload": "{\"action\": \"delete\", \"target\": \"file-77654\", \"by\": \"admin\"}" }
{ "source": "api-gateway", "timestamp": "2025-08-04T10:00:15Z", "payload": "INVALID_JSON_HERE" }

⚠️ 最后一行是故意构造的解析失败日志,用于演示 add_error_key: true 的作用。


🧪 预期输出(控制台)
正常解析的示例:
{"@timestamp": "2025-08-04T10:00:00.000Z","source": "api-gateway","timestamp": "2025-08-04T10:00:00Z","payload": "{\"method\": \"POST\", \"path\": \"/login\", \"status\": 200, \"user_id\": \"usr-1001\"}","method": "POST","path": "/login","status": 200,"user_id": "usr-1001","log": { "file": { "path": "/tmp/nested-data.log" } },"input": { "type": "log" }
}

method, path, status, user_id 原本在 payload 字符串中,现在被提取为顶级字段

解析失败的示例(自动添加 error):
{"@timestamp": "2025-08-04T10:00:15.000Z","source": "api-gateway","timestamp": "2025-08-04T10:00:15Z","payload": "INVALID_JSON_HERE","error": {"message": "Failed to decode JSON: invalid character 'I' looking for beginning of value"},"log": { "file": { "path": "/tmp/nested-data.log" } }
}

✅ 因为 add_error_key: true,即使解析失败,日志也不会丢,而是带错误信息继续传输。


案例 7:多行 JSON 日志解析(换行分隔的 JSON 对象)

用途:采集每行一个 JSON 对象但被系统换行截断的日志,先合并多行,再解析为结构化数据。

🔍 常见场景:Java 应用打印 JSON 日志时自动换行、Docker 容器日志截断等。

# config/multiline-json.yaml
# Filebeat 配置:先合并多行 JSON,再使用处理器解析
# 适用于日志被截断、跨行的 JSON 日志
# 作者:You are Qwen, created by Alibaba Cloud. You are a helpful assistant.filebeat.inputs:- type: logenabled: truepaths:- /tmp/service-logs.json   # 日志路径# 多行合并配置:识别 JSON 起始行multiline:type: pattern              # 使用正则模式匹配pattern: ^\{               # 匹配以左大括号 "{" 开头的行(即 JSON 起始)negate: true               # 否定逻辑:如果某行**不匹配** pattern,则视为前一行的延续match: after               # 将不匹配的行附加到上一行末尾# 使用 processors 进行 JSON 解析(更灵活,推荐方式)processors:- decode_json_fields:     # 处理器:解析 JSON 字段fields: ["message"]    # 指定要解析的字段名(Filebeat 默认把整行日志放入 message)process_array: false   # 不处理数组类型(本例为单个对象)max_depth: 3           # 最大嵌套深度为 3 层target: ""             # 解析结果放入根层级(等同于 keys_under_root)overwrite_keys: false  # 如果字段已存在,不覆盖(避免冲突)# 输出到控制台,便于调试
output.console:pretty: true

📄 示例日志文件:/tmp/service-logs.json
{"level": "INFO","service": "auth-service","event": "user_login","user": "alice","ip": "192.168.1.100","timestamp": "2025-08-04T10:00:00Z"
}
{"level": "ERROR","service": "payment-service","event": "transaction_failed","order_id": "ord-99213","error": "timeout","duration_ms": 5000,"retry_count": 3,"timestamp": "2025-08-04T10:01:15Z"
}
{"level": "WARN","service": "cache-service","event": "connection_restored","details": {"host": "redis-primary","reconnect_time_ms": 120},"timestamp": "2025-08-04T10:02:00Z"
}

💡 注意:这个文件在实际环境中可能被日志系统(如 Docker)按行截断,导致每个 JSON 被拆成多行。


🧪 预期输出(控制台)
{"@timestamp": "2025-08-04T10:00:00.000Z","level": "INFO","service": "auth-service","event": "user_login","user": "alice","ip": "192.168.1.100","timestamp": "2025-08-04T10:00:00Z","log": { "file": { "path": "/tmp/service-logs.json" } },"input": { "type": "log" }
}

✅ 完整的 JSON 对象被成功还原并结构化!

当然可以!以下是 案例 8:使用 filestream完整增强版本,包含:

✅ 逐行中文注释
✅ 补充日志示例(NDJSON 格式)
✅ 输出效果说明
✅ 与传统 log 类型的对比
✅ 安全脱敏,适合用于文档、培训或生产参考


案例 8:使用 filestream(推荐方式)

用途filestreamlog 输入类型的现代化替代,专为高效、稳定地读取日志文件而设计,支持内置解析器(如 NDJSON、CSV),是 Filebeat 7.13+ 推荐的日志采集方式

# config/input-filestream.yaml
# Filebeat 配置:使用 filestream 输入类型采集结构化日志(如 NDJSON)
# 作者:You are Qwen, created by Alibaba Cloud. You are a helpful assistant.filebeat.inputs:# 定义 filestream 输入源(推荐替代 type: log)- type: filestreamenabled: true                    # 是否启用此输入# 指定要监控的日志文件路径(支持通配符)paths:- /var/log/nginx/access.log    # 示例:Nginx 访问日志(NDJSON 格式)# - /var/log/app/*.ndjson     # 也可匹配多个文件# parsers:filestream 内置的解析器,用于预处理日志行parsers:- ndjson:                      # 解析换行分隔的 JSON(Newline-delimited JSON)overwrite_keys: true       # 如果解析出的字段与现有字段冲突,允许覆盖target: ""                 # 将解析结果提升到根层级(等同于 keys_under_root: true)add_error_key: true        # 解析失败时添加 error.message 字段,不丢日志message_key: message       # 指定哪个字段作为日志消息体(可选,默认为 message)# 可选:额外的处理器(processors),在 parsers 之后执行
processors:# 如果 parsers.ndjson 未完全解析,可再用 decode_json_fields 增强- decode_json_fields:fields: ["message"]            # 解析 message 字段中的 JSONmax_depth: 2                   # 最大嵌套深度为 2 层target: ""                     # 提升到根层级overwrite_keys: false          # 避免覆盖已存在的字段process_array: false           # 不处理数组类型# 示例:添加时间戳字段(如果日志中无时间)# - add_fields:#     target: ''#     fields:#       event_source: "nginx-access"# 输出到控制台(调试用)
output.console:pretty: true                       # 格式化输出 JSON,便于阅读

📄 示例日志文件:/var/log/nginx/access.log(NDJSON 格式)

📌 NDJSON(Newline-Delimited JSON)是一种常见结构化日志格式,每行一个独立 JSON 对象。

{"time":"2025-08-04T10:00:00Z","client_ip":"192.168.1.100","method":"GET","path":"/index.html","status":200,"duration_ms":45,"user_agent":"Mozilla/5.0"}
{"time":"2025-08-04T10:00:02Z","client_ip":"192.168.1.105","method":"POST","path":"/api/login","status":401,"duration_ms":120,"user_agent":"PostmanRuntime/7.29"}
{"time":"2025-08-04T10:00:05Z","client_ip":"192.168.1.110","method":"GET","path":"/static/app.js","status":200,"duration_ms":10,"user_agent":"Safari/15.4"}
{"time":"2025-08-04T10:00:08Z","client_ip":"malicious-ip","method":"GET","path":"/admin","status":403,"duration_ms":5,"user_agent":"Nmap"}
{"invalid_json": "missing_brace"    # 故意构造的错误行,测试 add_error_key

💡 提示:Nginx 可通过 log_format 配置输出 JSON 或 NDJSON 格式日志。


🧪 预期输出(控制台)
✅ 正常解析的示例:
{"@timestamp": "2025-08-04T10:00:00.000Z","time": "2025-08-04T10:00:00Z","client_ip": "192.168.1.100","method": "GET","path": "/index.html","status": 200,"duration_ms": 45,"user_agent": "Mozilla/5.0","log": {"file": {"path": "/var/log/nginx/access.log"},"offset": 0},"input": {"type": "filestream"},"agent": { ... },"host": { ... }
}

✅ 所有字段已结构化,可直接在 Kibana 中查询、过滤、可视化!

❌ 解析失败的示例(add_error_key: true 生效):

{"@timestamp": "2025-08-04T10:00:08.000Z","message": "{\"invalid_json\": \"missing_brace\"","error": {"message": "Failed to decode NDJSON: unexpected end of JSON input"},"log": { "file": { "path": "/var/log/nginx/access.log" } }
}

✅ 日志未丢失,仅标记错误,便于后续排查。


🔍 filestream 优势详解(vs log
特性type: log(旧)type: filestream(新,推荐)
性能一般更高(优化的文件句柄管理)
稳定性较好更好(支持文件旋转、重命名更可靠)
内置解析器✅ 支持 ndjson, csv, multiline
配置方式使用 json.*processors使用 parsers + processors,逻辑更清晰
生命周期管理基础支持 close_* 规则(如按时间、大小关闭)
推荐程度❌ 不推荐新项目使用官方推荐,未来发展方向

🛠️ 可选高级配置(按需添加)
  # 文件关闭策略(可选)close:inactivity: 5m        # 文件 5 分钟无变化则关闭reader:               # 控制读取行为max_bytes: 1048576  # 每次最多读取 1MB# 排除临时文件exclude_files: ['\.tmp$', '\.log\.part$']

案例 9:Filebeat 采集 Nginx 日志


一、安装并配置 Nginx
1. 安装 Nginx
yum -y install nginx

✅ 说明:安装最新版 Nginx(CentOS/RHEL 系统),用于生成访问日志。


2. 修改 Nginx 配置,启用 JSON 格式日志

编辑配置文件:

vim /etc/nginx/nginx.conf

http { } 块中添加 JSON 日志格式定义

log_format nginx_json'{''"@timestamp":"$time_iso8601",''"host":"$server_addr",''"clientip":"$remote_addr",''"SendBytes":$body_bytes_sent,''"responsetime":$request_time,''"upstreamtime":"$upstream_response_time",''"upstreamhost":"$upstream_addr",''"http_host":"$host",''"uri":"$uri",''"domain":"$host",''"xff":"$http_x_forwarded_for",''"referer":"$http_referer",''"tcp_xff":"$proxy_protocol_addr",''"http_user_agent":"$http_user_agent",''"status":"$status"''}';

🔍 注解:

  • log_format:定义名为 nginx_json 的日志格式
  • 使用双引号和转义,确保输出为合法 JSON 字符串
  • $variable 是 Nginx 内置变量,代表请求的各个字段
  • 每个请求将输出一行 JSON,便于后续解析

设置访问日志使用该格式

server { } 块中修改或添加:

access_log /var/log/nginx/access.log nginx_json;

✅ 说明:指定日志路径和格式为 nginx_json


3. 检查 Nginx 配置语法
nginx -t

✅ 正常输出应为:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

4. 启动 Nginx 服务
systemctl enable --now nginx

✅ 说明:开机自启并立即启动 Nginx


5. 生成测试日志
curl http://localhost/
curl http://localhost/404

查看日志是否生成:

tail -f /var/log/nginx/access.log

示例输出(一行 JSON):

{"@timestamp":"2025-08-05T14:19:31+08:00","host":"127.0.0.1","clientip":"127.0.0.1","SendBytes":615,"responsetime":0.000,"upstreamtime":"-","upstreamhost":"-","http_host":"localhost","uri":"/index.html","domain":"localhost","xff":"-","referer":"-","tcp_xff":"-","http_user_agent":"curl/7.29.0","status":"200"}

二、编写 Filebeat 配置文件采集 Nginx 日志
创建配置文件
vim config/input-log-to-console.yaml
配置内容(带逐行注释)
# config/input-log-to-console.yaml
# Filebeat 配置:采集 Nginx JSON 格式访问日志,并输出到控制台
# 定义输入源
filebeat.inputs:- type: log                    # 输入类型:从日志文件读取enabled: true                # 启用此输入paths:- /var/log/nginx/access.log  # 采集 access.log 及轮转文件(如 access.log.1)# 配置 JSON 解析规则json.keys_under_root: true   # 将 JSON 字段提升到根层级(如 status、clientip 直接可用)json.add_error_key: true     # 解析失败时添加 error.message 字段,不丢日志json.overwrite_keys: false   # 如果字段已存在(如 @timestamp),不覆盖原值# 输出到控制台(调试用)
output.console:pretty: true                   # 格式化输出 JSON,便于阅读和调试

✅ 说明:

  • json.keys_under_root: true 是关键,否则所有字段会嵌套在 json.*
  • 支持日志轮转(access.log*
  • 使用 add_error_key 可避免因单条日志格式错误导致整个采集失败

三、启动 Filebeat 实例
./filebeat -e -c config/input-log-to-console.yaml

参数说明:

  • -e:将 Filebeat 自身日志输出到 stderr,便于调试
  • -c:指定配置文件路径

四、使用 filestream 采集(推荐方式)

⚠️ type: log 已逐步被 filestream 替代,推荐新项目使用。

# config/filestream-nginx-to-console.yaml
filebeat.inputs:- type: filestreamenabled: truepaths:- /var/log/nginx/access.log# 使用 NDJSON 解析器(每行一个 JSON 对象)parsers:- ndjson:overwrite_keys: truetarget: ""add_error_key: trueoutput.console:pretty: true

filestream + ndjson 是更现代、更稳定的组合。


案例 10:使用 Filebeat 采集 Tomcat 结构化日志

一、下载并安装 Tomcat
1. 下载二进制包(推荐 Apache 官方源)
wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.107/bin/apache-tomcat-9.0.107.tar.gz

🔍 提示:确保服务器已安装 wget 和网络可达。


2. 解压到统一软件目录
# 创建目录(建议使用 /data 或 /opt)
mkdir -p /data/softwares# 解压
tar xf apache-tomcat-9.0.107.tar.gz -C /data/softwares/# 创建软链接便于管理
ln -s /data/softwares/apache-tomcat-9.0.107 /data/tomcat

✅ 好处:升级时只需修改软链接指向新版本。


二、配置 JDK 环境(Tomcat 依赖)
1. 确保已安装 JDK 17(Tomcat 9+ 推荐)
# 示例路径,请根据实际 JDK 安装位置调整
export JAVA_HOME=/usr/lib/jvm/jdk-17.0.0.1
export PATH=$JAVA_HOME/bin:$PATH
2. 写入环境变量文件(永久生效)
cat > /etc/profile.d/java.sh << 'EOF'
export JAVA_HOME=/usr/lib/jvm/jdk-17.0.0.1
export PATH=$JAVA_HOME/bin:$PATH
EOF
3. 加载环境变量
source /etc/profile.d/java.sh
4. 验证 Java 是否可用
java -version

✅ 正常输出应包含 openjdk version "17" 或类似信息。


三、配置 Tomcat 输出 JSON 格式访问日志

🔍 默认日志为文本格式,不利于结构化分析。我们通过 AccessLogValve 配置为 NDJSON(Newline-delimited JSON) 格式。

  • 编辑 server.xml
vim /data/tomcat/conf/server.xml
  • <Host name="localhost"...> 标签内添加以下 Valve 配置:

💡 通常位于文件约 133–149 行之间,可搜索 <Host 定位。

<Valve className="org.apache.catalina.valves.AccessLogValve"directory="logs"prefix="access_log"suffix=".json"fileDateFormat=".yyyy-MM-dd"pattern="{&quot;time&quot;:&quot;%t&quot;,&quot;ip&quot;:&quot;%a&quot;,&quot;method&quot;:&quot;%r&quot;,&quot;status&quot;:&quot;%s&quot;,&quot;user_agent&quot;:&quot;%{User-Agent}i&quot;,&quot;bytes_sent&quot;:&quot;%B&quot;,&quot;request_time&quot;:&quot;%D&quot;}"resolveHosts="false"renameOnRotate="true" />

🔍 参数说明

  • directory="logs":日志输出到 logs/ 目录
  • prefix="access_log.":文件前缀,如 access_log.2025-08-05.json
  • suffix=".json":后缀为 .json,便于识别
  • pattern:定义 JSON 结构,字段需对双引号转义为 &quot;
    • %t:时间戳
    • %a:客户端 IP
    • %r:请求方法 + URI + 协议
    • %s:HTTP 状态码
    • %{User-Agent}i:User-Agent 头
    • %B:发送字节数
    • %D:请求处理时间(毫秒)
  • renameOnRotate="true":每日轮转时重命名旧文件

四、启动 Tomcat 服务
1. 启动 Tomcat
/data/tomcat/bin/startup.sh

✅ 输出:Tomcat started.

2. 验证是否监听 8080 端口
ss -tlnp | grep 8080
3. 测试访问并生成日志
curl http://127.0.0.1:8080/
curl http://127.0.0.1:8080/404
4. 查看 JSON 日志是否生成
tail -f /data/tomcat/logs/access_log.*.json

示例输出:

{"time":"[05/Aug/2025:14:59:11 +0800]","ip":"0:0:0:0:0:0:0:1","method":"GET / HTTP/1.1","status":"200","user_agent":"curl/7.29.0","bytes_sent":"11232","request_time":"235"}
{"time":"[05/Aug/2025:14:59:12 +0800]","ip":"0:0:0:0:0:0:0:1","method":"GET /404 HTTP/1.1","status":"404","user_agent":"curl/7.29.0","bytes_sent":"755","request_time":"8"}

✅ 成功!每行一个 JSON 对象(NDJSON 格式)。


五、编写 Filebeat 配置文件采集 Tomcat 日志
创建配置文件
vim config/tomcat-filestream-console.yaml
配置内容
# config/tomcat-filestream-console.yaml
# Filebeat 配置:采集 Tomcat JSON 格式访问日志。filebeat.inputs:- type: filestreamenabled: true# 采集所有 access_log 开头的 JSON 文件paths:- /data/tomcat/logs/access_log*.json# 可选:采集应用日志(如 catalina.out)# - /data/tomcat/logs/catalina.out# 内置解析器:自动解析每行为一个 JSON 对象(NDJSON)parsers:- ndjson:# 将解析后的字段提升到根层级target: ""# 允许覆盖已有字段(如 message)overwrite_keys: true# 解析失败时添加 error.message 字段add_error_key: true# 添加自定义字段,标识日志来源fields:app: "tomcat"log_type: "access"service: "web-application"# false的话 不将 fields 写入根层级,避免污染fields_under_root: true# 处理器链:进一步处理 message 字段(如果 parsers 未完全解析)
processors:- decode_json_fields:fields: ["message"]        # 解析 message 中的 JSONprocess_array: falsemax_depth: 2target: ""                 # 提升到根overwrite_keys: false      # 避免重复覆盖# 示例:移除原始 message 字段(可选,减少冗余)# - drop_fields:#     fields: ["message"]# 输出到控制台(调试用)
output.console:pretty: true                   # 格式化输出,便于阅读

六、启动 Filebeat 实例
./filebeat -e -c config/tomcat-filestream-console.yaml

参数说明:

  • -e:输出 Filebeat 自身日志到 stderr
  • -c:指定配置文件

案例11:Filebeat 采集 containerd 容器类型日志使用 dissect 处理器

1. 背景说明

在使用 containerd 作为容器运行时的环境中,容器日志通常以 JSON 格式存储在宿主机的特定目录下。Filebeat 支持通过 container 输入类型直接采集这些日志文件,适用于 Kubernetes 或直接使用 ctr 命令运行容器的场景。

2. 环境准备
  • Filebeat 版本:8.2.2
  • 容器运行时:containerd(Docker 使用 containerd 作为底层运行时)
  • 日志路径:/var/lib/docker/containers/*/*.log

⚠️ 确保 Filebeat 有权限读取容器日志目录。

3. 配置 Filebeat

创建配置文件:

vim config/container-console.yaml

内容如下:

filebeat.inputs:- type: containerpaths:- '/var/lib/docker/containers/*/*.log'# 可选:只采集标准输出stream: stdout# 可选:排除某些容器# exclude_containers: ['.*redis.*', '.*nginx.*']
processors:- dissect:tokenizer: "%{clientip} - %{ident} [%{timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{response} %{bytes} \"%{referrer}\" \"%{agent}\""field: "message"target_prefix: "nginx.access"ignore_failure: true
output.console:pretty: true
4. 启动 Filebeat
./filebeat -e -c config/container-console.yaml

案例12:Filebeat 采集 Redis 慢查询日志(实验性功能)

1. 背景说明

Filebeat 提供实验性 redis 输入类型,用于从 Redis 实例的慢查询日志(slow log)中提取性能数据,适用于监控高延迟命令。

⚠️ 此功能为实验性(experimental),不建议用于生产环境。

2. 启动 Redis 实例
docker run --name redis-server -d -p 6379:6379 redis:7.2.5-alpine3.20
3. 配置 Redis 慢查询日志

进入容器,设置慢查询阈值(单位:微秒):

docker exec -it redis-server redis-cli
127.0.0.1:6379> CONFIG SET slowlog-log-slower-than 1000
OK
4. 写入测试数据并触发慢日志
127.0.0.1:6379> SET app_config '{"version": "v1.0", "timeout": 30}'
OK
127.0.0.1:6379> LPUSH request_queue "task_001" "task_002" "task_003"
(integer) 3
5. 配置 Filebeat
  • 创建配置文件:
vim config/redis-console.yaml
filebeat.inputs:- type: redishosts: ["172.17.0.3:6379"]  # Redis 容器 IP# 可选:设置密码# password: "your_password"# 可选:设置数据库# db: 0output.console:pretty: true
  • 查看容器IP
docker inspect 58c32cfd9c27 | grep -i "ipaddress""SecondaryIPAddresses": null,"IPAddress": "172.17.0.3","IPAddress": "172.17.0.3",
6. 启动 Filebeat
./filebeat -e -c config/12-redis-to-console.yaml
7. 验证输出

控制台将输出 Redis 慢查询日志条目,例如:

{"@timestamp": "2025-08-05T10:05:00.000Z","redis": {"slowlog": {"id": 1,"duration": 1500,"client_ip": "172.17.0.1","command": "LPUSH request_queue task_001 task_002 task_003"}},"agent": {"type": "filebeat","hostname": "monitor-host"}
}

案例13:Filebeat 采集 TCP 日志并输出到 Redis

1. 背景说明

在分布式系统中,某些应用可能通过 TCP 协议直接发送日志。Filebeat 可作为 TCP 服务器接收日志,并将结构化数据写入 Redis 缓冲区,供后续 Logstash 或消费者处理。

2. 启动 Redis 服务
docker run -d --name redis-server --network host redis:7.2.5-alpine3.20

验证端口监听:

ss -ntl | grep 6379
3. 配置 Filebeat

创建配置文件:

vim config/tcp-to-redis.yaml
filebeat.inputs:- type: tcphost: "0.0.0.0:8888"# 可选:设置最大连接数# max_connections: 1024# 可选:设置超时# timeout: 30soutput.redis:hosts: ["192.168.130.62:6379"]   # ✅ 端口写在 host 后面key: "app-logs-ingest"db: 3timeout: 5# 可选:启用 SSL# ssl.enabled: true
4. 启动 Filebeat
./filebeat -e -c config/tcp-to-redis.yaml
5. 发送测试日志

从另一台机器发送日志:

echo "Application event: user_login success from 192.168.130.62" | nc 192.168.130.62 8888
6. Redis 验证数据

进入 Redis 查看数据:

docker exec -it redis-server redis-cli -n 3
127.0.0.1:6379[3]> KEYS *
1) "app-logs-ingest"127.0.0.1:6379[3]> TYPE app-logs-ingest
list127.0.0.1:6379[3]> LRANGE app-logs-ingest 0 -1
1) "{\"@timestamp\":\"2025-08-06T09:44:40.032Z\",\"@metadata\":{\"beat\":\"filebeat\",\"type\":\"_doc\",\"version\":\"8.2.2\"},\"host\":{\"name\":\"es-node-02\"},\"agent\":{\"version\":\"8.2.2\",\"ephemeral_id\":\"6f41ee94-a3f4-493b-ab5b-cad7498394ce\",\"id\":\"f11283a2-e3df-4ccf-99cb-ef1fac593cfa\",\"name\":\"es-node-02\",\"type\":\"filebeat\"},\"message\":\"Application event: user_login success from 192.168.130.62\",\"log\":{\"source\":{\"address\":\"192.168.130.61:37942\"}},\"input\":{\"type\":\"tcp\"},\"ecs\":{\"version\":\"8.0.0\"}}"

好的!以下是根据你的 实际环境(IP: 192.168.130.61/62/63) 重新整理、优化并标准化的 三大经典 Filebeat 日志采集案例,适用于生产级 EFK 架构。


案例14:采集 TCP 日志 → 输出到本地文件

用途:调试、备份、审计等场景

📁 配置文件:config/tcp-to-file.yaml
filebeat.inputs:- type: tcphost: "0.0.0.0:8888"max_connections: 1024timeout: 30soutput.file:path: "/data/logs/filebeat-output"         # 建议使用独立目录filename: app-logs.txtrotate_every_kb: 10240                     # 每 10MB 滚动一次number_of_files: 7                         # 最多保留 7 个文件permissions: 0644                          # 定义文件的权限# 启用日志输出方便调试
logging.level: info
🧪 创建输出目录并启动
mkdir -p /data/logs/filebeat-output
./filebeat -e -c config/tcp-to-file.yaml
📦 发送测试数据(从任意节点)
echo "用户登录成功: zhangsan @ $(date)" | nc 192.168.130.62 8888
🔍 查看结果
cat /data/logs/filebeat-output/* |jq

输出示例:

{"@timestamp": "2025-08-06T09:57:13.723Z","@metadata": {"beat": "filebeat","type": "_doc","version": "8.2.2"},"agent": {"ephemeral_id": "888abfa0-c230-4a6c-92f5-52a8472ac87c","id": "f11283a2-e3df-4ccf-99cb-ef1fac593cfa","name": "es-node-02","type": "filebeat","version": "8.2.2"},"message": "用户登录成功: zhangsan @ Wed Aug  6 17:57:13 CST 2025","log": {"source": {"address": "192.168.130.62:42754"}},"input": {"type": "tcp"},"ecs": {"version": "8.0.0"},"host": {"name": "es-node-02"}
}

案例15:采集 TCP 日志 → 输出到 Elasticsearch 集群(自定义索引模板)

用途:结构化日志入 ES,用于 Kibana 展示

📁 配置文件:config/tcp-to-es.yaml
# 定义 Filebeat 的输入源(日志来源)
filebeat.inputs:# 配置一个 TCP 类型的输入- type: tcp# 监听本机所有 IP 的 8888 端口,接收外部通过 TCP 发送的日志host: "0.0.0.0:8888"# 配置输出目标为 Elasticsearch
output.elasticsearch:# 指定 ES 集群的多个节点地址,实现高可用和负载均衡hosts: ["http://192.168.130.61:9200", "http://192.168.130.62:9200", "http://192.168.130.65:9200"]# 指定日志写入的索引名称,按天分割,例如:app-tcp-logs-2025.08.07index: "app-tcp-logs-%{+yyyy.MM.dd}"data_stream.enabled: false   # 强制关闭 Data Stream# 显式关闭模板自动加载,避免报错
setup.template.enabled: false
手动创建模版
curl -X PUT "http://192.168.130.61:9200/_template/app-tcp-logs" -H "Content-Type: application/json" -d'
{"index_patterns": ["app-tcp-logs-*"],"settings": {"number_of_shards": 3,"number_of_replicas": 1}
}'
启动命令
./filebeat -e -c config/tcp-to-es.yaml
发送测试日志
echo "订单创建成功: order_20250807_001" | nc 192.168.130.62 8888
验证 ES 中数据
curl -s 'http://192.168.130.61:9200/app-tcp-logs-*/_search?pretty' | grep -A 5 -B 5 "order_20250807"

✅ 成功写入 ES,可用于 Kibana 可视化。


案例15:Nginx JSON 日志采集 → EFK 架构(推荐生产用法)

架构:Nginx → Filebeat → Elasticsearch → Kibana


第一步:配置 Nginx 输出 JSON 格式日志
📄 修改 Nginx 配置:/etc/nginx/nginx.conf
http {# 定义 JSON 格式日志log_format json_access_log escape=json'{''"@timestamp":"$time_iso8601",''"clientip":"$remote_addr",''"remote_user":"$remote_user",''"request":"$request",''"status": "$status",''"body_bytes_sent": "$body_bytes_sent",''"http_referer":"$http_referer",''"http_user_agent":"$http_user_agent",''"http_x_forwarded_for":"$http_x_forwarded_for",''"upstream_response_time":"$upstream_response_time",''"request_time":"$request_time",''"host":"$host"''}';# 使用 JSON 格式记录访问日志access_log /var/log/nginx/access.log json_access_log;error_log /var/log/nginx/error.log;server {listen 80;server_name localhost;root /usr/share/nginx/html;index index.html;}
}
✅ 重载 Nginx
nginx -t && systemctl reload nginx
🧪 访问测试
curl "http://192.168.130.62?from=filebeat"
🔍 检查日志是否为 JSON
tail -1 /var/log/nginx/access.log

输出应为:

{"@timestamp":"2025-08-06T10:10:20+08:00","clientip":"192.168.130.61","remote_user":"-","request":"GET /?from=filebeat HTTP/1.1","status": "200","body_bytes_sent": "612","http_referer":"-","http_user_agent":"curl/7.68.0","http_x_forwarded_for":"-","upstream_response_time":"-","request_time":"0.000","host":"192.168.130.62"}

第二步:Filebeat 采集 Nginx 日志 → ES
📁 配置文件:config/nginx-to-es.yaml
filebeat.inputs:- type: filestreamid: nginx-accesspaths:- /var/log/nginx/access.log# 忽略 24 小时前的日志ignore_older: 24h# ✅ 正确方式:使用 ndjson 解析整行 JSON 日志parsers:- ndjson:# 整行就是 JSON,不需要嵌套在 message 字段里target: ""# 因为日志不是 { "message": "{...}" } 这种结构,所以不需要 message_key# 删除这一行 → 否则解析失败# message_key: messageoverwrite_keys: true  # 允许覆盖 @timestamp 等字段add_error_key: false  # 可选:不添加解析错误信息# 添加主机和 agent 元数据
processors:- add_host_metadata: ~- add_agent_metadata: ~# ❌ 删除 decode_json_fields 和 drop_fields
# 因为 ndjson 已经完成了解析,不需要二次处理output.elasticsearch:hosts:- "http://192.168.130.61:9200"- "http://192.168.130.62:9200"- "http://192.168.130.65:9200"index: "nginx-access-logs-%{+yyyy.MM.dd}"# 可选:设置用户名密码(如果你启用了安全)# username: "filebeat_writer"# password: "your_password"# 🔥 关键:关闭自动模板和 ILM
setup.ilm.enabled: false
setup.template.enabled: false   # ✅ 禁用自动模板创建# ❌ 不要再用 setup.template.* 配置
# 因为 ES 8.x 容易报错,建议手动管理
手动创建模板
PUT http://192.168.130.62:9200/_index_template/nginx-access-logs-template
{"index_patterns": ["nginx-access-logs-*"],"priority": 100,"template": {"settings": {"number_of_shards": 3,"number_of_replicas": 1,"mapping.total_fields.limit": 2000,"refresh_interval": "30s"},"mappings": {"dynamic": true,"properties": {"@timestamp": { "type": "date" },"clientip": { "type": "ip" },"remote_user": { "type": "keyword" },"request": { "type": "text" },"status": { "type": "short" },"body_bytes_sent": { "type": "long" },"http_referer": { "type": "keyword" },"http_user_agent": { "type": "text" },"http_x_forwarded_for": { "type": "ip" },"upstream_response_time": { "type": "float" },"request_time": { "type": "float" },"host": {"type": "object","properties": {"hostname": { "type": "keyword" },"ip": { "type": "ip" },"name": { "type": "keyword" },"architecture": { "type": "keyword" },"containerized": { "type": "boolean" },"id": { "type": "keyword" }}},"hostname": { "type": "keyword" }}}},"_meta": {"description": "Nginx JSON log template for ES 8.x"}
}
▶️ 启动 Filebeat
./filebeat -e -c config/nginx-to-es.yaml

第三步:Kibana 验证(可选)
  1. 登录 Kibana:http://192.168.130.61:5601
  2. 进入 Stack Management > Index Patterns
  3. 创建索引模式:nginx-access-logs-*
  4. 进入 Discover,查看实时日志

案例16:Filebeat 配置:将 JSON 字段提升到顶级

在使用 Filebeat 采集日志时,如果你的日志内容是 JSON 格式,并且你希望将这些 JSON 内容的字段 提升到顶级字段(Top-level Fields),而不是嵌套在 message 或某个子字段中,可以通过 json.keys_under_root 配置项来实现。


✅ 目标

将原始日志中 JSON 内容的字段,直接提升为 Elasticsearch 中的顶级字段。


📦 示例场景
原始日志内容(/var/log/app.log):
{"status":"success","user":"alice","ip":"192.168.1.1","@timestamp":"2025-04-05T12:34:56Z"}
{"status":"fail","user":"bob","ip":"192.168.1.2","@timestamp":"2025-04-05T12:35:01Z"}

✅ Filebeat 配置:将 JSON 字段提升到顶级

在你的 filebeat.yml 配置文件中,添加如下配置:

filebeat.inputs:
- type: logenabled: truepaths:- /var/log/app.log# 启用 JSON 解析json.keys_under_root: truejson.add_error_key: truejson.message_key: "log"  # 可选,如果你的消息字段名不是默认的 messageoutput.elasticsearch:hosts:- "http://192.168.130.61:9200"- "http://192.168.130.62:9200"- "http://192.168.130.65:9200"index: "nginx-%{+yyyy.MM.dd}"

📌 配置说明
配置项说明
json.keys_under_root: true将 JSON 的键提升到顶级字段
json.add_error_key: true如果 JSON 解析失败,添加 error.message 字段说明错误
json.message_key如果你希望保留原始 JSON 内容,可以指定字段名,默认是 message

📈 输出效果(Elasticsearch 文档)
{"@timestamp": "2025-04-05T12:34:56Z","status": "success","user": "alice","ip": "192.168.1.1"
}

而不是:

{"@timestamp": "...","message": "{\"status\":\"success\",...}"
}
http://www.dtcms.com/a/332709.html

相关文章:

  • Java集合Map与Stream流:Map实现类特点、遍历方式、Stream流操作及Collections工具类方法
  • 【软件设计模式】前置知识类图、七大原则(精简笔记版)
  • C++ 调试报错 常量中有换行符
  • 基于桥梁三维模型的无人机检测路径规划系统设计与实现
  • Cursor 分析 bug 记录
  • 3D视觉与空间智能
  • imx6ull-驱动开发篇25——Linux 中断上半部/下半部
  • 智谱开源了最新多模态模型,GLM-4.5V
  • 关系型数据库从入门到精通:MySQL 核心知识全解析
  • 高并发系统性能优化实战:实现5万并发与毫秒级响应
  • Kafka生产者——提高生产者吞吐量
  • LeetCode 面试经典 150_数组/字符串_最长公共前缀(20_14_C++_简单)(暴力破解)(求交集)
  • 简单使用 TypeScript 或 JavaScript 创建并发布 npm 插件
  • 从零到一:发布你的第一个 npm 开源库(2025 终极指南)
  • IT资讯 | VMware ESXi高危漏洞影响国内服务器
  • Day62--图论--97. 小明逛公园(卡码网),127. 骑士的攻击(卡码网)
  • 嵌入式 C 语言编程规范个人学习笔记,参考华为《C 语言编程规范》
  • 使用CMAKE-GU生成Visual Studio项目
  • ​Visual Studio 2013.5 ULTIMATE 中文版怎么安装?iso镜像详细步骤
  • Pushgateway安装和部署,以及对应Prometheus调整
  • 六维力传感器:工业机器人的“触觉神经”如何突破自动化瓶颈?
  • Linux crontab定时任务
  • 3.1. CPU拓扑配置
  • 4.2 寻址方式 (答案见原书 P341)
  • Nginx蜘蛛请求智能分流:精准识别爬虫并转发SEO渲染服务
  • 嵌入式学习日记(29)进程、线程
  • Java 中 Map 接口详解:知识点与注意事项
  • HarmonyOS 实战:用 List 与 AlphabetIndexer 打造高效城市选择功能
  • Java-99 深入浅出 MySQL 并发事务控制详解:更新丢失、锁机制与MVCC全解析
  • 中小体量游戏项目主干开发的流程说明