ProxySQL 源码探析

ProxySQL 架构介绍

多级配置系统介绍

ProxySQL 是一个拥有复杂多级配置系统(Multi layer configuration system),但是易于使用的 Proxy。正是因为有一个复杂的多级配置系统,所以,可以满足以下需求

  • 允许对配置进行简单的动态更新,允许业务零停机时间配置
  • 允许动态修改尽可能多的配置项,且无需重启 ProxySQL 进程
  • 可以实现回滚无效配置

**Note:**配置层分为以下三个层级

 +-------------------------+
 |         RUNTIME         |
 +-------------------------+
       /|\          |
        |           |
    [1] |       [2] |
        |          \|/
 +-------------------------+
 |         MEMORY          |
 +-------------------------+ 
       /|\          |      |\
        |           |        \
    [3] |       [4] |         \ [5]
        |          \|/         \
 +-------------------------+  +-------------------------+
 |          DISK           |  |       CONFIG FILE       |
 +-------------------------+  +-------------------------+

  • RUNTIME 代表 ProxySQL 运行时,其线程使用的内存中的数据结构。运维人员是不能直接休干 RUNTIME配置部分的内容,只能通过底层然后去 load 配置。RUNTIME 变量(runtime variables)包括:
    • global variables 中的实际值
    • hostgroups 中的 后端服务(backend servers)的实际使用值
    • mysql_users 中可以链接到 ProxySQL 和 backend servers 的用户信息
  • MEMORY(也可以表示为 main),代表通过 MySQL 兼容的公开接口的内存数据库,运维人员可以通过 MySQL Client 连接到 ProxySQL 的管理界面以查询各种 ProxySQL 的配置参数等信息。通过该接口可配置的表是:
    • mysql_servers: ProxySQL连接到的后端服务器列表
    • mysql_users: 连接到ProxySQL的用户及其认证列表。ProxySQL也将使用相同的认证信息连接到后端服务器。
    • mysql_query_rules: 将流量路由到各种后端服务器时评估的查询规则列表,读写分离将依赖此配置
    • global_variables: ProxySQL 使用的全局变量以及变量的值
  • DISK & CONFIG FILE
    • DISK: 磁盘上的存于 SQLite3 的数据。重启 ProxySQL 时,内存中(RUNTIME+MEMORY)配置将丢失。
    • CONFIG FILE: 配置文件,默认在 /etc/proxysql.cnf

ProxySQL 实例生命周期

启动

正常启动时,会通过该命令 proxysql -c /etc/proxysql.cnf 读取配置文件,确定 datadir

所以,datadir 是否存在将产生根本差别:

  • datadir 不存在:
    解析配置文件,并将内容加载到内存数据库(in-memory database),同时将其保存在磁盘数据库(on-disk database)中,运行时加载
  • datadir 存在:
    ProxySQL将从持久存储的磁盘数据库初始化其内存中配置。因此,磁盘被加载到内存中,然后传播到RUNTIME。
初始化启动方式

使用 --initial 标识,或者,datadir 不存在 的时候,都会解析配置文件,并加载到内存,并传播到RUNTIME。

重载启动

使用 --reload 标识,会尝试 merge CONFIG FILE 和 DISK 的配置,如果有冲突会报错。

在 RUNTIME 修改配置

修改 RUNTIME 相关 tables 后,通过支持的命令集合进行 load configuration。详尽方法参见下一节

修改配置后传到其他配置层

修改方法可以是从 DISK TO RUNTIME/MEMORY 或者 MEMORY TO RUNTIME/DISK ,这里只介绍如何从 MEMORY TO RUNTIME/DISK。

LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL USERS TO DISK;

LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;

LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;

LOAD MYSQL VARIABLES TO RUNTIME;
SAVE MYSQL VARIABLES TO DISK;

LOAD ADMIN VARIABLES TO RUNTIME;
SAVE ADMIN VARIABLES TO DISK;

ProxySQL 配置

