Golang
2026-08-22
关于Passkey 的介绍参见 《Passkey 无密码登录》。
前 Go 密码学负责人 Filippo Valsorda 提交 golang/go#80663 提案,拟在标准库新增 crypto/passkey 包,用“无状态、无回调、无接口”的极简服务端 API,把中小型网站接入 Passkey(WebAuthn 可发现凭据)的门槛降到最低。
核心设计有三块。一是 passkey-record 规范:把认证器数据、transports 等编码成类似 PHC 密码哈希的单行不透明字符串(如 $webauthn$v=1$...),存库时当黑盒文本即可,支持跨语言、跨库迁移。 二是 存储模型简化:数据库只需 user_id + record 两列、按 user_id 建索引,不对 credential_id 建唯一索引——凭据永远先按用户查再逐条比对,从根上规避跨账号主键碰撞攻击。 三是 流式无状态工作流:RelyingParty 是纯配置对象,注册走 NewRegistration→前端 navigator.credentials.create→Register 存串;登录走 NewLogin→ParseResponse 取 UnauthenticatedUserID(验签前不可信,仅用于查记录)→Login。
关键安全取舍:弃用回调(吸取 GO-2024-3321 教训,避免未验数据流入应用判断)、强制单 Origin 配置(多源需建多个 RP 实例,防跨站断言重放)、User ID 用 crypto/rand.Text() 生成随机不透明串、不校验 attestation 与签名计数器,专为消费级 95% 场景减负。
提案仍处评审期,未进正式版本,但信号明确:Passkey 接入正从“懂协议”降级为“按 user_id 存一个字符串”。
Golang
2026-08-12
Golang
2026-03-25
看了这篇博客 Go 语言之父亲自下场道歉:藏在 Spec 里的十年“笔误”,终于要修正了!,讲的是 Go 语言规范中对 bool 类型定义的长期笔误被修正,其本质是内建类型(predeclared type)而非自定义类型(defined type)。
重新梳理一下我对 Golang 类型系统的一点认知。
-
从实现角度看,Go 类型系统可以抽象为三个核心维度:
- Type Identity(类型同一性):决定类型是否相等
- Underlying Type(底层类型):决定类型转换规则
- Assignability(可赋值性):决定类型之间是否可以直接使用
大多数类型问题,本质都是这三者的组合。
-
类型系统
Type
├── Named Type(命名类型)
│ ├── Predeclared Type(内建类型)
│ ├── Defined Type(通过 type 定义的新类型)
│ └── Type Parameter(泛型参数)
├── Unnamed Type(Type Literal)
└── Alias(类型别名,声明机制,不是类型分类)
Golang
2026-01-26
Go 标准库已经提供了 sync/atomic,为什么还会出现 Uber 的 atomic?答案是:两者的底层定位接近,但工程目标不同。sync/atomic 是 Go 官方提供的底层原子操作 API,而 go.uber.org/atomic 则是在此基础上进一步提供类型安全、API 友好以及更符合大型工程实践的封装。
Python Golang
2026-01-21
Starlark 是一种受 Python 强烈影响的动态脚本语言,可以理解为“去掉 Python 大部分系统能力和复杂特性、保留熟悉语法,并专门用于配置、规则和宿主程序扩展的脚本语言”。
NodeJS Golang
2026-01-10
在 2026 年的后端技术版图中,Node.js 与 Golang 的博弈早已超越了单纯的“性能跑分”。Node.js 赢在开发效率和 I/O 并发的性价比,而 Go 赢在多核并行上限和系统级稳定。
高并发不等于 CPU 忙,大部分 Web 高并发场景本质是“等 I/O”,所以 Node.js 从未掉队;只有当面临算力密集型需求或超大规模微服务底座时,Go 的优势才会彻底显现。
Node.js 胜在开发效率与 I/O 并发性价比,适合 BFF 与实时应用;Go 赢在多核并行、内存优化与系统级稳定,适合 CPU 密集与大型微服务。
比如在大多数 Web 项目中,主要的
核心机制:单线程事件循环 VS 协程并行
理解两者的差异,首先要打破“并发即并行”的迷思。
Node.js 就像一个熟练的厨师同时照料着 10 个锅。它采用单线程加事件循环的机制,遇到 I/O 操作(如网络请求、数据库查询、文件读写)时,就通过挂起回调来处理其他任务,实现了并发但不并行的工作方式。这表示任意时刻只有一段 JS 代码在运行,虽然这样效率很高,但却无法发挥多核处理器的优势。
Go 语言则如同一个完整的厨师团队。使用 go 关键字启动 Goroutine 后,由 Runtime 负责调度到多个 CPU 核心上,实现真正的并行处理,让多个任务能够同步进行。
关键区分在于:并发是处理多个任务的能力,并行是同时执行多个任务的能力。Node.js 胜在前者,Go 胜在后者。
性能边界:I/O 与 CPU 的分水岭
性能的选择取决于业务瓶颈在哪里。
在 I/O 密集型场景(如 API 网关、CRUD、查库、调第三方接口),瓶颈往往在网卡、磁盘或数据库,而不在语言运行时。此时 Node.js 的事件循环和 Go 的协程效果接近,都能轻松扛住上万连接。
但在 CPU 密集型场景(如图片压缩、视频转码、加解密、重计算),Node.js 的单线程一旦被一段重计算卡住,整个进程的响应时间就会抖动,新请求无法进入;而 Go 可以将任务分散到多核,不阻塞主服务,在此场景下 Go 完胜。
此外,在内存占用上,Node.js 挂起的异步任务只是个回调,极轻;而 Go 每个 Goroutine 约占 2KB。在极端海量连接时,Node.js 反而略占优,尽管差距很小。
进阶视角:内存、冷启动与尾部延迟
除了基础机制,Go 在内存管理和冷启动上拥有 Node.js 难以逾越的优势。
除了基础机制,Go 在内存管理和冷启动上拥有 Node.js 难以逾越的优势。
在承受相同并发压力时,Go 版本的内存使用量通常只有 Node.js 的五分之一甚至更低。Node.js 的 V8 引擎启动需预分配大量内存,且“万物皆对象”导致堆内存开销巨大;而 Go 允许使用紧凑的值类型,并能智能地将短生命周期变量直接分配在栈上(用完即销,无需垃圾回收),内存管理极其高效。
同时,Go 作为编译型语言,直接编译为机器码,没有预热过程。在 Serverless 场景或微服务频繁重启时,Go 的毫秒级冷启动和首请求低延迟表现远优于 Node.js。
在高并发压测中,瓶颈往往在于“等待”。Node.js 虽然原始吞吐量不如 Go,但它天然的单线程摩擦像一个“软限流”,迫使系统正视反压,在下游抖动时反而能保持相对平稳的 P99 延迟。Go 虽然吞吐上限极高,但在突发流量下,其垃圾回收压力和 Goroutine 调度偶尔会在 P99 延迟上产生毛刺,这对团队的容量规划提出了更高要求。
同时,Go 作为编译型语言,直接编译为机器码,没有预热过程。在 Serverless 场景或微服务频繁重启时,Go 的毫秒级冷启动和首请求低延迟表现远优于 Node.js。
在高并发压测中,瓶颈往往在于“等待”。Node.js 虽然原始吞吐量不如 Go,但它天然的单线程摩擦像一个“软限流”,迫使系统正视反压,在下游抖动时反而能保持相对平稳的 P99 延迟。Go 虽然吞吐上限极高,但在突发流量下,其垃圾回收压力和 Goroutine 调度偶尔会在 P99 延迟上产生毛刺,这对团队的容量规划提出了更高要求。
为什么高并发系统依然偏爱 Node.js
真实工程中,技术选型往往不是性能至上,而是综合成本的考量。
开发速度是首要因素。JS/TS 实现了前后端同语言,CRUD、API、BFF 层出活极快,npm 生态的海量包更是如虎添翼。
其次是 I/O 场景够用。多数业务系统的瓶颈在数据库和下游服务,不在语言运行时,Node.js 完全顶得住。
团队匹配度也不容忽视。前端团队能直接写后端,招聘和协作成本大幅降低。
在实时/长连接场景(如 WebSocket、聊天室、Agent 等待 LLM 响应),Node.js 的事件循环天然契合这种“傻等 I/O”的场景。
终极解法:混合架构与选型对照
在大型复杂系统中,最成熟的做法往往是微服务架构下的混合选型。
让 Node.js 作为 BFF 层或前端网关,负责处理客户端请求、静态文件服务、轻量级 RESTful API 以及实时通信,发挥其开发快、生态全的优势。
让 Go 作为核心业务底座,负责高吞吐量的数据处理、复杂的计算密集型任务、底层微服务以及高频的内部 RPC 调用,发挥其多核并行和稳定上限的优势。
Golang
2025-10-13

