1. 项目概述为什么用C重写一个“苍穹外卖”看到这个标题很多朋友第一反应可能是市面上不是已经有成熟的Java版“苍穹外卖”项目了吗为什么还要用C再搞一遍这不是重复造轮子吗这正是我想聊的起点。作为一个在后台系统开发里摸爬滚打了十来年的老码农我见过太多项目初期为了快速上线选择了Java、Python这类开发效率高的语言。但随着业务量滚雪球般增长系统开始“咳嗽”——高峰期订单处理延迟、数据库连接池被打满、内存占用居高不下。这时候团队往往面临两难要么投入大量人力做架构重构和性能调优要么忍受越来越差的用户体验。“苍穹外卖”作为一个典型的高并发、实时性要求强的业务场景恰恰是检验一门语言底层性能和控制力的绝佳试金石。用Java实现可以快速验证业务逻辑享受Spring生态的便利。但用C来实现则是另一种维度的挑战和收获你需要亲手管理内存、设计高效的数据结构、榨干CPU的每一分算力去构建一个理论上响应更快、资源占用更少、在同等硬件下能承载更高并发的系统内核。这个项目不是一个简单的“翻译”工作。它是一次从应用层思维到底层系统思维的穿越。你会直面指针、内存管理、多线程同步、网络I/O模型这些C的“硬核”部分并思考如何将它们优雅地组织起来支撑起一个完整的外卖业务。对于想深入理解计算机系统如何工作以及如何构建高性能服务的开发者来说这是一次不可多得的实战演练。2. 核心需求与架构设计思路2.1 业务场景与核心挑战拆解在动手写第一行代码之前我们必须先把外卖系统的核心业务逻辑和面临的挑战想清楚。一个外卖平台核心无外乎“人”、“货”、“场”的匹配与流转。核心角色与流程消费者浏览商家/菜品 - 下单支付 - 等待配送 - 确认收货/评价。商家维护菜品信息 - 接收并处理订单 - 出餐。骑手抢单/派单 - 到店取餐 - 配送至消费者。平台系统作为中枢需要协调以上所有流程并确保其正确、高效、可靠。由此我们可以提炼出几个关键的技术挑战高并发读首页商家列表、菜品详情这些是读多写少的典型需要高效的缓存策略。高并发写与强一致性下单、支付、库存扣减。这里涉及到“秒杀”场景热门菜品和资金交易对数据的一致性和事务性要求极高。实时性订单状态从商家接单、出餐、骑手取餐到送达需要近乎实时地推送给用户和骑手。这通常需要WebSocket或长轮询支持。地理位置服务为消费者推荐附近商家为骑手进行智能派单/抢单都依赖于高效的地理位置计算如GeoHash。高可用与可扩展外卖业务有明显的波峰波谷午高峰、晚高峰系统必须能弹性伸缩并且在任何单点故障时不影响核心流程。2.2 C技术栈选型与架构蓝图明确了需求接下来就是选择趁手的“兵器”并勾勒架构。用C做Web后端和用Java/Go的思路有显著不同。我们更关注性能和控制力同时也要避免陷入“刀耕火种”的原始状态。以下是我的选型思路1. 网络通信框架这是后端系统的基石。我不会推荐从socket、epoll开始裸写那会极大拖慢项目进度。成熟的网络库是更好的起点。候选Boost.Asio或libevent。最终选择与理由Boost.Asio。它是一个跨平台的、基于前摄器设计模式的异步I/O库模型现代性能强劲且是C标准库网络化的重要参考。用它我们可以轻松构建出高性能的HTTP/WebSocket服务器。虽然学习曲线稍陡但一旦掌握对理解异步编程模型大有裨益。2. HTTP/Web应用框架我们需要解析HTTP请求、路由到对应的处理函数。自己基于Asio封装一套太耗时。候选cpp-httplib,drogon,Crow。最终选择与理由drogon。这是一个国产的、基于C14/17的异步Web应用框架底层基于epollLinux或IOCPWindows性能卓越。它提供了ORM、模板引擎、插件系统等一站式解决方案能极大提升开发效率让我们的精力更聚焦于业务逻辑而非底层轮子。3. 数据库与ORM外卖系统数据关系复杂关系型数据库是主力。数据库MySQL。生态成熟事务支持完善是业务数据存储的不二之选。ORMdrogon框架内置了基于libpqPostgreSQL和mysqlclient的ORM组件我们可以直接使用。它支持模型定义、链式查询、事务等能有效降低手写SQL的繁琐和错误。4. 缓存应对高并发读Redis必不可少。客户端库hiredis是官方推荐的C客户端轻量高效。我们可以在drogon中通过插件或单例模式封装一个Redis工具类。5. 消息队列用于解耦耗时操作如发短信、更新搜索索引和削峰填谷如集中式的订单创建。选择Redis Streams或RabbitMQ (C客户端)。理由在项目初期如果系统复杂度不高可以优先使用Redis Streams。它同样是Redis生态的一部分无需引入新的中间件能满足基本的消息持久化和消费组需求。如果后续需要更复杂的路由、确认机制再迁移到RabbitMQ。6. 构建与依赖管理现代C项目离不开好的构建工具。构建系统CMake。行业标准跨平台支持好。包管理vcpkg或Conan。用于管理drogon、hiredis、mysqlclient等第三方库的依赖。这里我推荐vcpkg与Visual Studio和CMake集成较好。架构蓝图基于以上我们可以设计一个分层架构网络接入层由drogon框架承载负责HTTP/WebSocket协议的解析、路由分发。业务逻辑层核心所在包含用户、订单、支付、配送等各个业务模块的服务类。数据访问层封装对MySQL通过ORM和Redis通过hiredis的操作。异步任务层基于drogon的异步特性或单独的线程池处理非实时任务并通过消息队列与其他模块通信。整个系统以异步非阻塞为核心思想利用C的高效和drogon框架的现代化设施目标是构建一个响应迅速、资源利用率高的后端服务。3. 核心模块设计与实现要点3.1 用户与商家服务从数据模型到接口任何系统都始于数据模型。我们首先设计核心的实体。数据模型设计以MySQL为例-- 用户表 CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, openid varchar(255) DEFAULT NULL COMMENT 微信openid, name varchar(50) DEFAULT NULL COMMENT 姓名, phone varchar(11) DEFAULT NULL COMMENT 手机号, sex varchar(2) DEFAULT NULL COMMENT 性别, id_number varchar(18) DEFAULT NULL COMMENT 身份证号, avatar varchar(500) DEFAULT NULL COMMENT 头像, create_time datetime NOT NULL COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY idx_openid (openid) USING BTREE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户信息; -- 商家表 CREATE TABLE shop ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, name varchar(255) NOT NULL COMMENT 店铺名称, address varchar(255) NOT NULL COMMENT 店铺地址, latitude decimal(10,7) NOT NULL COMMENT 纬度, longitude decimal(10,7) NOT NULL COMMENT 经度, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 营业状态 1:营业 0:打烊, create_time datetime NOT NULL COMMENT 创建时间, update_time datetime NOT NULL COMMENT 更新时间, PRIMARY KEY (id), SPATIAL KEY idx_location (longitude,latitude) -- 空间索引用于附近查询 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商家信息;注意商家表增加了地理位置字段和空间索引。虽然MySQL的空间索引功能相对简单但对于初版“附近商家”查询足够。后期若性能成为瓶颈可迁移至专门的GIS数据库或使用Redis Geo。C模型定义使用drogon的ORM// User.h namespace model { class User : public drogon::orm::ModelUser { public: struct PrimaryKeyType { int64_t id; PrimaryKeyType() default; explicit PrimaryKeyType(const int64_t id_) : id(id_) {} operator int64_t() const { return id; } }; static const std::string tableName() { return staticTableName(); } static const std::string staticTableName() { static std::string name user; return name; } void setId(const int64_t id) { id_ std::make_sharedint64_t(id); } const int64_t getId() const { return *id_; } // ... 其他字段的getter/setter void setOpenid(const std::string openid) { openid_ std::make_sharedstd::string(openid); } const std::string getOpenid() const { return *openid_; } // 关系定义例如一个用户有多个订单 drogon::orm::HasManyOrder orders() const; private: std::shared_ptrint64_t id_; std::shared_ptrstd::string openid_; // ... 其他字段 }; } // namespace model接口实现要点用户登录以微信登录为例是一个典型流程。客户端调用微信接口获取code。服务端接口/api/v1/user/login接收code。向微信服务器发起请求用code换取openid和session_key。这里必须用HTTPS且session_key绝不能下发到客户端以openid为唯一标识查询用户表。如果用户不存在则插入一条新记录。生成一个自定义的Token如JWT或UUID将用户ID、openid等信息存入并设置过期时间。将Token与用户ID的映射关系存入Redis键如session:token值为用户ID设置相同的过期时间。这是实现会话状态和快速登录验证的关键。将Token返回给客户端客户端后续请求需在Header如Authorization: Bearer token中携带。实操心得Token管理Token最好采用无状态的结构如JWT但将Token-UserID的映射缓存在Redis中可以实现“主动踢下线”或“修改密码后使旧Token失效”的功能兼顾了无状态的扩展性和有状态的控制力。安全性所有涉及用户隐私的接口如获取手机号必须验证Token并且业务逻辑中要二次校验当前操作的用户是否与Token中的用户一致防止越权。连接池在drogon的配置文件中务必正确配置MySQL和Redis的连接池参数最大连接数、最小连接数、超时时间。高峰期连接数不足会导致请求排队甚至失败。3.2 订单与支付系统的核心逻辑这是系统最复杂、对一致性要求最高的部分。核心流程下单 - 扣减库存 - 创建订单 - 支付 - 回调更新状态。1. 下单与库存扣减——如何防超卖超卖是电商、外卖系统的经典问题。在C中我们更要谨慎处理。方案一数据库乐观锁。在商品表中增加一个version字段或使用stock 0的条件判断。UPDATE dish SET stock stock - 1, version version 1 WHERE id ? AND stock 0 AND version ?;如果更新影响行数为0说明库存不足或版本号不对返回失败。这是最通用的方案。方案二Redis原子操作。将库存预热到Redis中利用DECR或DECRBY的原子性进行扣减。// 伪代码 long long remain redisCommand(DECR, key.c_str()); if (remain 0) { // 库存不足需要回滚INCR redisCommand(INCR, key.c_str()); return Failure(库存不足); }注意Redis扣减成功后需要有一个异步任务或最终一致性方案将扣减结果同步回数据库。同时要处理Redis宕机导致数据丢失的风险可通过持久化或数据库作为最终备份来缓解。2. 创建订单——本地事务订单主表order、订单明细表order_detail的插入必须在同一个数据库事务中完成。drogon::app().getDbClient()-execSqlSync(...); // 非异步示例实际应用异步接口 trans-execSql(INSERT INTO order (...) VALUES (...);); for (auto item : cartItems) { trans-execSql(INSERT INTO order_detail (...) VALUES (...);); } trans-commit();使用drogon的ORM可以通过Mapper的transaction方法更优雅地管理事务。3. 支付与回调——保证最终一致性支付通常引入第三方微信支付、支付宝。流程如下下单成功后调用第三方支付接口生成预支付交易单。将订单ID和第三方交易单号的关联关系存入数据库状态为“待支付”并存入Redis设置一个较短的过期时间如30分钟用于后续回调快速查找订单。将支付参数返回给客户端引导用户调起支付。支付回调第三方支付平台会异步通知我们支付结果。这个回调接口必须幂等无论收到多少次相同通知处理结果都一样。可以通过在数据库中记录回调处理状态来实现。验签验证回调请求的签名确保来源合法。快速响应验证通过后先更新Redis中订单状态或直接更新数据库然后立刻返回SUCCESS给支付平台避免对方重试。复杂的后续业务发短信、更新统计放入消息队列异步处理。补偿机制对于超过Redis过期时间仍未收到回调的订单需要有定时任务扫描数据库中“待支付”状态的订单主动去支付平台查询状态进行对账和状态同步。踩坑记录事务隔离级别MySQL默认的REPEATABLE READ在涉及多行更新时可能导致死锁。对于库存扣减这类高并发更新可以考虑使用SELECT ... FOR UPDATE悲观锁或尝试将隔离级别调整为READ COMMITTED并配合重试机制。Redis与数据库的数据同步这是一个经典的缓存-数据库一致性问题。我们的策略是库存扣减以Redis为准扣减成功后发送消息同步数据库订单状态以数据库为准通过数据库更新事件来淘汰或更新Redis缓存。允许极短时间的不一致但通过补偿任务保证最终一致。3.3 实时配送与地理位置服务实时追踪骑手位置、智能派单是外卖系统的亮点。核心是WebSocket和地理位置计算。1. WebSocket长连接管理使用drogon框架可以很方便地创建WebSocket控制器。连接建立骑手APP登录后建立WebSocket连接服务端将连接与骑手ID关联存入一个全局的线程安全映射表如std::unordered_mapriderId, WebSocketConnectionPtr或更结构化的连接管理器。心跳保活客户端定时发送心跳包服务端定时检查断开超时连接。消息推送当订单状态变化如商家已接单、已出餐时系统根据订单ID找到对应的骑手ID再从映射表中找到WebSocket连接推送状态更新消息。位置上报骑手端定期如每5秒通过WebSocket上报经纬度。服务端收到后更新Redis中该骑手的位置使用GEOADD命令。2. 附近骑手查找与派单当订单需要派送时系统需要找到附近的空闲骑手。Redis Geo将在线且空闲的骑手位置信息存入一个Redis Geo集合Key如geo:riders:free。// 骑手上报位置时 redisCommand(GEOADD, geo:riders:free, longitude, latitude, riderId); // 查找附近3公里内的骑手 redisReply *reply (redisReply*)redisCommand(GEORADIUS, geo:riders:free, orderLng, orderLat, 3, km, WITHDIST, ASC);GEORADIUS命令可以返回按距离排序的骑手列表非常适合快速查找。派单逻辑可以从最近的骑手开始尝试派单发送WebSocket推送如果超时未接单则顺延给下一位。更复杂的策略可以考虑骑手负载、顺路程度等。3. 商家推荐根据用户的地理位置推荐附近的商家。同样可以使用Redis Geo存储商家位置进行附近查询。为了提高性能可以将商家基本信息ID, 名称评分也哈希化存储一次GEORADIUS查询后再用HMGET批量获取商家详情避免多次网络往返。注意事项WebSocket连接的多线程安全连接映射表必须使用锁如std::shared_mutex或并发容器如folly::ConcurrentHashMap来保护因为连接建立、断开和消息推送可能发生在不同线程。位置信息的更新频率过于频繁如每秒会给服务器和Redis带来压力过于稀疏则轨迹不连续。需要根据业务折中并考虑在APP端做轨迹平滑。Geo数据清理骑手下线或忙碌时需要将其从空闲Geo集合中移除ZREM否则会导致派单给无效目标。4. 性能优化与高级特性实现当基础功能跑通后我们需要关注如何让这个C系统飞起来。4.1 异步编程模型与线程池优化drogon基于事件循环默认是单线程事件循环线程池的模式。理解这一点对写出高性能代码至关重要。I/O密集型操作如数据库查询、Redis请求、HTTP客户端调用都应该使用drogon提供的异步接口返回Task或使用回调避免阻塞事件循环线程。事件循环线程一旦被阻塞整个服务器的响应能力都会下降。// 好的做法异步数据库查询 auto db drogon::app().getDbClient(); co_await db-execSqlCoro(SELECT ...); // 使用C20协程 // 或 db-execSqlAsync(SELECT ..., [](const Result r, const Exception e){ // 在回调中处理结果 });CPU密集型操作如复杂的订单价格计算、图像处理等应该提交到专门的计算线程池中执行防止占用I/O线程池影响网络请求的响应。// 将计算任务提交到自定义线程池 auto result co_await drogon::async_func([](){ // 复杂的计算... return computeResult; });配置调优在drogon的配置文件中关注以下几个参数thread_numI/O线程池大小。通常设置为CPU核心数的1.5到2倍。db_clients数据库连接池配置根据数据库服务器性能和业务压力调整最大连接数。redis_clientsRedis连接池同理。4.2 缓存策略设计与实战缓存是提升性能的银弹但用不好就是炸弹。缓存粒度粗粒度缓存整个商家列表页的HTML或JSON。适用于极少变化的页面失效策略简单如定时过期。细粒度缓存单个对象如商家信息、菜品信息。更灵活但管理复杂。我们的选择对象缓存为主。将常用的、修改不频繁的数据库对象序列化后存入Redis。例如Key设计为obj:shop:id。缓存模式Cache-Aside旁路缓存这是最常用的模式。读时先读缓存命中则返回未命中则读数据库并写入缓存。写时先更新数据库然后删除缓存而非更新。重要心得为什么是“删缓存”而不是“更新缓存”因为在并发写场景下“更新数据库”和“更新缓存”的顺序难以保证可能导致缓存脏数据。直接删除缓存下次读取时自然会从数据库加载最新数据虽然可能带来一次Cache Miss但保证了数据一致性。这被称为“延迟加载”或“懒更新”。缓存穿透、击穿、雪崩穿透查询一个不存在的数据如不存在的ID缓存不命中每次都打到数据库。解决方案将空结果也进行短时间缓存如obj:shop:-1 - NULL过期时间5分钟或者使用布隆过滤器在查询前拦截。击穿某个热点Key过期瞬间大量请求同时涌入数据库。解决方案使用互斥锁Redis的SETNX命令只让一个请求去加载数据库其他请求等待。在C服务端也可以使用本地互斥锁std::mutex配合std::shared_ptr来实现更高效的热点数据保护。雪崩大量Key在同一时间过期导致所有请求涌向数据库。解决方案给缓存过期时间加上一个随机值如基础30分钟 随机0-5分钟分散过期时间。4.3 分布式环境下的挑战与应对单机性能总有上限。当业务增长我们必须考虑分布式部署。无状态服务我们的C后端服务业务逻辑应该设计为无状态的。会话信息Token存储在Redis中用户请求可以被负载均衡器如Nginx转发到任意一台后端实例上。这是水平扩展的前提。分布式ID生成订单ID、支付单号等需要全局唯一且大致有序。不能使用数据库自增ID。常用方案雪花算法Snowflake生成一个64位的Long型ID包含时间戳、机器ID、序列号。可以在每台服务启动时配置不同的机器ID。Redis原子INCR利用Redis的INCR命令的原子性生成一个全局序列。可以设计为INCR order_id_counter并结合日期前缀如20240521000001。UUID虽然全局唯一但无序且较长不适合作为数据库主键影响索引效率。分布式锁在扣减库存、派单等需要强一致性的场景如果服务是多实例部署本地锁就失效了。需要使用分布式锁。基于Redis的分布式锁使用SET key random_value NX PX 30000命令NX表示不存在才设置PX设置毫秒过期时间random_value必须是全局唯一值如UUID用于安全释放锁。释放锁时要用Lua脚本保证“获取锁值-对比-删除”的原子性防止误删其他客户端的锁。-- 释放锁的Lua脚本 if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end注意事项Redis分布式锁不是完美的在极端情况下如主从切换可能导致锁失效。对于金融级场景可能需要考虑更严格的锁服务如ZooKeeper、etcd但外卖库存扣减场景Redis锁在做好重试和日志监控的情况下是可行的。数据分片Sharding当单表数据量巨大时如订单表需要考虑分库分表。可以根据用户ID或订单创建时间进行分片。在C中需要在数据访问层根据分片键路由到不同的数据库连接。这引入了额外的复杂度项目初期应通过合理的索引和归档策略尽量避免。5. 开发环境搭建、调试与部署实战5.1 从零开始搭建C开发环境工欲善其事必先利其器。一个顺手的开发环境能极大提升效率。编译器与构建工具Linux/macOS安装g9.0或clang以及CMake。Windows推荐使用Visual Studio 2019/2022并安装“使用C的桌面开发”工作负载。同时安装CMake。也可以使用MSYS2或WSL2来获得类Linux环境。依赖管理vcpkg# 1. 克隆vcpkg仓库 git clone https://github.com/microsoft/vcpkg.git cd vcpkg # 2. 执行引导脚本 ./bootstrap-vcpkg.sh # Linux/macOS .\bootstrap-vcpkg.bat # Windows # 3. 安装项目所需库集成到系统 ./vcpkg install drogon hiredis mysql-connector-cpp ./vcpkg integrate install这会将drogon等库编译安装并生成一个CMake工具链文件方便在CMake项目中引用。IDE配置VS Code为例安装C/C、CMake Tools扩展。在项目根目录创建.vscode/settings.json配置CMake工具链为vcpkg。{ cmake.configureSettings: { CMAKE_TOOLCHAIN_FILE: [你的vcpkg目录]/scripts/buildsystems/vcpkg.cmake } }使用CtrlShiftP-CMake: Configure来配置项目然后选择编译目标进行构建和调试。避坑指南编译drogon失败最常见的原因是OpenSSL版本问题。确保系统已安装OpenSSL开发库如libssl-dev。在Windows上vcpkg会自动处理。找不到MySQL客户端库mysql-connector-cpp这个包在vcpkg中有时链接有问题。一个更稳定的替代方案是使用mysqlclientC接口配合drogon的ORM它通常更可靠。5.2 系统监控、日志与调试技巧一个健壮的系统离不开可观测性。日志drogon内置了日志库但功能较基础。建议集成spdlog这是一个高性能的C日志库。#include spdlog/spdlog.h #include spdlog/async.h #include spdlog/sinks/rotating_file_sink.h auto logger spdlog::rotating_logger_mtspdlog::async_factory(my_logger, logs/server.log, 1024*1024*10, 3); logger-info(User {} logged in, userId);配置异步日志、按文件大小滚动避免日志I/O阻塞业务线程。指标监控使用Prometheus C Client库暴露应用指标如请求QPS、延迟分位数、错误率、数据库连接池状态等。然后通过Grafana进行可视化。#include prometheus/exposer.h #include prometheus/registry.h auto counter_family prometheus::BuildCounter().Name(http_requests_total).Help(Total HTTP requests).Register(*registry); auto counter counter_family.Add({{method, GET}, {endpoint, /api/orders}}); counter.Increment(); // 在请求处理中递增调试技巧GDB/LLDB学习基本的断点、查看变量、回溯栈命令。对于死锁问题thread apply all bt命令可以打印所有线程的堆栈非常有用。Valgrind/AddressSanitizer用于检测内存泄漏、越界访问。在开发阶段务必经常使用。# 编译时加入ASan选项 g -fsanitizeaddress -g -o myapp main.cpp # 使用Valgrind valgrind --leak-checkfull ./myapp性能剖析使用perfLinux或InstrumentsmacOS来查找CPU热点。有时你会发现性能瓶颈不在你的业务代码而在一次不经意的JSON序列化或字符串拷贝中。5.3 容器化部署与CI/CD流水线现代部署离不开Docker和Kubernetes。编写Dockerfile# 使用多阶段构建减小镜像体积 FROM ubuntu:22.04 AS builder RUN apt-get update apt-get install -y g cmake libssl-dev ... WORKDIR /build COPY . . RUN cmake -B build -DCMAKE_BUILD_TYPERelease cmake --build build FROM ubuntu:22.04 RUN apt-get update apt-get install -y libssl3 ca-certificates rm -rf /var/lib/apt/lists/* WORKDIR /app COPY --frombuilder /build/build/my_waimai_server . COPY config.json . EXPOSE 80 CMD [./my_waimai_server]编写docker-compose.yml用于本地测试一键启动服务、MySQL、Redis。version: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: waimai volumes: - mysql_data:/var/lib/mysql redis: image: redis:7-alpine command: redis-server --appendonly yes volumes: - redis_data:/data app: build: . ports: - 8080:80 depends_on: - mysql - redis environment: DB_HOST: mysql REDIS_HOST: redis volumes: mysql_data: redis_data:CI/CD以GitHub Actions为例name: Build and Deploy on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Build with CMake run: | cmake -B build -DCMAKE_BUILD_TYPERelease cmake --build build - name: Run Tests run: ./build/test_runner # 假设你有单元测试 - name: Build Docker Image run: docker build -t myregistry/waimai-server:${{ github.sha }} . - name: Push to Registry run: | docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASS }} docker push myregistry/waimai-server:${{ github.sha }} deploy: needs: build runs-on: ubuntu-latest if: github.ref refs/heads/main steps: - name: Deploy to K8s run: | kubectl set image deployment/waimai-server servermyregistry/waimai-server:${{ github.sha }} kubectl rollout status deployment/waimai-server通过以上步骤我们完成了一个从需求分析、技术选型、核心模块实现、性能优化到最终部署的完整C外卖系统实战。这个过程充满了挑战但也正是这些挑战让你对系统设计的理解不再浮于表面。当你看到自己用C构建的服务稳定地处理着一个个订单请求时那种对系统全局的掌控感和成就感是使用高级语言框架所难以比拟的。这不仅仅是完成了一个项目更是完成了一次从“程序员”到“系统工程师”的思维升级。