写点什么

上云价值倒挂严重,1 年云支出高达 320 万美元,这家公司彻底去云、放弃 K8s

  • 2023-03-31
    北京
  • 本文字数:9447 字

    阅读完需:约 31 分钟

上云价值倒挂严重,1年云支出高达320万美元,这家公司彻底去云、放弃K8s

“我们成功了,找到了一条适合自己的迁移道路”。

37signals 的“去云”和“去 K8s”行动


根据 37signals 公布的一组数据显示,2022 年,37signals 在所有 AWS 云服务上总共花费了 3,201,564 美元,这相当于每月 266,797 美元。


这一巨额支出令 37signals 决定,通过“去云”的方式大幅削减费用。


2022 年 10 月,37Signals 公司首席技术官兼 Ruby On Rails 之父 David Heinemeier Hansson 发文称,37Signals 要“下云”。


对 37signals 运营团队来说,2023 年里最重要的一项举措就是摆脱云依赖,着手将应用程序栈迁移回本地数据中心的自有硬件之上。37signals 还决定放弃目前业内主流的容器编排系统 K8s,也没有选择 Docker Swarm,转而寻求性价比更高的替代方案。“去云+去 K8s”,并不是一个简单的决定,这意味着要舍弃行业的主流方案和基准,走一条没人走过的道路。


但令 37signals 感到惊喜地是,这项行动在短时间内取得了惊人进展。


近日,37signals 在博客上讲述了这一决定背后的考量,实施历程和成效。以下是是博客原文,由 InfoQ 翻译。

为什么要折腾?

多年以来,我们的很多应用程序都运行在不同云服务商的平台之上。


最初是把应用程序从自有数据中心迁往 AWS ECS,毕竟听说这样能直接使用 Docker build 并节约最终成本。


我们对 Docker 没有任何意见,但 ECS 本身的灵活性确实不够。所以我们决定转向 Google Cloud 的 GKE 来尝试 Kubernetes,但由于网络控制平面宕机,我们决定马上放弃。但请别误会,我们的云之旅并没有就此终结,反而把遗留应用程序迁移到了 AWS Kubernetes(EKS),一部分应用程序至今仍然运行在那里。


但在这样的过程中,我们不可避免地要积累下一定的技术债务和复杂性。除了部署策略之外,我们还需要发明新的工具来管理这些堆栈,并创建贴合需求的 CI/CD 来支撑运营和编程。还有监控策略,这些都是需要费心的工作。


再就是怎样把信息安全和运营安全这两种完全不同的范式融合起来,比如对员工做安全培训。反正云服务用起来就那个样子,一会是某项服务出问题了,一会是整个区域崩溃了……


总之,运营这样一套体系会涉及到大量流程。我们发现实际支出往往超过我们从中获得的回报。这种价值倒挂不仅体现在经济上,更体现在运营上。


下面来看我们的最小应用程序 Tadalist 在 EKS 上的运行情况:


                            +--------------------------------------------------------+                              |    eksctl VPC                                          |                              |    +----------------------------------------------+    |                              |    |                      EKS                     |    |                              |    | +------------------------------+ +---------+ |    |                              |    | |app namespace                 | |default  | |    |                              |    | |                              | |namespace| |    |+-------------------+         |    | +--------+ +--------+ +--------+ +---------+ |    ||   tadalist VPC    |         |    | |pod     | |pod     | |pod     | |pod      | |    ||                   |         |    | +--------+ +--------+ +--------+ +---------+ |    || +---------------+ |         |    | |Unicorn | |Unicorn | |Unicorn +-+Logstash | |    || |   Services    | |         |    | |        | |        | |        | |         | |    || |               | |   VPC   |    | +--------+ +--------+ +--------+ +---+-----+ |    || | +-----------+ | | Peering |    | |Nginx   | |Nginx   | |Nginx   |     |       |    || | |    RDS    | | <--------->    | |        | |        | |        +-----+       |    || | +-----------+ | |         |    | +-^------+-+-^------+-+-^------+             |    || +---------------+ |         |    |   |          |          |                    |    |+-------------------+         |    |   |          |          |                    |    |                              |    +----------------------------------------------+    |                              |        |          |          |                         |                              |      +-+----------+----------+---+                     |                              |      | Application Load Balancer |                     |                              |      +------------^--------------+                     |                              |                   |                                    |                              +--------------------------------------------------------+                                                  |                                                  |                                                  +                                           Internet Traffic
