使用go連接clickhouse方式
前言
近段時(shí)間業(yè)務(wù)在一個(gè)局點(diǎn)測(cè)試clickhouse,用java寫的代碼在環(huán)境上一直連接不上clickhouse服務(wù),報(bào)錯(cuò)信息也比較奇怪,No client available,研發(fā)查了一段時(shí)間沒查出來(lái),讓運(yùn)維這邊繼續(xù)查:

運(yùn)維同學(xué)查了各種監(jiān)聽配置,防火墻這些,都沒什么問題,但是沒有明確證據(jù)能夠提供證明通過http方式能訪問到數(shù)據(jù)庫(kù),時(shí)間拖得比較久,項(xiàng)目上就急了,讓盡快找到問題,所以就用go寫了個(gè)小工具拉到集群上試試看8123這個(gè)端口到底能不能正常提供服務(wù)。
正文
先安裝必要的庫(kù),clickhouse官方提供了2個(gè)版本的庫(kù),v1和v2,v1版本已經(jīng)明確不會(huì)繼續(xù)更新了,所以用新不用舊哈,可以用官方庫(kù)的方式或者用dsn的方式,這個(gè)我下面一起說,安裝庫(kù)的命令:

go get github.com/ClickHouse/clickhouse-go/v2
構(gòu)造結(jié)構(gòu)體
編寫結(jié)構(gòu)體,存放基本信息:
type Clickhouse struct {
Host string // 服務(wù)端主機(jī)
Port int // 端口
DB string // 數(shù)據(jù)庫(kù)
User string // 用戶名
Password string // 密碼
Connection *sql.DB // 建立連接后存放連接
Rows *sql.Rows // 運(yùn)行sql后的結(jié)果存放
}
Connection主要是用來(lái)建立連接后把相關(guān)信息存放,這樣方便繼續(xù)調(diào)用其他的方法,因?yàn)槲业闹饕康氖菧y(cè)試數(shù)據(jù)庫(kù)能否連通和運(yùn)行Sql,所以這里Rows用來(lái)存放測(cè)試的select語(yǔ)句的結(jié)果。
參數(shù)讀取
這塊沒什么好說的,連接的參數(shù)直接從命令行讀取,用flag包就好:
var (
host = flag.String("host", "localhost", "clickhouse host")
port = flag.Int("port", 8123, "clickhouse port")
user = flag.String("user", "default", "clichouse user")
pass = flag.String("password", "", "clickhouse password")
db = flag.String("db", "default", "clickhouse database")
query = flag.String("query", "show tables", "query you will run")
mode = flag.String("mode", "driver", "driver or dsn")
)
前面幾個(gè)參數(shù)不用解釋,主要是query和mode,query是要運(yùn)行的sql語(yǔ)句,我們默認(rèn)就認(rèn)為跑的是select語(yǔ)句,然后是mode,允許選擇模式,用戶可以使用driver或者dsn兩種模式進(jìn)行連接,我寫了兩個(gè)不同的方法,其實(shí)也可以在一個(gè)Connect方法里做判斷,看個(gè)人習(xí)慣;
建立連接
接下來(lái)我們建立數(shù)據(jù)庫(kù)連接:
//
func (c *Clickhouse) Conn() {
c.Connection = clickhouse.OpenDB(&clickhouse.Options{
Addr: []string{fmt.Sprintf("%s:%d", c.Host, c.Port)},
Auth: clickhouse.Auth{
Database: c.DB,
Username: c.User,
Password: c.Password,
},
Settings: clickhouse.Settings{
"max_execution_time": 60,
},
DialTimeout: 5 * time.Second,
Compression: &clickhouse.Compression{
Method: clickhouse.CompressionBrotli,
Level: 5,
},
// 必須添加協(xié)議方式
Protocol: clickhouse.HTTP,
})
}
func (c *Clickhouse) ConnDsn() {
conn, err := sql.Open("clickhouse", fmt.Sprintf("http://%s:%d/%s?username=%s&password=%s", c.Host, c.Port, c.DB, c.User, c.Password))
if err != nil {
log.Printf("Connect to the server failed, %s.\n", err.Error())
return
}
c.Connection = conn
}
參考官網(wǎng)的實(shí)例,實(shí)現(xiàn)兩種連接方式,關(guān)閉方法就直接把sql.DB和sql.Rows都關(guān)閉就可以了:
func (c *Clickhouse) Close() {
c.Connection.Close()
c.Rows.Close()
}
發(fā)起查詢
查詢使用Query方法進(jìn)行:
func (c *Clickhouse) Select(query string) {
rows, err := c.Connection.Query(query)
if err != nil {
log.Printf("Query select failed, %s.\n", err.Error())
return
}
c.Rows = rows
}
查詢的結(jié)果我保存到Rows里,方便后面的解析
結(jié)果解析
比較麻煩的就是結(jié)果的解析了,用過database/sql庫(kù)的哥們都知道,這個(gè)庫(kù)只提供了基礎(chǔ)的一些接口,查詢出來(lái)一般用Scan去獲取數(shù)據(jù),用法類似這樣:

