【基础知识】版本迭代数据兼容

  • 旧数据与新数据时相对概念,指数据的格式,数据的大小,数据的内容等相较于原有数据发生了改变,原有数据称为旧数据,变化后的数据称为新数据
  • 旧数据的兼容指新发布的版本是否了兼容旧数据新数据兼容反之

数据结构和模式的演变

数据管理的一个重要方面是架构演变。定义初始架构后,应用程序可能需要随着时间的推移对其进行改进。发生这种情况时,对于下游消费者而言,能够无缝处理使用旧模式和新模式编码的数据至关重要。在您遇到第一个生产问题之前,该领域在实践中往往会被忽略。如果不仔细考虑数据管理和架构演变,人们以后往往要付出高得多的代价。
使用数据库或者或消息等数据时,不可避免会发生数据结构的变化,最重要的事情之一是管理结构并考虑这些结构应如何演变。通过对每个单个结构进行版本控制,并需要完成结构兼容性检查。兼容性的类型决定新结构与老结构的比较策略。

兼容类型

对于“架构演变和兼容性”,可以为所有架构格式定义以下兼容性级别:

兼容类型允许的改动可以和哪些结构比较最先更新
BACKWARDDelete fields Add optional fields上一个版本消费者
BACKWARD TRANSITIVEDelete fields Add optional fields所有之前的版本消费者
FORWARDAdd fields Delete optional fields上一个版本生产者
FORWARD TRANSITIVEAdd fields Delete optional fields所有之前的版本生产者
FULLAdd optional fields Delete optional fields上一个版本任意次序
FULL TRANSITIVEAdd optional fields Delete optional fields所有之前的版本任意次序
NONEAll changesN/AN/A

向后兼容

BACKWARD兼容性意味着使用新架构的消费者可以读取上一个架构产生的数据。例如,如果某个主题的三个架构按X-2,X-1和X的顺序更改,那么BACKWARD兼容性将确保使用新架构X的消费者可以处理由生产者使用架构X或X-1编写的数据,但是不一定是X-2。如果使用新架构的消费者需要能够处理所有已注册架构(不仅是最后两个架构)写入的数据,请使用BACKWARD_TRANSITIVE代替BACKWARD。例如,如果某个主题的三个架构按X-2顺序更改,则X-1和X然后BACKWARD_TRANSITIVE兼容性确保使用新的架构消费者X可以处理通过使用模式生产者写入的数据X,X-1 ,或X-2 。

  • BACKWARD:使用模式X的消费者可以处理使用模式X或X-1生成的数据
  • BACKWARD_TRANSITIVE:使用模式X的消费者可以处理使用模式X,X-1或X-2生成的数据

向后兼容更改的一个示例是删除字段。首先开发一个能够处理没有此字段的数据的消费者,他将能够处理使用旧模式编写的数据(包含该字段)–消费者忽略该字段即可。

前向兼容性

FORWARD兼容性意味着使用新模式生成的数据可由老(上一代)的消费者读取,即使他们可能无法使用新模式的全部功能。例如,如果某个主题的三个模式按顺序X-2,X-1和X更改,那么FORWARD兼容性将确保使用模式X或X-1的消费者可以处理生产者使用新模式X编写的数据。,但不一定是X-2。如果消费者需要使用所有历史的模式(而不仅仅是最后两个模式)读取使用新模式生成的数据,则使用FORWARD_TRANSITIVE代替FORWARD。例如,如果某个主题的三个模式按顺序X-2,X-1和X更改,那么FORWARD_TRANSITIVE兼容性将确保使用模式X,X-1的消费者可以处理生产者使用新模式X编写的数据。或X-2。

  • FORWARD:使用架构X或X-1的消费者可以读取使用架构X生成的数据
  • FORWARD_TRANSITIVE:在使用的模式产生的数据X可以通过消费者的模式来读取X,X-1 ,或X-2

