借 Go 语言 database/sql 包谈数据库驱动和连接池设计

即使你不了解 Go 语言,阅读本文也不会有障碍

什么是池化技术

池化技术 (Pool) 是一种很常见的编程技巧,在请求量大时能明显优化应用性能,降低系统频繁建连的资源开销。我们日常工作中常见的有数据库连接池、线程池、对象池等,它们的特点都是将 “昂贵的”、“费时的” 的资源维护在一个特定的 “池子” 中,规定其最小连接数、最大连接数、阻塞队列等配置,方便进行统一管理和复用,通常还会附带一些探活机制、强制回收、监控一类的配套功能。

database/sql 包

设计哲学

Go 语言中对数据库进行操作需要借助标准库下的 database/sql 包进行,它对上层应用提供了标准的 API 操作接口,对下层驱动暴露了简单的驱动接口,并在内部实现了连接池管理。这意味着不同数据库的驱动可以很方便地实现这些驱动接口,但不再需要关心连接池的细节,只需要基于单个连接。

image

极简接口

它对外暴露的接口简单易懂,利于第三方 Driver 去实现,接口的功能包括 Driver 注册、ConnStmtTxRows结果集等,我们通过 ConnStmt 这两个接口来体会一下接口设计的精妙(这两个接口对应到 Java 就是 ConnectionStatement 接口,只是 Go 更加简单)

image

image

我相信你即使没有学习过 Go 语言,仅凭你的 Java 知识,也可以毫不费力地看懂上面这些接口的意思,这些对于驱动层暴露的接口非常简单,让驱动程序可以方便地去实现。

调用关系

整个 database/sql 驱动接口的调用关系非常清晰,简单来说驱动程序先通过 Open 方法拿到一个新建的 Conn,然后调用 ConnPrepare 方法,传入 SQL 语句得到该语句的 Stmt,最后调用 StmtExec 方法传入参数返回结果,查询语句同理,但返回的是行数据结果集。

image

连接池设计

sql.DB 对象关键属性

Go 语言操作数据库时,我们先使用 sql.Open 方法返回一个具体的 sql.DB 对象,如下代码片中的 db

image

sql.DB 对象即是我们访问数据库的入口,我们看看它里面的关键属性,均与连接池的设计相关

image

建立连接

事实上,连接并不是在 sql.Open 返回 db 对象时就建立的,这一步仅仅开了个接收建连请求的 channel,实际建连步骤要等到执行具体 SQL 语句时才会进行。下面我们通过一些例子讲述一下连接是怎么建立的,连接池的逻辑又是怎么实现的。

讲述这部分原理不会贴太多的源码,那就变成源码解析了,对不了解 Go 语言的同学也不友好,主要希望能传达一些连接池设计的思想。

database/sql 对上层应用暴露的操作接口中,比较常用的是 ExecQuery,前者常用于执行写 SQL,后者可以用于读 SQL。但是不论走哪个方法,都会调用到建连逻辑 db.conn 方法,附带建连上下文和建连策略两个参数。

image

其中建连策略分为 cachedOrNewConnalwaysNewConn。前者优先从 freeConn 空闲连接中取出连接,否则就新建一个;后者则永远走新建连接的逻辑。

使用 cachedOrNewConn 策略的建连逻辑中,会先判断是否有空闲连接,如果有取出首个空闲连接,紧接着判断该连接是否过期需要被回收,如果没有过期则可以正常使用进入后续逻辑。如果没有空闲连接则判断连接数是不是已经达到最大,若没有可以新建连接,反之就得阻塞这个请求让它等待可用连接。

如果需要新建连接,则调用底层 Driver 实现的连接器的 Connect 接口,这部分就是由各个数据库 Driver 自行去实现了。

image

释放连接

某个连接使用完毕之后需要归还给连接池,这也是数据库连接池实现中比较重要的逻辑,通常还伴随着对连接的可靠性检测,如果连接异常关闭,那么不应该继续还给连接池,而是应该新建一个连接进行替换。

image

JavaDruid 连接池会有 testOnReturn 或者 testOnBorrow 选项,表示在归还连接或者是获取连接时进行有效性检测,但是开启这两项本质上会延长连接被占用的时间,损失一部分性能。Go 语言中对这项功能的实现比较简单,并没有具体的有效性检测机制,只是直接根据连接附带的 err 信息,如果是 ErrBadConn 异常则关闭并发送信号新建一个。

清理连接

database/sql 包下提供了与连接池相关的三个关键参数设置,分别是 maxIdlemaxOpenmaxLifeTime

三个参数的含义很容易理解,如果想要深入了解,推荐阅读 Configuring sql.DB for Better Performance

MySQL 侧会强制 kill 掉长时间空闲的连接(8h),Go 语言提供了 maxLifeTime 选项设置连接被复用的最大时间,注意并不是连接空闲时间,而是从连接建立到这个时间点就会被回收,从而保证连接活性。

这块的清理机制是通过一个异步任务来做的,关键是逻辑是每个一秒遍历检查 freeConn 中的空闲连接,判断是否超出最大复用期限,超出的连接加入 Closing 数组,后续被 Close

总结

最近的工作内容是基于 go-sql-driver 实现了一个支持读写分离和高可用的自定义 driver,在调研和学习期间感受到了 Go 语言 database/sql 包的简明清晰,虽然它在部分功能的实现上偏简单甚至没有,但是依旧覆盖了大部分数据库连接池的主要功能和特性,因此我觉得用它来抛砖引玉是个好选择。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容