复制代码


看起来就很乱,对吧?


而且这还只是粗略的基础设施结构,不涉及运行其上的其他辅助工具,例如:


  • 集群自动缩放器

  • 入口控制器

  • 存储驱动

  • 外部 DNS

  • 节点终止处理程序

  • VPN、对等互连、路由表、NAT 等复杂概念

  • DNS 处理位置

  • …这里还没提到资源身份与访问管理等其他需要维护的元素,更不用说由此衍生出的基础设施即代码了。


公平地讲,纵观整个上云经历,我们一直也没体会到“云让生活更简单”的承诺。


而 Tadalist 还只是个非常基础、非常独立的 Rails 应用核心。其他体量更大的应用程序会对后端服务有着更多依赖性,所以架构复杂程度可想而知。


我们去云化的第一步,是打算配合厂商支持在自有硬件上运行 Kubernetes。这样就能保住多年以来投入并建设的资产,“单纯”把我们的工具重新部署在新位置。


另一个挑战是,我们的大部分应用程序在几年之前就完成了容器化,为了继续兼容遗留资产,我们希望保持这种状态。


这主意听起来全是好处,但实际操作起来却昂贵且极为复杂。 所以我们还是得回到规划中来。

迁移 Tadalist

mrsk成为我们这波去云加去 K8s 行动的核心范式:


它能够创建一条路径,帮助我们简化对现有容器化应用的部署,同时极大加快整个操作过程。我们既能保留大量现有容器化技术,又能以相当熟悉的全新方式运行应用程序。


多年以来,我们一直在数据中心内用 capistrano 部署。


Basecamp 4 和其他应用,而 mrsk 指向的也是相同的命令式模型。不必新增控制平面、活动部件还能有所减少,这太棒了!


Tadalist 无疑是个完美的迁移样本。 它在所有应用程序中的重要度最低,而且没有任何付费客户。事实上,Tadalist 就像是为运营系统报警的“金丝雀”,而这一次它将再次扮演这个角色。


当然,迁移不可能一蹴而就。 在开始之前,我们还得做好其他准备。

配置虚拟机

近年来,我们一直关注云及相关技术,所以我们的本地基础设施配置流程已经有点落后。除了去云工作之外,我们还希望全面实现配置管理的现代化与精简,并升级至最新版 Chef。


由于 mrsk 将以本地服务器为目标,我们还需要建立新流程来快速、轻松配置虚拟机。


结合现有知识,我们决定运行基于 KVM 的虚拟机访客,使用 cloud-init 简化引导,并将这一切都编排进同一套架构。这些改进让访客虚拟机的完全引导时间从之前约 20 分钟缩短到了不足 1 分钟——现在我们还可以在 KVM 主机上通过一条 chef converge 同时定义和启动多个虚拟机。


# Example definition from our cookbooknode.default['guests'] = case node['hostname']                         when "kvm-host-123"                           {                            "test-guest-101": { ip_address: "XX.XX.XX.XX/XX", memory: "8192", cpu: "4", disk: "30G", os: "ubuntu22.04", os_name: "jammy", domain: "guest.internal-domain.com" },                           }                         when "kvm-host-456"                           {                            "test-guest-08": { ip_address: "XX.XX.XX.XX/XX", memory: "4096", cpu: "2", disk: "20G", os: "ubuntu18.04", os_name: "bionic", domain: "guest.internal-domain.com" },                           }                         end
include_recipe "::_create_guests"
复制代码


# Excerpt from our ::_create_guests Chef recipenode['guests'].each do |guest_name, guest_config|  execute "Create and start a new guest with virt-install" do    command "virt-install --name #{guest_name} --memory #{guest_config[:memory]} \             --vcpus #{guest_config[:cpu]} --network bridge=br0,model=virtio --graphics none \             --events on_reboot=restart --os-variant #{guest_config[:os]} \             --import --disk /u/kvm/guests-vol/#{guest_name}-OS.qcow2 \             --noautoconsole --cloud-init user-data=/u/kvm/guests-config/user-data-#{guest_name},network-config=/u/kvm/guests-config/network-config-#{guest_name} \             --autostart"  endend
复制代码


之所以获得如此巨大的速度提升,就是因为我们的系统变简单了,除了基本的用户管理、Filebeat 配置和 Docker 安装之外,再没什么其他多余的部分。

