我用ClickHouse JDBC官方驅(qū)動(dòng),踩坑無數(shù)。。
?前言
最近遇到一個(gè)ClickHouse的線上問題:Code: 242, e.displayText() = DB::Exception: Table is in readonly mode(zookeeper path:/clickhouse/tables/02/xxx) (version 21.12.4.1) (official build)
這個(gè)問題我在網(wǎng)上查原因說是由于Zookeeper?壓力過大,表變成只讀狀態(tài),導(dǎo)致ClickHouse插入數(shù)據(jù)失敗。
具體原因有兩個(gè):
- 寫入數(shù)據(jù)頻率過高。
- Zookeeper中的集群節(jié)點(diǎn)掛掉。
而我們項(xiàng)目出現(xiàn)這個(gè)問題的原因是第一個(gè):寫入數(shù)據(jù)頻率過高。
但是在網(wǎng)上搜資料的過程中,我又發(fā)現(xiàn)了另外一個(gè)問題:我們項(xiàng)目用了JDBC驅(qū)動(dòng)?Maven groupId ru.yandex.clickhouse?,但ClickHouse官方并不推薦。
于是我果斷的訪問了ClickHouse的官網(wǎng),通過它訪問了ClickHouse的GitHub地址:https://github.com/ClickHouse/clickhouse-jdbc。
證實(shí)了官網(wǎng)確實(shí)不建議使用ru.yandex.clickhouse?驅(qū)動(dòng):
而應(yīng)該改成?com.clickhouse?驅(qū)動(dòng),并且推薦使用0.3.2?以上的版本:
于是,后面幾天開始了?ClickHouse的JDBC驅(qū)動(dòng)升級(jí)之旅。踩了不少坑,拿出來跟大家一起分享一下,希望對(duì)你會(huì)有所幫助。
1. 第一次升級(jí)
ClickHouse?官方GitHub上面推薦使用的JDBC驅(qū)動(dòng)是0.3.2?以上的版本:
于是,我果斷把項(xiàng)目中的?pom.xml?文件中的groupId?換成了com.clickhouse?,版本換成了0.3.2。
刷新了一下maven,本地啟動(dòng)項(xiàng)目,能夠正常運(yùn)行。
然后在本地測試了一下業(yè)務(wù)功能,能夠正常從ClickHouse中讀取和寫入數(shù)據(jù)。
心里不禁在想:這次升級(jí)實(shí)在太容易了。
2. 第二次升級(jí)
后來,項(xiàng)目組的同事建議換成最新版本,說有更多新功能,并且性能有很大提升。
我聽到性能有很大提升這幾個(gè)字,就決定再升級(jí)試試。
于是,把版本升級(jí)成了0.3.2-patch11。
在本地再次測試,業(yè)務(wù)功能一切正常。
然后把項(xiàng)目部署到測試環(huán)境了。
3. 發(fā)現(xiàn)問題了
第二天收到了兩封sentry?的報(bào)警郵件,報(bào)警級(jí)別都是warn。
第一封郵件中提示異常:This driver is DEPRECATED. Please use [com.clickhouse.jdbc.ClickHouseDriver] instead。
意思是說ru.yandex.clickhouse的驅(qū)動(dòng)已經(jīng)被廢棄了,請(qǐng)使用com.clickhouse.jdbc.ClickHouseDriver驅(qū)動(dòng)。
第二封郵件中提示異常:Also everything in package [ru.yandex.clickhouse] will be removed starting from 0.4.0。
意思是說ru.yandex.clickhouse將被移除。
看到這兩封郵件,我當(dāng)時(shí)有點(diǎn)懵,不就是用的com.clickhouse?驅(qū)動(dòng)包嗎,ru.yandex.clickhouse是從哪里來的?
于是全局搜索了一下ru.yandex.clickhouse關(guān)鍵字,并沒有搜到任何記錄。
這讓我更懵了。
接下來,我打開了clickhouse-jdbc-0.3.2-patch11-all.jar文件,看到了讓人意想不到的結(jié)果:
這個(gè)jar包下面竟然有兩個(gè)目錄:?com.clickhouse和ru.yandex.clickhouse,也就是說jar包中新驅(qū)動(dòng)和老驅(qū)動(dòng)兩種都支持。
而且ClickhouseDriver?類有兩個(gè):
我此時(shí)心里有十萬個(gè)為什么:為什么不直接把?ru.yandex.clickhouse包的代碼刪除了,卻在日志文件中打印一些警告呢?
這實(shí)在太坑了吧。
也就是說升級(jí)驅(qū)動(dòng)之后,項(xiàng)目依然用的老驅(qū)動(dòng)的代碼,我測試了個(gè)寂寞。。。
4. 如何使用新驅(qū)動(dòng)?
接下來我內(nèi)心的OS是:既然ClickHouse官方驅(qū)動(dòng)包,新老驅(qū)動(dòng)都支持,必然有個(gè)開關(guān)控制是使用新的JDBC驅(qū)動(dòng),還是使用老的JDBC驅(qū)動(dòng)。
從目前來看,如果沒有調(diào)整開關(guān),ClickHouse官方驅(qū)動(dòng)包默認(rèn)使用的是老的JDBC驅(qū)動(dòng)。
接下來,最重要的問題是要搞清楚:如何使用新驅(qū)動(dòng)?
很快,我查到通過配置下面的參數(shù):
就能指定Spring使用的JDBC驅(qū)動(dòng)。
果然在application.properties?文件中,配置數(shù)據(jù)源的地方,增加了這樣一個(gè)配置,重啟項(xiàng)目,Spring就是使用了新的ClickHouse JDBC驅(qū)動(dòng)。
日志中沒有打印郵件中那兩個(gè)warn了。
此時(shí),心里暗自竊喜,終于使用了ClickHouse官方推薦的JDBC驅(qū)動(dòng)。
項(xiàng)目已經(jīng)正常運(yùn)行起來了,趕緊測試一下業(yè)務(wù)功能是否正常。
5. 出現(xiàn)了兩個(gè)新問題
結(jié)果馬上被啪啪打臉了。
在測試批量insert數(shù)據(jù)的業(yè)務(wù)場景時(shí),系統(tǒng)運(yùn)行日志中出現(xiàn)了兩個(gè)異常:
異常1:Code: 6. DB:Exception: Cannot prse string '2022-11-22 14:42:37.025' as DateTime:syntax error at position 19...?從提示的信息看,它表示時(shí)間2022-11-22 14:42:37.025不能轉(zhuǎn)換成DateTime類型。
異常2:Please consider to use one and only one values expression, for example: use 'values(?)' instead of 'values(?),(?).'從提示的信息看,它表示不支持批量insert數(shù)據(jù)。
我去。。。
升級(jí)ClickHouse JDBC驅(qū)動(dòng)出問題了。
ClickHouse 官方最新的JDBC驅(qū)動(dòng)竟然不支持批量insert數(shù)據(jù),這個(gè)問題更嚴(yán)重。
趕緊搜索一下解決辦法。
6. 回退版本
很快,在clickhouse-jdbc的issues中查到了類似的問題,地址:https://github.com/ClickHouse/clickhouse-jdbc/issues/1106?。問題如下:
下面有人回答:
使用老版本就沒有這個(gè)警告。
我一下子如夢(mèng)初醒。
不要迷戀最新的版本,clickhouse-jdbc一定要找最合適的版本。
于是,我查了dev、st和ga環(huán)境的ClickHouse服務(wù)器版本,發(fā)現(xiàn)dev用的是20.12.8.5?,而st和ga用的21.12.4.1。
為了兼容dev環(huán)境,ClickHouse服務(wù)器版本以20+為準(zhǔn),再看看clickhouse-jdbc能用什么版本。
很快在releases中查到,clickhouse-jdbc能用0.3.2,最高只能0.3.2-patch1。因?yàn)?.3.2-patch2以上,要求ClickHouse服務(wù)器是21+的版本。
因此,我只能將clickhouse-jdbc的版本回退到:0.3.2-patch1。
果然,回退版本之后,不能批量insert的問題解決了。
接下來,就是一個(gè)問題。
7. DateTime
讓我們一起回顧一下那個(gè)問題:Code: 6. DB:Exception: Cannot prse string '2022-11-22 14:42:37.025' as DateTime:syntax error at position 19...?從提示的信息看,它表示時(shí)間2022-11-22 14:42:37.025不能轉(zhuǎn)換成DateTime類型。
而DateTime的時(shí)間格式是:yyyy-MM-dd HH:mm:ss?,這個(gè)問題是由于2022-11-22 14:42:37.025包含了毫秒,不能直接轉(zhuǎn)換成2022-11-22 14:42:37導(dǎo)致的。
我查了一下代碼和表結(jié)構(gòu),代碼中Entity中time字段定義成的Date類型。
而表中定義的time字段是DateTime類型。
ClickHouse官方驅(qū)動(dòng)無法將Date類型的時(shí)間直接轉(zhuǎn)換成DateTime類型。
怎么解決這個(gè)問題呢?
答:修改表中的字段類型不就行了,將DateTime?轉(zhuǎn)換成DateTime64,DateTime64?是支持毫秒的。
我親測過,使用DateTime64類型接收J(rèn)ava中Date類型的時(shí)間,能夠正常解析。
那張表有三個(gè)DateTime類型的字段:create_time、edit_time和time。
前面兩個(gè)字段的字段類型,很容易就修改成功了。
但修改time字段時(shí),卻報(bào)了一個(gè)異常:Code: 524,e.displayText() = DB::Exception: Alter of key column time from type DateTime to type DateTime64(3) must be metadata-only (20.12.8.5)
提示作為key的字段不能被修改。
這又是為什么?
8. order by
我這一次直接查看了那張表的建表語句:
發(fā)現(xiàn)該表處理主鍵和普通索引之外,還特別加了order by的索引。
例如:order by (code, time)。
看到這里我迅速明白了,原來time字段是order by的索引字段,難怪不允許隨便修改的。
于是,找DBA商討對(duì)策。
DBA說要修改ClickHouse中表的索引字段的類型,只能重新建表,然后把數(shù)據(jù)同步過去。
很顯然這個(gè)方案太麻煩了。
我在想有沒有其他更簡單的方案呢?
9. date_time_input_format參數(shù)
我此時(shí)在思考,不就是時(shí)間轉(zhuǎn)換出的問題嗎?
讓ClickHouse在保存數(shù)據(jù)時(shí),自動(dòng)轉(zhuǎn)換一個(gè)時(shí)間格式不就解決問題了嗎?
我在官網(wǎng)上查到一個(gè)叫:date_time_input_format的參數(shù)。
該參數(shù)允許選擇日期和時(shí)間的文本表示的解析器。
它可能的值:
- 'best_effort' — Enables extended parsing.
ClickHouse可以解析基本 YYYY-MM-DD HH:MM:SS 格式和所有 ISO 8601 日期和時(shí)間格式。例如, '2018-06-08T01:02:03.000Z'.
- 'basic' — Use basic parser.
ClickHouse只能解析基本的 YYYY-MM-DD HH:MM:SS 格式。例如, '2019-08-20 10:18:56'.
默認(rèn)值: 'basic'.
原來這個(gè)是時(shí)間轉(zhuǎn)換失敗的根源,如果我們把date_time_input_format?的值設(shè)置成best_effort,不就解決問題了。
為了不影響全局,我想只給那三張表調(diào)整date_time_input_format的值。
但是在保存設(shè)置時(shí),報(bào)錯(cuò)了。
原來date_time_input_format參數(shù)只允許在MergeTree?存儲(chǔ)引擎上使用,而我們表的存儲(chǔ)引擎用的ReplacingMergeTree。
暈死了。。。
只能想想其他辦法了。
10. parseDateTimeBestEffortOrNull
在insert數(shù)據(jù)的地方,用函數(shù)手動(dòng)轉(zhuǎn)換一下不就OK了嗎?
當(dāng)然修改Java的Entity中的Date類型,改成String也是可以的,不過review了一下代碼,這種改動(dòng)有點(diǎn)大,涉及的地方很多。
最小的改動(dòng)是在mapper層處理,因?yàn)橐粋€(gè)mapper中最多只有一個(gè)insert存在。
而我review了所有的ClickHouse表,只有3張表用了DateTime類型,其他的表都是DateTime64類型。
剛開始,我在ClickHouse的官方文檔中查到了formatDateTime函數(shù),測試之后發(fā)現(xiàn)該函數(shù)不太合適。
后來找到了parseDateTimeBestEffort?系列函數(shù),決定使用parseDateTimeBestEffortOrNull函數(shù)。
只需在mapper.xml的insert語句中,使用parseDateTimeBestEffortOrNull(#{item.time})改造一下即可。
測試后發(fā)現(xiàn),時(shí)間轉(zhuǎn)換問題被解決了。
后來,有條select語句中又出現(xiàn)了這個(gè)異常。
我剛開始以為是toDate(time)函數(shù)導(dǎo)致的,但后面發(fā)現(xiàn)該select的where條件中使用了time字段作為查詢條件,才導(dǎo)致了該問題的發(fā)生。
這時(shí)同樣使用parseDateTimeBestEffortOrNull函數(shù),解決了問題。
至此,ClickHouse的JDBC驅(qū)動(dòng)包升級(jí)完成,沒有再出現(xiàn)其他的問題。
需要特別注意的是:以后新創(chuàng)建的表或新加的字段,如果有時(shí)間類型的字段,務(wù)必要定義成DateTime64類型的。
其實(shí),我們?cè)谑褂肅lickHouse的過程中,同樣也遇到過很多坑,文章開頭的那個(gè)問題只是其中一個(gè),后面會(huì)有一篇專題文章分享給大家,敬請(qǐng)期待。