前向兼容架构修改的一个示例是添加新字段。在大多数数据格式中,被编码成在没有新字段的情况下处理事件的消费者即使在收到包含新字段的新事件时也能够继续正常消费数据。 考虑以下用例,其中消费者将应用程序逻辑绑定到特定版本的架构。当架构发展时,应用程序逻辑可能不会立即更新。因此,您需要能够将具有较新架构的数据投影到应用程序可以理解的(较旧)架构上。为了支持该用例,您可以以向前兼容的方式来发展模式:可以使用旧模式读取用新模式编码的数据。新数据架构与旧版本兼容。将使用新模式写入的数据投影到旧模式时,只需删除新字段。

完全相容

FULL兼容性意味着架构既可以向后兼容又可以向前兼容。模式以完全兼容的方式发展:旧数据可以用新模式读取,新数据也可以用最后一个模式读取。例如,如果某个主题的三个架构按X-2,X-1和X的顺序更改,那么FULL兼容性将确保使用新架构X的消费者可以处理由生产者使用架构X或X-1编写的数据,但是不一定是X-2,并且生产者使用新模式X编写的数据可以由消费者使用模式X或X-1处理,但不一定是X-2。如果新模式需要与所有历史模式进行向前和向后兼容,而不仅仅是最后两个模式,则使用FULL_TRANSITIVE代替FULL。例如,如果某个主题的三个架构按X-2,X-1和X的顺序更改,则FULL_TRANSITIVE兼容性可确保使用新架构X的消费者可以处理由生产者使用架构X,X-1或X编写的数据。X-2,并且生产者可以使用模式X,X-1或X来处理生产者使用新模式X编写的数据。X-2。

  • FULL:模式X和X-1之间的向后和向前兼容性
  • FULL_TRANSITIVE:模式X,X-1和X-2之间的向后和向前兼容性

在某些数据格式(例如JSON)中,没有完全兼容的更改。每个修改都是仅向前兼容或仅向后兼容。但是,在其他数据格式(例如Avro)中,您可以定义具有默认值的字段。在那种情况下,添加或删除具有默认值的字段是完全兼容的更改。 没有兼容性检查 NONE 兼容性类型表示架构兼容性检查已禁用。 有时我们进行不兼容的更改。例如,将字段类型从修改Number为String。在这种情况下,您将需要同时将所有生产者和消费者升级到新的架构版本,或者更有可能–创建一个全新的主题并开始迁移应用程序以使用新的主题和新的架构,从而避免了需要处理同一主题中的两个不兼容的版本。

客户端升级顺序

配置的兼容性类型对升级客户端应用程序的顺序有影响,即使用模式将数据写入中间件的生产者和使用模式从中间件读取数据的消费者。根据兼容性类型:

  • BACKWARD或BACKWARD_TRANSITIVE:不能保证使用旧架构的消费者可以读取使用新架构产生的数据。因此,在开始制作新事件之前,请升级所有消费者。
  • FORWARD或FORWARD_TRANSITIVE:不能保证使用新架构的消费者可以读取使用旧架构产生的数据。因此,首先将所有生产者升级为使用新模式,并确保使用较旧模式产生的数据对消费者不可用,然后升级消费者。
  • FULL或FULL_TRANSITIVE:可以保证使用旧模式的消费者可以读取使用新模式产生的数据,并且可以确保使用新模式的消费者可以读取使用旧模式产生的数据。因此,您可以独立升级生产者和消费者。
  • NONE:兼容性检查已禁用。离线同步升级客户端。

传递性

一旦给定主题具有两个以上版本的架构,则传递兼容性检查非常重要。如果兼容性可传递,则新模式与所有先前注册的模式兼容;否则,它仅确保新模式与最新模式的兼容。例如,如果某个主题的三个架构按X-2,X-1和X的顺序更改,则:

  • 可传递的:确保X-2 <==> X-1和X-1 <==> X和X-2 <==> X之间的兼容性
  • 非传递性的:确保X-2 <==> X-1和X-1 <==> X之间的兼容性,但不一定是X-2 <==> X

