标签 go语言 下的文章

步入 2026 年,中国企业数字化转型已从工具的浅层应用进入到核心能力的自主重构阶段。即时通讯系统作为承载组织内部信息流转、指挥调度与核心数据资产流转的数字血液,其地位已从通用办公工具跃升为战略级的协作基座。

对于政企单位、金融机构、能源巨头及军工科研院所而言,即时通讯软件的选型已不再仅仅是功能列表的勾选,而是一场关于数据主权、架构韧性、信创合规与全生命周期成本的全方位博弈。在日益复杂的网络安全环境与信创国产化全面替代的大背景下,企业需要的不仅是一个聊天工具,而是一座能够捍卫通信主权的数字堡垒。

本文将为您深度盘点 2026 年度最具代表性的企业即时通讯应用软件,并发布详尽的选型指南,为您揭示数字化时代的协同真相。

第一章:2026 年度企业级即时通讯平台综合排行榜

根据部署模式、安全等级、集成深度与长期持有成本,我们将 2026 年市场上的主流平台进行综合排名。

第一名:喧喧 —— 私有化协同的性能冠军

在 2026 年的综合评测中,喧喧凭借其纯粹的私有化基因、极轻量的架构以及颠覆性的财务模型荣登榜首。作为禅道软件旗下的核心协作平台,喧喧专为追求极致安全与效能的大型组织打造。

  1. 核心定位:全私有化部署的安全协作底座。
  2. 核心优势:全栈信创适配、消息即业务的 ChatOps 闭环、极低资源占用的高性能引擎。
  3. 财务价值:买断制授权模式,终结了随人数无限膨胀的订阅费用。

第二名:钉钉专属版 —— 行政管控的数字化巨头

钉钉依托阿里巴巴的强大生态,依然是公有云与混合云市场的重要力量。其专属版针对中大型企业提供了一定程度的私有化定制能力。

  1. 核心定位:一站式行政管理与组织协同门户。
  2. 核心优势:功能极其庞杂,内置完善的审批、考勤与智能人事模块。
  3. 局限分析:由于功能模块过多,应用软件体积日益膨胀,对低配置的信创终端负担较重。

第三名:飞书 —— 知识密集型团队的协作先锋

字节跳动推出的飞书,以极致的文档协同体验在知识型团队中广受欢迎。

  1. 核心定位:内容驱动的透明协作套件。
  2. 核心优势:文档即沟通的顺滑体验,以及先进的多维表格数据处理能力。
  3. 局限分析:其公有云 SaaS 基因深厚,在完全断网或涉密环境下的私有化适配灵活性不及原生私有化软件。

第四名:360 智语 —— 安全连接的数字门户

依托 360 集团的安全基因,该平台专注于为政企单位提供高安全的协同环境。

  1. 核心定位:主打防泄密与门户聚合的安全通讯。
  2. 核心优势:极其精细的消息审计、密级管控与防内鬼功能。
  3. 局限分析:架构设计相对较重,主要面向超大型组织的门户化合规需求,在敏捷研发协同方面略逊于喧喧。

第五名:蓝信 —— 专注政企的国家队平台

蓝信在党政军及大型央企中有着深厚的积淀,具备极高的信创政治红利。

  1. 核心定位:政企安全移动办公平台。
  2. 核心优势:深度适配 PKS 体系,符合等保三级及以上的严苛审计要求。
  3. 局限分析:部署与维护成本较高,通常以大型项目制形式交付,中小团队进入门槛较高。

第二章:为什么 2026 年的选型红线是“物理隔离”?

在过去,便捷性是选型的首要指标;现在,数据主权高于一切。

  1. 拒绝数据托管的“黑箱”焦虑

依赖公有云 SaaS 的应用软件,数据实际上存储在厂商的中心化机房。即便厂商宣称采用了加密技术,但在物理层面,单位失去了对核心秘密的绝对掌控权。一旦遭遇外部网络制裁或云端泄密,后果不可估量。

  1. 物理隔离的必然选择

真正安全的企业即时通讯应当支持 100% 的物理隔离。喧喧支持将全套服务端、消息中转层以及存储节点部署在单位内部自有的物理机房内。数据流转的边界就是物理网线的边界,从物理层面杜绝了云端被爬取或境外泄密的可能。

第三章:硬核技术拆解 —— 喧喧如何重塑性能标准

很多单位在选型时担心私有化部署会带来沉重的运维压力,喧喧通过架构创新解决了这一难题。

  1. 高性能 Go 语言驱动的消息心脏

喧喧的核心消息中转服务器采用高性能的 Go 语言开发。

  • 极致并发处理:Go 语言天生具备的高并发协程机制,使得喧喧能以极低的资源占用支撑起万人级的长连接。
  • 算力红利:实测表明,在支撑全集团高频交互时,喧喧对 CPU 与内存的占用仅为同类重型架构产品的四分之一。

  • 点对点大文件秒传技术

在研发、设计及制造行业,经常涉及数 GB 级的设计图纸或安装包流转。

  • 技术优势:喧喧在局域网内引入了 P2P 传输技术。文件直接在两台终端间对传,不经过中转服务器。
  • 效率表现:传输速度直接拉满至网卡物理上限,实现了内网秒传且不占出口带宽,解决了公有云传输被限速的顽疾。

第四章:信创国产化全栈适配指南

在信创 2.0 时代,应用软件必须具备原生国产芯魂,而非简单的套壳运行。

  1. 六大国产 CPU 的原生级适配 喧喧完成了对龙芯、飞腾、鲲鹏、海光、申威及兆芯的全方位适配。
  • 原生性能:在龙芯 LoongArch 架构下,喧喧实现了原生级优化,启动速度与文字输入流畅度达到行业标杆水平。

  1. 国产操作系统的灵魂融合 系统深度兼容统信 UOS、银河麒麟、中科方德等国产操作系统。
  • 系统级调用:喧喧深度调用国产操作系统底层的通知系统、托盘管理器及安全沙箱,确保了在信创替代工程中的平滑过渡。
  1. 国产数据库的深度衔接 喧喧支持对接达梦、人大金仓等国产关系型数据库,实现了从芯片到存储的全链路国产化闭环,完全符合国家等保合规要求。

第五章:业务协同新境界 —— 从聊天工具到 ChatOps 引擎

如果协作软件只能聊天,那它只是成本中心;如果它能驱动业务,那它就是利润中心。

  1. 消息即业务的实战闭环

喧喧与禅道项目管理系统的原生集成是其核心杀手锏。

  • 任务转化:在对话框内发现的 Bug 或需求,用户通过鼠标右键点击消息,即可一键转化为正式的任务。
  • 实时反馈:任务的状态变更会自动以结构化卡片的形式推送回群组。这种沟通流与业务流的高度合一,消灭了信息传递的孤岛。

  • 统一办公门户的聚合能力

喧喧提供了丰富的网络钩子和开放接口。企业可以像搭积木一样,将内部的国产 OA 流程、ERP 报表、运维报警接入。

  • 指尖协同:业务通知不再是枯燥的文字,而是带操作按钮的动态卡片。员工在窗口内即可完成审批,真正实现了指尖上的敏捷行政。

第六章:安全性加固 —— 筑牢纵深防御防线

安全不应只有一把锁,而应是一套闭环。

  1. 传输与存储的全链路加密

无论是在局域网还是分布式环境下,喧喧默认强制开启 WSS 与 HTTPS 高级加密协议。同时,服务端数据库和文件柜支持物理层加密。即便服务器硬盘遗失,非法获取者在没有密钥的情况下,面对的也仅是一堆无法破解的乱码。

  • 严苛的访问控制与 IP 围栏

管理员可以设定精细的 IP 登录策略,限定账号仅能在受控的办公区域网段登录。配合设备强绑定技术,有效防止了由账号密码泄露导致的非授权异地登录。

  • 全界面动态水印溯源

针对拍摄截屏、拍照泄密这一痛点,喧喧支持全局开启动态水印功能。水印会根据当前登录人员的姓名、工号、即时时间及登录 IP 实时生成。这种视觉威慑极大提高了泄密的心理成本,并为事后审计提供了证据链。

第七章:财务模型分析 —— 算清全生命周期的 TCO 账

软件选型也是一笔精细的财务账。企业应当关注 3 到 5 年的总拥有成本。

  1. 终结复利增长的人头税

主流公有云软件按人头每年收费。随着单位人数从 100 人扩张到 1000 人,每年的订阅费支出将呈指数级爆炸。

  • 喧喧模式:支持买断制授权。单位一次投入,永久拥有数字主权。这种资产化的 IT 投入,在长周期内显著降低了企业的数字化门槛。
  • 极简运维节省的人力开销

得益于轻量级的架构,喧喧提供了 Windows 与 Linux 下的一键安装包。

  • 零运维感:无需复杂的依赖环境配置,即使在资源受限的国产服务器上,喧喧依然能保持极高的稳定性,极大地节省了 IT 部门的维护精力。

