暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Oracle采集(三)-基于Logminer项目实战

我说行 2020-11-24
1745

简单实现

logminer启动参数

continuous_mine-前面我们提到了logminer的挖掘模式,因为我们需要实现oracle日志的实时采集,因此需要使用持续挖掘模式,这样可以向后持续解析日志。

committed_data_only - 参数来只显示那些被提交的事务

dict_from_online_catalog-使用联机日志,基于我们的需求场景,实时解析使用当前最新的字典文件即可,关于追数需求后面会作为专题来讲。

startSCN - 作为解析起点SCN

执行Query

  1. try (PreparedStatement statement = connection.prepareCall(ORA.SQL_QUERY_LOG_CONTENT))) {

  2. statement.setFetchSize(fetchSize);

  3. statement.setLong(1, lastSCN);


  4. ResultSet resultSet = statement.executeQuery();


  5. // 使用dbms_logmnr.continuous_mine连续采集

  6. while (resultSet.next()) {

  7. // 获取日志数据

  8. }

  9. }

关键问题

效率和性能的选择

前面提到v$logmnr_contents是一张会话级临时表,且我们使用continue_mine参数进行开启logminer,因此该表中的数据会持续不断的产生,我们执行查询时,如果不指定fetch_size参数,每次select游标获取defalut_fetch_size行数据后, 就会返回一次,而每次返回都会有一些列的性能损耗, 如果fetch_size设置过大, 又会造成等待时间过长。

ps.默认情况下Statement对象的default_fetch_size跟数据库有关系,例如oracle为10。

如果我们不设置fetch,使用默认值,那么数据库每匹配到10行数据,则会返回一次数据,交互比较频繁,但及时性较好。
如果我们将fetch设置为10000,那么当数据库端匹配到10000行数据时,则会整体响应该数据,如果匹配不够10000时,会持续挖掘日志,直到凑够10000行,交互频率较低,对带宽及服务器压力消耗小。

所以我们需要怎么去设置fetch_size呢? 如果我们设置过小, 那么解析速度是跟不上源库产生日志的速度的? 如果我们设置过大, 虽然解析效率会变得更高, 但是对于是解析的实效性就会产生影响.

因此我们考虑使用多线程进行解析, 即批量线程+实时线程, 他们共享一个startSCN, 谁先解析谁维护startSCN.
这样批量线程每次fetch 20000行数据, 可以保证解析速度, 实时线程每次fetch 1行数据, 可以保证解析实效.

排重机制

上面的双解析机制, 即两个线程同时解析, 可以做到效率和性能兼并. 那么问题就来了, 两个线程解析到的数据可能是相同的, 如何高效的排重, 就是我们现在面临的问题.

通过v$logmnr_contents字段描述可以看出,RS_ID+SSN可以标记出表内唯一行,通过这个机制我们可以很简单的做到数据排重。

字符串比较法:
因为RS_ID是十六进制字符串,本身是有序的列,因此只需要比较Rs_ID和ssn整体字符串即可进行排重。

双写hash排重法:
使用HashSet缓存RS_ID+SSN, 每次解析时, 判断下RS_ID+SSN是否已经存在.
因为批量解析设置fetch_size为20000, 因此我们可以将缓存大小设置为30000. 当HashSet的size超过30000后, 我们需要将最开始缓存的RS_ID移除, 但是Set没有此类api, 而List可以.
因此我们再使用LinkedList缓存RS_ID, 缓存达到3万后, 移除index为0的元素获得元素值, 再通过Set的hash去移除set集合中的值

经过大量的性能测试, 双写hash排重法性能更优.

大事务处理方案

大事务我们暂时定义为一次事务或者一条sql影响行数超过千万级别的操作.

测试过程中, 我们发现, 当出现大事务时, logminer会等待很长时间才会将数据返回, 或者出现logminer使用PGA超限的错误提示. 经过分析我们发现, 当出现大事务的时候, 一个事务的redo日志可能会跨越多个日志文件, logminer设置了committed_data_only, 在解析的时候, 会将几个日志文件内容全部加载到内存, 导致内存爆掉.

为了解决这个问题, 我们需要在启动logminer时去掉该参数, 去掉后, 日志数据包含的内容范围更广了, 因此我们还需要两个动作来完善:
1, 限定日志获取的内容, 例如operationCode需要insert/update/delete/commit/rollback 等等
2, 解决事务数据的暂存

其中第2点, 比较关键, 针对小事务直接内存缓存即可, 针对大事务, 我们需要根据内存大小, 来实现文件转储, 可以是本地磁盘也可以是分布式缓存.

这样当碰到新的事务ID时,开始缓存数据, 碰到commit动作时, 读取缓存处理数据, 并清理缓存即可.

ddl变更与字典维护

在日志解析过程中, 如果采集源表的库表结构发生了变化, 比如说增加了字段或者修改了字段, 那么logminer在解析日志时拿出来的redo内容中, 字段名称是不正确的, 比如说Id, name字段会得到COL1 COL2这种名称.

为什么会出现这个问题?
其实不难理解, 在logminer启动时, 指定了online_catalog, 即使用最新的字典文件作为字典数据, 当出现ddl为变更字段时, logminer切换字典或者维护字典不够及时, 导致解析redo时找不到正确的字典.

有一个很简单的方式可以解决该问题, 即在遇到ddl变更时, 重启logminer即可.

但是我们面临的问题会更复杂, 因为我们是多线程解析, 需要考虑的因素会更多, 我们分两种情况说明:
A, 假设短时间内操作两次ddl变更, 字典数据会产生三个版本, 老版本, 新版本, 最新版本
批量解析线程收到10000行数据, 开始处理时, 如果实时的解析进度才处理到老版本或者新版本日志, 批量线程会根据lastSCN进行重启, 此时加载的字典是最新版本, 处理老版本或者新版本字典对应的数据时, 会出现解析失败的语句

B, 假设在1万行日志内,只有1次DDL, ddl变更前的数据都是正确解析的
如果实时的解析进度在ddl变更之前, 它碰到ddl会重启,拿到最新字典,正确解析后面的数据,
如果批量解析先解析到ddl, 那么批量解析会先重启(丢弃掉原来会话中的错误数据,ddl点位->10000区间的)拿到最新字典, 开始正确解析后面数据, 超过1万行说明,批量解析已经完成10000行数据,重新回到上面的情况了.

针对第一种情况中这种极端场景时, 如出现COL1 COL2以COL字样, 并且存在 HEXTORAW字样的sql时, 我们标记并记录, 然后手工处理即可.

sql解析方案

略..



点击原文链接了解Porter大数据集成产品

文章转载自我说行,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论