海量轨迹数据存储与分区优化
海量轨迹数据存储与分区优化 轨迹数据是典型的”海量时序 + 空间”双重特征的数据,存储设计不好,查询直接卡死。本文复盘一次轨迹数据存储方案的演进。 轨迹数据的特征以巡检人员定位为例,每个终端每隔几秒上报一个 GPS 点,一天下来就是海量数据: 单终端:约 2 万点/天 数百终端:数百万点/天 一年:上亿条 轨迹点有三个维度的特征:时间(何时)、空间(在哪)、对象(谁的)。查询场景也围绕这三者展开。 演进一:单表裸奔最初所有轨迹点堆在一张表里,按时间倒序查最近轨迹还算快(走时间索引)。但一旦查”某个时间段 + 某个区域”的历史轨迹,就要在几千万行里扫描,查询直接超时。 问题本质:单表没有利用时间这个天然的切分维度。 演进二:按时间分区轨迹数据最有效的优化是按时间分区。PostgreSQL 原生支持分区表: 12345678910CREATE TABLE track_points ( id BIGSERIAL, device_id VARCHAR(32), geom GEOMETRY(Point, 4326), ts TIMESTAM...
PostgreSQL + PostGIS 地理数据实践
PostgreSQL + PostGIS 地理数据实践 当业务里出现”点、线、面、轨迹、空间关系”这些概念时,PostGIS 是 PostgreSQL 最有力的扩展。本文梳理它的核心用法。 为什么不用 MySQL 存地理数据MySQL 也有空间类型,但能力远弱于 PostGIS。PostGIS 提供: 完整的几何类型(点、线、面、多点、集合) 丰富的空间函数(距离、包含、相交、缓冲区) 空间索引(GiST),让空间查询走索引 对 GIS 相关业务,PostgreSQL + PostGIS 是事实上的标准组合。 核心数据类型12345678-- 点SELECT ST_GeomFromText('POINT(121.4737 31.2304)', 4326);-- 线(轨迹)SELECT ST_GeomFromText('LINESTRING(121.47 31.23, 121.48 31.24)', 4326);-- 面(区域)SELECT ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 ...
K3s 轻量级集群搭建与踩坑
K3s 轻量级集群搭建与踩坑 对很多中小规模团队来说,完整 Kubernetes 太重,K3s 是更现实的选择。本文记录我在政企内网环境搭建 K3s 集群并承载生产服务的完整过程。 为什么选 K3s我们面临的环境是:内网隔离、机器资源有限(几台普通服务器)、没有专职 SRE。在这种条件下: 完整 K8s 的 etcd、控制面组件开销大、维护成本高 K3s 把控制面打包成单个二进制,默认用 SQLite 存数据,资源占用极低 更重要的是,K3s 保留了标准 Kubernetes API,业务上的编排能力一点不少。 搭建过程安装本身非常简单: 12345# 主节点curl -sfL https://get.k3s.io | sh -# 查看节点 token,供 worker 加入cat /var/lib/rancher/k3s/server/node-token worker 节点加入: 12curl -sfL https://get.k3s.io | K3S_URL=https://<master-ip>:6443 \ K3S_TOKEN=<node-t...
MySQL 索引优化实战
MySQL 索引优化实战 索引是 MySQL 性能优化的第一抓手。本文系统梳理索引原理与实战经验。 索引基础B+ Tree 索引InnoDB 默认索引结构是 B+ Tree: 叶子节点存储数据(聚簇索引)或主键(二级索引) 非叶子节点只存储键值和指针 叶子节点之间通过链表相连,适合范围查询 聚簇索引 vs 二级索引 聚簇索引:叶子节点存完整行数据(每张表只有一个) 二级索引:叶子节点存主键值,需要回表查询 索引失效的常见情况 SQL 写法 是否走索引 WHERE col + 1 = 5 ❌ 函数失效 WHERE col LIKE '%abc' ❌ 前导模糊 WHERE col IS NULL ⚠️ 取决于数据分布 WHERE col IN (...) ✅ 命中索引 WHERE col = 'a' AND col2 = 'b' ✅ 联合索引 索引设计原则 高频查询字段建索引 区分度高的字段建索引(如 user_id,避免在 gender 上建索引) 联合索引注意顺序:最左前缀原则 不要过度索引:写...
一个定时任务从 14 分钟优化到 2 分钟
一个定时任务从 14 分钟优化到 2 分钟 “我会 Spring Boot”很普通,”我解决过一个线上任务内存 15G+、耗时十几分钟的问题”才有价值。这篇文章复盘一次真实的性能优化。 问题现象一个每天凌晨跑的数据同步任务,负责把外部系统的数据拉取、清洗、写入本地库。最初数据量小没感觉,随着数据积累到几百万条,问题集中爆发: 执行时间从 2 分钟一路涨到 14 分钟 内存峰值 15G+,频繁触发 Full GC 偶尔直接把服务 OOM 打挂 排查过程第一步:定位耗时点在任务里埋了几个计时点,发现 90% 的时间都花在”写入数据库”这一步,而不是拉取或清洗。 第二步:找到内存杀手用 JVM 的堆 dump 分析,发现内存里堆了 几十万个待写入的对象。原因是一开始的写法是”查一条、改一条、写一条”,后来为了”提速”改成了”全部加载进内存、最后一次性批量写”——结果把内存打爆了。 第三步:分析根因真正的根因是没有分批(batching)+ 逐条 UPDATE 没有走批量。数据量大之后,逐条执行的开销是指数级增长的。 优化方案1. 流式处理 + 分批不再把所有数据 load 进内存...
整洁代码与代码审查:写出别人愿意读的代码
有个说法我很认同: 代码是写给人读的,只是顺便让机器执行。 代码的正确性当然重要,但如果一段代码除了作者谁都读不懂,那它就是个”定时炸弹”——三个月后连作者自己都读不懂了。 这一篇是”基础系列”的收尾,聊一个软性但极重要的话题:怎么写出整洁、可维护的代码。 命名:代码可读性的第一道关命名的目标是:让人不读注释,光看名字就知道是什么意思。 几个原则: 用能表达意图的名字:d 不如 elapsedDays,process() 不如 calculateOrderTotal() 避免缩写:usrNm 不如 userName 布尔值用疑问句:isValid、hasPermission、isEmpty 方法名用动词:getUser、createOrder、validateInput 一个反例: 1int d; // 天数 一个正例: 1int elapsedDays; 好名字是免费的文档,不要吝啬。 函数:小而专一一个函数应该只做一件事,并且要短。 判断标准: 函数是否小到”一眼能看完”?理想是 20 行以内 函数是否只做了一件事?如果描述它需要”和”字,就该拆 函数的嵌套层级...
并发编程基础:线程、锁与内存可见性
如果说单线程编程是”确定性的世界”,那并发编程就是”不确定性的世界”。 多线程 bug 之所以难查,是因为它不是每次都复现——可能跑了 100 次都正常,第 101 次突然出错。 要驾驭并发,得先理解它为什么难。 并发问题的三大根源1. 原子性:一个操作被”打断”比如 count++,看起来是一步,实际上是三步: 读取 count 的值 加 1 写回 count 两个线程同时执行 count++,可能都读到 0,都加 1,都写回 1,结果本应是 2,却变成了 1。 这就是原子性问题:多个步骤的操作,在执行过程中被别的线程插入了。 解决:用锁(synchronized、Lock)或原子类(AtomicInteger)保证操作不可分割。 2. 可见性:改了但别人看不到每个线程都有自己的工作内存(缓存),变量修改可能先改在缓存里,没有及时同步到主内存。 结果就是:线程 A 改了变量,线程 B 却看不到,还在读旧值。 这就是可见性问题。解决:用 volatile 关键字,保证变量修改立刻对其他线程可见。 volatile 的作用是:保证可见性 + 禁止指令重排序。注意它不保证原子性,...
企业级微服务基础平台设计
企业级微服务基础平台设计 当公司同时有几十个系统在跑,每个系统都自己实现一遍登录、权限、菜单、数据同步,这就是巨大的浪费。基础平台的价值,是把这些重复建设收敛成一套统一能力。 为什么要做统一技术底座接手基础研发后我面对的现实是:公司内部巡检、安全、应急、设备、GIS、气量等业务系统,加上 ERP、客服、营收等外部系统,各自为战。同样的认证逻辑被写了十几遍,安全策略不统一,新项目交付周期长。 统一底座要解决三个核心问题:重复建设、安全不统一、交付慢。 通用能力的抽象经过梳理,我们把所有系统的共性能力抽象成下面几块: 能力 解决什么问题 统一认证 一个账号登录所有系统(SSO) RBAC 权限 统一的用户-角色-资源授权模型 多租户 不同客户/组织数据隔离 菜单集成 各系统菜单统一编排到门户 主数据同步 用户、组织等基础数据一处维护、多处使用 文件服务 统一的文件上传、存储、访问控制 消息通信 系统间异步消息与轨迹数据通道 定时任务 统一的任务调度与监控 这些能力每一个单独看都不难,难的是把它们收敛成一套稳定、可复用、可运维的体...