实战案例解析:多元化场景下的选型回响

案例一:某大型高科技制造企业 —— 守护核心配方

客户背景:该企业拥有数千名核心研发人员,工艺配方是企业的最高机密。 选型考量:绝对不能接受数据上公有云。 喧喧方案:通过部署定制化私有化协作平台,实现了物理隔离环境下的顺畅通讯。配合 P2P 秒传技术,解决了数 GB 级设计图纸的极速分发。 实战收益:核心资产泄密风险降低了百分之九十九,且内网文件流转效率提升了三倍。

案例二:某市级政务大厅 —— 信创替代领跑者

客户背景:响应国家信创号召,全员更换国产终端。 选型考量:软件必须在麒麟系统上跑得稳。 喧喧方案:部署原生适配的客户端,实现毫秒级启动。利用接口将公文流转转化为卡片消息。 实战收益:领导出差途中通过国产移动终端一键审批,公文处理周期从两天缩短至四小时。

案例三:某金融证券公司 —— 极端高并发下的指令必达

客户背景:在市场行情剧烈波动的交易时段,通讯压力巨大。 选型考量:系统必须具备极致的稳定性。 喧喧方案:利用高性能 Go 语言消息引擎,构建高可用集群。 实战收益:平稳支撑了万人在线的消息吞吐,确保了交易讨论与风控指令的秒级触达。

第八章:选型决策建议总结

面对 2026 年的复杂环境,选型决策建议遵循以下优先级:

  1. 如果您视数据安全为生命线,拒绝数据托管: 请务必选择以喧喧为代表的原生私有化系统。
  2. 如果您处于信创替代的关键期,需要适配国产环境: 优先选择已获得统信、麒麟官方互认证明的全栈信创应用软件。
  3. 如果您追求研发流程的闭环,希望消息驱动业务: 喧喧与禅道的双核联动是目前市场上公认的最优实践。
  4. 如果您希望算清长账,降低总拥有成本: 买断制或开源模式将比订阅制为您节省数以十万计的年费支出。

结语:掌握通信主权,拥抱数字化未来

回顾企业级即时通讯系统的演进历程,我们不难发现:最好的安全永远来自于对底层的完全掌控。

公有云平台赋予了暂时的便捷,但也拿走了单位修改门锁的权利。而通过喧喧打造的企业级私有协作平台,则赋予了组织一整块可以自由耕耘、高度安全的数字化领地。

喧喧用硬核技术证明了:

  • 物理上的绝对隔离,可以通过私有化部署完美实现;
  • 性能上的极致需求,可以通过高性能引擎高效满足;
  • 业务上的孤岛难题,可以通过基因级集成彻底打破;
  • 环境上的适配挑战,可以通过全栈原生适配平滑消除。

在数字化浪潮的下一个十年,掌握通信主权就是掌握了组织的发展权。喧喧将持续深耕私有化协作领域,用极致的技术打磨每一个接口,为每一家视安全、合规与效率为生命的单位,构建起一道坚不可摧、智慧互联的数字屏障。

选择喧喧,让每一次沟通都安全落地,让每一个指令都精准必达。

刚出的榜单,Go掉得挺多

今年1月的TIOBE编程语言排行榜出来了。有个事儿挺显眼的,Go语言这次排到了第16名。

要知道,2024年11月它还在第7名呢,这才过了多久,直接掉了9名。

很多写Go的朋友看到这个可能心里会犯嘀咕:这语言是不是不行了?以后还能不能用它找工作了?

咱们先别急着下结论。

在讨论这个问题之前,咱们得先搞清楚这个榜单到底是怎么回事,这次排名下降是不是真的代表Go语言出了大问题。

TIOBE指数到底是啥?

TIOBE这个榜单,它统计的数据来源其实是各大搜索引擎。

简单说,就是看有多少人在百度、谷歌、必应这些地方搜这门语言的名字。

它反映的是一种“搜索热度”。这里面有个逻辑大家要明白:一门语言搜的人多,不代表用的人就多;

反过来,搜的人少,也不代表用的人就少。

通常什么样的人会去搜?新手刚开始学的时候,或者遇到报错搞不定的时候,搜得最多。

如果一门语言大家都会用了,或者它运行很稳定、没啥新花样,大家反倒不去搜了。

所以,TIOBE的排名主要代表的是大家对这门语言的“好奇心”和“陌生感”,而不是它在实际项目里的使用率。

为啥这次Go掉到了第16?

那Go语言这次为啥排名掉得这么明显?我觉得有这么几个实实在在的原因。

Rust语言现在确实很受欢迎

在这次榜单上,Rust排到了第13名。Rust在安全性、系统底层开发这些方面确实有优势,吸引了很多开发者的注意力。

本来有些可能打算学Go的人,现在可能转头去研究Rust了。大家的关注点分散了,搜Go的人自然就少了一些。

还有就是Go语言现在太“稳”了

它现在的版本兼容性做得很好,依赖管理也成熟了。以前大家可能会经常搜“Go怎么配置环境”、“Go这个库怎么用”,现在这些问题都解决了,不需要老去搜。

而且,Go语言现在主要用在服务器后台、云计算这些地方。大家用Docker、用Kubernetes,底层其实都是Go写的,但大家平时操作的是命令行,不需要直接去写Go代码,也就不会去搜它。

实际情况到底怎么样?

排名虽然掉了,但咱们看看实际工作中的情况。

现在的互联网公司,特别是做后端开发的,用Go的还是非常多。像很多大厂的核心系统,依然是用Go在写。

在云原生这个领域,Go的位置目前还是很稳固的,没什么语言能轻易替代它。

Go语言有个很大的优点,就是简单、直接。代码写起来快,跑起来性能也不错,维护起来也方便。

对于公司来说,这能省成本,能提高效率。只要这个优势还在,公司就不会轻易把它换掉。

总结

所以看到排名下降,不用太担心。这个榜单反映的是当下的关注度和话题度,不是实际的市场占有率。

Go语言现在进入了一个平稳发展的阶段,不像刚出来时那么有新鲜感,但它在实际工作中还是非常有用的。

大家该学还是学,该用还是用。选编程语言,看的是能不能解决实际问题,能不能帮你把活干好,而不是看它在榜单上排第几。

只要它还能帮你高效地开发系统,它就是有价值的。

⚡️ 别把时间浪费在低效复习上

很多人复习抓不住重点。作为过来人,我分析了100+份大厂面试记录,将 Go/Java/AI 的核心考察点、高频题、易错点 浓缩进了一份 PDF。

不搞虚的,全是干货。

加我微信:wangzhongyang1993,备注 【面经】 免费发你,立即纠正你的复习方向,把时间用在刀刃上。

今天跟大家分享一个etcd的内存大量占用的问题,这是前段时间在我们开源软件Easegress中遇到的问题,问题是比较简单的,但是我还想把前因后果说一下,包括,为什么要用etcd,使用etcd的用户场景,包括etcd的一些导致内存占用比较大的设计,以及最后一些建议。希望这篇文章不仅仅只是让你看到了一个简单的内存问题,还能让你有更多的收获。当然,也欢迎您关注我们的开源软件,给我们一些鼓励。

为什么要用ETCD

先说一下为什么要用etcd。先从一个我们自己做的一个API网关 – Easegress(源码)说起。

Easegress 是我们开发并开源的一个API应用网关产品,这个API应用网关不仅仅只是像nginx那样用来做一个反向代理,这个网关可以做的事很多,比如:API编排、服务发现、弹力设计(熔断、限流、重试等)、认证鉴权(JWT,OAuth2,HMAC等)、同样支持各种Cloud Native的架构如:微服务架构,Service Mesh,Serverless/FaaS的集成,并可以用于扛高并发、灰度发布、全链路压力测试、物联网……等更为高级的企业级的解决方案。所以,为了达到这些目标,在2017年的时候,我们觉得在现有的网关如Nginx上是无法演进出来这样的软件的,必需重新写一个(后来其他人也应该跟我们的想法一样,所以,Lyft写了一个Envoy。只不过,Envoy是用C++写的,而我用了技术门槛更低的Go语言)

另外,Easegress最核心的设计主要有三个:

  • 一是无第三方依赖的自己选主组集群的能力
  • 二是像Linux管道命令行那样pipeline式的插件流式处理(支持Go/WebAssembly)
  • 三是内置一个Data Store用于集群控制和数据共享。

对于任何一个分布式系统,都需要有一个强一制性的基于Paxos/Raft的可以自动选主机制,并且需要在整个集群间同步一些关键的控制/配置和相关的共享数据,以保证整个集群的行为是统一一致的。如果没有这么一个东西的话,就没有办法玩分布式系统的。这就是为什么会有像Zookeeper/etcd这样的组件出现并流行的原因。注意,Zookeeper他们主要不是给你存数据的,而是给你组集群的。

