面试题答案
一键面试1. 连接池参数配置优化
- 最大连接数(MaxIdleConns 和 MaxOpenConns):
MaxIdleConns
决定了连接池中可以保持的最大空闲连接数。如果设置过小,在高并发时可能需要频繁创建新连接,增加开销;如果设置过大,会浪费系统资源。一般建议根据服务器的资源(如内存、CPU)以及预估的并发量来设置,例如对于一台中等配置的服务器,初始可以设置为10 - 50。MaxOpenConns
定义了连接池可以打开的最大连接数。这应根据数据库服务器的负载能力和应用程序的并发需求来调整,避免过多连接耗尽数据库资源。通常可以设置为几百,具体数值需要通过性能测试来确定。
- 连接超时(ConnectTimeout):设置连接数据库的超时时间,防止长时间等待无效连接。例如设置为 5 秒,以确保在网络不稳定等情况下,快速失败并尝试重新连接。
- 空闲连接超时(IdleTimeout):设置连接在连接池中保持空闲的最长时间。超过这个时间,空闲连接将被关闭,以释放资源。比如设置为 30 秒,避免长时间占用资源。
2. 故障自动切换机制
- 使用心跳检测:定期(如每隔 5 - 10 秒)向数据库发送简单的查询(如
SELECT 1
),以检查连接是否正常。如果检测到连接不可用,标记该连接为故障连接,并从连接池中移除。 - 重新连接策略:当检测到连接故障时,立即尝试重新连接数据库。可以设置重试次数(如 3 - 5 次)和重试间隔(如每次间隔 1 - 2 秒),如果多次重试仍失败,通知系统管理员。
- 备用数据库切换:对于关键业务,配置备用数据库。当主数据库出现故障且多次重试连接失败后,自动切换到备用数据库,并记录切换日志。在主数据库恢复后,提供手动或自动切回主库的机制。
3. 避免连接泄露
- 连接使用规范:在代码中确保每个获取连接的操作都有对应的释放连接操作。使用
defer
关键字在函数结束时释放连接,如下所示:
func queryDB() {
conn, err := dbPool.Get()
if err != nil {
// 处理获取连接错误
return
}
defer conn.Close()
// 执行数据库操作
_, err = conn.Exec("SELECT * FROM products")
if err != nil {
// 处理数据库操作错误
return
}
}
- 连接监控:在连接池实现中添加监控机制,记录连接的获取和释放时间,以及当前连接池中的连接使用情况。通过监控可以及时发现异常长时间未释放的连接,并定位代码中的问题。
4. 关键部分代码示例
以下以 MySQL 连接池为例:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"time"
)
func main() {
// 初始化数据库连接池
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/yourdatabase")
if err != nil {
panic(err.Error())
}
defer db.Close()
// 配置连接池参数
db.SetMaxIdleConns(10)
db.SetMaxOpenConns(100)
db.SetConnMaxLifetime(time.Minute * 5)
// 测试连接池
err = db.Ping()
if err != nil {
panic(err.Error())
}
fmt.Println("Connected to database!")
// 模拟高并发操作
var numRoutines = 100
for i := 0; i < numRoutines; i++ {
go func() {
conn, err := db.Conn(context.Background())
if err != nil {
fmt.Println("Failed to get connection:", err)
return
}
defer conn.Close()
_, err = conn.ExecContext(context.Background(), "SELECT * FROM your_table")
if err != nil {
fmt.Println("Database operation error:", err)
}
}()
}
time.Sleep(time.Second * 10)
}
对于 Redis 连接池,可以使用 go-redis
库,示例如下:
package main
import (
"fmt"
"github.com/go-redis/redis/v8"
"context"
)
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
PoolSize: 10,
MinIdleConns: 5,
})
ctx := context.Background()
pong, err := rdb.Ping(ctx).Result()
if err != nil {
panic(err)
}
fmt.Println(pong)
// 模拟高并发操作
var numRoutines = 100
for i := 0; i < numRoutines; i++ {
go func() {
err := rdb.Set(ctx, "key", "value", 0).Err()
if err != nil {
fmt.Println("Redis operation error:", err)
}
}()
}
time.Sleep(time.Second * 10)
}