Spaces:
Runtime error
Runtime error
File size: 11,242 Bytes
49c44b0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | # 零成本生产级 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
|