Zookeeper是一个很流行的开源软件,也被用于各大公司的生产线,包括一些开源软件,比如:Kafka。但是,这会让其它软件有一个依赖,并且在运维上带来很大的复杂度。所以,Kafka在最新的版本也通过内置了选主的算法,而抛弃了外挂zookeeper的设计。Etcd是Go语言社区这边的主力,也是kubernetes组建集群的关键组件。Easegress在一开始(5年前)使用了gossip协议同步状态(当时想的过于超前,想做广域网的集群),但是后发现这个协议太过于复杂,而且很难调试,而广域网的API Gateway也没遇到相应的场景。所以,在3年前的时候,为了稳定性的考量,我们把其换成了内嵌版本的etcd,这个设计一直沿用到今天。

Easegress会把所有的配置信息都放到etcd里,还包括一些统计监控数据,以及一些用户的自定义数据(这样用户自己的plugin不但可以在一条pipeline内,还可以在整个集群内共享数据),这对于用户进行扩展来说是非常方便的。软件代码的扩展性一直是我们追求的首要目标,尤其是开源软件更要想方设法降低技术门槛让技术易扩展,这就是为什么Google的很多开源软件都会选使用Go语言的原因,也是为什么Go正在取代C/C++的做PaaS基础组件的原因。

背景问题

好了,在介绍完为什么要用etcd以后,我开始分享一个实际的问题了。我们有个用户在使用 Easegress 的时候,在Easegress内配置了上千条pipeline,导致 Easegress的内存飙升的非常厉害- 10+GB 以上,而且长时间还下不来。

用户报告的问题是——

在Easegress 1.4.1 上创建一个HTTP对象,1000个Pipeline,在Easegres初始化启动完成时的内存占用大概为400M,运行80分钟后2GB,运行200分钟后达到了4GB,这期间什么也没有干,对Easegress没有进行过一次请求。

一般来说,就算是API再多也不应该配置这么多的处理管道pipeline的,通常我们会使用HTTP API的前缀把一组属于一个类别的API配置在一个管道内是比较合理的,就像nginx下的location的配置,一般来说不会太多的。但是,在用户的这个场景下配置了上千个pipeline,我们也是头一次见,应该是用户想做更细粒度的控制。

经过调查后,我们发现内存使用基本全部来自etcd,我们实在没有想到,因为我们往etcd里放的数据也没有多少个key,感觉不会超过10M,但不知道为什么会占用了10GB的内存。这种时候,一般会怀疑etcd有内存泄漏,上etcd上的github上搜了一下,发现etcd在3.2和3.3的版本上都有内存泄露的问题,但都修改了,而 Easegress 使用的是3.5的最新版本,另外,一般来说内存泄漏的问题不会是这么大的,我们开始怀疑是我们哪里误用了etcd。要知道是否误用了etcd,那么只有一条路了,沉下心来,把etcd的设计好好地看一遍。

大概花了两天左右的时间看了一下etcd的设计,我发现了etcd有下面这些消耗内存的设计,老实说,还是非常昂贵的,这里分享出来,避免后面的同学再次掉坑。

首当其冲是——RaftLog。etcd用Raft Log,主要是用于帮助follower同步数据,这个log的底层实现不是文件,而是内存。所以,而且还至少要保留 5000 条最新的请求。如果key的size很大,这 5000条就会产生大量的内存开销。比如,不断更新一个 1M的key,哪怕是同一个key,这 5000 条Log就是 5000MB = 5GB 的内存开销。这个问题在etcd的issue列表中也有人提到过  issue #12548 ,不过,这个问题不了了之了。这个5000还是一个hardcode,无法改。(参看 DefaultSnapshotCatchUpEntries 相关源码

// DefaultSnapshotCatchUpEntries is the number of entries for a slow follower
// to catch-up after compacting the raft storage entries.
// We expect the follower has a millisecond level latency with the leader.
// The max throughput is around 10K. Keep a 5K entries is enough for helping
// follower to catch up.
DefaultSnapshotCatchUpEntries uint64 = 5000

另外,我们还发现,这个设计在历史上etcd的官方团队把这个默认值从10000降到了5000,我们估计etcd官方团队也意识到10000有点太耗内存了,所以,降了一半,但是又怕follwer同步不上,所以,保留了 5000条……(在这里,我个人感觉还有更好的方法,至少不用全放在内存里吧……)

另外还有下面几项也会导致etcd的内存会增加

  1. 索引。etcd的每一对 key-value 都会在内存中有一个 B-tree 索引。这个索引的开销跟key的长度有关,etcd还会保存版本。所以B-tree的内存跟key的长度以及历史版本号数量也有关系。
  2. mmap。还有,etcd 使用 mmap 这样上古的unix技术做文件映射,会把他的blotdb的内存map到虚拟内存中,所以,db-size越大,内存越大。
  3. Watcher。watch也会占用很大的内存,如果watch很多,连接数多,都会堆积内存。

(很明显,etcd这么做就是为了一个高性能的考虑)

Easegress中的问题更多的应该是Raft Log 的问题。后面三种问题我们觉得不会是用户这个问题的原因,对于索引和mmap,使用 etcd 的 compact 和 defreg (压缩和碎片整理应该可以降低内存,但用户那边不应该是这个问题的核心原因)。

针对用户的问题,大约有1000多条pipeline,因为Easegress会对每一条pipeline进行数据统计(如:M1, M5, M15, P99, P90, P50等这样的统计数据),统计信息可能会有1KB-2KB左右,但Easegress会把这1000条pipeline的统计数据合并起来写到一个key中,这1000多条的统计数据合并后会导致出现一个平均尺寸为2MB的key,而5000个in-memory的RaftLog导致etcd要消耗了10GB的内存。之前没有这么多的pipeline的场景,所以,这个内存问题没有暴露出来。

于是,我们最终的解决方案也很简单,我们修改我们的策略,不再写这么大的Value的数据了,虽然以前只写在一个key上,但是Key的值太大,现在把这个大Key值拆分成多个小的key来写,这样,实际保存的数据没有发生变化,但是RaftLog的每条数据量就小了,所以,以前是5000条 2M(10GB),现在是5000条 1K(500MB),就这样解决了这个问题。相关的PR在这里 PR#542

总结

要用好 etcd,有如下的实践

  • 避免大尺寸的key和value,一方面会通过一个内存级的 Raft Log 占大量内存,另一方面,B-tree的多版本索引也会因为这样耗内存。
  • 避免DB的尺寸太大,并通过 compact和defreg来压缩和碎片整理降低内存。
  • 避免大量的Watch Client 和 Watch数。这个开销也是比较大的。
  • 最后还有一个,就是尽可能使用新的版本,无论是go语言还是etcd,这样会少很多内存问题。比如:golang的这个跟LInux内核心相关的内存问题 —— golang 1.12的版sget的是 MADV_FREE 的内存回收机制,而在1.16的时候,改成了 MADV_DONTNEED ,这两者的差别是,FREE表示,虽然进程标记内存不要了,但是操作系统会保留之,直到需要更多的内存,而 DONTNEED 则是立马回收,你可以看到,在常驻内存RSS 上,前者虽然在golang的进程上回收了内存,但是RSS值不变,而后者会看到RSS直立马变化。Linux下对 MADV_FREE 的实现在某些情况下有一定的问题,所以,在go 1.16的时候,默认值改成了 MADV_DONTNEED 。而 etcd 3.4 是用 来1.12 编译的。

最后,欢迎大家关注我们的开源软件! https://github.com/megaease/ 

下面代码中 f()会被重复执行吗?

package main

import (
	"fmt"
	"sync"
)

type Once struct {
	m    sync.Mutex
	done uint32
}

func (o *Once) Do(f func()) {
	if o.done == 1 {
		return
	}

	o.m.Lock()
	defer o.m.Unlock()

	fmt.Println("bing: ", o.done)

	if o.done == 0 {
		o.done = 1
		f()
	}
}

func main() {
	var once Once
	wg := sync.WaitGroup{}

	wg.Add(100)
	for i := 0; i < 100; i++ {
		go func() {
			defer wg.Done()
			once.Do(func() {
				println("executed---------》 ")
			})
		}()
	}
	wg.Wait()
}


一直使用 vs code 开发 go,主要搞 web ,最近体验了一下 sublime-text ,发现这个曾经流行的开发工具对 GO 的支持很一般,插件还是很多年前的,是不是我不会配置,有没大神使用 ST 开发?

大家好我是地鼠哥。

如果你也是从 fmt.Println("Hello, World!")if err != nil 开始Go语言生涯的,那说明你已经是个成熟的Go开发者了。在日常的业务开发中,我们每天都在写着各种各样的结构体和接口,有时候会觉得Go的语法过于简单,写起来甚至有点繁琐。

但其实,Go语言的设计虽然崇尚简洁,却在细节中隐藏了很多巧思。从经典的Go 1.11到最新的Go 1.26,它一直在稳步进化,引入了很多实用的特性和设计模式。用好它们,不仅能让代码更清晰,还能在同事面前展示你的专业能力。

