文盘Rust -- 安全连接 TiDB/Mysql

rust,安全,连接,tidb,mysql · 浏览次数 : 42

小编点评

**数据库连接与操作** SeaOrm 构建实体比较麻烦,如果不是通过工具手工构建实体比较烧脑;实体中包含各种与其他实体的关系;动态sql 可以通过 sea_query 工具包来构建。 Rbatis 构建实体心智负担就小很多,一张表一个实体;动态 sql 可以通过 HtmlSql 和 PySql 实现,sql 与代码充分解耦。 **数据库关系与操作** ribatis 依赖 rbdc_mysql::options::MySqlConnectOptions,构建 rbdc_mysql::options::MySqlConnectOptions 作为 Rbatis 的衍生项目,显然是要做 rust 生态的JDBC。 从感觉上来讲 SeaOrm 更像 hibernate;而 Rbatis 是复刻 Mybatis。 数据库是应用程序打交道最多的外部资源,相关话题也很多,有机会再和大家聊聊 rust 与 数据库打交道的更多细节。 **数据库连接与操作** 数据库连接与操作,通常会通过 c/c++ driver 进行构建,在编译时失败也是因为在macos上找不到 mysqlclient 导致。 **数据库关系与操作** 数据库关系与操作,通常会通过 sql 与代码进行解耦,例如,动态sql 可以通过 HtmlSql 和 PySql 实现,sql 与代码充分解耦。

正文

作者:京东科技 贾世闻

最近在折腾rust与数据库集成,为了偷懒,选了Tidb Cloud Serverless Tier 作为数据源。Tidb 无疑是近五年来最优秀的国产开源分布式数据库,Tidb Cloud Serverless Tier作为pingcap旗下的云产品方便又经济,这次使用还有一些小惊喜,这个后文再说。

Tidb Cloud Serverless Tier 的使用文档还是很全面的,详细情况请参考使用 TiDB Cloud (Serverless Tier) 构建 TiDB 集群.

集群建立完成后,Tidb Cloud Serverless Tier 有个小功能是可以显示主流客户端以及流行编程语言的连接代码。包括: MysqlCli、MyCli、JDBC、Python、golang以及Nodejs。

ME1678775876781.png

嗯?rust 的代码在哪儿?很遗憾没有rust的代码。而且为了安全起见,Tidb Cloud Serverless Tier 貌似只支持安全连接。在查找文档过程中rust 的 数据库驱动和很多orm文档中也没有关于安全详细的描述,不少思路是在issues里面给出的。索性把rust 连接 mysql 主流方式的安全连接代码都记录下来,一来给自己留个备忘,二来给需要的同学做个提示。

以下实例所使用的的标的建表语句如下

