前言
随着业务体量上去势必会遇到数据库的性能问题,从合理的 sql 语句、字段索引、分库分表、读写分离等,在加上缓存技术,基本上就可以解决几乎所有业务,再差提升硬件即可,而 Go 自身就已实现连接池, 以解决连接带来的时延从而提升性能(空间换时间),于是想了解提升下。
MYSQL 参数说明
连接池 : 通过在内存中维护一组数据库连接,以便在需要时快速分配和释放连接。这样可以避免频繁地打开和关闭数据库连接,从而提高系统的效率和性能。
故它是以长连接的形式存在内存中,那势必存在 忙碌 / 空闲 两种状态,在空闲状态下需要考虑超时的情况,除去本身客户端的超时限制,那服务端也会存在超时限制;同样连接池是池,所有会有很多连接,在配置对应数量同样会受制于服务端的限制。
所以我们在配置连接池前,必须了解相关 mysql 的相关参数配置以及作用,否则会弄巧成拙。
字段 | 简述 | 补充说明 |
---|---|---|
max_connection | 同时处理最大连接数 | 这个参数的默认值通常是 100,但可以根据服务器的配置和性能需求进行调整。如果服务器达到了 max_connections 的限制,新的连接请求将被拒绝,直到当前的连接数降到小于 max_connections 为止。因此,如果你的服务器经常出现“Too many connections”错误,你可以通过增加 max_connections 的值来解决这个问题。但是,也要记住,增加 max_connections 可能会增加服务器的资源消耗,因此要根据实际情况进行调整。 |
extra_max_connections | 超过最大连接数后最多接受连接数 | 它指定了服务器可以同时处理的最大连接数。如果服务器繁忙,并且已经达到了 max_connections 的限制,那么服务器将不再接受新的连接请求,直到有空闲的连接出现。extra_max_connections 参数允许服务器在特殊情况下,接受更多的连接请求,以便在紧急情况下提供更多的灵活性。例如,如果你将 extra_max_connections 设置为 10,那么在 max_connections 达到限制之后,服务器仍然可以接受 10 个额外的连接请求。需要注意的是,使用 extra_max_connections 这个参数可能会影响服务器的性能,因此应该在必要的情况下使用。 |
wait_timeout | 非交互闲置后最大等待时间 | 如果在这段时间内连接没有发送任何请求,那么 MySQL 就会断开这个连接。这个参数的默认值通常是 8 小时,但是可以通过修改配置文件来更改它的值。这个参数的目的是为了避免连接泄漏,也就是说,如果一个连接长期不活动,那么 MySQL 就会断开它,以便节约资源。当然,如果你的应用需要长时间的连接,那么你可以适当地调大这个参数的值 |
wait_timeout 描述中 非交互 是指代码这类运行,而对应交互式则是 interactive_timeout 控制, 指 navicat 这类连接数据库的客户端。
这里提是因为当我们修改对应值若只是修改前者,在用客户端打印系统参数时会发现没有变动(其实已生效),如果强迫症可以都进行修改。
相关语句
-- 查询语句
SHOW VARIABLES LIKE '%max_connections';
SHOW VARIABLES LIKE 'wait_timeout%' ;
SHOW VARIABLES LIKE '%interactive_timeout%';
-- 设置全局变量
SET GLOBAL max_connections = 100;
SET GLOBAL wait_timeout = 10;
SET GLOBAL interactive_timeout = 10;
Go 连接池说明
连接池参数配置
=// 全局 db 对象
var DB *sql.DB
// ConnectionPool 连接池的学习
func ConnectionPool() {
// 连接 db
db, err := sql.Open("mysql", "root:123456@/db?charset=utf8")
if err != nil {
panic(err)
}
//defer db.Close()
db.SetMaxOpenConns(100) // 设置连接数总数, 需要根据实际业务来测算, 应小于 mysql.max_connection (应该远远小于), 后续根据指标进行调整
db.SetMaxIdleConns(50) // 设置最大空闲连接数, 该数值应该小于等于 SetMaxOpenConns 设置的值
db.SetConnMaxLifetime(0) // 设置连接最大生命周期, 默认为 0(不限制), 我不建议设置该值, 只有当 mysql 服务器出现问题, 会导致连接报错, 恢复后可以自动恢复正常, 而我们配置了时间也不能卡住出问题的时间, 配置小还不如使用 SetConnMaxIdleTime 来解决
db.SetConnMaxIdleTime(4 * time.Second) // 设置空闲状态最大生命周期, 该值应小于 mysql.wait_timeout 的值, 以避免被服务端断开连接, 产生报错影响业务, 一般可以配置 1天。
// 赋值 db 对象
DB = db
}
相关服务端的连接超时报错示例
database/sql 中关于连接池的指标和参数说明
关于 DBStats 的数据指标
字段 | 说明 |
---|---|
MaxOpenConnections | 最大连接数, MaxOpenConnections,由 SetMaxOpenConns 设置 |
OpenConnections | 活跃的总连接数, 该值不会超过 MaxOpenConnections 值 |
InUse | 正在使用的连接数,若执行 close 或 scan(自带 close)就会归还连接,否则会一直占用直到连接超时 |
Idle | 当前空闲的连接数 |
WaitCount | 当前等待数据库连接的总数 |
WaitDuration | 等待数据库连接的时间 |
MaxIdleClosed | 空闲连接数超限被关闭总数,受 SetMaxIdleConns 限制 |
MaxIdleTimeClosed | 因空闲连接超时被关闭总数,受 SetMaxIdleTime 限制 |
MaxLifetimeClosed | 因连接超时被关闭总数,受 SetMaxLiftTime 限制 |
调用方式
func getDbStats(w http.ResponseWriter, r *http.Request) {
info := fmt.Sprintf("最大连接数:%d, 当前总连接数;%d, 已使用: %d, 空闲数量:%d \n", DB.Stats().MaxOpenConnections, DB.Stats().OpenConnections, DB.Stats().InUse, DB.Stats().Idle)
info2 := fmt.Sprintf("数量指标 :) \n等待连接数量;%d, 等待创建新连接时长(秒): %f, 空闲超限关闭数量:%d, 空闲超时关闭数量:%d, 连接超时关闭数量:%d \n",
DB.Stats().WaitCount,
DB.Stats().WaitDuration.Seconds(),
DB.Stats().MaxIdleClosed,
DB.Stats().MaxIdleTimeClosed,
DB.Stats().MaxLifetimeClosed,
)
_, _ = fmt.Fprintln(w, info+info2)
}
返回示例
代码实现
mdb.go
/*
Package study_package
*/
package study_package
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"time"
)
// 全局 db 对象
var DB *sql.DB
// ConnectionPool 连接池的学习
func ConnectionPool() {
fmt.Println("mysql 连接池实践 -- ")
// 连接 db
db, err := sql.Open("mysql", "root:12345678@/spider?charset=utf8")
if err != nil {
panic(err)
}
//defer db.Close()
db.SetMaxOpenConns(100) // 设置连接数总数
db.SetMaxIdleConns(50) // 设置最大空闲连接数, 该数值应该小于等于 SetMaxOpenConns 设置的值
db.SetConnMaxLifetime(0) // 设置连接最大生命周期, 默认为 0(不限制), 我不建议设置该值, 只有当 mysql 服务器出现问题, 会导致连接报错, 恢复后可以自动恢复正常, 而我们配置了时间也不能卡住出问题的时间, 配置小还不如使用 SetConnMaxIdleTime 来解决
db.SetConnMaxIdleTime(86400 * time.Second) // 设置空闲状态最大生命周期, 该值应小于 mysql.wait_timeout 的值, 以避免被服务端断开连接, 产生报错影响业务。
// 创建连接池
DB = db
}
web.go
// Copyright 2023 The wangkai. ALL rights reserved.
package study_package
import (
"fmt"
"net/http"
)
// Pool 学习搭建 数据库连接池相关概念
func Pool(w http.ResponseWriter, r *http.Request) {
var count int
// 对应数据库查询
row := DB.QueryRow("SELECT COUNT(1) count FROM `xz_house`")
// 遍历数据
err := row.Scan(&count)
if err != nil {
fmt.Println(err)
}
_, _ = fmt.Fprintln(w, count)
}
// getDbStats 获取 DB 状态和指标
func getDbStats(w http.ResponseWriter, r *http.Request) {
info := fmt.Sprintf("最大连接数:%d, 当前总连接数;%d, 已使用: %d, 空闲数量:%d \n", DB.Stats().MaxOpenConnections, DB.Stats().OpenConnections, DB.Stats().InUse, DB.Stats().Idle)
info2 := fmt.Sprintf("数量指标 :) \n等待连接数量;%d, 等待创建新连接时长(秒): %f, 空闲超限关闭数量:%d, 空闲超时关闭数量:%d, 连接超时关闭数量:%d \n",
DB.Stats().WaitCount,
DB.Stats().WaitDuration.Seconds(),
DB.Stats().MaxIdleClosed,
DB.Stats().MaxIdleTimeClosed,
DB.Stats().MaxLifetimeClosed,
)
_, _ = fmt.Fprintln(w, info+info2)
}
web_test.go
package study_package
import (
"net/http"
"testing"
)
func TestPool(t *testing.T) {
type args struct {
w http.ResponseWriter
r *http.Request
}
tests := []struct {
name string
args args
}{
{"base", args{
w: nil,
r: nil,
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 创建连接池
ConnectionPool()
http.HandleFunc("/pool", Pool)
http.HandleFunc("/getDbStats", getDbStats)
// 创建 Listen Socket, 监听 9090 端口
err := http.ListenAndServe(":9090", nil)
if err == nil {
log.Fatal(" ListenAndServe:", err)
}
})
}
}
总结
掌握 Go 本身的功能相关参数配置以及了解 mysql 的配置,才能更加熟练的使用 Go 的连接池的功能,从而保障服务的健壮性。
当然不能仅仅关注代码层次,还需要了解硬件的指标,例如内存、I/O读写、CPU 等,不过那个时候相比是有专业的运维来介入支持,毕竟我们日常的业务和个人业务如前言所说,都能妥善解决。