فهرست منبع

docs(influx_study): 添加QuestDB学习Demo的README和示例代码

添加README文档说明QuestDB与InfluxDB兼容性的学习Demo,包含环境要求、快速开始指南和代码说明
同时添加questdb_demo.py示例脚本,演示通过InfluxDB行协议写入数据和REST API查询数据的功能
skyfffire 1 هفته پیش
والد
کامیت
ebaddd1cd9
2فایلهای تغییر یافته به همراه203 افزوده شده و 0 حذف شده
  1. 81 0
      src/influx_study/README.md
  2. 122 0
      src/influx_study/questdb_demo.py

+ 81 - 0
src/influx_study/README.md

@@ -0,0 +1,81 @@
+# QuestDB (InfluxDB 兼容) 学习 Demo
+
+本项目旨在演示如何通过 Python 与 QuestDB 数据库进行交互。QuestDB 为了方便用户从 InfluxDB 迁移,其 `9000` 端口同时支持 InfluxDB 行协议(Line Protocol)写入和其自身的 REST API 查询。
+
+本 Demo 将展示:
+1.  如何使用 **InfluxDB 行协议**将时序数据写入 QuestDB。
+2.  如何使用 **QuestDB REST API** 通过标准 SQL 查询数据。
+
+## 环境要求
+
+- Python 3.x
+- `requests` 库
+- Docker
+
+## 快速开始
+
+### 1. 启动 QuestDB 实例
+
+首先,请确保您已通过 Docker 启动了 QuestDB。如果您尚未启动,请运行以下命令:
+
+```bash
+docker run -p 9000:9000 questdb/questdb:9.1.0
+```
+
+此命令将在本地的 `9000` 端口上启动 QuestDB。您可以通过访问 `http://localhost:9000` 来打开 QuestDB 的 Web 控制台。
+
+### 2. 安装依赖
+
+本 Demo 需要 `requests` 库来发送 HTTP 请求。请通过 pip 安装:
+
+```bash
+pip install requests
+```
+
+如果您项目的 `requirements.txt` 中已包含 `requests`,请确保已通过 `pip install -r requirements.txt` 安装。
+
+### 3. 运行 Demo 脚本
+
+进入 `src/influx_study` 目录的父级目录(即 `src` 目录),然后运行 `questdb_demo.py` 脚本:
+
+```bash
+python -m influx_study.questdb_demo
+```
+
+或者,如果您在项目根目录,可以运行:
+
+```bash
+python src/influx_study/questdb_demo.py
+```
+
+### 4. 查看输出
+
+脚本运行时,您将在控制台看到类似以下的日志输出:
+
+```
+2023-10-27 14:30:00 - INFO - --- QuestDB (InfluxDB 兼容) Demo 开始 ---
+2023-10-27 14:30:00 - INFO - 准备写入数据: sensors,location=room1 temperature=23.5,humidity=45.2 1698388200123456789
+2023-10-27 14:30:00 - INFO - 数据写入成功。
+2023-10-27 14:30:01 - INFO - 准备执行查询: SELECT * FROM 'sensors' ORDER BY timestamp DESC LIMIT 10
+2023-10-27 14:30:01 - INFO - 数据查询成功。
+2023-10-27 14:30:01 - INFO - 查询结果:
+location | temperature | humidity | timestamp
+---------------------------------------------
+room1 | 23.5 | 45.2 | 2023-10-27 14:30:00
+2023-10-27 14:30:01 - INFO - --- Demo 结束 ---
+```
+
+这表明数据已成功写入并被查询出来。
+
+## 代码说明
+
+- **`questdb_demo.py`**: 核心脚本文件。
+  - **`write_sensor_data()`**: 构造一条符合 InfluxDB 行协议的数据,并通过 HTTP POST 请求发送到 QuestDB 的 `/write` 端点。
+  - **`query_sensor_data()`**: 构造一条标准 SQL 查询语句,并通过 HTTP GET 请求发送到 QuestDB 的 `/exec` 端点,然后解析并打印返回的 JSON 结果。
+  - **`main()`**: 调用写入和查询函数,演示完整的交互流程。
+
+## 注意事项
+
+- **端口**:请确保 `questdb_demo.py` 中配置的 `QUESTDB_INFLUX_PORT` 和 `QUESTDB_REST_PORT` 与您 Docker 启动时映射的端口一致(默认为 `9000`)。
+- **表结构**:QuestDB 会在第一次收到某个表的行协议数据时自动创建该表。`TAG` 类型的列(如 `location`)会被自动索引,而 `FIELD` 类型的列(如 `temperature`)则不会。
+- **时间戳**:写入时必须提供纳秒级的时间戳。

+ 122 - 0
src/influx_study/questdb_demo.py

