• Go web 开发数据库管理平台,利用远程过程调用(RPC)实现对MySQL数据库的管理和使用


    Go web 开发数据库管理平台,利用远程过程调用(RPC)实现对MySQL数据库的管理和使用

    前言

    DBA,最基本的工作就是需要管理公司的数据库系统。工作中,常常需要维护的数据库数量是非常多的。小公司可能有个几十套,上百套。大一点的公司,甚至可能有上万的数据库实例。对于数据库,如果我们不通过开发或者购买些第三方管理平台,维护工作是一个非常繁重的事情。

    因为这个初衷,笔者想开启数据库云平台开发这个项目。

    借开发过程中的几个具体问题的思考和实现,分享一些常见的技术问题。

    本文主要分享,在go语言中,如何使用grpc框架去开发对MySQL数据库的操作等相关功能。

    rpc框架介绍

    RPC是(Remote Procedure Call)的简称,英文直译过来就是"远程的过程调用",可以理解为有不同的两个运行操作系统的服务器A和服务器B,从服务器A可以调用服务器B的某些程序的方法,反过来B调用A也是一样的。那么,被调用的一方,我们称它为服务端,调用的一方,也就是命令请求方,我们称它为客户端。

    如何实现rpc功能呢?

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBLwTCww-1661345390797)(https://grpc.io/img/landing-2.svg)]

    gRPC 是一个现代开源的高性能远程过程调用 (RPC) 框架,在微服务,移动通信,浏览器等场景中有丰富的应用。其丰富的开发语言支持,双向流式传输,身份验证,负载均衡和健康检查都是非常亮眼的功能。

    在数据库领域,TiDB的源码中,便用到了大量的gRPC实现不同的功能节点之间的通信。

    在众多的面试题中,有大佬也会去问gRPC跟HTTP协议的区别,以及为什么gRPC有更好的性能等,gRPC也是一个面试的热点和难点。

    gRPC还是一个CNCF的一个孵化项目,在云原生方向有大量的应用。

    关于它的更多的介绍,可以参考grpc官网 https://grpc.io/about/

    如何使用gRPC

    gRPC通过protobuf实现对数据的传输,关于protobuf的使用,主要是下载安装protobuf的二进制文件,可以参考七米老师的blog

    https://www.liwenzhou.com/posts/Go/Protobuf3-language-guide-zh/

    七米老师是一个非常优秀的开发者,同时他也是老男孩教育的go语言讲师,笔者的go语言课程就是学自七米老师的bilibili课程。

    以上为基本的知识储备,后面是本节的主要内容,我们通过gRPC实现对MySQL的使用操作。

    通过gRPC操作MySQL

    1、实现各种备份

    定义通信消息

    首先我们要定义一个MySQLBackUP的server端,将其部署在MySQL所在的节点上。

    通过proto3定义grpc通信的message

    首先是一个BackUpRequest的请求消息

    message BackupTaskRequest {
      MySQLConn MySQLConn = 1;    // 备份MySQL的连接信息
      BackUpType BackUpType = 2;  // 备份类型
    }
    
    message MySQLConn {
      string MySQLUser = 1;
      string MySQLUserpasswd = 2;
      string MySQLHost = 3;
      uint32 MySQLPort = 4;
    }
    
    message BackUpType{
      enum Types {
        // 物理备份全备
        FullBackUpWithXtra = 0;
        // 物理备份增备 -- 留个标志 一般不用
        IncrBackUpWithXtra = 1;
        // 逻辑备份 需要先落地再上传 这种选择节点的时候 看是否找几台中间机器
        // 逻辑备份使用mydmper 全备
        FullBackUpWithMydumper = 2;
        // 逻辑备份使用mudmper 单表
        SingleTableBackUpWithMydumper = 3;
        // 有些操作系统环境太次了,不支持安装mydumper 使用mysqldump
        FullBackUpWithMySQLDump = 4;
        SingleTableBackUpWithMySQLDump = 5;
      }
      Types Type = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    有了备份的请求消息之后,还需要有一个返回消息来告诉我们任务是否成功,或者作为异步消息,不关心是否成功,通过备份的任务脚本来往其他地方记录备份执行情况

    message BackupTaskResponse{
      string MessageInfo = 1;
      string MessageWarn = 2;
    }
    
    • 1
    • 2
    • 3
    • 4

    接着我们定义备份服务的核心,即备份服务

    service MySQLBackupService{
      rpc NewBackup(BackupTaskRequest) returns (BackupTaskResponse){};
    }
    // 这个代码的意思是我们这个备份服务,接收一个备份任务的请求体和返回一个备份服务的返回体,这两个的内容为我们上方定义的消息的内容
    
    • 1
    • 2
    • 3
    • 4

    有了定义消息之后,我们可以通过proto3工具来为我们生成自动化的代码

    protoc --go_out=../pb --go_opt=paths=source_relative \
      --go-grpc_out=../pb --go-grpc_opt=paths=source_relative ./*.proto
    # 这个的意思是我们的protoc命令行工具会把我们当前目录下的所有的.proto文件编译成go语言的代码
    
    • 1
    • 2
    • 3

    之后我们发现在当前同级目录的pd文件夹下,生成了如下文件

    mysqlbackup.pb.go
    mysqlbackup_grpc.pb.go
    
    • 1
    • 2

    他已经帮我们把相关的消息都整理成go代码了,举例来看这个备份任务请求结构体

    type BackupTaskRequest struct {
    	state         protoimpl.MessageState
    	sizeCache     protoimpl.SizeCache
    	unknownFields protoimpl.UnknownFields
    
    	WorkVm          *WorkVm          `protobuf:"bytes,1,opt,name=WorkVm,proto3" json:"WorkVm,omitempty"`
    	MySQLConn       *MySQLConn       `protobuf:"bytes,2,opt,name=MySQLConn,proto3" json:"MySQLConn,omitempty"`
    	SaasDBMySQLConn *SaasDBMySQLConn `protobuf:"bytes,3,opt,name=SaasDBMySQLConn,proto3" json:"SaasDBMySQLConn,omitempty"`
    	BackUpType      *BackUpType      `protobuf:"bytes,4,opt,name=BackUpType,proto3" json:"BackUpType,omitempty"`
    	RemoteStorageS3 *RemoteStorageS3 `protobuf:"bytes,5,opt,name=RemoteStorageS3,proto3" json:"RemoteStorageS3,omitempty"`
    	BackUpTimeout   uint32           `protobuf:"varint,6,opt,name=BackUpTimeout,proto3" json:"BackUpTimeout,omitempty"`
    	DomainId        uint32           `protobuf:"varint,7,opt,name=DomainId,proto3" json:"DomainId,omitempty"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    源码生成之后,建议不要手动修改,如果发现有不合适的定义消息,可以修改对应的.proto文件并重新生成一次代码。

    服务端的实现

    grpc有四种类型的同步模式,分为:无任何流传输 ,服务端到客户端单项流传输,客户端到服务端单项流传输,双向流传输

    备份任务,我们只需要通过一个任务下发,所以不需要任何流传输,

    根据自动化代码备注,通过声明一个不需要任何流传输的服务

    type BackUpServer struct {
    	pb.UnimplementedMySQLBackupServiceServer
    }
    
    • 1
    • 2
    • 3

    之后我们可以定义这个结构体的指针接收类型的func,这个类型的方法名称,我们可以从mysqlbackup_grpc.pb.go文件的最后几个func中得到他的名称,比如

    func _MySQLBackupService_NewBackup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    	in := new(BackupTaskRequest)
    	if err := dec(in); err != nil {
    		return nil, err
    	}
    	if interceptor == nil {
    		return srv.(MySQLBackupServiceServer).NewBackup(ctx, in)
    	}
    	info := &grpc.UnaryServerInfo{
    		Server:     srv,
    		FullMethod: "/hello_grpc.MySQLBackupService/NewBackup",
    	}
    	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
    		return srv.(MySQLBackupServiceServer).NewBackup(ctx, req.(*BackupTaskRequest))
    	}
    	return interceptor(ctx, in, info, handler)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    于是我们定义如下类型的备份函数,这个函数要求第一个参数是上下文ctx,它同时接受一个BackupTaskRequest,并返回BackupTaskResponse

    func (server *BackUpServer) NewBackup(con context.Context, req *pb.BackupTaskRequest) (*pb.BackupTaskResponse, error) {
    // 相关消息的内容可以通过Get开头的这几个函数得到
    	backupType := req.GetBackUpType()
      mysqlConn := req.GetMySQLConn()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    由于server部署在mysql所在节点,所以可以直接调用所在节点的命令行工具
    此时我们可以声明一个taskjob的任务,根据备份类型分别选择不同的备份方式
    taskjob通过定义BackUpJob方法来实现,需要注意的是,我们要保护数据库不能同时发起多个备份任务,所以这里可以通过go的metux锁来保护下任务顺序。

    func (server *BackUpServer) BackupJob(ctx context.Context, j *backjobmetadata, db *gorm.DB) error {
    	// 加锁 defer unlock 这里必须加一个lock,否则可能出现多个备份进程存在的情况 不允许 同时有不同类型的备份任务
    	// TODO 发起备份的时候,先检查 实例的状态是否是 available ,再决定是否可以进行备份
    	onlyOneBackJobRun.Lock()
    	defer onlyOneBackJobRun.Unlock()
    
    	if j.BackUpType.Type == pb.BackUpType_FullBackUpWithXtra {
    	} else if j.BackUpType.Type == pb.BackUpType_IncrBackUpWithXtra {
    
    	} else if j.BackUpType.Type == pb.BackUpType_FullBackUpWithMydumper {
    
    	} else if j.BackUpType.Type == pb.BackUpType_SingleTableBackUpWithMydumper {
    
    	} else if j.BackUpType.Type == pb.BackUpType_FullBackUpWithMySQLDump {
    		return server.UseMysqlDump(ctx, j, db) // 后面可以走自己的实现方式来调度这个任务
    	} else if j.BackUpType.Type == pb.BackUpType_SingleTableBackUpWithMySQLDump {
    	}
    	return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    此时,备份的流程,基本已经有了,需要把我们的备份服务在main函数里开启grpc服务,并把任务声明出来

    func main() {
    	// openssl生成SAN证书
    	//https://www.cnblogs.com/outsrkem/p/16056756.html
    	creditsServeKey := "/Users/anderalex/go/src/workgrpc/certify/server.key"
    	creditsServeCrt := "/Users/anderalex/go/src/workgrpc/certify/server.crt"
    	creds, _ := credentials.NewServerTLSFromFile(creditsServeCrt, creditsServeKey)
    	s := grpc.NewServer(grpc.Creds(creds))
    	listen, err := net.Listen("tcp", ":3000")
    	if err != nil {
    		log.Panic("xxxxxx")
    	}
    
    	/* 备份服务 */
    	pb.RegisterMySQLBackupServiceServer(s, &BackUpServer{})
      /* ... 其他任务服务 ... */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这里我是用的是x509 ca证书的方式来实现客户端跟服务端端加密认证

    客户端的实现

    客户端的实现相对较简单,我们只需要将通信的方法通过grpc给它声明出来,然后把请求消息和接收返回消息的功能实现

    func main() {
    	creditsServePem := "/Users/anderalex/go/src/workgrpc/certify/server.crt"
    	commandName := "example.server.com"
    	creds, _ := credentials.NewClientTLSFromFile(creditsServePem, commandName)
    
    	conn, err := grpc.Dial("127.0.0.1:3000", grpc.WithTransportCredentials(creds))
    	if err != nil {
    		fmt.Println(err)
    	}
    	client := pb.NewMySQLBackupServiceClient(conn)
    	ctx, cancle := context.WithTimeout(context.Background(), time.Second)
    	defer cancle()
    	// 唯一要做的就是我们需要把请求消息和接收返回消息的功能实现
    	res, err := client.NewBackup(ctx, NewBackupTaskRequest(vmip, host, user, passwd, port ,100, pb.BackUpType_FullBackUpWithMySQLDump))
    
    	if err != nil {
    		fmt.Println(err)
    	}
    // 处理req的返回消息
      fmt.Println(res.GetMessageInfo())
    }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    分别运行服务端,客户端,观察是否符合预期

  • 相关阅读:
    spring boot项目中使用nacos作为配置中心
    SpringBoot定义优雅全局统一Restful API 响应框架六
    Linux篇19多线程第三部分
    vue3中使用better-scroll
    tcp三次握手的一些疑问
    Nacos注册中心和服务消费方式
    POS系统完整体系的介绍 Pos终端主密钥MK、DUKPT、PEK、DEK、MEK、TUSN的含义 ---安全行业基础篇7
    Springboot整合Websocket(推送消息通知)
    9、学习MySQL DELETE 语句
    HTTPS 的加密过程
  • 原文地址:https://blog.csdn.net/qq_39570637/article/details/126512555