成本低误差小,携程基于 Kafka 的 Serverless 延迟队列的实践
作者简介
【资料图】
Pin,关注 RPC、Service Mesh、Serverless 等云原生技术。
一、背景随着上云项目的不断推进,大量的应用需要部署到 aws 上,其中有很多应用都依赖延迟队列的功能。而在aws上,我们选择以Kafka 作为消息队列,但是 Kafka 本身不支持延迟队列,这就需要思考如何基于 Kafka 来实现延迟队列。
二、需求统计了一下所有需要使用到延迟队列的场景,有以下几大特点:
延迟时间不固定。有的 topic 需要支持 5 分钟的延迟,有的却要求支持 7 天的延迟。延迟消息数量小。所有的场景中涉及到的每天延迟消息的数量不超过 1 亿条,每条消息的大小不超过 1MB。延迟消息不能丢失,可以不保证有序。延迟误差小。延迟误差是指实际消费消息的时间和希望消费消息之间的时间差值。根据统计的业务场景来看,要求延迟误差在 2s 以内。生产延迟消息的峰值比较高。很多情况下,业务会一次性创建 1000 万条延迟消息,并且这些延迟消息的延迟时长都是一致的。三、目标由于实现延迟队列的方式有很多,我们在满足需求的前提下,制定了几个目标:云上成本低、运维成本低、开发成本低、稳定性高和延迟误差小。
四、产品选型在aws上支持消息队列的产品有 RabbitMQ、Apache ActiveMQ 和 SQS。其中 RabbitMQ 和 Apache ActiveMQ aws 主要是托管其安装部署,并非是以 Serverless 的方式对外提供服务。另外,我们当前已经选择使用 Kafka 作为消息队列,若仅仅为了满足延迟队列的功能而去更换消息队列,成本显然是巨大的。
除此之外,aws 还提供了 SQS 来支持延迟队列,虽然 SQS 是 Serverless 的,但是 SQS 有他自身的局限性:SQS 最多支持 15 分钟以内的延迟,明显无法满足我们的需求。
可见,仅仅基于云上已有的产品已无法满足我们的需求,基于这个原因,我们开始调研延时消息的实现方案,看看能否通过少量的开发来实现我们的需求。
五、方案调研业界实现延时队列功能的方案比较多,我们对其进行了简单的分析,具体如下:
5.1 RabbitMQ
RabbitMQ 是基于 TTL+ 死信队列的方式来实现的。具体来说,通过设置消息的 TTL,当达到 TTL 时消息还没有被消费,此时会投递到死信队列。TTL 分两种:
Queue 级别的 TTL:所有消息统一的 TTLMessage 级别的 TTL:每条消息可以是不同的 TTL,但是存在队头阻塞问题该方案的优点是实现简单,但是延迟误差不确定。
5.2 Apache ActiveMQ
Apache ActiveMQ 是基于定时调度的方式来实现的。具体来说,配置延迟时间或者 cron 表达式表示消息的投递策略,基于 Java 的 Timer 实现,将消息分级存储在文件和内存中。
该方案的优点是实现简单,延迟误差可控,但是可能会占用大量内存。
5.3 RocketMQ
RocketMQ 是基于定时调度+延迟等级的方式来实现的。具体来说,将延时消息发送到指定的延时等级队列(一共有 18 个等级),然后通过一个定时器进行轮询这些 ConsumeQueue 实现延时的效果。具体实现如下:
修改消息 topic 名称和队列信息投递到对应等级的延时消息的 ConsumeQueue 中ScheduleMessageService消费ConsumeQueue中的消息再重新投递到 CommitLog 中将 CommitLog 中的消息投递到目标 topic 中,消费者消费目标 topic 中的消息该方案的优点是延迟误差可控,但是实现复杂。
5.4 Redis
基于 Redis 实现延迟队列的方式有很多,在这里简单描述两种:
1)定时轮询
该方案的大致步骤如下:
将消息的延时时间戳作为 zset 的 key,消息的 ID 作为 zset 的 value消息 ID 作为 key,消息体序列化成 String 作为 value 存储在 Redis 中定时轮询 zset,大于当前时间则投递到 Redis 的 List 中供消费者消费2)Key 过期监听器
每条消息设置一个过期时间,监听过期事件然后将消息投递到 target topic。
基于 Redis 实现延时队列的优点是实现简单,但是都可能存在丢消息的情况,并且存储成本高。
六、实现方案既然使用单一的云上产品不能满足我们的需求,那就只能考虑通过少量的开发并结合云上产品的特性来实现基于 Kafka 的延迟队列的功能。具体的实现方案有如下几种:
6.1 RabbitMQ 或 Apache ActiveMQ
RabbitMQ 或者 Apache ActiveMQ 都是 aws 上支持的产品,从功能层面来看是可以满足需求。当前的消息队列是基于 Kafka 实现的,如果再结合 RabbitMQ 或者 Apache ActiveMQ 来实现延迟队列的功能,主要面临的问题是:缺少对 RabbitMQ 或者 Apache ActiveMQ 相关的技术储备,由于 aws 上对 RabbitMQ 或者 Apache ActiveMQ 仅仅只是部署层面的托管,当出现问题时,是需要有研发人员自己去 troubleshooting 的。所以,该方案就不考虑了。
6.2 基于 SQS 的多级队列
既然 SQS 已经支持 15 分钟内的延时队列,那么如果要实现更长时间的延迟队列是不是可以考虑通过多级延迟队列来实现?具体实现方案如下:
在延迟消息中增加一个字段 times 用来表示当前是第几轮(借鉴时间轮算法的思路)。如果延迟消息的延迟时间小于 15 分钟,将延迟消息的 times 设置为 0,直接投递到 SQS 中。如果延迟消息的延迟时间大于 15 分钟,计算一下 times 的值(延迟时间/15 分钟),然后直接投递到 SQS 中。如果 Consumer 从 SQS 中消费到了一个延迟消息且 times 大于 0,则将 times 的值减去 1,再次投递到 SQS 中。如此反复,直到 times 为 0。如果 Consumer 从 SQS 中消费到了一个延迟消息且 times 为 0,则表示该消息已经达到了延迟时间,则 Consumer 会直接将该消息投递到对应的目标 topic。这种方案虽然能够实现延迟队列的功能,且 SQS 本身也是 Serverless 的,维护成本也比较低。
但是我们调研了一下 SQS 的计费标准发现,SQS 主要是根据消息数量来收费的。这样一来,如果延迟时间越长,消息数量会被放大的越严重。而我们实际业务中延迟时间在 15 分钟以内的没有,一般是 1 小时到 7 天,所以这种方案不可行。
6.3 基于 SQS 和定时调度策略
使用基于 SQS 的多级队列的方式最大的问题是云上的成本问题,更具体一点是云上的存储成本问题。因为该方案将所有的延迟消息都存储在 SQS 中,这是导致费用增加的最主要原因。既然如此,那我们是不是可以考虑将大于 15 分钟延迟时间的消息写入到一个成本低的存储上,然后在时间延迟时间小于 15 分钟的时候将其查询出来投递到 SQS 中即可。这样一来,延迟时间的长短不会对 SQS 的费用有影响,仅仅只需要考虑如何选择一个存储成本低、读写方便的 Serverless 产品作为延迟消息的存储即可。
基于这一思路,设计了一个基于 SQS 和定时调度策略的实现方案:
具体流程如下:
生产者 Producers 生产的正常消息直接投递到 Kafka 的目标 topic,如果是延迟消息投递到 Kafka 的一个延迟消息的 Delay Message Topic 中。Consumer 消费 Delay Message Topic 中的消息,如果该消息的延迟时间小于 15 分钟,直接投递到 SQS(Delay Queue)中。如果消息的延迟时间大于 15 分钟,直接将消息写入到 Message Store 中。Scheduler 会定时扫描 Message Store 中的消息,如果发现延迟时间小于 15 分钟,则直接投递到 SQS(Delay Queue)中,Scheculer 是通过 Event Bridge 来触发的。Emitter 会消费 SQS(Delay Queue)中的消息,并将该消息投递到目标 topic 中。整个流程不算复杂,里面涉及到的 aws 服务都是 Serverless 的,但是涉及的服务太多以后 troubleshooting 就会比较复杂。
基于以上问题,我们对该方案的实进行了改进和简化,具体如下:
具体流程如下:
生产者 Producers 生产的正常消息直接投递到 Kafka 的目标 topic,如果是延迟消息投递到 Kafka 的一个延迟消息的 Delay Message Topic 中。Service 消费 Delay Message Topic 中的消息,如果该消息的延迟时间小于 15 分钟,直接投递到SQS(Delay Queue)中。如果消息的延迟时间大于 15 分钟,直接将消息写入到 Message Store 中。Service 会定时扫描 Message Store 中的消息,如果发现延迟时间小于 15 分钟,则直接投递到 SQS(Delay Queue)中。Service 会消费 SQS(Delay Queue)中的消息,并将该消息投递到目标 topic 中。简化后的方案将 Consumer、Emitter 和 Scheduler 的逻辑都集中在 Service 这个服务中,Service 服务是集群部署的,这种方案所有的逻辑都在 Service 这个服务中,在 troubleshooting 时相对来说要方便一些。整体实现方案的大方向确定好以后,还需要细化以下几个问题:
1)消息如何存储
我们可以看到 Message Store的主要功能是存储延迟时间大于 15 分钟的延迟消息, 并供 Scheduler 进行查询,查询的时候是根据时间来查询的。支持 Serverless 方式存储的服务也比较多,经过调研最后选择 DynamoDB。
DynamoDB 中的 partition key 是延迟时间,sorted key 选择 message id,这样可以保证通过 partition key 和 sorted key 能够唯一定位到一条消息,不会出现冲突。同时,在查询的时候只需要根据 partition key 就可以查询出该时间片段内的所有消息,也不会出现热点或者 partition 不均匀的问题。
假设 partition key 为 1677400776(是 2023-02-26 16:39:35 的时间戳,精确到秒),则该 partition key 中对应的所有消息都是延迟时间从 2023-02-26 16:39:35 到 2023-02-26 16:39:36 之间的消息。因为每个消息都有唯一的 message id,所以将 sorted key 设置为 message id 就不会导致消息冲突的问题。Scheduler 在查询的时候只需要传入需要查询的时间戳就可以拉取该时间段内所有的消息,如果没有查询到,则表示该时间段内没有延迟消息。
同时,对于 DynamoDB 中的消息也设置了 TTL 用来自动删除数据的,设置的 TTL 时间比延迟时间大 24 小时,主要是方便 troubleshooting 的。当 DynamoDB 中的延迟消息被投递到 SQS 以后,会调用 API 去删除该消息。DynamoDB 中消息的数据结构还包括 topic、消息体等信息。
2)单点问题
单点问题主要是因为对于存储在 DynomaDB 中大于 15 分钟的延迟消息进行扫描的时候,接收到扫描通知的 Scheduler 出现了问题,则该时间段的消息没有被投递到 SQS中,从而导致消息丢失。现在 Scheduler 的功能都集成在 Service 服务中,而 Service 服务是集群部署,所以 Scheduler 不存在单点的问题。
但是需要解决另外一个问题:如何保证集群中只有一个 Scheduler 扫描 DynamoDB 中的数据,并且当 Scheduler 出现了问题以后,集群中其他 Scheduler 也可以继续接着执行?
为了解决这个问题:我们使用了 SQS 的 FIFO 队列。SQS 支持两种队列,一种是 Standard 对列,一种是 FIFO 队列。FIFO 队列可以严格保证消息的有序,同时支持消息的可见性,也就是说在一段时间内该消息只能有一个消费者可见,其他消费者无法访问。同时,SQS 的 FIFO 队列还支持去重的功能。基于 SQS 的 FIFI 队列的这些特性,解决单点问题就比较容易了。具体实现方案如下:
在 Service 服务中启动一个 Timer 定时向 SQS 的 FIFO 队列投递通知消息,一分钟投递一次。通知消息的消息体是当前时间的时间戳,精度到分钟。这样即使有 n 个 Timer 在同一分钟内向 SQS 的 FIFO 队列投递 n 次消息,也只会有一条消息被成功投递到 SQS 的 FIFO 队列中,n-1 条消息被 SQS 的 FIFO 队列的去重功能过滤掉了。投递到 SQS 的 FIFO 队列中的可见性设置为 5分钟(可以配置)。可以保证在 5 分钟内只有一个 Scheduler 可以消费到通知消息,如果该 Scheduler 出现了故障,后续的其他 Scheduler 也可以接着继续消费。当 Scheduler 消费到通知消息时,会根据消息内容转换成时间戳,并在 DynamoDB 中查询这一时间戳范围内的所有消息,修改消息的延迟时间,投递到 SQS 的 Standard 队列中,最后删除 SQS 的 FIFO 队列中的这一条通知消息。基于上面的方案,能够很好的解决单点问题。
3)消息丢失问题
因为 Timer 和 Schduler 都在 Service 服务中,都是集群问题,不存在单点问题。并且,SQS 的 FIFO 队列能够保证消息严格有序,所以不存在消息丢失的问题。唯一可能存在的问题是,因为消息量大积压导致的消息延迟过长。
4)如何查询延迟消息
Scheduler 查询的消息要满足该消息的延迟时间小于 15 分钟,所以在接收到通知消息并转换成对应的时间戳以后,查询当前时间戳 +14 分钟(延迟消息不能超过 15 分钟)的消息即可。
5)如何部署 Service 服务
对于 Service 服务,我们采用了 ECS+Fargate 的方式来部署。整个代码的部署都是通过 Terraform 脚本来创建 Code Pipeline、DynamoDB、SQS 和 ECS 等资源实现的,所有的资源都是通过代码来实现的,整个部署方案的设计全部都是基于 gitOps 的思想。
经过多以上方案的综合评估,最后我们选择基于 SQS 和定时调度策略的方案来实现延迟消息。
6.4 性能优化
以上方案在实践的过程中,做了很多优化,大致可以归纳成以下几点:
1)消息积压
由于需要处理的延迟消息会因为消费能力不足的情况导致消息积压的问题。优化这一问题主要从以下几个方面入手:
Delay Message Topic 的 partition 设置成 64 个。提高 Kafka 消费的消费能力可以通过增加 consumer 来实现,但是前提是要保证 partition 的数量大于等于 consumer 的数量。降低 Service 的服务配置,增加 Service 服务的副本数。Service 集群消费 Delay Message Topic 中的消息,副本数越多,消费能力越强。2)DynamoDB 中 WCU 和 RCU
DynamoDB 的费用有很大一部分是通过 WCU 和 RCU 来统计的。WCU 是指单位时间内消息写入的数量,RCU 是指单位时间内消息读取的数量。如果单位时间内写入消息的数量超过了 WCU 的限制会导致消息写入失败,同理也会导致读取消息失败。
如果将 WCU 和 RCU 都设置成峰值肯定不会导致读写失败的问题,但是会产生巨大的成本浪费。为此,我们将 WCU 和 RCU 设置成动态扩缩容的方式。在扩容期间如果产生失败,则进行重试。经过相关参数的优化,现在已经可以达到一个最佳现状。
3)ECS 扩缩容设置
ECS 中最小的运行单元是 task,对于每一个 task 要求扩容要快,缩容要缓慢。task 快速扩容遇到的最大的问题是,拉起 Service 的耗时比较长。对于 Service 服务我们采用 golang 来实现,扩一个 task 能够基本上可以在 8s 内完成。扩缩容是基于 CPU 的使用峰值来设置的,每次扩容会扩 4 个 task,每次所容会缩 1 个 task。
4)消息平滑处理
由于写入 Delay Message Topic 中的消息峰值可能会比较大,如果快速消费这些消息,会导致后续对 DynamoDB 的读写压力比较大。因此,在消费 Kafka 的 Delay Message Topic 中的消息时,会将控制每个 Service 消费消息的数量。尽管有多个 Service 会同时消费,但是对于单个 Service 来说,写入消息的数量较少,对 DynamoDB 来说,每一次的写入比较平稳,并非一次性写入大量的数据,从而写入失败的概率会小很多。
6.5 实践效果
目前已经在生产环境稳定运行了 6 个月,各项指标都比较健康,拉取了最近 4 周的数据。
1)延迟消息成功率
如上图所示,延迟误差在 2 秒以内的延迟消息成功率基本上是 100%。
2)延迟消息的数量
如果上图所示,延迟消息在 5 分钟内的峰值达到 15 万,也就是峰值每秒处理 500 个延迟消息。
3)DynamoDB 性能指标
从 PutItem ThrottledRequests 这个指标可以看出,通过 DynamoDB 写入消息没有发生写入失败的情况。从 QueryThrottledRequests 这个指标可以看出,通过 DynamoDB 查询消息也没有发生查询失败的情况。从 QueryReturnedItemCount 指标可以看出,延迟消息的峰值是 5 分钟内 3350 条,每秒低于 60 条。这是因为我们在 Service 中对写入消息进行了缓冲,从而降低了并发读写压力。
4)Kafka 消息积压
如上图所示,Kafka 在 5 分钟内消息积压的峰值是 6 万,积压的消息都能很快被消费掉。
5)Timer 性能指标
Timer 会每分钟向 SQS 的 FIFO 队列中投递一个消息,消息的数量与 Service 的副本数相同。从上图可以看出,5 分钟内最多投递了 300 个消息(因为 Service 的副本数最大为 64)。但是最后接收的消息是5分钟内仅仅接收了 5 个消息,也就是 1 分钟接收 1 条消息。
七、总结由于该实现方案完全是基于 Serverless 的方式实现的,所以维护成本非常低。尽管开发起来有些复杂,但这是一次性的成本投入。从近几个月的数据来看,云上的使用成本大约每个月不超过 200 美元,误差延迟比较小,到目前为止整体运行起来比较稳定。
标签:
- 成本低误差小,携程基于 Kafka 的 Serverless 延迟队列的实践
- 青海高速交警发布交通管制通告
- 坦克300最大对手来了,全新一代北京BJ40设计曝光!
- 始兴县开展第五次全国经济普查综合试点工作 地毯式清查确保不重不漏
- 2023年服贸会首场海外推介会走进法国巴黎
- 铁路部门紧密对接农业生产需求 高效服务全国“三夏”
- 2023高考 | 今日18:00系统关闭!志愿填报时间截止后不接受任何补报志愿
- 海关总署进出口食品安全局负责人就国际原子能机构发布日本福岛核污染水处置综合评估报告回答记者提问
- 美国小非农超预期,恒生科技ETF基金(513260)接连获资金增仓!恒生指数再度破净,何时企稳?
- 沙盒与副本:英勇之地进不去/打不开游戏/联机掉线/卡顿/黑屏/闪退
- 大专学动漫设计好找工作吗?动漫课程推荐!
- 理想汽车法务部:“App 屏蔽蔚来充电桩”传闻与事实不符
- 中国电子材料产业创新发展(唐山)大会举办 7个项目现场签约
- 铂傲推出 Beosound 2 家居音响雅黑新配色,到手价 23480 元
- 长沙银行:7月6日融资净买入23.66万元,连续3日累计净买入541.96万元
- 凯赛生物(688065.SH):公司与招商局的此次业务合作是双方需求的落地
- 吉木萨尔县开启社银合作“就近办”服务新模式
- 中国在这方面正引领世界!外籍人士点赞亚运之城低碳实践
- 1865万股份将被拍卖,湖北银行回应:占比较小经营未受影响
- 王亚平回山东啦!她说:青春的梦想可以传承,当年“太空授课”学生已成“战友”
- 蹲点影记①|土豆育种攻关:“天选之子”落地成熟啦
- 夏日养生正当时 京东京造精选养生茶饮助你降温解暑、健康一“夏”
- 《南通老字号》荣获南通市第十六届哲学社会科学优秀成果奖
- 赵露思新剧,被全网抵制?
- 【2023中国汽车论坛】叶盛基:《中国汽车工业发展报告2023》解读
- 我 新冠5阳!现在活蹦乱跳
- 联邦快递推出线上自助反馈平台
- 华为带火了,长沙老板喜提22亿
- 五藏六福膏到底有啥用_五藏六福
- 公积金贷款有必要提前还款吗 情况是这样的
- 航空工业泛华:极速攻克 冲锋在前
- 新乡经开区组织召开优化营商环境工作推进会
- lovely怎么读(cut怎么读)
- 推动汽车产业转型升级,广州加速迈向“智车之城”
- 真相 | “经济胁迫”是美国手中杀人的“刀”
- “男子掏警官证让交警放行”,当地深夜通报
- 微研报:国内机械制造业需求关注复苏进展
- 中部地区国家公共文化服务体系示范区创新发展主题交流活动在晋城举行
- 期货公司观点汇总一张图:7月7日农产品(棉花、豆粕、白糖、玉米、鸡蛋、生猪等)
- 小暑:倏忽温风至 因循小暑来
- SEO文章采集软件:经验与心得
- 23家企业推出昇腾AI系列新品 覆盖云、边、端智能硬件
- 2023薛之谦演唱会即将开唱!青岛高新交警这份提示请查收
- 天津美达菲国际学校2023年国际高中班招生简章公布,高额奖学金发布!
- 千亿民营石化巨头,要打一场硬仗
- 中泰证券:给予山推股份增持评级
- 国泰君安晨报
- 港股燃气概念走低 新奥能源跌7%
- 临武:放手游戏 回归童年
- 外地人在苏州怎么获取积分?
- 还是熟悉的味道,哈弗全新H5官图发布!
- 中国电子:除了“大练模型”外,还要全力构建三大体系
- 美联储加息令投资者远离,投行下调今年余下时间金银均价预测!
- 2023《迷你世界》7月8日福利激活码分享
- 巴黎时装周刘亦菲周冬雨关晓彤比谁都美
- 东风科技:公司暂未提供汽车锂电池相关产品
- 李玟姐姐呼吁大家不要作任何揣测 不要相信网上的信息
- 两个细节,透露法国骚乱不简单,马克龙的对华政策,动了美国蛋糕
- 二胎准生证办理需要什么材料,二胎准生证在哪里办理
- 主板肝炎板块股票有哪几家?
- 上半年24个创新药、28个创新医疗器械获批上市
- 河南便民利民微改革取得积极成效
- 中央气象台继续发布暴雨蓝色预警和强对流天气黄色预警
- 杭州快递小哥把两万多的化妆品放车顶上!结果……
- 适宜技术融入近视防控,中医治未病能力进一步提升
- 2023年简阳市土地推介会推出1136亩宝地
- 河洛古俗回望 缫 丝 村南村北响繅车
- 大学四年如何不虚度,这份攻略送给准大学生
- 南方早稻陆续收获 夏粮收储加紧进行
- 港股异动 | 昊天国际建投(01341)涨超7% 年度纯利1.08亿港元 同比扭亏为盈
- 致尚科技正式登陆创业板,上市首日一度涨超20%
- 拼多多上的笔记本(拼多多买的笔记本靠谱吗)
- Steam卖爆力压《艾尔登法环》!《潜水员戴夫》好评如潮:M站评分略降
- 海希通讯(831305):工业无线遥控设备企业研究
- 【高端访谈】AI技术重构影像产业 工具价值将迎来爆发——访美图公司董事长吴欣鸿
- 有一种怀旧,叫做木综厂遗址公园!
- 男子入户盗窃被发现后行凶致2死 具体情况是怎么样的?
- 山东已建成中医药优势专科集群28个,住院患者次均费用降低
- “关键变量”成为“最大增量”(高质量发展调研行)
- 速腾进化论 从驾驶者之车到全能选手|汽势5年礼赞70车
- 工行凯里分行:金融为民初心在服务“村超”中闪闪发光
- 迪普科技:7月6日融资买入376.84万元,融资融券余额1.31亿元
- 李玟曾买走一位湖南老人 200 斤滞销橙子,当事老人:没见过她,但我记得她
- 我国天文学家提出同时揭秘宇宙第一代星系和暗物质的新方法
- 领动火花塞多少公里换一次最好
- 她是央视女主持,颜値与实力并存,如今40多岁无人娶!
- 大中小学共建实践就业基地!首师大师范生有了对口实习地
- 康欣新材:公司目前经营面临较大压力
- 寿仙谷: 公司是2022年杭州亚运会官方指定灵芝产品供应商
- 租借女友:水原千鹤约会装手办追加官图如果有了袜子将会绝杀
- 《原神》海岛蔓生溪谷宝箱位置一览
- 大学四年如何不虚度,这份攻略送给准大学生
- 玻璃清洁剂有毒吗 哪个牌子的好用
- 勇士官方晒保罗荣誉:12次全明星 11次最佳阵容 6抢断王 5助攻王
- 乔治若来我就走,明确表态不当三当家,湖人距离顶级3D就差一步
- 勇士官方晒保罗荣誉:12次全明星 11次最佳阵容 6抢断王 5助攻王
- 沉迷黄金的大妈们,后继有人了!
- 高中奖状名称大全(小学生奖状名称大全)
- 香港贸发局:欢迎大湾区内地读者参加香港书展
- 从产业协同发展到共塑“资足常乐”,一项项川渝合作项目在大足区落地开花