TUXEDO在自动语音应答系统中的使用
姜晓亮 2002/11/08
随着企业经营观念、服务意识的不断提高,自动语音应答(IVR,Interactive Voice Response)系统得到了越来越广泛的应用,如电信企业提供的170话费查询系统、1861手机话费查询系统、手机充值卡系统、各大银行提供的储蓄业务查询系统乃至航空公司提供的航班查询系统等,都是使用IVR系统来实现的。由于不需要人工介入、同时具有全天候服务的特点,有理由相信IVR还会在各个行业得到更广泛的应用。
IVR系统与中间件 通过IVR实现的业务都是用户实时参与的,具有一定的实时性,每笔事务的信息量较小,所以,都属于典型的联机事务处理(OLTP)业务。随着IVR单台集成度的不断提高,使语音呼叫的密度大大增加,形成了对IVR后台业务数据库的大量并发访问,对后台数据库压力也大大增加。 现在处理大量并发OLTP访问的通用方式是利用中间件技术,以减少后台数据库的负担,降低频繁的数据库连接与断开带来的系统开销,缩短事务处理的时间,提高数据访问的效率,同时,利用交易中间件,还可以保证交易处理的完整性,避免可能引起的数据不一致。但在IVR系统中使用中间件和中间件的传统使用方式有所不同。传统上中间件的使用是单客户端——单访问,而在IVR中有可能是单客户端——多并发访问,这种不同给整个系统的实现带来了挑战,只有采用特定的手段,才能使两者紧密结合在一起,形成一个高性能的IVR系统。 当前流行的优秀中间件有BEA TUXEDO,IBM TXSeries,TONG LINK/EASY等产品,本文仅对BEA TUXEDO在IVR中的使用进行阐述。
IVR系统的实现方式 下面是当前流行的IVR实现方式(以单机900线的IVR为例): 1. 单线程实现 此方式利用了有限状态机的原理,利用一个线程不断地依次监控话路的状态,并执行相应的操作,并完成其状态的迁移。要使一次轮询的时间足够小,程序中执行的单个操作以及状态的迁移,都必须只占用极短的时间,也就是说,程序中只能使用非阻塞(异步)的操作方式。 2. N线程实现 该实现方式和单线程方式的原理是一样的,只是基于系统稳定和处理能力方面的考虑,利用三个(或N个)线程分担处理呼叫,这样每个线程分担300(900/N)线语音呼叫。同样,程序中只能使用非阻塞(异步)的操作方式。 3. 多线程实现 呼叫处理上的多线程方式和前面介绍的单/N线程方式的实现原理有着本质的区别,每当有呼叫时,系统就产生一个线程,用来监控、跟踪用户操作的全过程,当呼叫结束后,此线程也结束。由于线程间互不影响,线程内部可以采用阻塞(同步)的方式进行数据操作。
TUXEDO编程接口 TUXEDO服务器端为了保证与多个客户端的通信,为每个客户端建立一块独立的连接资源,称为上下文(Context);而客户端本身也分配了专用的资源,用来存储发送/接收的数据。在新版的TUXEDO中,每个Context分别对应一个User License。 需要特别注意的是,TUXEDO对每个应用连接的访问请求进行了限制,具体来说,就是在一个Context上,未完成的访问请求数目不能超过50个,如果到达了50个,后续的请求就会返回“Elimit”的错误,直到请求队列长度小于50为止。 1.单线程与多线程模式 在TUXEDO中,涉及单线程还是多线程的关键API是tpinit,主要作用是使Server端分配相应的资源,并建立连接。涉及到的API定义如下:tpinit(),tpgetctxt(),tpsetctxt()。 (1)单线程/进程
在单线程/进程模式下,通过调用tpinit,使Server端为每个Client端建立一个独立的Context。如图1所示,每个客户端都利用一个单独的连接与服务器进行通信,独享一个请求队列。 在单线程/进程的模式下,TUXEDO API的使用不需要任何专门设置: tpinit( (TPINIT *)NULL) //建立连接 (2)多线程
在多线程模式下,调用tpinit就Server端为每个线程建立一个独立的Context。如图2所示,每个线程利用一个单独的连接与服务器进行通信,独享一个请求队列。 在多线程的模式下,TUXEDO API的使用按以下步骤进行: Ⅰ.在父进程中分配tpinit参数空间: tpinitbuf = tpalloc(TPINIT, NULL, TPINITNEED(0)); Ⅱ.在父进程中设置多线程(多上下文)标记: tpinitbuf->flags = TPMULTICONTEXTS; Ⅲ.在线程中建立与Server连接,分配独立Context资源: tpinit(tpinitbuf); Ⅳ.在线程中保存Context资源句柄:tpgetctxt(&ctxt, 0); Ⅴ.在线程中设置此线程使用的Context资源:tpsetctxt(ctxt, 0); Ⅵ.线程中继续其他API使用,使用方式不受影响。 注:TUXEDO7.1(含)以后的版本,才支持此多线程模式。 (3)伪多线程
所谓伪多线程模式,是指整个应用利用了多线程机制,在每个线程中也调用tpinit建立了与TUXEDO Server端的应用连接,但是,实际上Server端并没有为每个线程建立一个独立的Context,而是所有线程共享一个Context资源。如图3所示,所有线程都利用一个共享的连接与服务器进行通信,共享一个请求队列。 2.同步/异步调用模式 TUXEDO的请求/响应方式有同步和异步两种。同步方式中,请求方要一直等待到有响应后,再执行其他操作,也称为阻塞方式; 异步方式中,请求方发送出请求后,立即得到一个响应句柄,继续执行其他操作,在合适的时候,再利用响应句柄,得到此请求的响应结果,也称为非阻塞方式。 涉及到API定义如下: tpcall(),tpacall(),tpgetreply()。其中,tpcall是同步调用方式,tpacall是发起异步请求,tpgetreply是得到异步请求的结果。 需要注意的是,只有将tpacall和tpgetreply的参数flags设置为TPNOBLOCK,才能实现真正的非阻塞方式,否则,仅仅能够实现发送请求和取结果的异步分离,不能保证请求和取结果两个动作本身的非阻塞化。 前面我们提到,TUXEDO对每个Context上未完成的访问请求数目进行了限制,不能超过50个,这个限制对于同步/异步方式同样有效,即未完成的tpcall的数目,或者已经tpacall成功,但是未用tpgetreply成功取回结果的数目,都不能超过50个。
IVR与TUXEDO的结合 1. IVR单线程实现 在单进程/线程IVR中,由于只有一个进程/线程,所以,利用TUXEDO的单线程/进程方式即可,考虑到数据访问的速度不可能一直稳定在毫秒级,所以,必须利用TUXEDO真正的异步调用模式,保证整个IVR系统轮询和状态迁移的时效性。 这里要注意900线的IVR存在一个Context只能对应50个未完请求的限制。但900线IVR中,超过50个并发TUXEDO请求的可能性是存在的,而且在系统繁忙阶段,一旦系统性能稍有下降,后台数据库操作时间超过3~5秒,在这段时间内,累积的tpacall请求超过50个的可能性非常大。 因此,对应一个大规模的IVR系统,如果利用单线程来实现,在其中又结合了TUXEDO, 由于不能突破50个的限制,很难提供高效、稳定的实现方案。 2. IVR N线程实现 在N线程 IVR中,可以利用TUXEDO的单线程和多线程方式,甚至可以利用伪多线程方式,同样,必须利用TUXEDO真正的异步调用模式。 如果利用TUXEDO单线程或者伪多线程方式,数字对比仍然是“900∶50”,和单线程IVR一样的问题还会出现。如果我们利用TUXEDO多线程方式,数字对比就是“900∶N*50”,这样,就可以解决单线程IVR中遇到的不能突破50个请求的限制问题。因此,对应一个大规模的IVR系统,如果利用N线程实现,在其中结合使用TUXEDO多线程方式,由于将限制扩大到了N*50,所以,系统繁忙阶段,也能够保证提供出高效、稳定的实现方案。 3. IVR多线程实现 在多线程IVR中,同样可以利用TUXEDO的单线程、多线程、伪多线程方式,根据我们对多线程IVR的分析,各线程相对独立,互不影响,利用TUXEDO的同步调用模式就可以了。 如果利用TUXEDO单线程或者伪多线程方式,我们仍然会遇到“请求”限制问题。如果利用TUXEDO多线程方式,则可以解决“请求”限制问题,但是,各线程频繁地TPINIT、TPTERM会占用系统资源,而且多线程生成的多Context,并发占用了大量的license,如果license数量是60,那么这时遇到的问题将是license限制,购买大量的license也就意味着投资的增加,否则,系统繁忙阶段,就不能提供稳定的服务。 通过分析,我们发现,每线IVR对应一个线程,占用一个Context,独享50个请求的限制,非常浪费,可以考虑多个线程共享一个Context。 我们可以这样实现: 在父进程中通过9次tpinit可得到9个Context访问句柄,将900线IVR分成9组,每组100线,每100线共享一个Context,在这100线各自对应的线程中,先调用tpsetctxt,指定此线程利用的Context,这样就形成“100∶50”的比例关系,完全可以保证系统在繁忙阶段提供高效的服务。IVR多线程实现方式如下图所示:
在多线程的模式下,TUXEDO API的使用需要专门步骤: (1) 在父进程中分配tpinit参数空间: tpinitbuf = tpalloc(TPINIT, NULL, TPINITNEED(0)); (2) 在父进程中设置多线程(多上下文)标记: tpinitbuf->flags = TPMULTICONTEXTS; (3) 在父进程中建立与Server连接,分配独立Context资源:tpinit(tpinitbuf); (4) 在父进程中保存Context资源句柄: tpgetctxt(&ctxt1, 0); (5) 重复N次步骤(3)和(4)。 (6) 在某组IVR线程中都设置线程使用的共享Context资源:tpsetctxt(ctxtN, 0); (7) 线程中继续其他API使用,使用方式不受影响。
结论 由于IVR系统实现方式的不同,TUXEDO中间件使用方式的不同,系统规模的不同,为我们的系统设计提供了众多组合的可能,同时,也只有了解这些不同的实现方式,才能在众多组合中选择出有效方案。 通过以上介绍,我们知道,从功能上说,大容量IVR系统与TUXEDO等中间件进行结合是一种趋势。从系统实现上,我们也可以相信,IVR与TUXEDO一定可以提供一种有效的实现方式组合,架构出高效、稳定、投资节省的IVR系统。