rust写的mongo同步小工具,降本增效

工具简介

  • 特点:轻量级 稳定性强 占用资源少 体积小
  • 目的:推荐rust开发 分享使用场景方案 分享分布式处理方式
  • 场景:亚马逊跨区数据库流量优化 对增量式海量数据做高并发分布式同步
  • 价值:待同步库做分库逻辑 同步结束后清理现场 保证最低服务器配置运行 节约成本

职能

服务端:负责对待同步数据库做数据游标与分区块管理
客户端:负责并发拉取服务端接口数据到新数据库,并回调状态

流程

所有亚马逊数据都集中暂存到同个区域mongo库 => 服务端游标分块所有collection => 服务端提供接口 => 客服端调用接口拉取
=> 回调处理结果给服务端 =>服务端标记清理块

流程图

实时备份服务概况

在这里插入图片描述

备份流程

在这里插入图片描述

分块调度流程

在这里插入图片描述

生成块流程

在这里插入图片描述

完成目标

  • 简单便捷
  • 无损同步
  • 支持高并发分布式
  • 降本增效 用相对便宜的cdn流量替换亚马逊跨区跨机房较贵流量

源码分析

块结构

a#[derive(Serialize, Deserialize, Clone, Debug)]
struct Block {
    d: String,               // 库名
    c: String,               // 表名
    _l_k: Option<String>,    // 锁
    _l_ex: Option<DateTime>, // 锁到期时间
    f: Option<ObjectId>,     // 开始id(不包含)
    t: Option<ObjectId>,     // 结束id(包含),
    s: u8,                   // 状态 0 未分配 1 执行中
    ct: DateTime,            // 创建时间
    ut: DateTime,            // 操作时间
    st: Option<DateTime>,    // 开始时间
    et: Option<DateTime>,    // 结束时间,时间到则重新分配
}

分配块逻辑

// 生成块
    async fn gen_block(
        &self,
        d: String,
        c: String,
    ) -> Result<(Option<Block>, bool), Box<dyn std::error::Error>> {
        // 查找记录的位置
        let sys_info_db = self.db.get_database("_sys_info").await?;
        let coll_coll = sys_info_db.collection::<Document>("t_coll");
        let queue_coll = sys_info_db.collection::<Document>("t_queue");
        let pos_doc = coll_coll
            .find_one(
                doc! {
                    "d": d.to_string(),
                    "c": c.to_string()
                },
                None,
            )
            .await?;
        // 找到开始位置
        let f = match pos_doc.as_ref().map(|doc| match doc.get_object_id("pos") {
            Ok(p) => Some(p),
            Err(_) => None,
        }) {
            Some(a) => match a {
                Some(b) => Some(b),
                None => None,
            },
            None => None,
        };
        // 找到最后一条记录id
        let filter = Document::new();
        let db = self.db.get_database(&d).await?;
        let collection = db.collection::<Document>(&c);
        let t = self.get_latest_id(&collection, filter.clone()).await?;

        // 获取块的长度
        let size = Config::get_instance()?
            .get_value("block.size")
            .map_or(100i64, |a| a.as_i64().unwrap());

        // 等待次数
        let almost_done_wait_time = Config::get_instance()?
            .get_value("block.almost_done_wait_time")
            .map_or(60i64, |a| a.as_i64().unwrap());

        // 如果开始等于结束id,则返回None 即完成该表转移

        // 获取块的数据
        let (new_latest_id, count) = self
            .get_next_block_tid(&collection, filter.clone(), f, t, size)
            .await?;

        let now = DateTime::now();
        if count < size {
            // 不够数量,则判断需要等待n次后,才能处理
            let mut trigger_time = DateTime::now();
            let mut hasTime = true;
            if let Some(ref e) = pos_doc {
                if !e.contains_key("trigger_time") {
                    hasTime = false;
                }
                trigger_time = e
                    .get_datetime("trigger_time")
                    .map_or(DateTime::now(), |a| *a);
            } else {
                hasTime = false;
            }
            if !hasTime {
                trigger_time = DateTime::from_millis(
                    trigger_time.timestamp_millis() + almost_done_wait_time * 1000,
                );
                coll_coll
                    .update_one(
                        doc! {
                            "d": d.to_string(),
                            "c": c.to_string()
                        },
                        doc! {
                                "$set": doc!{
                                    "trigger_time":trigger_time,
                                    "ut": now.clone()
                                }
                        },
                        UpdateOptions::builder().upsert(false).build(),
                    )
                    .await?;
            }
            if trigger_time > DateTime::now() {
                // 等待
                return Ok((None, false));
            }
        }

        // 如果没有数据,则返回None
        // 如果有数据,则记录位置,并存储块
        match new_latest_id {
            Some(id) => {
                // 记录块
                let b = Block {
                    d: d.clone(),
                    c: c.clone(),
                    _l_k: None,
                    _l_ex: None,
                    f,
                    t: Some(id),
                    s: 0,
                    ct: now.clone(),
                    ut: now.clone(),
                    st: None,
                    et: None, // 超时时间
                };
                let v = serde_json::to_value(&b).unwrap();
                let document: Document = serde_json::from_value(v).unwrap();
                queue_coll.insert_one(document, None).await?;
                // 重置pos
                coll_coll
                    .update_one(
                        doc! {
                            "d": d.to_string(),
                            "c": c.to_string()
                        },
                        doc! {
                                "$set": doc!{
                                    "pos": new_latest_id,
                                    "ut": now.clone()
                                },
                                                                "$unset":doc!{
                                                                        "trigger_time":1i32
                                                                },
                                "$setOnInsert": doc!{
                                    "ct": now.clone()
                                }
                        },
                        UpdateOptions::builder().upsert(true).build(),
                    )
                    .await?;
                Ok((Some(b), false))
            }
            None => Ok((None, true)),
        }
    }

统计与监控

在这里插入图片描述

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值