Skip to content

Latest commit

 

History

History
1171 lines (1093 loc) · 27.1 KB

PROGRAMMING_GUIDE.md

File metadata and controls

1171 lines (1093 loc) · 27.1 KB

编译/安装/运行

详见快速入门手册 对应章节

源码简介

调试

添加log

g_message/critical/debug/...,参数与printf完全一致,级别可以动态设置,set log-level, 日志文件位置$log_path/$instance.log

gdb调试

1. 运行时调试
  • 获取DBProxy进程号
ps -ef|grep proxy
  • attach DBProxy进程
gdb -p [pid]
  • 设置断点
break [断点]
  • 程序继续运行
continue
  • 断点触发

  • 调试

2. 启动时调试
  • 启动
gdb [安装路径]/bin/mysql-proxy
  • 设置参数
set args --defaults-file=[配置文件绝对路径]
  • 设置断点

  • 程序运行

run
  • 断点触发

  • 调试

代码文件含义

文件名 作用 使用频率
autogen.sh 用于生成configure文件,修改Makefile.am时使用,通常用于新加文件,修改版本
lib/admin.lua admin接口的命令分析文件,与admin接口相关的命令都需要修改这个文件
plugins/admin/admin-plugin.c 实现admin接口的功能
plugins/proxy/proxy-percentile.c

plugins/proxy/proxy-percentile.h

实现percentile功能
plugins/proxy/proxy-plugin.c

plugins/proxy/proxy-plugin.h

实现proxy功能的主要文件
plugins/proxy/proxy-sql-log.c

plugins/proxy/proxy-sql-log.h

实现proxy的SQL日志管理
script/encrypt.c 加密程序
script/source.cnf.samples 模板配置文件,参数改变后需要修改此文件
src/chassis-event-thread.c

src/chassis-event-thread.h

工作线程的定义,调度,回收
src/chassis-exports.h 定义chassis库的导出函数
src/chassis-filemode.c

src/chassis-filemode.h

设置chassis文件的权限
src/chassis-filter.c

src/chassis-filter.h

实现过滤功能
src/chassis-frontend.c

src/chassis-frontend.h

参数读取模块,处理命令行参数和配置文件中的参数
src/chassis-gtimeval.c

src/chassis-gtimeval.h

时间处理
src/chassis-keyfile.c

src/chassis-keyfile.h

配置文件处理
src/chassis-limits.c

src/chassis-limits.h

处理proxy的资源限制功能,如文件句柄个数
src/chassis-log.c

src/chassis-log.h

管理日志处理
src/chassis-mainloop.c

src/chassis-mainloop.h

1. 定义核心数据结构 chassis 2. 定义了启动及运行的主流程
src/chassis-options-utils.c

src/chassis-options-utils.h

参数处理函数
src/chassis-options.c

src/chassis-options.h

参数处理模块
src/chassis-path.c

src/chassis-path.h

路径处理
src/chassis-plugin.c

src/chassis-plugin.h

插件管理
src/chassis-shutdown-hooks.c

src/chassis-shutdown-hooks.h

声明退出时的回调函数
src/chassis-stats.c

src/chassis-stats.h

未知,目前未用到
src/chassis-timings.c

src/chassis-timings.h

chassis_timestamp_t结构体声明
src/chassis-unix-daemon.c

src/chassis-unix-daemon.h

实现daemon和keepalive功能
src/chassis-win32-service.c

src/chassis-win32-service.h

window平台服务配置,目前没有使用
src/disable-dtrace.h trace接口定义
src/glib-ext-ref.c 未知,使用频率低
src/glib-ext-ref.h 未知,使用频率低
src/glib-ext.c 未知,使用频率低
src/glib-ext.h 未知,使用频率低
src/lua-env.c lua接口定义
src/lua-env.h lua接口声明
src/lua-load-factory.c 未知,使用频率低
src/lua-load-factory.h 未知,使用频率低
src/lua-registry-keys.h 未知,使用频率低
src/lua-scope.c 未知,使用频率低
src/lua-scope.h 未知,使用频率低
src/my_rdtsc.c 时间处理函数
src/my_rdtsc.h 时间处理函数
src/mysql-binlog-dump.c 未使用
src/mysql-myisam-dump.c 未使用
src/mysql-proxy-cli.c main函数入口
src/network-address-lua.c

