ProxySQL是灵活强大的MySQL代理层,是一个能实实在在用在生产环境的MySQL中间件,可以实现读写分离,支持Query路由功能,支持动态指定某个SQL进行cache,支持动态加载配置、故障切换和一些SQL的过滤功能。还有一些同类产品比如DBproxy、MyCAT、OneProxy等。但经过反复对比和测试之后,还是觉得ProxySQL是一款性能不谙,靠谱稳定的MySQL中间件产品!
ProxySQL的亮点所在
-几乎所有的配置均可在线更改(其配置数据基于SQLite存储),无需重启proxysql-基于正则和client_addr的强大和灵活的路由规则-详细的状态统计,统计结果和pt-query-digest对慢日志的分析结果类似,相当于有了统一的查看sql性能和sql语句统计的入口(DesignedbyaDBAforDBAs)-自动重连和重新执行机制(auto-reconnectandautomaticre-executionofqueriesusingit’sConnectionsPool):若一个请求在链接或执行过程中意外中断,proxysql会根据其内部机制重新执行该操作-querycache功能:比mysql自带QC更灵活,可在mysql_query_rules表中依据digest,match_pattern,client_addr等维度控制哪类语句可以缓存-支持连接池(connectionpool)并且支持multiplexing,区别于atlas之流的连接池实现。
ProxySQL的特点
ProxySQL是一个高性能的MySQL中间件,拥有强大的规则引擎。具有以下特性:-连接池,而且是multiplexing;-主机和用户的最大连接数限制;-自动下线后端DB;-延迟超过阀值-ping延迟超过阀值-网络不通或宕机-强大的规则路由引擎;-实现读写分离-查询重写-sql流量镜像-支持preparedstatement;-支持QueryCache;-支持负载均衡,与gelera结合自动failover;-将所有配置保存写入到SQLit表中。-支持动态加载配置,即一般可以在线修改配置,但有少部分参数还是需要重启来生效。-支持querycache。-支持对query的路由,可以针对某个语句进行分配去哪个实例执行。-不支持分表,可以分库,但是利用规则配置实现分表。
如上可知,ProxySQL集合了很多优秀特性于一身,那么它的缺点呢就是项目不够成熟,好在官方网站一直在及时更新,并且受到Percona官方的支持。
ProxySQL多层管理配置设计(有三层配置)
-runtime:运行中使用的配置文件-memory:提供用户动态修改配置文件-disk:将修改的配置保存到磁盘SQLit表中(即:proxysql.db)-config:一般不使用它(即:proxysql.cnf)
ProxySQL运行机制草图如下:ProxySQL有一个完备的配置系统,配置ProxySQL是基于sql命令的方式完成的。ProxySQL支持配置修改之后的在线保存、应用,不需要重启即可生效。整个配置系统分三层设计。整个配置系统分为三层,如下图所示:
ProxySQL配置系统分为三层的目的:1)自动更新;2)尽可能的不重启proxysql就可以修改配置;3)方便回滚错误配置;
简单说就是配置proxysql分为三个级别,RUNTIME是即时生效的,MEMORY是保存在内存中但并不立即生效的,DISK|CONFIGFILE是持久化或写在配置文件中的。
这三个级别的配置文件互不干扰,在某个层级修改了配置文件,想要加载或保存到另一个层级,需要额外的LOAD或SAVE操作:"LOADxx_configFROMxx_level|LOADxx_configTOxx_level|SAVExx_configTOxx_level|SAVExx_configFROMxx_level",达到加载配置或者持久化配置的目的。这三层中每层的功能与含义如下:-RUNTIME层代表的是ProxySQL当前生效的配置,包括global_variables,mysql_servers,mysql_users,mysql_query_rules。无法直接修改这里的配置,必须要从下一层load进来。该层级的配置时在proxysql管理库(sqlite)的main库中以runtime_开头的表,这些表的数据库无法直接修改,只能从其他层级加载;该层代表的是ProxySQL当前生效的正在使用的配置,包括global_variables,mysql_servers,mysql_users,mysql_query_rules表。无法直接修改这里的配置,必须要从下一层load进来。也就是说RUNTIME这个顶级层,是proxysql运行过程中实际使用的那一份配置,这一份配置会直接影响到生产环境的,所以要将配置加载进RUNTIME层时需要三思而行。
-MEMORY层是平时在mysql命令行修改的main里头配置,可以认为是SQLite数据库在内存的镜像。该层级的配置在main库中以mysql_开头的表以及global_variables表,这些表的数据可以直接修改;用户可以通过MySQL客户端连接到此接口(admin接口),然后可以在mysql命令行查询不同的表和数据库,并修改各种配置,可以认为是SQLite数据库在内存的镜像。也就是说MEMORY这个中间层,上面接着生产环境层RUNTIME,下面接着持久化层DISK和CONFIGFILE。MEMORY层是我们修改proxysql的唯一正常入口。一般来说在修改一个配置时,首先修改Memory层,确认无误后再接入RUNTIME层,最后持久化到DISK和CONFIGFILE层。也就是说memeory层里面的配置随便改,不影响生产,也不影响磁盘中保存的数据。通过admin接口可以修改mysql_servers、mysql_users、mysql_query_rules、global_variables等表的数据。
-DISK|CONFIGFILR层持久存储的那份配置,一般在$(DATADIR)/proxysql.db,在重启的时候会从硬盘里加载。/etc/proxysql.cnf文件只在第一次初始化的时候用到,完了后,如果要修改监听端口,还是需要在管理命令行里修改,再save到硬盘。该层级的配置在磁盘上的sqlite库或配置文件里。DISK/CONFIGFILE层表示持久存储的那份配置,持久层对应的磁盘文件是$(DATADIR)/proxysql.db,在重启ProxySQL的时候,会从proxysql.db文件中加载信息。而/etc/proxysql.cnf文件只在第一次初始化的时候使用,之后如果要修改配置,就需要在管理端口的SQL命令行里进行修改,然后再save到硬盘。也就是说DISK和CONFIGFILE这一层是持久化层,我们做的任何配置更改,如果不持久化下来,重启后,配置都将丢失。
需要注意1)ProxySQL每一个配置项在三层中都存在,但是这三层是互相独立的,也就是说proxysql可以同时拥有三份配置,每层都是独立的,可能三份配置都不一样,也可能三份都一样。2)RUNTIME层代表ProxySQL当前生效的正在使用的配置,无法直接修改这里的配置,必须要从下一层"load"进来。3)MEMORY这一层上面连接RUNTIME层,下面连接持久化层。在这层可以正常操作ProxySQL配置,随便修改,不会影响生产环境。修改一个配置一般都是先在MEMORY层完成,然后确认正常之后再加载到RUNTIME和持久化到磁盘上。4)DISK和CONFIGFILE层持久化配置信息,重启后内存中的配置信息会丢失,所以需要将配置信息保留在磁盘中。重启时,可以从磁盘快速加载回来。
ProxySQL配置文件的修改流程一般是:-启动时:先修改必要的CONFIGFILE配置,比如管理端口,然后启动;-其他配置:修改MEMORY中的表,然后加载到RUNTIME并持久化。
一般,修改的配置都是在memory层。可以load到runtime,使配置在不用重启proxysql的情况下也可以生效,也可以save到disk,将对配置的修改持久化!
需要修改配置时,直接操作的是MEMORAY,以下命令可用于加载或保存users(mysql_users):(序号对应上图“运行机制”草图)
[1]:LOADMYSQLUSERSTORUNTIME/LOADMYSQLUSERSFROMMEMORY#常用。将修改后的配置(在memory层)用到实际生产[2]:SAVEMYSQLUSERSTOMEMORY/SAVEMYSQLUSERSFROMRUNTIME#将生产配置拉一份到memory中[3]:LOADMYSQLUSERSTOMEMORY/LOADMYSQLUSERSFROMDISK#将磁盘中持久化的配置拉一份到memory中来[4]:SAVEMYSQLUSERSTODISK/SAVEMYSQLUSERSFROMMEMORY#常用。将memoery中的配置保存到磁盘中去[5]:LOADMYSQLUSERSFROMCONFIG#将配置文件中的配置加载到memeory中个人还是比较习惯用TO,记住往上层是LOAD,往下层是SAVE。以下命令加载或保存servers(mysql_servers):
[1]:LOADMYSQLSERVERSTORUNTIME#常用,让修改的配置生效[2]:SAVEMYSQLSERVERSTOMEMORY[3]:LOADMYSQLSERVERSTOMEMORY[4]:SAVEMYSQLSERVERSTODISK#常用,将修改的配置持久化[5]:LOADMYSQLSERVERSFROMCONFIG后面的使用方法也基本相同,一并列出。以下命令加载或保存queryrules(mysql_query_rules):
[1]:loadmysqlqueryrulestorun#常用[2]:savemysqlqueryrulestomem[3]:loadmysqlqueryrulestomem[4]:savemysqlqueryrulestodisk#常用[5]:loadmysqlqueryrulesfromconfig以下命令加载或保存mysqlvariables(global_variables):
[1]:loadmysqlvariablestoruntime[2]:savemysqlvariablestomemory[3]:loadmysqlvariablestomemory[4]:savemysqlvariablestodisk[5]:loadmysqlvariablesfromconfig以下命令加载或保存adminvariables(select*fromglobal_variableswherevariable_namelike'admin-%'):
[1]:loadadminvariablestoruntime[2]:saveadminvariablestomemory[3]:loadadminvariablestomemory[4]:saveadminvariablestodisk[5]:loadadminvariablesfromconfigProxySQL启动过程总结:当proxysql启动时,首先读取配置文件CONFIGFILE(/etc/proxysql.cnf),然后从该配置文件中获取datadir,datadir中配置的是sqlite的数据目录。如果该目录存在,且sqlite数据文件存在,那么正常启动,将sqlite中的配置项读进内存,并且加载进RUNTIME,用于初始化proxysql的运行。如果datadir目录下没有sqlite的数据文件,proxysql就会使用configfile中的配置来初始化proxysql,并且将这些配置保存至数据库。sqlite数据文件可以不存在,/etc/proxysql.cnf文件也可以为空,但/etc/proxysql.cnf配置文件必须存在,否则,proxysql无法启动。
一、ProxySQL安装(两种方式)
mysql_ifaces也就是说proxysql有一个admin接口专门来做配置,相当于一个mysqlshell可以通过sql来让配置实时生效。mysql_ifaces配置了允许连接proxysql的ip和port
表mysql_users
表mysql_replication_hostgroups
MySQL[(none)]>showcreatetablemysql_replication_hostgroups\G;***************************1.row***************************table:mysql_replication_hostgroupsCreateTable:CREATETABLEmysql_replication_hostgroups(writer_hostgroupINTCHECK(writer_hostgroup>=0)NOTNULLPRIMARYKEY,reader_hostgroupINTNOTNULLCHECK(reader_hostgroup<>writer_hostgroupANDreader_hostgroup>0),commentVARCHARNOTNULLDEFAULT'',UNIQUE(reader_hostgroup))1rowinset(0.001sec)ERROR:NoqueryspecifiedMySQL[(none)]>select*frommysql_replication_hostgroups;+------------------+------------------+---------+|writer_hostgroup|reader_hostgroup|comment|+------------------+------------------+---------+|10|20|1|+------------------+------------------+---------+1rowinset(0.000sec)定义hostgroup的主从关系。ProxySQLmonitor模块会监控HG后端所有servers的read_only变量,如果发现从库的read_only变为0、主库变为1,则认为角色互换了,自动改写mysql_servers表里面hostgroup关系,达到自动Failover效果。
表mysql_query_rulesmysql_query_rules是ProxySQL非常核心一个表,定义查询路由规则
以上都是匹配查询的规则,1.3.5版本使用的正则引擎只有RE2,1.4版本可以通过变量mysql-query_processor_regex设置RE2或者PCRE,且1.4开始默认是PCRE。推荐用match_digest。关于每条查询都会计算digest对性能的影响,计算querydigest确实会有性能损失,但是这却是proxysql里面非常重要的特性,主要是两点:-proxysql无法知道连接复用(multipexing)是否必须被自动禁用,比如连接里面有variables/tmptables/locktable等特殊命令,是不能复用的。-完整的查询去匹配正则的效率,一般没有参数化后的查询匹配效率高,因为有很长的字符串内容需要处理。再者,SELECT*FROMrandomtableWHEREcommentLIKE‘%INTOsbtest1%FROMsbtest2%’字符串里有类似这样的语句,很难排除误匹配。-negate_match_pattern:反向匹配,相当于对match_digest/match_pattern的匹配取反。-re_modifiers:修改正则匹配的参数,比如默认的:忽略大小写CASELESS、禁用GLOBAL.
proxysql对后端server健康检查
这里强烈推荐用第一种方式因为第一种是完全由我们控制的;而第二种假如我们误将读server的read_only属性设置为0,则proxysql会将其重新分配到写组,这绝对是不期望的。
ProxySQL下添加与修改配置
1.实验环境
4.1ProxySQL实现读写分离
向ProxySQL中添加MySQL节点
首先在后端master主数据节点上创建一个用于监控的用户名(只需在master上创建即可,因为会复制到slave上),这个用户名只需具有USAGE权限即可。如果还需要监控复制结构中slave是否严重延迟于master(这个俗语叫做"拖后腿",术语叫做"replicationlag"),则还需具备replicationclient权限。
必须注意:这只是实验,实际的路由规则绝不应该仅根据所谓的读、写操作进行分离,而是从各项指标中找出压力大、执行频繁的语句单独写规则、做缓存等等。和查询规则有关的表有两个:mysql_query_rules和mysql_query_rules_fast_routing,后者是前者的扩展表,1.4.7之后才支持该快速路由表。本案例只介绍第一个表。插入两个规则,目的是将select语句分离到hostgroup_id=20的读组,但由于select语句中有一个特殊语句SELECT...FORUPDATE它会申请写锁,所以应该路由到hostgroup_id=10的写组.
4.4scheduler打印proxysql状态到日志
[root@mysql-proxy~]#mkdir-p/opt/proxysql/log[root@mysql-proxy~]#vim/opt/proxysql/log/status.sh#!/bin/bashDATE=`date"+%Y-%m-%d%H:%M:%S"`echo"{\"dateTime\":\"$DATE\",\"status\":\"running\"}">>/opt/proxysql/log/status_log[root@mysql-proxy~]#chmod777/opt/proxysql/log/status.sh然后在proxysql插入一条scheduler(定义每分钟打印一次,即60000毫秒)[root@mysql-proxy~]#mysql-uadmin-padmin-h127.0.0.1-P6032........................MySQL[(none)]>insertintoscheduler(active,interval_ms,filename)values(1,60000,'/opt/proxysql/log/status.sh');QueryOK,1rowaffected(0.000sec)MySQL[(none)]>LOADSCHEDULERTORUNTIME;QueryOK,0rowsaffected(0.001sec)MySQL[(none)]>SAVESCHEDULERTODISK;QueryOK,0rowsaffected(0.105sec)然后查看日志就可以看到proxysql的运行结果了:[root@mysql-proxy~]#tail-f/opt/proxysql/log/status_log{"dateTime":"2019-02-1914:24:03","status":"running"}{"dateTime":"2019-02-1914:25:03","status":"running"}{"dateTime":"2019-02-1914:26:03","status":"running"}{"dateTime":"2019-02-1914:27:03","status":"running"}