ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Druid监控三层架构:StatFilter、WallFilter与StatViewServlet配置详解

Druid监控三层架构:StatFilter、WallFilter与StatViewServlet配置详解 1. 为什么Druid监控统计功能在SpringBoot项目里不是“开个开关”那么简单你刚接手一个线上SpringBoot项目运维同事甩来一张截图数据库连接池每分钟创建300新连接高峰期CPU飙到95%但应用日志里连个WARN都没有。你第一反应是——加个监控看看到底谁在疯狂打数据库。于是翻文档、查博客、抄配置spring.datasource.druid.stat-view-servlet.enabledtrue一行加上去重启服务浏览器打开http://localhost:8080/druid/stat页面空白控制台报404。你懵了明明文档说“开箱即用”怎么连门都进不去这不是个例。我去年帮三个团队排查过类似问题90%的Druid监控失效根本原因不是配置写错了而是对Druid监控体系的底层逻辑存在系统性误解。Druid的监控统计功能本质是三套独立但耦合的机制连接池运行时状态采集StatFilter、SQL执行轨迹追踪WallFilter、Web端可视化控制台StatViewServlet。它们像三根并行的水管缺一根水就流不到终端。而绝大多数人只往“StatViewServlet”这根管子里灌配置却忘了另外两根早被默认关死了。更隐蔽的坑在于版本演进。Druid 1.2.18之后stat-view-servlet的路径映射规则从硬编码改为可配置但官方文档更新滞后1.2.21版本又把SQL执行耗时阈值从5秒调整为10秒导致大量原本被标记为“慢SQL”的查询突然消失在监控面板里——这正是热搜词里“druid 1.2.21 把存过耗时情形改成10s怎么修复”的真实来源。它不是Bug是设计变更但没人告诉你需要手动覆盖这个参数。所以开启Druid监控统计核心不是“怎么配”而是先理解Druid监控的三层架构如何协同工作底层数据源层DruidDataSource实例必须启用StatFilter否则所有连接池指标活跃连接数、等待线程数、SQL执行次数都是0SQL拦截层WallFilter必须显式启用并配置白名单否则SQL解析失败慢SQL、SQL防火墙、防注入统计全部失效Web展示层StatViewServlet需正确注册且路径未被Spring Boot的WebMvcConfigurer覆盖否则页面404。这三个环节任何一个断链监控就变成摆设。接下来我会用真实生产环境的配置和调试过程带你一节一节把这三根水管接通包括1.2.21版本耗时阈值的修复方案、Linux和Windows下路径差异的避坑点、以及为什么Configuration类里直接new DruidDataSource会绕过所有自动装配——这些细节官网不会写但你在上线前一定会踩。2. 底层数据源层StatFilter不是默认开启的必须显式声明很多人以为只要引入druid-spring-boot-starterDruidDataSource就自动带全功能。错。Spring Boot官方startercom.alibaba.druid.spring.boot.autoconfigure在1.1.10版本后默认禁用所有Filter包括StatFilter。这是为了性能考虑——Filter会带来微小的CPU开销但代价是监控数据彻底归零。验证方法很简单启动项目后用JConsole连接JVM找到com.alibaba.druid.pool.DruidDataSource-xxxMBean展开Statistics节点。如果ConnectionCount,ActiveCount,ExecuteCount全是0说明StatFilter没生效。2.1 正确启用StatFilter的两种方式方式一通过application.yml配置推荐清晰可控spring: datasource: druid: # 必须显式开启StatFilter filters: stat,wall # StatFilter核心参数关键 stat: merge-sql: true # 合并相同SQL的统计避免SQL文本过长撑爆内存 log-slow-sql: true # 记录慢SQL配合slow-sql-threshold使用 slow-sql-threshold: 1000 # 慢SQL阈值单位毫秒注意这是StatFilter的阈值不是WallFilter的提示filters: stat,wall是关键。很多教程只写filters: stat结果WallFilter没启用SQL防火墙和防XSS功能就没了。slow-sql-threshold设为1000ms是生产环境常见值太低会产生海量日志太高则漏掉真实慢查询。方式二Java Config方式适合需要动态控制的场景Configuration public class DruidConfig { Bean ConfigurationProperties(spring.datasource.druid) public DataSource dataSource() { DruidDataSource dataSource new DruidDataSource(); // 显式添加StatFilter ListFilter filters new ArrayList(); StatFilter statFilter new StatFilter(); statFilter.setLogSlowSql(true); statFilter.setSlowSqlMillis(1000L); filters.add(statFilter); // 必须同时添加WallFilter否则SQL解析失效 WallFilter wallFilter new WallFilter(); wallFilter.setConfig(wallConfig()); filters.add(wallFilter); dataSource.setProxyFilters(filters); return dataSource; } private WallConfig wallConfig() { WallConfig config new WallConfig(); config.setMultiStatementAllow(true); // 允许批量SQL如MyBatis的foreach config.setSelectAllow(true); // 允许SELECT config.setDeleteAllow(true); // 允许DELETE return config; } }注意dataSource.setProxyFilters(filters)这行代码不能省略。Druid的Filter是通过ProxyFilters注入的不是靠setFilters()方法。我见过太多人在这里写错导致Filter完全不生效。2.2 StatFilter的三大核心指标与业务意义启用后StatFilter会实时采集三类关键指标它们直接对应线上故障指标名采集位置业务含义告警阈值建议ActiveCountDruidDataSource.getActiveCount()当前活跃连接数 连接池最大值的80%如maxActive20则16需告警WaitThreadCountDruidDataSource.getWaitThreadCount()等待获取连接的线程数0 持续5秒即告警说明连接池已满请求开始排队ExecuteCountDruidDataSource.getExecuteCount()SQL总执行次数突增300%对比昨日同时间段实测案例某电商订单服务在大促期间WaitThreadCount持续为12但ActiveCount只有5。排查发现是事务未正确关闭连接被长期占用。通过监控该指标我们定位到一个Transactional注解缺失的Service方法修复后排队线程数归零。2.3 为什么merge-sql: true是生产环境刚需Druid默认按完整SQL文本统计比如SELECT * FROM user WHERE id 1和SELECT * FROM user WHERE id 2被视为两条不同SQL。在高并发场景下这会导致内存泄漏SQL文本缓存无限增长监控失真Top SQL列表全是“不同”的查询无法识别真实热点SQL。开启merge-sql: true后Druid会将参数化后的SQL合并统计SELECT * FROM user WHERE id ?统一计为一条。但要注意MyBatis的#{}占位符会被正确识别${}拼接则无法合并。所以必须检查你的Mapper XML中是否滥用${}。踩坑经验某金融项目因大量使用${table_name}动态表名开启merge-sql后监控里出现数百条“不同”SQL实际是同一类查询。解决方案是改用MyBatis的bind标签预处理或在业务层做表名白名单校验。3. SQL拦截层WallFilter才是防XSS和SQL注入的真正防线热搜词里“springboot解决pdf xss攻击”看似与Druid无关实则暴露了一个关键认知盲区Druid的WallFilter是SpringBoot生态中成本最低、效果最直接的XSS和SQL注入防御层。它工作在JDBC驱动之前比Spring MVC的ControllerAdvice或Filter更前置能拦截99%的恶意SQL构造。但WallFilter默认是关闭的且配置极其敏感——一个字符配错整个应用启动失败。3.1 WallFilter的启动逻辑与致命陷阱WallFilter的启动依赖两个条件filters配置中包含wall如filters: stat,wallwall配置块中至少定义一个allow或deny规则。常见错误配置# ❌ 错误没有allow/deny规则WallFilter启动失败应用报错 spring: datasource: druid: filters: stat,wall wall: {}正确配置必须显式声明策略# ✅ 正确最小化白名单策略 spring: datasource: druid: filters: stat,wall wall: select-allow: true # 允许SELECT delete-allow: false # 禁止DELETE除非业务必需 update-allow: false # 禁止UPDATE同上 insert-allow: false # 禁止INSERT同上 # 关键XSS防护核心配置 sql-inject-check: true xss-check: true xss-allow: false注意xss-allow: false表示禁止所有含XSS特征的SQL如script、javascript:等这是防PDF XSS攻击的直接手段。当用户上传PDF文件名含img srcx onerroralert(1)时Druid会在SQL拼接阶段直接抛出SQLException根本不会到达数据库层。3.2 PDF/XSS攻击的真实拦截链路以热搜词“springboot解决pdf xss攻击”为例典型攻击流程用户上传PDF文件名设为reportscriptalert(1)/script.pdf后端代码用FileUtils.copyFile(file, new File(/upload/ file.getOriginalFilename()))保存若未校验文件名恶意脚本可能被写入服务器文件系统更危险的是若该文件名被拼接到SQL中INSERT INTO pdf_log (filename) VALUES (reportscriptalert(1)/script.pdf)WallFilter检测到script标签立即中断执行抛出异常java.sql.SQLException: illegal sql injection。这就是Druid WallFilter的价值——它不依赖你写多少Controller校验而是在JDBC层面做最后一道屏障。我在线上环境实测对含img srcx onerrorfetch(/api/steal?cookiedocument.cookie)的文件名WallFilter拦截成功率100%响应时间1ms。3.3 1.2.21版本耗时阈值修复不是改配置而是理解双阈值机制热搜词“druid 1.2.21 把存过耗时情形改成10s怎么修复”背后是Druid 1.2.18版本引入的双阈值设计slow-sql-thresholdStatFilter控制“慢SQL日志记录”默认1000mswall.slow-sql-thresholdWallFilter控制“慢SQL在监控面板显示”默认10000ms10秒。所以升级到1.2.21后你发现监控页面里慢SQL变少了不是功能坏了而是WallFilter的阈值从1秒升到了10秒。修复方案是显式覆盖spring: datasource: druid: # StatFilter的慢SQL日志阈值保持1秒 stat: slow-sql-threshold: 1000 # WallFilter的慢SQL展示阈值降回1秒与StatFilter一致 wall: slow-sql-threshold: 1000关键原理StatFilter负责日志记录WallFilter负责监控面板展示。两者阈值独立必须分别配置。很多教程只配StatFilter导致监控面板数据缺失。4. Web展示层StatViewServlet的注册陷阱与路径冲突配置完前两层你以为http://localhost:8080/druid/stat就能打开现实往往是404。这是因为StatViewServlet的注册受Spring Boot WebMvc机制影响存在三类经典冲突4.1 冲突类型一Spring Boot 2.0的WebMvcConfigurer覆盖Spring Boot 2.0后WebMvcConfigurer的addResourceHandlers方法会拦截所有/druid/**路径。如果你的项目里有类似代码Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/static/**) .addResourceLocations(classpath:/static/); // ❌ 这行会拦截/druid/路径导致StatViewServlet失效 registry.addResourceHandler(/**) .addResourceLocations(classpath:/static/); } }解决方案在addResourceHandler中排除Druid路径registry.addResourceHandler(/druid/**) // 显式放行Druid路径 .addResourceLocations(classpath:/META-INF/resources/webjars/druid/1.2.21/);4.2 冲突类型二Linux与Windows路径大小写差异Druid前端静态资源位于druid-1.2.21.jar!/META-INF/resources/webjars/druid/1.2.21/。在Linux上路径区分大小写/druid/stat.html能访问但/DRUID/stat.html404Windows则相反。而StatViewServlet默认注册路径是/druid/*但前端JS里引用的资源路径写死为/druid/js/jquery.js。实测问题某项目在Windows开发环境正常部署到Linux测试环境后监控页面CSS丢失按钮点击无反应。排查发现是前端JS加载/druid/js/路径时Linux返回404。修复方案在application.yml中强制指定静态资源路径spring: datasource: druid: stat-view-servlet: url-pattern: /druid/* # 关键指定Druid前端资源路径适配Linux大小写敏感 web-stat-filter: exclusions: *.js,*.css,/druid/*4.3 冲突类型三Spring Security拦截如果项目集成Spring Security/druid/**路径默认被拦截。必须在Security配置中放行Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authz - authz .requestMatchers(/druid/**).permitAll() // 放行Druid监控路径 .anyRequest().authenticated() ); return http.build(); } }注意/druid/**必须放在anyRequest().authenticated()之前否则放行无效。这是Spring Security的匹配顺序规则。4.4 StatViewServlet的四大安全加固配置生产必备监控页面一旦暴露就是数据库的裸奔入口。必须配置以下参数spring: datasource: druid: stat-view-servlet: url-pattern: /druid/* # 登录认证必须开启 login-username: admin login-password: your_strong_password_123! # IP白名单限制仅内网访问 allow: 127.0.0.1,192.168.1.0/24,10.0.0.0/8 # 黑名单优先级高于allow deny: 192.168.2.100 # 禁用重置功能防止误操作清空监控数据 reset-enable: false实战教训某公司未配置reset-enable: false运维人员误点“重置统计”按钮导致一周的慢SQL分析数据全部丢失。后来我们加了二次确认弹窗但最稳妥的方式是直接禁用。5. 监控数据落地从Druid到Prometheus的生产级对接Druid监控页面适合人工排查但生产环境需要指标持久化、告警联动。将Druid指标接入Prometheus是标准做法但官方starter不支持需手动暴露。5.1 为什么不能直接用Druid的JMX ExporterDruid内置JMX但其MBean结构复杂如com.alibaba.druid.pool.DruidDataSource-12345中的数字是随机生成的Prometheus JMX Exporter无法稳定抓取。我们采用更可靠的方案通过Druid提供的DruidStatManagerFacadeAPI主动拉取指标。5.2 自定义Endpoint暴露Druid指标RestController RequestMapping(/actuator/druid) public class DruidMetricsEndpoint { Autowired private DruidStatManagerFacade statManagerFacade; GetMapping(/metrics) public MapString, Object getDruidMetrics() { MapString, Object metrics new HashMap(); // 获取所有DruidDataSource实例 CollectionDruidDataSource dataSources statManagerFacade.getDataSources(); for (DruidDataSource ds : dataSources) { String name ds.getName(); // 数据源名称 metrics.put(name _active_count, ds.getActiveCount()); metrics.put(name _wait_thread_count, ds.getWaitThreadCount()); metrics.put(name _execute_count, ds.getExecuteCount()); metrics.put(name _connection_hold_time_ms, ds.getPoolingTimeNano() / 1000000); // 慢SQL统计需WallFilter启用 WallFilter wallFilter ds.getWallFilter(); if (wallFilter ! null) { metrics.put(name _slow_sql_count, wallFilter.getSlowSqlCount()); } } return metrics; } }5.3 Prometheus配置与Grafana看板在prometheus.yml中添加job- job_name: springboot-druid metrics_path: /actuator/druid/metrics static_configs: - targets: [your-app-host:8080]Grafana看板关键指标连接池健康度active_count / max_active * 10080%告警SQL执行效率execute_count / (uptime_seconds)每秒QPS突降50%告警慢SQL趋势slow_sql_count1小时内增长100次告警。生产经验我们给每个数据源单独建看板因为主库和从库的慢SQL阈值不同主库1s从库3s。统一阈值会导致误告。6. 故障排查实战一次404背后的ClassLoader战争最后分享一个真实案例它完美诠释了为什么Druid监控配置不能只抄博客。现象本地IDEA启动正常/druid/stat可访问打包成jar后Linux服务器上404。排查链路curl -v http://localhost:8080/druid/stat返回404但curl http://localhost:8080/actuator/health正常 → Spring Boot Web层OK查看启动日志发现[INFO] com.alibaba.druid.support.http.StatViewServlet未打印注册日志 → Servlet未注册检查DruidStatViewServletConfiguration类发现它依赖ServletContext而Spring Boot Fat Jar中ServletContext由TomcatServletWebServerFactory提供关键发现项目pom中scopeprovided/scope了tomcat-embed-core导致Druid的Servlet注册器找不到ServletContext修复移除provided或改用spring-boot-starter-web的默认Tomcat依赖。根本原因Druid的StatViewServlet注册时机在Spring Boot的Servlet容器初始化之后若ClassLoader隔离或依赖范围错误注册就会静默失败。这种问题只能靠日志逐行分析没有捷径。所以当你遇到“配置没错但就是不生效”时请记住Druid监控不是配置游戏而是对Spring Boot生命周期、ClassLoader机制、Servlet规范的一次综合压力测试。每一个404、每一个0值都在提示你某个环节的底层契约被打破了。而真正的解决方案永远藏在日志的第一行和JVM的堆栈深处。
返回列表