From Python to Go

From Python to Go: Why We Rewrote Our Ingest Pipeline at Telemetry Harbor 我们将 Telemetry Harbor 的摄取管道从 Python FastAPI 重写为 Go,原因是遇到了严重的性能瓶颈。迁移后,效率提升了 10 倍,数据完整性因严格类型检查而得到加强,系统也拥有了稳定、可扩展的高并发时间序列数据摄取基础。 背景:打造一个时间序列数据平台 Telemetry Harbor 源自我们在汽车行业积累的经验。几乎每个项目都要重复搭建相同的基础设施:数据库、后端、数据摄取管道、可视化界面。每次都要花费数周时间,这让我们萌生了打造一个开箱即用平台的想法。 当时的市场方案并不理想。InfluxDB 的商业化策略让许多关键特性被锁在付费墙后,版本迁移成本高且在大数据负载下表现不佳。TimescaleDB 与 ClickHouse 技术上更强大,但依旧需要用户自行构建后端与摄取管道。我们看到了缺口——需要一个极简、可靠、可直接使用的平台。 Python FastAPI:原型开发的正确选择 MVP 阶段,我们在开发速度与运行性能之间权衡。最终选择了 Python FastAPI,因为它允许我们: 快速验证市场假设 迅速收集客户反馈并迭代 在低成本下尝试多种方案 尽快上线以抢占市场 早期架构非常直接:HTTP API(避免防火墙问题)、Redis + RQ 队列、TimescaleDB。测试效果良好,但很快暴露了性能隐患——RQ 的同步处理方式无法支撑高吞吐场景。 性能瓶颈:Python 无法跟上增长 随着数据量上升,性能问题逐渐浮现: 空闲 CPU 占用:10% 中等负载:约 40% CPU 高负载:120–300% CPU(峰值 800%),频繁崩溃 问题不仅在于 RQ 的同步限制,而是整个 Python 架构在常规负载下都难以维持稳定。这迫使我们考虑全面重写。 迁移决策:为什么选择 Go? 我们评估了 Rust 和 Go: ...

August 27, 2025 · 1 min · 148 words · Starslayerx

Intorduce UUID

UUID(Universally Unique Identifier, 通用唯一标识符) 是一种标准化的128位标识符, 用于在分布式系统中生成几乎不会重复的唯一 ID. 最早于 IETF 制定为 RFC 4122 标准, 保证在不同机器、不同时间生成的 ID 也能保持全局唯一. UUID 通常以16进制表示, 采用5段结构, 用连字符 - 分隔, 例如: 550e8400-e29b-41d4-a716-446655440000 有如下特点: 全局唯一 无中心依赖 不可预测 跨平台通用 UUID 有以下不同版本: 版本 生成 特点 v1 基于时间戳 + MAC 地址 按时间排序,含生成设备信息 v3 基于命名空间的 MD5 哈希 输入相同则输出相同(MD5 已不再安全) v4 基于操作系统的随机数生成 完全随机, 最常用 v5 基于命名空间的 SHA-1 哈希 与 v3 类似, 但使用 SHA-1 v6~v8 现代版本(草案) 提高排序性能和隐私保护 其中, 对于需要时间有序的使用 v1, 大多数通用场景使用 v4 UUID 的应用场景 数据库主键(分布式环境避免冲突) 会话标识(Session ID、Token) 文件命名(防止重名) 分布式系统节点 ID 追踪请求链路(Trace ID) 示例代码 ...

August 16, 2025 · 1 min · 138 words · Starslayerx

Python Generics

本篇文件介绍 Python 中的 泛型(Generics) Intro 在没有泛型的情况下, 会遇上以下几个问题: 难以表达意图 假设你编写了一个函数, 它接受一个列表, 并返回列表中的第一个元素. 在不使用类型提示的情况下, 这个函数可以处理任何类型的列表, 但我们无法在函数签名中表达"返回的元素的类型与列表中的元素类型相同"这个意图 def get_first_element(items): return items[0] 丧失类型信息 如果使用类型提示, 可能会像下面这样写, 但这样会丢失类型信息. list[Any] 表示可以接收任何类型的列表, 但 -> Any 意味着不知道返回的元素类型是什么, 这使得 mypy 等静态类型检测工具无法追踪类型, 降低了代码的可读性和安全性 from typing import Any def get_first_element(items: list[Any]) -> Any: return items[0] # 调用时, 类型检查工具无法得知 first_str 的类型 first_str = get_first_element(["hello", "world"]) 代码重复 如果为每种可能的类型都编写一个单独的函数, 则会导致代码重复 def get_first_int(items: list[int]) -> int: return items[0] def get_first_str(items: list[str]) -> str: return items[0] 通过引入 类型变量 (TypeVar) 来解决问题, 类型变量就像一个占位符, 代表在未来某时刻会被具体指定的类型 from typing import TypeVar T = TypeVar("T") def get_first_element(items: list[T]) -> T: return items[0] # 现在, 类型检查工具可以正确推断出类型 first_str: str = get_first_element(["hello", "world"]) first_int: int = get_first_element([1, 2, 3]) T = TypeVar('T') 定义了一个名为 T 的类型变量, 这里 T 只是一个约定俗成的名字, 也可以使用其他字母 items: list[T] 表示 items 是一个列表, 其内部元素类型是 T -> T: 返回类型也是 T 当使用 ["hello", "world"] 调用函数时, 静态类型检查器会推断出 T 是 str, 返回类型为 str 当使用 [1, 2, 3] 调用函数时, T 被推断为 int 注意: 这个函数假设列表非空, 如果传入空列表会抛出 IndexError ...

August 14, 2025 · 3 min · 553 words · Starslayerx

