Change Stream

  1. 什么是Change Stream
    Change Stream 是MongoDB用于实现变更追踪的解决方案,类似于关系数据库的触发器,但原理不完全相同:
Change Stream触发器
触发方式异步同步(事务保证)
触发位置应用回调事件数据库触发器
触发次数每个订阅事件的客户端1次(触发器)
故障恢复从上次断点重新触发事务回滚
  1. Change Stream 实现原理
    Change Stream 是基于oplog实现的。它在oplog上开启一个tailable cursor 来追踪复制集上的变更操作,
    最终调用应用中定义的回调函数。被追踪的变更事件主要包括:

· insert/update/replace/delete:插入、更新、删除;

. drop:集合被删除;

. rename:集合被重命名;

. dropDatabase:数据库被删除;

. invalidate: drop/rename/dropDatabase 将导致invalidate被触发,并关闭 change stream;

正常我们都只监听 insert/update/replace/delete,但我们查询监听政策只拿 insert/update/replace

政策删除时,会先逻辑删除,把ISD更新为1,实际是update更新操作,且等过了2小时再物理删除delete,这时的delete就不需要再监听了

  1. Change Stream 与可重复读
    Change Stream 只推送已经在大多数节点上提交的变更操作。即“可重复读”的变更。
    这个验证是通过{readConcern:“majority”}实现的。因此:

· 未开启majority readConcern的集群无法使用Change Stream;

· 当集群无法满足{w:“majority”}时,不会触发Change Stream(例如PSA架构中的S因故宕机)。

提测时开发告知qa不能测,才发现

政策qa库是单机模式,导致无法使用Change Stream

Change Stream只能在副本集模式且配置{readConcern:“majority”}才能实现监听

  1. Change Stream 变更过滤
    如果只对某些类型的变更事件感兴趣,可以使用聚合管道的过滤步骤过滤事件。
    例如:
var cs = db.collection.watch([{
$match: {
operationType: {
$in: ['insert', 'delete']
}
} 
}])
  1. Change Stream 故障恢复
    假设在一系列写入操作的过程中,订阅Change Stream的应用在接收到“写3”之后于t0时刻崩溃,重启后后续的变更怎么办?
    !qr
    想要从上次中断的地方继续获取变更流,只需要保留上次变更通知中的_id即可。
    右侧所示是一次Change Stream回调所返回的数据。每条这样的数据都带有一个_id,这个_id可以用于断点恢复。例如:

var cs = db.collection.watch([],{resumeAfter:<_id>})
即可从上一条通知中断处继续获取后续的变更通知。
6. Change Stream 使用场景
· 跨集群的变更复制——在源集群中订阅Change Stream, 一旦得到任何变更立即写入目标集群。

· 微服务联动——当一个微服务变更数据库时,其他微服务得到通知并做出响应的变更。

· 其他任何需要联动的场景。

///

    /// 读取 changestream channel  写 sendMsg channel

    /// </summary>

    private void ChannelReadChangeStream(ChannelReader<ChangeStreamDocument<TCFlyPriceDocument>> readerOfChangeStream, ChannelWriter<PolicySendMsg> wirterOfSendMsg)

    {

        try

        {

            _skynetLog.With(_module, filter2: _redisLockValue)

                .Information("开始读取 ChannelReaderChangeStream");

            int sampling = 1;//采样,采样率1%;

            while (readerOfChangeStream.WaitToReadAsync().Wait())

            {

                while (readerOfChangeStream.TryRead(out var msg))

                {

                    if (msg.OperationType == ChangeStreamOperationType.Insert ||

                        msg.OperationType == ChangeStreamOperationType.Update ||

                        msg.OperationType == ChangeStreamOperationType.Replace

                        )

                    {

                        var message = new PolicySendMsg

                        {

                            Id = msg.DocumentKey["_id"].ToString(),

                            OpType = (int)msg.OperationType,

                            ResumeToken = msg.ResumeToken.ToString()

                        };

                        if (!wirterOfSendMsg.TryWrite(message))

                        {

                            _skynetLog.With(_module, _redisLockValue)

                                .Error("push SendMq Channel 消息失败,开始退出");

                            return;

                        }

                        sampling++;

                        if (sampling >= 100)

                        {

                            var utcNow = DateTime.UtcNow;

                            var clusterTime = DateTimeOffset.FromUnixTimeSeconds(msg.ClusterTime.Timestamp).UtcDateTime;

                            var timeSpan = utcNow - clusterTime;

                            var metric = new PolicySyncMetricInfo

                            {

                                MetricName = Constants.MetricNames.ChangeStreamLatency,

                                MetricValue = (int)timeSpan.TotalMilliseconds,

                                LatencyOfChangeStream = msg.ClusterTime.Timestamp,

                                Route = msg.OperationType.ToString().ToLower()

                            };

                            _policyMetricPush.PushMetricToBuffer(metric);

                            sampling = 1;

                        }



                    }

                    else

                    {

                        //

                    }



                }

            }



            _skynetLog.With(_module, filter2: _redisLockValue)

                .Warning("开始结束 sendMsg channel");

        }

        catch (Exception ex)

        {

            _skynetLog.With(_module, filter2: _redisLockValue)

                .Error(ex, "change stream channel 消费出现异常");

        }

        finally

        {

            wirterOfSendMsg.Complete();

            _skynetLog.With(_module, filter2: _redisLockValue)

                .Warning("change stream channel 消费已经停止");

        }

    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值