兼容性检查

  • 可以添加字段。默认情况下,尽量让结构中的所有字段都是可选的。如果指定默认值,则将这些默认值用于向后兼容。
  • 可以修改字段。字段可以由相同类型的新字段重用。字段不能被其他类型的新字段重用。
  • 数值类型在向上提升时,类型是兼容的(可以在相同的字段被交换)。
  • 日期类型在向上提升时,类型是兼容的(可以在同一字段中交换)。
  • 文本类型在向上提升时,类型是兼容的(可以在同一字段中交换)。
  • 枚举类型在向上提升时,类型是兼容的(可以在同一字段中交换)。

JSON模式兼容性规则

JSON Schema兼容性规则基于之前的兼容性规则,但是,向后兼容的规则更为复杂。

JSON模式兼容性规则
JSON Schema兼容性规则基于之前的兼容性规则,但是,向后兼容的规则更为复杂。

基本类型兼容性
JSON Schema的类型集更为有限。以下规则也适用于JSON模式:

  • Writer的结构integer可以提升为reader的架构number。

可以对JSON基本类型的架构进行许多更改,以使该架构的约束性降低,从而允许具有新架构的客户端读取使用旧架构编写的JSON文档。这里有些例子:

  • 对于字符串类型,编写者的架构的minLength值可以大于minLength读取者的架构中的值,或者不存在于读取器的架构中;或maxLength小于maxLength读取器架构中的值,或不存在于读取器架构中的值。
  • 对于字符串类型,生产者的结构可能有读取者的结构中不存在的模式。
  • 对于数字类型,生产者结构的minimum值可能大于消费者的结构中minimum的值或不存在;或maximum小于消费者结构中的maximum值,或不存在消费者结构中的值。

对象相容性
对于对象模式,JSON模式支持开放内容模型,封闭内容模型和部分开放内容模型。

  • 开放内容模型允许在JSON文档中显示任何数量的其他属性,而无需在JSON模式中指定。这可以通过指定additionalProperties为true来实现,这是默认设置。
  • 如果JSON文档中未指定的属性出现在JSON文档中,则封闭的内容模型将导致发出错误信号。这可以通过指定additionalProperties为false来实现。
  • 部分开放的内容模型允许其他属性显示在JSON文档中,而无需在JSON模式中进行命名,但是其他属性被限制为具有特定类型和/或具有特定名称。

例如,消费者的模式可以向生产者的模式添加一个附加属性,例如myProperty,但是只有当生产者的模式具有封闭的内容模型时,才能以向后兼容的方式完成。这是因为,如果生产者的模式具有开放的内容模型,那么生产者可能使用myProperty与阅读者的模式中预期的类型不同的类型来生成具有myProperty的JSON文档。

使用内容模型的概念,您可以按以下方式修改规则:

  • 字段的顺序可能不同:字段按名称匹配。
  • 两条记录中具有相同名称的字段的模式将递归解析。
  • 如果生产者的架构包含一个字段,而该字段的名称未出现在消费者的架构中,则该消费者的架构必须具有捕获丢失字段的开放内容模型或部分开放内容模型。
  • 如果生产者的模式具有包含默认值的必填字段,而生产者的模式具有封闭的内容模型并且没有相同名称的字段或具有相同名称的可选字段,则消费者应使用其字段中的默认值。
  • 如果消费者的模式具有必填字段且没有默认值,而生产者的模式不具有相同名称的字段,或者具有相同名称的可选字段,则将指示错误。
  • 如果消费者的模式具有可选字段,而生产者的模式具有封闭的内容模型且没有名称相同的字段,则消费者应忽略该字段。

以下是一些特定于JSON模式的其他兼容性规则:

  • 生产者的架构的minProperties值可以大于消费者的架构中的minProperties值,或者不存在于消费者的架构中;或者maxProperties值小于消费器架构中的maxProperties值,或者不存在于消费者架构中。
  • 生产者的架构可能具有所需的值,该必需的值是消费者的架构中的所需值的超集或不存在于生产者的架构中。
  • 生产者的架构可能具有一个依赖性值,该依赖性值是消费者架构中依赖性值的超集或不存在于消费者架构中。
  • 生产者的架构可能具有一个false的AdditionalProperties值,而它可以是true或消费者的架构中的一个架构。

