556 lines
16 KiB
Markdown
556 lines
16 KiB
Markdown
[MySQL](https://www.mysql.com/)是著名的开源的关系型数据库,为了使用户更快捷地访问mysql并充分利用bthread的并发能力,brpc直接支持mysql协议。示例程序:[example/mysql_c++](https://github.com/brpc/brpc/tree/master/example/mysql_c++/)
|
||
|
||
**注意**:只支持MySQL 4.1 及之后的版本的文本协议,支持事务,支持Prepared statement。目前支持的鉴权方式为mysql_native_password,使用事务的时候不支持single模式。
|
||
|
||
相比使用[libmysqlclient](https://dev.mysql.com/downloads/connector/c/)(官方client)的优势有:
|
||
|
||
- 线程安全。用户不需要为每个线程建立独立的client。
|
||
- 支持同步、异步、半同步等访问方式,能使用[ParallelChannel等](combo_channel.md)组合访问方式。
|
||
- 支持多种[连接方式](client.md#连接方式)。支持超时、backup request、取消、tracing、内置服务等一系列brpc提供的福利。
|
||
- 明确的返回类型校验,如果使用了不正确的变量接受mysql的数据类型,将抛出异常。
|
||
- 调用mysql标准库会阻塞框架的并发能力,使用本实现将能充分利用brpc框架的并发能力。
|
||
- 使用brpc实现的mysql不会造成pthread的阻塞,使用libmysqlclient会阻塞pthread [线程相关](bthread.md),使用mysql的异步api会使编程变得很复杂。
|
||
# 访问mysql
|
||
|
||
创建一个访问mysql的Channel:
|
||
|
||
```c++
|
||
# include <brpc/policy/mysql/mysql.h>
|
||
# include <brpc/policy/mysql/mysql_authenticator.h>
|
||
# include <brpc/channel.h>
|
||
|
||
brpc::ChannelOptions options;
|
||
options.protocol = brpc::PROTOCOL_MYSQL;
|
||
options.connection_type = FLAGS_connection_type;
|
||
options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/;
|
||
options.max_retry = FLAGS_max_retry;
|
||
options.auth = new brpc::policy::MysqlAuthenticator("yangliming01", "123456", "test",
|
||
"charset=utf8&collation_connection=utf8_unicode_ci");
|
||
if (channel.Init("127.0.0.1", 3306, &options) != 0) {
|
||
LOG(ERROR) << "Fail to initialize channel";
|
||
return -1;
|
||
}
|
||
```
|
||
|
||
向mysql发起命令。
|
||
|
||
```c++
|
||
// 执行各种mysql命令,可以批量执行命令如:"select * from tab1;select * from tab2"
|
||
std::string command = "show databases"; // select,delete,update,insert,create,drop ...
|
||
brpc::MysqlRequest request;
|
||
if (!request.Query(command)) {
|
||
LOG(ERROR) << "Fail to add command";
|
||
return false;
|
||
}
|
||
brpc::MysqlResponse response;
|
||
brpc::Controller cntl;
|
||
channel.CallMethod(NULL, &cntl, &request, &response, NULL);
|
||
if (!cntl.Failed()) {
|
||
std::cout << response << std::endl;
|
||
} else {
|
||
LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText();
|
||
return false;
|
||
}
|
||
return true;
|
||
```
|
||
|
||
上述代码的说明:
|
||
|
||
- 请求类型必须为MysqlRequest,回复类型必须为MysqlResponse,否则CallMethod会失败。不需要stub,直接调用channel.CallMethod,method填NULL。
|
||
- 调用request.Query()传入要执行的命令,可以批量执行命令,多个命令用分号隔开。
|
||
- 依次调用response.reply(X)弹出操作结果,根据返回类型的不同,选择不同的类型接收,如:MysqlReply::Ok,MysqlReply::Error,const MysqlReply::Columnconst MysqlReply::Row等。
|
||
- 如果只有一条命令则reply为1个,如果为批量操作返回的reply为多个。
|
||
|
||
目前支持的请求操作有:
|
||
|
||
```c++
|
||
bool Query(const butil::StringPiece& command);
|
||
```
|
||
|
||
对应的回复操作:
|
||
|
||
```c++
|
||
// 返回不同类型的结果
|
||
const MysqlReply::Auth& auth() const;
|
||
const MysqlReply::Ok& ok() const;
|
||
const MysqlReply::Error& error() const;
|
||
const MysqlReply::Eof& eof() const;
|
||
// 对result set结果集的操作
|
||
// get column number
|
||
uint64_t MysqlReply::column_number() const;
|
||
// get one column
|
||
const MysqlReply::Column& MysqlReply::column(const uint64_t index) const;
|
||
// get row number
|
||
uint64_t MysqlReply::row_number() const;
|
||
// get one row
|
||
const MysqlReply::Row& MysqlReply::next() const;
|
||
// 结果集中每个字段的操作
|
||
const MysqlReply::Field& MysqlReply::Row::field(const uint64_t index) const;
|
||
```
|
||
|
||
# 事务操作
|
||
|
||
事务可以保证在一个事务中的多个RPC请求最终要么都成功,要么都失败。
|
||
|
||
```c++
|
||
rpc::Channel channel;
|
||
// Initialize the channel, NULL means using default options.
|
||
brpc::ChannelOptions options;
|
||
options.protocol = brpc::PROTOCOL_MYSQL;
|
||
options.connection_type = FLAGS_connection_type;
|
||
options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/;
|
||
options.connect_timeout_ms = FLAGS_connect_timeout_ms;
|
||
options.max_retry = FLAGS_max_retry;
|
||
options.auth = new brpc::policy::MysqlAuthenticator(
|
||
FLAGS_user, FLAGS_password, FLAGS_schema, FLAGS_params);
|
||
if (channel.Init(FLAGS_server.c_str(), FLAGS_port, &options) != 0) {
|
||
LOG(ERROR) << "Fail to initialize channel";
|
||
return -1;
|
||
}
|
||
|
||
// create transaction
|
||
brpc::MysqlTransactionOptions options;
|
||
options.readonly = FLAGS_readonly;
|
||
options.isolation_level = brpc::MysqlIsolationLevel(FLAGS_isolation_level);
|
||
auto tx(brpc::NewMysqlTransaction(channel, options));
|
||
if (tx == NULL) {
|
||
LOG(ERROR) << "Fail to create transaction";
|
||
return false;
|
||
}
|
||
|
||
brpc::MysqlRequest request(tx.get());
|
||
if (!request.Query(*it)) {
|
||
LOG(ERROR) << "Fail to add command";
|
||
tx->rollback();
|
||
return false;
|
||
}
|
||
brpc::MysqlResponse response;
|
||
brpc::Controller cntl;
|
||
channel.CallMethod(NULL, &cntl, &request, &response, NULL);
|
||
if (cntl.Failed()) {
|
||
LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText();
|
||
tx->rollback();
|
||
return false;
|
||
}
|
||
// handle response
|
||
std::cout << response << std::endl;
|
||
bool rc = tx->commit();
|
||
```
|
||
|
||
# Prepared Statement
|
||
|
||
Prepared statement对于一个需要执行很多次的SQL语句,它把这个SQL语句注册到mysql-server,避免了每次请求在mysql-server端都去解析这个SQL语句,能得到性能上的提升。
|
||
|
||
```c++
|
||
rpc::Channel channel;
|
||
// Initialize the channel, NULL means using default options.
|
||
brpc::ChannelOptions options;
|
||
options.protocol = brpc::PROTOCOL_MYSQL;
|
||
options.connection_type = FLAGS_connection_type;
|
||
options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/;
|
||
options.connect_timeout_ms = FLAGS_connect_timeout_ms;
|
||
options.max_retry = FLAGS_max_retry;
|
||
options.auth = new brpc::policy::MysqlAuthenticator(
|
||
FLAGS_user, FLAGS_password, FLAGS_schema, FLAGS_params);
|
||
if (channel.Init(FLAGS_server.c_str(), FLAGS_port, &options) != 0) {
|
||
LOG(ERROR) << "Fail to initialize channel";
|
||
return -1;
|
||
}
|
||
|
||
auto stmt(brpc::NewMysqlStatement(channel, "select * from tb where name=?"));
|
||
if (stmt == NULL) {
|
||
LOG(ERROR) << "Fail to create mysql statement";
|
||
return -1;
|
||
}
|
||
|
||
brpc::MysqlRequest request(stmt.get());
|
||
if (!request.AddParam("lilei")) {
|
||
LOG(ERROR) << "Fail to add name param";
|
||
return NULL;
|
||
}
|
||
|
||
brpc::MysqlResponse response;
|
||
brpc::Controller cntl;
|
||
channel->CallMethod(NULL, &cntl, &request, &response, NULL);
|
||
if (cntl.Failed()) {
|
||
LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText();
|
||
return NULL;
|
||
}
|
||
|
||
std::cout << response << std::endl;
|
||
```
|
||
|
||
|
||
|
||
# 性能测试
|
||
|
||
我在example/mysql_c++目录下面写了两个测试程序,mysql_press.cpp mysqlclient_press.cpp,mysql_go_press.go 一个是使用了brpc框架,一个是使用了的libmysqlclient访问mysql,一个是使用[go-sql-driver](https://github.com/go-sql-driver)/**go-mysql**访问mysql
|
||
|
||
启动单线程测试
|
||
|
||
##### brpc框架访问mysql(单线程)
|
||
|
||
./mysql_press -thread_num=1 -op_type=0 // insert
|
||
|
||
```
|
||
qps=3071 latency=320
|
||
qps=3156 latency=311
|
||
qps=3166 latency=310
|
||
qps=3151 latency=312
|
||
qps=3093 latency=317
|
||
qps=3146 latency=312
|
||
qps=3139 latency=313
|
||
qps=3114 latency=315
|
||
qps=3055 latency=321
|
||
qps=3135 latency=313
|
||
qps=2611 latency=376
|
||
qps=3072 latency=320
|
||
qps=3026 latency=324
|
||
qps=2792 latency=352
|
||
qps=3181 latency=309
|
||
qps=3181 latency=309
|
||
qps=3197 latency=307
|
||
qps=3024 latency=325
|
||
```
|
||
|
||
./mysql_press -thread_num=1 -op_type=1
|
||
|
||
```
|
||
qps=6414 latency=151
|
||
qps=5292 latency=182
|
||
qps=6700 latency=144
|
||
qps=6858 latency=141
|
||
qps=6915 latency=140
|
||
qps=6822 latency=142
|
||
qps=6722 latency=144
|
||
qps=6852 latency=141
|
||
qps=6713 latency=144
|
||
qps=6741 latency=144
|
||
qps=6734 latency=144
|
||
qps=6611 latency=146
|
||
qps=6554 latency=148
|
||
qps=6810 latency=142
|
||
qps=6787 latency=143
|
||
qps=6737 latency=144
|
||
qps=6579 latency=147
|
||
qps=6634 latency=146
|
||
qps=6716 latency=144
|
||
qps=6711 latency=144
|
||
```
|
||
|
||
./mysql_press -thread_num=1 -op_type=2 // update
|
||
|
||
```
|
||
qps=3090 latency=318
|
||
qps=3452 latency=284
|
||
qps=3239 latency=303
|
||
qps=3328 latency=295
|
||
qps=3218 latency=305
|
||
qps=3251 latency=302
|
||
qps=2516 latency=391
|
||
qps=2874 latency=342
|
||
qps=3366 latency=292
|
||
qps=3249 latency=302
|
||
qps=3346 latency=294
|
||
qps=3486 latency=282
|
||
qps=3457 latency=284
|
||
qps=3439 latency=286
|
||
qps=3386 latency=290
|
||
qps=3352 latency=293
|
||
qps=3253 latency=302
|
||
qps=3341 latency=294
|
||
```
|
||
|
||
##### libmysqlclient实现(单线程)
|
||
|
||
./mysqlclient_press -thread_num=1 -op_type=0 // insert
|
||
|
||
```
|
||
qps=3166 latency=313
|
||
qps=3157 latency=314
|
||
qps=2941 latency=337
|
||
qps=3270 latency=303
|
||
qps=3305 latency=300
|
||
qps=3445 latency=287
|
||
qps=3455 latency=287
|
||
qps=3449 latency=287
|
||
qps=3486 latency=284
|
||
qps=3551 latency=279
|
||
qps=3517 latency=281
|
||
qps=3283 latency=302
|
||
qps=3353 latency=295
|
||
qps=2564 latency=386
|
||
qps=3243 latency=305
|
||
qps=3333 latency=297
|
||
qps=3598 latency=275
|
||
qps=3714 latency=267
|
||
```
|
||
|
||
./mysqlclient_press -thread_num=1 -op_type=1
|
||
|
||
```
|
||
qps=8209 latency=120
|
||
qps=8022 latency=123
|
||
qps=7879 latency=125
|
||
qps=8083 latency=122
|
||
qps=8504 latency=116
|
||
qps=8112 latency=121
|
||
qps=8278 latency=119
|
||
qps=8698 latency=113
|
||
qps=8817 latency=112
|
||
qps=8755 latency=112
|
||
qps=8734 latency=113
|
||
qps=8390 latency=117
|
||
qps=8230 latency=120
|
||
qps=8486 latency=116
|
||
qps=8038 latency=122
|
||
qps=8640 latency=114
|
||
```
|
||
|
||
./mysqlclient_press -thread_num=1 -op_type=2 // update
|
||
|
||
```
|
||
qps=3583 latency=276
|
||
qps=3530 latency=280
|
||
qps=3610 latency=274
|
||
qps=3492 latency=283
|
||
qps=3508 latency=282
|
||
qps=3465 latency=286
|
||
qps=3543 latency=279
|
||
qps=3610 latency=274
|
||
qps=3567 latency=278
|
||
qps=3381 latency=293
|
||
qps=3514 latency=282
|
||
qps=3461 latency=286
|
||
qps=3456 latency=286
|
||
qps=3517 latency=281
|
||
qps=3492 latency=284
|
||
```
|
||
|
||
##### golang访问mysql(单线程)
|
||
|
||
go run test.go -thread_num=1
|
||
|
||
```
|
||
qps = 6905 latency = 144
|
||
qps = 6922 latency = 143
|
||
qps = 6931 latency = 143
|
||
qps = 6998 latency = 142
|
||
qps = 6780 latency = 146
|
||
qps = 6980 latency = 142
|
||
qps = 6901 latency = 144
|
||
qps = 6887 latency = 144
|
||
qps = 6943 latency = 143
|
||
qps = 6880 latency = 144
|
||
qps = 6815 latency = 146
|
||
qps = 6089 latency = 163
|
||
qps = 6626 latency = 150
|
||
qps = 6361 latency = 156
|
||
qps = 6783 latency = 146
|
||
qps = 6789 latency = 146
|
||
qps = 6883 latency = 144
|
||
qps = 6795 latency = 146
|
||
qps = 6724 latency = 148
|
||
qps = 6861 latency = 145
|
||
qps = 6878 latency = 144
|
||
qps = 6842 latency = 146
|
||
```
|
||
|
||
从以上测试结果看来,使用brpc实现的mysql协议和使用libmysqlclient在插入、修改、删除操作上性能是类似的,但是在查询操作看会逊色于libmysqlclient,查询的性能和golang实现的mysql类似。
|
||
|
||
##### brpc框架访问mysql(50线程)
|
||
|
||
./mysql_press -thread_num=50 -op_type=1 -use_bthread=true
|
||
|
||
```
|
||
qps=18843 latency=2656
|
||
qps=22426 latency=2226
|
||
qps=22536 latency=2203
|
||
qps=22560 latency=2193
|
||
qps=22270 latency=2226
|
||
qps=22302 latency=2247
|
||
qps=22147 latency=2225
|
||
qps=22517 latency=2228
|
||
qps=22762 latency=2176
|
||
qps=23061 latency=2162
|
||
qps=23819 latency=2070
|
||
qps=23852 latency=2077
|
||
qps=22682 latency=2214
|
||
qps=22381 latency=2213
|
||
qps=24041 latency=2069
|
||
qps=24562 latency=2022
|
||
qps=24874 latency=2004
|
||
qps=24821 latency=1988
|
||
qps=24209 latency=2073
|
||
qps=21706 latency=2281
|
||
```
|
||
|
||
##### libmysqlclient实现(50线程)
|
||
|
||
./mysql_press -thread_num=50 -op_type=1 -use_bthread=true
|
||
|
||
```
|
||
qps=23656 latency=378
|
||
qps=16190 latency=555
|
||
qps=20136 latency=445
|
||
qps=22238 latency=401
|
||
qps=22229 latency=403
|
||
qps=19109 latency=470
|
||
qps=22569 latency=394
|
||
qps=26250 latency=343
|
||
qps=28208 latency=318
|
||
qps=29649 latency=301
|
||
qps=29874 latency=301
|
||
qps=30033 latency=301
|
||
qps=25911 latency=345
|
||
qps=28048 latency=317
|
||
qps=27398 latency=329
|
||
```
|
||
|
||
##### golang访问mysql(50协程)
|
||
|
||
go run ../mysql_go_press.go -thread_num=50
|
||
|
||
```
|
||
qps = 23660 latency = 2049
|
||
qps = 23198 latency = 2160
|
||
qps = 23765 latency = 2181
|
||
qps = 23323 latency = 2149
|
||
qps = 14833 latency = 2136
|
||
qps = 23822 latency = 2853
|
||
qps = 20389 latency = 2474
|
||
qps = 23290 latency = 2151
|
||
qps = 23526 latency = 2153
|
||
qps = 21426 latency = 2613
|
||
qps = 23339 latency = 2155
|
||
qps = 25623 latency = 2084
|
||
qps = 23048 latency = 2210
|
||
qps = 20694 latency = 2423
|
||
qps = 23705 latency = 2122
|
||
qps = 23445 latency = 2125
|
||
qps = 24368 latency = 2054
|
||
qps = 23027 latency = 2175
|
||
qps = 24307 latency = 2063
|
||
qps = 23227 latency = 2096
|
||
qps = 23646 latency = 2173
|
||
```
|
||
|
||
以上是启动50并发的查询请求,看上去qps都比较相似,但是libmysqlclient延时明显低。
|
||
|
||
##### brpc框架访问mysql(100线程)
|
||
|
||
./mysql_press -thread_num=100 -op_type=1 -use_bthread=true
|
||
|
||
```
|
||
qps=26428 latency=3764
|
||
qps=26305 latency=3780
|
||
qps=26390 latency=3779
|
||
qps=26278 latency=3787
|
||
qps=26326 latency=3787
|
||
qps=26266 latency=3792
|
||
qps=26394 latency=3773
|
||
qps=26263 latency=3797
|
||
qps=26250 latency=3783
|
||
qps=26362 latency=3782
|
||
qps=26212 latency=3796
|
||
qps=26260 latency=3800
|
||
qps=24666 latency=4035
|
||
qps=25569 latency=3896
|
||
qps=26223 latency=3794
|
||
qps=25538 latency=3890
|
||
qps=20065 latency=4958
|
||
qps=23023 latency=4331
|
||
qps=25808 latency=3875
|
||
```
|
||
|
||
##### libmysqlclient实现(100线程)
|
||
|
||
./mysql_press -thread_num=50 -op_type=1 -use_bthread=true
|
||
|
||
```
|
||
qps=29467 latency=304
|
||
qps=29413 latency=305
|
||
qps=29459 latency=304
|
||
qps=29562 latency=302
|
||
qps=30657 latency=291
|
||
qps=30445 latency=295
|
||
qps=30179 latency=298
|
||
qps=30072 latency=297
|
||
qps=29802 latency=299
|
||
qps=29752 latency=301
|
||
qps=29701 latency=304
|
||
qps=29731 latency=301
|
||
qps=29622 latency=299
|
||
qps=29440 latency=304
|
||
qps=29495 latency=306
|
||
qps=29297 latency=303
|
||
qps=29626 latency=306
|
||
qps=29482 latency=300
|
||
qps=28649 latency=313
|
||
qps=29537 latency=305
|
||
qps=29634 latency=299
|
||
```
|
||
|
||
##### golang访问mysql(100协程)
|
||
|
||
go run ../mysql_go_press.go -thread_num=100
|
||
|
||
```
|
||
qps = 22108 latency = 4553
|
||
qps = 21930 latency = 4536
|
||
qps = 20653 latency = 4906
|
||
qps = 22100 latency = 4443
|
||
qps = 21091 latency = 4850
|
||
qps = 21718 latency = 4600
|
||
qps = 21444 latency = 4488
|
||
qps = 17832 latency = 5859
|
||
qps = 18296 latency = 5378
|
||
qps = 20463 latency = 4963
|
||
qps = 21611 latency = 4880
|
||
qps = 18441 latency = 5424
|
||
qps = 20731 latency = 4834
|
||
qps = 20611 latency = 4837
|
||
qps = 20188 latency = 4979
|
||
qps = 15450 latency = 5723
|
||
qps = 20927 latency = 5328
|
||
qps = 19893 latency = 5027
|
||
qps = 21080 latency = 4782
|
||
qps = 20192 latency = 4970
|
||
```
|
||
|
||
以上是启动100并发的查询请求,看上去qps都比较相似,但是libmysqlclient延时明显低。
|
||
|
||
并发调整到150的时候,mysql-server已经报错"Too many connections"。
|
||
|
||
为什么并发数50或者100的时候libmysqlclient的延时会那么低呢?因为libmysqlclient使用的IO模式为阻塞模式,我们运行的mysql_press和mysqlclient_press都是使用的bthread模式(-use_bthread=true),底层默认都是9个pthread,使用阻塞模式的libmysqlclient和mysql交互的相当于并发度是9个线程,mysql会启动9个线程,使用非阻塞模式的rpc访问mysql并发度相当于100个,mysql会启动100个线程,所以会造成mysql的频繁上线文切换。
|
||
|
||
如果将libmysqlclient的执行方式改为不使用bthread,那么100个线程的执行效果为如下:
|
||
|
||
```
|
||
qps=26919 latency=1927
|
||
qps=27155 latency=2037
|
||
qps=28054 latency=1784
|
||
qps=26738 latency=1856
|
||
qps=27807 latency=1781
|
||
qps=26734 latency=1730
|
||
qps=26562 latency=1939
|
||
qps=27473 latency=1845
|
||
qps=26677 latency=1806
|
||
qps=27369 latency=1948
|
||
qps=27955 latency=1618
|
||
qps=26574 latency=2151
|
||
qps=27343 latency=1777
|
||
qps=26705 latency=1822
|
||
qps=26668 latency=1807
|
||
qps=25347 latency=2104
|
||
qps=26651 latency=1560
|
||
qps=27815 latency=1979
|
||
qps=27221 latency=1762
|
||
qps=26516 latency=2017
|
||
```
|
||
|
||
这个结果就和brpc框架启动100个bthread访问mysql的效果类似了。
|
||
|
||
|
||
|
||
以上为我的一些简单测试,以及一些简单的分析,在低并发的情况下同步IO的效率高于异步IO,可以阅读[IO相关的内容](io.md)有更多解释,后续还将继续分析性能问题,优化协议,给出更多测试。 |