src/network-address-lua.h

和lua交互中ip地址的处理
src/network-address.c

src/network-address.h

ip地址的处理
src/network-backend-lua.c

src/network-backend-lua.h

和lua交互中backend相关的操作
src/network-backend.c

src/network-backend.h

实现backend的管理
src/network-conn-errcode.h MySQL端错误号对应的日志,用于给客户端返回MySQL的日志
src/network-conn-pool-lua.c

src/network-conn-pool-lua.h

连接池管理
src/network-conn-pool.c

src/network-conn-pool.h

连接池管理
src/network-debug.h 空文件,没有使用
src/network-exports.h 定义编译库的宏
src/network-injection-lua.c

src/network-injection-lua.h

定义injection与lua交互的模块
src/network-injection.c

src/network-injection.h

injection处理函数
src/network-mysqld-binlog.c

src/network-mysqld-binlog.h

没有使用
src/network-mysqld-lua.c

src/network-mysqld-lua.h

定义与lua交互的主要函数
src/network-mysqld-masterinfo.c

src/network-mysqld-masterinfo.h

未使用
src/network-mysqld-packet.c

src/network-mysqld-packet.h

与MySQL交互的数据包管理
src/network-mysqld-proto.c

src/network-mysqld-proto.h

与MySQL交互的协议
src/network-mysqld-stats.h DBProxy状态定义
src/network-mysqld.c

src/network-mysqld.h

定义network_mysqld_con及其相关的对象
src/network-queue.c

src/network-queue.h

声明数据包管理缓存队列
src/network-socket-lua.c

src/network-socket-lua.h

没用到,可以忽略
src/network-socket.c

src/network-socket.h

socket连接管理
src/network_mysqld_proto_binary.c

src/network_mysqld_proto_binary.h

处理MySQL数据包的数据类型
src/network_mysqld_type.c

src/network_mysqld_type.h

没用到
src/string-len.h 没用到
src/sys-pedantic.h 未用到
src/test-latency.c 测试代码

主要数据结构介绍

  • chassis
名称 类型 含义 备注
event_base struct event_base * 管理 注册事件 的结构体
event_hdr_version gchar *
modules GPtrArray * 管理插件的数组 module[0]: admin插件 module[1]:proxy插件
base_dir gchar * DBProxy安装路径
log_path gchar * 日志路径
user gchar * DBProxy进程的所属用户 root用户下可以设置DBProxy以非root用户启动
instance_name gchar * 实例名
log chassis_log * 用来管理 日志 相关参数的 数据结构 例如可以设置日志rotate的策略、trace的模块等等
stats chassis_stats_t *
event_thread_count guint 工作线程的数量
proxy_max_connections gint 客户端连接DBProxy的最大连接数
proxy_connections volatile gint 当前DBProxy持有的客户端的连接数
proxy_max_used_connections volatile gint DBProxy当前实例,历史持有客户端连接的最大数
proxy_attempted_connects volatile gint client试图与DBProxy建立连接的数据量
proxy_aborted_connects volatile gint client与DBProxy建立连接时,被异常关闭的连接的数量
proxy_closed_clients volatile gint client主动发起断开连接时,被关闭的连接数 客户端会发送COM_QUIT报文
proxy_aborted_clients volatile gint 正常关闭的连接数
long_wait_time gint DBProxy与backend建立连接的时间 后续做监控视图,可以监控这个参数
long_query_time gint 慢查询的阈值 超过阈值,会在sql日志中打印一条慢日志的记录
query_response_time_range_base gint 查询响应时间的基数 默认是2
query_response_time_stats gint 统计信息的开关 0:不做统计1:仅统计总体的响应时间(包括慢查询) 2:直方图的统计
db_connection_idle_timeout gint 最大空闲连接超时
db_connection_max_age gint 连接的生命周期
my_version MYSQL_VERSION mysql版本号
threads GPtrArray * 工作线程数组
shutdown_hooks chassis_shutdown_hooks_t *
sc lua_scope *
backends network_backends_t * backend数组 数组的数量与event_thread_count一致
wait_timeout volatile gint 客户端连接超时
shutdown_timeout volatile gint 关闭DBProxy时等待的最长时间 如果有正在执行中的事务,会等待该时间,如果超过该时间仍旧存在事务连接,则直接关闭DBProxy
max_backend_tr gint backend的threadrunning阈值
thread_running_sleep_delay gint backend的threadrunning超过阈值时,等待重试的时间
proxy_filter sql_filter * 与sql黑名单相关的结构体 黑名单列表、过滤标识、黑名单的文件路径、锁等信息
proxy_reserved sql_reserved_query * 与查询统计相关的结构体 例如保存的最近查询的sql,自动加入黑名单的触发条件等
daemon_mode gint 标识DBProxy启动方式 后台启动或是前台启动
max_files_number gint64 所持有的文件句柄数的阈值
auto_restart guint 标识是否启动守护进程 启动守护进程时,一旦DBProxy退出,守护进程会重启DBProxy
opts chassis_options_t * 全局的参数信息
  • network_mysqld_con
