时序数据库在物联网日志系统中的设计与实践

时序数据库在物联网日志系统中的设计与实践:从数据结构到SpringBoot接入 一、一张表撑不住了 📌 前置知识:了解关系型数据库(RDBMS)基本概念,知道什么是SQL、索引、事务。知道什么是磁盘IO,理解顺序IO和随机IO的大致性能差距(约一个数量级)。 某开发者接手了一个物联网项目,设备每秒上报一次数据,1000台设备跑了一周,MySQL 的 iot_logs 表已经几千万行,查询一条设备最近一小时的数据要跑十几秒。加了索引,写入又慢得让人抓狂。这种场景,写过的都懂——关系型数据库不是不能存时序数据,而是它的每一个设计决策都和时序场景的需求背道而驰。 物联网日志(IoT Logs)具有几个典型特征: 写入密集且顺序:数据按时间顺序持续产生,极少更新或删除 查询模式固定:通常按时间范围 + 设备ID聚合查询,很少跨设备做复杂JOIN 数据冷热分明:最近几小时的数据被频繁访问,一周前的数据偶尔查一次 压缩空间巨大:传感器数据变化缓慢,相邻时间点数据高度相似,而 RDBMS 的通用压缩算法根本不认识这种模式 这就引出了一个核心问题:时序数据库(TSDB,Time Series Database)到底在数据结构层面做了哪些改造,让它和经典关系型数据库(RDBMS)产生了本质差异?以及如何在 Spring Boot 项目中把这些 TSDB 用起来? 二、数据结构层面的根本差异 2.1 行式存储 vs 列式存储 RDBMS 以 行(Row) 为单位组织数据,一行数据的各个字段在磁盘上连续存放。这种设计让单行读写非常高效——适合 OLTP 场景下"查一行、改一行"的套路。但面对时序查询时,问题就暴露了:查询"过去一小时内所有传感器的温度平均值",只需要温度这一个列,行式存储却会把湿度、气压、设备状态等几十个列一并从磁盘读进内存——IO 利用率奇低。 TSDB 采用 列式存储(Columnar Storage),将同一列的数据在磁盘上连续存放。查询温度列时,只读取温度相关的数据块,其他列完全不参与 IO。这和 ClickHouse 等 OLAP 引擎的思路一致,但 TSDB 在列式基础上又叠加了时间维度的特殊优化。 flowchart TD subgraph row["📦 行式存储 RDBMS"] direction TB r1["Row1│ts:1000│temp:25.3│hum:68│loc:WH1"] --> r2["Row2│ts:1001│temp:25.4│hum:67│loc:WH1"] r2 --> r3["Row3│ts:1002│temp:25.3│hum:68│loc:WH1"] end subgraph col["📦 列式存储 TSDB"] direction TB c_ts["Col_ts: 1000, 1001, 1002"] --> c_temp["Col_temp: 25.3, 25.4, 25.3"] c_temp --> c_hum["Col_hum: 68, 67, 68"] c_hum --> c_loc["Col_loc: WH1, WH1, WH1"] end row -.-> col classDef default fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; class r1,r2,r3,c_ts,c_temp,c_hum,c_loc default class r1,c_temp highlight ⚠️ 新手提示:不要混淆"列式存储的数据库"和"时序数据库"。列式存储是 TSDB 的技术手段之一,但不是全部。ClickHouse 是列式存储的 OLAP 数据库,但它不是 TSDB——它没有时间维度的特殊处理(如自动分区、自动过期删除)。 ...

一月 16, 2023 · 7 分钟 · 1406 字 · yaomingye

七牛云 OSS + SMTP 邮件:两个轻量级第三方接入实战

