文盘Rust -- 安全连接 TiDB/Mysql
作者:京东科技 贾世闻
最近在折腾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。
嗯?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
依赖
[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 与 数据库打交道的更多细节。
咱们下期见。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
震惊,一行MD5居然让小伙伴都回不了家!!!
作者:京东零售 付伟 1. 前言 大家好,当你点开这篇文章的时候也许心想是哪个 XX 小编混到这里,先不要着急扔臭鸡蛋,本文是一篇标准(正经)的问题复盘文章。好了,一行MD5居然让小伙伴下不了班,到底是什么问题呢,让我们一起来看看吧。 2. 正文 2.1 需求是什么 这里不再介绍具体的业务。简而言之,有两个接口(查询、确认)对前端页面提供服务。 查询接口返回的数据依赖于本地数据与外部接口计算后的结果,也就是页面展示的是数据快照。确认接口是按照页面的展示结果请求外部接口。 考虑到用户打开展示页面时的数据与提交操作可能间隔很久,实际请求时结果已发生变化,而这种操作会影响业务结果。因此在提交时会进行一次 check,如果发现数据发生变化需要提示页面进行刷新。 为了方便大家理解,我简单的画了个图,毕竟上面太啰嗦了。 查询接口 确认接口 虽然这个图有点草率,但是相信看到这里的小伙伴(默认都是聪明的)都对需求了然于胸了。 2.2 我怎么搞得 掰扯了半天,我们的主角MD5还没有出场,别着急风雨总在彩虹后。 可以看出,这里需要前端将查询接口的返回值重新组装作为确认接口的入参。而后端需要再次走数据聚合的...
- 下一篇
JRC Flink流作业调优指南
作者:京东物流 康琪 本文综合Apache Flink原理与京东实时计算平台(JRC)的背景,详细讲述了大规模Flink流作业的调优方法。通过阅读本文,读者可了解Flink流作业的通用调优措施,并应用于生产环境。 写在前面 Apache Flink作为Google Dataflow Model的工业级实现,经过多年的发展,如今已经成为流式计算开源领域的事实标准。它具有高吞吐、低时延、原生流批一体、高一致性、高可用性、高伸缩性的特征,同时提供丰富的层级化API、时间窗口、状态化计算等语义,方便用户快速入门实时开发,构建实时计算体系。 古语有云,工欲善其事,必先利其器。要想让大规模、大流量的Flink作业高效运行,就必然要进行调优,并且理解其背后的原理。本文是笔者根据过往经验以及调优实践,结合京东实时计算平台(JRC)背景产出的面向专业人员的Flink流作业调优指南。主要包含以下四个方面: TaskManager内存模型调优 网络栈调优 RocksDB与状态调优 其他调优项 本文基于Flink 1.12版本。阅读之前,建议读者对Flink基础组件、编程模型和运行时有较深入的了解。 01 T...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2全家桶,快速入门学习开发网站教程
- MySQL8.0.19开启GTID主从同步CentOS8
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS6,7,8上安装Nginx,支持https2.0的开启