存储系统python api扩展模块开发

背景

千万级并发的分布式kv缓存系统构建及运营实践中所述,在我们重建了我们推荐业务的底层分布式缓存系统后,我们需要迁移我们的业务,业务方需要通过c++/java/python接口接入我们的系统,其中,python的api接入方式我们采用的是开发python扩展模块的方式实现的,在线上生产环境稳定运营近2年时间,无一次线上问题。

实现说明

  • 添加python头文件(一般是在/usr/local/include/python2.x):
#include <Python.h>
  • 定义模块包装方法数组,创建好包装函数后,需要在该数组中把它们列出来,以便python解释器导入并调用它们,如我们的模块中定义了如下数组:
static PyMethodDef pythonVdeClientApiMethods[] =
{
  {"init_vde", wrap_init_vde, METH_VARARGS, "init vde"},
  {"sync_put", wrap_sync_put, METH_VARARGS, "sync put"},
  {"sync_get", wrap_sync_get, METH_VARARGS, "sync get"},
  {"sync_remove", wrap_sync_remove, METH_VARARGS, "sync remove"},
  {"set_timeout", wrap_set_timeout, METH_VARARGS, "set timeout"},
  {"close_vde", wrap_close_vde, METH_VARARGS, "close vde"},
  {NULL, NULL}
};

说明:
init_vde、sync_put、sync_get、sync_remove、set_timeout、close_vde是提供给业务层使用的python api,实际是调用对应的包装函数的实现。

  • 增加模块初始化函数void initpython_vde_client_api()
    Python解释器规定所有的初始化函数的函数名都必须以init开头,并加上模块的名字,这样导入模块的时候,python解释器才能识别并调用模块初始化函数。对于我们的模块python_vde_client_api而言,相应的初始化函数为:
extern "C" void initpython_vde_client_api()
{
  PyObject* m;
  m = Py_InitModule("python_vde_client_api", pythonVdeClientApiMethods);
}
  • 为包装的函数添加函数实现
static PyObject* wrap_init_tair(PyObject* self, PyObject* args)
{
  if (g_python_client_api != NULL)
  {
    return Py_BuildValue("i", 1);
  }
  ...
  if (! PyArg_ParseTuple(args, "sssisi", &cs_addr, &slave_addr, &group_name, &timeout_ms, &log_file_path, &i_log_level))
  {
    return Py_BuildValue("i", -2);
  }
  ...
  if (g_python_client_api != NULL)
  {
    return Py_BuildValue("i", 1);
  }
  g_python_client_api = new python_tair_client_api();
  bool bRet = g_python_client_api->init_tair(cs_addr, slave_addr, group_name, 0, timeout_ms, log_file_path, s_log_level.c_str());
  if (!bRet)
  {
    ...
    return Py_BuildValue("i", -1);
  }
  ...
  return Py_BuildValue("i", 0);
}
static PyObject* wrap_sync_put(PyObject* self, PyObject* args)
{
  ...
}
static PyObject* wrap_sync_get(PyObject* self, PyObject* args)
{
...
}
static PyObject* wrap_sync_remove(PyObject* self, PyObject* args)
{
...
}
static PyObject* wrap_set_timeout(PyObject* self, PyObject* args)
{
...
}
static PyObject* wrap_close_tair(PyObject* self, PyObject* args)
{
...
}
  • python扩展模块数据类型

    Py_BuildValue用法参考:

    以我们的模块中put、get操作为例:
static PyObject* wrap_sync_put(PyObject* self, PyObject* args)
{
    if (! PyArg_ParseTuple(args, "ss#iii", &key, &value, &len, &len, &area, &expired))
    {
        return Py_BuildValue("i", -2);
    }
  int32_t ret = g_python_client_api->sync_put(key, value, len, area, expired, 0);
  return Py_BuildValue("i", ret);
}

static PyObject* wrap_sync_get(PyObject* self, PyObject* args)
{
   if (! PyArg_ParseTuple(args, "si", &key, &area))
  {
       return Py_BuildValue("{s:i}", "result", -2);
   }
   ...
    PyObject* obj = Py_BuildValue("{s:i,s:s#,s:i,s:l}", "result", ret, "data", data->get_data(), data->get_size(), "data_size", data->get_size(), "edate", expiretime);
 ...
  return obj;
}
  • Makefile
    python扩展模块的编译与其他常规so的编译差异不大,附上我们模块编译的makefile,仅供参考。
SOURCE_PATH=$(shell pwd)
VDE_DIR=$(shell pwd)/../../
DEF_INCLUDE = -I${VDE_DIR}/src/client -I/usr/include/python2.6 -I/usr/lib/python2.6/config
DEF_FLAGS =
DEF_LIBS = ${VDE_DIR}/src/client/libvdeclientapi.a
DEF_LIB_DIRS = 

include $(VDE_DIR)/makefile.incl

TARGET = python_vde_client_api.so
all : $(TARGET)
vdeclient_python_SOURCES = $(wildcard $(SOURCE_PATH)/*.cpp)
vdeclient_python_OBJS = $(patsubst %.cpp, %.o, $(vdeclient_python_SOURCES))

$(TARGET) : $(vdeclient_python_OBJS)
    $(CXX) $(INCLUDE) -fPIC -shared $^ -o $@ $(LDFLAGS)
clean:
    $(RM) $(TARGET) $(vdeclient_python_OBJS)
  • 其他
    我们扩展模块的第一版本实现中,内部定义了一个全局的存储系统api对象,模块导入时初始化,无法多次初始化得到多个对象,因此,业务方调用python api时无法连接多个集群,只能启动多个进程连接不同的集群做相关的业务处理,这个问题我们已在我们的第二版本中优化,支持多次创建不同的python对象,以连接多个不同的集群。

猜你喜欢

转载自blog.csdn.net/shangshengshi/article/details/78235178