cx_Oracle CQN 回调在路由器上的使用

cx_Oracle CQN Callback usage across a router

我正在使用 python 编写一个 flask 网页,它维护来自 Oracle DB (v11.2) 的缓存查询结果。当我的缓存失效时,我想使用连续查询通知 (CQN) 来获取通知。我能够注册 CQN 通知侦听器,但是当 table 更改时我的回调没有被调用。请注意,数据库在外部托管在 AWS 上,我的开发(和最终生产)计算机位于公司路由器后面的 LAN 上。

我使用此代码注册 CQN 回调:

import cx_Oracle
def CQNCallback(message):
    print("Callback triggered.")

ops = cx_Oracle.OPCODE_INSERT | cx_Oracle.OPCODE_DELETE | cx_Oracle.OPCODE_UPDATE
conn = cx_Oracle.connect('userID','passwd','IP:port/ID',events=True)
subs = conn.subscribe(callback = CQNCallback, operations=ops, rowids = False, timeout=3000)
subs.registerquery('select * from MYTABLE')

此代码成功执行,我在数据库中看到一个新条目 USER_CHANGE_NOTIFICATION_REGS table:

REGID: 269
REGFLAGS: 0
CALLBACK: net8://(ADDRESS=(PROTOCOL=tcp)(HOST=<gateway>)(PORT=56824))?PR=0
OPERATIONS_FILTER: 14
CHANGELAG: 0
TIMEOUT: 2987
TABLE_NAME: MYTABLE

"gateway" 是我公司 gateway/router 的 public IP 地址,端口 56824 似乎是由 cx_Oracle 分配的。 为了验证我的开发机器是否正在侦听这些通知,我 运行 netstat 并看到计算机正在侦听 IPv4 和 6

上的端口 56824
Proto  Local Address     Foreign Address    State
TCP    0.0.0.0:56824     <devComputer>:0    LISTENING
TCP    [::]:56824        <devComputer>:0    LISTENING

但是,当我使用 SQLDeveloper 手动更新或插入(并提交!)对 MYTABLE 的更改时,我的回调从未被调用而且我不知道为什么(我确保注册在更改时没有超时被制造了)。

我的推测:我怀疑数据库似乎使用与本地进程相同的端口号——这表明存在 NAT 问题。也许通知的 tcp 连接是由数据库发起的,它正在向 gateway:56824 发送通知消息。如果是这样,路由器可能不知道如何处理该消息并因此将其丢弃。如果是这种情况,我如何配置 cx_Oracle 以正确处理中间路由器?

我发现一个 OTN 线程表明这是 Oracle 11.2.0.1 (https://community.oracle.com/thread/2292328?tstart=0) 中的一个已知错误。我想我的新任务是说服数据库管理员升级数据库并重试。我的问题现在变成了 "What is the best way to bribe/convince a DB admin to upgrade Oracle on a system that he hasn't had to think about since 2009 without being told to 'go pound sand'?"

通过执行两项任务,我能够使用上述代码的测试版本接收更改通知:

  1. 调用上面 link 中提供的 "set events" hack。这很好,因为即使我不能说服 DBA 将服务器软件更新到 11.2.0.4,我仍然可以继续前进。
  2. 在路由器上设置端口转发,将指定端口上的传入连接转发到我的内部 IP。我可以在家里用笔记本电脑控制它,但我需要说服我公司的 IT 支持人员为我工作的生产服务器实施端口转发。

上面的第 2 项证实了我最初的怀疑,即如果您的数据库位于 LAN 外部的网络上,则无法使用更改通知(如果不破解您的路由器)。原因是,不是本地客户端打开用于传达更改的长期连接,而是从数据库端启动更改通知连接,并为每个更改通知创建一个新连接

然后我能够在工作 LAN 上执行上述 2 个步骤并成功触发我的回调代码。我需要通过修改订阅创建来为订阅指定一个固定端口,以便我可以在公司防火墙上正确实现端口转发:

subs = conn.subscribe(callback = CQNCallback, operations=ops, rowids = False, timeout=3000, port=50000)

我了解到的另一件可以帮助其他人的事情是,您不能只关闭 python shell 或使用 cx_Oracle 注册更改通知的 ctrl-c 代码。您需要显式调用 "del subs" 否则订阅将在数据库中永久注册(永远?),没有任何机制可以删除它。要在开发期间绕过此限制,请务必明确指定订阅的短超时时间。这样,如果您错误地按下了 ctrl-c,您只需等待一小段时间让订阅超时,然后您就可以继续工作了。我不打算为生产代码使用超时,但我还不确定我将如何清理因崩溃而产生的孤立订阅。

这有效地关闭了这个问题。让它工作仍然存在问题,例如 Oracle 拒绝在通知端口上处理 FIN 和 RST,但这将是另一个问题的主题。