space-test / ARCHITECTURE.md
fdwei4
init: Spring Boot demo with MySQL + Redis
49c44b0
|
Raw History Blame Contribute Delete
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