模式解析

无论是从RPC还是从文件读取数据,由于提供了其模式结构,因此它们始终可以解析该数据。但是该结构可能与预期的结构不完全相同。例如,如果数据是使用与读取软件不同的版本编写的,则记录可能已添加或删除了字段。本节指定如何解决这种架构差异。
我们将用于写入数据的架构称为生产者的架构,并将应用程序期望的架构称为消费者的架构。两者之间的差异应按以下方式解决:

  • 如果这两个模式不匹配,那就是一个错误。

要匹配,必须满足以下条件之一:

  • 这两个模式都是其项目类型匹配的数组
  • 两种模式都是其值类型匹配的映射
  • 这两个模式都是名称匹配的枚举
  • 两种模式都是固定的,其大小和名称匹配
  • 这两个模式都是具有相同名称的记录
  • 任何一个模式都是一个联合
  • 两种模式具有相同的原始类型
  • 生产者的模式可以提升为消费者,如下所示:
    • int可提升为long,float或double
    • long可提升为float或double
    • float可提升为double
    • 字符串可转为为字节数组
    • 字节数组可转为为字符串

如果两者均为记录:

  • 字段的顺序可能不同:字段按名称匹配。
  • 两条记录中具有相同名称的字段的模式将递归解析。
  • 如果生产者的记录包含一个字段,而该字段的名称未出现在消费者的记录中,则该生产者字段的值将被忽略。
  • 如果消费者的记录模式具有包含默认值的字段,而生产者的模式没有具有相同名称的字段,则消费者应使用其字段中的默认值。
  • 如果消费者的记录模式具有没有默认值的字段,并且生产者的模式没有具有相同名称的字段,则表明错误。

如果两者都是枚举:如果生产者的枚举未出现在消费者的枚举中,则表明存在错误。

怎么去兼容历史数据

1.增加字段

我们经常会遇到在表格上“增加字段”的情况,比如增加了新的业务字段,增加了新的统计项

如果不做兼容处理,就会出现增加的字段有增加后的新数据,但是没有历史数据

这种情况下,需要我们判断历史数据能否被补全,若能,则补全历史数据;若无法补全,新增的字段历史数据空白展示
在这里插入图片描述
2.减少字段

当出现“减少字段”的情况,如果不做处理,会出现减少的字段没有新数据,但是有历史数据

这种情况下,我们的处理方式是保留历史数据,减少统计后该字段空白展示
在这里插入图片描述
3.原字段统计逻辑或规则改变

统计逻辑或规则被改变时,不进行数据兼容的话,因为新数据和历史数据的统计方式不一致,会导致数据结果出现差异

这个时候,需要我们去判断历史数据能否按新的统计逻辑换算,若 能 , 则按新逻辑重新统计 ; 若不能保留历史数据 , 并记录统计逻辑的改变记录
在这里插入图片描述
4.原字段下钻或合并统计

这种改变会出现新字段和历史字段是包含或者被包含的关系,需要我们去补全历史数据,比如字段A被下钻成了新字段B+新字段C,根据下钻规则补全新字段B和C的历史数据值
在这里插入图片描述
而在实际的场景中,数据冲突会同时存在多种,所采用的方案也是多个解决手段组合的

比如下面这个案例,我们对“客户管理”模块进行迭代,通过调研发现内部销售团队希望能在“客户管理”菜单中增加“客户微信”字段,并提供根据客户等级自动计算出“下次回访时间”,为此我们对“客户管理”的字段进行了调整
在这里插入图片描述
表格改动为:增加“客户微信”、“下次回访时间”字段,减少“创建时间”字段

