diff --git a/deploy/deploy.md b/deploy/deploy.md new file mode 100644 index 0000000..71c252d --- /dev/null +++ b/deploy/deploy.md @@ -0,0 +1,211 @@ +# 项目依赖服务部署指南 + +本项目依赖于 $\text{PostgreSQL}$ 数据库和 $\text{Redis Stack Server}$(包含 $\text{Redisearch}$ 等模块)部署文档将使用 $\text{Docker}$ 容器化技术部署这两个依赖服务 + +## 前提条件 + +1. 已安装 $\text{Docker}$ +2. 下载相关容器镜像 +3. 确保主机的 $\text{5432}$ 端口($\text{Postgres}$)和 $\text{6379}$ 端口($\text{Redis}$)未被占用 + +### 1\. 部署 PostgreSQL 数据库 + +使用官方的 `postgres:13.16` 镜像,并设置默认的用户、密码和端口 + +#### 1.1 部署命令 + +运行以下命令启动 $\text{PostgreSQL}$ 容器 + +```bash +docker run --name postgres \ +-e POSTGRES_USER=postgres \ +-e POSTGRES_PASSWORD=coslight \ +-p 5432:5432 \ +-d postgres:13.16 +``` + +#### 1.2 连接信息 + +| 参数 | 值 | 说明 | +| :--- | :--- | :--- | +| **容器名称** | `postgres` | 容器名 | +| **镜像版本** | `postgres:13.16` | 镜像名 | +| **主机端口** | `5432` | 外部应用连接使用的端口 | +| **用户名** | `postgres` | 默认超级用户 | +| **密码** | `coslight` | 配置的密码 | + +#### 1.3 状态检查 + +要确认容器是否正在运行,请执行 + +```bash +# 检查容器启动状态 +docker ps -a |grep postgres +# 检查容器启动日志信息 +docker logs postgres +``` + +### 2\. 部署 Redis Stack Server + +我们将使用 `redis/redis-stack-server:latest` 镜像该镜像内置了 $\text{Redisearch}$ 模块,用于 $\text{ModelRT}$ 项目中补全功能 + +#### 2.1 部署命令 + +运行以下命令启动 $\text{Redis Stack Server}$ 容器 + +```bash +docker run --name redis -p 6379:6379 \ +-d redis/redis-stack-server:latest +``` + +#### 2.2 连接信息 + +| 参数 | 值 | 说明 | +| :--- | :--- | :--- | +| **容器名称** | `redis` | 容器名 | +| **镜像版本** | `redis/redis-stack-server:latest` | 镜像名 | +| **主机端口** | `6379` | 外部应用连接使用的端口 | +| **地址** | `localhost:6379` | | +| **密码** | **无** | 默认未设置密码 | + +> **注意:** 生产环境中建议使用 `-e REDIS_PASSWORD=` 参数来设置 $\text{Redis}$ 访问密码 + +#### 2.3 状态检查 + +要确认容器是否正在运行,请执行 + +```bash +# 检查容器启动状态 +docker ps -a |grep redis +# 检查容器启动日志信息 +docker logs redis +``` + +#### 2.4 数据注入 + +测试数据注入 + +##### 2.4.1 Postgres数据注入 + +```SQL +INSERT INTO public."Topologic" VALUES (2, 1, '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', '10f155cf-bd27-4557-85b2-d126b6e2657f', 1, NULL); +INSERT INTO public."Topologic" VALUES (3, 1, '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', 'e32bc0be-67f4-4d79-a5da-eaa40a5bd77d', 1, NULL); +INSERT INTO public."Topologic" VALUES (4, 1, '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', '70c190f2-8a75-42a9-b166-ec5f87e0aa6b', 1, NULL); +INSERT INTO public."Topologic" VALUES (5, 1, 'e32bc0be-67f4-4d79-a5da-eaa40a5bd77d', '70c200f2-8a75-42a9-c166-bf5f87e0aa6b', 1, NULL); +INSERT INTO public."Topologic" VALUES (1, 1, '00000000-0000-0000-0000-000000000000', '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', 1, NULL); +``` + +##### 2.4.2 Redis数据注入 + +Redis数据脚本 + +```Lua +redis.call('SADD', 'grid_keys', 'transformfeeder1_220', 'transformfeeder1_220_35', 'transformfeeder1_220_36') +redis.call('SADD', 'grid_transformfeeder1_220_zones_keys', 'I_A_rms', 'I_B_rms', 'I_C_rms') +redis.call('SADD', 'grid_transformfeeder1_220_35_zones_keys', 'I_A_rms', 'I_B_rms', 'I_C_rms') +redis.call('SADD', 'grid_transformfeeder1_220_36_zones_keys', 'I_A_rms', 'I_B_rms', 'I_C_rms') + +local dict_key = 'search_suggestions_dict' + +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_35', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_36', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220.I_A_rms', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220.I_B_rms', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220.I_C_rms', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_35.I_A_rms', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_35.I_B_rms', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_35.I_C_rms', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_36.I_A_rms', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_36.I_B_rms', 1) +redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_36.I_C_rms', 1) + +return 'OK' +``` + +在Redis CLI 中导入命令 + +1. 使用 `EVAL "lua脚本" 0`即可成功导入数据 +2. 使用 `SCRIPT LOAD "lua脚本"`加载脚本,然后使用 `EVAL SHA1值 0` 命令执行上一步存储命令返回的哈希值即可 + +### 3\. 启动 ModelRT 服务 + +#### 3.1 配置服务配置文件 + +以下表格为配置文件参数说明表 + +| 类别 | 参数名 | 作用描述 | 示例值 | +| :--- | :--- | :--- | :--- | +| **Postgres** | `host` | PostgreSQL 数据库服务器的 $\text{IP}$ 地址或域名。 | `"192.168.1.101"` | +| | `port` | PostgreSQL 数据库服务器的端口号。 | `5432` | +| | `database` | 连接的数据库名称。 | `"demo"` | +| | `user` | 连接数据库所使用的用户名。 | `"postgres"` | +| | `password` | 连接数据库所使用的密码。 | `"coslight"` | +| **Kafka** | `servers` | Kafka 集群的 $\text{Bootstrap Server}$ 地址列表(通常是 $\text{host:port}$ 形式,多个地址用逗号分隔)。 | `"localhost:9092"` | +| | `port` | Kafka 服务器的端口号。 | `9092` | +| | `group_id` | 消费者组 $\text{ID}$,用于标识和管理一组相关的消费者。 | `"modelRT"` | +| | `topic` | Kafka 消息的主题名称。 | `""` | +| | `auto_offset_reset` | 消费者首次启动或 $\text{Offset}$ 无效时,从哪个位置开始消费(如 `earliest` 或 `latest`)。 | `"earliest"` | +| | `enable_auto_commit` | 是否自动提交 $\text{Offset}$。设为 $\text{false}$ 通常用于手动控制 $\text{Offset}$ 提交。 | `"false"` | +| | `read_message_time_duration` | 读取消息时的超时或等待时间。 | `”0.5s"` | +| **Logger (Zap)** | `mode` | 日志模式,通常为 `development`(开发)或 `production`(生产)。影响日志格式。 | `"development"` | +| | `level` | 最低日志级别(如 $\text{debug, info, warn, error}$)。 | `"debug"` | +| | `filepath` | 日志文件的输出路径和名称格式(`%s` 会被替换为日期等)。 | `"/Users/douxu/Workspace/coslight/modelRT/modelRT-%s.log"` | +| | `maxsize` | 单个日志文件最大大小(单位:$\text{MB}$)。 | `1` | +| | `maxbackups` | 保留旧日志文件的最大个数。 | `5` | +| | `maxage` | 保留旧日志文件的最大天数。 | `30` | +| | `compress` | 是否压缩备份的日志文件。 | `false` | +| **Ants Pool** | `parse_concurrent_quantity` | 用于解析任务的协程池最大并发数量。 | `10` | +| | `rtd_receive_concurrent_quantity` | 用于实时数据接收任务的协程池最大并发数量。 | `10` | +| **Locker Redis** | `addr` | 分布式锁服务所使用的 $\text{Redis}$ 地址。 | `"127.0.0.1:6379"` | +| | `password` | $\text{Locker Redis}$ 的密码。 | `""` | +| | `db` | $\text{Locker Redis}$ 使用的数据库编号。 | `1` | +| | `poolsize` | $\text{Locker Redis}$ 连接池的最大连接数。 | `50` | +| | `timeout` | $\text{Locker Redis}$ 连接操作的超时时间(单位:毫秒)。 | `10` | +| **Storage Redis** | `addr` | 数据存储服务所使用的 $\text{Redis}$ 地址(例如 $\text{Redisearch}$)。 | `"127.0.0.1:6379"` | +| | `password` | $\text{Storage Redis}$ 的密码。 | `""` | +| | `db` | $\text{Storage Redis}$ 使用的数据库编号。 | `0` | +| | `poolsize` | $\text{Storage Redis}$ 连接池的最大连接数。 | `50` | +| | `timeout` | $\text{Storage Redis}$ 连接操作的超时时间(单位:毫秒)。 | `10` | +| **Base Config** | `grid_id` | 项目所操作的默认电网 $\text{ID}$。 | `1` | +| | `zone_id` | 项目所操作的默认区域 $\text{ID}$。 | `1` | +| | `station_id` | 项目所操作的默认变电站 $\text{ID}$。 | `1` | +| **Service Config** | `service_name` | 服务名称,用于日志、监控等标识。 | `"modelRT"` | +| | `secret_key` | 服务内部使用的秘钥,用于签名或认证。 | `"modelrt_key"` | +| **DataRT API** | `host` | 外部 $\text{DataRT}$ 服务的主机地址。 | `"http://127.0.0.1"` | +| | `port` | $\text{DataRT}$ 服务的端口号。 | `8888` | +| | `polling_api` | 轮询数据的 $\text{API}$ 路径。 | `"datart/getPointData"` | +| | `polling_api_method` | 调用该 $\text{API}$ 使用的 $\text{HTTP}$ 方法。 | `"GET"` | + +#### 3.2 编译 ModelRT 服务 + +```bash +go build -o model-rt main.go +``` + +#### 3.3 启动服务 + +使用编译好的二进制文件进行启动 + +```bash +./model-rt +``` + +#### 3.4 检测服务启动日志 + +在发现控制台输出如下信息`starting ModelRT server` +后即代表服务启动成功 + +### 4\. 后续操作(停止与清理) + +#### 4.1 停止容器 + +```bash +docker stop postgres redis +``` + +#### 4.2 删除容器(删除后数据将丢失) + +```bash +docker rm postgres redis +``` diff --git a/main.go b/main.go index d3e161d..df55d40 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "os/signal" + "path/filepath" "syscall" "time" @@ -40,7 +41,7 @@ func init() { } var ( - modelRTConfigDir = flag.String("modelRT_config_dir", "./config", "config file dir of model runtime service") + modelRTConfigDir = flag.String("modelRT_config_dir", "./configs", "config file dir of model runtime service") modelRTConfigName = flag.String("modelRT_config_name", "config", "config file name of model runtime service") modelRTConfigType = flag.String("modelRT_config_type", "yaml", "config file type of model runtime service") ) @@ -59,6 +60,22 @@ func main() { // init logger logger.InitLoggerInstance(modelRTConfig.LoggerConfig) + configPath := filepath.Join(*modelRTConfigDir, *modelRTConfigName+"."+*modelRTConfigType) + if _, err := os.Stat(configPath); os.IsNotExist(err) { + logger.Info(ctx, "configuration file not found,checking for example file") + + exampleConfigPath := filepath.Join(*modelRTConfigDir, *modelRTConfigName+".example."+*modelRTConfigType) + if _, err := os.Stat(exampleConfigPath); err == nil { + if err := util.CopyFile(exampleConfigPath, configPath); err != nil { + logger.Error(ctx, "failed to copy example config file", "error", err) + panic(err) + } + logger.Info(ctx, "successfully copied example config to actual config file") + } else { + logger.Error(ctx, "No config file and no config example file found.") + } + } + modelRTConfig = config.ReadAndInitConfig(*modelRTConfigDir, *modelRTConfigName, *modelRTConfigType) hostName, err := os.Hostname() @@ -196,7 +213,7 @@ func main() { } }() - logger.Info(ctx, "Starting ModelRT server...") + logger.Info(ctx, "starting ModelRT server") err = server.ListenAndServe() if err != nil { if err == http.ErrServerClosed { diff --git a/util/copy.go b/util/copy.go new file mode 100644 index 0000000..789c978 --- /dev/null +++ b/util/copy.go @@ -0,0 +1,35 @@ +// Package util provide some utility functions +package util + +import ( + "fmt" + "io" + "os" +) + +// CopyFile define func of copies a file from src to dst. +// If the destination file exists, it will be overwritten. +func CopyFile(src, dst string) error { + sourceFile, err := os.Open(src) + if err != nil { + return fmt.Errorf("failed to open source file %s: %w", src, err) + } + defer sourceFile.Close() + + destFile, err := os.Create(dst) + if err != nil { + return fmt.Errorf("failed to create destination file %s: %w", dst, err) + } + defer destFile.Close() + + _, err = io.Copy(destFile, sourceFile) + if err != nil { + return fmt.Errorf("failed to copy file contents from %s to %s: %w", src, dst, err) + } + + err = destFile.Sync() + if err != nil { + return fmt.Errorf("failed to sync destination file %s: %w", dst, err) + } + return nil +}