[toc]
Deno如何實現(xiàn)Mysql中間件
其他語言類似,換湯不換藥
首先講解一下mysql協(xié)議
想要編寫mysql中間件,必須需要對mysql協(xié)議有所了解的圆。mysql協(xié)議中間件。接下來會簡單講解一些基本協(xié)議半火,如果您想仔細(xì)了解越妈,請您移步官方文檔mysql protocol
mysql 協(xié)議了解
簡介
mysql采用C/S模式,服務(wù)器啟動后會監(jiān)聽本地端口钮糖∶仿樱客戶端請求到達時,會執(zhí)行三段握手以及mysql的權(quán)限認(rèn)證店归,驗證成功后會客服端會發(fā)送請求報文阎抒,服務(wù)端發(fā)送響應(yīng)報文進行交互
C->S
graph LR
Client-->Server
存在以下數(shù)據(jù)包
- 登陸時的auth包
- 執(zhí)行SQL的CMD包
S->C
graph LR
Server-->Client
存在以下數(shù)據(jù)包
- 握手包
- 數(shù)據(jù)包
- 數(shù)據(jù)流結(jié)束包
- 成功包(OK Packet)
- 錯誤信息包
如何與MySql建立連接
所有客戶端鏈接都需要和經(jīng)過server認(rèn)證
驗證分為4個步驟
1、三次握手建立tcp連接
客服端撥號進行鏈接
// 偽代碼
public async connect(){
this.conn = Deno.dail({
hostname,
port,
transport:"tcp"
})
}
2消痛、建立mysql連接且叁,也就是認(rèn)證階段
認(rèn)證階段 Initial Handshake
- 服務(wù)器按照 Protocol::HandshakeV10協(xié)議發(fā)送給客服端
- 客戶端根據(jù)協(xié)議內(nèi)容進行內(nèi)容修改然后Protocol::HandshakeResponse提交服務(wù)端進行驗證
下面這個圖更直觀
密碼驗證圖-盜用
// **** 驗證流程 ****
// 1、mysql.user中存儲的是兩次sha1加密過后的stage2hash
// 2秩伞、服務(wù)端發(fā)送隨機字符串scramble到客服端并且mysqld利用stage2hash+scramble進行一次sha1操作逞带,生成key欺矫。
// 3、客戶端利用用戶輸入pwd生成stage2hash展氓,加上mysqld發(fā)送過來的scramble進行一次sha1操作穆趴,生成和mysqld相同的key。然后再拿這個key和stage1hash進行一次xor操作带饱,生成ciphertext毡代,發(fā)送給mysqld
// 4、mysqld拿到客戶端發(fā)送的ciphertext勺疼,加上之前生成的key教寂,進行一次xor逆向操作,解密出stage1hash执庐,再對stage1hash進行一次sha1操作酪耕,生成stage2hash爹梁,再拿著這個stage2hash和mysql.user表中存儲的信息對比识樱,如果一致,則此次密碼認(rèn)證通過揭绑。
export function authPwd(password:string,scramble:Unit8Array){
// 客服發(fā)送驗證信息
const hash = new Hash("sha1");
const pwdDigestOnce = hash.digest(encode(password)).data;
const pwdDigestTwice = hash.digest(pwd1).data;
let scrambleAndPwdDigest = new Unit8Array(seed.length + pwdDigestTwice.length);
scrambleAndPwdDigest.set(scramble);
scrambleAndPwdDigest.set(pwdDigestTwice, scramble.length);
scrambleAndPwdDigest = hash.digest(scrambleAndPwdDigest).data;
const ciphertext = scrambleAndPwdDigest.map((byte, index) => {
// key和stage1hash進行一次xor操作
return byte ^ pwdDigestOnce[index];
});
return ciphertext;
}
- 服務(wù)端返回驗證結(jié)果
3递鹉、認(rèn)證通過之后盟步,客戶端開始于服務(wù)端進行交互,也就是命令執(zhí)行階段
// receive = await this.packet(); 等待服務(wù)端驗證結(jié)果
// 通過是OK packet Err packet 返回驗證結(jié)果
if (header === 0xff) {
const error = parseError(receive.body, this);
log.error(`connect error(${error.code}): ${error.message}`);
this.close();
throw new Error(error.message);
}
// 鏈接成功
4躏结、斷開mysql連接
客服端發(fā)出退出命令包
四次握手?jǐn)嚅_tcp連接 只需 this.conn.close()
如何維護一個連接池
// promise 經(jīng)典defer操作
interface Defered<T>{
promise:promise<T>;
reslove:(c?:T)=>void;
reject:(e?:any)=>void;
}
export function defer<T>():Defer<T>{
let reject: (arg?: any) => void;
let resolve: (arg?: any) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return {
promise,
reject,
resolve
};
}
const connection = await this.pool.getConnection();
async getConnection(): Promise<T> {
// 如果存在可用已經(jīng)建立的conn直接取出來用
if (this.availableConn.length) {
return this.availableConn.pop();
} else if (this._size < this.poolSize) {
// 沒有可用conn如果創(chuàng)建新的conn直至最大
this._size++;
const item = await this.create();
return item;
}
// 沒有可用conn却盘,且已經(jīng)達到最大鏈接數(shù)量 等待
const d = defer<T>();
this._queue.push(d);
await d.promise;
return this.availableConn.pop();
}
// 使用完畢歸還
async returnConnection(item: T) {
this.availableConn.push(item);
if (this._queue.length) {
this._queue.shift().resolve();
}
}
如何增刪改查 - part 2
避免篇幅較長,下次繼續(xù)講解媳拴。原理與建立連接不變黄橘,按照協(xié)議格式進行curd
總結(jié)
編寫mysql中間件的難點是 需要分析協(xié)議,按照協(xié)議進行無腦式編寫屈溉。
其中連程池的需要借助promise實現(xiàn)協(xié)程是一個難點塞关。
思考
mysql中間件目前利用js編寫,替換成rust編寫ffi是否能提高性能子巾。
感覺可以 【故作思考.jpg】
待我繼續(xù)學(xué)習(xí)學(xué)習(xí)rust
參考:
https://github.com/manyuanrong/deno_mysql
https://github.com/bartlomieju/deno-postgres
https://github.com/bartlomieju/deno-postgres