美文网首页
MongoDB change stream ResumeToke

MongoDB change stream ResumeToke

作者: Yellowtail | 来源:发表于2021-07-14 15:37 被阅读0次

概述

近期我们的change stream 服务遇到了一个问题
报错如下

org.springframework.data.mongodb.UncategorizedMongoDbException: Command failed with error 10334 (BSONObjectTooLarge): 'BSONObj size: 18360146 (0x1182752) is invalid. Size must be between 0 and 16793600(16MB) First element: _id: { _data: "8260EBB7E9000009932B022C0100296E5A100456CA186C15614BC48E761990EE38781E46645F696400645E953035E1031700014771360004" }' on server 172.16.4.232:3717. The full response is {"operationTime": {"timestamp": {"t": 1626089928, "i": 1235}}, "ok": 0.0, "errmsg": "BSONObj size: 18360146 (0x1182752) is invalid. Size must be between 0 and 16793600(16MB) First element: _id: { _data: "8260EBB7E9000009932B022C0100296E5A100456CA186C15614BC48E761990EE38781E46645F696400645E953035E1031700014771360004" }", "code": 10334, "codeName": "BSONObjectTooLarge", "clusterTime": {"clusterTime": {"timestamp": {"t": 1626089928, "i": 1235}}, "signature": {"hash": {"binary": {"base64": "DzlqmKctlL/0Mbt6d6cV+KerDYE=", "subType": "00"}}, "keyId": 6944255543972200451}}}; nested exception is com.mongodb.MongoCommandException: Command failed with error 10334 (BSONObjectTooLarge): 'BSONObj size: 18360146 (0x1182752) is invalid. Size must be between 0 and 16793600(16MB) First element: _id: { _data: "8260EBB7E9000009932B022C0100296E5A100456CA186C15614BC48E761990EE38781E46645F696400645E953035E1031700014771360004" }' on server 172.16.4.232:3717. The full response is {"operationTime": {"timestamp": {"t": 1626089928, "i": 1235}}, "ok": 0.0, "errmsg": "BSONObj size: 18360146 (0x1182752) is invalid. Size must be between 0 and 16793600(16MB) First element: _id: { _data: "8260EBB7E9000009932B022C0100296E5A100456CA186C15614BC48E761990EE38781E46645F696400645E953035E1031700014771360004" }", "code": 10334, "codeName": "BSONObjectTooLarge", "clusterTime": {"clusterTime": {"timestamp": {"t": 1626089928, "i": 1235}}, "signature": {"hash": {"binary": {"base64": "DzlqmKctlL/0Mbt6d6cV+KerDYE=", "subType": "00"}}, "keyId": 6944255543972200451}}}

格式化一下


image.png

可以看到是因为拿到的数据大小超过 BSON 的 最大值 16M, 导致报错
oplogresume token8260EBB7E9000009932B022C0100296E5A100456CA186C15614BC48E761990EE38781E46645F696400645E953035E1031700014771360004

那么到底是哪个文档出了问题呢?或者说是哪个 oplog 出了问题

因为 resume token 支持 resumeAfter startAfter 等 断点继续消费 的功能,所以猜测 resume token 肯定可以精准定位到 一个 或者 一小部分的 oplog,我只要解析出来这个信息,去找到对应的 oplog 就能知道到底出了什么问题

但是呢,从token本身信息看不出来
翻了翻官方文档,没说这个 resume token 格式是咋来的, 经过一番搜索和尝试,最后解析出来信息,并且定位到了,特此分享

0x1 文档

知乎看到一篇 MongoDB 4.2 内核解析 - Change Stream
里面描述如下

image.png

还在github找到了 4.2 版本源码, resume token

image.png

C 代码已经全部忘了,看不懂了。。。
然后又找到了一篇 format

image.png

看起来比较靠谱,对照着手动解析一下,发现可行

0x2 格式

格式如下


MommyTalk1626246926776.png

LaTex在线

写出正则表达式