这篇文章《Plugins in Go》深入探讨了在 Go 语言中实现插件机制的多种方式,尽管 Go 本身不支持运行时加载动态库。以下是文章的核心要点汇总:
🧩 为什么在 Go 中使用插件
- 虽然 Go 编译速度快,但某些应用仍需在运行时加载插件。
- 插件机制可以扩展应用功能,提升灵活性和可维护性。
✅ 理想插件架构的标准
- 速度:调用插件方法应尽可能快。
- 可靠性:插件应稳定运行,支持故障恢复。
- 安全性:防止篡改,例如通过代码签名。
- 易用性:插件 API 应简洁易懂。
🔄 插件运行方式对比
| 插件类型 |
优点 |
缺点 |
| 进程内插件 |
快速调用、部署简单、无需额外管理 |
插件崩溃可能影响主程序、语言受限(通常只能用 Go 编写) |
| 独立进程插件 |
崩溃隔离、安全性高、支持多语言、可动态启用/禁用 |
通信复杂、性能略低、需额外管理(如健康检查) |
🔧 Go 中的插件实现方式
-
通过 stdin/stdout 使用 RPC 通信
- 主程序启动插件进程,通过标准输入输出进行 RPC 通信。
- 示例:Pie 插件框架。
-
通过网络使用 RPC 通信
- 使用
net/rpc 在本地网络中连接插件。
- 示例:HashiCorp 的 go-plugin,功能强大,适合企业级应用。
-
通过消息队列通信
- 使用如 nanomsg 的 ReqRep 或 Survey 协议进行插件交互。
- 虽未有现成框架,但架构灵活,适合自定义实现。
-
编译时集成的进程内插件
- 插件作为 Go 包编译进主程序,常见于 HTTP 中间件。
- 优点是性能高,缺点是需重新编译主程序以更新插件。
-
脚本插件(解释执行)
- 使用嵌入式脚本语言(如 Lua、JavaScript)在运行时加载插件。
- 示例:GopherLua、Otto、Agora。
- 性能较低,但灵活性高。
🧪 示例代码概览
文章提供了一个基于 net/rpc 的简单插件系统示例,包括:
- 插件定义与注册
- 主程序启动插件进程并通过 RPC 调用方法
- 插件方法包括字符串反转和退出操作
Golang Email DNS
2025-07-08
背景知识:什么是 SPF?
SPF(Sender Policy Framework,发送方策略框架)是一种电子邮件验证协议,定义在 https://datatracker.ietf.org/doc/html/rfc7208 中。它的核心作用是让域名所有者声明:哪些 IP 地址或服务器被允许代表该域名发送邮件。
接收方邮件服务器收到邮件后,会查询发件人域名的 SPF 记录(一条 DNS TXT 记录),检查连接 IP 是否在授权列表中。这能有效防止邮件伪造和钓鱼攻击。
一条典型的 SPF 记录长这样:
v=spf1 ip4:192.0.2.0/24 include:_spf.google.com ~all
Golang 解析
注意:这个库只有 12 次提交,已经 2 年多没有更新,肯定不完备,但是放在小工具中简单使用也没有什么问题。
UPDATE: 这个库太简陋了,不如自己写一个。
mileusna/spf 是一个轻量级的 Go 语言 SPF 检查库,提供了两个核心接口:
| 功能 |
函数 |
说明 |
| SPF 校验 |
spf.CheckHost() |
给定 IP、域名和发件人,返回完整的 SPF 判定结果 |
| 记录查询 |
spf.LookupSPF() |
仅从 DNS 拉取原始 SPF TXT 字符串 |
package main
import (
"net"
"github.com/mileusna/spf"
)
func main() {
// optional, set DNS server which will be used by resolver.
// Default is Google's 8.8.8.8:53
spf.DNSServer = "1.1.1.1:53"
ip := net.ParseIP("123.123.123.123")
r := spf.CheckHost(ip, "qq.com", "service@qq.com", "");
// returns spf check result
// "PASS" / "FAIL" / "SOFTFAIL" / "NEUTRAL" / "NONE" / "TEMPERROR" / "PERMERROR"
// if you only need to retrive SPF record as string from DNS
spfRecord, _ := spf.LookupSPF("qq.com")
fmt.Println(spfRecord)
// v=spf1 include:spf.mail.qq.com -all
}
spf.CheckHost() 返回以下 7 种字符串结果之一:
| 返回值 |
含义 |
邮件处理建议 |
"PASS" |
IP 被明确授权 |
正常接收 |
"FAIL" |
IP 不在授权列表中(-all 或 -ip4 匹配失败) |
建议拒收 |
"SOFTFAIL" |
软失败(~all),IP 可能未授权但无法确定 |
接受但标记/放入垃圾箱 |
"NEUTRAL" |
策略判定为中立(?all) |
按常规流程处理 |
"NONE" |
域名无 SPF 记录或记录无法解析 |
建议拒收 |
"TEMPERROR" |
DNS 临时错误(超时、SERVFAIL 等) |
可能是 DNS 抖动,稍后重试,暂不拒收 |
"PERMERROR" |
SPF 记录存在永久错误(语法错误、多重记录等) |
配置异常,建议拒收 |
DNS Golang
2025-01-18
- https://github.com/mr-karan/doggo
- https://doggo.mrkaran.dev/docs/
安装
$ go install github.com/mr-karan/doggo/cmd/doggo@latest
$ doggo
NAME:
doggo 🐶 DNS Client for Humans
USAGE:
doggo [--] [query options] [arguments...]
VERSION:
unknown - unknown
EXAMPLES:
doggo mrkaran.dev Query a domain using defaults.
doggo mrkaran.dev CNAME Query for a CNAME record.
doggo mrkaran.dev MX @9.9.9.9 Uses a custom DNS resolver.
doggo -q mrkaran.dev -t MX -n 1.1.1.1 Using named arguments.
doggo mrkaran.dev --aa --ad Query with Authoritative Answer and Authenticated Data flags set.
doggo mrkaran.dev --cd --do Query with Checking Disabled and DNSSEC OK flags set.
doggo mrkaran.dev --gp-from Germany Query using Globalping API from a specific location.
FREE FORM ARGUMENTS:
Supply hostnames, query types, and classes without flags. Example:
doggo mrkaran.dev A @1.1.1.1
TRANSPORT OPTIONS:
Specify the protocol with a URL-type scheme.
UDP is used if no scheme is specified.
@udp:// eg: @1.1.1.1 initiates a UDP query to 1.1.1.1:53.
@tcp:// eg: @tcp://1.1.1.1 initiates a TCP query to 1.1.1.1:53.
@https:// eg: @https://cloudflare-dns.com/dns-query initiates a DOH query to Cloudflare via DoH.
@tls:// eg: @tls://1.1.1.1 initiates a DoT query to 1.1.1.1:853.
@sdns:// initiates a DNSCrypt or DoH query using a DNS stamp.
@quic:// initiates a DOQ query.
SUBCOMMANDS:
completions [bash|zsh|fish] Generate the shell completion script for the specified shell.
QUERY OPTIONS:
-q, --query=HOSTNAME Hostname to query the DNS records for (eg mrkaran.dev).
-t, --type=TYPE Type of the DNS Record (A, MX, NS etc).
-n, --nameserver=ADDR Address of a specific nameserver to send queries to (9.9.9.9, 8.8.8.8 etc).
-c, --class=CLASS Network class of the DNS record (IN, CH, HS etc).
-x, --reverse Performs a DNS Lookup for an IPv4 or IPv6 address. Sets the query type and class to PTR and IN respectively.
--any Query all supported DNS record types (A, AAAA, CNAME, MX, NS, PTR, SOA, SRV, TXT, CAA).
RESOLVER OPTIONS:
--strategy=STRATEGY Specify strategy to query nameserver listed in etc/resolv.conf. (all, random, first).
--ndots=INT Specify ndots parameter. Takes value from /etc/resolv.conf if using the system namesever or 1 otherwise.
--search Use the search list defined in resolv.conf. Defaults to true. Set --search=false to disable search list.
--timeout=DURATION Specify timeout for the resolver to return a response (e.g., 5s, 400ms, 1m).
-4, --ipv4 Use IPv4 only.
-6, --ipv6 Use IPv6 only.
--tls-hostname=HOSTNAME Provide a hostname for verification of the certificate if the provided DoT nameserver is an IP.
--skip-hostname-verification Skip TLS Hostname Verification in case of DOT Lookups.
QUERY FLAGS:
--aa Set Authoritative Answer flag.
--ad Set Authenticated Data flag.
--cd Set Checking Disabled flag.
--rd Set Recursion Desired flag (default: true).
--z Set Z flag (reserved for future use).
--do Set DNSSEC OK flag.
OUTPUT OPTIONS:
-J, --json Format the output as JSON.
--short Short output format. Shows only the response section.
--color Defaults to true. Set --color=false to disable colored output.
--debug Enable debug logging.
--time Shows how long the response took from the server.
GLOBALPING OPTIONS:
--gp-from=Germany Query using Globalping API from a specific location.
--gp-limit=INT Limit the number of probes to use from Globalping.
DNS 查询
$ doggo sendcloud.net a @223.5.5.5
NAME TYPE CLASS TTL ADDRESS NAMESERVER
sendcloud.net. A IN 60s 106.75.106.173 223.5.5.5:53
sendcloud.net. A IN 60s 106.75.106.166 223.5.5.5:53
$ doggo sendcloud.net a @223.5.5.5 --json
{
"responses": [
{
"answers": [
{
"name": "sendcloud.net.",
"type": "A",
"class": "IN",
"ttl": "60s",
"address": "106.75.106.173",
"status": "",
"rtt": "67ms",
"nameserver": "223.5.5.5:53"
},
{
"name": "sendcloud.net.",
"type": "A",
"class": "IN",
"ttl": "60s",
"address": "106.75.106.166",
"status": "",
"rtt": "67ms",
"nameserver": "223.5.5.5:53"
}
],
"authorities": null,
"questions": [
{
"name": "sendcloud.net.",
"type": "A",
"class": "IN"
}
]
}
]
}
$ doggo sendcloud.net a @223.5.5.5 --json | jq ".responses[].answers[].address"
"106.75.106.166"
"106.75.106.173"
查反解
$ doggo --reverse 101.44.172.1 @223.5.5.5
NAME TYPE CLASS TTL ADDRESS NAMESERVER
1.172.44.101.in-addr.arpa. PTR IN 300s hwsg1c1.email.engagelab.com. 223.5.5.5:53
$ doggo hwsg1c1.email.engagelab.com. a @223.5.5.5
NAME TYPE CLASS TTL ADDRESS NAMESERVER
hwsg1c1.email.engagelab.com. A IN 600s 101.44.172.1 223.5.5.5:53
Global Ping
$ doggo markjour.com --gp-from Germany,Japan --gp-limit 2
LOCATION NAME TYPE CLASS TTL ADDRESS NAMESERVER
Falkenstein, DE, EU, Hetzner
Online GmbH (AS24940)
markjour.com. A IN 600s 121.42.82.115 private
Osaka, JP, AS, Oracle
Corporation (AS31898)
markjour.com. A IN 600s 121.42.82.115 8.8.8.8
Golang DB
2025-01-13
Go 标准库已经提供了 database/sql,为什么生产环境还要专门配置连接池?因为 sql.DB 本身就是连接池,而它的默认配置只是“能工作”,并不意味着适合所有 Web 服务。