茶艺师学微服务(实操篇5 数据迁移怎么做? 下)

922 阅读4分钟

茶艺师学微服务(实操篇5 数据迁移怎么做? 下)

前言

经过了上篇《茶艺师学微服务(实操篇4 数据迁移怎么做? 中)》,我们已经讨论了如何全量校验与修复。
在开始数据迁移的双写阶段之间,可以根据整体数据量的大小选择是否开启全量校验与修复。
而进入了双写阶段(同时写源表与目标表),为了保证数据的一致,且不会有机器、以及数据库太大的花销,根据之前步骤的铺垫,这时我们可以选择“一直保持对新写的数据进行校验与修复”策略,即我们接下来讨论的增量校验

增量校验如何实现

这里有个最直接的实现方法,就是“遍历数据库,找出刚刚更新的,换句话说就是 utime > ? 的数据”。
这需要数据的字段里包含 utime更新时间 ,以及结合 offset偏移量 避免不必要的重复比对。
相关代码的示例写法:

	err := v.base.WithContext(dbCtx).
		Where("utime > ?", v.utime).
		Offset(offset).
		Order("utime ASC, id ASC").First(&src).Error

通过这样的增量校验,发现不一致的数据,接下来像全量校验与修复那样,把修复通知发给 Kafka 。
为了方便调用,这里可以设计成“全量校验开关”“增量校验开关”。

// 全量校验
func (v *Validator[T]) fullFromBase(ctx context.Context, offset int) (T, error) {}

// 增量校验
func (v *Validator[T]) intrFromBase(ctx context.Context, offset int) (T, error){}

// 全量校验开关
func (v *Validator[T]) Full(ctx context.Context) *Validator[T] {
    v.formBase = v.fullFromBase
    return v
}

// 增量校验开关
func (v *Validator[T]) Incr(ctx context.Context) *Validator[T] {
    v.formBase = v.intrFromBase
    return v
}


// 校验方法本体
func (v *Validator[T]) validateBaseToTarget(ctx context.Context){
    offset := 0
    for {
    // 校验逻辑 全量校验 或者 增量校验的起效位置
    src, err  := v.formBase(ctx, offset)
    switch err {
    
    ....
    }
    
    offset++
    }
}

集成全功能

在完成了增量校验的部分,那么数据迁移的主要功能都已经准备好了。
接着,就该把这些功能“拼装”起来,就是将以下流程给“拼装”起来。

很容易看出来,如何与 Kafka 打交道是关键。
首先看 生产者:校验部分
在构建校验结构体时,需要加入 Kafka 的生产者,像这样:

type Validator[T migrator.Entity] struct {
	base          *gorm.DB
	target        *gorm.DB
	l             logger.LoggerV1
	p             events.Producer // Kafka 的生产者
    ...
}

别忘了构建要发送的事件:

type Producer interface {
	ProduceInconsistentEvent(ctx context.Context, event InconsistentEvent) error
}

type SaramaProducer struct {
	p sarama.SyncProducer
	topic string
}

然后就是 消费者:修复部分
也是和构建校验结构体那样,加入 Kafka 的消费者,像这样:

type Consumer[T migrator.Entity] struct {
	client sarama.Client // Kafka 的消费者
    srcFirst *fixer.OverrideFixer[T] // 修复部分
	dstFirst *fixer.OverrideFixer[T] // 修复部分
    ...
	topic string
}

这里可以记住,Producer 与 Consumer 是成对出现的,这样就不会漏。
到这里可以想想如何调用这数据迁移能力,我这里设计成用 http 调用。
这样我就可以通过“网址”来调用测试每个环节,以及今后如果说想配一个操作界面也简单。
这里简单配置一下:

func (s *Scheduler[T]) RegisterRoutes(server *gin.RouterGroup) {
	// 将这个暴露为 HTTP 接口
	// 你可以配上对应的 UI
	server.POST("/src_only", ginx.Wrap(s.SrcOnly))   // 只读 Src
	server.POST("/src_first", ginx.Wrap(s.SrcFirst)) // 以 Src 为准
 	server.POST("/dst_first", ginx.Wrap(s.DstFirst)) // 只读 Dst
	server.POST("/dst_only", ginx.Wrap(s.DstOnly))   // 以 Dst 为准
	server.POST("/full/start", ginx.Wrap(s.StartFullValidation))  // 开启全量校验与修复
	server.POST("/full/stop", ginx.Wrap(s.StopFullValidation))    // 关闭全量校验与修复
	server.POST("/incr/stop", ginx.Wrap(s.StopIncrementValidation))  // 关闭增量校验与修复
	server.POST("/incr/start", ginx.WrapBodyV1[StartIncrRequest](s.StartIncrementValidation)) // 开启增量校验与修复
}

这里别忘记了,我们是在整微服务,这个数据迁移可以整成一个独立的包。
因此要真正用起来,还要将这(数据迁移)包写进需要该能力的微服务模块里,处理好其对应的依赖注入。
这里随便测试一下:

本地调研 http 测试

四个状态切换测试

全量校验修复测试 前

全量校验修复测试 后

至此,微服务化的数据迁移算是整好了。

结语

我们用了3篇文章:
《茶艺师学微服务(实操篇3-数据迁移怎么做? 上)》
《茶艺师学微服务(实操篇4 数据迁移怎么做? 中)》
《茶艺师学微服务(实操篇5 数据迁移怎么做? 下)》
讨论了在应用模块微服务化中必定会遇到的问题:数据迁移。
依据微服务原则,独立出来的模块,它所依赖的数据库要需要“独立”出来。
在现实业务情况,很有可能是得不停机数据迁移。
为了保证数据迁移过程中尽量不出错,我们把整个数据迁移分出来以下阶段:

设计出以下具体步骤:


为了不对还在运行的业务造成影响,我们选择引入 Kafka 进行解耦与消峰:

最后的最后

坚持阅读的小伙伴可以给自己点赞!还请动一下您的发财小手,关注一下公众号:『耀龙读书』!!谢谢大家的点赞与关注,掰掰~