高效、简洁:全新的数据库引擎

最近对蓝星际语音平台数据库访问的接口进行了优化和功能升级,使用Koodoo语言来访问数据库更加简单方便了,而且在 语音平台核心部分采用了连接池机制,对于大容量应用性能提升十分明显,而且可以不用原来频繁采用的线路间函数调用, 可以在物理线路上直接写SQL语句来访问数据库。

那我们就先来谈谈连接池。 我们做语音IVR或CTI方面应用,在很多情况下单机的线路数是很多的,蓝星际语音平台的很多电信用户都达到了单机24E1, 也就是720线/机,因为Koodoo语言的脚本是部署在每条单独的线路上运行的,如此高的容量,如果每条线路都去连接数据 库的话,除了数据库本身有连接数限制以外(很多商品化数据库以连接数作为许可卖钱),大量的连接将造成性能的急剧 下降。

为了解决这个问题,市场上的大部分语音平台采用外部数据库网关的方式,流程编写者需要在网关上用C/C++或其他高级语言 编写数据库访问的服务,语音平台的流程脚本则通过TCP/IP等方式访问数据库网关,这种方式的弊端很明显,一是需要部署 额外的数据库网关,增加管理复杂性,二是存在通讯开销会降低性能,而且因为使用网络通讯,因此对于数据结果集大小有 限制。此外也很难进行调试,故障点增加了,一旦出问题往往不知道在哪个环节。

Koodoo语言提供了虚拟线路和线路间通讯的机制,我们可以在虚拟线路上运行编写好的数据库访问服务的脚本,把常见的业务 封装成函数,函数内是实际的SQL语句,在物理线路上编写同名的函数,参数顺序也一样,物理线路上的函数会自动调用数据库 服务脚本中的同名函数。每个虚拟线路相当于一个连接,可以配置多几条虚拟线路运行相同的数据库服务脚本,我们的系统会 自动进行分配,让当前最空闲的虚拟线路去进行处理,这其实就是连接池的概念。因为物理线路和虚拟线路是运行在同一个进程 空间内的线程,他们之间通过内存进行消息通讯的,所以性能很好,而且虚拟线路上的脚本和物理线路上的脚本都是用Koodoo 语言来完成,整合起来也还算方便。 额外的好处是,线路间函数调用的方式要求把业务分解成一个个的函数,等于强制的模块化,对于业务功能的整理和维护带 来了好处。

但是,这种方式还是存在一些不方便之处,比如相同的业务函数要写两遍,要保证相同的函数名字和参数顺序,如果业务发生 修改,则同时要修改两个地方,难免挂一漏万,也增加调试的复杂性,也就是不方便重构。此外,在开发平台没办法调试, 因为要两个脚本在两条线路上运行,所以往往在开发平台写出来的脚本不能部署到运行平台,还要根据上面的方法进行改写, 拆成服务端脚本和调用端脚本。最后,因为封装成函数,如果有多条记录返回,则不太好处理,因为函数只是参数形式返回, 如果要在一个结果集里面循环处理,则颇为不便。

从V1.87版本开始,系统内嵌连接池,这种方法可以大大简化数据库的编程。 在运行平台配置文件BsTelRun.cfg中增加了一个新的配置项:

	DB_MAX_CONN = 5   // 最大数据库连接数,如为0则不受限制

按上面配置后,假设1000条线路上都运行相同的脚本,他们都连接相同的数据库,但根据应用流程在中途执行不同的SQL操作, 那么在脚本开始部分,使用DbOpen()进行连接的时候,系统内部会自动维护一个连接池(对于相同连接参数),池里面最多只有 5个连接。

那么在某线路在具体执行SQL语句的时候究竟采用哪个连接呢?系统会从连接池里面找一个空闲的连接执行SQL操作,结果集则返 回给调用的线路。如果当前连接池里面的全部连接都在忙,则系统会等候,直到有空闲的连接,或者超时。 由此可见,这种方法是很高效的,不需要额外的虚拟线路,而且数据库操作的函数完全做到了兼容。在开发平台写好的脚本, 可以直接部署到运行平台。

如果物理线路很多,SQL调用比较繁忙,则可以通过调大配置文件中的DB_MAX_CONN配置项,来达到优化的目的。 当然,如果你还是喜欢线路间函数调用的方式,或者原来的代码是线路间函数调用的方式,不想花时间改用新方式,也没有问题, 照样可以工作的很好,系统做到了完全兼容。

除了性能提升和编程方式的简化,新版本增加了一个函数,可以将结果集和数组绑定,这是个非常方便的功能:

3.9 执行一个SQL命令并将结果集映射到数组: DbExecMap(iHd, cmd, m, indexType, keyFieldName);
参  数:iHd - 操作句柄,已经打开的数据库连接
         cmd - 合法的SQL命令,可以是SQL语句或存储过程调用
         m - 数组变量: m[下标1][下标2], 下标1表示行(记录), 下标2表示列(字段)
         indexType - 指出下标2的类型, 0-用0开始的连续数字作下标,
               1-用字段名作下标, 2-将字段名转换成大写作下标, 3-将字段名转换成小写作下标
         keyFieldName - 如果为字符串,则表示键值字段名,如果为""则表示下标1为整型记录号, 从1开始
               如果为整型数,表示指出字段编号,为从0开始的顺序号

原来要取结果,必须用下面几个函数配合使用,比较麻烦:

3.2 执行一个SQL命令: DbExec(iHd, sCmd);
3.3 得到总记录数: DbRows(iHd, iNum);
3.4 读取字段值: DbGetField(iHd, vField, iRow, sVar);

新版本只要用一个DbExecMap()就轻松搞定了,以数组表示的结果集,显得更加直观,也能够修改结果集的内容 (只是改数组的值,不会回写到数据库)。

我还是举例来说明一下吧,“代码不会骗人”: 假设数据库里面有一张表tUsers, 里面有这几个字段:Name(姓名),Sex(性别),Note(说明);其中姓名是Key字段,每条记录的 Name都不相同。那么我们可以执行这样的操作:

m = 0;  // 定义一个变量,供存放查询结果集
DbExecMap(hd, "select Name, Sex, Note from tUsers", m, 1, "Name");  // 用字段名做列下标,用"Name"字段值做行下标

那我们就可以这样来直接操作结果集了:

if( m["张三"]["Sex"]=="男" )  // 看看张三这个人是不是男的
   DispInfo(0, m["张三"]["Note"]);  // 如果是,则把张三的说明内容显示出来

当然,数组m会占用一定的内存,如果要将m占用的内存释放,可以这样:

m = 0;  // 把m变成整数0

最新版本的Koodoo语言还增加了集合运算,这样可以对异构的数据库结果集进行运算:

x = m + n;   // 如果m和n都是数组,则x是m和n的并集
x = m - n;   // 如果m和n都是数组,则x是m和n的差集
x = m * n;   // 如果m和n都是数组,则x是m和n的交集
m = m >> i;  // 如果m是数组,则去掉i为下标的成员

连接池让蓝星际语音平台具有现代应用服务器的性能,结果集绑定到数组则让数据处理更加直观,高效+直观,就是 我们所追求的目标。

bluesen 2007.3 于深圳