茶艺师学微服务(实操篇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)) // 开启增量校验与修复
}
这里别忘记了,我们是在整微服务,这个数据迁移可以整成一个独立的包。
因此要真正用起来,还要将这(数据迁移)包写进需要该能力的微服务模块里,处理好其对应的依赖注入。
这里随便测试一下:
至此,微服务化的数据迁移算是整好了。
结语
我们用了3篇文章:
《茶艺师学微服务(实操篇3-数据迁移怎么做? 上)》
《茶艺师学微服务(实操篇4 数据迁移怎么做? 中)》
《茶艺师学微服务(实操篇5 数据迁移怎么做? 下)》
讨论了在应用模块微服务化中必定会遇到的问题:数据迁移。
依据微服务原则,独立出来的模块,它所依赖的数据库要需要“独立”出来。
在现实业务情况,很有可能是得不停机数据迁移。
为了保证数据迁移过程中尽量不出错,我们把整个数据迁移分出来以下阶段:
设计出以下具体步骤:
为了不对还在运行的业务造成影响,我们选择引入 Kafka 进行解耦与消峰:
最后的最后
坚持阅读的小伙伴可以给自己点赞!还请动一下您的发财小手,关注一下公众号:『耀龙读书』!!谢谢大家的点赞与关注,掰掰~