@@ -0,0 +1,122 @@
+import requests
+import time
+import logging
+from datetime import datetime, timezone
+
+# --- 配置 ---
+# QuestDB 实例的连接参数
+# 由于您使用 docker run -p 9000:9000 启动,因此 REST API 和 InfluxDB Line Protocol 都在 9000 端口
+QUESTDB_HOST = "127.0.0.1"
+QUESTDB_INFLUX_PORT = 9000
+QUESTDB_REST_PORT = 9000
+TABLE_NAME = "sensors"
+
+# InfluxDB 行协议写入的 URL
+INFLUX_URL = f"http://{QUESTDB_HOST}:{QUESTDB_INFLUX_PORT}/write"
+# QuestDB REST API 查询的 URL
+REST_URL = f"http://{QUESTDB_HOST}:{QUESTDB_REST_PORT}/exec"
+
+# 配置日志记录
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
+
+def write_sensor_data():
+    """
+    使用 InfluxDB 行协议向 QuestDB 写入单条数据。
+    行协议格式: <table_name>,<tag_key>=<tag_value> <field_key>=<field_value> <timestamp_ns>
+    - table_name: 表名 (sensors)
+    - tag: 索引列,用于快速过滤 (location)
+    - field: 数据列 (temperature, humidity)
+    - timestamp: 时间戳 (纳秒)
+    """
+    location = "room1"
+    temperature = 23.5
+    humidity = 45.2
+    
+    # 获取当前的 UTC 时间戳(纳秒)
+    timestamp_ns = int(time.time_ns())
+    
+    # 构建 InfluxDB 行协议字符串
+    # 注意:tag 的值不需要引号,field 的浮点数值会自动处理
+    line_protocol = f"{TABLE_NAME},location={location} temperature={temperature},humidity={humidity} {timestamp_ns}"
+    
+    logging.info(f"准备写入数据: {line_protocol}")
+    
+    try:
+        # 发送 POST 请求写入数据
+        response = requests.post(INFLUX_URL, data=line_protocol.encode('utf-8'), timeout=5)
+        
+        # 检查响应状态码
+        if response.status_code == 204:
+            logging.info("数据写入成功。")
+        else:
+            logging.error(f"数据写入失败。状态码: {response.status_code}, 响应: {response.text}")
+            
+    except requests.exceptions.RequestException as e:
+        logging.error(f"写入数据时发生网络错误: {e}")
+
+def query_sensor_data():
+    """
+    使用 QuestDB 的 REST API 执行 SQL 查询。
+    """
+    # 构建 SQL 查询语句
+    # QuestDB 使用标准 SQL
+    query = f"SELECT * FROM '{TABLE_NAME}' ORDER BY timestamp DESC LIMIT 10"
+    
+    logging.info(f"准备执行查询: {query}")
+    
+    params = {'query': query}
+    
+    try:
+        # 发送 GET 请求执行查询
+        response = requests.get(REST_URL, params=params, timeout=5)
+        
+        # 检查响应状态码
+        if response.status_code == 200:
+            logging.info("数据查询成功。")
+            result = response.json()
+            
+            # 格式化并打印查询结果
+            logging.info("查询结果:")
+            if result and result.get('dataset'):
+                # 从 'columns' 列表中提取表头名称
+                headers = [col.get('name', '') for col in result.get('columns', [])]
+                rows = result.get('dataset', [])
+            
+                # 打印表头
+                if headers:
+                    print(f"{' | '.join(headers)}")
+                    print("-" * (len(' | '.join(headers))))
+            
+                # 打印数据行
+                for row in rows:
+                    # 将时间戳转换为可读格式
+                    row[headers.index('timestamp')] = datetime.fromisoformat(row[headers.index('timestamp')].replace('Z', '+00:00')).strftime('%Y-%m-%d %H:%M:%S')
+                    print(f"{' | '.join(map(str, row))}")
+            else:
+                logging.warning("查询成功,但未返回任何数据。")
+                
+        else:
+            logging.error(f"数据查询失败。状态码: {response.status_code}, 响应: {response.text}")
+
+    except requests.exceptions.RequestException as e:
+        logging.error(f"查询数据时发生网络错误: {e}")
+
+def main():
+    """
+    主函数,演示如何调用写入和查询功能。
+    """
+    logging.info("--- QuestDB (InfluxDB 兼容) Demo 开始 ---")
+    
+    # 1. 写入数据
+    write_sensor_data()
+    
+    # 等待一秒,确保数据已被处理
+    time.sleep(1)
+    
+    # 2. 查询数据
+    query_sensor_data()
+    
+    logging.info("--- Demo 结束 ---")
+
+if __name__ == "__main__":
+    main()