mysql连接池的坑

数据库连接池的配置不仅会影响client的连接,也会影响server端的性能。

建连的坑

线上环境的p95的数据的性能很差,通过trace链路分析出『均是由于数据库的sql执行的RT较长导致』,这样就比较奇怪了,同样的sql为什么会这么不稳定;
尤其在服务发布或流量徒增的过程中,问题现象更加明显;通过DBA的分析得出结论『数据库在有突增的新建连接时会影响sql的执行』,。

针对线上集群,单个数据库端口连接数非常多,每次业务有新的发布会创建比较多的连接,首先会导致『新建连接比较慢』,其次会导致『新建连接过程中会影响已有链接的RT』

建连慢

建连慢的主要原因在于tcp的建连队列大小,如果设置的队列过小会导致丢弃部分建连的请求,导致服务不可用;
https://stackoverflow.com/questions/36594400/what-is-backlog-in-tcp-connections

调整内核配置参数,解决单端口连接过多的问题:

  • tcp_max_sync_backlog 2048->8192
  • somaxconn 128->2048

建连RT影响

建连登录认证也是一个sql,正常都会进入sql队列,与正常的sql在一个队列中,所以会影响正常的sql执行;
https://dev.mysql.com/doc/refman/8.0/en/thread-pool-tuning.html

  • thread_pool_stall_limit:线程池中无可用线程时设置建新线程的等待时间,单位为毫秒,默认是500;
  • thread_pool_idle_timeout:设置空闲线程销毁前的等待时间,单位为秒,默认是60;

通过减小timmer检测间隔并提高连接池的超时时间,调整线程的创建和回收策略来优化RT的问题:

  • thread_pool_stall_limit 500ms->10ms
  • thread_pool_idle_timeout 60s->600s

由于以上配置会由于减少线程的回收和增加线程的创建频率均会对占用一定的服务器资源和性能,因此在评估这些参数的时候可以逐渐增加或减少该值,并在当前数据库负载的情况下找到一个合理的值;

建接数量

mysql的默认建连数据是xxx,实际可以按照数据库的性能做一定的调整;
数据库的连接会占用服务器的cpu和内存,所以谨慎的限制数据库client和server的连接数是非常重要的;
根据经验,32核64G内存的数据库可以配置到万级别的数据库连接数量,同时会有上千台的服务器与单分片的数据库保持着连接没,例如,1000台服务器,没台服务的连接池配置为50,则整体5W的连接数;

数据库连接数较多情况下,可能会导致那个数据库夯住:

  • 故障切换时由于大量的请求同时打到DB,连接数会有翻倍的可能,导致连接数超过数据库上限;
  • 故障切换时会导致业务访问DB的重试,可能导致连接数超过数据库上限;
  • 当服务已有连接数过多的时候,业务如果同时发布但并未销毁原有容器,则可能创建最多翻倍的连接,并且会申请最多翻倍的数据库临时内存;

mysql线程池

参考文档:https://dbaplus.cn/news-11-1989-1.html

mysql线程池采用分组管理,连接按照分组算法分配到不同的分组,此后相同请求会在次分组内调度线程执行;此外线程池使用Timer线程定期检查分组运行的情况,并为阻塞的group分配线程。

线程池的组成部分

group分组

  • 工作线程:用于处理请求和任务;
  • 监听线程:用于接收请求并加入任务队列;
  • 任务队列:用于存放待执行的IO任务,分为高优先级队列和低优先级队列(高优先级队列的任务会优先被处理);

timer线程

timer线程会定期扫描各个group检查是否被阻塞,当发现group内有任务堆积,就会给group创建新的worker进行处理;

线程池的工作流程

工作流程

  • 客户端优先会向server发起请求,根据threadid%thread_pool_size确定路由的分组;
  • 监听线程在收到请求后,会按照队列情况分别处理:
    • 队列无请求,则转换为工作线程立即处理;
    • 队列有请求,则将任务加入任务队列中,排队等待被处理;
  • 工作线程从队列中获取等待被处理的任务,依次进行处理,如果队列为空,则会在等待配置的超时时间后销毁;
  • timmer线程会定期检查group是否阻塞,如果存在任务阻塞会创建新的工作线程;

配置参数

thread_handling

  • one-thread-per-connection(默认,一个连接一个线程)
  • pool-of-threads(线程池)

thread_pool_size

  • 线程池的分组数量,默认为cpu数量;

thread_pool_oversubscribe

  • 分组内的最大线程数,监听不再其内;

thread_pool_high_prio_mode

  • transactions:启动事务的语句放到高优先级队列;
  • statements:所有的语句都会放到高优先级队列,不会使用优先级队列;
  • none:不使用高优先级队列;

thread_pool_high_prio_tickets

  • 控制每个连接最多被放入高优先级队列中的次数,默认为4294967295;
  • 仅在transactions模式下生效,如果超过阈值,则直接放入低优先级队列;

thread_pool_idle_timeout

  • 工作线程最大空闲时间,默认为60秒,超过限制后会退出;

thread_pool_max_threads

  • 限制线程池最大的线程数,超过该限制后将无法再创建更多的线程,默认为100000;

thread_pool_stall_limit

  • timer线程的检测group是否异常的时间间隔,默认为500ms;