开始此章节的时候,假定 proxysql 以及 后端 MySQL 主从服务( backend service) 已经安装完毕。如果 ProxySQL 安装有问题,参考[下节](#ProxySQL 源码探析)。接下来开始 ProxySQL 配置(均采用默认配置项)。

配置 Backend Service

  • 根据 proxysql.cnf 默认给定的 monitor 账户(监管 backend service 状态)在 backend service 建立相关监控账户。
mysql -uroot -hbackend_master_ip --prompt='backend_service> '
backend_service> CREATE USER 'monitor'@'%' IDENTIFIED BY 'monitor';
backend_service> GRANT USAGE ON *.* TO 'monitor'@'%';

配置 ProxySQL

  • 同步 backend service users 到 ProxySQL 的 mysql_users 表中。后端以 MySQL 5.7.20-18 为例:
mysql -uroot -hbackend_master_ip --prompt='backend_service> '
backend_service> select User, authentication_string from mysql.user where host not in ('127.0.0.1', 'localhost', '::1')";

-- 得到下面两行数据,将用户信息插入到 ProxySQL 的 mysql_users 表中。

+-------+-------------------------------------------+
| User  | authentication_string                     |
+-------+-------------------------------------------+
| test  | *94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29 |
| admin | *4ACFE3202A5FF5CF467898FC58AAB1D615029441 |
+-------+-------------------------------------------+

mysql -uadmin -padmin  -h127.0.0.1 -P6032 --prompt='proxysql> '
proxysql> insert into mysql_users(username, password) values('test', '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29'), ('admin', '*4ACFE3202A5FF5CF467898FC58AAB1D615029441');

proxysql> LOAD MYSQL USERS TO RUNTIME;

proxysql> SAVE MYSQL USERS TO DISK;

  • 配置 mysql_services
mysql -uadmin -padmin  -h127.0.0.1 -P6032 --prompt='proxysql> '
proxysql> select * from mysql_servers;
+--------------+---------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
| hostgroup_id | hostname      | port | gtid_port | status | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment |
+--------------+---------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
| 1            | 192.168.0.250 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 20                  | 1       | 0              |         |
| 0            | 192.168.0.250 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 20                  | 1       | 0              |         |
| 1            | 192.168.0.249 | 3306 | 0         | ONLINE | 4      | 0           | 1000            | 20                  | 1       | 0              |         |
+--------------+---------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+

Note:

  • max_replication_lag 表示复制延迟超过该值后,将把 该 hostgroup_id 设置为 shunning 状态。max_replication_lag 设置为 0 则不会检测延迟落后。
  • 写IP 设置到 hostgroup_id 为 读组别 中是防止 从库 shunning 状态时,访问失败。但是,需要调低 weight。
  • max_connections 表示该 backend 允许最大链接数。建议和 backend service 的 max_connections 值相同。
  • mysql_replication_hostgroups
proxysql> select * from runtime_mysql_replication_hostgroups;
+------------------+------------------+-----------------+---------+
| writer_hostgroup | reader_hostgroup | check_type      | comment |
+------------------+------------------+-----------------+---------+
| 0                | 1                |   read_only     |         |
+------------------+------------------+-----------------+---------+

Note: 该表填写后,通过 monitor 账户去获取该值,会以 read_only 的值为 读/写 库判断依据,将 mysql_servers 的 hostname 对应的 hostgroup_id 设置为 0 或 1。建议采用 LVS 分配 VIP,方便管理。

ProxySQL 读写分离

ProxySQL 是通过自定义sql路由规则就可以实现读写分离。定义路由规则,如:除select * from tb for update的select全部发送到slave,其他的的语句发送到master。

  • mysql_query_rules
proxysql> select rule_id,active,match_digest,destination_hostgroup,apply from mysql_query_rules;
+---------+--------+----------------------+-----------------------+-------+
| rule_id | active | match_digest         | destination_hostgroup | apply |
+---------+--------+----------------------+-----------------------+-------+
| 1       | 1      | ^SELECT.*FOR UPDATE$ | 0                     | 1     |
| 2       | 1      | ^SELECT              | 1                     | 1     |
+---------+--------+----------------------+-----------------------+-------+

ProxySQL V2.0.2 源码探析

源码是基于 C/C++ 编写的。Makefile 是硬编码,所以,需要 make && sudo make install 将命令安装到 /usr/bin/proxysql。

源代码组织结构

目录作用
deps依赖,如lz4,ssl,jemalloc 等
includeProxySQL 代码包含的所有头文件
lib有所有 ProxySQL 生成 libproxysql.a 和ProxySQL 核心 API
srcProxySQL Main 调用

src/

  • src/proxysql_global.cpp:
#define PROXYSQL_EXTERN
#include "proxysql.h"
//#include "proxysql_glovars.hpp"
#include "cpp.h"
//ProxySQL_GlobalVariables GloVars;

proxysql_global.cpp 定义了 PROXYSQL_EXTERN,对应 include/proxysql_structs.h:

583:#ifdef PROXYSQL_EXTERN
...
  • main.cpp:

(1) 定义了用于返回定义为 plugin 的全部变量
(2) 内存管理机制

const char *malloc_conf = "xmalloc:true,lg_tcache_max:16,purge:decay,prof:true,prof_leak:true,lg_prof_sample:20,lg_prof_interval:30,prof_active:false";

  • xmalloc:true --> Abort-on-out-of-memory enabled
  • lg_tcache_max:16 --> Maximum size class to cache in the thread-specific cache 2^16=64KB
  • prof_leak:true --> Leak reporting enabled
  • prof:true --> Memory profiling enabled
  • lg_prof_sample:20 --> sample interval 1MB, decreases the computational overhead.

(3) 定义 mysql_worker_thread_func() ,每个 worker_thread 都会调用

void * mysql_worker_thread_func(void *arg) {

// __thr_sfp=l_mem_init();
// 监听 TCP 和 Unix socket
pthread_attr_t thread_attr;
size_t tmp_stack_size=0;
if (!pthread_attr_init(&thread_attr)) {
if (!pthread_attr_getstacksize(&thread_attr , &tmp_stack_size )) {
__sync_fetch_and_add(&GloVars.statuses.stack_memory_mysql_threads,tmp_stack_size);
}
}

proxysql_mysql_thread_t *mysql_thread=(proxysql_mysql_thread_t *)arg;
MySQL_Thread *worker = new MySQL_Thread();
mysql_thread->worker=worker;
worker->init();
// worker->poll_listener_add(listen_fd);
// worker->poll_listener_add(socket_fd);
// 等待所有 load_ 开头的函数
sync_fetch_and_sub(&load,1);
do { usleep(50); } while (load
);

worker->run();
//delete worker;

// worker->run() 函数可能会收到 shutdown = 1 ,如果不删除会破坏内存分配。
delete worker;
// l_mem_destroy(__thr_sfp);
__sync_fetch_and_sub(&GloVars.statuses.stack_memory_mysql_threads,tmp_stack_size);
return NULL;
}

// num_threads 由 参数 mysql-threads 的值决定,default: 4
for (i=0; inum_threads; i++) {
GloMTH->create_thread(i,mysql_worker_thread_func, false);


(4) 优先读配置文件,然后在读 datadir
```cpp
void ProxySQL_Main_process_global_variables(int argc, const char **argv) {
...
   if (GloVars.confFile->OpenFile(GloVars.config_file) == true) {}
...
    if (GloVars.__cmd_proxysql_datadir==NULL) {
       // datadir was not specified , try to read config file
        if (GloVars.configfile_open==true) {...}
     }
   else {
       GloVars.datadir=GloVars.__cmd_proxysql_datadir;
   }

include/

  • proxysql_struct.h

定义了 global variables

lib/

  • mysql_session.cpp

实现 mysql_session.h, 基于一个 session 状态确定执行路径。抽取 mysql_errcode 进行分类分析,并决定如何处理该链接。

switch (myerr) {
case 1317:  // Query execution was interrupted
    if (killed==true) { // this session is being kiled
        handler_ret = -1;
        return handler_ret;
    }
    if (myds->killed_at) {
        // we intentionally killed the query
        break;
    }
case 1290: // read-only
case 1047: // WSREP has not yet prepared node for application use
case 1053: // Server shutdown in progress
    myconn->parent->connect_error(myerr);
    if (myds->query_retries_on_failure > 0) {
        myds->query_retries_on_failure--;
        if ((myds->myconn->reusable==true) && myds->myconn->IsActiveTransaction()==false && myds->myconn->MultiplexDisabled()==false) {
            retry_conn=true;
            proxy_warning("Retrying query.\n");
        }
    }
    switch (myerr) {
        case 1047: // WSREP has not yet prepared node for application use
        case 1053: // Server shutdown in progress
            myds->destroy_MySQL_Connection_From_Pool(false);
            break;
        default:
            if (mysql_thread___reset_connection_algorithm == 2) {
                create_new_session_and_reset_connection(myds);
            } else {
                myds->destroy_MySQL_Connection_From_Pool(true);
            }
            break;
    }
    myconn = myds->myconn; // re-initialize
    myds->fd=0;
    if (retry_conn) {
        myds->DSS=STATE_NOT_INITIALIZED;
        //previous_status.push(PROCESSING_QUERY);
    switch(status) { // this switch can be replaced with a simple previous_status.push(status), but it is here for readibility
        case PROCESSING_QUERY:
            previous_status.push(PROCESSING_QUERY);
            break;
        case PROCESSING_STMT_PREPARE:
            previous_status.push(PROCESSING_STMT_PREPARE);
            break;
        default:
            assert(0);
            break;
        }
        if (errmsg) {
            free(errmsg);
            errmsg = NULL;
        }
        NEXT_IMMEDIATE(CONNECTING_SERVER);
    }
    //handler_ret = -1;
    //return handler_ret;
    break;
case 1153: // ER_NET_PACKET_TOO_LARGE
    proxy_warning("Error ER_NET_PACKET_TOO_LARGE during query on (%d,%s,%d): %d, %s\n", myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myerr, mysql_error(myconn->mysql));
    break;
default:
    break; // continue normally

}


* MySQL_HostGroups_Manager.cpp
 shunned 状态的 HostGroup 不会被清理。链接可用
 ```cpp
 bool MySQL_HostGroups_Manager::shun_and_killall(char *hostname, int port) {
     ...
   switch (mysrvc->status) {
   case MYSQL_SERVER_STATUS_SHUNNED:
      if (mysrvc->shunned_automatic==false) {
           break;
      }
  case MYSQL_SERVER_STATUS_ONLINE:
       if (mysrvc->status == MYSQL_SERVER_STATUS_ONLINE) {
           ret = true;
       }
       mysrvc->status=MYSQL_SERVER_STATUS_SHUNNED;
   case MYSQL_SERVER_STATUS_OFFLINE_SOFT:
       mysrvc->shunned_automatic=true;
       mysrvc->shunned_and_kill_all_connections=true;
       mysrvc->ConnectionsFree->drop_all_connections();
       break;
   default:
       break;
}

重点线程

Admin thread

MySQL workers -> mysql-threads

Monitor thread -> monitor scheme 中的表即为监控项

ProxySQL VS Other Proxy

https://proxysql.com/compare

https://www.percona.com/blog/2016/05/12/proxysql-versus-maxscale-for-oltp-ro-workloads/

https://proxysql.com/blog/point-select-benchmark

https://proxysql.com/blog/proxy-wars

https://proxysql.com/blog/proxysql-vs-maxscale-persistent-connection-response-time

https://www.percona.com/live/17/sessions/mysql-load-balancers-maxscale-proxysql-haproxy-mysql-router-nginx-close-look

ProxySQL 性能测试(SSL ON/OFF)

CPUMEMORYROLETHREADS NUMRUNTIME
4C8GBMySQL Instance1001H
4C4GBProxySQL1001H

性能比:
(28695.79−25054.72)/28695.79*100% = 12.69%

开启 SSL 认证

    queries performed:
        read:                            63140070
        write:                           18039754
        other:                           9019925
        total:                           90199749
    transactions:                        4509920 (1252.72 per sec.)
    queries:                             90199749 (25054.72 per sec.)
    ignored errors:                      85     (0.02 per sec.)
    reconnects:                          0      (0.00 per sec.)

Throughput:
    events/s (eps):                      1252.7172
    time elapsed:                        3600.1102s
    total number of events:              4509920

Latency (ms):
         min:                                   16.64
         avg:                                   79.82
         max:                               104651.22
         95th percentile:                      130.13
         sum:                            359993465.21

Threads fairness:
    events (avg/stddev):           45099.2000/8625.57
    execution time (avg/stddev):   3599.9347/0.04

关闭 SSL 认证加密

SQL statistics:
    queries performed:
        read:                            72316454
        write:                           20661536
        other:                           10330817
        total:                           103308807
    transactions:                        5165356 (1434.77 per sec.)
    queries:                             103308807 (28695.79 per sec.)
    ignored errors:                      105    (0.03 per sec.)
    reconnects:                          0      (0.00 per sec.)

Throughput:
    events/s (eps):                      1434.7661
    time elapsed:                        3600.1380s
    total number of events:              5165356

Latency (ms):
         min:                                   14.91
         avg:                                   69.69
         max:                                 1962.88
         95th percentile:                      123.28
         sum:                            359992116.60

Threads fairness:
    events (avg/stddev):           51653.5600/1571.66
    execution time (avg/stddev):   3599.9212/0.03

ProxySQL + MySQL Plus 注意事项

deploy 优化

  • ProxySQL 会检测 ReadGroup/WriteGroup 节点设置 read_only 所以,lvs必须配置 RVIP 不负载到主库

  • 提供同步用户功能,在 MySQL 主实例更新用户后,需要同步用户信息到 ProxySQL mysql_users 才可以通过 ProxySQL 链接

  • checkservice 中 SELECT 1 FROM stats_mysql_query_digest_reset LIMIT 1; 定期清理 stats_mysql_query_digest

  • 自定义服务 SSL 配置链接

  • 增加 ProxySQL 后开启 SSL 配置

  • 增加 ProxySQL 前开启 SSL 配置

  • mysql_servers, mysql_users 的 max_connections 应该设置为资源配置组中 max_connections 的大小

  • mysql_servers 的 max_replication_lag 是否需要调整为: 保证永远不会摘掉 rvip

  • 多路复用参数值设置 1, 2 ,个人建议关闭多路复用。当 call procedure() 中执行 select 会报错https://github.com/sysown/proxysql/issues/1903

  • 查询缓存,不需要开启

需要依赖于 Appcenter 调整功能

  • 切换私有网络需要被 proxysql 感知

本作品采用 知识共享署名 4.0 国际许可协议 进行许可。 转载时请注明原文链接。From TCeason

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值