日志记录

我们的日志记录管线是一套完全整合的 ELK 栈,能跟我们的云和本地栈实现互操作。


mrsk 的日志记录以纯 Docker 日志为基础,所以我们要做的就是对 Filebeat 进行重新路由,从 /var/lib/docker/containers/ 中获取这些日志并将其发送至 Logstash 安装。

CDN

多年以来,我们一直在用 CloudFront 和 Route53 来获取 CDN 和 DNS 功能。但现在我们决定去云,自然得找个替代方案 — 答案就是 Cloudflare,现在位于我们数据中心本地 F5 负载均衡器之前。

CI/CD

之前用的是 Buildkite,现在我们转向了 GitHub Actions。

数据库复制和备份

RDS 确实不错,但我们之前也已经有几十年的本地 MySQL 数据库运行经验。对于由 mrsk 支持的应用部署,我们沿袭之前的实践并升级了技术栈以运行 Percona MySQL 8,还把基于 cron 的备份流程引入数据中心内的专用备份位置,所有这些都被封装在同一 cookbook 内以供相关数据库服务器使用。


在不到六周时间内,我们已经建立起了上述运营设施,mrsk 开始正常起效,Tadalist 也立足自有硬件成功对接了生产环境。


切换过程非常简单,一条转移小型数据库和转型 DNS 的调用就能实现。但期间确实出现了一定停机时间,这是因为 Tadalist 不支持零停机。


完成之后,我们来看 Tadalist 现在的样子:


+----------------------------------------------------------------+|  On-Prem                                                       ||            +--------------------------------------------+      ||            |                                            |      ||            |                                   +--------v--+   ||  +---------+-------+ +-----------------+------>| db-01     |   ||  | app-01          | | app-02          |       |           |   ||  | +-------------+ | | +-------------+ |       |    mysql  |   ||  | | tadalist-app| | | | tadalist-app| |   +---+-----------+   ||  | |             | | | |             | |   |                   ||  | +-------------+ | | +-------------+ |   |replicates to      ||  | |   traefik   | | | |  traefik    | |   |   +------------+  ||  | +-------------+ | | +-------------+ |   |   | db-02      |  ||  +-+-----------+-+-+ +-+--+----------+-+   +-->|            |  ||                |          |                    |   mysql    |  ||                |          |                    +------------+  ||             +--v----------v--+                                 ||             |       F5       |                                 ||             |  load balancer |                                 ||             +--------+-------+                                 ||                      |                                         |+----------------------+-----------------------------------------+                       |              +--------+-------+              |   Cloudflare   |              +--------^-------+                       |                       |               Internet traffic
复制代码


这就是非常标准的 Rails 应用部署样式了,对吧?事实证明,包括 Tadalist 在内,我们的大多数应用也就只需要这些。


应用程序主机被配置为 F5 负载均衡器上的池,负责将流量路由至它该去的地方,同时为 Cloudflare 提供作为通信目标的公共端点。这里的一切就是最基础的 Ruby、Rails 还有 Docker,Docker 还被包含在 mrsk 当中。


现在我们的部署时间从几分钟缩短到了大约一分钟,有时候还更短

好事成双 : 迁移 Writeboard

继续选取下一个复杂度较低的迁移对象,Writeboard。


因为之前有了 Tadalist 的迁移经验,所以 Writeboard 的整个迁移过程只用了不到两周时间。


考虑到 Writeboard 涉及计划作业的概念,所以我们的安全/基础设施/性能团队随后也加入进来,从应用方面进行扩展验证和最终变更。但除此之外,Writeboard 对于应用和基础设施的要求跟 Tadalist 基本一致。


由于 Writeboard 涉及付费客户而且数据库更大,所以 SLO 服务等级目标也有所变化。这里,我们为数据库预迁移选择了简单的三重复制过程。


+-----------------+            +-----------------+            +-----------------+|       RDS       |            |     wb-db-01    |            |     wb-db-02    ||                 | replicates |                 | replicates |                 ||      (r/w)      +----------->|      (r/o)      +----------->|      (r/o)      |+-----------------+            +-----------------+            +-----------------+
复制代码


在部署到位后,实际迁移步骤为:


  • 启用维护窗口

  • 将 RDS 设置为只读

  • 将本地数据库设置为可写入

  • 停止从 RDS 复制

  • 切换 DNSWriteboard 的整个切换过程耗时 16 分钟,没有停机也没有其他故障。