CREATE TABLE IF NOT EXISTS sample (
        id BIGINT NOT NULL ,
        name VARCHAR(128) NOT NULL,
        gender TINYINT NOT NULL,
        mobile VARCHAR(11) NOT NULL,
        create_time DATETIME NOT NULL, 
        update_time DATETIME NOT NULL, 
        PRIMARY KEY(id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

mysql rust driver

rust-mysql-simple,纯 rust 实现的 mysql 驱动。

  • 依赖

    [dependencies]
    # mysql origin
    mysql = "*"
    
    
  • 代码

    use chrono::Local;
    use mysql::prelude::*;
    use mysql::*;
    use rbatis::snowflake::new_snowflake_id;
    use serde::Deserialize;
    use serde::Serialize;
    
    pub const TABLE_NAME: &str = "sample";
    
    #[derive(Clone, Debug, Serialize, Deserialize)]
    pub struct BizOrigin {
        pub id: i64,
        pub name: String,
        pub gender: u8,
        pub mobile: String,
        pub create_time: Option<String>,
        pub update_time: Option<String>,
    }
    
    fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
        let fmt = "%Y-%m-%d %H:%M:%S";
        // 原生方式连接
        let cert_path = std::path::Path::new("/etc/ssl/cert.pem");
        let ssl_opts = SslOpts::default().with_root_cert_path(Some(cert_path));
        let opts = OptsBuilder::new()
            .ip_or_hostname(Some("gateway01.us-east-19.prod.aws.tidbcloud.com"))
            .tcp_port(4000)
            .user(Some("tidbcloudtier.root"))
            .pass(Some("xxxxxxxxxxxx"))
            .ssl_opts(ssl_opts)
            .db_name(Some("test"));
    
        let mut conn_origin = Conn::new(opts)?;
        let (_, cipher_origin): (Value, String) = "SHOW STATUS LIKE 'Ssl_cipher'"
            .first(&mut conn_origin)?
            .unwrap();
        println!(">>>>> Cipher in use from origin: {}", cipher_origin);
    
        let create_statment = format!(
            "
        CREATE TABLE IF NOT EXISTS {} (
            id BIGINT NOT NULL ,
            name VARCHAR(128) NOT NULL,
            gender TINYINT NOT NULL,
            mobile VARCHAR(11) NOT NULL,
            create_time DATETIME NOT NULL, 
            update_time DATETIME NOT NULL, 
            PRIMARY KEY(id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;",
            TABLE_NAME
        );
        conn_origin.query_drop(create_statment)?;
    
        let bizes = vec![
            BizOrigin {
                id: new_snowflake_id(),
                name: "Bob".to_string(),
                gender: 1,
                mobile: "13037777876".to_string(),
                create_time: Some(Local::now().format(fmt).to_string()),
                update_time: Some(Local::now().format(fmt).to_string()),
            },
            BizOrigin {
                id: new_snowflake_id(),
                name: "Jecika".to_string(),
                gender: 0,
                mobile: "13033457876".to_string(),
                create_time: Some(Local::now().format(fmt).to_string()),
                update_time: Some(Local::now().format(fmt).to_string()),
            },
        ];
    
        conn_origin.exec_batch(
            r"insert into sample (id,name,gender,mobile,create_time,update_time) 
        values (:id,:name,:gender,:mobile,:create,:update)",
            bizes.iter().map(|p| -> Params {
                params! {
                    "id"=>p.id,
                    "name"=>p.name.to_owned(),
                    "gender"=>p.gender.to_owned(),
                    "mobile"=>p.mobile.to_owned(),
                    "create"=>p.create_time.as_ref(),
                    "update"=>p.update_time.as_ref()
                }
            }),
        )?;
    
        // Let's select payments from database. Type inference should do the trick here.
        let selected_bizs = conn_origin.query_map(
            "SELECT id,name,gender,mobile,create_time,update_time from sample",
            |(id, name, gender, mobile, create_time, update_time)| BizOrigin {
                id,
                name,
                gender,
                mobile,
                create_time,
                update_time,
            },
        )?;
        println!("selected result {:?}", selected_bizs);
    
        Ok(())
    }
    
    

代码并不复杂,首先创建SslOpts,指定CA文件的位置;然后使用OptsBuilder 生成链接配置信息;最后创建Connection。后面是执行表创建以及验证链接,最后是对标的 insert 和 select 操作。

sqlx

sqlx是纯 Rust 编写的异步 SQL Crate。

  • 依赖

    [dependencies]
    # sqlx
    sqlx = "0.6.2"
    
    
  • 代码

    use futures::TryStreamExt;
    use sqlx::mysql::MySqlPoolOptions;
    
    #[tokio::main]
    async fn main() {
        let sqlx_opts = sqlx::mysql::MySqlConnectOptions::new()
            .host("gateway01.us-east-19.prod.aws.tidbcloud.com")
            .port(4000)
            .database("test")
            .username("tidbcloudtier.root")
            .password("xxxxxxxxxxxx")
            .ssl_ca("/etc/ssl/cert.pem");
    
        let pool = MySqlPoolOptions::new()
            .connect_with(sqlx_opts)
            .await
            .unwrap();  
    
        let mut rows = sqlx::query("select * from sample").fetch(&pool);
        while let Some(row) = rows.try_next().await.unwrap() {
            println!("row is {:?}", row);
        }
    }
    
    

SeaORM

SeaORM是在 sqlx 之上构建的 orm 框架。

  • 依赖

    [dependencies]
    # SeaORM
    sqlx = "0.6.2"
    sea-orm = { version = "0.10.6", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "macros" ] }
    
    
  • 代码

    use sea_orm::ConnectionTrait;
    use sea_orm::DbBackend;
    use sea_orm::SqlxMySqlConnector;
    use sea_orm::{FromQueryResult, Statement as sea_statment};
    use sqlx::MySqlPool;
    
    #[derive(Debug, FromQueryResult)]
    pub struct SeaOrmBiz {
        pub id: i64,
        pub name: String,
        pub gender: Option<i8>,
        pub mobile: String,
        pub create_time: chrono::NaiveDateTime,
        pub update_time: chrono::NaiveDateTime,
    }
    
    #[tokio::main]
    async fn main() {
        let sqlx_opts = sqlx::mysql::MySqlConnectOptions::new()
            .host("gateway01.us-east-19.prod.aws.tidbcloud.com")
            .port(4000)
            .database("test")
            .username("tidbcloudtier.root")
            .password("xxxxxxxxx")
            .ssl_ca("/etc/ssl/cert.pem");
    
        let pool = MySqlPool::connect_with(sqlx_opts).await.unwrap();
        let db = SqlxMySqlConnector::from_sqlx_mysql_pool(pool);
    
        let rs = db
            .execute(sea_statment::from_string(
                db.get_database_backend(),
                "select 1 from dual;".to_string(),
            ))
            .await;
        println!(">>>>> Cipher in use from sea_orm:{:?}", rs);
    
        let biz: Vec<SeaOrmBiz> = SeaOrmBiz::find_by_statement(sea_statment::from_sql_and_values(
            DbBackend::MySql,
            r#"SELECT * FROM sample;"#,
            vec![],
        ))
        .all(&db)
        .await
        .unwrap();
        println!(">>>>> selet rs is {:?}", biz);
    }
    
    

SeaOrm 依赖 sqlx。首先构建 sqlx::MySqlConnectOptions 然后根据 MySqlConnectOptions 构建 sqlx::MySqlPool 最后构建 sea_orm::SqlxMySqlConnector 用于与 mysql 通信。

Rbatis

rbatis

  • 依赖

    [dependencies]
    # rbatis integration
    rbs = "0.1.13"
    rbatis = "4.0.44"
    rbdc-mysql = "0.1.18"
    
    
  • 代码

    use rbatis::rbdc::datetime::FastDateTime;
    use rbatis::Rbatis;
    use rbdc_mysql::options::MySqlConnectOptions;
    use rbdc_mysql::{driver::MysqlDriver, options::MySqlSslMode as rbdc_MysqlSslMode};
    use rbs::to_value;
    use serde::{Deserialize, Serialize};
    use std::collections::HashMap;
    
    pub const TABLE_NAME: &str = "sample";
    
    #[derive(Clone, Debug, Serialize, Deserialize)]
    pub struct BizRbatis {
        pub id: Option<i64>,
        pub name: Option<String>,
        pub gender: Option<u8>,
        pub mobile: Option<String>,
        pub create_time: Option<FastDateTime>,
        pub update_time: Option<FastDateTime>,
    }
    rbatis::crud!(BizRbatis {}, TABLE_NAME);
    
    #[tokio::main]
    async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
        // rbatis 连接
        let rb = Rbatis::new();
        let opt = MySqlConnectOptions::new()
            .host("gateway01.us-east-19.prod.aws.tidbcloud.com")
            .port(4000)
            .database("test")
            .username("tidbcloudtier.root")
            .password("xxxxxxxxxx")
            .ssl_mode(rbdc_MysqlSslMode::VerifyIdentity)
            .ssl_ca("/etc/ssl/cert.pem");
        rb.init_opt(MysqlDriver {}, opt).unwrap();
        rb.get_pool().unwrap().resize(3);
    
        let sql_show_ssl_cipher = "SHOW STATUS LIKE 'Ssl_cipher'";
    
        let cipher_rbatis = rb
            .fetch_decode::<Vec<HashMap<String, String>>>(sql_show_ssl_cipher, vec![])
            .await;
    
        println!(">>>>> Cipher in use from rbatis: {:?}", cipher_rbatis);
    
        let sql_select_one = format!("select * from {} limit ?;", TABLE_NAME);
        let row = rb
            .fetch_decode::<BizRbatis>(&sql_select_one, vec![to_value!(1)])
            .await;
        println!(">>>>> rbatsis select result={:?}", row);
    
        Ok(())
    }
    
    

首先,新建一个Rbatis struct;构建 rbdc_mysql::options::MySqlConnectOptions (rbdc 相当于java体系里的jdbc,是rbatis的衍生项目);最后通过配置好的 rbdc_mysql::options::MySqlConnectOptions 初始化 Rbatis。

后记

在这次实验中笔者也试图使用Diesel建立 mysql 安全连接,不过在编译的时候失败,未入门先放弃。Diesel 由于开发时间久远,彼时各个数据库的 rust 原生驱动缺失,所以大量才用 c/c++ driver进行构建,这次编译失败也是因为在macos上找不到 mysqlclient 导致。有对 Diesel 强依赖的同学可以继续探索。 再来说说对 SeaOrm 和 Rbatis 的直观感受。SeaOrm 构建实体比较麻烦,如果不是通过工具手工构建实体比较烧脑;实体中包含各种与其他实体的关系;动态sql 可以通过 sea_query 工具包来构建。Rbatis 构建实体心智负担就小很多,一张表一个实体;动态 sql 可以通过 HtmlSql 和 PySql 实现,sql 与代码充分解耦。rbdc 作为 Rbatis 的衍生项目,显然是要做 rust 生态的JDBC。从感觉上来讲 SeaOrm 更像 hibernate;而 Rbatis 是复刻 Mybatis。 数据库是应用程序打交道最多的外部资源,相关话题也很多,有机会再和大家聊聊 rust 与 数据库打交道的更多细节。

咱们下期见。

与文盘Rust -- 安全连接 TiDB/Mysql相似的内容:

文盘Rust -- 安全连接 TiDB/Mysql

最近在折腾rust与数据库集成,选了Tidb Cloud Serverless Tier 作为数据源。Tidb 无疑是近五年来最优秀的国产开源分布式数据库,Tidb Cloud Serverless Tier作为pingcap旗下的云产品方便又经济,这次使用还有一些小惊喜。

文盘Rust -- 把程序作为守护进程启动

当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服。今天我们就来聊聊这个事儿。 最早大家部署应用的通常操作是 “nohup xxxx &”,别说像weblogic 或者其他java 容器有启动脚本,里面其实也差不多;很喜欢 ngi

文盘Rust -- r2d2 实现redis连接池

作者:贾世闻 我们在开发应用后端系统的时候经常要和各种数据库、缓存等资源打交道。这一期,我们聊聊如何访问redis 并将资源池化。 在一个应用后端程序访问redis主要要做的工作有两个,单例和池化。 在后端应用集成redis,我们主要用到以下几个crate:​ ​once_cell​​​、​ ​re

文盘Rust -- rust 连接云上数仓 starwift

最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好京东云发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番。 Starwfit 是京东云自主研发的新一代云原生数据仓库,通过存算分离降低了存储成本,同时兼具性能和扩展弹性。其写入和查询速度可达到传统数据仓库的数倍,为用户提供实时数据分析能力。广泛应用于流量分析、精准营销、用户画像、广

文盘Rust -- 领域交互模式如何实现

书接上文,上回说到如何通过interactcli-rs四步实现一个命令行程序。但是 shell 交互模式在有些场景下用户体验并不是很好。比如我们要连接某个服务,比如 mysql 或者 redis 这样的服务。如果每次交互都需要输入地址、端口、用户名等信息,交互起来太麻烦。通常的做法是一次性输入和连接相关的信息或者由统一配置文件进行管理,然后进入领域交互模式,所有的命令和反馈都和该领域相关。inte

文盘Rust -- 本地库引发的依赖冲突

clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs 。两个库在单独使用时没有任何问题,但是,在同一工程同时引用时会报错。本篇内容主要讲解如何用rust语言解决本地库引发的依赖冲突问题

文盘Rust -- FFI 浅尝

rust FFI 是rust与其他语言互调的桥梁,通过FFI rust 可以有效继承 C 语言的历史资产。本期通过几个例子来聊聊rust与C 语言交互的具体步骤

文盘Rust -- Mutex解决并发写文件乱序问题

在实际开发过程中,我们可能会遇到并发写文件的场景,如果处理不当很可能出现文件内容乱序问题。下面我们通过一个示例程序描述这一过程并给出解决该问题的方法。

文盘Rust -- tokio绑定cpu实践

tokio 是 rust 生态中流行的异步运行时框架。在实际生产中我们如果希望 tokio 应用程序与特定的 cpu core 绑定该怎么处理呢?这次我们来聊聊这个话题。

文盘Rust -- 生命周期问题引发的 static hashmap 锁

2021年上半年,撸了个rust cli开发的框架,基本上把交互模式,子命令提示这些cli该有的常用功能做进去了。项目地址:[https://github.com/jiashiwen/interactcli-rs。](https://github.com/jiashiwen/interactcli-