Go实战准备工作---创建数据库连接池

此项目改写根据个人习惯来创建,实际项目准备工作不分先后顺序,准备好了就行,不需要纠结这个。

本项目属于公司内部项目,只是提供思路和关键代码,需要的源码可以添加本人微信:wjw1103608332

一:项目简介

项目属于智能客服平台的以及智能外呼子平台的项目,改写的只是PHP部分的代码,此项目是结合了Go和NLP以及Java共同实现的项目。PHP是负责web端和go端的数据交互,以及业务处理。本次改写除了基本的业务替换,还有性能的对比以及优化,没有这些改写也就毫无意义。此前PHP的版本是ThinkPHP3.2,相当古老,本次也是根据公司的需求按照实际情况来改写,不仅仅个人的Demo,后期的变动也是有可能的,由于领导的瞎指挥,不一定能尽人意。

话不多说,目前需要准备工作有:数据库的连接池、redis连接池、go协程连接池、日志管理等。内容可能比较多,今天这篇就介绍数据库连接池,其他两个后面文章会补上。目前网络请求框架是Gin和Beengo,还没有决定使用哪个,暂时用的是Gin,可扩展性强,灵活使用。后续对比再来确定使用哪个,毕竟框架只有最合适没有最好一说,后续决定使用 哪个也会说明原因,也许是技术原因,也许是客观原因。

本项目属于云服务智能机器人项目,属于SAAS服务项目,总库和企业分库的方式,由于库已经创建并使用,无法进行更改和替换测试,所以数据库的设计这一块我们只有优化语句,对于结构不做过多描述,当然吐槽是少不了的,毕竟,有时候写代码碰到不合理的结构时是真的很气人。

二:数据库的连接池创建

 本项目会 涉及到多个数据库的切换,正常来说是两个库,一个总库,一个分库,无论哪个企业,最多也就两个库的切换。当然,如果是后台管理员账号会涉及到所有企业的库切换。

单库连接池创建

  第一步:引入数据库驱动: _ "github.com/go-sql-driver/mysql"   也可以是其他驱动,这没什么好解释的,用的最多的,也是目前最广泛使用的。创建代码如下:
//创建指定库的连接池
func createNewDBConn(dbName string) *sql.DB {
    var err error
    dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=30s&charset=utf8mb4&collation=utf8mb4_general_ci", config.Instance.MySqlUser, config.Instance.MySqlPwd, config.Instance.MySqlAddress, dbName)
    fmt.Println("dsn: ", dsn)
    db, err = sql.Open("mysql", dsn)
    if err != nil {
        return nil
    }
    db.SetMaxOpenConns(100)
    db.SetMaxIdleConns(50)
    db.SetConnMaxLifetime(time.Minute * 10) //10分钟回收
    err = db.Ping()
    if err != nil {
        return nil
    }
    return db
}
  直接设置数据库名称或者写死就是单库的连接池。

   思考几个问题:1、最大连接数和最大空闲数怎么设置和确定?2、回收时间如何确定呢?

   这两个问题对于只会PHP开发的同学来说确实有点难度,因为如果没用过swoole或者基于swoole框架的话,是没有池的概念的。更别谈数据最大值的设置。但是对于Java(我们Android也是哈)或者其他使用常驻内存的开发者来说,这是非常常见的问题,最原始的莫过于线程池的使用和核心线程数优化了,想想就头痛。数据库的其实没那么复杂。我们设置成100,是因为MySQL默认连接数就是100,当我们的机器承受不了的时候,我们需要执行命令:show processlist 查看下当前运行的query数量,然后使用TOP命令观察占用情况。当然,机器牛逼的可以直接设置成更多的,比如200,那就执行命令:set GLOBAL max_connections=200或者这更多,查看占用情况。找到最合适的数据,这就是简单的优化方案,复杂一点的可以借助压测工具进行压测,看看机器承受的临界值是多少再来确定具体的值。空闲值我的做法是根据用户量和访问情况来设置的。其实10个就完全满足我们目前的需求,为了显示SAAS服务的高端大气,就写成50,也好方便商务出去吹牛逼。空闲时间同样是根据业务实际情况,高并发状态下长点是可以接受的,太短同样会引起频繁的创建,太长也会 占用资源得不到有效的释放。