这里就涉及到了“增加字段”和“减少字段”两种情况,通过分析“客户微信”和“下次回访”字段对存量客户具有重要意义,收集到客户的微信联系方式和具体的回访时间,方便业务员展开业务,两个字段的数据也有被补全的条件;而减少的“创建时间”字段对于业务影响不大,可以废弃

基于上面的考虑,我们对“客户管理”菜单做了如图处理
在这里插入图片描述
迭代发布上线后,产品同学提出“下次回访时间”直接展示时间,对销售团队来说不够直观,可以对“下次回访时间”进一步处理,更加直接明了,因为“下次回访时间”字段中原有的时间格式是支持现在的规则换算的,就可以对时间进行了换算处理

新知达人, 表格改变字段时,如何兼容历史数据
对“下次回访时间”的展示进行处理,计算“下次回访时间”和当前时间的差值

原统计格式:yyyy-mm-dd

新统计格式:X天后回访;已过期X天
在这里插入图片描述
随着业务的发展又遇到了“字段统计逻辑和规则无法转化”的情况,“客户管理”中“意向产品”的可选项从“商品1,商品2,商品3”变成了“商品5,商品6,商品7”

改动前后的数据没有办法去简单的进行兼容,而前后数据对于业务来说都是具有意义的,那么我们需要在保留两者数据格式的前提下,做一些文案上的提示,例如在操作日志记录系统对于规则的更改

怎么去兼容未来新数据

1.增加字段
一般框架都会不受增加字段的影响,显示照旧

例如一个用户对象:
第一版中,包括了姓名(字符串),年龄(数字),性别(布尔值)

let user1 = {
  version: 1,
  name: 'Michael',
  age: 20,
  isFemale: true,
};

第二版中

产品改了需求,针对国外用户,姓名改成了姓,名两个字段

性别改成了字符串,有的用户选择不显示性别

let user2 = {
  version: 2,
  first_name: 'Michael',
  last_name: 'An',
  age: 20,
  gender: 'male' or 'female' or ''
}

第三版中,增加了地址(字符串)

let user3 = {
  version: 3,
  first_name: 'Michael',
  last_name: 'An',
  age: 20,
  address: 'Beijing',
  gender: 'male',
}

第四版中,可能有多个住址,地址字段改成数组

let use4 = {
  version: 4,
  first_name: 'Michael',
  last_name: 'An',
  age: 20,
  address: ['Beijing', 'Shanghai'],
  gender: 'male' or 'female',
}

后端返回的数据中,有这些情况,那么前端使用时,需要兼容这多个版本的数据。

解决思路
可以添加 version 字段,数据预处理代码如下:

const preprocess = function(user) {
  if (typeof user !== 'object') {
    console.log('用户无效');
    return null;
  }
  if (user.version === 1) {
    const { age } = user;
    return {
      version: 4,
      first_name: user.name,
      last_name: '',
      age,
      address: [],
      gender: user.isFemale ? 'female' : 'male',
    };
  }
  else if (user.version === 2) {
    const { first_name, last_name, age, gender } = user;
    return {
      version: 4,
      first_name,
      last_name,
      age,
      address: [],
      gender,
    }
  }
  else if (user.version === 3) {
    const { first_name, last_name, age, gender, address } = user;
    return {
      version: 4,
      first_name,
      last_name,
      age,
      address: [address],
      gender,
    }
  }
  else if (user.version === 4) {
    return user;
  }
}

如果未来新增其他属性,那么根据新属性继续兼容。

也可以写一个 model user 类处理属性,优化代码,代码如下。

class User {
  constructor(obj) {
    this.version = obj.version || 4;
    this.first_name = obj.first_name || '';
    this.last_name = obj.last_name || '';
    this.age = obj.age || 0;
    this.address = obj.address || [];
    this.gender = obj.gender || '';
  }
}

2.减少字段
做判空处理
3.其他
不行的话只能强制用户升级了哇

参考

数据结构和模式的演变和兼容性
表格改变字段时,如何兼容历史数据

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软泡芙

给爷鞠躬!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值