問題就在于,Scan要指定和sql查詢出來(lái)一樣多的變量,對(duì)于我們這個(gè)小工具來(lái)說,sql是不一定的,所以查詢出來(lái)的字段數(shù)量肯定yes不定的,如何動(dòng)態(tài)處理這個(gè)問題,肯定是不能直接寫一個(gè)結(jié)構(gòu)體解決的,先看我的代碼:
func (c *Clickhouse) Show() {
cols, err := c.Rows.Columns()
if err != nil {
log.Printf("Failed to get table columns, %s.\n", err.Error())
return
}
// 一行數(shù)據(jù),使用any是為了避開數(shù)據(jù)類型的問題
var rows = make([]any, len(cols))
// 存實(shí)際的值,是byte數(shù)組,長(zhǎng)度以列的數(shù)量為準(zhǔn)
var values = make([][]byte, len(cols))
for i := 0; i < len(cols); i++ {
rows[i] = &values[i]
}
// 打印表頭
fmt.Println(strings.Join(cols, ","))
for c.Rows.Next() {
if err = c.Rows.Scan(rows...); err != nil {
fmt.Println(err)
return
}
var vString []string
for _, v := range values {
vString = append(vString, string(v))
}
// 逐行打印出來(lái)
fmt.Println(strings.Join(vString, ","))
}
}
大概思路是這樣:
- Scan需要傳入每個(gè)用來(lái)綁定單行數(shù)據(jù)值的變量,所以values是實(shí)際存儲(chǔ)數(shù)據(jù)的byte數(shù)組,然后把數(shù)組的每個(gè)元素的地址再存入到rows數(shù)組中;
- 現(xiàn)在可以用rows[index]這樣的方式來(lái)訪問values中的值了,把rows直接作為入?yún)魅氲絊can,在每次循環(huán)中,把values的值轉(zhuǎn)成逗號(hào)分割的字符串,直接打印
結(jié)果驗(yàn)證
OK,現(xiàn)在邏輯完成了,我們運(yùn)行測(cè)試一下,
go run main.go -host hostname -password paswword -query "select * from clusters" -db system -mode dsn

只查詢2個(gè)字段,2行數(shù)據(jù):

結(jié)語(yǔ)
完成,然后把工具放到生產(chǎn)環(huán)境一測(cè)試,查詢都正常,這下開發(fā)哥們要繼續(xù)查他的程序問題了
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Go的gin參數(shù)校驗(yàn)中的validator庫(kù)詳解
這篇文章主要介紹了Go的gin參數(shù)校驗(yàn)之validator庫(kù),使用 validator 以后,只需要在定義結(jié)構(gòu)體時(shí)使用 binding 或 validate tag標(biāo)識(shí)相關(guān)校驗(yàn)規(guī)則,就可以進(jìn)行參數(shù)校驗(yàn)了,而不用自己?jiǎn)为?dú)去寫常見的校驗(yàn)規(guī)則,需要的朋友可以參考下2023-08-08
go使用SQLX操作MySQL數(shù)據(jù)庫(kù)的教程詳解
sqlx 是 Go 語(yǔ)言中一個(gè)流行的操作數(shù)據(jù)庫(kù)的第三方包,它提供了對(duì) Go 標(biāo)準(zhǔn)庫(kù) database/sql 的擴(kuò)展,簡(jiǎn)化了操作數(shù)據(jù)庫(kù)的步驟,下面我們就來(lái)學(xué)習(xí)一下go如何使用SQLX實(shí)現(xiàn)MySQL數(shù)據(jù)庫(kù)的一些基本操作吧2023-11-11
Golang中Context.WithCancel 的實(shí)戰(zhàn)指南
本文介紹了Go語(yǔ)言中context包的核心用法,重點(diǎn)講解了context.Background()和context.WithCancel()的使用場(chǎng)景,具有一定的參考價(jià)值,感興趣的可以了解一下2025-11-11
Go語(yǔ)言基本的語(yǔ)法和內(nèi)置數(shù)據(jù)類型初探
這篇文章主要介紹了Go語(yǔ)言基本的語(yǔ)法和內(nèi)置數(shù)據(jù)類型,是golang入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10
詳解Go多協(xié)程并發(fā)環(huán)境下的錯(cuò)誤處理
這篇文章主要介紹了詳解Go多協(xié)程并發(fā)環(huán)境下的錯(cuò)誤處理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08

