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

    科技2024-10-30  16

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

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

    一:项目简介

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

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

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

    二、数据库的连接池创建。

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

         1、单库连接池创建。

          第一步:引入数据库驱动: _ "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,也好方便商务出去吹牛逼。空闲时间同样是根据业务实际情况,高并发状态下长点是可以接受的,太短同样会引起频繁的创建,太长也会 占用资源得不到有效的释放。

     2、多库切换连接池创建

          鉴于没有想到好的办法,目前的做法是创建多个数据库把数据库放在切片里面,再从切片里面去拿,有就hi直接 使用没有就创建。首先创建结构体

        

    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目前是单库的连接,所以比较简单就没有加上,后续代码可能会出现这种连接。其他的几种连接池,后续文章分享记录。

    Processed: 0.039, SQL: 8