下面就聊几个在实际工作中非常实用的技巧,看看你是否都在使用。

用自定义类型(Defined Types)提升安全性

在业务代码里,我们经常用 int64string 来表示各种ID,比如 UserID, OrderID, ProductID。直接使用基础类型的一个主要风险是,方法的参数很容易传混。

比如下面这个函数:

// 很容易写错的调用
func ProcessOrder(userID int64, orderID int64) {
    // ...
}

// 调用时可能不小心把两个ID搞反
var uid int64 = 1001
var oid int64 = 9527
ProcessOrder(oid, uid) // 编译器不会报错,但逻辑全错了

为了解决这个问题,我们可以利用Go的自定义类型特性,给ID加一层身份验证。这在编译阶段就能帮我们发现错误。

type UserID int64
type OrderID int64

func ProcessOrder(uid UserID, oid OrderID) {
    fmt.Printf("处理用户 %d 的订单 %d\n", uid, oid)
}

func main() {
    var uid UserID = 1001
    var oid OrderID = 9527

    ProcessOrder(uid, oid) // 正确
    // ProcessOrder(oid, uid) // 编译错误:cannot use oid (variable of type OrderID) as type UserID
}

这个简单的改动,几乎零成本地消除了ID混用的隐患。

用函数选项模式(Functional Options)优化配置

在Java中如果你需要创建一个复杂的对象,可能会用Builder模式。而在Go中,我们经常遇到初始化一个服务或组件时,有几十个配置项,但大部分都用默认值的情况。

如果写一个包含所有参数的 NewServer 函数,调用起来会非常麻烦;如果传入一个配置结构体,又需要定义一个很大的Struct。

这时候,函数选项模式就是最佳选择。

type Server struct {
    Host    string
    Port    int
    Timeout time.Duration
}

type Option func(*Server)

func WithHost(h string) Option {
    return func(s *Server) {
        s.Host = h
    }
}

func WithPort(p int) Option {
    return func(s *Server) {
        s.Port = p
    }
}

func NewServer(opts ...Option) *Server {
    // 默认配置
    server := &Server{
        Host:    "localhost",
        Port:    8080,
        Timeout: 30 * time.Second,
    }
    
    // 应用选项
    for _, opt := range opts {
        opt(server)
    }
    
    return server
}

func main() {
    // 使用默认配置
    s1 := NewServer()
    
    // 只修改端口
    s2 := NewServer(WithPort(9090))
    
    // 修改多个配置,清晰直观
    s3 := NewServer(WithHost("127.0.0.1"), WithPort(8888))
}

这种模式让初始化的代码变得非常灵活,而且未来增加新的配置项时,不需要修改现有的调用代码,兼容性极好。

用反引号(Raw String Literals)优雅处理多行文本

在代码中拼接SQL语句或者JSON字符串时,使用双引号往往需要大量的转义字符 \,写起来麻烦,读起来也费劲。

