背景:
项目中一部分数据如页面静态数据需要从redis中获取,以提高页面加载速度。页面数据的修改是通过管理画面修改mysql,通过定时任务或手动将mysql数据同步到redis,以供页面获取。这里数据同步不是增量同步,而是根据条件查询得到数据集后同步。网关中的一些参数同样是在mysql维护,然后定时或手动推送到redis,并通知网关刷新内存数据。有时增加一个数据的变动会出发多个同步操作,如增加一个api,既要将其在门户画面展示,又要推送到网关,如果想实时生效就需要两次手动操作。这样就很繁琐,因此设计一种实时或准实时将mysql同步至redis的方案。
方案1:
更新数据库的同时执行同步redis方法,如在增删改首页数据的方法添加注解,利用aop统一处理数据同步的逻辑。这种方案实际就是spring cache。但是这里不仅仅要将数据同步至redis,还要发布订阅事件,因此需要自己实现aop逻辑。
方案2:
利用canal,监听mysql数据变更进行数据同步和发布订阅事件。这样不必修改原代码,逻辑解耦。并且如果mysql修改失败不会触发同步操作。
综上采用方案2。
详细方案设计:
在canal定义一个destination监听数据库,当有感兴趣的数据变动时,通过feign调用对应服务的同步方法(这里的同步方法复用已有的手动调用方法,不用新写)。因为同步的是准全量数据,为避免mysql每次更新都查询,党一个数据变动后一分钟内没有新的数据变动,才触发同步操作。此外,通过apollo配置中心动态启停同步服务。
canal部署:
参照官网的方案,利用zookeeper进行服务协调,实现canal server,canal client的高可用。
由于测试环境分为SIT,QA和UAT,每个环境有单独的数据库,因此canal定义三个destination监听对应的数据库实例。
未来的优化方向:
- Redis获取数据失败fallback到数据库
- 增加一层内存缓存
- 感知同步数据成功与否
对于canal设计的一些思考
对于canal的高可用,通过zk保证server和client同一时间只能有一个节点工作
- server能不能根据数据id进行分片读取,提高读取数据的性能,类似kafka的设计,应该是不能的。因为parser向master发起dump请求得到的是字节流,无法获取原始数据。那能不能一个parser对应多个sink再放入store。没必要,因为canal的性能瓶颈在canal与数据库的网络IO,解析及sink是很快的。
- 客户端能不能多个节点同时工作,从一个destination消费数据。如果不保证数据成功消费及有序性是可以的。如果某一client消费数据失败,当前store的设计(环形结构保存数据)无法做到回滚。如果一个destination分多个队列由client消费,只能保证数据局部有序,同时设计复杂。
当前store的数据保存在内存中,是否有必要持久化到文件
- 类似于logstash的数据也是保存在内存中,官方文档说会支持,但没有也可以,因为持久化会影响整体性能,通过zk存储client的消费位置会保证数据至少被消费一次。
- store保存的数据受到大小和条数的限制,当达到限制时,sink会堵塞parser,不会撑爆内存
- canal与logstash,kafka的一些比较
对以后写公共组件的一些启示:
- 支持多种配置方式如配置文件,http,并可动态生效
- 通过协调服务保证系统的高可用
- 暴露服务监控指标至prometheus
- 获取数据的方式:达到一定数量或时间
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。