名称 类型 含义 备注
con_id guint64 连接的ID
state network_mysqld_con_state_t 连接的状态
wait_status network_mysqld_con_wait_t 等待的状态
server network_socket * 服务端连接的管理结构体
client network_socket * 客户端连接的管理结构体
plugins network_mysqld_hooks 插件提供的一组函数
config chassis_plugin_config * 插件的参数信息
srv chassis * 指向chassis结构体的指针
is_listen_socket int
auth_result_state guint8
resultset_is_needed gboolean 是否需要向客户端发送结果集 有些情况下不需要向客户端返回结果集,例如DBProxy隐式发送的一些语句等
resultset_is_finished gboolean 接收服务端的结果集是否接收完成
com_quit_seen gboolean 客户端发送COM_QUIT报文断开连接
parse struct network_mysqld_con_parse 存放查询结果相关的结构体
plugin_con_state void *
conn_status_var connection_status_var_t 存储查询语句信息的结构体
conn_status connection_status_t 存储连接状态的结构体
locks GHashTable* 查询中的锁信息的存储
merge_res merge_res_t* 用来存储和合并结果集的结构体
challenge GString* challenge包
con_filter_var conn_filter_t sql过滤相关的结构体
is_in_wait gboolean 是否连接处于等待中
try_send_query_times gint 当backend的threadrunning过高,尝试次数
server_lock GRWLock db连接的锁
server_error_code guint16 db返回的错误码

glib

DBProxy的基础数据类型用到Glib的库,官方介绍

DBProxy 使用较多的

  • gchar
    • g_strdup,g_strdup_printf
    • g_free
  • GString
    • g_string_new, g_string_free
    • g_string_append_printf
  • PointerAarray
    • g_ptr_array_new(), g_ptr_array_sized_new(), g_ptr_array_new_with_free_func()
    • g_ptr_array_add() g_ptr_array_insert()
    • g_ptr_array_remove()
    • g_ptr_array_sort ()
    • g_ptr_array_free ()
    • 示例: network-backend->raw_pwds
  • Hash Tables
    • g_hash_table_new(), g_hash_table_new_full()
    • g_hash_table_insert(), g_hash_table_lookup(), g_hash_table_lookup_extended ()
    • g_hash_table_remove_all()
    • g_hash_table_destroy()
    • network-backend->pwd_table, network_connection_pool, proxy->config->db_table
  • Asynchronous Queues
    • g_async_queue_new()
    • g_async_queue_pop()
    • thread->event_queue
  • Doubly-Linked Lists
  • Double-ended Queues
    • g_queue_new (), g_queue_free()
    • g_queue_push_head(), g_queue_push_tail()
    • g_queue_pop_head(), g_queue_pop_tail()
    • g_queue_clear ()
  • Thread相关
    • GThread
      • g_thread_try_new
      • g_thread_join
      • g_thread_exit
    • GRWLock
      • g_rw_lock_init ()
      • g_rw_lock_writer_lock ()
      • g_rw_lock_writer_unlock ()
      • g_rw_lock_reader_lock ()
      • g_rw_lock_reader_unlock ()
      • g_rw_lock_clear ()
    • GMutex
      • g_mutex_init ()
      • g_mutex_lock ()
      • g_mutex_unlock ()
      • g_mutex_clear ()

