| [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)有更多解释,后续还将继续分析性能问题,优化协议,给出更多测试。 |