Python Strings

这篇文章总结一下 Python 中字符串的类型 Unicode String 字符串 u 在 Python3 中是多余的, 因为所有的普通字符串默认都是 Unicode, 但在 Python2 中, u 用来显示的表示 Unicode 字符串, 现在保留这个是为了向后兼容 Fromatted String 格式化字符串 f 前缀用于创建格式化字符串, 这是最常见的字符串格式方法, 运行在字符串中嵌入表达式, 在求值时转换为普通的 str name = "World" greeting = f"Hello, {name}!" # 结果: "Hello, World!" Raw String 原始字符串 r 前缀用于创建原始字符串, 会忽略反斜杠 \ 的转义功能, 在编写文件路径或正则表达式的时候非常有用, 可以避免大量的反斜杠转义 path = r"C:\Users\Documents" # 单个反斜杠 ' regex = r"\bword\b" # \b 不会被转义 Bytes String 字节串 b 前缀用于创建字节串字面量, 表示一个不可变的字节序列, 而不是 Unicode 文本, 字节串主要用于二进制数据, 例如图像文件、网络数据或压缩文件等 binary_data = b"Hello" # 存储的是 ASCII 编码的字节 Template String 模板字符串 t 前缀用于创建模板字符串, 这是 Python 3.14 引入的新功能, 由 PEP 750 通过. ...

August 13, 2025 · 1 min · 151 words · Starslayerx

Python Function Parameters

今天是周日, 简单写点吧, 简单总结一下 Python 中函数参数 Python Function Parameters Python 函数参数机制非常灵活丰富, 理解各种参数类型及其用法对于写出优雅、易维护的代码非常重要. 本文将介绍 Python 中函数参数的种类与用法, 并详细讲解 Python 3.8 引入的参数分隔符 / 和 *, 帮助你更好地设计函数接口. 1. Postional Arguments 位置参数 函数定义中最常见的参数, 调用时按顺序传入值 def greet(name, age): print(f"Hello, {name}. You are {age} years old.") greet("Alice", 30) # Hello, Alice. You are 30 years old. 2. Keyword Arguments 关键字参数 调用时以 key=value 形式传入, 顺序可变 greet(age=30, name="Alice") 3. Default Arguments 默认参数 定义函数时给参数赋默认值, 调用时可省略 def greet(name, age=20): print(f"Hello, {name}. You are {age} years old.") greet("Bob") # 使用默认年龄20 greet("Bob", 25) # 指定年龄 注意: 使用默认参数尽量不要使用可变类型(mutable), 例如列表, 因为默认参数是存储在函数中的, 而非函数实例中, 多次调用会改变默认值的内容. def greet(names: list[str] = ["Alice", "Bob"]): ... 若希望使用默认值, 建议使用下面这种方法 ...

August 10, 2025 · 2 min · 278 words · Starslayerx

Python Tricks

1. The Self-Replicating Trick 将一个含有空列表的列表乘5, 得到有5个空列表的列表 x = [[]] * 5 x [[], [], [], [], []] 当使用.append("x")方法时, 所有列表都被修改 x[0].append("x") x [[“x”], [“x”], [“x”], [“x”], [“x”]] 打印其 id 可以看到, id 都相同 for item in x: print(id(item)) 4417579584 4417579584 4417579584 4417579584 4417579584 或者使用set()发现 id 唯一 set(id(item) for item in x) {4417579584} 也就是说, 当使用乘法的时候, 创建了5个内部列表的引用副本 使用反汇编发现, 只创建了两个列表, 并执行乘5 dis.dis("[[]] * 5") 0 0 RESUME 0 # 用于支持解释器恢复 (py3.11) 1 2 BUILD_LIST 0 # 构造一个空列表[], 压栈 4 BUILD_LIST 1 # 从栈顶取一个对象, 构造列表[[]] 6 LOAD_CONST 0 (5) # 加载常量 5 8 BINARY_OP 5 (*) # 对栈顶两个元素执行乘法 12 RETURN_VALUE # 返回栈顶结果 The alternative 如果要构造独立列表, 应改用列表推导式 ...

August 5, 2025 · 3 min · 432 words · Starslayerx

Executing arbitrary Python code from a comment

通过注释执行任意Python代码 问题描述 Q: 只能控制一行的.py代码中注释的内容(\n\r均会被替换为空字符), 如何执行任意代码? A: 在注释#中, 构造一个.zip 文件, python 会将该内容当成一个 zip 包执行, 触发任意代码执行 解决方案 从 Python 3.5 起, 可以直接执行一个 .zip 文件 python myapp.zip 前提是ZIP 包中包含一个顶层的__main__.py文件, Python 会把它当作 zipapp, 自动解压并运行__main__.py Python 会从末尾找到 ZIP 的目录结构, 而不是依赖文件头, 所以前面的“垃圾”字节会被忽略 Python 源码中的任何行, 只要以 # 开头, 解释器都会忽略后面内容, 因此可以: 把 ZIP 文件的数据藏在 Python 源码中的注释中(开头加 #) 把 ZIP 数据直接拼接在 Python 文件的后面, 只保证文件头部分是合法 Python ZIP 不关心前缀, Python 只要最前面是有效源码, 也不会管后面 难点 ZIP 文件头包含二进制字段,比如 偏移量(文件数据相对于 ZIP 开头的位置) 长度(文件名长度、注释长度等) 这些值写死在 header 里, 是十六进制整数 如果这些字节中出现了像 \x00、\xFF 等非 ASCII 内容, Python 就不能把它当注释 解决方法: 暴力穷举合法组合 ...

August 4, 2025 · 3 min · 563 words · Starslayerx