libevent

官网

  • event_base:
    保存event的队列
  • event_set:
    设置event的属性,回调函数
  • event_add:
    将event添加到event_base中
  • 示例: WAIT_FOR_EVENT

LUA

Lua 与 C 的简单交互

  • C 与 Lua 交互的基础
    • 虚拟栈
    • C 端处理
      • 定义变量
      • index: 下标方法
      • newindex: 赋值方法
      • call: 函数方法
      • 其它方法
      • 代码参见 *-lua.c/h 的文件
  • 示例讲解 selelct * from backend
    • Lua 端参数传递,结果展示

      • 命令识别 admin.lua 中 _function read_query,在函数中使用正则匹配的方式来判断输入命令

      • 方法调用

        for i = 1, #proxy.global.backends do
            local b = proxy.global.backends[i]
            rows[#rows + 1 ] = ...
        end
        
      • 结果展示

        	proxy.response = {
             type = proxy.MYSQLD_PACKET_OK,
             resultset = {
                    fields = fields,
                    rows = rows
             }
         }
        
    • C 端定义 backend变量及方法

      • 全局变量(network-mysqld-lua.c)

         network_mysqld_lua_setup_global 
         proxy --> global  --> backends
                           --> raw_ips_p
                           --> raw_pwds_p
                           --> status
                           --> sys_config                         
               --> config  --> instance
                           --> logpath 
        
      • backend变量及其方法(network-backend-lua.c)

      int network_backends_lua_getmetatable(lua_State *L) { static const struct luaL_reg methods[] = { { "__index", proxy_backends_get }, { "__newindex", proxy_backends_set }, { "__len", proxy_backends_len }, { "__call", proxy_backends_call }, { NULL, NULL }, }; return proxy_getmetatable(L, methods); } ```

      • 示例: 实现load config 命令
        1. admin.lua 的read_query函数中添加语法判断 参照 save config 命令的处理

             elseif string.find(query:lower(), "^load%s+config+$") then
             local ret = proxy.global.sys_config("", "loadconfig")
             if ret == 1 then
                set_error("load config-file failed")
                return proxy.PROXY_SEND_RESULT
             end
             fields = {
                     { name = "status",
                       type = proxy.MYSQL_TYPE_STRING },
             }
          
        2. proxy_sys_config_call(network-mysqld-lua.c) (network_mysqld_lua_setup_global -> network_sys_config_lua_getmetatable ->proxy_sys_config_call) 函数内添加分支

          elseif (strleq(key, keysize, C("loadconfig"))) {
                 ret = load_config(chas);
          } 
          

          load_config函数的定义:

          int load_config(chassis *chas) {
              return 0;
          }
          
      • TODO
        • 显示event_thread等待连接的总数
          • tips
            • admin.lua 中的 show proxy status 命令
            • chassis_event_thread_t->event_queue 保了等待的连接
            • network-mysqld-lua.c proxy_status 分支添加处理
* 参考文献
    * [Lua5.3参考手册](https://cloudwu.github.io/lua53doc/manual.html)
    * [Lua C 接口](https://www.lua.org/manual/5.3/manual.html#4)
    * [Lua C API 的正确用法](http://blog.codingnow.com/2015/05/lua_c_api.html)

修改的bug介绍

解决用户权限不足、Atlas用户名密码配置错误等导致使用错误用户的问题

连接池的优化

```
network_connection_pool *network_connection_pool_new(void) {
network_connection_pool *pool =
            g_hash_table_new_full((GHashFunc)network_connection_pool_hash_func,
		                            (GEqualFunc)network_connection_pool_equal_func,
		                            (GDestroyNotify)network_connection_pool_key_free,
		                            (GDestroyNotify)network_connection_pool_value_free);
key:value : network_connection_pool_add
```

解决SQL语句中有注释时语句分析不正确的问题

```
check_flags
skip_comment_token
```

屏蔽了KILL语句,避免在后端MySQL可能误KILL的问题。

```
is_in_blacklist

```

解决客户端发送空串导致Atlas挂掉的问题

```
proxy_read_query
        if (type == COM_QUERY && tokens->len <= 1) {
```

解决在分表情况下,返回值有 NULL 的情况下,查询超时的问题

此问题是Atlas在多个分表merge结果的过程中未处理 NULL 值,导致结果集返回不对,而JDBC接口会认为此种情况下是未收到结果,会处于一直等待状态,触发超时。

```
merge_rows
```

解决在分表情况下, IN 子句中分表列只支持int32,不支持int64的问题

```
combine_sql
    for (i = 0; i < num; ++i) mt[i] = g_array_new(FALSE, FALSE, sizeof(guint64));
```

解决0.0.2版本连接断开的内存泄露问题

在连接的结构体的释放接口中,lock 的成员变量未释放,导致在连接断开,回收连接对象时会泄漏24个字节。

```
network_mysqld_con_free
g_hash_table_remove_all(con->locks);
g_hash_table_destroy(con->locks);
```

修改了事务内语句执行错误时,Atlas未保留后台连接导致rollback发送到其它结点的问题

http://wiki.sankuai.com/pages/viewpage.action?pageId=441093332

解决了绑定后端连接断开时,客户端连接未及时断开的问题。

```
network_mysqld_con_handle
CON_STATE_READ_QUERY:
服务端连接
// 异常,server端收到了数据,直接断开连接
if (con->server && event_fd == con->server->fd) {
    gchar *log_str = g_strdup_printf("there is something to read from server(%s).",
                                NETWORK_SOCKET_SRC_NAME(con->server));
    CON_MSG_HANDLE(g_critical, con, log_str);
    g_free(log_str);
    g_atomic_int_add(&srv->proxy_aborted_clients, 1);
    con->state = CON_STATE_ERROR;
    break;
}
```

解决了show processlist在显示backend host时引起core dump的问题。

```
show processlist
con->server_lock
```

修复分表查询结果合并时列字符集错误的问题,该问题可能会导致结果乱码。

```
函数 network_mysqld_con_send_resultset
g_string_append_c(s, field->charsetnr & 0xff); /* charset */
g_string_append_c(s, (field->charsetnr >> 8) & 0xff); /* charset */
```

连接保持

相关代码

    proxy_read_query_result()
    case PROXY_SEND_RESULT:
        gboolean b_reserve_conn = (inj->qstat.insert_id > 0) || (inj->qstat.warning_count > 0) || (inj->qstat.affected_rows > 0); //found_rows(), last_insert_id(), row_count(), show warnings
        if (!con->conn_status.is_in_transaction &&
                 !con->conn_status.is_in_select_calc_found_rows &&
                 !b_reserve_conn && g_hash_table_size(con->locks) == 0) {
			network_connection_pool_lua_add_connection(con); //放回连接池
	    }

特殊情况描述

  1. 查询上下文信息: found_rows(), last_insert_id(), row_count(), show warnings
  2. 事务内
  3. 显示锁

连接的状态迁移

proxy 建立的连接的状态迁移

admin 建立的连接的状态迁移

event_thread/backends/pool 的关系

注释:

  1. 每个backend为每个thread-event线程都保存了独立的连接池,例如backend为event-thread2提供了连接池et2
  2. 每个连接池中的socket按照用户名来组织,例如客户端需要以user2的用户名连接后端数据库,则从连接池的user2的链表中取出其中的一个socket,分配给客户端
  3. 在分配event-thread时,是轮询方式分配event-thread的
  4. 在选择backend的时,最基本的选择方式是轮询方式。