前言:在深入了解SMProxy之前,一直以为连接池是对mysql连接对象的统一管理,但是随之而来的问题是,现有的php框架没有自己的mysql连接池。如何以最小的成本更换框架的数据库模块一直是个难题。深入了解SMProxy后,我发现SMProxy的妙处在于不需要对框架的数据库模块做任何修改就可以使用SMProxy架构。它是一个基于mysql客户端和mysql服务器的中间件。通过swoole/服务器本身模拟与mysql消息交互,内部管理连接池对象,提高效率。优点:明显客观的性能提升,减少创建连接的资源消耗无需对框架做任何修改即可使用缺点:需要对mysql协议有一定的了解。如果要更改它,则需要对协议有更深入的了解。如果出现错误,则会增加故障排除的成本。知识点补充:协议中常用的数据,十进制,十六进制,二进制。为什么用十六进制来表示字节中的内容,而二进制中每4位代表十六进制中的一位,而且二进制和十六进制的转换比十进制快很多。swoole:swoole/server和swoole/client用到的知识点,不多介绍tcp粘包问题:https://www.cnblogs.com/JsonM...client->tcpbuffer(等待cpu命令,如果buffercache达到上限,会直接发送给服务器,所以可以一次接受多个数据)->servermysql协议分析https://www.cnblogs.com/davyg...SMProxy:Execution流程图接下来将对mysql与SMProxy的swoole服务的交互进行一些解释:(如果对以下消息有疑问,可以仔细阅读mysql协议https://www.cnblogs.com/davyg...然后与tcp交互,需要服务器向mysql客户端发送握手初始化报文,只要发送符合mysql协议的握手报文,mysql客户端就会进行下一步发送认证请求。//在SMProxy/src/Handler/Frontend/FrontendAuthticator中publicfunctiongetHandshakePacket(int$server_id){$rand1=RandomUtil::randomBytes(8);$rand2=RandomUtil::randomBytes(12);$this->seed=array_merge($rand1,$rand2);$hs=new握手包();$hs->packetId=0;//以下是根据握手消息//协议版本号$hs->protocolVersion=Versions::PROTOCOL_VERSION;//服务器版本号信息$hs->serverVersion=Versions::SERVER_VERSION;//服务器线程$hs->threadId=$server_id;//随机数$hs->seed=$rand1;//填充值,服务器能力ID,$hs->serverCapabilities=$this->getServerCapabilities();//字符编码$hs->serverCharsetIndex=(CharsetUtil::getIndex(CONFIG['server']['charset']??'utf8mb4')&0xff);//服务器状态$hs->serverStatus=2;//服务器能力ID+填充值$hs->restOfScrambleBuff=$rand2;returngetString($hs->write());}//位于SMProxy/src/HandshakePacketpublicfunctionwrite(){//默认init256,所以它can避免buff提取$buffer=[];//写入消息头长度BufferUtil::writeUB3($buffer,$this->calcPacketSize());//写入序号--消息头的$buffer[]=$this->packetId;//写入协议版本号$buffer[]=$this->protocolVersion;//写入服务器版本信息BufferUtil::writeWithNull($buffer,getBytes($this->serverVersion));//写入服务器线程IDBufferUtil::writeUB4($buffer,$this->threadId);//挑战随机数9字节包含一个填充值BufferUtil::writeWithNull($buffer,$this->seed);//服务器能力识别BufferUtil::writeUB2($buffer,$this->serverCapabilities);//1字节字符编码$buffer[]=$this->serverCharsetIndex;//服务器状态BufferUtil::writeUB2($buffer,$this->serverStatus);if($this->serverCapabilities&Capabilities::CLIENT_PLUGIN_AUTH){//服务器能力标志16位BufferUtil::writeUB2($buffer,$this->serverCapabilities);//挑战长度+填充值+挑战随机数$buffer[]=max(13,count($this->seed)+count($this->restOfScrambleBuff)+1);$buffer=array_merge($buffer,[0,0,0,0,0,0,0,0,0,0]);}else{//10字节填充$buffer=array_merge($buffer,self::$FILLER_13);}//+12字节挑战随机数if($this->serverCapabilities&Capabilities::CLIENT_SECURE_CONNECTION){BufferUtil::writeWithNull($buffer,$this->restOfScrambleBuff);}if($this->serverCapabilities&Capabilities::CLIENT_PLUGIN_AUTH){BufferUtil::writeWithNull($buffer,getBytes($this->pluginName));}return$buffer;}当mysql客户端发送登录认证报文,然后登录账号密码验证是swoole/server而不是mysql服务器,所以配置文件server.json中的root和password就是账号mysql客户端请求的和密码,以及swoole/server和mysql服务器互锁需要的账号密码位于数据库的配置中//位于SMProxy/src/SMProxyServerprivatefunctionauth(BinaryPacket$bin,\swoole_server$server,int$fd){//如果数据长度为20,--可以自定义,4-20为密码,以及最后4位不知道怎么办if($bin->data[0]==20){//密码长度为16,确定账户密码$checkAccount=$this->checkAccount($server,$fd,$this->source[$fd]->user,array_copy($bin->data,4,20));if(!$checkAccount){//发送错误消息$this->accessDenied($server,$fd,4);}else{if($server->exist($fd)){//发送OK消息$server->send($fd,getString(OkPacket::$SWITCH_AUTH_OK));}//认证标志设置为真$this->source[$fd]->auth=true;}}elseif($bin->data[4]==14){//序列号等于14if($server->exist($fd)){//不需要身份验证意味着登录$server->send($fd,getString(OkPacket::$OK));}}else{$authPacket=newAuthPacket();//读取消息信息并登录认证消息$authPacket->读取($bin);//确定账户密码$checkAccount=$this->checkAccount($server,$fd,$authPacket->user??'',$authPacket->password??[]);if(!$checkAccount){//密码验证失败if($authPacket->pluginName=='mysql_native_password'){//发送错误消息$this->accessDenied($server,$fd,2);}else{//记录用户数据$this->source[$fd]->user=$authPacket->user;$this->source[$fd]->database=$authPacket->database;//填充$this->source[$fd]->seed=RandomUtil::randomBytes(20);//发送EOF消息$authSwitchRequest=array_merge([254],getBytes('mysql_native_password'),[0],$this->source[$fd]->seed,[0]);如果($server->exist($fd)){$server->send($fd,getString(array_merge(getMysqlPackSize(count($authSwitchRequest)),[2],$authSwitchRequest)));}}}else{//账户正确发送OK消息并记录数据if($server->exist($fd)){$server->send($fd,getString(OkPacket::$AUTH_OK));}$this->source[$fd]->auth=true;$this->source[$fd]->database=$authPacket->database;}}}tcp握手和登录认证成功后,mysql端可以传输执行语句等操作,而这里SMProxy也对语句进行解析,判断是读、写还是东西等,如果是读语句,并且配置了读数据库,那么读语句只会从读池中获取链接。如果没有配置读数据库会获取到写数据库,所以这里使用的时候需要注意一下。如果公司的读库配置低于写库,采用这种架构可能会对读库造成一定的压力。至此,SMProxy的工作大概就完成了。Swoole/server会将mysql客户端发送的执行命令文本发送给mysql服务器,mysql服务器返回的数据将不再过多的被SMProxy处理。又因为客户端是mysql客户端,可以直接解析mysql服务器返回的消息。SMProxy架构的基本流程已经描述完毕,更详细的连接池和mysql消息等处理,我也在代码中注释了,并上传到github。想了解更多的朋友可以下载查看。评论和不是很完美,但是我对大部分的句子都加了自己的看法(有一些误解)https://github.com/linjinmin/...总而言之,SMProxy是一个非常好的框架。SMProxygithub地址:https://github.com/louislivi/...参考文章来源:https://www.cnblogs.com/JsonM...//tcp粘包问题https://www.cnblogs.com/davyg...//mysql协议