Go语言原生支持反引号 ` 来定义原生字符串,所见即所得。

func main() {
    // 以前的方式,难以阅读
    jsonStr := "{\n" +
               "  \"name\": \"Alice\",\n" +
               "  \"age\": 30\n" +
               "}"

    // 使用反引号,清晰明了
    jsonNew := `
{
  "name": "Alice",
  "age": 30
}
`
    fmt.Println(jsonNew)
}

这在编写内嵌的SQL、HTML模板或者测试用的JSON数据时非常有用。

用表格驱动测试(Table-Driven Tests)简化测试代码

Go语言标准库非常推崇表格驱动测试。如果你还在写大量的 if-else 或者重复的测试逻辑,是时候改变一下了。

通过定义一个包含输入和期望输出的结构体切片,我们可以用一个循环覆盖所有的测试用例。

func Add(a, b int) int {
    return a + b
}

func TestAdd(t *testing.T) {
    tests := []struct {
        name string
        a    int
        b    int
        want int
    }{
        {"正数相加", 1, 2, 3},
        {"负数相加", -1, -1, -2},
        {"零相加", 0, 0, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Add(tt.a, tt.b); got != tt.want {
                t.Errorf("Add() = %v, want %v", got, tt.want)
            }
        })
    }
}

新增测试用例只需要在列表中加一行数据,逻辑与数据分离,非常易于维护。

用 ErrGroup 并发处理任务

Go的 go 关键字让并发变得很容易,但协调多个并发任务并处理错误却不简单。手动使用 sync.WaitGroupchannel 来收集错误会写出很多样板代码。

errgroup 包(golang.org/x/sync/errgroup)能完美解决这个问题。

import (
    "context"
    "fmt"
    "golang.org/x/sync/errgroup"
)

func main() {
    g, _ := errgroup.WithContext(context.Background())
    
    urls := []string{"http://www.google.com", "http://www.bing.com"}

    for _, url := range urls {
        url := url // 注意闭包捕获问题(Go 1.22之前需要)
        g.Go(func() error {
            // 模拟请求
            fmt.Printf("Fetching %s\n", url)
            return nil // 或者返回错误
        })
    }

    // 等待所有任务完成,如果有任何一个返回错误,这里会返回那个错误
    if err := g.Wait(); err != nil {
        fmt.Println("出错了:", err)
    } else {
        fmt.Println("所有任务完成")
    }
}

它能自动处理 WaitGroup 的计数,并且一旦有一个任务出错,可以取消其他任务(配合 Context),是处理并发任务的有效工具。

管理好Go环境,才能高效开发

看到这里,你可能意识到,Go的版本更新也非常快。从Go 1.11引入Module,到Go 1.18引入泛型,再到Go 1.22修复循环变量问题,每个版本都有重要的变化。在实际工作中,我们经常面临这样的场景:

  • 维护的老项目还在用Go 1.20。
  • 新开发的服务要用Go 1.25。
  • 想体验最新的Go 1.26 RC版本。

在本地同时管理多个Go版本,配置 GOROOT, GOPATH,修改环境变量,是一件非常繁琐的事情。

所以,这时候就需要ServBay。

虽然它常被认为是Web开发工具,但它对Go语言的支持也非常出色。最让我满意的是,它可以一键安装和管理多个Go版本。你可以同时安装Go 1.20、1.23、1.26等多个版本,它们之间完全隔离,互不干扰。

而且,你可以为不同的项目指定使用不同的Go版本。比如,设置项目A使用Go 1.20,项目B使用Go 1.25。这样一来,在切换项目时,根本不用担心版本不兼容的问题,ServBay会自动处理好环境变量。

对于Go开发者来说,这意味着可以把更多精力放在架构设计和代码逻辑上,而不是被环境配置这些琐事消耗时间。

总结

Go语言虽然以简单著称,但写出地道的Go代码(Idiomatic Go)依然需要不断的积累。掌握这些技巧,可以让你的代码更加健壮、优雅。而借助像ServBay这样的工具,又能帮你轻松搞定环境管理,让你专注于创造价值。

你还有什么Go语言的开发技巧吗?欢迎在评论区分享交流。

如果你也对Go语言感兴趣,欢迎关注并私信我领取pdf面经资料,保证完全免费

微软近日分享了TypeScript 7(代号为 Corsa 项目)的最新进展,披露了对 TypeScript 编译器的一次根本性重构。该更新发布于2025年12月,详细介绍了团队将 TypeScript 编译器用 Go 语言重写的宏伟计划,他们承诺构建速度最高可提升 10 倍,并显著降低内存的占用。

 

这款名为tsgo的全新原生编译器充分利用了 Go 语言的性能优势,带来了大幅度的速度提升。据 TypeScript 团队表示,与旧版本相比,完整构建速度最高可提升 10 倍,并具备高效的多项目并行处理能力。为编辑器功能(如代码补全、跳转定义、重构等)提供支持的原生语言服务目前已基本稳定,可供日常使用。

 

用户现在就可以试用这一预览版:

npm install -g @typescript/native-preview
复制代码

 

TypeScript 7 最重要的变化之一是默认启用严格模式(strict mode),这是一项与以往版本不兼容的破坏性变更。这一转变体现了团队对类型安全的坚定承诺,也符合行业最佳实践,但可能要求从旧版本升级的项目进行相应调整。

 

选择 Go 作为实现语言在开发者社区引发了广泛讨论。团队在一份详尽的FAQ中解释说,Go 提供了自动垃圾回收机制,同时又是目前最贴近“原生优先”理念的语言。此外,现有 TypeScript 代码库采用高度函数式的编程风格,几乎不使用类,因此 Go 的函数与数据结构范式比面向对象语言更为契合。

 

Hacker News上,开发者们对性能提升表现出了极大的热情。一位用户评论说:

哇,这太震撼了!10 倍的速度提升对我们这类大型 TypeScript 项目将是颠覆性的。我一直在等待这样的改进,我们团队的项目在 CI 上的类型检查耗时极长,并严重拖慢了 IDE 的响应速度。

 

不过,也有开发者对依赖 TypeScript 编译器 API 的工具迁移路径表示担忧:

……对于我们这些工具作者来说,这个原生编译器将如何分发?我猜会通过 WebAssembly(WASM)?编译器 API 是否兼容?比如转换器(transforms)、抽象语法树(AST)、LanguageService、Program、SourceFile、Checker 等等?

 

我非常担心工具生态的迁移可能会异常困难。

 

一些开发者已经上手尝试。Reddit 上有用户称其类型检查时间减少了 75%。还有人对默认开启严格模式表示欢迎:

默认启用严格模式真是太棒了。我们以前经常在项目中工作到一半才发现严格模式没启用,结果要修复一大堆问题,非常令人头疼。

 

对于重度依赖编译器的开发工具而言,TypeScript 7 的原生实现使其与其它以原生语言编写的高性能 JavaScript 工具站在了同一赛道。例如,用 Go 编写的esbuild,以及用 Rust 编写的 SWC 和 oxc,均已证明原生实现能带来显著的性能优势。TypeScript 团队此次转型不仅验证了这一架构方向的正确性,同时也确保了与 TypeScript 语言规范的完全兼容。

 

TypeScript 是由微软开发和维护的一种强类型编程语言,它在 JavaScript 基础上增加了静态类型定义。自 2012 年发布以来,TypeScript 可编译为纯 JavaScript,运行于任何支持 JavaScript 的环境,包括浏览器、Node.js 及其他 JavaScript 运行时。通过其类型系统,开发者能在编译阶段而非运行时捕获错误;借助智能代码补全、重构等特性,IDE 支持也得到了显著增强,同时,显式的类型契约使大型代码库更易于维护。

 

原文链接:

Microsoft Share Update on TypeScript 7

微软近日分享了TypeScript 7(代号为 Corsa 项目)的最新进展,披露了对 TypeScript 编译器的一次根本性重构。该更新发布于2025年12月,详细介绍了团队将 TypeScript 编译器用 Go 语言重写的宏伟计划,他们承诺构建速度最高可提升 10 倍,并显著降低内存的占用。

 

这款名为tsgo的全新原生编译器充分利用了 Go 语言的性能优势,带来了大幅度的速度提升。据 TypeScript 团队表示,与旧版本相比,完整构建速度最高可提升 10 倍,并具备高效的多项目并行处理能力。为编辑器功能(如代码补全、跳转定义、重构等)提供支持的原生语言服务目前已基本稳定,可供日常使用。

 

用户现在就可以试用这一预览版:

npm install -g @typescript/native-preview
复制代码

 

TypeScript 7 最重要的变化之一是默认启用严格模式(strict mode),这是一项与以往版本不兼容的破坏性变更。这一转变体现了团队对类型安全的坚定承诺,也符合行业最佳实践,但可能要求从旧版本升级的项目进行相应调整。

 

选择 Go 作为实现语言在开发者社区引发了广泛讨论。团队在一份详尽的FAQ中解释说,Go 提供了自动垃圾回收机制,同时又是目前最贴近“原生优先”理念的语言。此外,现有 TypeScript 代码库采用高度函数式的编程风格,几乎不使用类,因此 Go 的函数与数据结构范式比面向对象语言更为契合。

 

Hacker News上,开发者们对性能提升表现出了极大的热情。一位用户评论说:

哇,这太震撼了!10 倍的速度提升对我们这类大型 TypeScript 项目将是颠覆性的。我一直在等待这样的改进,我们团队的项目在 CI 上的类型检查耗时极长,并严重拖慢了 IDE 的响应速度。

 

不过,也有开发者对依赖 TypeScript 编译器 API 的工具迁移路径表示担忧:

……对于我们这些工具作者来说,这个原生编译器将如何分发?我猜会通过 WebAssembly(WASM)?编译器 API 是否兼容?比如转换器(transforms)、抽象语法树(AST)、LanguageService、Program、SourceFile、Checker 等等?

 

我非常担心工具生态的迁移可能会异常困难。

 

一些开发者已经上手尝试。Reddit 上有用户称其类型检查时间减少了 75%。还有人对默认开启严格模式表示欢迎:

默认启用严格模式真是太棒了。我们以前经常在项目中工作到一半才发现严格模式没启用,结果要修复一大堆问题,非常令人头疼。

 

对于重度依赖编译器的开发工具而言,TypeScript 7 的原生实现使其与其它以原生语言编写的高性能 JavaScript 工具站在了同一赛道。例如,用 Go 编写的esbuild,以及用 Rust 编写的 SWC 和 oxc,均已证明原生实现能带来显著的性能优势。TypeScript 团队此次转型不仅验证了这一架构方向的正确性,同时也确保了与 TypeScript 语言规范的完全兼容。

 

TypeScript 是由微软开发和维护的一种强类型编程语言,它在 JavaScript 基础上增加了静态类型定义。自 2012 年发布以来,TypeScript 可编译为纯 JavaScript,运行于任何支持 JavaScript 的环境,包括浏览器、Node.js 及其他 JavaScript 运行时。通过其类型系统,开发者能在编译阶段而非运行时捕获错误;借助智能代码补全、重构等特性,IDE 支持也得到了显著增强,同时,显式的类型契约使大型代码库更易于维护。

 

原文链接:

Microsoft Share Update on TypeScript 7

今天跟大家分享一个etcd的内存大量占用的问题,这是前段时间在我们开源软件Easegress中遇到的问题,问题是比较简单的,但是我还想把前因后果说一下,包括,为什么要用etcd,使用etcd的用户场景,包括etcd的一些导致内存占用比较大的设计,以及最后一些建议。希望这篇文章不仅仅只是让你看到了一个简单的内存问题,还能让你有更多的收获。当然,也欢迎您关注我们的开源软件,给我们一些鼓励。

为什么要用ETCD

先说一下为什么要用etcd。先从一个我们自己做的一个API网关 – Easegress(源码)说起。

Easegress 是我们开发并开源的一个API应用网关产品,这个API应用网关不仅仅只是像nginx那样用来做一个反向代理,这个网关可以做的事很多,比如:API编排、服务发现、弹力设计(熔断、限流、重试等)、认证鉴权(JWT,OAuth2,HMAC等)、同样支持各种Cloud Native的架构如:微服务架构,Service Mesh,Serverless/FaaS的集成,并可以用于扛高并发、灰度发布、全链路压力测试、物联网……等更为高级的企业级的解决方案。所以,为了达到这些目标,在2017年的时候,我们觉得在现有的网关如Nginx上是无法演进出来这样的软件的,必需重新写一个(后来其他人也应该跟我们的想法一样,所以,Lyft写了一个Envoy。只不过,Envoy是用C++写的,而我用了技术门槛更低的Go语言)

另外,Easegress最核心的设计主要有三个:

  • 一是无第三方依赖的自己选主组集群的能力
  • 二是像Linux管道命令行那样pipeline式的插件流式处理(支持Go/WebAssembly)
  • 三是内置一个Data Store用于集群控制和数据共享。

对于任何一个分布式系统,都需要有一个强一制性的基于Paxos/Raft的可以自动选主机制,并且需要在整个集群间同步一些关键的控制/配置和相关的共享数据,以保证整个集群的行为是统一一致的。如果没有这么一个东西的话,就没有办法玩分布式系统的。这就是为什么会有像Zookeeper/etcd这样的组件出现并流行的原因。注意,Zookeeper他们主要不是给你存数据的,而是给你组集群的。

Zookeeper是一个很流行的开源软件,也被用于各大公司的生产线,包括一些开源软件,比如:Kafka。但是,这会让其它软件有一个依赖,并且在运维上带来很大的复杂度。所以,Kafka在最新的版本也通过内置了选主的算法,而抛弃了外挂zookeeper的设计。Etcd是Go语言社区这边的主力,也是kubernetes组建集群的关键组件。Easegress在一开始(5年前)使用了gossip协议同步状态(当时想的过于超前,想做广域网的集群),但是后发现这个协议太过于复杂,而且很难调试,而广域网的API Gateway也没遇到相应的场景。所以,在3年前的时候,为了稳定性的考量,我们把其换成了内嵌版本的etcd,这个设计一直沿用到今天。

Easegress会把所有的配置信息都放到etcd里,还包括一些统计监控数据,以及一些用户的自定义数据(这样用户自己的plugin不但可以在一条pipeline内,还可以在整个集群内共享数据),这对于用户进行扩展来说是非常方便的。软件代码的扩展性一直是我们追求的首要目标,尤其是开源软件更要想方设法降低技术门槛让技术易扩展,这就是为什么Google的很多开源软件都会选使用Go语言的原因,也是为什么Go正在取代C/C++的做PaaS基础组件的原因。

背景问题

好了,在介绍完为什么要用etcd以后,我开始分享一个实际的问题了。我们有个用户在使用 Easegress 的时候,在Easegress内配置了上千条pipeline,导致 Easegress的内存飙升的非常厉害- 10+GB 以上,而且长时间还下不来。

用户报告的问题是——

在Easegress 1.4.1 上创建一个HTTP对象,1000个Pipeline,在Easegres初始化启动完成时的内存占用大概为400M,运行80分钟后2GB,运行200分钟后达到了4GB,这期间什么也没有干,对Easegress没有进行过一次请求。

一般来说,就算是API再多也不应该配置这么多的处理管道pipeline的,通常我们会使用HTTP API的前缀把一组属于一个类别的API配置在一个管道内是比较合理的,就像nginx下的location的配置,一般来说不会太多的。但是,在用户的这个场景下配置了上千个pipeline,我们也是头一次见,应该是用户想做更细粒度的控制。

经过调查后,我们发现内存使用基本全部来自etcd,我们实在没有想到,因为我们往etcd里放的数据也没有多少个key,感觉不会超过10M,但不知道为什么会占用了10GB的内存。这种时候,一般会怀疑etcd有内存泄漏,上etcd上的github上搜了一下,发现etcd在3.2和3.3的版本上都有内存泄露的问题,但都修改了,而 Easegress 使用的是3.5的最新版本,另外,一般来说内存泄漏的问题不会是这么大的,我们开始怀疑是我们哪里误用了etcd。要知道是否误用了etcd,那么只有一条路了,沉下心来,把etcd的设计好好地看一遍。

大概花了两天左右的时间看了一下etcd的设计,我发现了etcd有下面这些消耗内存的设计,老实说,还是非常昂贵的,这里分享出来,避免后面的同学再次掉坑。

首当其冲是——RaftLog。etcd用Raft Log,主要是用于帮助follower同步数据,这个log的底层实现不是文件,而是内存。所以,而且还至少要保留 5000 条最新的请求。如果key的size很大,这 5000条就会产生大量的内存开销。比如,不断更新一个 1M的key,哪怕是同一个key,这 5000 条Log就是 5000MB = 5GB 的内存开销。这个问题在etcd的issue列表中也有人提到过  issue #12548 ,不过,这个问题不了了之了。这个5000还是一个hardcode,无法改。(参看 DefaultSnapshotCatchUpEntries 相关源码

// DefaultSnapshotCatchUpEntries is the number of entries for a slow follower
// to catch-up after compacting the raft storage entries.
// We expect the follower has a millisecond level latency with the leader.
// The max throughput is around 10K. Keep a 5K entries is enough for helping
// follower to catch up.
DefaultSnapshotCatchUpEntries uint64 = 5000

另外,我们还发现,这个设计在历史上etcd的官方团队把这个默认值从10000降到了5000,我们估计etcd官方团队也意识到10000有点太耗内存了,所以,降了一半,但是又怕follwer同步不上,所以,保留了 5000条……(在这里,我个人感觉还有更好的方法,至少不用全放在内存里吧……)

另外还有下面几项也会导致etcd的内存会增加

  1. 索引。etcd的每一对 key-value 都会在内存中有一个 B-tree 索引。这个索引的开销跟key的长度有关,etcd还会保存版本。所以B-tree的内存跟key的长度以及历史版本号数量也有关系。
  2. mmap。还有,etcd 使用 mmap 这样上古的unix技术做文件映射,会把他的blotdb的内存map到虚拟内存中,所以,db-size越大,内存越大。
  3. Watcher。watch也会占用很大的内存,如果watch很多,连接数多,都会堆积内存。

(很明显,etcd这么做就是为了一个高性能的考虑)

Easegress中的问题更多的应该是Raft Log 的问题。后面三种问题我们觉得不会是用户这个问题的原因,对于索引和mmap,使用 etcd 的 compact 和 defreg (压缩和碎片整理应该可以降低内存,但用户那边不应该是这个问题的核心原因)。

针对用户的问题,大约有1000多条pipeline,因为Easegress会对每一条pipeline进行数据统计(如:M1, M5, M15, P99, P90, P50等这样的统计数据),统计信息可能会有1KB-2KB左右,但Easegress会把这1000条pipeline的统计数据合并起来写到一个key中,这1000多条的统计数据合并后会导致出现一个平均尺寸为2MB的key,而5000个in-memory的RaftLog导致etcd要消耗了10GB的内存。之前没有这么多的pipeline的场景,所以,这个内存问题没有暴露出来。

于是,我们最终的解决方案也很简单,我们修改我们的策略,不再写这么大的Value的数据了,虽然以前只写在一个key上,但是Key的值太大,现在把这个大Key值拆分成多个小的key来写,这样,实际保存的数据没有发生变化,但是RaftLog的每条数据量就小了,所以,以前是5000条 2M(10GB),现在是5000条 1K(500MB),就这样解决了这个问题。相关的PR在这里 PR#542

总结

要用好 etcd,有如下的实践

  • 避免大尺寸的key和value,一方面会通过一个内存级的 Raft Log 占大量内存,另一方面,B-tree的多版本索引也会因为这样耗内存。
  • 避免DB的尺寸太大,并通过 compact和defreg来压缩和碎片整理降低内存。
  • 避免大量的Watch Client 和 Watch数。这个开销也是比较大的。
  • 最后还有一个,就是尽可能使用新的版本,无论是go语言还是etcd,这样会少很多内存问题。比如:golang的这个跟LInux内核心相关的内存问题 —— golang 1.12的版sget的是 MADV_FREE 的内存回收机制,而在1.16的时候,改成了 MADV_DONTNEED ,这两者的差别是,FREE表示,虽然进程标记内存不要了,但是操作系统会保留之,直到需要更多的内存,而 DONTNEED 则是立马回收,你可以看到,在常驻内存RSS 上,前者虽然在golang的进程上回收了内存,但是RSS值不变,而后者会看到RSS直立马变化。Linux下对 MADV_FREE 的实现在某些情况下有一定的问题,所以,在go 1.16的时候,默认值改成了 MADV_DONTNEED 。而 etcd 3.4 是用 来1.12 编译的。

最后,欢迎大家关注我们的开源软件! https://github.com/megaease/ 

一个基于 Wails 框架开发的网页备份工具,支持完整备份网页内容,包括 HTML、CSS、JavaScript、图片等所有资源,并提供隐私清理功能。

Star History

Star History Chart

⚠️ 重要声明

本工具仅供学习和研究使用,请勿用于任何违法活动!

  • 🎓 学习目的:仅用于学习网页技术和备份个人网站
  • 📋 遵守规则:请遵守目标网站的 robots.txt 和使用条款
  • 🚫 禁止滥用:不得用于恶意爬取、侵犯版权或其他违法行为
  • ⚖️ 自负责任:使用者需自行承担使用责任

🚀 功能特性

核心功能

  • 📦 完整备份:备份网页的所有资源文件(HTML、CSS、JS、图片、视频等)
  • 🛡️ 隐私清理:自动移除第三方跟踪代码、统计代码、广告代码
  • 📊 实时进度:显示备份进度和文件下载状态
  • 🗜️ ZIP 打包:自动将备份文件打包为 ZIP 格式
  • 📁 目录选择:支持选择自定义保存目录
  • 📱 响应式布局:栅格布局,适应不同窗口大小
  • 🌍 跨平台:支持 Windows、macOS、Linux

界面特性

  • 🎨 现代化 UI:基于 Naive UI 的美观界面
  • 📋 详细配置:丰富的备份选项配置
  • 📈 进度监控:实时显示文件下载状态
  • 🔍 文件详情:可查看每个文件的下载进度

⚠️ 功能限制

请注意:本工具主要适用于简单的静态网页备份

技术限制

  • 动态内容:无法备份需要 JavaScript 动态加载的内容
  • 懒加载:不支持懒加载(lazy loading)内容
  • 用户交互:无法处理需要用户交互才显示的内容
  • SPA 路由:不支持单页应用(SPA)的动态路由内容
  • 登录内容:无法备份需要登录才能访问的内容
  • 复杂框架:对于 React、Vue、Angular 等现代框架构建的复杂应用效果有限

适用场景

  • 静态网站:个人博客、企业官网等静态页面
  • 简单页面:新闻文章、产品介绍页面
  • 文档网站:技术文档、帮助页面
  • 传统网站:基于传统 HTML/CSS/JS 的网站

🛡️ 隐私清理功能

自动清理的内容

  • 📊 统计代码:Google Analytics、百度统计、CNZZ、Mixpanel、Segment 等
  • 👁️ 跟踪代码:Facebook Pixel、TikTok Pixel、Snapchat Pixel、Hotjar、CrazyEgg、Clarity 等
  • 📢 广告代码:Google Ads、DoubleClick、Taboola、Outbrain、PopAds、PropellerAds、AdCash 等
  • 🏷️ 标签管理器:Google Tag Manager (GTM) 等
  • ⚠️ 恶意标签:base 标签劫持、自动跳转、来源伪造、恶意重定向等

安全防护

  • 🔒 链接劫持防护:自动删除所有 base 标签,防止恶意网站劫持页面中的所有相对链接
  • 🚫 自动跳转防护:删除 meta refresh 标签,防止页面自动跳转到钓鱼网站或恶意网站
  • 🎭 来源伪造防护:删除 meta referrer 标签,防止恶意网站伪造访问来源
  • 🔄 重定向防护:检测并删除包含恶意重定向的 JavaScript 代码

🛠️ 技术栈

前端技术

  • 框架:Vue 3 + TypeScript
  • UI 库:Naive UI
  • 构建工具:Vite
  • 路由:Vue Router 4
  • 图标:Ionicons 5

后端技术

  • 语言:Go 1.23+
  • 框架:Wails v2
  • 网页解析:goquery
  • 文本编码:golang.org/x/text
  • HTTP 客户端:Go 标准库

开发工具

  • 包管理:Go Modules + npm
  • 类型检查:TypeScript + Vue TSC
  • 代码格式化:内置支持

📋 系统要求

开发环境

  • Go:1.23 或更高版本
  • Node.js:18 或更高版本
  • Wails CLI:v2 最新版本

运行环境

  • Windows:Windows 10/11 (x64)
  • macOS:macOS 10.15+ (Intel/Apple Silicon)
  • Linux:主流发行版 (x64)

🚀 快速开始

1. 克隆项目

复制
git clone https://github.com/adiudiuu/site_backup.git
cd site_backup

2. 安装依赖

复制
# 安装 Go 依赖
go mod tidy

# 安装前端依赖
cd frontend
npm install
cd ..

3. 开发运行

复制
# 使用 Makefile(推荐)
make run

# 或直接使用 Wails CLI
wails dev

4. 构建发布

复制
# 构建 Windows 版本
make build-win

# 构建 macOS 版本(需要在 macOS 上运行)
make build-mac

# 或使用 Wails CLI
wails build

📖 使用指南

基本使用步骤

  1. 输入网址:在目标网址框中输入要备份的网页 URL
  2. 选择目录:点击"选择目录"按钮,选择备份文件的保存位置
  3. 配置选项
    • 选择要备份的内容类型(图片、样式、脚本、视频)
    • 选择要清理的隐私内容(统计代码、跟踪代码、广告代码)
    • 调整高级选项(超时时间、最大文件数、并发数)
  4. 开始备份:点击"开始备份"按钮
  5. 监控进度:实时查看备份进度和文件下载状态
  6. 完成备份:备份完成后,ZIP 文件将保存到指定目录

使用建议

  • 🎯 优先选择:静态网站或博客进行备份
  • ⚠️ 避免备份:复杂的动态网站或 SPA 应用
  • 🧪 先测试:测试小页面后再备份大型网站
  • ⏱️ 注意频率:注意网站的访问频率限制,避免过于频繁的请求
  • 📏 合理配置:根据网络情况调整超时时间和并发数

故障排除

  • 网络错误:检查网络连接和 URL 是否正确
  • 访问被拒:可能遇到反爬虫机制,建议稍后重试
  • 文件过大:调整最大文件数限制或增加超时时间
  • 权限问题:确保对保存目录有写入权限

🤝 贡献指南

欢迎提交 Issue 和 Pull Request!

开发流程

  1. Fork 本仓库
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启 Pull Request

代码规范

  • Go 代码遵循 gofmt 格式
  • TypeScript 代码使用 ESLint 规范
  • 提交信息使用英文,格式清晰

📄 许可证

本项目采用 GNU General Public License v3.0 许可证。

这意味着:

  • ✅ 可以自由使用、修改和分发
  • ✅ 可以用于商业目的
  • ⚠️ 修改后的代码必须开源
  • ⚠️ 必须保留原始许可证和版权声明
  • ⚠️ 不提供任何担保

详细信息请查看 LICENSE 文件。

⚖️ 免责声明

  • 本工具仅供学习和研究使用
  • 使用本工具产生的任何法律后果由使用者自行承担
  • 开发者不承担任何责任
  • 请确保您的使用行为符合当地法律法规和目标网站的使用条款
  • 请尊重网站的 robots.txt 文件和访问限制

📞 联系方式


站内已经有很多佬分享了方法了,正好我昨天试出来了,我这里粗略总结一下整体的流程,方便佬们参考。

0、安装必要包

由于我们的服务器上已经有 git, gcc, make 这些包了,我就得多装一个 go(至于为啥没有,因为我们服务器是做科学计算的,也不咋用到 go 这个语言)
但是我没有 sudo 权限(如果有的话,直接 apt install golang-go 或者 dnf install golang 就行),所以就用户级安装一下

(1) 先确认 linux 内核版本,决定安装哪个版本的 go

从 go 1.24 开始,Linux 内核的最低要求是 3.2。如果服务器内核版本低于 3.2,就不要安装太新的 go,可以用 1.23。
使用如下命令查看内核版本:

uname -r

我的显示 4.18.0-348.el8.x86_64,所以我就直接安装最新版本的 go 了

(2) 下载 Go

我直接在本地机器上访问 go 官方下载页 ,由于我的系统架构是 x86-64,所以就直接下载了对应版本的包,然后将包拖到 mobaxterm 传到服务器上。比如我放在 ~/software/go1.25.5.linux-amd64.tar.gz

然后我将 go 放在 ~/.local/ 里面,运行如下命令:

mkdir -p $HOME/.local
cd $HOME/.local
tar -xzf ~/software/go1.25.5.linux-amd64.tar.gz

这样就会出现 ~/.local/go,binary 也在里面。

(3) 把 go 放到 bashrc 里面

运行如下命令:

echo 'export PATH=$HOME/.local/go/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

这样就安装完成了。可以透过如下命令查看是否成功安装:

go version

1、配置 graftcp

将这个 clone 下来(比如我的是 clone 到 ~/software 底下)

git clone https://github.com/hmgle/graftcp.git
cd graftcp

如果说由于网络问题 clone 不了,可以参考文末「常见问题」。

直接运行 make 即可。如果没有报错的话进入下一步,有报错的话需要及时解决一下,比如如果你的报错 log 中显示 443 网络问题,那就是网络问题,得挂代理或者使用「常见问题」里面提到的方法解决一下。

再设置可执行权限。

chmod +x ./graftcp
chmod +x local/graftcp-local

配置一下 conf 文件:

cp local/example-graftcp-local.conf local/graftcp-local.conf

然后编辑一下 local/graftcp-local.conf,把 http_proxy 那一栏改一下

## HTTP proxy address (default "") http_proxy = 127.0.0.1:7890 

2、修改 antigravity 的 language-server 服务

这时使用 antigravity 登录你的远程 linux 服务器,可以发现模型肯定还是加载不出来。

然后可以按照如下帖子的方法找到 language-server 服务的位置:

比如我的位置是:~/.antigravity-server/bin/94f91bc110994badc7c086033db813077a5226af/extensions/antigravity/bin/language_server_linux_x64

我先把这个改为.bak 后缀:

mv ~/.antigravity-server/bin//extensions/antigravity/bin/language_server_linux_x64 ~/.antigravity-server/bin//extensions/antigravity/bin/language_server_linux_x64.bak

然后创建一个同名的新的 language_server_linux_x64

vim ~/.antigravity-server/bin//extensions/antigravity/bin/language_server_linux_x64

写入以下内容:(这个方法也是上述引用帖子中的方法)

#!/bin/bash # ================= 配置区域 ================= # 1. Graftcp 安装目录(改为你安装的实际目录)
GRAFTCP_DIR="/your/graftcp" # 2. 代理地址(改为自己的代理地址,我这是本地的)
PROXY_URL="127.0.0.1:10808" # =========================================== # 调试日志
LOG_FILE="/tmp/graftcp_wrapper.log" echo "[$(date)] Starting wrapper for $@" >> "$LOG_FILE" # 检查 graftcp-local 服务是否在运行 if ! pgrep -f "$GRAFTCP_DIR/local/graftcp-local" > /dev/null; then echo "Starting graftcp-local daemon..." >> "$LOG_FILE" # 后台启动,将日志丢入黑洞防止阻塞 nohup "$GRAFTCP_DIR/local/graftcp-local" -socks5="$PROXY_URL" > /dev/null 2>&1 &

sleep 0.5
fi # 1. 强制使用系统 DNS (解决解析问题) export GODEBUG=netdns=cgo

# 2. 强制关闭 HTTP/2 (解决 EOF 问题) # Go 的 HTTP/2 客户端在代理环境下非常敏感,强制用 HTTP/1.1 通常能解决 EOF export GODEBUG=$GODEBUG,http2client=0
# 使用 graftcp 启动真正的程序 # "$0.bak" 是原程序的备份 exec "$GRAFTCP_DIR/graftcp" "$0.bak" "$@" 

比如我就需要将脚本改为:

GRAFTCP_DIR="~/software/graftcp" PROXY_URL="127.0.0.1:7890" 

保存后,添加可执行文件:

chmod +x ~/.antigravity-server/bin/94f91bc110994badc7c086033db813077a5226af/extensions/antigravity/bin/language_server_linux_x64

这样就可以确保 antigravity server 中的 language-server 的程序每次启动的时候都能够通过 graftcp 代理。

3、重新启动 IDE 并 ssh 连接

稍等片刻后,如果上述配置顺利,应该可以顺利看到 models 并且可以进行对话:


常见问题

A. 没有 wget

我在用 antigravity ssh 到自己实验室其中一个服务器的时候,曾经遇到过一个问题,ssh 连接上之后秒断连,查看 log 才发现是没有 wget

2026-01-04 02:10:51.424 [info] [Trace	- 18:10:51.422] Waiting for lock...
2026-01-04 02:10:51.425 [info] [Trace	- 18:10:51.424] Lock acquired, proceeding with installation.
2026-01-04 02:10:51.425 [info] [Trace	- 18:10:51.424] Cleaning up old server installations...
2026-01-04 02:10:51.427 [info] [Trace	- 18:10:51.426] Finished cleaning up old server installations.
2026-01-04 02:10:51.440 [info] [Trace	- 18:10:51.439] [stderr] /usr/bin/which: no wget in (/ShareS/UserHome/user007/.local/bin:/ShareS/UserHome/user007/.nvm/versions/node/v22.20.0/bin:/ssd02/PipelineWeb/sshpass/bin:/ssd02/PipelineWeb/imod-4.11.25/imod_4.11.25/bin:/ssd02/AutoPipeline/qt5.12/Tools/QtCreator/bin:/ssd02/AutoPipeline/qt5.12/5.12.5/gcc_64/bin:/ssd02/PipelineWeb/cuda-11.2/bin:/ssd02/PipelineWeb/cuda-10.2/bin:/ssd02/PipelineWeb/cuda-10.1/bin:/ssd02/PipelineWeb/cuda-10.0/bin:/ssd02/app/program/cuda-9.0/bin:/ssd02/PipelineWeb/sqlite3/bin:/ssd02/PipelineWeb/pipeline-client/bin:/ssd02/PipelineWeb/cmake-3.18.2-Linux-x86_64/bin:/ssd02/PipelineWeb/mpich-3.2/bin:/ShareS/UserHome/user007/opt/git/bin:/ShareS/UserHome/user007/opt/ssh/bin:/ShareS/UserHome/user007/opt:/ShareS/UserHome/user007/.local/bin:/ShareS/UserHome/user007/bin:/usr/share/Modules/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin)
2026-01-04 02:10:51.440 [info] [Trace	- 18:10:51.439] Error need wget to download server binary
2026-01-04 02:10:51.440 [info] [Trace	- 18:10:51.439] Error: installation failed.
2026-01-04 02:10:51.440 [info] [Trace	- 18:10:51.439] 456189e41f49b65457df3cf9: start
2026-01-04 02:10:51.440 [info] [Trace	- 18:10:51.439] exitCode==1==

我没有服务器的 sudo 权限,所以没办法直接安装 wget
然后我 command -v curl 之后,确认是有 curl
于是在 terminal 运行如下命令即可,可以创建一个 wget-like 的兼容的脚本。

mkdir -p ~/.local/bin

cat > ~/.local/bin/wget <<'SH' #!/usr/bin/env bash set -euo pipefail

out=""
url="" while [[ $# -gt 0 ]]; do case "$1" in
    -q|--quiet) shift ;;
    -O) out="${2:-}"; shift 2 ;;
    -O*) out="${1#-O}"; shift ;;
    *) url="$1"; shift ;;
  esac done

[[ -z "${url}" ]] && { echo "wget wrapper: missing URL" >&2; exit 2; }

if [[ -z "${out}" || "${out}" == "-" ]]; then exec curl -fsSL "${url}" else exec curl -fsSL "${url}" -o "${out}" fi
SH

chmod +x ~/.local/bin/wget
hash -r 2>/dev/null || true 

B. 远程服务器没法魔法

最简单的方法就是首先打开本地机器的 clash 之类的代理软件。

先创建一个 ssh 通道:

ssh -R 7890:localhost:7890 <username>@<ip>
  • mac 直接在 terminal 运行就行,windows 上就用 mobaxterm 在 home 目录运行就可以
  • 之所以选 7890,是因为本地机器的 clash verge rev 上是这个端口号。如果是别的端口,替换即可。

然后在远程 linux 服务器上执行:

export all_proxy="http://127.0.0.1:7890" export ALL_PROXY="$all_proxy" export http_proxy="$all_proxy" export https_proxy="$all_proxy" 

这样涉及到 git clone 之类的就完全不会出现问题了。


📌 转载信息
原作者:
zhenhuang
转载时间:
2026/1/4 12:18:24

之前用 watchtower 发现必须要:ladder:才能检查镜像,对于国内机器太不友好了。于是我自己搞了个轻量版的,配好 Docker 的镜像加速源就能直接用了。

[bsgit user="naomi233"]watchducker[/bsgit]
一个用 Go 语言编写的 Docker 容器镜像更新检查和自动更新工具

  • 智能检查: 自动检测容器使用的镜像是否有新版本可用
  • 标签驱动: 通过 watchducker.update=true 标签自动管理需要更新的容器
  • 定时执行: 支持使用 cron 表达式进行定时检查
  • 自动更新: 检测到更新后可自动重启容器使用新镜像
  • 灵活控制: 提供只检查不重启的选项
  • 实时反馈: 检查过程中提供实时进度和结果输出
  • Docker 原生: 完全基于 Docker API,无需额外依赖
  • 无需代理: 复用现有 Docker 配置,无需额外配置认证和代理、加速镜像源

示例

# 检查指定容器
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker nginx redis mysql
# 检查所有带有更新标签的容器
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --label
# 只更新镜像,不重启容器
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --no-restart nginx redis
# 使用标签模式,同时防止自动重启
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --label --no-restart
# 每天凌晨2点检查所有标签容器
docker run --name watchducker -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --cron "0 2 * * *" --label
# 每30分钟检查指定容器
docker run --name watchducker -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --cron "*/30 * * * *" nginx redis
# 每天执行,只检查不重启
docker run --name watchducker -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --cron "@daily" --no-restart nginx

Docker compose yaml 示例

services:
  watchducker:
    image: naomi233/watchducker
    container_name: watchducker
    network_mode: bridge
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - TZ=Asia/Shanghai
      - WATCHDUCKER_LOG_LEVEL=DEBUG
      - WATCHDUCKER_CRON=0 2 * * *
      - WATCHDUCKER_LABEL=true

一、前言

Go语言是一个神奇的语言。

但是我不会,我连开机都不会,Go是什么

Go to it!

这个页面上是不是很熟悉,你们在渗透扫目标的时候是不是也遇到过。

pprof是一个用于Go开发时对收集的数据分析和可视化工具

它能收集信息,也能被我们利用!

首先要知道一些默认的功能

cpu(CPU Profiling): $HOST/debug/pprof/profile
默认进行 30s 的 CPU Profiling,
得到一个分析用的 profile 文件


$HOST/debug/pprof/threadcreate
得到一个分析用的 新OS线程的堆栈跟踪

可以查看创建新OS线程的堆栈跟踪


当然 我要这乱码有何用

你们可以看下我的笔记


cpu(CPU Profiling): $HOST/debug/pprof/profile,默认进行 30s 的 CPU Profiling,得到一个分析用的 profile 文件
block(Block Profiling):$HOST/debug/pprof/block,查看导致阻塞同步的堆栈跟踪
goroutine:$HOST/debug/pprof/goroutine,查看当前所有运行的 goroutines 堆栈跟踪
heap(Memory Profiling): $HOST/debug/pprof/heap,查看活动对象的内存分配情况
mutex(Mutex Profiling):$HOST/debug/pprof/mutex,查看导致互斥锁的竞争持有者的堆栈跟踪
threadcreate:$HOST/debug/pprof/threadcreate,查看创建新OS线程的堆栈跟踪

对应的文件名称解析

二、获取交互的利用

利用自己的服务器安装Go环境

Let's Go! 的那个Go

再利用以下命令
go tool pprof https://(host)/debug/pprof/profile?seconds=60

它默认是30秒的

我们可以改成60秒来查看更多的信息

执行以上的命令的后

会出现以下字样

(pprof)

相当于shell的交互式

我们可以执行

top10

查看最前面的10个CPU运行信息
(看不清的,放大图片看)

继续

go tool pprof https://(host)/debug/pprof/heap

照上面的笔记

heap 是获取内存的信息

执行以上的命令的后

会出现以下字样

(pprof)

相当于shell的交互式
(上面说过了)

执行

o
(字母小o)

看上面的图

就可以获取配置信息

再利用top命令

可以看到在运行的内存信息
其他的功能点