七牛云 OSS + SMTP 邮件接入 第1步:目标说明 — 图片上传和发邮件,每个项目的标配 电商项目两个常见的外部依赖:图片存储和邮件通知。商品图、用户头像得有个地方存,登录异常告警、注册欢迎得有个通道发。 Mall 项目用七牛云 OSS 存图片和文件(CDN 加速),用 SMTP 发邮件(Freemarker 模板渲染 HTML 正文),两个接入都不复杂,加起来不超过 200 行代码。本教程从申请凭证到代码封装,两套接入一次讲完。 第2步:前置条件 条件 七牛云 OSS SMTP 邮件 账号 qiniu.com 注册,实名认证 任意邮箱服务(163、QQ邮箱、企业邮箱) 凭证 AccessKey / SecretKey 邮箱地址 + SMTP 授权码 存储空间 在七牛云控制台创建 Bucket 无需 域名 Bucket 绑定 CDN 加速域名 无需 ⚠️ 新手提示:七牛云 OSS 和阿里云 OSS 是竞品,功能几乎一样。Mall 项目选了七牛云,如果公司已经在用阿里云 OSS,代码结构完全能用,换 SDK 即可。接入模式是通用的。 第3步:环境搭建 — 七牛云 OSS 添加 Maven 依赖 <dependency> <groupId>com.qiniu</groupId> <artifactId>qiniu-java-sdk</artifactId> <version>7.4.0</version> </dependency> 配置属性类 @Component @ConfigurationProperties(prefix = "oss.qiniu") @Data public class QiNiuConfig { private String accessKey; // 七牛云 AK private String secretKey; // 七牛云 SK private String bucketPictureName; // 图片 Bucket 名称 private String domainPicture; // 图片 CDN 域名 private String bucketFileName; // 文件 Bucket 名称 private String domainFile; // 文件 CDN 域名 } 图片和文件分两个 Bucket——图片需要 CDN 加速 + 图片处理(裁剪、加水印),文件的访问频率低但需要支持大文件下载,分开管理方便设置不同的生命周期策略。 ...

一月 15, 2023 · 4 分钟 · 846 字 · yaomingye

支付宝支付接入:沙箱调试 + EasySDK + QR 码支付实战

支付宝支付接入 第1步:目标说明 — 支付接入最怕的是什么 不是代码复杂,而是"没法在本地调"——每次测试都要真的扫码付钱,退款、对账、异常场景根本模拟不了。 支付宝提供了沙箱环境(sandbox),完全模拟生产接口的行为,但用的是虚拟账户和虚拟资金。开发人员在沙箱里可以反复测试支付、退款、异常场景,不花一分钱。 Mall 项目对接的是支付宝"当面付"(FaceToFace),生成二维码让用户扫码支付。本教程覆盖:沙箱环境申请 → RSA2 密钥配置 → EasySDK 集成 → QR 码生成 → MockPay 开发模式 → 生产切换。 第2步:前置条件 条件 要求 获取方式 支付宝开放平台账号 已注册并实名 open.alipay.com 沙箱环境 已开通(免费) 开放平台 → 控制台 → 沙箱环境 沙箱应用 自动创建 沙箱环境会自动生成一个测试应用 RSA2 密钥对 2048 位 支付宝密钥生成工具 或 openssl genrsa ⚠️ 新手提示:沙箱环境和正式环境是两套完全独立的系统——沙箱的 APPID、网关地址、密钥、支付宝公钥都和正式环境不同。Sandbox 网关是 openapi-sandbox.dl.alipaydev.com,生产网关是 openapi.alipay.com。切换环境不是改一两个配置项,而是整套凭证都得换。 第3步:环境搭建 添加 Maven 依赖 <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-easysdk</artifactId> <version>2.2.0</version> </dependency> EasySDK 是支付宝官方封装的"开箱即用"SDK。老版 SDK alipay-sdk-java 需要手动构造请求参数、手动验签,代码量是 EasySDK 的 3 ~ 5 倍。EasySDK 一个 Factory.Payment.FaceToFace().preCreate() 就完成预下单。 ...

一月 14, 2023 · 4 分钟 · 655 字 · yaomingye

阿里云短信接入:双 Provider + Mock 验证码实战

阿里云短信接入 第1步:目标说明 — 别在生产环境调试短信 发送短信验证码是登录/注册流程的核心环节。但对接阿里云短信服务时有两个现实问题: 2024 年后个人资质基本申请不到官方短信签名和模板,审核周期长还不一定过 开发调试时不可能真发短信,每条几分钱不说,频繁发送会被运营商拦截 Mall 项目从这两个痛点出发,设计了一套"双 Provider + Mock 开关"的短信架构: 生产环境:用 dysmsapi(阿里云官方短信 SDK),需要企业资质 个人测试:用 dypnsapi(阿里云号码验证服务),个人账号可申请 本地开发:Mock 模式跳过真发,固定验证码 123456 目标是把这套架构讲清楚,读者照着做能在 30 分钟内完成短信接入。 第2步:前置条件 条件 要求 验证/获取方式 阿里云账号 已实名认证 aliyun.com 注册 AccessKey 已创建 RAM 用户,获取 AK/SK 阿里云控制台 → RAM 访问控制 → 创建 AccessKey 签名和模板(dysmsapi) 企业资质,审核通过 阿里云短信服务控制台(个人很难申请) 号码验证服务(dypnsapi) 个人账号可开通 阿里云号码验证服务控制台 ⚠️ 新手提示:dysmsapi 和 dypnsapi 是阿里云的两个不同产品。dysmsapi 是传统短信服务,需要申请签名和模板;dypnsapi 是号码验证服务,提供预置的短信模板(验证码、通知等),个人资质就能用。本教程两种都讲,读者根据自己的资质选一种即可。 第3步:环境搭建 添加 Maven 依赖 <!-- 方案1:官方短信 SDK --> <dependency> <groupId>com.aliyun</groupId> <artifactId>alibabacloud-dysmsapi20170525</artifactId> <version>3.0.0</version> </dependency> <!-- 方案2:号码验证服务 SDK(个人可用) --> <dependency> <groupId>com.aliyun</groupId> <artifactId>alibabacloud-dypnsapi20170525</artifactId> <version>1.0.8</version> </dependency> 两个依赖都加也没问题,项目通过 @ConditionalOnProperty 在运行时选一个生效,不会冲突。 ...

一月 13, 2023 · 5 分钟 · 878 字 · yaomingye

Flyway 数据库迁移:告别手工执行 SQL 脚本

Flyway 数据库迁移 第1步:目标说明 — 从 38 个手工 SQL 脚本说起 Mall 商城项目的 README 里有一句坦诚的自我检讨: SQL 脚本丢在 sql/ 目录手工执行,没有 Flyway / Liquibase。无法追踪某台机器跑过哪些 DDL,回滚靠猜。 打开 sql/feature_1.0.1/ 目录一看——38 个 SQL 文件,命名靠日期: create_table_2024_01_05.sql create_table_2024_01_29.sql alter_table_2024_02_27.sql alter_table_2024_05_12.sql alter_table_2024_09_26.sql ... 每次上线,开发人员手动连上数据库,挑出"这次要跑的"脚本,逐个执行。脚本里还夹杂了手工更新历史数据的 DML: use mall_db; alter table mall_product add column `cover_url` varchar(200) DEFAULT NULL COMMENT '封面图片url'; -- 更新历史数据 update mall_product p inner join mall_product_photo m on p.id = m.product_id set p.cover_url = m.url where m.type=1 and m.is_del=0; -- 别忘了还有分库 use mall_db_order_0; alter table order_trade_item_0 add column `cover_url` varchar(200) ... alter table order_trade_item_1 add column `cover_url` varchar(200) ... use mall_db_order_1; alter table order_trade_item_0 add column `cover_url` varchar(200) ... alter table order_trade_item_1 add column `cover_url` varchar(200) ... 这种模式下会发生什么,写过的人都懂: ...

一月 12, 2023 · 5 分钟 · 1029 字 · yaomingye

Knife4j 接口文档从配置到上线

Knife4j 接口文档 第1步:目标说明 — 打造可交互的 API 文档 后端写完接口,前端过来问"这个参数什么意思"“返回字段有哪些"“能不能让我直接调一下看看效果”——这种场景写过的都懂。 Swagger 就是来解决这个问题的。它能根据代码里的注解自动生成接口文档页面,前端直接在页面上看字段说明、调接口、看返回,不用再追着后端问。而 Knife4j 是 Swagger 的增强 UI,比原生 Swagger UI 好看得多,还支持离线文档导出、全局参数设置、接口排序等实用功能。 本教程基于 Mall 商城项目的真实配置,从零开始搭建一套 Knife4j + Swagger 接口文档,目标是让读者看完就能在自己的项目里用起来。 最终效果:访问 Knife4j 页面,能看到按模块分组的接口列表,点开任意接口能看到请求参数、响应示例,还能直接在页面上填入 Authorization 请求头,在线调试接口。 第2步:前置条件 开始之前,先确认项目环境满足以下条件。 条件 要求 验证命令 JDK 1.8+ java -version Maven 3.6+ mvn -v Spring Boot 2.x 查看 pom.xml 中 spring-boot-starter-parent 版本 现有 Spring Boot Web 项目 已有 Controller 项目中存在 @RestController 类 ⚠️ 新手提示:Knife4j 3.0.2 基于 Springfox 3.0.0,兼容 Spring Boot 2.x。如果是 Spring Boot 3.x 项目,需要使用 knife4j-openapi3-spring-boot-starter 4.x 版本,注解包名也从 io.swagger.annotations 变为 io.swagger.v3.oas.annotations,差异较大,本教程不涉及。 ...

一月 11, 2023 · 6 分钟 · 1108 字 · yaomingye

一个商城项目的结构化日志改造实录

结构化日志改造实录 第1步:目标说明 — 结构化日志到底解决什么问题 某开发者接手了一个 Spring Boot 商城项目的维护。项目跑得挺稳,直到某天凌晨收到告警——短信发送失败了,但翻遍日志找不到任何记录,因为 catch(Exception e) 的块是空的。 这就是非结构化日志的典型场景:日志看似写了,但关键信息全丢了。 结构化日志(Structured Logging)不是一门新技术,而是一种日志编写规范。它的核心目标只有一句话: 让日志既可以被人快速理解,也可以被机器(ELK、Loki、Splunk)精确检索。 本次教程通过一个真实商城项目的日志审计和改造过程,教会读者: 如何识别团队代码中的日志反模式 如何用 SLF4J 的参数化语法替代字符串拼接 如何配置 logback 实现 dev 控制台 + prod 文件持久化 的双环境策略 如何避免异常栈丢失、日志级别混乱等常见坑 完成本教程后,读者能独立完成一个 Spring Boot 项目的日志规范化改造。 第2步:前置条件 — 需要准备什么 开始之前,确保本地环境满足以下条件。 前置项 版本要求 说明 JDK 1.8+ Spring Boot 2.x 编译和运行 Spring Boot 2.x 自带 spring-boot-starter-logging(Logback + SLF4J) Lombok 1.18+ 提供 @Slf4j 注解,免去手写 Logger 声明 Maven 3.6+ 项目构建工具 验证命令: # 检查 JDK java -version # 检查 Maven mvn -version # 检查 Lombok 依赖(在 IDE 中确认 @Slf4j 可用) 📌 前置知识:读者需要了解 Java 异常体系的基本概念(checked / unchecked exception)、Spring Boot 项目的基本结构(Controller → Service → Mapper),以及日志级别 TRACE / DEBUG / INFO / WARN / ERROR 的含义。 ...

一月 10, 2023 · 11 分钟 · 2305 字 · yaomingye

第4步:开发者 K8s 全景图 —— Ingress、Helm 和你的职责边界

开发者 K8s 全景图 一、目标说明 前四篇文章把 K8s 的概念地基、YAML 编写、探针配置、kubectl 命令全拆完了。这篇是收网篇——把剩下的重要但散落的知识点串起来,然后画一条清晰的线:什么归你管,什么扔给运维。 读完这篇文章,读者能: 写出完整的 Ingress YAML,理解域名路由规则 用 Helm 安装和管理应用( helm install / upgrade / rollback ) 选择适合自己场景的本地 K8s 环境 知道 StatefulSet、HPA、Job/CronJob、PVC 是干什么的、什么时候需要 认清 Dev vs Ops 的分界线,不再背不该背的锅 二、前置条件 前置条件 要求 理解 Service(ClusterIP/NodePort) 第 0 ~ 1 步已覆盖 会基本的 kubectl 操作 第 3 步已覆盖 了解域名和 HTTP 路径的基本概念 api.example.com/users 这种格式能看懂 三、分步实践 3.1 Ingress —— 域名路由,外部流量的大门 3.1.1 为什么需要 Ingress? Service 的三种类型: Service 类型 外部访问 问题 ClusterIP 不能 只能集群内用 NodePort 能( NodeIP:30000-32767 ) 端口丑、不能基于域名路由,一个端口只能绑一个 Service LoadBalancer 能(云 LB 分配公网 IP) 每个 Service 都要创建一个 LB,烧钱 Ingress 解决的问题:用一个入口(一个 LB / 一个公网 IP),根据域名和路径把流量分发到不同的 Service。 ...

一月 9, 2023 · 8 分钟 · 1621 字 · yaomingye

第3步:kubectl 生存手册 —— 开发者每天必敲的命令

kubectl 生存手册 一、目标说明 前三篇文章把概念、YAML、配置都讲完了。这一篇不讲"是什么",只讲 **“怎么查”**和 “怎么排” 。 这是整个系列最实用的一篇——开发者 90% 跟 K8s 打交道的时间,不是写 YAML,而是在这几个命令之间反复横跳: kubectl get → kubectl describe → kubectl logs → kubectl exec — 然后回到 get 读完这篇文章,读者能: 用 4 个核心查看命令快速定位问题 用 3 个交互命令深入容器内部或桥接流量 掌握 9 种 Pod 异常状态的完整诊断流程 用 -o wide/json/yaml 和 --sort-by 提取关键信息 建立"从现象到根因"的排查肌肉记忆 二、前置条件 前置条件 要求 本地 K8s 环境可用 kubectl cluster-info 正常 有几个 Pod 在跑 前几篇文章的 my-first-app 即可 理解 Pod / Deployment / Service 是什么 至少知道它们是干什么的 三、环境准备 沿用前面的 Namespace,确认有资源在跑: ...

一月 8, 2023 · 11 分钟 · 2166 字 · yaomingye

第2步:让 Pod 活得久一点 —— 探针、资源和配置注入实战

让 Pod 活得久一点 一、目标说明 上一篇文章成功部署了第一个 K8s 应用。但现实是——Pod 不会永远乖乖 Running。第二天打开监控一看:一个 Pod 被 OOMKilled,一个在 CrashLoopBackOff 无限重启,还有一个 Pending 了 3 小时没人管。 这篇文章要解决的就是:怎么让 Pod 活得久、死得明白、配置配得清楚。 读完这篇文章,读者能: 区分三种探针的适用场景,写出正确的探针配置 给容器设置合理的 resources 限制,避免 OOMKilled 和 CPU 被偷 掌握环境变量注入的 3 种方式及其选型标准 用 Volume Mount 把配置文件挂进 Pod 看懂 Pod 最常见的 6 种异常状态及其排查方向 二、前置条件 前置条件 要求 验证命令 已完成第 1 步 本地 K8s 能正常 deploy kubectl get deploy -n my-first-app 理解 Pod 基本概念 知道 Pod 里跑容器 看一眼第 0 步速查表即可 理解 Deployment 基本概念 知道 replicas、selector 看一眼第 1 步 Deployment YAML 即可 三、环境准备 沿用第 1 步的环境,先重新部署一遍做基准: ...

一月 7, 2023 · 8 分钟 · 1551 字 · yaomingye
Cat Radio