V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
leia
V2EX  ›  分享发现

秒杀传统数据库! Cloudflare D1 + Drizzle 组合拳,高并发高可用,让我们的成本爆降 10 倍 - D1

  •  
  •   leia · 56 天前 · 2687 次点击
    这是一个创建于 56 天前的主题,其中的信息可能已经有所发展或是发生改变。

    秒杀传统数据库! Cloudflare D1 + Drizzle 组合拳,高并发高可用,让我们的成本爆降 10 倍 - D1

    想象一下:我们的应用用户量稳步增长,​传统数据库的成本和维护压力也随之上升​。而在这个时代,有没有更高效、更经济的数据库解决方案?​Cloudflare D1 结合 Drizzle ORM 的组合​,正在为众多​出海应用开发提供一条全新的技术路径​。

    传统数据库方案在高并发场景下往往需要复杂的扩容、分片和负载均衡,成本随着流量呈指数级增长。而当我们了解了 Cloudflare D1 这款​基于 SQLite 构建的边缘数据库​,再配合 Drizzle 这个轻量级 ORM 的强大能力,我们会惊讶于这个组合如何能在保持高性能的同时,​将我们的基础设施成本直接腰斩​!

    无需复杂的数据库集群,无需昂贵的专用服务器,无需担心地理位置带来的延迟问题 — 这个方案将​彻底改变我们对数据库架构的认知​。

    Cloudflare D1 实战:从零开始搭建高性能数据库

    Cloudflare D1 是 Cloudflare 推出的一款分布式 SQL 数据库,它基于 SQLite 构建,完全集成在 Cloudflare Workers 生态系统中。D1 将 SQLite 数据库部署到 Cloudflare 的全球边缘网络,让我们的数据库与应用代码一样,运行在离用户最近的位置,大幅降低延迟。

    D1 成本计算与对比

    在深入技术细节前,让我们先来看看 D1 在成本方面的巨大优势。Cloudflare D1 采用了极具竞争力的定价模型:

    资源类型 Workers Free (免费版) Workers Paid (付费版)
    读取行数 每天 500 万行限制 每月前 250 亿行免费,超出部分 $0.001/百万行
    写入行数 每天 10 万行限制 每月前 5000 万行免费,超出部分 $1.00/百万行
    存储空间 总计 5GB 限制 前 5GB 免费,超出部分 $0.75/GB-月

    让我们来分析一下免费版的套餐:

    • 读取成本​:每天 500 万行的读取量,一个月约 1.5 亿行,完全在免费额度内。即使你的应用流量翻倍,达到每天 1000 万行读取,每月 3 亿行,超出免费额度的 5000 万行只需要额外支付 $0.05/月。
    • 写入成本​:每天 10 万行的写入,一个月约 300 万行,远低于免费额度的 5000 万行。即使写入量增长 10 倍,仍然在免费额度内。
    • 存储成本​:5GB 的存储空间完全免费。对于大多数中小型应用来说,这已经足够存储数百万条记录

    付费版的价格是 5$,免费版的规模足够处理 5000-20000 日活的应用,付费 20000-100w 日活。

    快速上手 D1

    1. 安装 Wrangler CLI

    首先,我们需要安装 Cloudflare 的 Wrangler CLI 工具:

    npm install -g wrangler
    

    2. 创建 D1 数据库

    登录我们的 Cloudflare 账户后,创建一个新的 D1 数据库:

    wrangler login
    wrangler d1 create my-database
    

    执行后,我们会看到类似这样的输出:

    ✅ Successfully created DB 'my-database' in region APAC
    Created D1 database 'my-database' with id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    

    请记下这个数据库 ID ,我们后续会用到。

    3. 配置 wrangler.toml

    在我们的项目根目录创建或编辑 wrangler.toml 文件,添加 D1 数据库配置:

    [[d1_databases]]
    binding = "DB" # 在 Workers 中使用的变量名
    database_name = "my-database"
    database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # 替换为我们的数据库 ID
    

    4. 创建数据表

    创建一个 SQL 文件,例如 schema.sql

    CREATE TABLE users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE NOT NULL,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );
    

    然后执行:

    wrangler d1 execute my-database --file=./schema.sql
    

    5. 在 Workers 中使用 D1

    现在,我们可以在 Cloudflare Workers 中使用 D1 数据库了:

    export interface Env {
      DB: D1Database
    }
    
    export default {
      async fetch(request: Request, env: Env): Promise<Response> {
        // 查询用户列表
        const { results } = await env.DB.prepare('SELECT * FROM users ORDER BY created_at DESC LIMIT 10').all()
    
        return new Response(JSON.stringify(results), {
          headers: { 'Content-Type': 'application/json' }
        })
      }
    }
    

    D1 的实用命令与简单实践

    在实际开发中,我们需要更多的工具来管理数据库。D1 提供了一系列强大的命令行工具,让数据库管理变得轻松高效。 数据库迁移:管理我们的架构变更 数据库结构会随着需求不断变化。D1 提供了完善的迁移系统,让我们可以版本化管理数据库结构:

    创建一个新的迁移文件
    wrangler d1 migrations create my-database add_user_role
    

    这会在项目中创建一个类似 migrations/0001_add_user_role.sql` 的文件。编辑这个文件,添加我们的 SQL 变更:

    -- Migration: add_user_role
    -- Created at: 2023-10-15 14:30:00
    
    -- 向用户表添加角色字段
    ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user' NOT NULL;
    
    -- 创建一个新的角色权限表
    CREATE TABLE role_permissions (
      role TEXT NOT NULL,
      permission TEXT NOT NULL,
      PRIMARY KEY (role, permission)
    );
    

    然后应用这些迁移:

    应用到本地开发环境
    wrangler d1 migrations apply my-database --local
    
    应用到生产环境
    wrangler d1 migrations apply my-database --remote
    

    这种方式让我们可以: - 追踪数据库的所有变更历史 - 在团队中同步数据库结构 - 在不同环境(开发、测试、生产)之间保持一致性

    数据导入导出:备份与恢复

    需要备份数据或将数据迁移到其他环境? D1 提供了简单的导出导入功能

    导出整个数据库(结构+数据)
    wrangler d1 export my-database --output=backup.sql
    
    只导出特定表
    wrangler d1 export my-database --table=users --output=users_backup.sql
    
    只导出结构,不导出数据
    wrangler d1 export my-database --output=schema.sql --no-data
    

    导入数据同样简单:

    wrangler d1 execute my-database --file=backup.sql
    

    实战案例:构建一个博客系统

    让我们通过一个实际案例来展示 D1 的强大功能。假设我们要构建一个简单的博客系统,需要存储文章和评论。

    首先,创建数据库结构:

    -- migrations/0001_create_blog_tables.sql
    CREATE TABLE posts (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      title TEXT NOT NULL,
      content TEXT NOT NULL,
      author_id INTEGER NOT NULL,
      published_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      status TEXT DEFAULT 'draft' NOT NULL
    );
    
    CREATE TABLE comments (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      post_id INTEGER NOT NULL,
      author_name TEXT NOT NULL,
      content TEXT NOT NULL,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE
    );
    
    CREATE INDEX idx_posts_status ON posts(status);
    CREATE INDEX idx_comments_post_id ON comments(post_id);
    

    npx wrangler d1 execute prod-d1-tutorial --local --file=./migrations/0001_create_blog_tables.sql

    然后,在 Workers 中实现 API 接口:

    export interface Env {
      DB: D1Database
    }
    
    export default {
      async fetch(request: Request, env: Env): Promise<Response> {
        const url = new URL(request.url)
        const path = url.pathname
    
        // 获取博客文章列表
        if (path === '/api/posts' && request.method === 'GET') {
          const { results } = await env.DB.prepare(
            "SELECT id, title, published_at FROM posts WHERE status = 'published' ORDER BY published_at DESC LIMIT 10"
          ).all()
    
          return new Response(JSON.stringify(results), {
            headers: { 'Content-Type': 'application/json' }
          })
        }
    
        // 获取单篇文章及其评论
        if (path.match(/^\/api\/posts\/\d+$/) && request.method === 'GET') {
          const postId = path.split('/').pop()
    
          // 获取文章详情
          const post = await env.DB.prepare('SELECT * FROM posts WHERE id = ?').bind(postId).first()
    
          if (!post) {
            return new Response(JSON.stringify({ error: 'Post not found' }), {
              status: 404,
              headers: { 'Content-Type': 'application/json' }
            })
          }
    
          // 获取文章评论
          const { results: comments } = await env.DB.prepare(
            'SELECT * FROM comments WHERE post_id = ? ORDER BY created_at DESC'
          )
            .bind(postId)
            .all()
    
          return new Response(JSON.stringify({ post, comments }), {
            headers: { 'Content-Type': 'application/json' }
          })
        }
    
        // 添加评论
        if (path.match(/^\/api\/posts\/\d+\/comments$/) && request.method === 'POST') {
          const postId = path.split('/')[3]
          const { author_name, content } = await request.json()
    
          // 插入评论
          const result = await env.DB.prepare(
            'INSERT INTO comments (post_id, author_name, content) VALUES (?, ?, ?) RETURNING id'
          )
            .bind(postId, author_name, content)
            .run()
    
          return new Response(JSON.stringify({ id: result.results[0].id }), {
            status: 201,
            headers: { 'Content-Type': 'application/json' }
          })
        }
    
        return new Response('Not Found', { status: 404 })
      }
    }
    

    这个简单的博客 API 已经能够: - 获取已发布的文章列表 - 获取单篇文章及其评论 - 为文章添加新评论

    本地开发与调试

    在开发过程中,我们可以使用本地数据库进行测试:

    启动本地开发服务器,使用本地 D1 数据库
    wrangler dev --local
    

    这会在本地创建一个 SQLite 数据库文件,我们可以在开发过程中使用它,而不需要每次都操作远程数据库。当我们的代码准备好后,再将变更应用到远程数据库。

    应用迁移到远程数据库
    wrangler d1 migrations apply my-database --remote
    

    通过这种方式,我们可以在本地快速迭代开发,同时确保生产环境的数据安全。

    结束

    而在下一章节中,就讲解``Drizzle\,讲这个的主要目的是为了给大家普及一下`海外批量应用`的基础套件的知识 关于我

    27 条回复    2025-07-13 20:09:03 +08:00
    summerwar
        1
    summerwar  
       56 天前
    cloudflare d1 这种数据库的一个坑:一个 SELECT ... LIMIT 10 OFFSET 90000 的查询,可能会导致 D1 计算接近 90,010 次“行读取”。

    这个时候尽量用游标分页,就可以避免。
    julyclyde
        2
    julyclyde  
       56 天前
    @summerwar
    是按行读取收费吗?还是行读取多了会严重性能下降呢?
    summerwar
        3
    summerwar  
       56 天前   ❤️ 1
    @julyclyde 是按行读取收费

    其实这个不是 d1 的坑,offset 在 mysql 、sqlite 中也都是读取这么多行,只不过因为 d1 是按行读取计费,所以显得这条命令变成了坑。
    chihiro2014
        4
    chihiro2014  
       56 天前
    有了 cf ,基本连服务器钱都不需要了
    chesha1
        5
    chesha1  
       56 天前
    Craveu 不是 cozyai 的吗?为什么这是你的站点?
    june4
        6
    june4  
       56 天前
    @summerwar 根本就不应该允许用户翻这么多页。真有这类需求也有高效的分页方案,比如按一个字段排序分页。
    streamrx
        7
    streamrx  
       56 天前 via iPhone
    D1 延迟挺高的。 用 druable objects + sqlite 比 d1 好用
    cookii
        8
    cookii  
       56 天前 via Android
    我看到有个老哥吐槽,count 一下就是几美刀
    qingmeng
        9
    qingmeng  
       56 天前
    免费版 5GB 是十个库的额度,单库只有 500MB ,完全不够用
    lloovve
        10
    lloovve  
       56 天前 via iPhone
    全球分布式数据库?一个节点数据能实时同步到其他节点吗?其他节点能实时同步过来?
    iX8NEGGn
        11
    iX8NEGGn  
       56 天前
    D1 延迟挺高,不能高并发吧,而且不支持交互式事务,这才是致命缺陷,不过个人项目玩玩倒无所谓。
    RedBeanIce
        12
    RedBeanIce  
       56 天前
    @chihiro2014worker 好像只能 nodejs?
    pottwr
        13
    pottwr  
       56 天前
    适合 MVP 阶段,确定要真正投入时间做产品的话还是谨慎使用
    hanguofu
        14
    hanguofu  
       55 天前 via Android
    谢谢分享宝贵经验
    ttthys
        15
    ttthys  
       55 天前
    按行收费的话那不是 count 一下就过免费额度了,还以为是按返回的行收费😂
    ersic
        16
    ersic  
       55 天前
    数据多了别 count ,算全表总行数的读
    bigtear
        17
    bigtear  
       55 天前 via Android
    无服务器方案到最后都是后悔
    yb2313
        18
    yb2313  
       55 天前
    又秒杀上了
    wenjie83
        19
    wenjie83  
       55 天前
    这种付费方式,不仅要单独根据付费特性去优化语句,而且如果以后付费规则发生变动,是迁移还是不迁移?
    感觉还是买断制放心
    elevioux
        20
    elevioux  
       55 天前   ❤️ 1
    这个账号专门给自己博客引流的吗?
    chihiro2014
        21
    chihiro2014  
       55 天前
    @RedBeanIce worker 支持的语言都可以操作 D1 吧,nodejs 只是 worker 用的频繁罢了。rust 啥的也都可以
    leia
        22
    leia  
    OP
       55 天前
    @elevioux 没有了 哈哈哈 老哥,
    leia
        23
    leia  
    OP
       55 天前
    @yb2313 包秒的
    molvqingtai
        24
    molvqingtai  
       55 天前
    默认不是分布式,延迟太高了,国内一个请求 500+ms
    Foxkeh
        25
    Foxkeh  
       55 天前
    "成本暴降 10 倍"?
    Selenium39
        26
    Selenium39  
       55 天前
    一股 AI 味
    leia
        27
    leia  
    OP
       53 天前
    @Selenium39 原文写完让他润色改格式是这样的
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1179 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 17:49 · PVG 01:49 · LAX 10:49 · JFK 13:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.