• 简易的慢SQL自定义告警实战经验(支持多数据源)


    背景

    对于慢SQL相信大家都不陌生了,一旦遇到后,相信大家会很快的提供出来对应的优化方法、索引优化建议工具使用等等,对于此我相信大家已经熟悉的不能再熟悉了,但是比较不尽人意的是:在此之前我们往往是花费了大量时间才发现造成系统出现问题的是慢SQL引起的,风险自然而然地也就慢慢升高了,基于此开发了一款简易的慢SQL自定义告警组件,为的就是提前预警,在影响扩大化之前进行快速止损,甚至是在一些新业务在上线前(测试、预发布)提前发现风险并规避

    原理简介

    通过mybatis拦截器进行sql语句执行过程的拦截,同步执行过程中计算sql执行的时间,当实际执行时间大于指定的配置阈值时发出告警信息并打印日志(日志中带有详细的调用链信息方便快速定位调用源头),同时将对应的sql放入异步队列disruptorQueue中,异步分析sql的执行计划,这里主要做3中场景预警

    1.出现不走索引的全表扫描场景

    2.没有走索引

    3.扫描数据量超过配置阈值

    关键代码

    1、Mybatis拦截器实现执行时间超时预警以及放入异步队列

    自定义一个类实现接口org.apache.ibatis.plugin.Interceptor,增加上官方提供的注解@Intercepts,这里主要介绍几个细节,具体关键代码如下

    ◦同步执行逻辑中根据sql的实际执行时间判断是否进行预警

    ◦预警日志中打印出具体的调用链堆栈信息

    ◦同一条sql在同一天最多只预警一次

    ◦具体sql的explain分析以及预警放在异步执行

    ◦开放子类可覆盖方法自行实现个性逻辑:比如mybatic-Plus的多数据源支持

    1. /**
    2. * 数据库操作性能拦截器,记录耗时
    3. * @Intercepts定义Signature数组,因此可以拦截多个,但是只能拦截类型为:
    4. * Executor
    5. * ParameterHandler
    6. * StatementHandler
    7. * ResultSetHandler
    8. */
    9. @Slf4j
    10. @Component
    11. @Intercepts(value = {
    12. @Signature(type=Executor.class,
    13. method="query",
    14. args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class,
    15. CacheKey.class,BoundSql.class}),
    16. @Signature(type=Executor.class,
    17. method="query",
    18. args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})})
    19. public class SqlInterceptor implements Interceptor {
    20. /**
    21. * 判定sql执行超时的时间标准,单位毫秒
    22. */
    23. private final int DEFAULST_TIMEOUT = 1000;
    24. /**
    25. * sql最大扫描行数
    26. */
    27. private final int DEFAULT_MAX_SCANROWS = 50000;
    28. /**
    29. * 每秒sql入队的最大限制
    30. */
    31. private final double SQL_PERMITS_PERSECOND = 10.0;
    32. /**
    33. * 同一个sql在redis中的过期时间
    34. */
    35. private final Long DEFAULT_REDIS_TIME = 24 * 60 *60L;
    36. /**
    37. * 分析sql阈值告警默认时间 单位:毫秒
    38. */
    39. private final Long DEFAULT_EXPLAIN_ALERM_TIME = 0L;
    40. /**
    41. * 缓存时间 单位:秒
    42. */
    43. private Long redisTime;
    44. /**
    45. * 分析sql阈值告警时间 单位:毫秒
    46. */
    47. private Long explainAlermTime;
    48. /**
    49. * 限流器
    50. */
    51. private RateLimiter rateLimiter = RateLimiter.create(SQL_PERMITS_PERSECOND,3*60, TimeUnit.SECONDS);
    52. private String appCode;
    53. private String appName;
    54. private String appStackBasePackage;
    55. private String dataSourceId;
    56. private String redisClientId;
    57. private Cluster redisClient;
    58. private DataSource dataSource;
    59. private String dbName;
    60. private JdbcTemplate jdbcTemplate;
    61. private Integer maxScanRows;
    62. private Integer sqlTimeout;
    63. private Boolean explainSwitch;
    64. protected final Map jdbcTemplateMap = new ConcurrentHashMap<>();
    65. /**
    66. * 初始jdbcTemplateMap,可由子类实现
    67. */
    68. public void initjdbcTemplateMap(){
    69. }
    70. /**
    71. * 实现拦截的地方
    72. * @param invocation
    73. * @return
    74. * @throws Throwable
    75. */
    76. @Override
    77. public Object intercept(Invocation invocation) throws Throwable {
    78. Object target = invocation.getTarget();
    79. Method method = invocation.getMethod();
    80. Object result = null;
    81. if (target instanceof Executor) {
    82. long start = System.currentTimeMillis();
    83. /**执行方法*/
    84. try{
    85. result = invocation.proceed();
    86. long end = System.currentTimeMillis();
    87. long executeTime = end-start;
    88. log.debug("sql性能监控-execute-target:{}毫秒",executeTime);
    89. this.doTimeOutSql(invocation, executeTime);
    90. }catch (Throwable var1) {
    91. long end = System.currentTimeMillis();
    92. long executeTime = end-start;
    93. String logId = UUID.randomUUID().toString();
    94. this.businessAlarmError(invocation, executeTime, logId);
    95. throw new RuntimeException("sql性能监控-调用原始方法出错:logId=" + logId + ",方法:" + method, var1);
    96. }
    97. }
    98. return result;
    99. }
    100. /**
    101. * Plugin.wrap生成拦截代理对象
    102. * @param target
    103. * @return
    104. */
    105. @Override
    106. public Object plugin(Object target) {
    107. return Plugin.wrap(target, this);
    108. }
    109. @Override
    110. public void setProperties(Properties properties) {
    111. }
    112. /**
    113. * 处理超时SQL
    114. * 1.超时自定义报警
    115. * 2.超时入redis慢SQL队列
    116. * @param invocation
    117. */
    118. private void doTimeOutSql(Invocation invocation,long executeTime){
    119. long startTime = System.currentTimeMillis();
    120. String preSql = "";
    121. try{
    122. SqlEventInfo sqlEventInfo = this.getRealSqlInfo(invocation, executeTime);
    123. preSql = sqlEventInfo.getPreSql();
    124. //自定义告警
    125. List pivotalStackTraces = getPivotalStackTraces();
    126. sqlEventInfo.setPivotalStackTraces(pivotalStackTraces);
    127. if(executeTime - getSqlTimeout() > 0){
    128. StringBuffer alermKeyBuf = new StringBuffer();
    129. alermKeyBuf.append(this.appCode).append(".").append(SqlAlermEnum.SQL_ALERM_KEY.getDesc());
  • 相关阅读:
    【学习笔记】[ARC156E] Non-Adjacent Matching
    流量封顶时代,容联七陌智能客服构筑企业“第二增长曲线”
    python——线程总结
    AI DevOps | ChatGPT 与研发效能、效率提升(中)
    深入理解Kubernetes Pod调试
    Allegro基本规则设置指导书
    CC2541蓝牙低功耗芯片中文资料提供
    实现切换图片透明度轮播效果
    工作之余,学习Go,快速入门,第一节:变量
    青少年python系列 45.文件操作1
  • 原文地址:https://blog.csdn.net/long290046464/article/details/133859433