接口访问统计
本文档详细介绍如何在 Tio-Boot 框架中实现接口访问统计功能。通过实现 RequestStatisticsHandler
接口,我们创建了 DbRequestStatisticsHandler
类,用于记录每个 HTTP 请求的关键信息,并通过异步任务将数据保存到数据库中,为后续问题排查和系统性能分析提供数据依据。
1. 需求概述
在使用 Tio-Boot 框架时,我们需要对 HTTP 请求进行统计,记录每个请求的基本信息(如请求 ID、channel ID、IP 地址、请求方法、URI、User-Agent、header、body 等)。通过对这些数据进行分析,可以有效帮助我们进行问题排查以及系统性能监控。
2. 实现步骤
2.1 请求 ID 获取
Tio-Boot 为每个请求分配了两个标识:request id 和 channel id。获取方式及输出如下:
log.info("request id:{}", request.getId());
log.info("channel id:{}", Long.parseLong(request.getChannelContext().getId()));
输出示例:
request id:1
channel id:1831315515085565952
在业务层将这两个标识传递下去,便于在日志记录中追踪和定位问题。
2.2 创建 DbRequestStatisticsHandler
类
2.2.1 数据库表设计
首先,在数据库中创建用于保存 HTTP 请求统计数据的表。以下是 PostgreSQL 数据库下的建表 SQL 语句:
DROP TABLE IF EXISTS sys_http_request_statistics;
CREATE TABLE sys_http_request_statistics (
id BIGINT NOT NULL PRIMARY KEY,
channel_id BIGINT,
ip VARCHAR,
user_id VARCHAR,
method VARCHAR,
uri VARCHAR,
user_agent VARCHAR,
header TEXT,
body TEXT,
remark VARCHAR(256),
creator VARCHAR(64) DEFAULT '',
create_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updater VARCHAR(64) DEFAULT '',
update_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT NOT NULL DEFAULT 0,
tenant_id BIGINT NOT NULL DEFAULT 0
);
2.2.2 业务类实现:插入数据库
接下来编写业务类 DbRequestStatisticsService
,用于将 HTTP 请求统计数据插入到数据库中。代码如下:
package com.litongjava.tio.boot.admin.services;
import com.litongjava.db.activerecord.Db;
public class DbRequestStatisticsService {
public static final String sql = "INSERT INTO sys_http_request_statistics (id,channel_id,ip,user_id,method,uri,user_agent,header,body) values(?,?,?,?,?,?,?,?,?)";
public void saveDb(long id, long channel_id, String clientIp, Object userId, String method, String uri, String user_agent, String header, String body) {
Db.updateBySql(sql, id, channel_id, clientIp, userId, method, uri, user_agent, header, body);
}
}
2.2.3 实现 RequestStatisticsHandler
的 count
方法
通过继承 RequestStatisticsHandler
接口,实现 count
方法,在方法中提取请求中的关键信息,并使用异步任务调用业务层方法保存数据。代码如下:
package com.litongjava.tio.boot.admin.handler;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.tio.boot.admin.services.DbRequestStatisticsService;
import com.litongjava.tio.boot.http.handler.internal.RequestStatisticsHandler;
import com.litongjava.tio.http.common.HttpMethod;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.RequestLine;
import com.litongjava.tio.http.common.utils.HttpIpUtils;
import com.litongjava.tio.utils.snowflake.SnowflakeIdUtils;
import com.litongjava.tio.utils.thread.TioThreadUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DbRequestStatisticsHandler implements RequestStatisticsHandler {
DbRequestStatisticsService dbRequestStatisticsService = Aop.get(DbRequestStatisticsService.class);
@Override
public void count(HttpRequest request) {
RequestLine requestLine = request.getRequestLine();
HttpMethod method = requestLine.getMethod();
String uri = requestLine.getPathAndQuery();
long channel_id = Long.parseLong(request.getChannelContext().getId());
String userAgent = request.getUserAgent();
String authorization = request.getHeader("authorization");
String token = request.getHeader("token");
StringBuffer header = new StringBuffer();
if (authorization != null) {
header.append("authorization:").append(authorization).append("\n");
}
if (token != null) {
header.append("token:").append(token).append("\n");
}
String bodyString = request.getBodyString();
// 接口访问统计在拦截器之前运行,此时 还有解出id
Object userId = request.getAttribute("userId");
String clientIp = HttpIpUtils.getRealIp(request);
// 使用ExecutorService异步执行任务
TioThreadUtils.submit(() -> {
try {
long id = SnowflakeIdUtils.id();
dbRequestStatisticsService.saveDb(id, channel_id, clientIp, userId, method.toString(), uri, userAgent, header.toString(), bodyString);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
});
}
}
上述代码中:
- 从
HttpRequest
对象中提取了请求方法、URI、channel id、User-Agent、header 信息以及请求体数据。 - 使用
HttpIpUtils.getRealIp
获取客户端 IP。 - 利用
SnowflakeIdUtils
生成唯一 ID。 - 通过
TioThreadUtils.submit
异步提交任务,调用DbRequestStatisticsService.saveDb
方法保存数据,确保主线程不被阻塞。
2.3 配置 TioBootServer
最后,需要在 Tio-Boot Server 中配置 DbRequestStatisticsHandler
,使其在每次 HTTP 请求到来时自动调用 count
方法记录请求数据。配置代码如下:
package com.litongjava.admin.blog.config;
import com.litongjava.tio.boot.admin.handler.DbRequestStatisticsHandler;
import com.litongjava.tio.boot.server.TioBootServer;
public class TioBootServerConfig {
public void config() {
// 获取 TioBootServer 实例
TioBootServer server = TioBootServer.me();
// 将 DbRequestStatisticsHandler 设置到 TioBootServer
server.setRequestStatisticsHandler(new DbRequestStatisticsHandler());
}
}
通过以上配置,当 Tio-Boot Server 接收到 HTTP 请求时,系统会自动调用 DbRequestStatisticsHandler
的 count
方法,将请求相关数据异步保存到数据库中。
3. 数据统计效果
当系统接收到 HTTP 请求后,DbRequestStatisticsHandler
会自动记录请求信息并保存到数据库中。下面是一个数据记录的示例:
{
"id": 469717282465456128,
"channel_id": 1880014355744837633,
"ip": "0:0:0:0:0:0:0:1",
"user_id": null,
"method": "GET",
"uri": "/_next/static/chunks/webpack-dee5989a200fd06b.js",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"header": "",
"body": null,
"remark": null,
"creator": "",
"create_time": "2025-01-16T12:09:28.983977",
"updater": "",
"update_time": "2025-01-16T12:09:28.983977",
"deleted": 0,
"tenant_id": 0
}
该记录包含了请求的关键信息,可用于后续对接口访问情况的监控、分析和问题排查。
4. 常用 SQL 查询
为了对系统的接口访问数据进行进一步分析,可以使用如下 SQL 语句按秒统计并发请求数:
SELECT
date_trunc('second', update_time) AS SECOND,
COUNT(*) AS concurrent_count
FROM
sys_http_request_response_statistics
GROUP BY
date_trunc('second', update_time)
ORDER BY
SECOND DESC;
该语句利用 date_trunc
函数将 update_time
截断到秒,并统计每秒内的请求数量,然后按时间降序排序,方便我们观察系统在各个时间点的访问峰值。
5. 附录
- Tio-Boot Admin 内置支持
Tio-Boot Admin 已内置了DbRequestStatisticsHandler
的实现,用户可以直接使用或根据具体需求进行二次开发和扩展。