再次成功,那让我们继续前进!

乘胜追击:迁移 Backpack

Backpack 同样可以借助之前积累的迁移经验,只有一点需要注意:它运行着一个由 postfix 实现的有状态邮件管线,所以需要在磁盘上处理这些邮件。


在 EKS 上,我们可以在单独的 EC2 节点上运行 postfix,再通过由 EFS 支持的共享 PVC 将其挂载至作业 pod 当中。所以在 mrsk 部署架构中,我们也决定采用类似的方案,在邮件接收主机和作业容器之间使用共享 NFS。


这里就要聊聊 mrsk 的下一项重要功能了—— 将文件系统挂载至容器。这对 Docker 并不是什么大事,问题是如何 mrsk 中实现并做全面测试。


Backpack 运行的计划作业比 Writeboard 还要多,所以我们必须找到确保各作业正常运行、不会在给定的应用要求中相互干扰的办法。之前的解决方案,是将它们运行在同一 pod/主机之上,并共享一个临时的文件锁。但我们需要同时规划多个节点,这种方式起不了作用、也不够灵活。所以,我们在重写的逻辑中指定 Redis 作为后端,借此将其与作业主机隔离开来。


                                                              ^                                                              |--------------------------------------------------------------+----------  On-Prem                                                     |                                  +----------+            +---v---+                 passes mail      |          |            |       |                  content to +--->|  app-01  |<---------->|  F5   |                     app     |    |          |            |       |                             |    +----------+            +---+---+                             v                                |                      +---------------+                       |    processes jobs    |    jobs-01    |     NFS share         |  Incoming           +--------->|               |<---------+            |   mail           |          +---------------+          |            |           v                                     v            |    +---------------+                   +---------------+     |    |   redis-01    |                   |  mail-in-01   |<----+    +---------------+                   +---------------+
复制代码


运行容器化工作负载的应用和作业虚拟机,均由 mrsk 负责部署。我们还选择在虚拟机上运行非容器化的关键有状态工作负载,例如 MySQL 和 postfix。


Backpack 的迁移工作在 Writeboard 切换完成后立即开始,但为了安全起见并开展扩展验收测试,这次迁移前后花了约三个星期。尽管如此,我们仍然顺利按照之前的迁移步骤完成了零停机切换,现在的我们信心满满!

总结

在各应用之间整理出的共通方案,帮助我们取得了一个又一个胜利:


  • 基础设施复杂度大幅降低,活动部件减少

  • 部署策略的一致性更强

  • 将部署时间大大缩短

  • 清理了大量代码 —— 如下图所示


更重要的是,这次去云/去 K8s 行动还促使我们:


  • 用最新版本的 Chef 重写了整个配置管理系统

  • 结合实际需求建立起超快的虚拟机配置流程

  • 将 MySQL 从 5.7 升级至 8 我们还重新审视了自己的应用程序。它们真的需要跟云和 Kubernetes 牢牢绑定吗?难道没有更好、更简单、成本更低的替代选项吗?


事实证明是有的,而且我们在达成目标的同时,也仍然保持着多年来一直追寻的容器化优势。

未来展望

在放弃本地实现 K8s 的尝试之后,我们踏上了一段漫长的旅程,以自己的方式解决问题、满足需求、验证假设。


学习曲线确实陡峭,也调动了 37signals 的全体运营人力。但无论如何,我们成功了,找到了一条适合自己的迁移道路。


而且**从目前来看,复杂性与依赖性的降低已经转化为实实在在的收益,体现在我们的云账单数字上。**现在,我们正在积极筹备新一轮迁移计划。


我们要“带回家”的不只是应用程序, 还有搜索后端、监控等元素。


这场去云、去 K8s 的探索远未结束,请大家与我们共同见证!

“去 K8s”的大讨论

37signals 的博客文章发布后,在Hacker News上引起了激烈讨论,“去 K8s” 成为热议焦点,这里摘取了部分网友的观点:


因为觉得自托管 K8s 太贵、太复杂,所以 37signals 决定自主构建编排工具。对此,一位网友“crabbone ”评论道:


“先强调一点,千万别把 Kubernetes 理解成那种可以解决所有基础设施问题的交钥匙方案。其实 K8s 里一切有价值的东西都不是现成的,需要稍后添加并自主管理。容器运行时、存储部署信息的数据库、网络设置和管理、存储设置和管理等等,这一切都不是 Kubernetes 的本体。在实际使用 Kubernetes 之后,我们肯定会用其他东西替换掉它所默认的几乎每个组件。CoreDNS?支持不了大系统。存储分卷?谁会在本地文件系统中用分卷啊......一般都会用 Ceph 之类的方案代替。至于权限管理……目前可选的只有 Kyverno,而且效果实在不怎么样。所以 K8s 在实际部署中终将沦为一大堆不同组件的集合体,让人看了就头皮发麻。 但我们又无法摆脱,因为所需要的功能也被缠杂在这团乱麻之上”。


K8s 的复杂性得到了很多人的认同。一位网友对 37signals 遇到的问题表示深有同感。“我大规模使用过 K8s,确实不简单。云服务商也无法提供开箱即用的选项。要在 AWS 中构建一套可持续且‘生产就绪’的 K8s 版本,就不可避免地做大量的调整、插件扩展和测试工作。我也觉得 K8s 还当不起‘部署/编排技术的顶峰’这个名号。我期待后续情况能有所好转,毕竟我被它折磨得不行,相信很多朋友都跟我有同感。总之,K8s 就是一款工具,有时候还很难用”。


但也有人认为,问题的核心不在于 K8s 有多难。 K8s 的学习曲线确实很陡,但其他很多技术也差不多。 问题是要想理解 K8s 的全部复杂性、维护方式和服务运营方法,很多企业根本就拿不出这样的时间和精力。 K8s 提供的好处对于大多数尚处于生命周期之初的项目也没什么意义,甚至永远用不上。从财务角度看,这代表着风险高、回报却很低。 所以如果能找到一种既能降低投资和维护成本,又适合短期与长期需求的解决方案,那么对于大多数业务体量不是特别大的公司来说,这应该才是最理想的答案。


“我也觉得 K8s 的操作没有那么难。概念其实都摆在那里,它本质上只是提供一套框架。而且很多 K8s 运营商还提供托管选项,真的没必要一棒子打死”,网友“llama052”说道。


对于成本和回报的问题,一位网友从他的个人经历出发谈道,“从成本和管理角度来看,公有云的特性决定其适合承载的负载规模是有最佳值的。一旦超过这个值,IAM 管理就会变得非常痛苦。而且往往也在同一时间,公有云的成本也开始爆发和失控,迫使我们处理这些问题。所以这应该就是开展健全性检查的最佳时机。其实 K8s 有的问题,其他方案也都有。最重要的是保证技术栈的维护投入物有所值,合理的回报率才是判断标准”。


“恕我直言,一切问题的根源在于生态”,另一位网友认为,“无论是 K8s 还是 k3s,它们在实验环境下倒是表现不错。可一旦涉及云生产环境,那就必须得考虑负载均衡和入口控制器、存储、网络、IAM 和角色、安全组、集中日志记录、注册表管理、漏洞扫描、CI/CD 和 GitOps 等等。这样搞下来,K8s 根本就没有复杂性优势,而且没有任何一家云服务商真能降低 K8s 的使用门槛。再有,如果某些关键部分出了故障,那惹出的就是大麻烦,调试起来也很困难,我们相当于是在一碗汤面里找出特定一根面条(日志记录)”。


不过,也有很多网友发表了更为中立的意见:


一位名叫“0x500x79”的网友表示,“我承认,你们说的有道理:EKS 的初始部署需要配合大量插件,K8s 一出故障就是大问题,本地磁盘对某些工作负载类型的支持一塌糊涂等等…… 但你说它是不是好工具、值不值得用,我觉得仍然是值得的。只要度过了前面的艰难适应期,后面还是有不少优点。当然,我也觉得它绝对不是基础设施/容器编排的终极解决方案。多朋友似乎认为 K8s 是那种刚开始简单,后面越用越复杂的工具。我觉得并不是这样。另外,我们公司拥有自己的一套基础设施部署栈,效果比 K8s 好上千倍。我觉得这才是 K8s 接下来的发展方向,把自己这些‘棱角’都藏在引擎盖下面。目前 K8s 的抽象水平有问题,有点太过贪多了”。


