最近公司在做个SNS子项目,需要把交易系统中的交易数据实时地发送到SNS子系统中。最自然的设计是修改各个交易模块,将数据向SNS系统传输。但是在评审和开发的时候遇到了不小的阻力。理由很简单,这些修改直接影响系统的核心交易模块,引入了一定的风险。综合考虑后,决定增加个类似于Memcached Functions for MySQL的备选方案。SNS子系统虽然大量地使用了cache,但是没有使用Memcached或EHCache,原因是这些cache实现无法满足所有的需求。最初的想法是在UDF中使用ActiveMQ CPP,将消息直接发送到SNS子系统的cache中。不过最终还是放弃了这种比较激进的做法,而是在UDF中将数据通过socket发送到某个Java程序,然后再对数据进行分类处理后,转发给SNS cache。
关于user-defined function(UDF),在MySQL的官方文档上有比较详细的说明。为了使用UDF,必须要动态链接mysqld,也就是不能使用--with-mysqld-ldflags=-all-static,而是应该使用--with-mysqld-ldflags=-rdynamic。UDF通常需要用C/C++编写,如果要编写一个名为xxx的UDF,那么需要定义如下的C/C++方法:
当在SQL中调用名为XXX()的UDF时,MySQL会首先尝试调用名为xxx_init() 的方法,如果该方法返回false,那么MySQL会终止SQL的执行,并返回一个error message(在xxx_init()中保存在message参数中的以null结尾的字符串,最大长度为 MYSQL_ERRMSG_SIZE)。否则MySQL会对每个row调用xxx() 方法。在所有的row都被处理后,MySQL会调用xxx_deinit() 方法来执行相应的清理工作。对于那些象SUM()之类的聚集函数,还有一些其他的C/C++函数需要编写。需要注意的是,如果采用C++编写UDF,由于C++的"name mangling"会导致MySQL无法找到对应的C++函数,因此需要将函数声明包含在extern "C" { ... }中。以下是这些函数的例子:
my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message); char *xxx(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error); void xxx_deinit(UDF_INIT *initid);
initid参数会被传递到所有三个函数中(用于在这三个函数中共享数据),它指向一个UDF_INIT结构体。关于该结构体的成员,可以参考MySQL的在线文档。args参数指向一个UDF_ARGS结构体,它有如下成员:
以下是个UDF C代码的片段:
#include#include #include #include #include #include #include #include #include "mysql.h" /// extern "C" { // my_bool send_message_open_init(UDF_INIT *initid, UDF_ARGS *args, char *message); char * send_message_open(UDF_INIT *initid, UDF_ARGS *args, char* result, unsigned long *length, char *is_null, char *error); void send_message_open_deinit(UDF_INIT *initid); // my_bool send_message_close_init(UDF_INIT *initid, UDF_ARGS *args, char *message); char * send_message_close(UDF_INIT *initid, UDF_ARGS *args, char* result, unsigned long *length, char *is_null, char *error); void send_message_close_deinit(UDF_INIT *initid); // my_bool send_message_init(UDF_INIT *initid, UDF_ARGS *args, char *message); char * send_message(UDF_INIT *initid, UDF_ARGS *args, char* result, unsigned long *length, char *is_null, char *error); void send_message_deinit(UDF_INIT *initid); } // Open my_bool send_message_open_init(UDF_INIT *initid, UDF_ARGS *args, char *message) { // Validation if(args->arg_count != 2) { strcpy(message,"usage: select send_message_open('10.15.3.68', 7777)"); return -1; } else if(args->arg_type[0] != STRING_RESULT || args->arg_type[1] != INT_RESULT){ strcpy(message,"usage: select send_message_open('10.15.3.68', 7777)"); return -1; } else { return 0; } } char * send_message_open(UDF_INIT *initid, UDF_ARGS *args, char* result, unsigned long *length, char *is_null, char *error){ ... } void send_message_open_deinit(UDF_INIT *initid) { } ...
编译:
g++ -I/usr/local/mysql/include/mysql/ -shared -o send_message.so send_message.c
编译后需要将so文件拷贝到MySQL可以加载的位置,如:
cp send_message.so /usr/local/mysql6320/lib/mysql/plugin/
在MySQL中创建UDF:
DROP FUNCTION IF EXISTS send_message_open;
DROP FUNCTION IF EXISTS send_message_close;
DROP FUNCTION IF EXISTS send_message;
CREATE FUNCTION send_message_open RETURNS STRING SONAME 'send_message.so';
CREATE FUNCTION send_message_close RETURNS STRING SONAME 'send_message.so';
CREATE FUNCTION send_message RETURNS STRING SONAME 'send_message.so';
测试:
select send_message_open('10.15.3.68', 7777);
select send_message('test message');
select send_message_close();