做微服务项目最难的不是把某个组件跑起来,而是把整个链路串起来以后还能稳定运行。我选择自己动手做一个基于SpringCloud的美食分享交流平台,表面上看是个普通的社区类项目,但仔细拆开以后,它其实把用户、内容、评论、互动、搜索这几个业务域天然隔离成了独立的微服务,正好是练手SpringCloud的合适载体。这篇文章我不打算讲教科书式的概念,而是从实际开发角度,把我这个项目从架构设计、技术选型到落地过程中的方案取舍和踩坑经历完整写出来,尤其希望给正在做SpringCloud入门项目或者准备面试总结的同学一点参考。
1. 项目业务分析与微服务架构规划
1.1 美食分享平台的核心业务模块
这个项目的定位是一个美食爱好者社区,用户注册登录以后,可以发美食帖子、传图片、写菜谱,其他用户可以点赞、收藏、评论,还能搜索感兴趣的内容。你可以把它理解成一个垂直领域的小某书,只是内容全部围绕美食展开。
我从业务上一共梳理出六个核心模块:用户模块、帖子内容模块、评论模块、互动模块、搜索模块、文件存储模块。其中文件存储模块我没有单独拆成微服务,而是作为公共能力放到帖子服务里,因为文件上传牵扯到和业务强相关的图片关联逻辑,拆出去反而让调用链变长。
每个模块对应的业务动作大概是这样的:
- 用户模块:注册、登录、个人资料、关注关系、用户主页
- 帖子模块:发布美食帖子、图文上传、帖子列表、帖子详情、分类浏览
- 评论模块:查看评论、发表评论、回复评论
- 互动模块:点赞、取消点赞、收藏、浏览统计
- 搜索模块:按关键词搜索美食内容、按分类筛选
这些模块看起来做单体也能跑,为什么非要拆?这里要说清楚一个观点:微服务不是目的,是手段。美食平台这个业务,用户和帖子之间是典型的读多写少模式,互动模块点赞这种接口对稳定性要求极高,而搜索又需要独立的存储和索引能力。把它们拆开以后,各个服务可以独立扩容、独立发布、独立优化,尤其是互动服务被流量打满时,不能把用户服务也拖死。这是拆的最重要理由。
1.2 服务拆分方案与端口规划
确定拆分方案时,我遵循了两个原则:按业务域划分、服务间单向依赖。最终落到这套服务矩阵上:
| 服务名称 | 端口 | 核心职责 | 主要数据表 |
|---|---|---|---|
| gateway-server | 8080 | 统一入口、路由转发、登录鉴权、跨域处理 | 无 |
| user-service | 8081 | 用户注册登录、资料维护、关注关系 | 用户表、关注表 |
| post-service | 8082 | 帖子发布、图文管理、分类浏览 | 帖子表、图片表、分类表 |
| comment-service | 8083 | 帖子评论、回复评论 | 评论表 |
| interaction-service | 8084 | 点赞、收藏、浏览统计 | 点赞表、收藏表、浏览记录表 |
| search-service | 8085 | 内容搜索、索引同步 | 索引数据(ES) |
端口规划上有一个细节,固定服务端口而不是让SpringBoot随机分配,这样网关配置路由、Nacos管理服务列表、本地调试的时候都方便很多。每个服务独立一个数据库,库名和微服务名保持一致,比如user_db、post_db,避免多个服务操作同一张表的混乱情况。
数据库独立这件事,很多初学者会忽略。如果微服务拆了,数据库还是共用一个,那么一旦某个服务的SQL写得有问题,会拖垮整个库,微服务隔离的意义就少了一半。我实际开发中是把SQL脚本按服务分目录管理,每个服务只访问自己的库,跨服务数据一律通过接口获取。比如帖子列表中展示作者信息时,post-service会调用user-service的接口拿到用户名和头像,而不是直接去查用户表。
1.3 整体技术栈与组件分工
技术栈我选的是当前国内团队最主流的组合:SpringBoot作为基础框架,SpringCloud负责微服务治理,SpringCloud Alibaba提供Nacos和Sentinel支持,再加一套RabbitMQ做异步解耦,Redis做缓存和热点数据存储,Elasticsearch做搜索。这套组合在真实团队里很常见,资料多、问题好查,面试讲出来也容易引起共鸣。
SpringCloud里的组件我从实际项目角度做了取舍。注册中心和配置中心统一用Nacos,没有选择Eureka加SpringCloud Config的组合,后面会细说原因;网关用SpringCloud Gateway而不是Zuul,因为Gateway基于WebFlux响应式模型,性能更好,而且SpringCloud官方对Gateway的维护力度远大于Zuul。
远程调用我选了OpenFeign,这是目前最主流的声明式HTTP客户端,配合Ribbon负载均衡可以做到对开发者透明。熔断限流则用Sentinel,比起Hystrix,Sentinel在控制台、规则持久化、实时监控方面好用得多。这套组件分工我后面每一个都展开讲。
2. SpringCloud核心组件选型与落地细节
2.1 Nacos注册中心与配置中心的落地
Nacos把注册中心和配置中心合二为一,这是我选它的核心原因。Eureka也能做注册中心,但配置还需要单独搭SpringCloud Config,并且Eureka 2.x已经停止开发,维护上处于半停滞状态。Nacos支持服务注册发现、配置管理、动态路由配置,一套搞定,控制台是中文的,对团队上手也很友好。
我使用的是Nacos 2.2.1版本,部署方式采用Docker单机模式。配置上需要特别注意SpringCloud和SpringCloud Alibaba的版本对应关系,这一步踩坑的人非常多。我本地项目用的是SpringBoot 2.6.13、SpringCloud 2021.0.5、SpringCloud Alibaba 2021.0.5.0,这个组合经过官方验证,稳定性不错。
服务接入Nacos的配置很简单,在application.yml里加上:
spring: application: name: user-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: public config: server-addr: 127.0.0.1:8848 file-extension: yaml group: DEFAULT_GROUP这里 namespace 我用的默认public。如果公司内部有多个环境,比如dev、test、prod,建议每个环境建一个namespace,服务配置从环境维度隔离,避免开发人员改配置影响到生产。我以前就吃过不区分环境的亏,本地联调时把测试环境的配置改坏了,整个测试组一起遭殃。
配置管理还有一个常用玩法是配置动态刷新。比如某个接口开不开限流开关、某个线程池的阈值是多少,这些参数可以放到Nacos配置中心,服务端通过@RefreshScope注解实现不重启服务直接生效。不过要注意,加了@RefreshScope的Bean会被代理,某些场景下会导致实例数量变多,如果用在无状态组件上没问题,用在有状态对象上要格外小心。
2.2 Gateway网关统一入口与登录鉴权
网关是微服务架构最外层的一道门,所有外部请求都从这里进,所有响应也统一从这里出。我用的是SpringCloud Gateway,它基于SpringWebFlux和Reactor,底层是Netty,性能比传统Servlet模型的Zuul 1.x高不少,而且官方维护活跃。
路由配置我放在Nacos配置中心统一管理,没有写死在本地。这样调整路由规则时可以动态刷新。一个典型的路由片段:
spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/api/user/** filters: - StripPrefix=1 - id: post-service uri: lb://post-service predicates: - Path=/api/post/** filters: - StripPrefix=1路由地址里lb://user-service就是通过注册中心负载均衡到对应服务实例,Gateway会从Nacos拉取可用实例列表,然后默认轮询转发。这里有个细节,如果你在配置里写成http://localhost:8081这种硬编码地址,那么实例宕机了网关不会自动切换,负载均衡也就失去了意义,所以务必加上lb://前缀。
网关第二个核心职责是登录鉴权。我在Gateway里写了一个全局过滤器,拦截/api/user/login和/api/user/register之外的请求,校验请求头里的JWT Token。Token有效就解析用户ID并在请求头加上X-User-Id字段,转发给下游服务。下游服务不再重复解析Token,直接信任网关传递的用户身份。
这里有一个经验教训:鉴权逻辑放网关做没问题,但要确保网关不把用户的敏感信息通过Header传给下游。我只传了用户ID,不传用户角色、不传Token原文。因为Header在微服务链路中会经过多个节点,任何一环打日志都可能把敏感信息打出来,尽量减少传递范围是安全基本功。
2.3 OpenFeign远程调用与负载均衡配置
服务之间的同步通信我用的是OpenFeign。这个组件的用法很简单,定义一个接口,加上@FeignClient注解,方法签名和被调服务的Controller对应即可,底层会像调用本地方法一样调远程接口。
项目中用得比较频繁的调用场景是:post-service需要展示帖子作者信息,于是声明一个FeignClient指向user-service的接口;post-service发布帖子之后,需要异步通知search-service更新索引;comment-service回复评论后,需要通知post-service更新评论数量。
一个典型的FeignClient定义:
@FeignClient(name = "user-service", path = "/api/user") public interface UserClient { @GetMapping("/info/{userId}") UserInfoDTO getUserInfo(@PathVariable("userId") Long userId); }OpenFeign默认集成了Ribbon做负载均衡,调用时会从Nacos拿到服务实例列表,自动完成选主和重试。但有一个配置必须手动调,就是超时时间。OpenFeign默认的连接超时和读取超时都很短,生产环境下稍微执行慢一点的SQL或者网络抖动,就会触发超时,导致调用失败。
我当时遇到的问题是查找帖子作者信息时,偶尔报Read timed out,排查后发现是user-service某个查询走了大表全表扫描,耗时超过500ms。调整方案是双管齐下:一方面优化SQL加了索引,另一方面在Feign配置里把超时时间调大,连接超时设为2秒,读取超时设为5秒。配置如下:
feign: client: config: default: connectTimeout: 2000 readTimeout: 5000还有一个非常隐蔽的问题:Feign的降级和熔断默认是关闭的,开启之后需要显式配置feign.sentinel.enabled: true,并且@FeignClient注解里加fallback属性指定降级类。如果你直接用Hystrix,还要注意Hystrix,不推荐。我项目里是把Sentinel作为Feign的兜底框架,配置开启后,被调服务挂了会走fallback方法返回友好提示,而不是直接给前端抛异常。
2.4 Sentinel限流与熔断保护策略
美食平台最怕的就是某篇帖子突然爆了,一瞬间大量用户涌入请求接口。如果所有流量都直接打到数据库,很可能直接把连接池打满,导致整个服务不可用。这种场景线上经常发生,尤其是美食打卡类内容特别容易在节假日被转发。
我用Sentinel来做限流和熔断。Sentinel相比Hystrix最大的优势是支持实时监控和规则持久化。我在项目中使用了Sentinel Dashboard,启动一个独立的控制台程序,然后服务接入后就能在控制台看到每个接口的实时流量情况,包括QPS、线程数、响应时间。我可以直接在控制台给某个热点接口配置限流规则,比如点赞接口POST /api/interaction/like每秒钟最多放行200次,超过就排队或者直接拒绝。
配置限流规则时要注意维度选择。按接口限流是最基础的,更精确的做法是按用户限流。美食平台会有部分活跃用户频繁刷接口,这种人肉爬虫的行为容易影响正常用户。我在Sentinel里配置了针对用户ID的限流规则,同一个用户每秒最多调用点赞接口5次,防止脚本刷量。
Sentinel的熔断规则也非常实用。当某个下游服务(比如search-service)连续失败率达到一定阈值,Sentinel会直接熔断该调用链路,避免请求继续打到已经不可用的服务上。我在帖子列表接口上配置了慢调用比例熔断:如果响应时间超过1000ms的请求比例达到30%,后续10秒内直接走fallback逻辑。
@RestController public class PostController { @GetMapping("/list") @SentinelResource(value = "postList", blockHandler = "listBlockHandler", fallback = "listFallback") public R listPost(...) { // 业务逻辑 } public R listBlockHandler(..., BlockException e) { return R.error("系统繁忙,请稍后重试"); } public R listFallback(..., Throwable t) { return R.error("服务异常,请稍后重试"); } }注意区分blockHandler和fallback的区别:前者是Sentinel拦截到限流或熔断后触发的,后者是业务代码抛出异常后触发的。如果你把两者混用,会导致流量控制失效或者异常信息被吞掉。这个点我在面试的时候反复讲过,也比较能体现对Sentinel的熟悉程度。
3. 平台核心功能实现与难点攻关
3.1 用户登录鉴权链路打通
用户登录鉴权是微服务链路的第一个难点。用户输入用户名密码,请求到达Gateway,Gateway把请求转发给user-service,user-service校验密码生成JWT返回给前端。前端后续请求都会在Header里带上这个JWT,Gateway再拦截校验。
JWT我这里没有引入额外的授权服务器框架,而是直接用Java的jjwt库生成和解析。生成逻辑大概是这样:
String token = Jwts.builder() .setSubject(String.valueOf(userId)) .claim("username", user.getUsername()) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000)) .signWith(secretKey) .compact();JWT的有效期我设为7天。这个时间要考虑好,太短用户要频繁登录,太长有安全风险。实际项目中通常还会配合刷新Token机制,我这边为了控制复杂度做了简化,但把过期时间的配置抽到了Nacos配置文件里,后续想调整不用改代码。
用户注册还有一个实用细节:注册时需要确认用户名唯一。user-service的注册接口要处理并发注册同一个用户名的情况,如果只用数据库唯一索引兜底,会抛出底层SQL异常,体验不好。我的做法是先查一次用户名是否已存在,做前置校验,再依赖唯一索引做最终兜底,捕获冲突异常后返回“用户名已被注册”的友好提示。
3.2 美食帖子发布与异步解耦
发布美食帖子是整个平台的核心流程。用户在移动端或者网页端填写标题、正文,上传多张美食图片,点击发布,请求从Gateway转发到post-service。post-service先把图片内容保存到MinIO对象存储,再把帖子记录和图片地址写入数据库,最后通知其他服务处理索引和推荐。
图片上传这里有一个重要取舍:同步还是异步。我最初的版本是前端把图片Base64编码发给后台上传,结果是发布一篇帖子耗时长、流量大,用户等很久。后来改成前端直传MinIO:前端从后端获取一个临时的上传凭证,然后直接上传图片到MinIO,上传完成后再把图片地址提交给post-service保存。这样后端不经过图片二进制数据的传输,压力小很多。
帖子发布成功后,需要通知search-service把帖子内容写入Elasticsearch索引。这个场景我用RabbitMQ异步解耦:post-service发布消息到交换机,search-service监听队列消费,更新索引。如果search-service暂时不可用,消息会积压在MQ里,等它恢复后可以继续消费,不影响用户看到帖子发布成功的反馈。
事务边界在这里要理清:帖子主流程(写数据库)和索引更新不能放在同一个事务里。如果数据库写入失败就直接返回失败,如果写库成功但索引更新失败,可以通过MQ重试最终达到一致。消息队列的ack机制要设置好,消费成功后手动确认,失败则重回队列,避免消息丢失。
@Transactional public Long publishPost(PostCreateRequest request) { Post post = new Post(); BeanUtils.copyProperties(request, post); post.setCreateTime(new Date()); postMapper.insert(post); // 发布MQ消息,通知search-service同步索引 rabbitTemplate.convertAndSend("post.exchange", "post.index", post.getId()); return post.getId(); }3.3 点赞收藏接口的高并发优化
点赞和收藏是互动服务的两把刷子,也是最容易被打爆的接口。用户点了赞进入“已赞”状态,再点一次取消,如果每次都直接更新数据库里的点赞表,高并发下数据库压力会非常大,而且容易出现数据不一致。
我做了一个经典的优化方案:Redis缓存点赞状态 + 异步批量落库。具体流程是:
- 点赞请求到达interaction-service后,先操作Redis,用
userId:postId作为key,做set操作。判断用户是否已点过赞,点击后状态翻转,同时把计数写入Redis的hash结构。 - Redis只记录状态,不实时改数据库。后台每隔一分钟把Redis中变更的数据批量写入MySQL。
- 查询某篇帖子的点赞数时,优先从Redis读,Redis没有则查库并回填。
这个方案的好处是把瞬时高并发流量挡在了数据库前面,Redis单机几万QPS是很轻松的事。坏处是短时间内如果Redis挂了,会丢失一部分点赞数据,所以实际项目里还要给Redis做持久化和高可用,我这套单机版对于项目演示和个人学习足够,但上线前最好加上主从或者集群。
还有一个细节是幂等性。用户在弱网环境下点了一次赞,前端可能自动重试,如果不做幂等,用户的动作会被执行两次。我的处理是Redis操作使用setIfAbsent判断,重复请求直接返回当前状态,保证接口幂等。
3.4 跨服务数据一致性处理
微服务架构下最麻烦的问题之一就是跨服务数据一致性问题。比如用户在comment-service发表评论后,post-service的帖子需要更新评论数量。如果评论表写入成功、帖子表更新失败,就造成了两边数据不一致。
这个场景我没有引入分布式事务框架(Seata),而是用了更轻量的最终一致性方案:评论服务写入评论后发MQ消息,帖子服务消费消息更新评论数。如果更新失败,消息重试;如果重试多次还失败,记录到失败表人工补偿。最终一致性的核心思想就是不追求数据库层面的强一致,而是让数据在某个时间点之后对齐。
这里我需要强调一个认知:分布式事务不是万能的,而且会带来很大的性能损耗。像评论计数这种场景,完全不必实时一致到毫秒级,用MQ解耦够用。但对于涉及金额、订单这类强一致的场景,就不能偷懒,必须用Seata或者设计好事务消息方案。
4. 项目实战中遇到的典型问题与排查案例
4.1 Nacos版本兼容问题导致服务启动失败
第一次启动服务时,Nacos控制台能看到服务注册进来,但服务之间通过Feign调用时,总是不稳定,偶尔报No provider available for the service,重启几次后才发现是版本兼容问题。
排查思路是这样:先看Nacos页面,确认服务是否正常注册;再检查Feign配置,确认服务名是否写错;最后查看服务启动日志,发现nacos-client版本和Nacos服务端版本不一致。具体是我本地使用了Nacos 2.2.1,但SpringCloud Alibaba引的nacos-client是1.4.x,客户端和服务端不同版本在通信上会有兼容性问题,服务发现偶尔失效。
解决方案有两个。一是把Nacos服务端降级到1.4.2,二是升级SpringCloud Alibaba版本来带动nacos-client版本对齐。我选的是方案二,因为团队更倾向于使用较新的版本,把SpringCloud Alibaba升到2021.0.5.0,nacos-client跟随升级到2.2.x,问题彻底解决。
给一个现成的版本组合供参考:
| 组件 | 版本 |
|---|---|
| SpringBoot | 2.6.13 |
| SpringCloud | 2021.0.5 |
| SpringCloud Alibaba | 2021.0.5.0 |
| Nacos Server | 2.2.1 |
| Sentinel Dashboard | 1.8.6 |
| RabbitMQ | 3.12.x |
| MinIO | RELEASE.2023-06-02 |
| Elasticsearch | 7.17.12 |
后面我在项目里专门维护了一份版本对应文档,每次升级组件时先对照官方版本说明,再决定要不要动依赖,省了很多排查时间。
4.2 OpenFeign调用超时与异常穿透
上线后发现帖子详情页经常偶发性报错,用户反馈有时候打开帖子很慢,刷新一次又好了。查日志发现是post-service调用user-service时偶发Read timed out,但user-service的健康检查显示正常。
进一步排查发现,请求打到了user-service的一个查询用户信息接口,这个SQL关联了三张表,数据量上来以后执行时间超过1秒。user-service本身没有挂,只是响应延迟。而Feign默认的读取超时只有1秒,所以大量请求在等待响应时超时。
治本方法是优化SQL,给关联字段加上联合索引,把用户信息查询耗从800ms降到100ms;治标方法是调大Feign超时时间。两者我都做了。这里要提醒的是,超时时间不能一味调大,否则接口出问题时会拖住调用方线程,导致上游服务线程池耗尽。连接超时建议1到2秒,读取超时建议3到5秒,具体结合业务调整。
另外异常穿透也是一个容易忽略的坑。被调服务抛出的业务异常,Feign默认会包装成FeignException,异常信息经常是一串JSON,调用方很难判断业务失败原因。我的做法是在user-service写了一个全局异常处理器,将所有业务异常统一返回{code: 50001, message: "用户不存在"}这种格式,Feign调用方拿到code后做对应处理,而不是直接把底层异常抛给前端。
4.3 网关偶发503 Service Unavailable
某天联调时,前端同事反馈偶尔有请求返回503 Service Unavailable,刷新后可能又恢复正常。这类偶发问题往往比稳定复现的问题更难排查,我花了一晚上才定位到根因。
首先排除网关路由配置错误,因为稳定复现的问题不是一直存在。查网关日志发现,503出现的时间点刚好是某台服务实例在Nacos中重新注册的瞬间。原来我的服务实例在发布新版本时会有一个下线再上线的过程,期间Nacos里实例列表短暂为空,Gateway从注册中心拉取不到可用实例,就返回了503。
解决方案是在Nacos中配置服务的健康检查和优雅下线时间,让服务平滑上下线。服务端加了spring.cloud.nacos.discovery.heart-beat-timeout和heart-beat-retry-count参数,同时确保服务在下线前完成正在处理的请求,而不是直接断开连接。还有一个简化处理是给网关配置了重试机制,转发的请求失败一次后会自动重试另一个实例,极大降低了用户体验到503的概率。
4.4 Nacos配置修改后不生效
Nacos配置热更新是项目里的一个常用功能,但我在实际使用中碰到过明明改了配置,服务却没有任何反应的情况。最初以为是Nacos的推送问题,反复重启服务后配置才生效。
后来排查到关键原因:SpringBoot 2.6以上版本默认不加载bootstrap.yml,但我的配置中心地址和微服务应用名都写在bootstrap.yml里,导致服务启动时根本没有去Nacos拉取外部配置。解决办法是添加spring-cloud-starter-bootstrap依赖,或者把配置信息写进spring.config.import对应的Nacos config server地址。
修改后确实生效了,但还有一个@RefreshScope的坑。配置刷新时,只有被@RefreshScope标注的Bean和内部属性会重新初始化,如果你在普通Service里直接注入@Value("${custom.config}"),这个值不会更新。需要确保使用这个配置的类本身是被Spring容器管理的,且类上加了@RefreshScope,两者缺一不可。
5. Docker Compose部署、项目亮点与后续演进
5.1 Docker Compose一键启动整套环境
项目开发完成后,我把所有依赖中间件写进了Docker Compose,包括Nacos、MySQL、Redis、RabbitMQ、MinIO、Elasticsearch、Sentinel Dashboard。这样不管是我自己重新拉环境,还是团队同学要跑这个项目,都是执行一条命令的事。
一个简化的docker-compose示例:
version: "3.8" services: mysql: image: mysql:8.0 ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=123456 - MYSQL_DATABASE=post_db volumes: - ./mysql_data:/var/lib/mysql nacos: image: nacos/nacos-server:v2.2.1 ports: - "8848:8848" - "9848:9848" environment: - MODE=standalone redis: image: redis:7.0 ports: - "6379:6379" rabbitmq: image: rabbitmq:3.12-management ports: - "5672:5672" - "15672:15672" minio: image: minio/minio ports: - "9000:9000" - "9001:9001" command: server /data这种部署方式只能用于开发和个人学习,生产环境还差很远。生产环境至少要考虑配置加密、镜像仓库、容器编排平台、日志采集、监控告警这些。但作为一套可以演示、可以学习、可以当作简历项目的工程来说,Docker Compose已经能让整个环境非常整洁。
5.2 项目复盘:亮点与不足
做完这个项目,我自己梳理了三个核心亮点。第一个亮点是业务闭环比较完整,从用户注册登录、发帖浏览、互动评论到搜索,一套流程走下来,能够体现对SpringCloud常见组件都有实际运用。第二个亮点是关键技术选择有业务依据,不是简单堆组件。比如点赞接口为什么用Redis而不是直接写库,为什么用MQ异步更新搜索索引,这些问题都能从业务场景和数据量出发给出解答。第三个亮点是具备一定的工程化思维:环境隔离、版本管理、统一异常处理、参数动态配置,这些细节比组件本身更能让你在面试中脱颖而出。
当然,不足也很明显。首先是分布式事务的处理偏简化,用了MQ最终一致性,但没有引入Seata做更完整的分布式事务方案,对于需要强一致的场景不一定适用。其次是搜索服务的数据同步依赖MQ手动发送消息,如果消息丢了,索引和数据库就会不一致,后续需要引入Canal监听MySQL的binlog来自动同步。最后是服务的可观测性做得不够,这一步我整了日志打印和Sentinel监控,但没有接Prometheus和Grafana做指标大盘,也没有引入链路追踪组件。
5.3 后续演进方向与个人建议
如果这个项目要继续做下去,我接下来的计划有三个方向。第一是接入链路追踪组件,比如Spring Cloud Sleuth搭配Zipkin,把一次请求跨越多个服务的调用链完整记录下来,排查线上问题时特别有用。第二是完善发布流水线,从代码提交、单元测试、镜像构建到自动部署,用GitLab CI或者GitHub Actions跑起来,向DevOps靠拢。第三是加入更多美食平台特有功能,比如基于用户标签的内容推荐、基于地理位置的附近美食推荐,这些都会让项目更有差异性。
最后分享一个我从这个项目里总结出来的建议:做SpringCloud项目,一定先用最小的单体Demo把注册中心、网关、两个服务之间的调用跑通,再往外扩展。一开始就把所有组件全部集成进来,出了问题你根本不知道是哪个环节引起的。我踩过的版本兼容、超时配置、Nacos拉取配置这些坑,十有八九都跟组件一锅端有关。先把骨架立住,再逐步叠加能力,这条路走得最稳。