多库切换连接池创建

  鉴于没有想到好的办法,目前的做法是创建多个数据库把数据库放在map集合里面,再从map里面去拿,有就直接使用没有就创建。首先创建结构体
type DbPool struct {
    maxDbs int
    DBs    map[string]*sql.DB
    mux    *sync.RWMutex
}
设置一个创建所有池的大小,注意,初始时2个,所以,默认就2个大小的切片。
func newDbPool(maxDBs int) *DbPool {
    return &DbPool{maxDbs: maxDBs, DBs: make(map[string]*sql.DB, 2), mux: new(sync.RWMutex)}
}
设置一个调用库的函数,有就调用,没有创建。
func (dp *DbPool) GetDB(dbName string) *sql.DB {
    dp.mux.Lock()
    defer dp.mux.Unlock()
    if db, ok := dp.DBs[dbName]; ok {
        return db
    }
    if len(dp.DBs) > dp.maxDbs {
        for k, v := range dp.DBs {
            _ = v.Close()
            delete(dp.DBs, k)
            break
        }
    }
    //获取连接池的时候,如果此数据库的连接池不存在,创建新的连接池
    if newDB := createNewDBConn(dbName); newDB != nil {
        dp.DBs[dbName] = newDB
        return newDB
    }

    return nil
你以为就完了?不,创建池,这种全局使用的,必定使用yaml创建常量。常量可以直接const,也可以yaml设置配置文件,官方描述的区别是:const修改要重新发布,yaml更改可以不发布版本,直接修改立即生效,对于运维的同学是个福音。当然,也要看你打包的方式了。

配置yaml文件

  依然是使用库:gopkg.in/yaml.v2   没错,还是第三方的框架,没有这些框架,我们就没必要换Go了,什么都要自己实现这也太痛苦了。接下来设置结构体

type Configuration struct {
    MySqlPwd     string `yaml:"mySqlPwd"`
    MySqlUser    string `yaml:"mySqlUser"`
    MySqlAddress string `yaml:"mySqlAddress"`
    MysqlMaxDBs  int    `yaml:"mysqlMaxDBs"`
}
 设置全局变量,既然是配置文件,少不了单例模式了:
var (
    once     sync.Once
    Instance *Configuration
)
加载并解析yaml文件
func InitConfig() {
    config, err := ioutil.ReadFile("config/config.yaml")
    if err != nil {
        log.Println("configYaml get err:", err)
    }
    err = yaml.Unmarshal(config, GetConfig())
    if err != nil {
        log.Println("configYaml get err:", err)
    }

单例模式实现

Go的单例模式不像Java的那么简单粗暴,懒汉式、饿汉式、静态内部类之类的。
func GetConfig() *Configuration {
    once.Do(func() {
        Instance = &Configuration{}
    })
    return Instance
}
我这边采用的是once.Do的方式来实现单例,还有其他的方式,大家都可以尝试,利弊目前不清楚,待确定。



总结:本篇博客记录的是单库和多库的连接池创建,对于多库采用的是放切片统一管理,但是缺点是:对于admin管理多个数据库切换是会出现爆发式的池创建过程,好在只有一个账号,不会出现大量场景。有更好的方式,请不吝赐教!同时,对于数据库配置的常量,我这边采用的yaml文件配置,并且采用单例的模式获取。(既然是成长,就是没难度也要给自己增加难度上)

ps:我们项目还有使用pgsql的部分业务,但是连接池都是一样的,pgsql目前是单库的连接,所以比较简单就没有加上,后续代码可能会出现这种连接。其他的几种连接池,后续文章分享记录。
本作品采用《CC 协议》,转载必须注明作者和本文链接
生命有限,随心而活,写代码不香吗?
棋布
讨论数量: 4
Summer

排版可以优化下,滥用代码块了。

参考 6.8. Markdown 语法上手(先学这 8 种)

2周前 评论
棋布

@Summer 感谢,第一次用这工具写。

2周前 评论

读取配置可以放在init函数里,就不用单例模式了,还有你多库放的是map,不是切片(slice

2周前 评论
棋布

@诺大的院子 已改正,多谢指出。

2周前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!