[{"data":1,"prerenderedAt":2239},["ShallowReactive",2],{"post-\u002Fposts\u002Fbackend-stack-deep-dive":3,"all-posts-nav":1968},{"id":4,"title":5,"body":6,"categories":1955,"date":1957,"description":12,"draft":1958,"extension":1959,"hidden":1958,"meta":1960,"navigation":288,"path":1961,"published":1958,"seo":1962,"stem":1963,"tags":1964,"__hash__":1967},"posts\u002Fposts\u002Fbackend-stack-deep-dive.md","后端五件套：FastAPI \u002F Node.js \u002F SQLAlchemy async \u002F PostgreSQL \u002F Docker 面试速通",{"type":7,"value":8,"toc":1932},"minimark",[9,13,18,22,25,91,94,100,195,205,210,329,332,337,344,348,351,488,503,506,510,514,517,525,528,534,537,540,771,775,778,845,856,858,862,866,869,932,935,938,944,947,950,1033,1035,1039,1042,1064,1067,1125,1129,1475,1484,1488,1491,1781,1788,1792,1795,1865,1868,1870,1873,1878,1881,1886,1893,1898,1901,1906,1909,1918,1928],[10,11,12],"p",{},"结合 DeepScientist 项目的实际经验，把这五个东西讲清楚。不是文档翻译，是真正用过之后的理解。",[14,15,17],"h2",{"id":16},"fastapi","FastAPI",[19,20,21],"h3",{"id":21},"为什么选它",[10,23,24],{},"Python Web 框架的选择通常是 Django \u002F Flask \u002F FastAPI 三选一。",[26,27,28,47],"table",{},[29,30,31],"thead",{},[32,33,34,38,41,44],"tr",{},[35,36,37],"th",{},"框架",[35,39,40],{},"异步支持",[35,42,43],{},"自动文档",[35,45,46],{},"适合场景",[48,49,50,65,78],"tbody",{},[32,51,52,56,59,62],{},[53,54,55],"td",{},"Django",[53,57,58],{},"有限",[53,60,61],{},"需插件",[53,63,64],{},"全功能 Web 应用，内置 ORM\u002FAdmin",[32,66,67,70,73,75],{},[53,68,69],{},"Flask",[53,71,72],{},"需扩展",[53,74,61],{},[53,76,77],{},"轻量 API，灵活但需要自己组装",[32,79,80,82,85,88],{},[53,81,17],{},[53,83,84],{},"原生",[53,86,87],{},"自动生成",[53,89,90],{},"I\u002FO 密集型 API 服务",[10,92,93],{},"FastAPI 的核心优势：",[10,95,96],{},[97,98,99],"strong",{},"1. 原生 async\u002Fawait",[101,102,107],"pre",{"className":103,"code":104,"language":105,"meta":106,"style":106},"language-python shiki shiki-themes github-dark-dimmed github-light","@app.get(\"\u002Fpapers\u002F{paper_id}\")\nasync def get_paper(paper_id: int, db: AsyncSession = Depends(get_db)):\n    result = await db.execute(select(Paper).where(Paper.id == paper_id))\n    return result.scalar_one_or_none()\n","python","",[108,109,110,137,166,186],"code",{"__ignoreMap":106},[111,112,115,119,123,127,131,134],"span",{"class":113,"line":114},"line",1,[111,116,118],{"class":117},"saVmf","@app.get",[111,120,122],{"class":121},"ssh_m","(",[111,124,126],{"class":125},"sXfbr","\"\u002Fpapers\u002F",[111,128,130],{"class":129},"sxsTv","{paper_id}",[111,132,133],{"class":125},"\"",[111,135,136],{"class":121},")\n",[111,138,140,144,147,150,153,157,160,163],{"class":113,"line":139},2,[111,141,143],{"class":142},"s6PUj","async",[111,145,146],{"class":142}," def",[111,148,149],{"class":117}," get_paper",[111,151,152],{"class":121},"(paper_id: ",[111,154,156],{"class":155},"swcJU","int",[111,158,159],{"class":121},", db: AsyncSession ",[111,161,162],{"class":142},"=",[111,164,165],{"class":121}," Depends(get_db)):\n",[111,167,169,172,174,177,180,183],{"class":113,"line":168},3,[111,170,171],{"class":121},"    result ",[111,173,162],{"class":142},[111,175,176],{"class":142}," await",[111,178,179],{"class":121}," db.execute(select(Paper).where(Paper.id ",[111,181,182],{"class":142},"==",[111,184,185],{"class":121}," paper_id))\n",[111,187,189,192],{"class":113,"line":188},4,[111,190,191],{"class":142},"    return",[111,193,194],{"class":121}," result.scalar_one_or_none()\n",[10,196,197,198,200,201,204],{},"函数加 ",[108,199,143],{},"，数据库查询加 ",[108,202,203],{},"await","，FastAPI 自动在 ASGI 事件循环里调度。",[10,206,207],{},[97,208,209],{},"2. Pydantic 自动校验",[101,211,213],{"className":103,"code":212,"language":105,"meta":106,"style":106},"class PaperCreate(BaseModel):\n    title: str\n    abstract: str\n    year: int = Field(ge=1900, le=2100)\n\n@app.post(\"\u002Fpapers\u002F\")\nasync def create_paper(paper: PaperCreate):\n    # 进到这里参数已经校验过了，year 一定在 1900-2100 之间\n    ...\n",[108,214,215,233,241,248,283,290,303,316,323],{"__ignoreMap":106},[111,216,217,220,224,226,230],{"class":113,"line":114},[111,218,219],{"class":142},"class",[111,221,223],{"class":222},"sqRhv"," PaperCreate",[111,225,122],{"class":121},[111,227,229],{"class":228},"sr-dd","BaseModel",[111,231,232],{"class":121},"):\n",[111,234,235,238],{"class":113,"line":139},[111,236,237],{"class":121},"    title: ",[111,239,240],{"class":155},"str\n",[111,242,243,246],{"class":113,"line":168},[111,244,245],{"class":121},"    abstract: ",[111,247,240],{"class":155},[111,249,250,253,255,258,261,265,267,270,273,276,278,281],{"class":113,"line":188},[111,251,252],{"class":121},"    year: ",[111,254,156],{"class":155},[111,256,257],{"class":142}," =",[111,259,260],{"class":121}," Field(",[111,262,264],{"class":263},"sNjOc","ge",[111,266,162],{"class":142},[111,268,269],{"class":155},"1900",[111,271,272],{"class":121},", ",[111,274,275],{"class":263},"le",[111,277,162],{"class":142},[111,279,280],{"class":155},"2100",[111,282,136],{"class":121},[111,284,286],{"class":113,"line":285},5,[111,287,289],{"emptyLinePlaceholder":288},true,"\n",[111,291,293,296,298,301],{"class":113,"line":292},6,[111,294,295],{"class":117},"@app.post",[111,297,122],{"class":121},[111,299,300],{"class":125},"\"\u002Fpapers\u002F\"",[111,302,136],{"class":121},[111,304,306,308,310,313],{"class":113,"line":305},7,[111,307,143],{"class":142},[111,309,146],{"class":142},[111,311,312],{"class":117}," create_paper",[111,314,315],{"class":121},"(paper: PaperCreate):\n",[111,317,319],{"class":113,"line":318},8,[111,320,322],{"class":321},"sgHix","    # 进到这里参数已经校验过了，year 一定在 1900-2100 之间\n",[111,324,326],{"class":113,"line":325},9,[111,327,328],{"class":155},"    ...\n",[10,330,331],{},"请求体自动解析 + 类型校验 + 错误信息生成，一行代码都不用多写。",[10,333,334],{},[97,335,336],{},"3. 自动生成 OpenAPI 文档",[10,338,339,340,343],{},"启动后访问 ",[108,341,342],{},"\u002Fdocs"," 就有交互式 API 文档，前端联调不需要手写文档。",[19,345,347],{"id":346},"sse-流式返回","SSE 流式返回",[10,349,350],{},"DeepScientist 的 AI Copilot 用 SSE（Server-Sent Events）实现流式输出：",[101,352,354],{"className":103,"code":353,"language":105,"meta":106,"style":106},"from fastapi.responses import StreamingResponse\n\nasync def generate_stream(prompt: str):\n    async for chunk in llm_client.stream(prompt):\n        yield f\"data: {chunk}\\n\\n\"\n\n@app.post(\"\u002Fcopilot\u002Fchat\")\nasync def chat(request: ChatRequest):\n    return StreamingResponse(\n        generate_stream(request.message),\n        media_type=\"text\u002Fevent-stream\"\n    )\n",[108,355,356,370,374,391,408,431,435,446,458,465,471,482],{"__ignoreMap":106},[111,357,358,361,364,367],{"class":113,"line":114},[111,359,360],{"class":142},"from",[111,362,363],{"class":121}," fastapi.responses ",[111,365,366],{"class":142},"import",[111,368,369],{"class":121}," StreamingResponse\n",[111,371,372],{"class":113,"line":139},[111,373,289],{"emptyLinePlaceholder":288},[111,375,376,378,380,383,386,389],{"class":113,"line":168},[111,377,143],{"class":142},[111,379,146],{"class":142},[111,381,382],{"class":117}," generate_stream",[111,384,385],{"class":121},"(prompt: ",[111,387,388],{"class":155},"str",[111,390,232],{"class":121},[111,392,393,396,399,402,405],{"class":113,"line":188},[111,394,395],{"class":142},"    async",[111,397,398],{"class":142}," for",[111,400,401],{"class":121}," chunk ",[111,403,404],{"class":142},"in",[111,406,407],{"class":121}," llm_client.stream(prompt):\n",[111,409,410,413,416,419,422,425,428],{"class":113,"line":285},[111,411,412],{"class":142},"        yield",[111,414,415],{"class":142}," f",[111,417,418],{"class":125},"\"data: ",[111,420,421],{"class":129},"{",[111,423,424],{"class":121},"chunk",[111,426,427],{"class":129},"}\\n\\n",[111,429,430],{"class":125},"\"\n",[111,432,433],{"class":113,"line":292},[111,434,289],{"emptyLinePlaceholder":288},[111,436,437,439,441,444],{"class":113,"line":305},[111,438,295],{"class":117},[111,440,122],{"class":121},[111,442,443],{"class":125},"\"\u002Fcopilot\u002Fchat\"",[111,445,136],{"class":121},[111,447,448,450,452,455],{"class":113,"line":318},[111,449,143],{"class":142},[111,451,146],{"class":142},[111,453,454],{"class":117}," chat",[111,456,457],{"class":121},"(request: ChatRequest):\n",[111,459,460,462],{"class":113,"line":325},[111,461,191],{"class":142},[111,463,464],{"class":121}," StreamingResponse(\n",[111,466,468],{"class":113,"line":467},10,[111,469,470],{"class":121},"        generate_stream(request.message),\n",[111,472,474,477,479],{"class":113,"line":473},11,[111,475,476],{"class":263},"        media_type",[111,478,162],{"class":142},[111,480,481],{"class":125},"\"text\u002Fevent-stream\"\n",[111,483,485],{"class":113,"line":484},12,[111,486,487],{"class":121},"    )\n",[10,489,490,491,494,495,498,499,502],{},"前端用 ",[108,492,493],{},"EventSource"," 或 ",[108,496,497],{},"fetch"," + ",[108,500,501],{},"ReadableStream"," 接收，实现打字机效果。",[504,505],"hr",{},[14,507,509],{"id":508},"sqlalchemy-async-asyncpg","SQLAlchemy async + AsyncPG",[19,511,513],{"id":512},"为什么-async-很重要","为什么 async 很重要",[10,515,516],{},"FastAPI 是 ASGI 框架，底层是一个事件循环（event loop）。如果用同步数据库驱动：",[101,518,523],{"className":519,"code":521,"language":522},[520],"language-text","请求 A 进来 → 查数据库（同步，阻塞 200ms）\n                ↑ 这 200ms 里事件循环被占用，请求 B 只能等\n","text",[108,524,521],{"__ignoreMap":106},[10,526,527],{},"换成 async：",[101,529,532],{"className":530,"code":531,"language":522},[520],"请求 A 进来 → 发出数据库查询（异步，挂起）\n              ↓ 事件循环空闲，处理请求 B\n              ↓ 数据库返回结果，恢复请求 A\n",[108,533,531],{"__ignoreMap":106},[10,535,536],{},"相同硬件，async 模式下并发能力可以提升数倍。",[19,538,539],{"id":539},"实际写法",[101,541,543],{"className":103,"code":542,"language":105,"meta":106,"style":106},"# 配置异步引擎\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSession\n\nengine = create_async_engine(\n    \"postgresql+asyncpg:\u002F\u002Fuser:pass@localhost\u002Fdbname\",\n    pool_size=20,\n    max_overflow=10,\n)\n\n# 查询\nasync with AsyncSession(engine) as session:\n    # 单条查询\n    user = await session.get(User, user_id)\n\n    # 条件查询\n    result = await session.execute(\n        select(Paper)\n        .where(Paper.user_id == user_id)\n        .order_by(Paper.created_at.desc())\n        .limit(20)\n    )\n    papers = result.scalars().all()\n\n    # 写入\n    session.add(Paper(title=\"...\", user_id=user_id))\n    await session.commit()\n",[108,544,545,550,562,566,576,584,596,608,612,616,621,637,642,655,660,666,678,684,695,701,711,716,727,732,738,762],{"__ignoreMap":106},[111,546,547],{"class":113,"line":114},[111,548,549],{"class":321},"# 配置异步引擎\n",[111,551,552,554,557,559],{"class":113,"line":139},[111,553,360],{"class":142},[111,555,556],{"class":121}," sqlalchemy.ext.asyncio ",[111,558,366],{"class":142},[111,560,561],{"class":121}," create_async_engine, AsyncSession\n",[111,563,564],{"class":113,"line":168},[111,565,289],{"emptyLinePlaceholder":288},[111,567,568,571,573],{"class":113,"line":188},[111,569,570],{"class":121},"engine ",[111,572,162],{"class":142},[111,574,575],{"class":121}," create_async_engine(\n",[111,577,578,581],{"class":113,"line":285},[111,579,580],{"class":125},"    \"postgresql+asyncpg:\u002F\u002Fuser:pass@localhost\u002Fdbname\"",[111,582,583],{"class":121},",\n",[111,585,586,589,591,594],{"class":113,"line":292},[111,587,588],{"class":263},"    pool_size",[111,590,162],{"class":142},[111,592,593],{"class":155},"20",[111,595,583],{"class":121},[111,597,598,601,603,606],{"class":113,"line":305},[111,599,600],{"class":263},"    max_overflow",[111,602,162],{"class":142},[111,604,605],{"class":155},"10",[111,607,583],{"class":121},[111,609,610],{"class":113,"line":318},[111,611,136],{"class":121},[111,613,614],{"class":113,"line":325},[111,615,289],{"emptyLinePlaceholder":288},[111,617,618],{"class":113,"line":467},[111,619,620],{"class":321},"# 查询\n",[111,622,623,625,628,631,634],{"class":113,"line":473},[111,624,143],{"class":142},[111,626,627],{"class":142}," with",[111,629,630],{"class":121}," AsyncSession(engine) ",[111,632,633],{"class":142},"as",[111,635,636],{"class":121}," session:\n",[111,638,639],{"class":113,"line":484},[111,640,641],{"class":321},"    # 单条查询\n",[111,643,645,648,650,652],{"class":113,"line":644},13,[111,646,647],{"class":121},"    user ",[111,649,162],{"class":142},[111,651,176],{"class":142},[111,653,654],{"class":121}," session.get(User, user_id)\n",[111,656,658],{"class":113,"line":657},14,[111,659,289],{"emptyLinePlaceholder":288},[111,661,663],{"class":113,"line":662},15,[111,664,665],{"class":321},"    # 条件查询\n",[111,667,669,671,673,675],{"class":113,"line":668},16,[111,670,171],{"class":121},[111,672,162],{"class":142},[111,674,176],{"class":142},[111,676,677],{"class":121}," session.execute(\n",[111,679,681],{"class":113,"line":680},17,[111,682,683],{"class":121},"        select(Paper)\n",[111,685,687,690,692],{"class":113,"line":686},18,[111,688,689],{"class":121},"        .where(Paper.user_id ",[111,691,182],{"class":142},[111,693,694],{"class":121}," user_id)\n",[111,696,698],{"class":113,"line":697},19,[111,699,700],{"class":121},"        .order_by(Paper.created_at.desc())\n",[111,702,704,707,709],{"class":113,"line":703},20,[111,705,706],{"class":121},"        .limit(",[111,708,593],{"class":155},[111,710,136],{"class":121},[111,712,714],{"class":113,"line":713},21,[111,715,487],{"class":121},[111,717,719,722,724],{"class":113,"line":718},22,[111,720,721],{"class":121},"    papers ",[111,723,162],{"class":142},[111,725,726],{"class":121}," result.scalars().all()\n",[111,728,730],{"class":113,"line":729},23,[111,731,289],{"emptyLinePlaceholder":288},[111,733,735],{"class":113,"line":734},24,[111,736,737],{"class":321},"    # 写入\n",[111,739,741,744,747,749,752,754,757,759],{"class":113,"line":740},25,[111,742,743],{"class":121},"    session.add(Paper(",[111,745,746],{"class":263},"title",[111,748,162],{"class":142},[111,750,751],{"class":125},"\"...\"",[111,753,272],{"class":121},[111,755,756],{"class":263},"user_id",[111,758,162],{"class":142},[111,760,761],{"class":121},"user_id))\n",[111,763,765,768],{"class":113,"line":764},26,[111,766,767],{"class":142},"    await",[111,769,770],{"class":121}," session.commit()\n",[19,772,774],{"id":773},"alembic-数据库迁移","Alembic 数据库迁移",[10,776,777],{},"修改 Model 之后不能直接改数据库，要走迁移：",[101,779,783],{"className":780,"code":781,"language":782,"meta":106,"style":106},"language-bash shiki shiki-themes github-dark-dimmed github-light","# 自动生成迁移脚本（对比 Model 和数据库现状）\nalembic revision --autogenerate -m \"add paper table\"\n\n# 执行迁移\nalembic upgrade head\n\n# 回滚一步\nalembic downgrade -1\n","bash",[108,784,785,790,807,811,816,826,830,835],{"__ignoreMap":106},[111,786,787],{"class":113,"line":114},[111,788,789],{"class":321},"# 自动生成迁移脚本（对比 Model 和数据库现状）\n",[111,791,792,795,798,801,804],{"class":113,"line":139},[111,793,794],{"class":222},"alembic",[111,796,797],{"class":125}," revision",[111,799,800],{"class":155}," --autogenerate",[111,802,803],{"class":155}," -m",[111,805,806],{"class":125}," \"add paper table\"\n",[111,808,809],{"class":113,"line":168},[111,810,289],{"emptyLinePlaceholder":288},[111,812,813],{"class":113,"line":188},[111,814,815],{"class":321},"# 执行迁移\n",[111,817,818,820,823],{"class":113,"line":285},[111,819,794],{"class":222},[111,821,822],{"class":125}," upgrade",[111,824,825],{"class":125}," head\n",[111,827,828],{"class":113,"line":292},[111,829,289],{"emptyLinePlaceholder":288},[111,831,832],{"class":113,"line":305},[111,833,834],{"class":321},"# 回滚一步\n",[111,836,837,839,842],{"class":113,"line":318},[111,838,794],{"class":222},[111,840,841],{"class":125}," downgrade",[111,843,844],{"class":155}," -1\n",[10,846,847,848,851,852,855],{},"迁移脚本会记录在 ",[108,849,850],{},"alembic\u002Fversions\u002F"," 里，可以 git 追踪，团队协作时每个人 ",[108,853,854],{},"upgrade head"," 就能同步数据库结构。",[504,857],{},[14,859,861],{"id":860},"postgresql","PostgreSQL",[19,863,865],{"id":864},"为什么不用-mysql","为什么不用 MySQL",[10,867,868],{},"两者都是成熟的关系型数据库，但 PostgreSQL 在以下场景更强：",[26,870,871,883],{},[29,872,873],{},[32,874,875,878,880],{},[35,876,877],{},"特性",[35,879,861],{},[35,881,882],{},"MySQL",[48,884,885,896,910,921],{},[32,886,887,890,893],{},[53,888,889],{},"JSON\u002FJSONB 字段",[53,891,892],{},"原生支持，可索引",[53,894,895],{},"支持但较弱",[32,897,898,901,907],{},[53,899,900],{},"全文搜索",[53,902,903,904],{},"内置 ",[108,905,906],{},"tsvector",[53,908,909],{},"需要插件",[32,911,912,915,918],{},[53,913,914],{},"复杂查询",[53,916,917],{},"CTE、窗口函数更完善",[53,919,920],{},"部分支持",[32,922,923,926,929],{},[53,924,925],{},"扩展生态",[53,927,928],{},"pgvector（向量搜索）等",[53,930,931],{},"较少",[10,933,934],{},"DeepScientist 用 PostgreSQL 存所有结构化数据，二进制文件（PDF、图片）存 MinIO，数据库只存路径和元数据。",[19,936,937],{"id":937},"文件存储原则",[101,939,942],{"className":940,"code":941,"language":522},[520],"❌ 错误做法：把 PDF 二进制存进数据库\n   → 表体积膨胀，备份困难，无法 CDN 加速\n\n✅ 正确做法：\n   上传 PDF → FastAPI → MinIO（存文件）\n                      → PostgreSQL（存路径、大小、MIME 类型）\n\n   下载 PDF → FastAPI → PostgreSQL（查路径）\n                      → MinIO（取文件）→ 返回客户端\n",[108,943,941],{"__ignoreMap":106},[19,945,946],{"id":946},"连接池",[10,948,949],{},"生产环境不能每次请求都新建数据库连接（开销大），要用连接池：",[101,951,953],{"className":103,"code":952,"language":105,"meta":106,"style":106},"engine = create_async_engine(\n    DATABASE_URL,\n    pool_size=20,        # 常驻连接数\n    max_overflow=10,     # 峰值时额外允许的连接数\n    pool_timeout=30,     # 等待连接超时时间（秒）\n    pool_recycle=1800,   # 连接复用超过 30 分钟就重建（防止数据库断开）\n)\n",[108,954,955,963,970,984,998,1013,1029],{"__ignoreMap":106},[111,956,957,959,961],{"class":113,"line":114},[111,958,570],{"class":121},[111,960,162],{"class":142},[111,962,575],{"class":121},[111,964,965,968],{"class":113,"line":139},[111,966,967],{"class":155},"    DATABASE_URL",[111,969,583],{"class":121},[111,971,972,974,976,978,981],{"class":113,"line":168},[111,973,588],{"class":263},[111,975,162],{"class":142},[111,977,593],{"class":155},[111,979,980],{"class":121},",        ",[111,982,983],{"class":321},"# 常驻连接数\n",[111,985,986,988,990,992,995],{"class":113,"line":188},[111,987,600],{"class":263},[111,989,162],{"class":142},[111,991,605],{"class":155},[111,993,994],{"class":121},",     ",[111,996,997],{"class":321},"# 峰值时额外允许的连接数\n",[111,999,1000,1003,1005,1008,1010],{"class":113,"line":285},[111,1001,1002],{"class":263},"    pool_timeout",[111,1004,162],{"class":142},[111,1006,1007],{"class":155},"30",[111,1009,994],{"class":121},[111,1011,1012],{"class":321},"# 等待连接超时时间（秒）\n",[111,1014,1015,1018,1020,1023,1026],{"class":113,"line":292},[111,1016,1017],{"class":263},"    pool_recycle",[111,1019,162],{"class":142},[111,1021,1022],{"class":155},"1800",[111,1024,1025],{"class":121},",   ",[111,1027,1028],{"class":321},"# 连接复用超过 30 分钟就重建（防止数据库断开）\n",[111,1030,1031],{"class":113,"line":305},[111,1032,136],{"class":121},[504,1034],{},[14,1036,1038],{"id":1037},"docker","Docker",[19,1040,1041],{"id":1041},"基本概念",[1043,1044,1045,1052,1058],"ul",{},[1046,1047,1048,1051],"li",{},[97,1049,1050],{},"镜像（Image）","：只读模板，类似类定义",[1046,1053,1054,1057],{},[97,1055,1056],{},"容器（Container）","：镜像的运行实例，类似对象实例",[1046,1059,1060,1063],{},[97,1061,1062],{},"Dockerfile","：描述如何构建镜像的脚本",[10,1065,1066],{},"DeepScientist 的 FastAPI Dockerfile：",[101,1068,1072],{"className":1069,"code":1070,"language":1071,"meta":106,"style":106},"language-dockerfile shiki shiki-themes github-dark-dimmed github-light","FROM python:3.11-slim\n\nWORKDIR \u002Fapp\n\n# 先复制依赖文件，利用 Docker 层缓存\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY . .\n\nCMD [\"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"18080\"]\n","dockerfile",[108,1073,1074,1079,1083,1088,1092,1097,1102,1107,1111,1116,1120],{"__ignoreMap":106},[111,1075,1076],{"class":113,"line":114},[111,1077,1078],{},"FROM python:3.11-slim\n",[111,1080,1081],{"class":113,"line":139},[111,1082,289],{"emptyLinePlaceholder":288},[111,1084,1085],{"class":113,"line":168},[111,1086,1087],{},"WORKDIR \u002Fapp\n",[111,1089,1090],{"class":113,"line":188},[111,1091,289],{"emptyLinePlaceholder":288},[111,1093,1094],{"class":113,"line":285},[111,1095,1096],{},"# 先复制依赖文件，利用 Docker 层缓存\n",[111,1098,1099],{"class":113,"line":292},[111,1100,1101],{},"COPY requirements.txt .\n",[111,1103,1104],{"class":113,"line":305},[111,1105,1106],{},"RUN pip install --no-cache-dir -r requirements.txt\n",[111,1108,1109],{"class":113,"line":318},[111,1110,289],{"emptyLinePlaceholder":288},[111,1112,1113],{"class":113,"line":325},[111,1114,1115],{},"COPY . .\n",[111,1117,1118],{"class":113,"line":467},[111,1119,289],{"emptyLinePlaceholder":288},[111,1121,1122],{"class":113,"line":473},[111,1123,1124],{},"CMD [\"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"18080\"]\n",[19,1126,1128],{"id":1127},"docker-compose-服务编排","Docker Compose 服务编排",[101,1130,1134],{"className":1131,"code":1132,"language":1133,"meta":106,"style":106},"language-yaml shiki shiki-themes github-dark-dimmed github-light","services:\n  frontend:\n    build: .\u002Ffrontend\n    ports: [\"1288:3000\"]\n    depends_on: [backend]\n\n  backend:\n    build: .\u002Fbackend\n    ports: [\"18080:18080\"]\n    environment:\n      DATABASE_URL: postgresql+asyncpg:\u002F\u002Fpostgres:pass@postgres\u002Fdeepscientist\n    depends_on: [postgres, minio]\n\n  postgres:\n    image: postgres:16\n    volumes:\n      - postgres_data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n    environment:\n      POSTGRES_PASSWORD: pass\n      POSTGRES_DB: deepscientist\n\n  minio:\n    image: minio\u002Fminio\n    ports: [\"9000:9000\", \"9001:9001\"]\n    command: server \u002Fdata --console-address \":9001\"\n    volumes:\n      - minio_data:\u002Fdata\n\n  sandbox:\n    image: deepscientist-sandbox\n    ports: [\"15900-15901:5900-5901\"]  # VNC\n\nvolumes:\n  postgres_data:\n  minio_data:\n\nnetworks:\n  default:\n    name: deepscientist-network\n","yaml",[108,1135,1136,1145,1152,1163,1177,1189,1193,1200,1209,1220,1227,1237,1253,1257,1264,1274,1281,1289,1295,1305,1315,1319,1326,1335,1351,1361,1367,1375,1380,1388,1398,1414,1419,1427,1435,1443,1448,1456,1464],{"__ignoreMap":106},[111,1137,1138,1142],{"class":113,"line":114},[111,1139,1141],{"class":1140},"shb1k","services",[111,1143,1144],{"class":121},":\n",[111,1146,1147,1150],{"class":113,"line":139},[111,1148,1149],{"class":1140},"  frontend",[111,1151,1144],{"class":121},[111,1153,1154,1157,1160],{"class":113,"line":168},[111,1155,1156],{"class":1140},"    build",[111,1158,1159],{"class":121},": ",[111,1161,1162],{"class":125},".\u002Ffrontend\n",[111,1164,1165,1168,1171,1174],{"class":113,"line":188},[111,1166,1167],{"class":1140},"    ports",[111,1169,1170],{"class":121},": [",[111,1172,1173],{"class":125},"\"1288:3000\"",[111,1175,1176],{"class":121},"]\n",[111,1178,1179,1182,1184,1187],{"class":113,"line":285},[111,1180,1181],{"class":1140},"    depends_on",[111,1183,1170],{"class":121},[111,1185,1186],{"class":125},"backend",[111,1188,1176],{"class":121},[111,1190,1191],{"class":113,"line":292},[111,1192,289],{"emptyLinePlaceholder":288},[111,1194,1195,1198],{"class":113,"line":305},[111,1196,1197],{"class":1140},"  backend",[111,1199,1144],{"class":121},[111,1201,1202,1204,1206],{"class":113,"line":318},[111,1203,1156],{"class":1140},[111,1205,1159],{"class":121},[111,1207,1208],{"class":125},".\u002Fbackend\n",[111,1210,1211,1213,1215,1218],{"class":113,"line":325},[111,1212,1167],{"class":1140},[111,1214,1170],{"class":121},[111,1216,1217],{"class":125},"\"18080:18080\"",[111,1219,1176],{"class":121},[111,1221,1222,1225],{"class":113,"line":467},[111,1223,1224],{"class":1140},"    environment",[111,1226,1144],{"class":121},[111,1228,1229,1232,1234],{"class":113,"line":473},[111,1230,1231],{"class":1140},"      DATABASE_URL",[111,1233,1159],{"class":121},[111,1235,1236],{"class":125},"postgresql+asyncpg:\u002F\u002Fpostgres:pass@postgres\u002Fdeepscientist\n",[111,1238,1239,1241,1243,1246,1248,1251],{"class":113,"line":484},[111,1240,1181],{"class":1140},[111,1242,1170],{"class":121},[111,1244,1245],{"class":125},"postgres",[111,1247,272],{"class":121},[111,1249,1250],{"class":125},"minio",[111,1252,1176],{"class":121},[111,1254,1255],{"class":113,"line":644},[111,1256,289],{"emptyLinePlaceholder":288},[111,1258,1259,1262],{"class":113,"line":657},[111,1260,1261],{"class":1140},"  postgres",[111,1263,1144],{"class":121},[111,1265,1266,1269,1271],{"class":113,"line":662},[111,1267,1268],{"class":1140},"    image",[111,1270,1159],{"class":121},[111,1272,1273],{"class":125},"postgres:16\n",[111,1275,1276,1279],{"class":113,"line":668},[111,1277,1278],{"class":1140},"    volumes",[111,1280,1144],{"class":121},[111,1282,1283,1286],{"class":113,"line":680},[111,1284,1285],{"class":121},"      - ",[111,1287,1288],{"class":125},"postgres_data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[111,1290,1291,1293],{"class":113,"line":686},[111,1292,1224],{"class":1140},[111,1294,1144],{"class":121},[111,1296,1297,1300,1302],{"class":113,"line":697},[111,1298,1299],{"class":1140},"      POSTGRES_PASSWORD",[111,1301,1159],{"class":121},[111,1303,1304],{"class":125},"pass\n",[111,1306,1307,1310,1312],{"class":113,"line":703},[111,1308,1309],{"class":1140},"      POSTGRES_DB",[111,1311,1159],{"class":121},[111,1313,1314],{"class":125},"deepscientist\n",[111,1316,1317],{"class":113,"line":713},[111,1318,289],{"emptyLinePlaceholder":288},[111,1320,1321,1324],{"class":113,"line":718},[111,1322,1323],{"class":1140},"  minio",[111,1325,1144],{"class":121},[111,1327,1328,1330,1332],{"class":113,"line":729},[111,1329,1268],{"class":1140},[111,1331,1159],{"class":121},[111,1333,1334],{"class":125},"minio\u002Fminio\n",[111,1336,1337,1339,1341,1344,1346,1349],{"class":113,"line":734},[111,1338,1167],{"class":1140},[111,1340,1170],{"class":121},[111,1342,1343],{"class":125},"\"9000:9000\"",[111,1345,272],{"class":121},[111,1347,1348],{"class":125},"\"9001:9001\"",[111,1350,1176],{"class":121},[111,1352,1353,1356,1358],{"class":113,"line":740},[111,1354,1355],{"class":1140},"    command",[111,1357,1159],{"class":121},[111,1359,1360],{"class":125},"server \u002Fdata --console-address \":9001\"\n",[111,1362,1363,1365],{"class":113,"line":764},[111,1364,1278],{"class":1140},[111,1366,1144],{"class":121},[111,1368,1370,1372],{"class":113,"line":1369},27,[111,1371,1285],{"class":121},[111,1373,1374],{"class":125},"minio_data:\u002Fdata\n",[111,1376,1378],{"class":113,"line":1377},28,[111,1379,289],{"emptyLinePlaceholder":288},[111,1381,1383,1386],{"class":113,"line":1382},29,[111,1384,1385],{"class":1140},"  sandbox",[111,1387,1144],{"class":121},[111,1389,1391,1393,1395],{"class":113,"line":1390},30,[111,1392,1268],{"class":1140},[111,1394,1159],{"class":121},[111,1396,1397],{"class":125},"deepscientist-sandbox\n",[111,1399,1401,1403,1405,1408,1411],{"class":113,"line":1400},31,[111,1402,1167],{"class":1140},[111,1404,1170],{"class":121},[111,1406,1407],{"class":125},"\"15900-15901:5900-5901\"",[111,1409,1410],{"class":121},"]  ",[111,1412,1413],{"class":321},"# VNC\n",[111,1415,1417],{"class":113,"line":1416},32,[111,1418,289],{"emptyLinePlaceholder":288},[111,1420,1422,1425],{"class":113,"line":1421},33,[111,1423,1424],{"class":1140},"volumes",[111,1426,1144],{"class":121},[111,1428,1430,1433],{"class":113,"line":1429},34,[111,1431,1432],{"class":1140},"  postgres_data",[111,1434,1144],{"class":121},[111,1436,1438,1441],{"class":113,"line":1437},35,[111,1439,1440],{"class":1140},"  minio_data",[111,1442,1144],{"class":121},[111,1444,1446],{"class":113,"line":1445},36,[111,1447,289],{"emptyLinePlaceholder":288},[111,1449,1451,1454],{"class":113,"line":1450},37,[111,1452,1453],{"class":1140},"networks",[111,1455,1144],{"class":121},[111,1457,1459,1462],{"class":113,"line":1458},38,[111,1460,1461],{"class":1140},"  default",[111,1463,1144],{"class":121},[111,1465,1467,1470,1472],{"class":113,"line":1466},39,[111,1468,1469],{"class":1140},"    name",[111,1471,1159],{"class":121},[111,1473,1474],{"class":125},"deepscientist-network\n",[10,1476,1477,1478,1480,1481,1483],{},"服务间通过容器名互相访问（",[108,1479,1245],{},"、",[108,1482,1250],{},"），不需要硬编码 IP。",[19,1485,1487],{"id":1486},"用-docker-sdk-动态管理容器","用 Docker SDK 动态管理容器",[10,1489,1490],{},"DeepScientist 的 AI 沙箱不是固定的，而是按需创建：",[101,1492,1494],{"className":103,"code":1493,"language":105,"meta":106,"style":106},"import docker\n\nclient = docker.from_env()\n\ndef create_sandbox(user_id: str) -> str:\n    container = client.containers.run(\n        \"deepscientist-sandbox\",\n        detach=True,\n        name=f\"sandbox-{user_id}\",\n        ports={\"5900\u002Ftcp\": None},   # 随机分配宿主机端口\n        mem_limit=\"2g\",\n        cpu_period=100000,\n        cpu_quota=50000,            # 限制 50% CPU\n        network=\"deepscientist-network\",\n    )\n    # 获取实际分配的端口\n    port = client.containers.get(container.id).ports[\"5900\u002Ftcp\"][0][\"HostPort\"]\n    return port\n\ndef destroy_sandbox(user_id: str):\n    try:\n        container = client.containers.get(f\"sandbox-{user_id}\")\n        container.stop()\n        container.remove()\n    except docker.errors.NotFound:\n        pass\n",[108,1495,1496,1503,1507,1517,1521,1541,1551,1558,1570,1594,1617,1629,1641,1657,1669,1673,1678,1703,1710,1714,1727,1734,1758,1763,1768,1776],{"__ignoreMap":106},[111,1497,1498,1500],{"class":113,"line":114},[111,1499,366],{"class":142},[111,1501,1502],{"class":121}," docker\n",[111,1504,1505],{"class":113,"line":139},[111,1506,289],{"emptyLinePlaceholder":288},[111,1508,1509,1512,1514],{"class":113,"line":168},[111,1510,1511],{"class":121},"client ",[111,1513,162],{"class":142},[111,1515,1516],{"class":121}," docker.from_env()\n",[111,1518,1519],{"class":113,"line":188},[111,1520,289],{"emptyLinePlaceholder":288},[111,1522,1523,1526,1529,1532,1534,1537,1539],{"class":113,"line":285},[111,1524,1525],{"class":142},"def",[111,1527,1528],{"class":117}," create_sandbox",[111,1530,1531],{"class":121},"(user_id: ",[111,1533,388],{"class":155},[111,1535,1536],{"class":121},") -> ",[111,1538,388],{"class":155},[111,1540,1144],{"class":121},[111,1542,1543,1546,1548],{"class":113,"line":292},[111,1544,1545],{"class":121},"    container ",[111,1547,162],{"class":142},[111,1549,1550],{"class":121}," client.containers.run(\n",[111,1552,1553,1556],{"class":113,"line":305},[111,1554,1555],{"class":125},"        \"deepscientist-sandbox\"",[111,1557,583],{"class":121},[111,1559,1560,1563,1565,1568],{"class":113,"line":318},[111,1561,1562],{"class":263},"        detach",[111,1564,162],{"class":142},[111,1566,1567],{"class":155},"True",[111,1569,583],{"class":121},[111,1571,1572,1575,1577,1580,1583,1585,1587,1590,1592],{"class":113,"line":325},[111,1573,1574],{"class":263},"        name",[111,1576,162],{"class":142},[111,1578,1579],{"class":142},"f",[111,1581,1582],{"class":125},"\"sandbox-",[111,1584,421],{"class":129},[111,1586,756],{"class":121},[111,1588,1589],{"class":129},"}",[111,1591,133],{"class":125},[111,1593,583],{"class":121},[111,1595,1596,1599,1601,1603,1606,1608,1611,1614],{"class":113,"line":467},[111,1597,1598],{"class":263},"        ports",[111,1600,162],{"class":142},[111,1602,421],{"class":121},[111,1604,1605],{"class":125},"\"5900\u002Ftcp\"",[111,1607,1159],{"class":121},[111,1609,1610],{"class":155},"None",[111,1612,1613],{"class":121},"},   ",[111,1615,1616],{"class":321},"# 随机分配宿主机端口\n",[111,1618,1619,1622,1624,1627],{"class":113,"line":473},[111,1620,1621],{"class":263},"        mem_limit",[111,1623,162],{"class":142},[111,1625,1626],{"class":125},"\"2g\"",[111,1628,583],{"class":121},[111,1630,1631,1634,1636,1639],{"class":113,"line":484},[111,1632,1633],{"class":263},"        cpu_period",[111,1635,162],{"class":142},[111,1637,1638],{"class":155},"100000",[111,1640,583],{"class":121},[111,1642,1643,1646,1648,1651,1654],{"class":113,"line":644},[111,1644,1645],{"class":263},"        cpu_quota",[111,1647,162],{"class":142},[111,1649,1650],{"class":155},"50000",[111,1652,1653],{"class":121},",            ",[111,1655,1656],{"class":321},"# 限制 50% CPU\n",[111,1658,1659,1662,1664,1667],{"class":113,"line":657},[111,1660,1661],{"class":263},"        network",[111,1663,162],{"class":142},[111,1665,1666],{"class":125},"\"deepscientist-network\"",[111,1668,583],{"class":121},[111,1670,1671],{"class":113,"line":662},[111,1672,487],{"class":121},[111,1674,1675],{"class":113,"line":668},[111,1676,1677],{"class":321},"    # 获取实际分配的端口\n",[111,1679,1680,1683,1685,1688,1690,1693,1696,1698,1701],{"class":113,"line":680},[111,1681,1682],{"class":121},"    port ",[111,1684,162],{"class":142},[111,1686,1687],{"class":121}," client.containers.get(container.id).ports[",[111,1689,1605],{"class":125},[111,1691,1692],{"class":121},"][",[111,1694,1695],{"class":155},"0",[111,1697,1692],{"class":121},[111,1699,1700],{"class":125},"\"HostPort\"",[111,1702,1176],{"class":121},[111,1704,1705,1707],{"class":113,"line":686},[111,1706,191],{"class":142},[111,1708,1709],{"class":121}," port\n",[111,1711,1712],{"class":113,"line":697},[111,1713,289],{"emptyLinePlaceholder":288},[111,1715,1716,1718,1721,1723,1725],{"class":113,"line":703},[111,1717,1525],{"class":142},[111,1719,1720],{"class":117}," destroy_sandbox",[111,1722,1531],{"class":121},[111,1724,388],{"class":155},[111,1726,232],{"class":121},[111,1728,1729,1732],{"class":113,"line":713},[111,1730,1731],{"class":142},"    try",[111,1733,1144],{"class":121},[111,1735,1736,1739,1741,1744,1746,1748,1750,1752,1754,1756],{"class":113,"line":718},[111,1737,1738],{"class":121},"        container ",[111,1740,162],{"class":142},[111,1742,1743],{"class":121}," client.containers.get(",[111,1745,1579],{"class":142},[111,1747,1582],{"class":125},[111,1749,421],{"class":129},[111,1751,756],{"class":121},[111,1753,1589],{"class":129},[111,1755,133],{"class":125},[111,1757,136],{"class":121},[111,1759,1760],{"class":113,"line":729},[111,1761,1762],{"class":121},"        container.stop()\n",[111,1764,1765],{"class":113,"line":734},[111,1766,1767],{"class":121},"        container.remove()\n",[111,1769,1770,1773],{"class":113,"line":740},[111,1771,1772],{"class":142},"    except",[111,1774,1775],{"class":121}," docker.errors.NotFound:\n",[111,1777,1778],{"class":113,"line":764},[111,1779,1780],{"class":142},"        pass\n",[10,1782,1783,1784,1787],{},"用户退出时调用 ",[108,1785,1786],{},"destroy_sandbox","，资源立即释放。",[19,1789,1791],{"id":1790},"docker-vs-kubernetes","Docker vs Kubernetes",[10,1793,1794],{},"面试常问：",[26,1796,1797,1809],{},[29,1798,1799],{},[32,1800,1801,1803,1806],{},[35,1802],{},[35,1804,1805],{},"Docker Compose",[35,1807,1808],{},"Kubernetes",[48,1810,1811,1822,1833,1844,1855],{},[32,1812,1813,1816,1819],{},[53,1814,1815],{},"适合规模",[53,1817,1818],{},"单机 \u002F 小团队",[53,1820,1821],{},"多节点集群",[32,1823,1824,1827,1830],{},[53,1825,1826],{},"学习成本",[53,1828,1829],{},"低",[53,1831,1832],{},"高",[32,1834,1835,1838,1841],{},[53,1836,1837],{},"自动扩缩容",[53,1839,1840],{},"不支持",[53,1842,1843],{},"支持",[32,1845,1846,1849,1852],{},[53,1847,1848],{},"服务发现",[53,1850,1851],{},"容器名",[53,1853,1854],{},"DNS + Service",[32,1856,1857,1859,1862],{},[53,1858,46],{},[53,1860,1861],{},"开发环境、小型生产",[53,1863,1864],{},"大规模生产",[10,1866,1867],{},"DeepScientist 目前用 Compose，够用。如果用户量上去了，迁移到 K8s 的成本也不高，因为 Compose 和 K8s 的概念是对应的。",[504,1869],{},[14,1871,1872],{"id":1872},"面试常见问题",[10,1874,1875],{},[97,1876,1877],{},"Q：FastAPI 和 Flask 的区别？",[10,1879,1880],{},"FastAPI 原生 async，性能接近 Node.js；Pydantic 自动校验省去大量手写代码；自动生成 OpenAPI 文档。Flask 更灵活但需要自己组装异步支持和校验逻辑。I\u002FO 密集型 API 服务首选 FastAPI。",[10,1882,1883],{},[97,1884,1885],{},"Q：SQLAlchemy ORM 和原生 SQL 怎么选？",[10,1887,1888,1889,1892],{},"简单 CRUD 用 ORM，开发快、类型安全。复杂查询（多表 JOIN、窗口函数、批量操作）用原生 SQL 或 ",[108,1890,1891],{},"text()","，性能更可控。两者可以混用，SQLAlchemy 支持直接执行原生 SQL。",[10,1894,1895],{},[97,1896,1897],{},"Q：数据库连接池的作用？",[10,1899,1900],{},"建立数据库连接有握手开销（TCP + 认证），每次请求都新建连接会很慢。连接池维护一组复用的连接，请求来了直接取，用完归还，避免重复建连开销。",[10,1902,1903],{},[97,1904,1905],{},"Q：Docker 容器和虚拟机的区别？",[10,1907,1908],{},"虚拟机模拟完整硬件，有独立 OS，隔离性强但开销大（GB 级镜像，秒级启动）。容器共享宿主机内核，只隔离进程和文件系统，镜像小（MB 级），启动毫秒级。容器不是完全隔离的，安全敏感场景还是用 VM。",[10,1910,1911],{},[97,1912,1913,1914,1917],{},"Q：",[108,1915,1916],{},"depends_on"," 能保证服务启动顺序吗？",[10,1919,1920,1921,498,1924,1927],{},"只能保证容器启动顺序，不能保证服务就绪。PostgreSQL 容器启动了不代表数据库已经可以接受连接。生产环境要在应用代码里加重试逻辑，或者用 ",[108,1922,1923],{},"healthcheck",[108,1925,1926],{},"condition: service_healthy","。",[1929,1930,1931],"style",{},"html pre.shiki code .saVmf, html code.shiki .saVmf{--shiki-default:#DCBDFB;--shiki-light:#6F42C1}html pre.shiki code .ssh_m, html code.shiki .ssh_m{--shiki-default:#ADBAC7;--shiki-light:#24292E}html pre.shiki code .sXfbr, html code.shiki .sXfbr{--shiki-default:#96D0FF;--shiki-light:#032F62}html pre.shiki code .sxsTv, html code.shiki .sxsTv{--shiki-default:#F47067;--shiki-light:#005CC5}html pre.shiki code .s6PUj, html code.shiki .s6PUj{--shiki-default:#F47067;--shiki-light:#D73A49}html pre.shiki code .swcJU, html code.shiki .swcJU{--shiki-default:#6CB6FF;--shiki-light:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sqRhv, html code.shiki .sqRhv{--shiki-default:#F69D50;--shiki-light:#6F42C1}html pre.shiki code .sr-dd, html code.shiki .sr-dd{--shiki-default:#6CB6FF;--shiki-light:#6F42C1}html pre.shiki code .sNjOc, html code.shiki .sNjOc{--shiki-default:#F69D50;--shiki-light:#E36209}html pre.shiki code .sgHix, html code.shiki .sgHix{--shiki-default:#768390;--shiki-light:#6A737D}html pre.shiki code .shb1k, html code.shiki .shb1k{--shiki-default:#8DDB8C;--shiki-light:#22863A}",{"title":106,"searchDepth":139,"depth":168,"links":1933},[1934,1938,1943,1948,1954],{"id":16,"depth":139,"text":17,"children":1935},[1936,1937],{"id":21,"depth":168,"text":21},{"id":346,"depth":168,"text":347},{"id":508,"depth":139,"text":509,"children":1939},[1940,1941,1942],{"id":512,"depth":168,"text":513},{"id":539,"depth":168,"text":539},{"id":773,"depth":168,"text":774},{"id":860,"depth":139,"text":861,"children":1944},[1945,1946,1947],{"id":864,"depth":168,"text":865},{"id":937,"depth":168,"text":937},{"id":946,"depth":168,"text":946},{"id":1037,"depth":139,"text":1038,"children":1949},[1950,1951,1952,1953],{"id":1041,"depth":168,"text":1041},{"id":1127,"depth":168,"text":1128},{"id":1486,"depth":168,"text":1487},{"id":1790,"depth":168,"text":1791},{"id":1872,"depth":139,"text":1872},[1956],"技术","2026-04-07",false,"md",{},"\u002Fposts\u002Fbackend-stack-deep-dive",{"title":5,"description":12},"posts\u002Fbackend-stack-deep-dive",[1965,17,861,1038,1966],"后端","面试","7KYsGE3RSm48En2qvEp1DSSFH8VKBZPxRRxFOffK8-w",[1969,1982,1994,2000,2011,2020,2028,2038,2048,2057,2066,2077,2089,2101,2109,2119,2131,2142,2152,2160,2171,2177,2183,2189,2197,2200,2208,2214,2222,2230],{"slug":1970,"path":1971,"title":1972,"date":1973,"tags":1974,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":325},"multimodal-rag-from-scratch","\u002Fposts\u002Fmultimodal-rag-from-scratch","从零实现多模态 RAG：BM25、Dense 检索、RRF 融合、MMR 重排全部手写","2026-06-30 18:00:00",[1975,1976,1977,1978,1979,1980,1981],"RAG","多模态","AI Infra","BM25","向量检索","混合检索","实习求职",{"slug":1983,"path":1984,"title":1985,"date":1986,"tags":1987,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":473},"mini-llm-engine-deep-dive","\u002Fposts\u002Fmini-llm-engine-deep-dive","讲透 mini-llm-engine：从显存碎片到六大推理优化","2026-06-30 14:00:00",[1988,1977,1989,1990,1991,1992,1993,1981],"LLM","vLLM","PagedAttention","KV Cache","推理优化","投机解码",{"slug":1995,"path":1996,"title":1997,"date":1998,"tags":1999,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":467},"mini-llm-engine-from-scratch","\u002Fposts\u002Fmini-llm-engine-from-scratch","从零实现 LLM 推理引擎：深挖 vLLM 的六大核心优化","2026-06-30 10:00:00",[1988,1977,1989,1990,1992,1993,1981],{"slug":2001,"path":2002,"title":2003,"date":2004,"tags":2005,"description":2010,"draft":1958,"hidden":1958,"published":288,"readingTime":686},"bytedance-recommendation-architecture-intern-interview","\u002Fposts\u002Fbytedance-recommendation-architecture-intern-interview","字节推荐架构实习生 Data 面试准备：推荐系统、实时特征与高并发八股","2026-06-09",[1966,2006,2007,2008,2009],"推荐系统","后端架构","实时计算","字节跳动","面向字节跳动推荐架构团队实习岗位的八股准备清单，覆盖推荐系统链路、实时数据、特征服务、高并发后端、分布式系统与项目包装。",{"slug":2012,"path":2013,"title":2014,"date":2004,"tags":2015,"description":2019,"draft":1958,"hidden":1958,"published":288,"readingTime":484},"high-concurrency-rate-limiting-algorithms","\u002Fposts\u002Fhigh-concurrency-rate-limiting-algorithms","高并发限流算法：固定窗口、滑动窗口、漏桶与令牌桶",[2016,2017,2007,2018,1966],"高并发","限流","系统设计","面试和工程都常见的限流算法总结，讲清楚固定窗口、滑动窗口、漏桶、令牌桶、并发数限制以及分布式限流如何落地。",{"slug":2021,"path":2022,"title":2023,"date":2004,"tags":2024,"description":2027,"draft":1958,"hidden":1958,"published":288,"readingTime":318},"kafka-producer-broker-consumer","\u002Fposts\u002Fkafka-producer-broker-consumer","Kafka 入门：生产者、Broker、消费者和“消费”到底是什么意思",[2025,2026,1965,1966,2006],"Kafka","消息队列","用推荐系统里的用户行为日志为例，讲清楚 Kafka 的作用、Producer、Broker、Consumer、Topic、Partition、Offset 和消费语义。",{"slug":2029,"path":2030,"title":2031,"date":2004,"tags":2032,"description":2037,"draft":1958,"hidden":1958,"published":288,"readingTime":292},"leetcode-lru-merge-k-reverse-list","\u002Fposts\u002Fleetcode-lru-merge-k-reverse-list","链表与缓存高频题：LRU Cache、合并 K 个有序链表、反转链表",[2033,2034,2035,2036,1966],"算法","链表","LRU","LeetCode","面试高频算法题速记，整理 LRU Cache、合并 K 个有序链表、反转链表的核心思路、复杂度和 C++ 代码。",{"slug":2039,"path":2040,"title":2041,"date":2042,"tags":2043,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":668},"meddococr-interpreter-source-analysis","\u002Fposts\u002Fmeddococr-interpreter-source-analysis","MedDocOCR-Interpreter 源码导读：医疗文档 OCR、结构化抽取与报告解读原型","2026-05-22 10:30:00",[2044,1976,2045,1975,2046,2047],"OCR","医疗 AI","Python","源码分析",{"slug":2049,"path":2050,"title":2051,"date":2052,"tags":2053,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":467},"cet6-writing-model-essays","\u002Fposts\u002Fcet6-writing-model-essays","六级写作范文背诵包：10 个高频话题","2026-05-20 09:00:00",[2054,2055,2056],"English","CET6","Writing",{"slug":2058,"path":2059,"title":2060,"date":2061,"tags":2062,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":1369},"claude-code-context-management","\u002Fposts\u002Fclaude-code-context-management","Claude Code 上下文管理机制：从 Microcompact 到 Auto Compact","2026-05-19 10:00:00",[2063,2064,1988,1977,2065],"Claude Code","Agent","上下文工程",{"slug":2067,"path":2068,"title":2069,"date":2070,"tags":2071,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":697},"nanotron-llm-pretraining-framework-analysis","\u002Fposts\u002Fnanotron-llm-pretraining-framework-analysis","Nanotron 项目详解：Hugging Face 的大模型预训练框架怎么做分布式训练","2026-05-10 12:10:00",[1988,2072,2073,2074,2075,1977,2076],"大模型训练","分布式训练","Nanotron","Hugging Face","PyTorch",{"slug":2078,"path":2079,"title":2080,"date":2081,"tags":2082,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":740},"gofoundry-go-backend-foundation-framework","\u002Fposts\u002Fgofoundry-go-backend-foundation-framework","GoFoundry 项目详解：基于 Go 的后端基础框架套件设计","2026-05-10 11:20:00",[2083,2084,2085,2086,2087,2026,2088],"Go","后端框架","ORM","分布式缓存","分布式锁","项目架构",{"slug":2090,"path":2091,"title":2092,"date":2093,"tags":2094,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":1369},"cloudvault-go-cloud-storage-system","\u002Fposts\u002Fcloudvault-go-cloud-storage-system","CloudVault 项目详解：基于 Go 的云端存储与网盘系统架构设计","2026-05-10 10:30:00",[2083,2095,2096,2097,2088,2098,2099,2100],"云存储","网盘系统","分布式系统","Redis","RabbitMQ","Elasticsearch",{"slug":2102,"path":2103,"title":2104,"date":2105,"tags":2106,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":703},"openclaw-source-code-analysis","\u002Fposts\u002Fopenclaw-source-code-analysis","OpenClaw 源码导读：个人 AI 助手的网关、通道、插件与运行时架构","2026-05-08 16:30:00",[2107,2064,1977,2108,2047],"OpenClaw","TypeScript",{"slug":2110,"path":2111,"title":2112,"date":2113,"tags":2114,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":467},"flow-matching-generative-models","\u002Fposts\u002Fflow-matching-generative-models","Flow Matching：从噪声到数据的连续流生成模型","2026-05-07 00:00:00",[2115,2116,2117,2118],"生成模型","Diffusion","Flow Matching","深度学习",{"slug":2120,"path":2121,"title":2122,"date":2123,"tags":2124,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":662},"database-ai-week4","\u002Fposts\u002Fdatabase-ai-week4","Week 4：数据库速成——从 Storage、Index、Query Optimization 到 Vector DB 与 RAG","2026-05-05 12:00:00",[2125,2126,2127,1975,2128,2129,2130],"数据库","CMU 15-445","Vector DB","LLM Memory","Query Optimization","Caching",{"slug":2132,"path":2133,"title":2134,"date":2135,"tags":2136,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":668},"distributed-systems-week3","\u002Fposts\u002Fdistributed-systems-week3","Week 3：分布式系统速成——MapReduce、Raft、容错与 Distributed KV Store","2026-05-05 11:00:00",[2097,2137,2138,2139,2140,2141,2064],"MIT 6.824","MapReduce","Raft","KV Store","Ray",{"slug":2143,"path":2144,"title":2145,"date":2146,"tags":2147,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":662},"gpu-inference-acceleration-week2","\u002Fposts\u002Fgpu-inference-acceleration-week2","Week 2：GPU 与推理加速——从 Kernel、算子融合到 LLM Serving","2026-05-05 10:00:00",[2118,2148,2149,1988,2150,1989,2151],"GPU","推理加速","CMU 10-414","TensorRT",{"slug":2153,"path":2154,"title":2155,"date":2156,"tags":2157,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":686},"dl-framework-autograd-mini","\u002Fposts\u002Fdl-framework-autograd-mini","Week 1：DL 框架与 Autograd——从计算图、反向传播到 Mini Autograd 实现","2026-05-05 09:00:00",[2118,2158,2076,2150,2159],"Autograd","Mini Framework",{"slug":2161,"path":2162,"title":2163,"date":2164,"tags":2165,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":484},"lock-free-concurrency-notes","\u002Fposts\u002Flock-free-concurrency-notes","无锁并发入门：从 CAS 到 Atomic Ring Buffer","2026-04-25",[2166,2167,2168,2169,2170],"C++","并发","无锁编程","性能优化","量化开发",{"slug":2172,"path":2173,"title":2174,"date":2175,"tags":2176,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":285},"agent-memory","\u002Fposts\u002Fagent-memory","Agent 对话记忆化：从原理到实现","2026-04-24",[1988,2064,1975,1966],{"slug":2178,"path":2179,"title":2180,"date":2175,"tags":2181,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":292},"llm-context-compression","\u002Fposts\u002Fllm-context-compression","LLM 上下文五层压缩机制详解",[1988,2064,2182,1966],"上下文压缩",{"slug":2184,"path":2185,"title":2186,"date":2187,"tags":2188,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":325},"cpp-concurrency-basics","\u002Fposts\u002Fcpp-concurrency-basics","C++ 并发编程入门：从数据竞争到线程池","2026-04-15",[2166,2167,1966,2170],{"slug":2190,"path":2191,"title":2192,"date":2193,"tags":2194,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":114},"travel-in-shenzhen","\u002Fposts\u002Ftravel-in-shenzhen","XCPC 深圳游记","2026-04-13",[2195,2166,2196],"XCPC","比赛",{"slug":2198,"path":1961,"title":5,"date":1957,"tags":2199,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":305},"backend-stack-deep-dive",[1965,17,861,1038,1966],{"slug":2201,"path":2202,"title":2203,"date":2204,"tags":2205,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":292},"deepscientist-tech-stack","\u002Fposts\u002Fdeepscientist-tech-stack","DeepScientist 技术栈全解析：一个 AI 科研平台的架构设计","2026-04-06",[2206,17,2207,861,1966],"全栈","Next.js",{"slug":2209,"path":2210,"title":2211,"date":2204,"tags":2212,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":318},"minicode-source-analysis","\u002Fposts\u002Fminicode-source-analysis","MiniCode 源码解析：用 5000 行 TypeScript 实现一个 AI 编程助手",[2108,2213,1988,2047,1966],"CLI",{"slug":2215,"path":2216,"title":2217,"date":2204,"tags":2218,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":285},"nova-theme-implementation","\u002Fposts\u002Fnova-theme-implementation","我是怎么从零实现 Nova 主题的",[2219,2220,2221],"Hexo","前端","开源",{"slug":2223,"path":2224,"title":2225,"date":2226,"tags":2227,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":139},"git-cheatsheet","\u002Fposts\u002Fgit-cheatsheet","Git 常用操作备忘","2026-04-05 14:00:00",[2228,2229],"Git","工具",{"slug":2231,"path":2232,"title":2233,"date":2234,"tags":2235,"description":106,"draft":1958,"hidden":1958,"published":288,"readingTime":168},"github-actions-intro","\u002Fposts\u002Fgithub-actions-intro","GitHub Actions 入门：自动化你的工作流","2026-04-04 09:00:00",[2236,2237,2238],"GitHub Actions","CI\u002FCD","自动化",1782796011451]