^82(.{8}).+5A1004(.{32})46645F69640064(.{24})0004$
image.png

捕获到的三个分组分别是

  • 时间戳(长度8)
  • uuid(长度32)
  • ObjectId(长度24)

下面讲一讲正则表达式的由来

0x3 时间戳

看一下 oplog 里的数据

image.png

数字1626244435 转为16进制就是 60EE8553, 所以时间戳在 resume token 里长度为8

0x4 ObjectId

这个不用多说, 24位

0x5 uuid

参考 stackoverflow 的那个回答,从前面的 82 到 8位时间戳,能对应起来
再往后就对不上了
这时就从后往前匹配,发现可以

0004 固定结尾
然后就是24位 ObjectId
然后就是一堆固定的 ObjectId BSON 格式前缀 46645F69640064
再往前就是 UUID,但是不知道有没有经过什么转换,暂时跳过
再往前就是 固定的 UUID BSON 格式前缀 5A1004
经过手动测量,发现 UUID 长度为 32, 只是把间隔符- 去掉了而已
按照长度 8 4 4 4 12 的间隔加上-,就可以把 oplogui 字段还原出来

0x6 Java 代码

贴上可以运行的Java代码

运行结果


image.png

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author YellowTail
 * @since 2021-07-12
 */
public class ResumeTokenTest {

    public static void main(String[] args) {

        String token = "8260EBB7E9000009932B022C0100296E5A100456CA186C15614BC48E761990EE38781E46645F696400645E953035E1031700014771360004";

        analysisToken(token);
    }

    private static void analysisToken(String value) {

        // 解析参考文档为 https://stackoverflow.com/a/54072030

        if (null == value) {
            return;
        }

        Pattern pattern = Pattern.compile("^82(.{8}).+5A1004(.{32})46645F69640064(.{24})0004$");

        Matcher matcher = pattern.matcher(value);

        if (! matcher.find()) {
            System.out.println("格式不匹配");
            return;
        }

        String timestampHexStr = matcher.group(1);
        String uuidStr = matcher.group(2);
        String oidStr = matcher.group(3);

        printTime(timestampHexStr);

        printUi(uuidStr);

        printObjectId(oidStr);

    }

    private static void printObjectId(final String oidStr) {
        System.out.println("ObjectId 是 " + oidStr);
    }

    private static void printUi(final String uuidStr) {
        // 长度分隔为 8 4 4 4 12

        Pattern pattern = Pattern.compile("^(.{8})(.{4})(.{4})(.{4})(.{12})$");
        Matcher matcher = pattern.matcher(uuidStr);
        if (! matcher.find()) {
            System.out.println("uuid 格式不匹配");
            return;
        }

        String str1 = matcher.group(1);
        String str2 = matcher.group(2);
        String str3 = matcher.group(3);
        String str4 = matcher.group(4);
        String str5 = matcher.group(5);

        String join = String.join("-", str1, str2, str3, str4, str5);

        System.out.println("UUID是 " + uuidStr + ", ui是 " + join);
    }

    private static String sub(String value, int len) {
        return value.substring(0, len);
    }

    private static void printTime(final String timestampHexStr) {
        long timeStamp = hexStr2Long(timestampHexStr);

        Date date = new Date(timeStamp * 1000);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");

        System.out.println("时间戳是 " + timeStamp + ", 时间是 " + sdf.format(date));
    }

    // 16进制字符串转long 
    private static long hexStr2Long(String str) {

        char[] charArray = str.toCharArray();

        int length = str.length();

        long count = 0;

        for (int i = 0; i < charArray.length; i++) {

            int anInt = Integer.parseInt(String.valueOf(charArray[i]), 16);

            int offset = (length - 1 - i ) * 4;

            long tmp = anInt << offset;

            count += tmp;
        }

        return count;
    }

}

相关文章

网友评论

      本文标题:MongoDB change stream ResumeToke

      本文链接:https://www.haomeiwen.com/subject/ygyypltx.html