1.什麼是DaemonPlugin
顧名思義,daemon plugin就是一種用來在後台運行的插件,在插件中,我們可以創建一些後台線程來做些有趣的事情。大名鼎鼎的handlesocket就是一個daemon plugin。而在mysql5.6中,也是通過daemon plugin來實現了memcached功能。
2.為什麼使用DaemonPlugin
就像handlersocket,大膽的想象力能夠創造無限的可能。MySQL Plugin的誘人之處在於其與Mysqld處於同一進程空間中,可以利用任何mysql內核的函數。Handlersocket在實現時,構造出相關參數並直接調用存儲引擎的接口,從而穿越了語法解析和優化部分,對於邏輯簡單的查詢而言,可以極大的提高效率。
另外所有的plugin都提供了showstatus和show variables 命令的接口,因此我們可以利用plugin來顯示一些我們想要的信息,例如mysql內部的全局變量值。
總的來說,daemon plugin可以做到以下幾點:
1) 創建後台線程,擴展mysql功能
2)擴展status和variables信息
Deamon plugin在mysqld啟動時進行初始化,執行完init函數, 因此並不適用於與服務器進行通信的情形,mysql也沒有提供任何相關的API
3.如何編寫daemonplugin
這裡涉及到的一些結構體,對其他類型的plugin而言也是通用的
1)st_mysql_plugin
無論聲明哪種plugin,至少要包含該結構體
字段
類型
描述
type
int
用於描述plugin的類型,隨著版本更新,越來越多,在5.5中包含8種類型:
MYSQL_UDF_PLUGIN
MYSQL_STORAGE_ENGINE_PLUGIN
MYSQL_FTPARSER_PLUGIN
MYSQL_DAEMON_PLUGIN
MYSQL_INFORMATION_SCHEMA_PLUGIN
MYSQL_AUDIT_PLUGIN
MYSQL_REPLICATION_PLUGIN
MYSQL_AUTHENTICATION_PLUGIN
info
void*
用於指向特定的plugin描述符結構體,在daemon plugin中結構體為st_mysql_daemon,一般第一個字段都是插件接口的版本號
name
const char*
plugin的名字,需要和安裝時的名字一致
author
const char*
plugin的作者信息,會在i_s.plugins表中顯示
descry
const char*
描述插件
license
ubt
插件許可證:PLUGIN_LICENSE_PROPRIETARY
PLUGIN_LICENSE_GPL
PLUGIN_LICENSE_BSD
init
int (*init)(void *)
當插件被加載時或者mysqld重啟時會執行該函數,一般我們會在這裡創建好後台線程
deinit
int (*deinit)(void *);
當插件被卸載時做的工作,例如取消線程,清理內存等
version
unsigned int
plugin的版本信息
status_vars
st_mysql_show_var*
描述在執行show status時顯示的信息
system_vars
st_mysql_sys_var **
描述在執行show variables顯示的信息
__reserved1
void*
注釋說為檢查依賴而保留,不太明白,直接設為NULL即可
flags
unsigned long
5.5之後增加的字段,plugin的flag:0、
PLUGIN_OPT_NO_INSTALL(不可動態加載)、PLUGIN_OPT_NO_UNINSTALL(不可動態加載)
在plugin.h裡提供了宏,我們可以通過宏來聲明插件:
mysql_declare_plugin(my_plugin)
{},
{},
……
mysql_declare_plugin_end;
在兩個宏之間,我們可以聲明多個插件,也就是說在一個文件裡,我們可以定義多個Plugin。
上面提到三個結構體,需要在plugin裡單獨進行定義:
a. st_mysql_daemon
該結構體只包含一個字段,用於聲明daemon plugin的版本信息
字段
類型
描述
interface_version
int
一般值為
MYSQL_DAEMON_INTERFACE_VERSION
也許有同學注意到了,這上面提到了兩個version,即st_mysql_plugin裡的version和st_mysql_daemon裡的version,這兩者是不相同的。
st_mysql_plugin.version記錄的是該plugin的版本號,使用16進制表示,低8位存儲副版本號,其他存儲主版本號。
而st_mysql_daemon裡存儲的是daemonplugin接口的版本號,針對不同的mysql版本,其接口可能會發生變化。
b. st_mysql_show_var
結構體如下:
字段
類型
描述
name
const char*
字段名
value
char*
字段值,我們可以將指針綁定到一個全局變量上
type
enum enum_mysql_show_type
數據類型,包括(括號裡對應value的指針類型):
SHOW_BOOL (bool *)
SHOW_INT (unsigned int *)
SHOW_LONG (long *)
SHOW_LONGLONG (long long*)
SHOW_DOUBLE (double *)
SHOW_CHAR (char *)
SHOW_CHAR_PTR (char **)
SHOW_ARRAY(st_mysql_show_var * )
SHOW_FUNC(
int (*)(MYSQL_THD, struct st_mysql_show_var*, char *) )
該結構體用於定義show status時顯示的值,可以看出在type字段最後兩個相對其他比較特殊。
當type類型為SHOW_ARRAY時,表明name字段並不是一個值,而是指向一個st_mysql_show_var類型的數組,數組以{0,0,0}結束,當前元素的name會成為引用數組元素name的前綴。
當type類型為SHOW_FUNC時,value值為一個函數指針,參數包括當前線程的THD,st_mysql_show_var* 以及一個大小為1024字節的內存區域頭指針;函數的目的是為了填充第二個字段的值,而buf作為存儲構建結構體的內存空間;這樣可以允許我們先做一些計算,然後顯示計算的結果。
c. st_mysql_sys_var
該結構體內包含一個宏MYSQL_PLUGIN_VAR_HEADER,包含了變量結構體的公共部分。
在這裡,MySQL巧妙的使用了C的宏定義,例如,當我們定義一個variable:
struct st_mysql_sys_var* my_sysvars[]= {
MYSQL_SYSVAR(my_var),
NULL}
展開MYSQL_SYSVAR看看:
#define MYSQL_SYSVAR_NAME(name)mysql_sysvar_ ## name
#define MYSQL_SYSVAR(name) \
((struct st_mysql_sys_var *)&(MYSQL_SYSVAR_NAME(name)))
那麼MYSQL_SYSVAR(my_var)被轉換為:
((struct st_mysql_sys_var *)mysql_sysvar_my_var
因此,在這之前,我們首先要先創建好結構體。針對不同的數據類型,提供了許多宏來創建,分為兩種:一種以MYSQL_SYSVAR開頭的全局變量(可以set global),另外一種是MYSQL_THDVAR開頭的session變量
MYSQL_SYSVAR_BOOL(name, varname, opt, comment, check, update, def)
char
MYSQL_SYSVAR_STR(name, varname, opt, comment, check, update, def)
char*
MYSQL_SYSVAR_INT(name, varname, opt, comment, check, update, def, min, max, blk)
int
MYSQL_SYSVAR_UINT(name, varname, opt, comment, check, update, def, min, max, blk)
unsigned int
MYSQL_SYSVAR_LONG(name, varname, opt, comment, check, update, def, min, max, blk)
long
MYSQL_SYSVAR_ULONG(name, varname, opt, comment, check, update, def, min, max, blk)
unsigned long
MYSQL_SYSVAR_LONGLONG(name, varname, opt, comment, check, update, def, min, max, blk)
long long
MYSQL_SYSVAR_ULONGLONG(name, varname, opt, comment, check, update, def, min, max, blk)
unsigned long long
MYSQL_SYSVAR_ENUM(name, varname, opt, comment, check, update, def, typelib)
unsigned long
MYSQL_SYSVAR_SET(name, varname, opt, comment, check, update, def, typelib)
unsigned long long
以下是session變量定義宏
MYSQL_THDVAR_BOOL(name, opt, comment, check, update, def)
char
MYSQL_THDVAR_STR(name, opt, comment, check, update, def)
char*
MYSQL_THDVAR_INT(name, opt, comment, check, update, def, min, max, blk)
int
MYSQL_THDVAR_UINT(name, opt, comment, check, update, def, min, max, blk)
unsigned int
MYSQL_THDVAR_LONG(name, opt, comment, check, update, def, min, max, blk)
long
MYSQL_THDVAR_ULONG(name, opt, comment, check, update, def, min, max, blk)
unsigned long
MYSQL_THDVAR_LONGLONG(name, opt, comment, check, update, def, min, max, blk)
long long
MYSQL_THDVAR_ULONGLONG(name, opt, comment, check, update, def, min, max, blk)
unsigned long long
MYSQL_THDVAR_ENUM(name, opt, comment, check, update, def, typelib)
unsigned long
MYSQL_THDVAR_SET(name, opt, comment, check, update, def, typelib)
unsigned long long
宏中參數描述如下:
參數名
描述
name
變量名,通過show variables顯示的變量名,
varname
C/C++變量,用來給name賦值
opt
變量選項:
PLUGIN_VAR_READONLY (變量只讀)
PLUGIN_VAR_NOSYSVAR (不是系統變量,只能在啟動時命令行加上)
PLUGIN_VAR_NOCMDOPT (與上述相反)
PLUGIN_VAR_NOCMDARG (命令行,必須沒有參數)
PLUGIN_VAR_RQCMDARG (命令行,不需有參數)
PLUGIN_VAR_OPCMDARG (命令行,參數可選)
PLUGIN_VAR_MEMALLOC (如果被設置,會分配內存存儲字符串的值,否則只能通過命令行來分配)
comment
變量的描述信息
check
函數指針,用來檢查參數是否有效,如果無效,則拒絕修改,函數原型為:
int check(MYSQL_THD thd, struct st_mysql_sys_var *var, void *save, struct st_mysql_value *value);
其中thd為當前線程,var是我們定義的系統變量,save指針用來存儲數據,value是傳遞給函數的值,我們可以從value中獲取值,並將其保存到save中。
update
函數指針,當發生更新時,可以調用該函數做一些額外處理,函數原型為:
void update(MYSQL_THD thd, struct st_mysql_sys_var *var, void *var_ptr, const void *save);
def
變量的默認值
min
最小值 (min及以下兩個需要為數值類型)
max
最大值
blk
塊大小,變量值需要是blk的整數倍
typelib
當變量類型為ENUM和SET類型時,使用該結構體定義:st_typelib
在check函數裡,我們從var中提取出新的值,並存儲到save指針中。在update函數中,我們可以從save指針提取出新值,st_mysql_value正是用於提取新值的結構體,成員為函數指針,如下:
函數指針
描述
int (*value_type)(struct st_mysql_value *)
用於獲取值的類型:
MYSQL_VALUE_TYPE_STRING 0
MYSQL_VALUE_TYPE_REAL 1
MYSQL_VALUE_TYPE_INT 2
const char *(*val_str)(struct st_mysql_value *, char *buffer, int *length);
獲取字符串
int (*val_int)(struct st_mysql_value *, long long *intbuf);
獲取整數
例如:
Int a ;
Var->val_int(var, &a);
一些系統變量允許為SET或者ENUM類型,這時候,需要通過額外的結構體st_typelib來定義:
字段
類型
描述
count
unsigned int
type_names裡的元素個數
name
const char*
plugin無需設置
type_names
const char**
存儲集合內的元素值
type_lengths
unsigned int*
plugin無需設置
舉個簡單的例子,比如我們想定義一個INT變量,該變量為只讀類型,即不允許通過set命令修改,最大為1000000,最小為0 ,默認值為 256:
Static int xx
Static MYSQL_SYSVAR_INT(xx_var, xx , PLUGIN_VAR_READONLY , “a read onlyint var”, NULL, NULL,256, 0, 1000000, 10)
例如,如果plugin的名字為name,則變量的全名為name_xx_var。我們可以將系統變量通過命令行來賦值,也可以寫在配置文件中,變量名為name-xx-var,賦值必須能被10整除,否則將被mysql拒絕。
定義一個枚舉類型,session變量
static const char *mode_names[] = {
"NORMAL", "TURBO","SUPER", "HYPER", "MEGA"
};
static TYPELIB modes = { 5, NULL,mode_names, NULL };
static MYSQL_THDVAR_ENUM(mode,PLUGIN_VAR_NOCMDOPT,
"one of NORMAL, TURBO, SUPER, HYPER,MEGA",
NULL, NULL, 0, &modes);
該變量屬於枚舉類型,每個session擁有自己的值,並且可在運行時修改;注意,當為session變量時,我們需要通過THDVAR(thd,mode)這樣一個宏來獲取相應的變量值
另外,對於Plugin中的系統變量無需加互斥鎖,MySQL會自動給我們加上。
實例:啟動一個後台線程,每隔5秒監控當前進程的狀態(記錄到log中),使用系統變量來控制是否記錄log,並在show status顯示記錄的次數
#include <string.h>
#include <plugin.h>
#include<mysql_version.h>
#include <my_global.h>
#include <my_sys.h>
#include<sys/resource.h>
#include <sys/time.h>
#define MONITORING_BUFFER1024
/*以下三個變量在sql/mysqld.cc中聲明,因此需要extern*/
extern ulong thread_id; //當前最大線程id
extern uint thread_count; //當前線程數
extern ulong max_connections;//最大允許連接數
static pthread_tmonitoring_thread; //線程id
static int monitoring_file; //日志文件fd
static my_bool monitor_state= 1; //為1表示記錄日志,為0則否
static ulong monitor_num = 0; //後台線程循環次數
static struct rusage usage;
/*創建系統變量,可以通過配置文件或set global來修改*/
MYSQL_SYSVAR_BOOL(monitors_state,monitor_state,
PLUGIN_VAR_OPCMDARG,
"disable monitor if 0,default TRUE",
NULL, NULL, TRUE);
struct st_mysql_sys_var*vars_system_var[] = {
MYSQL_SYSVAR(monitors_state),
NULL
};
/*創建status變量,可通過showstatus查看*/
static structst_mysql_show_var sys_status_var[] =
{
{"monitor_num", (char *)&monitor_num, SHOW_LONG},
{0, 0, 0}
};
/*線程函數,後台線程啟動後,會持續執行該函數*/
pthread_handler_tmonitoring(void *p)
{
char buffer[MONITORING_BUFFER];
char time_str[20];
while(1) {
/*每隔5秒記錄一次,我們也可以把5修改為一個可配置的系統變量*/
sleep(5);
if (!monitor_state)
continue;
monitor_num++;
/*獲取當前時間,mysql自有函數*/
get_date(time_str, GETDATE_DATE_TIME,0);
snprintf(buffer, MONITORING_BUFFER,"%s: %u of %lu clients connected, "
"%lu connections made\n",
time_str, thread_count,
max_connections, thread_id);
/*使用getrusage函數來獲得當前進程的運行狀態,具體man getrusage*/
if (getrusage(RUSAGE_SELF, &usage)== 0){
snprintf(buffer+strlen(buffer) ,
MONITORING_BUFFER, "user time:%d,system time:%d,"
"maxrss:%d,ixrss:%d,idrss:%d,"
"isrss:%d, minflt:%d, majflt:%d,"
"nswap:%d,inblock:%d,oublock:%d,"
"msgsnd:%d, msgrcv:%d,nsignals:%d,"
"nvcsw:%d, nivcsw:%d\n",
usage.ru_utime,
usage.ru_stime,
usage.ru_maxrss,
usage.ru_ixrss,
usage.ru_idrss,
usage.ru_isrss,
usage.ru_minflt,
usage.ru_majflt,
usage.ru_nswap,
usage.ru_inblock,
usage.ru_oublock,
usage.ru_msgsnd,
usage.ru_msgrcv,
usage.ru_nsignals,
usage.ru_nvcsw,
usage.ru_nivcsw);
/*寫入monitoring_file文件*/
write(monitoring_file, buffer,strlen(buffer));
}
}
}
/*系統啟動或加載插件時時調用該函數,用於創建後台線程*/
static int monitoring_plugin_init(void*p)
{
pthread_attr_t attr;
char monitoring_filename[FN_REFLEN];
char buffer[MONITORING_BUFFER];
char time_str[20];
monitor_num = 0;
/*format the filename
*The fn_format() function is designed tobuild a filename and path compatible
with the current operating system given aset of parameters. More details on its
functionality can be found inmysys/mf_format.c.
* */
fn_format(monitoring_filename,"monitor", "", ".log",
MY_REPLACE_EXT |MY_UNPACK_FILENAME);
unlink(monitoring_filename);
monitoring_file = open(monitoring_filename,
O_CREAT | O_RDWR, 0644);
if (monitoring_file < 0)
{
fprintf(stderr, "Plugin'monitoring': "
"Could not create file '%s'\n",
monitoring_filename);
return 1;
}
get_date(time_str, GETDATE_DATE_TIME, 0);
sprintf(buffer, "Monitoring started at%s\n", time_str);
write(monitoring_file, buffer,strlen(buffer));
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
if (pthread_create(&monitoring_thread,&attr,
monitoring, NULL) != 0){
fprintf(stderr, "Plugin'monitoring': "
"Could not create monitoringthread!\n");
return 1;
}
return 0;
}
/*卸載插件時調用*/
static intmonitoring_plugin_deinit(void *p)
{
char buffer[MONITORING_BUFFER];
char time_str[20];
/*通知後台線程結束*/
pthread_cancel(monitoring_thread);
pthread_join(monitoring_thread, NULL);
get_date(time_str, GETDATE_DATE_TIME, 0);
sprintf(buffer, "Monitoring stopped at%s\n", time_str);
write(monitoring_file, buffer,strlen(buffer));
close(monitoring_file);
return 0;
}
struct st_mysql_daemonmonitoring_plugin = { MYSQL_DAEMON_INTERFACE_VERSION };
/*聲明插件*/
mysql_declare_plugin(monitoring)
{
MYSQL_DAEMON_PLUGIN,
&monitoring_plugin,
"monitoring",
"yinfeng",
"a daemon montor,log process usagestate",
PLUGIN_LICENSE_GPL,
monitoring_plugin_init,
monitoring_plugin_deinit,
0x0100,
sys_status_var,
vars_system_var,
NULL
}
mysql_declare_plugin_end;
參考資料:
1.《mysql plugin development》
2. Mysql5.1.48 source code
轉載請署名:印風