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