Doris開發手記1:解決蛋疼的MySQL 8.0連線問題
阿新 • • 發佈:2021-02-15
> 筆者作為Apache Doris的開發者,平時感覺相關Doris的文章寫的很少。主要是很多時候不知道應該去記錄一些怎麼樣的問題,感覺寫的不好就會很慌張。新的一年,希望記錄自己在Doris開發過程之中所遇到一些有意思的事情。(**只希望能堅持下來,別打臉~~**)
言歸正傳,回到本篇想聊的問一個問題,筆者在開發ODBC of Doris的工作之中,發現通過**MySQL 8.0**的Driver連線Doris總是提示密碼驗證失敗。但是由於開發工作繁忙,一直沒有騰出手解決這個問題。最近重新抽時間梳理了一下這個問題,這個問題本身不難解決,但是解決問題的思路我覺得值得與大家分享,獻醜了啊,各位~~
### 1.老革命遇到新問題
使用MySQL 8.0的客戶端連線Doris時,如果不新增如下引數`--default-auth=mysql_native_password`的話,總會出現如下提示的密碼認證錯誤:
```
ERROR 1045 (28000): Access denied for user 'default_cluster:test' (using password: YES)
```
同樣的密碼認證問題也會同時出現在了使用ODBC的MySQL 8.0以上的Driver連線Doris時。更令人蛋疼的是,使用ODBC連結時並沒法呼叫上面的引數進行問題的規避。這會帶來兩個問題:
* 1. Doris本身的ODBC外表無法通過MySQL 8.0以上的Driver連線Doris
* 2.許多流行的BI分析工具如**Tableau**等:也無法通過ODBC的方式連線Doris
之前通過5.x的客戶端和Driver可以順利的連線Doris,而現在真是**老革命遇上新問題**了。
##### 預設的密碼認證外掛的變更
其實新問題的引入很簡單,就是MySQL的客戶端從8.0的版本開始,將原先客戶端的預設的密碼認證外掛由`mysql_native_password`改為了`caching_sha2_password`,兩種密碼認證方式不同。而Doris當前只支援`mysql_native_password`的密碼認證外掛,所以就導致了連線時密碼認證失敗了。而關於密碼認證外掛的變更,更為詳細的內容,可以參考[MySQL的官方文件](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/upgrading-from-previous-series.html)。
### 2.問題的分析與梳理
好的,確認了問題,就開始研究解決方案。從直覺上說,Doris支援新的`caching_sha2_password`密碼認證外掛肯定是最直接的解決思路。這種做法肯定是一勞永逸的解決問題的,但是這就得重構整個Doris的密碼管理系統,開發和支援起來的代價實在是有些太大了。
那既然我們否定了這種方式,就得另外想辦法解決了。**首先,使用MySQL 8.0的客戶端連線Doris時,新增如下引數`--default-auth=mysql_native_password`便可以認證成功。** 所以問題就回到了如何讓ODBC的連線能夠支援上述引數,筆者經歷了下面的分析歷程:
##### 2.1 ODBC連線文件
ODBC是通過連線串的方式傳參給MySQL的連線Driver的,如果能夠像使用MySQL客戶端的方式新增引數便可以解決,那麼自然無程式碼的Coding是成本最低的解決方案。
筆者首先嚐試查看了[MySQL官方的ODBC連線引數文件](https://dev.mysql.com/doc/connector-odbc/en/connector-odbc-configuration-connection-parameters.html),遺憾的是,並沒有找到ODBC關於認證方式的任何內容,這也就意為著:**此路不通**。
##### 2.2 新舊版本的相容性
既然MySQL從8.0開始切換了預設的密碼認證外掛,那麼新的客戶端是否可以連線老的MySQL伺服器呢?**MySQL本身是如何解決新老客戶端的相容問題的呢?**
於是筆者嘗試使用MySQL 8.0的客戶端連線了MySQL的5.x的伺服器,發現了下面的線索:**新客戶端並不需要像連線Doris一樣,修改預設的密碼認證外掛**。那也就意味著,MySQL的客戶端和伺服器可以在連線過程之中**通過某種方式交換確認一種伺服器支援的密碼認證方式。**
既然如此,筆者開始了Google之旅,但是並沒有搜尋到什麼有價值的資訊。沒辦法,**原始碼面前,了無祕密**。於是筆者決定嘗試閱讀一下MySQL Client端的程式碼,看看是否能發現上述的互動邏輯。
經過一番"痛苦"的原始碼搜尋和閱讀,筆者在找到了如下的註釋,完整的闡述了MySQL的客戶端與伺服器的連線過程:
```
1. The client connects to the server
2. The server sends @ref page_protocol_connection_phase_packets_protocol_handshake
3. The client respons with
@ref page_protocol_connection_phase_packets_protocol_handshake_response
4. The server sends the
@ref page_protocol_connection_phase_packets_protocol_auth_switch_request to tell
the client that it needs to switch to a new authentication method.
5. Client and server possibly exchange further packets as required by the server
authentication method for the user account the client is trying to authenticate
against.
6. The server responds with an @ref page_protocol_basic_ok_packet or rejects
with @ref page_protocol_basic_err_packet
```
把上述的註釋讀懂之後,筆者又回頭查閱了一下Doris之中處理MySQL客戶端連線的程式碼。總算是整明白了為啥新的客戶端連線Doris會失敗了,這個是新客戶端連線Doris的流程:
```
Drois ->: Authentication Plugin: mysql_native_password
Client <-: Client Auth Plugin: caching_sha2_password
Doris ->: MySQL Error 2012 (HY000): Password check failed.
```
而新客戶端連線老的MySQL的流程如下:
```
Mysql ->: Authentication Plugin: mysql_native_password
Client <-: Client Auth Plugin: caching_sha2_password
Mysql ->: Auth Switch Request: Auth Method Name: mysql_native_password
Client <-: Auth Switch Response
Mysql ->: OK
```
MySQL的伺服器支援了`Auth Switch Request`的網路請求來告知客戶端自己支援的認證的密碼外掛,而客戶端會進行密碼外掛的支援檢查,而客戶端則將密碼外掛加密的結果返回。
### 3.開發起來,解決問題
通過上一小節的分析,問題已經水落石出了。接下來就是如何在Doris上支援`Auth Switch Request`的網路請求。
##### 3.1 確認二進位制結構
所以這裡就需要研究這兩個`Auth Switch Request`和`Auth Switch Response`的二進位制包是如何組成的。這裡再次藉助官方文件,確認了兩個包的組成:
![AuthSwitchRequest](https://upload-images.jianshu.io/upload_images/8552201-8e0d7dcbbd78afdb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![AuthSwitchResponse](https://upload-images.jianshu.io/upload_images/8552201-db1ed26940a42a96.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
##### 3.2 程式碼開發
其實到這裡的工作已經很簡單了,直接上筆者修改Doris的程式碼吧:
```
if (!handshakePacket.checkAuthPluginSameAsDoris(authPacket.getPluginName())) {
// 1. clear the serializer
serializer.reset();
// 2. build the auth switch request and send to the client
handshakePacket.buildAuthSwitchRequest(serializer);
channel.sendAndFlush(serializer.toByteBuffer());
// Server receive auth switch response packet from client.
ByteBuffer authSwitchResponse = channel.fetchOnePacket();
if (authSwitchResponse == null) {
// receive response failed.
return false;
}
// 3. the client use default password plugin of Doris to dispose
// password
authPacket.setAuthResponse(readEofString(authSwitchResponse));
}
```
就是進行了密碼認證外掛的校驗,**如果不Match Doris預設的密碼認證外掛的話,則構造AuthSwitchRequest傳送給客戶端**。(筆者這裡只列出了部分程式碼,完整的程式碼修改請參考如下的[pr](https://github.com/apache/incubator-doris/pull/5386/files) .
Coding完成之後,編譯部署,進行測試,問題解決,提出issue,把解決問題的程式碼貢獻給Doris的官方程式碼倉庫提pr。完結撒花~~~~
### 4.小結
**Bingo!** 到此為止,問題順利解決了,希望通過和大家分享一個問題的解決流程,幫助大家梳理資料庫開發之中的解決問題的思路。我們有著最大的三個幫手
* 搜尋引擎
* 官方文件
* 原始碼
而如果你遇到的是Doris的問題,那你就有第四個幫手了:**百度Doris團隊**。(加星重點)
當然,這裡也留下一個TODO的問題:支援MySQL 8.0預設的`caching_sha2_password`的認證方式。相較原先的`mysql_native_password`的認證方式,它有一定的安全性優勢,但是這樣可能需要重構整個Doris的密碼體系了。
最後,也希望大家多多支援Apache Doris,多多給Doris貢獻程式碼,感恩~~
### 5.參考資料
[MySQL官方文件]([https://dev.mysql.com/doc](https://dev.mysql.com/doc)
)
[MySQL原始碼](https://github.com/mysql/mysql-server)
[Apache Doris原始碼](https://github.com/apache/incubator