Spaces:
Runtime error
Runtime error
|
Download ARCHITECTURE.md from vftfnn/space-test: direct link, hf CLI and curl.
- Browser
- Download file 11.2 kB
-
https://huggingface.co/spaces/vftfnn/space-test/resolve/main/ARCHITECTURE.md
- Command line
-
hf download hf://spaces/vftfnn/space-test/ARCHITECTURE.md
-
curl -L -o ARCHITECTURE.md https://huggingface.co/spaces/vftfnn/space-test/resolve/main/ARCHITECTURE.md
11.2 kB
| # 零成本生产级 Spring Boot 部署架构 | |
| 基于 HuggingFace Space Docker + 免费基础设施的完整方案。 | |
| ## 架构拓扑 | |
| ``` | |
| 用户 | |
| ↓ | |
| CloudFlare(自定义域名 + DDNS + CDN + WAF) | |
| ↓ | |
| HuggingFace Space Docker(Spring Boot 容器,2 vCPU / 16GB RAM) | |
| ↓ | |
| ├─→ SQLPub MySQL(免费 500MB 存储 / 100 连接) | |
| └─→ Upstash Redis(免费 10K 命令/天 / 256MB) | |
| ``` | |
| **关键特性**: | |
| - **零费用**:HF Space 免费持久 Docker 容器(非 Serverless,24x7 运行);数据库 / Redis / 域名全免费档 | |
| - **真实公网 IP + 自定义域名**:通过 CloudFlare DDNS 把 Space 的动态 `.hf.space` 域名映射到你的域名 | |
| - **生产可用性**:500MB MySQL(小应用够用)、Redis 缓存、CloudFlare CDN + WAF | |
| ## 组件清单与注册 | |
| | 组件 | 用途 | 免费额度 | 注册入口 | | |
| |---|---|---|---| | |
| | **HuggingFace Space** | 容器运行时 | 2 vCPU + 16GB RAM 持久容器 | https://huggingface.co/new-space | | |
| | **SQLPub MySQL** | 关系数据库 | 500MB 存储 / 100 并发连接 | https://console.sqlpub.com/user/login | | |
| | **Upstash Redis** | 缓存 / Session | 10K 命令/天 / 256MB 内存 | https://console.upstash.com/auth/sign-in | | |
| | **CloudFlare** | 域名解析 + CDN + DDNS | 无限流量 / 免费 SSL | https://dash.cloudflare.com/ | | |
| ## 详细配置步骤 | |
| ### 1. SQLPub MySQL | |
| 1. 注册 https://console.sqlpub.com/ → 创建数据库(选 MySQL 5.7+) | |
| 2. 在控制台获取连接信息: | |
| ``` | |
| 主机:<host>.sqlpub.com | |
| 端口:3306 | |
| 数据库名:<dbname> | |
| 用户名:<username> | |
| 密码:<password> | |
| ``` | |
| 3. **JDBC URL**(供 Spring Boot 用): | |
| ``` | |
| jdbc:mysql://<host>.sqlpub.com:3306/<dbname>?useSSL=true&serverTimezone=UTC | |
| ``` | |
| **限制**: | |
| - 500MB 存储(约 100 万小条记录) | |
| - 100 并发连接(对单容器应用足够) | |
| - 外网访问慢(约 100-300ms 延迟);如需低延迟,考虑付费升级或自建 | |
| ### 2. Upstash Redis | |
| 1. 注册 https://console.upstash.com/ → Create Database → 选 **Global**(最低延迟) | |
| 2. 拿连接信息: | |
| ``` | |
| UPSTASH_REDIS_REST_URL=https://xxx.upstash.io | |
| UPSTASH_REDIS_REST_TOKEN=AXXXxxxxx | |
| ``` | |
| 3. Spring Boot 集成:用 **Jedis** 或 **Lettuce** 客户端,配置 host/port/password(见下方 application.yml) | |
| **限制**: | |
| - **10,000 命令/天**(约每 8.6 秒一条,对低频应用够用) | |
| - 256MB 内存 | |
| - 超额后节流(不会直接拒绝,但会变慢) | |
| **优化建议**: | |
| - Session 存 Redis 时设 TTL(如 30 分钟),自动过期省配额 | |
| - 低频场景(如缓存配置、限流计数器)适合;高频实时计算不适合 | |
| ### 3. CloudFlare 域名 + DDNS | |
| #### 3.1 域名托管到 CloudFlare | |
| 1. 在域名注册商(如 Namesilo / Porkbun)购买域名(约 $2-10/年) | |
| 2. 登录 https://dash.cloudflare.com/ → Add Site → 输入域名 | |
| 3. 按提示修改域名 NS 记录指向 CloudFlare(如 `ns1.cloudflare.com` / `ns2.cloudflare.com`) | |
| #### 3.2 DDNS(动态更新 HF Space IP) | |
| **原理**:HF Space 的公网地址是 `<用户名>-<空间名>.hf.space`(动态,可能变化)。通过 **CloudFlare API + DDNS 脚本**,定期解析 `.hf.space` 的真实 IP 并更新到你的域名 A 记录,实现自定义域名访问。 | |
| **参考方案**(博客详解): | |
| - https://blog.qjyg.de/post/tech/domain-dpdns/ | |
| **核心步骤**: | |
| 1. 在 CloudFlare 获取 **Zone ID** 和 **API Token**(权限:Zone.DNS Edit) | |
| 2. 在 **HF Space 容器内** or **定时任务服务器**(如 GitHub Actions / 自己的服务器)跑 DDNS 脚本: | |
| ```bash | |
| #!/bin/bash | |
| DOMAIN="yourdomain.com" | |
| SUBDOMAIN="api" # 最终域名 api.yourdomain.com | |
| HF_SPACE_URL="<用户名>-<空间名>.hf.space" | |
| # 解析 HF Space 当前 IP | |
| CURRENT_IP=$(dig +short "$HF_SPACE_URL" | tail -1) | |
| # 调 CloudFlare API 更新 A 记录 | |
| curl -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \ | |
| -H "Authorization: Bearer $CF_API_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"type\":\"A\",\"name\":\"$SUBDOMAIN\",\"content\":\"$CURRENT_IP\",\"ttl\":120}" | |
| ``` | |
| 3. 定时执行(如 cron 每 5 分钟):HF Space IP 变化后自动同步 | |
| **优势**: | |
| - CloudFlare 免费 CDN + DDoS 防护 + 免费 SSL | |
| - 自定义域名(`api.yourdomain.com`)比 `.hf.space` 更专业 | |
| ### 4. HuggingFace Space 环境变量 | |
| 敏感信息(数据库密码 / Redis token)不能硬编码,用 **Space Secrets**: | |
| 1. 进入 Space 设置页 → **Repository secrets** | |
| 2. 添加变量(Spring Boot 会通过环境变量读取): | |
| ``` | |
| DB_HOST=<host>.sqlpub.com | |
| DB_PORT=3306 | |
| DB_NAME=<dbname> | |
| DB_USER=<username> | |
| DB_PASSWORD=<password> | |
| REDIS_HOST=<xxx>.upstash.io | |
| REDIS_PORT=6379 | |
| REDIS_PASSWORD=<token> | |
| CF_ZONE_ID=<zone_id> | |
| CF_API_TOKEN=<api_token> | |
| CF_RECORD_ID=<dns_record_id> | |
| ``` | |
| ## Spring Boot 配置示例 | |
| `src/main/resources/application.yml`: | |
| ```yaml | |
| spring: | |
| application: | |
| name: hf-springboot-demo | |
| # SQLPub MySQL | |
| datasource: | |
| url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:test}?useSSL=true&serverTimezone=UTC&characterEncoding=utf8 | |
| username: ${DB_USER:root} | |
| password: ${DB_PASSWORD:password} | |
| driver-class-name: com.mysql.cj.jdbc.Driver | |
| hikari: | |
| maximum-pool-size: 10 # SQLPub 免费限 100 连接,单容器 10 足够 | |
| connection-timeout: 30000 | |
| # JPA(可选,如果用 ORM) | |
| jpa: | |
| hibernate: | |
| ddl-auto: update # 生产改 validate | |
| show-sql: false | |
| properties: | |
| hibernate: | |
| dialect: org.hibernate.dialect.MySQL8Dialect | |
| # Upstash Redis | |
| redis: | |
| host: ${REDIS_HOST:localhost} | |
| port: ${REDIS_PORT:6379} | |
| password: ${REDIS_PASSWORD:} | |
| timeout: 5000 | |
| lettuce: | |
| pool: | |
| max-active: 8 # 连接池大小 | |
| max-idle: 4 | |
| min-idle: 1 | |
| server: | |
| port: 7860 # HF Space 要求 | |
| address: 0.0.0.0 | |
| ``` | |
| **依赖(`pom.xml` 新增)**: | |
| ```xml | |
| <!-- MySQL --> | |
| <dependency> | |
| <groupId>mysql</groupId> | |
| <artifactId>mysql-connector-java</artifactId> | |
| <version>8.0.33</version> | |
| </dependency> | |
| <!-- Spring Data JPA(可选) --> | |
| <dependency> | |
| <groupId>org.springframework.boot</groupId> | |
| <artifactId>spring-boot-starter-data-jpa</artifactId> | |
| </dependency> | |
| <!-- Redis --> | |
| <dependency> | |
| <groupId>org.springframework.boot</groupId> | |
| <artifactId>spring-boot-starter-data-redis</artifactId> | |
| </dependency> | |
| ``` | |
| ## DDNS 自动更新脚本(容器内定时任务) | |
| `src/main/java/com/example/hfspace/task/CloudFlareDDNSTask.java`: | |
| ```java | |
| package com.example.hfspace.task; | |
| import org.springframework.beans.factory.annotation.Value; | |
| import org.springframework.scheduling.annotation.Scheduled; | |
| import org.springframework.stereotype.Component; | |
| import org.springframework.web.client.RestTemplate; | |
| import java.net.InetAddress; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| @Component | |
| public class CloudFlareDDNSTask { | |
| @Value("${cf.zone-id:}") | |
| private String zoneId; | |
| @Value("${cf.api-token:}") | |
| private String apiToken; | |
| @Value("${cf.record-id:}") | |
| private String recordId; | |
| @Value("${hf.space-url:}") | |
| private String hfSpaceUrl; | |
| private final RestTemplate restTemplate = new RestTemplate(); | |
| // 每 5 分钟检查一次 | |
| @Scheduled(fixedRate = 300000) | |
| public void updateDNS() { | |
| if (zoneId.isEmpty() || hfSpaceUrl.isEmpty()) { | |
| return; // 未配置,跳过 | |
| } | |
| try { | |
| // 解析 HF Space 当前 IP | |
| String currentIP = InetAddress.getByName(hfSpaceUrl).getHostAddress(); | |
| // 调 CloudFlare API 更新 | |
| String url = String.format("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", | |
| zoneId, recordId); | |
| Map<String, Object> body = new HashMap<>(); | |
| body.put("type", "A"); | |
| body.put("content", currentIP); | |
| body.put("ttl", 120); | |
| var headers = new org.springframework.http.HttpHeaders(); | |
| headers.set("Authorization", "Bearer " + apiToken); | |
| headers.set("Content-Type", "application/json"); | |
| var entity = new org.springframework.http.HttpEntity<>(body, headers); | |
| restTemplate.exchange(url, org.springframework.http.HttpMethod.PUT, entity, String.class); | |
| System.out.println("DDNS updated: " + currentIP); | |
| } catch (Exception e) { | |
| System.err.println("DDNS update failed: " + e.getMessage()); | |
| } | |
| } | |
| } | |
| ``` | |
| **启用定时任务**(主类加 `@EnableScheduling`): | |
| ```java | |
| @SpringBootApplication | |
| @EnableScheduling | |
| public class HfSpaceApplication { | |
| public static void main(String[] args) { | |
| SpringApplication.run(HfSpaceApplication.class, args); | |
| } | |
| } | |
| ``` | |
| ## 部署流程 | |
| 1. **本地测试**: | |
| ```bash | |
| export DB_HOST=<host>.sqlpub.com | |
| export DB_USER=<user> | |
| export DB_PASSWORD=<password> | |
| export REDIS_HOST=<xxx>.upstash.io | |
| export REDIS_PASSWORD=<token> | |
| mvn spring-boot:run | |
| ``` | |
| 2. **推送到 HF Space**: | |
| ```bash | |
| git clone https://huggingface.co/spaces/<用户名>/<空间名> | |
| # 拷贝所有文件(Dockerfile / pom.xml / src / README.md) | |
| git add . | |
| git commit -m "add mysql/redis integration + DDNS" | |
| git push | |
| ``` | |
| 3. **配置 Space Secrets**:进入 Space 设置页添加环境变量(DB_HOST / DB_PASSWORD / REDIS_HOST 等) | |
| 4. **验证**: | |
| - 访问 `https://<用户名>-<空间名>.hf.space/api/hello` 应返回 JSON | |
| - 日志里看到 "DDNS updated: x.x.x.x" 说明定时任务工作 | |
| - 几分钟后通过自定义域名 `https://api.yourdomain.com/api/hello` 也能访问 | |
| ## 限制与成本边界 | |
| | 维度 | 免费额度 | 突破方式 | | |
| |---|---|---| | |
| | **HF Space CPU/RAM** | 2 vCPU + 16GB(持久容器) | 升级 Pro($9/月,8 vCPU + 64GB) | | |
| | **MySQL 存储** | 500MB | SQLPub 付费版 or 迁移 PlanetScale / Supabase(也有免费档) | | |
| | **Redis 命令数** | 10K/天(≈ 0.12 QPS) | Upstash 付费版 or 自建 Redis(需额外服务器) | | |
| | **域名** | 年付 $2-10 | 续费即可 | | |
| | **流量** | CloudFlare 无限 | N/A | | |
| **适用场景**: | |
| - 个人项目 / MVP / 低流量 API(< 1K DAU) | |
| - 学习 / 展示 / 开源项目托管 | |
| - **不适合**:高并发生产(DB/Redis 配额不够)、对数据安全性要求极高的场景(托管服务风险) | |
| ## 进阶优化 | |
| 1. **监控**:集成 Sentry(错误追踪)+ Uptime Robot(健康检查) | |
| 2. **CI/CD**:GitHub Actions 自动推送到 HF Space(避免手动 git push) | |
| 3. **多环境**:本地 dev 用 H2 内存库,Space 用 MySQL(通过 Spring Profile 切换) | |
| 4. **备份**:定期 `mysqldump` 导出 SQLPub 数据到 GitHub private repo | |
| ## 参考链接 | |
| - SQLPub 控制台:https://console.sqlpub.com/ | |
| - Upstash Redis:https://console.upstash.com/ | |
| - CloudFlare Dashboard:https://dash.cloudflare.com/ | |
| - CloudFlare DDNS 博客:https://blog.qjyg.de/post/tech/domain-dpdns/ | |
| - HuggingFace Spaces Docs:https://huggingface.co/docs/hub/spaces-sdks-docker | |