“K8s 一直在被误解”,jpgvm 表示,“人们总是关注它的复杂性、过度工程之类的问题,但这些东西在宏观层面上并不重要。K8s 的核心,在于以一致的 API 和部署目标对职责做正确划分。至于这种方式能带来多大的价值,那要由你实际运行的东西、有多少相关方的参与来决定。如果运行的东西不多、参与其中的相关方也不多,那 K8s 就没什么价值。但如果这两项中任何一项很多,那价值就非常大。总之,K8s 的意义在于组织价值,其他技术优点都是次要的”


如果你对“去 K8s”有什么看法,欢迎在评论区留言~


参考链接:


https://dev.37signals.com/bringing-our-apps-back-home/


https://news.ycombinator.com/item?id=35263285

2023-03-31 12:5310851
用户头像
刘燕 InfoQ高级技术编辑

发布了 1112 篇内容, 共 535.9 次阅读, 收获喜欢 1977 次。

关注

评论 2 条评论

发布
用户头像
瞎折腾吧
我一开始看到这个题目,怀疑java方案有个巨大的jre资源利用率很低 不适合放在容器和k8s里面
我们用python加上golang使用k8s还是很舒服的


2023-04-10 09:16 · 美国
回复
用户头像
文章提到的mrsk是什么。
2023-03-31 17:08 · 山东
回复
没有更多了
发现更多内容

浅述微服务安全管理机制

阿泽🧸

8月月更 微服务安全

开源知识库与SaaS解决方案的区别

Geek_da0866

中国开源先锋人物志 | ChinaOSC

CCF开源发展委员会

【云原生】SpringCloud-Spring Boot Starter使用测试

java李杨勇

Java web SpringCloud Alibaba 签约计划第三季

头脑风暴:零钱兑换3

HelloWorld杰少

算法 LeetCode 数据结构, 8月月更

什么是知识管理系统?如何改善客户体验?

Geek_da0866

RocketMQ高性能设计之动态缩扩容和消息实时投递

急需上岸的小谢

8月月更

C++继承中的对象模型与继承中构造和析构顺序

CtrlX

c c++ 面向对象 继承 8月月更

ICMPv6协议详解

穿过生命散发芬芳

8月月更 ICMPv6

StoneDB 读、写操作的执行过程

StoneDB

MySQL 数据库 大数据 #开源 8月月更

备受资本市场关注的Zebec,正在构建“新DeFi”生态

小哈区块

转转价格系统DDD实践

转转技术团队

领域驱动设计 DDD

超云

Kent Yao

超云 Supercloud

中台订单分库分表测试总结

转转技术团队

测试方案 后端测试

开源一夏 |分布式事务

六月的雨在InfoQ

开源 分布式事务 2PC 3PC 8月月更

多次拿下移动集团技术发明大奖,TA是怎么做到的?

鲸品堂

AI 资源

开源生态研究与实践| ChinaOSC

CCF开源发展委员会

李斯特:不同流派的产品创新方法及创新误区丨创新工作坊

声网

灵感宝盒 RTE NG-Lab

系统入门-Linux系统基础命令

Albert Edison

Linux centos 指令 8月月更

Hi,我是ChunJun,一个有趣好用的开源项目

袋鼠云数栈

【云原生】SpringCloud是什么?

java李杨勇

Java spring-boot 签约计划第三季

【云原生】微服务SpringCloud-eureka(server)集群搭建

java李杨勇

Eureka spring-cloud 签约计划第三季

开源中国专访 TJ:开源许可证,欢迎来到云时代

tapdata

Tapdata 开源社区

Redis 潜在问题

武师叔

8月月更

架构实战营毕业总结

Geek_53787a

备受资本市场关注的Zebec,正在构建“新DeFi”生态

西柚子

每日一R「07」类型系统(一)

Samson

8月月更 ​Rust

Flutter 动画组件那么多,记不住不会用怎么办?我都给你整理好了,收藏吧!

岛上码农

flutter 前端 安卓 移动端开发 8月月更

基于JavaSpringMVC+vue实现协同过滤电影推荐系统详细设计

java李杨勇

Java spring Vue 3 签约计划第三季 协同过滤

leetcode 239. Sliding Window Maximum 滑动窗口最大值

okokabcd

LeetCode 数据结构与算法 双端队列

“新DeFi”生态的构建,流支付协议Zebec或厚积薄发

BlockChain先知

上云价值倒挂严重,1年云支出高达320万美元,这家公司彻底去云、放弃K8s_AI&大模型_刘燕_InfoQ精选文章