一、你可能没重视的CouchDB复制过滤器
做过分布式数据同步的人都知道,不同业务场景下,数据同步的需求千差万别。比如做电商的,北京仓和上海仓的系统,不需要同步对方所有的商品数据,只需要同步自己仓负责的那部分;做内容平台的,不同区域的内容节点,只需要同步本区域用户发布的内容。要是不管三七二十一全量同步,不仅占带宽、耗存储,还会拖慢整个系统的响应速度。
CouchDB作为一款支持多活同步的NoSQL数据库,自带的复制功能本来是个好东西,但很多人用的时候只开了基础复制,完全忽略了复制过滤器这个关键特性。说白了,复制过滤器就是给CouchDB的复制功能加了个“过滤网”,能指定只同步符合条件的数据,把不需要的内容直接拦在同步链路外面。但实际工作中,我见过太多团队要么不知道这个功能,要么觉得配置麻烦、性能有损耗,直接放弃使用,结果就是系统的同步开销越来越大,最后不得不花大价钱扩容带宽、升级存储。
二、按业务属性裁剪变化流的核心逻辑
很多人觉得同步开销大,核心问题在于同步了大量“没用的数据”。这些没用的数据,要么是对目标节点完全没用的,要么是目标节点已经有了的,要么是不符合业务规则的。按业务属性裁剪变化流,本质就是把这些没用的数据提前过滤掉,只让真正需要的内容走同步链路。
CouchDB的复制过滤器工作在同步的源头,也就是说,数据还没被打包、压缩、传输之前,就已经被判断要不要同步了。这和在目标节点收到数据后再过滤完全是两码事:源头过滤能直接减少传输的数据量,连压缩的步骤都省了;目标端过滤只是把没用的数据存到本地,传输的开销一点没少。
举个简单的例子,假设你有1000条商品数据,其中只有100条属于北京仓,要是在源头过滤,只传100条;要是目标端过滤,得先把1000条都传过去,再删掉900条。两者的传输开销差了10倍,这就是复制过滤器的价值所在。
三、CouchDB复制过滤器的具体配置与示例
3.1 过滤器的基本配置规则
CouchDB的复制过滤器是一个写在源数据库设计文档里的JavaScript函数,配置复制任务的时候指定这个过滤器的名称,就能生效。整个配置分为两步:第一步是写过滤器函数,第二步是配置带过滤器的复制任务。
需要注意的是,过滤器函数必须返回布尔值:返回true就同步这条数据,返回false就不同步。而且过滤器函数只能访问当前要判断的这条数据本身,不能访问其他数据,也不能发起外部请求,这是为了保证过滤的性能和一致性。
3.2 完整的业务场景示例
3.2.1 场景说明
假设我们做一个生鲜电商,有两个区域仓:华北仓(仓编号为HB)和华东仓(仓编号为HD)。每个商品数据都有一个warehouse_code字段,用来表示这个商品属于哪个仓。我们需要配置复制任务,让华北仓的数据库只同步属于华北仓的商品,华东仓的数据库只同步属于华东仓的商品。
3.2.2 技术栈说明
本次示例使用的技术栈为:CouchDB 3.2.2,因为这个版本的复制过滤器功能稳定,语法兼容绝大多数业务场景。
3.2.3 第一步:写过滤器函数
首先,我们需要在源数据库(比如主仓的数据库,所有商品数据都存在这里)里创建一个设计文档,里面定义过滤器函数。设计文档的ID可以自定义,比如_design/warehouse_filter。
// 设计文档的JSON结构,包含过滤器函数
{
"_id": "_design/warehouse_filter",
"filters": {
// 过滤器函数的名称,复制任务里要用到这个名称
"by_warehouse": "function(doc, req) {
// doc参数:当前要判断的商品数据
// req参数:复制任务的请求信息,里面可以带自定义参数
// 第一步:判断这条数据是不是商品数据,我们只同步商品,其他类型的数据不同步
if (doc.type !== 'product') {
return false;
}
// 第二步:获取复制任务传过来的目标仓编号
const targetWarehouse = req.query.warehouse;
// 第三步:判断商品的仓编号是不是和目标仓编号一致
return doc.warehouse_code === targetWarehouse;
}"
}
}
3.2.4 第二步:配置带过滤器的复制任务
过滤器函数写好之后,我们需要配置两个复制任务,分别对应华北仓和华东仓。复制任务可以通过CouchDB的HTTP API创建,也可以在Fauxton(CouchDB自带的管理界面)里配置,这里我们用HTTP API的方式,更直观。
# 配置华北仓的复制任务:从主数据库同步到华北仓数据库,只同步华北仓的商品
curl -X POST http://localhost:5984/_replicator \
-H "Content-Type: application/json" \
-d '{
"source": "http://localhost:5984/main_db", // 源数据库:主仓的数据库
"target": "http://localhost:5984/hb_db", // 目标数据库:华北仓的数据库
"filter": "warehouse_filter/by_warehouse", // 指定过滤器:设计文档ID/过滤器名称
"query_params": { // 给过滤器传参数:目标仓编号是HB
"warehouse": "HB"
},
"continuous": true // 配置为持续复制,主数据库有变化就自动同步
}'
# 配置华东仓的复制任务:从主数据库同步到华东仓数据库,只同步华东仓的商品
curl -X POST http://localhost:5984/_replicator \
-H "Content-Type: application/json" \
-d '{
"source": "http://localhost:5984/main_db",
"target": "http://localhost:5984/hd_db",
"filter": "warehouse_filter/by_warehouse",
"query_params": {
"warehouse": "HD"
},
"continuous": true
}'
3.2.5 效果验证
配置完成后,我们可以往主数据库里插入几条测试数据,看看同步效果:
# 插入一条华北仓的商品
curl -X POST http://localhost:5984/main_db \
-H "Content-Type: application/json" \
-d '{
"type": "product",
"name": "北京水蜜桃",
"warehouse_code": "HB",
"price": 19.9
}'
# 插入一条华东仓的商品
curl -X POST http://localhost:5984/main_db \
-H "Content-Type: application/json" \
-d '{
"type": "product",
"name": "上海大闸蟹",
"warehouse_code": "HD",
"price": 99.9
}'
# 插入一条非商品数据(比如用户数据)
curl -X POST http://localhost:5984/main_db \
-H "Content-Type: application/json" \
-d '{
"type": "user",
"name": "张三",
"phone": "13800138000"
}'
然后分别查询华北仓和华东仓的数据库,会发现:华北仓数据库里只有北京水蜜桃,华东仓数据库里只有上海大闸蟹,用户数据没有被同步。整个过程中,华北仓只收到了1条数据,华东仓只收到了1条数据,主数据库里的3条数据,总共只传了2条,要是没有过滤器,就得传3条,虽然这个例子差异不大,但如果主数据库里有10万条非商品数据,差异就会非常明显。
四、复制过滤器的应用场景、优缺点与注意事项
4.1 核心应用场景
复制过滤器适合所有需要按业务规则裁剪同步数据的场景,常见的有这几类: 第一,多区域多活同步,比如不同地区的仓、不同区域的内容节点,只同步本区域的数据; 第二,数据隔离同步,比如把内部数据和外部数据分开同步,只把允许对外展示的数据同步到对外的数据库; 第三,测试环境同步,比如把生产环境的数据同步到测试环境时,只同步最近一个月的测试数据,不同步历史的旧数据; 第四,权限控制同步,比如不同的子系统只同步自己权限范围内的数据,比如财务系统只同步财务相关的数据,业务系统只同步业务相关的数据。
4.2 技术优缺点
复制过滤器的优点很明显:首先是大幅降低传输开销,源头过滤能直接减少传输的数据量,降低带宽成本;其次是减少目标节点的存储压力,不需要存储没用的数据;第三是提升同步效率,数据量少了,同步的速度自然就快了,系统的响应也会更及时。
但它也有缺点:首先是配置有一定门槛,需要写JavaScript函数,还要理解复制任务的配置规则,对新手不太友好;其次是有一定的性能损耗,每一条数据都要经过过滤器函数的判断,要是数据量特别大(比如每天有上百万条数据需要判断),可能会稍微影响源数据库的性能;第三是过滤器的逻辑不能太复杂,要是过滤器函数里做太多的判断、计算,不仅会增加性能损耗,还可能导致过滤逻辑出错。
4.3 使用注意事项
第一,过滤器函数要尽量简单,只做必要的判断,不要写太复杂的逻辑,比如不要在过滤器里循环、不要做字符串的复杂处理,能快速返回布尔值最好;
第二,要保证业务属性的一致性,比如商品的warehouse_code字段,一定要确保每条商品数据都有这个字段,而且值是正确的,要是有的商品没有这个字段,过滤器会返回false,就不会被同步;
第三,要注意参数的传递,复制任务里的query_params要和过滤器函数里的判断逻辑对应,比如华北仓的复制任务,一定要传warehouse: "HB",要是传错了,就会同步错数据;
第四,要测试过滤器的逻辑,配置好复制任务后,一定要插入测试数据验证,确保只有符合条件的数据会被同步,避免出现漏同步或者多同步的问题;
第五,不要用过滤器来做安全控制,复制过滤器只是用来裁剪同步数据的,不是用来做权限控制的,要是需要做安全控制,应该用CouchDB的权限系统,因为过滤器的逻辑是可以被修改的,不能保证绝对的安全。
五、总结
CouchDB的复制过滤器是一个非常实用但经常被忽略的功能,它能按业务属性裁剪变化流,直接降低同步的传输开销、存储压力,提升整个系统的效率。很多团队觉得它配置麻烦、性能有损耗,其实只要用对了场景,它的收益远大于成本。
在实际工作中,我们应该根据自己的业务需求,合理配置复制过滤器,不要盲目全量同步。对于多区域多活、数据隔离、测试环境同步等场景,复制过滤器能带来非常明显的优化效果。当然,使用的时候也要注意它的优缺点和注意事项,确保过滤器的逻辑简单、正确,避免出现问题。
最后要强调的是,复制过滤器不是万能的,它只是同步优化的一个手段,要是你的系统本身的同步架构有问题,比如源数据库和目标数据库的网络延迟特别高,那复制过滤器也解决不了根本问题,还需要配合其他的优化手段,比如升级网络、优化数据库架构等。
评论
围绕“CouchDB复制过滤器在大型数据集同步中的作用被低估了?按业务属性裁剪变化流降低传输开销的技巧”参与讨论