同学们,我们继续第四阶段**“软件工程与系统优化”**的学习!上一节我们成功地将前后端应用整合并部署上线。现在,我们将把目光投向一个更具挑战性、也更令人兴奋的领域——高并发与性能优化。
当你的应用上线后,如果用户量突然暴增,或者业务逻辑变得更加复杂,系统可能会变得缓慢、卡顿甚至崩溃。这时,你就需要掌握如何设计和优化系统,使其能够处理大量并发请求,并提供快速响应。这就像修建一条高速公路,不仅要修好,还要确保它在车流量高峰期依然能流畅通行。
课程4.2:高并发与性能优化实战(超详细版)
一、高并发与性能的核心概念:衡量系统“抗压能力”
1.1 什么是高并发
-
高并发(High Concurrency):
-
含义:指系统在单位时间内(通常是每秒)能够同时处理大量用户请求或任务的能力。
-
目标:在用户量急剧增长或请求量突发增加时,系统仍能保持稳定、快速响应。
-
典型场景:
-
社交平台:大量用户同时在线刷信息流、点赞、发评论。
-
电商秒杀/抢购:短时间内海量用户涌入抢购同一件商品。
-
资讯流/短视频:大规模用户同时请求内容。
-
在线教育/游戏:多用户同时进行互动。
-
-
比喻:你开了一家网红餐厅,平常只有几十个客人,但突然间来了几万人同时点餐、吃饭,系统不能因此瘫痪。
-
1.2 性能指标:如何量化“快”与“稳”?
要优化性能,首先要能量化性能。
-
吞吐量(Throughput):
-
含义:单位时间内系统处理的请求数量或事务数量。
-
指标:
-
QPS(Queries Per Second):每秒查询数,用于衡量Web服务器处理查询请求的能力。
-
TPS(Transactions Per Second):每秒事务数,用于衡量数据库或事务系统处理事务的能力。
-
-
目标:尽可能高。
-
-
响应时间(Response Time)/延迟(Latency):
-
含义:从客户端发出请求到接收到服务器响应所花费的时间。
-
指标:通常关注平均响应时间、P90(90%的请求在这个时间内完成)、P99(99%的请求在这个时间内完成),因为平均值可能被少数极端值掩盖。
-
目标:尽可能低。
-
-
并发数(Concurrency):
-
含义:系统能够同时处理的最大连接数或请求数。
-
指标:在线用户数、活跃连接数。
-
目标:尽可能高。
-
-
系统资源利用率:
-
指标:CPU利用率、内存占用、网络带宽、磁盘I/O(读写速度、IOPS)。
-
目标:合理利用,不出现瓶颈。
-
二、全链路性能瓶颈分析:找出“短板”
在优化之前,我们必须找到系统的性能瓶颈在哪里。性能瓶颈可能是前端、后端、数据库、网络,甚至是服务器硬件。这就像治病,要先诊断出病因。
2.1 常见瓶颈环节:哪里可能“卡顿”?
-
前端页面加载:
-
过大的资源文件(JS、CSS、图片)。
-
过多的HTTP请求。
-
阻塞渲染的JavaScript执行。
-
复杂的DOM结构或频繁的DOM操作导致重绘/重排。
-
-
后端API处理:
-
不优化的业务逻辑(CPU密集型计算)。
-
阻塞的I/O操作(文件读写、网络调用、数据库查询)。
-
线程模型(如多线程阻塞,或Node.js单线程被计算密集型任务阻塞)。
-
代码逻辑不合理,内存泄漏。
-
-
数据库查询与写入:
-
慢查询SQL语句(无索引或索引失效)。
-
大量写入操作导致数据库I/O瓶颈。
-
数据库连接池不足或配置不当。
-
并发事务死锁。
-
-
网络带宽与延迟:
-
服务器出口带宽不足。
-
客户端与服务器之间网络延迟过高。
-
-
服务器CPU/内存/磁盘:
-
CPU负载过高。
-
内存不足导致频繁Swap。
-
磁盘I/O读写速度慢,成为瓶颈。
-
2.2 性能分析工具:你的“诊断仪”
选择合适的工具,才能有效定位问题。
-
前端:
-
Chrome DevTools:浏览器内置,Network(查看请求瀑布流)、Performance(记录运行时性能)、Lighthouse(综合性能审计)。
-
Lighthouse:Google提供的开源工具,可以对网页进行性能、可访问性、最佳实践、SEO、PWA等方面的审计,并提供优化建议。
-
WebPageTest:在线工具,从全球不同地点模拟真实用户访问,提供详细的页面加载性能报告。
-
-
后端:
-
ab(Apache Benchmark):简单的HTTP并发测试工具。
-
wrk:高性能的HTTP基准测试工具,可以模拟大量并发连接。
-
JMeter:功能强大的性能测试工具,支持多种协议。
-
Postman/Insomnia:不仅能测试API,也支持简单的性能测试。
-
New Relic / Dynatrace 等 APM 工具:提供代码级性能分析、数据库性能分析、分布式链路追踪等。
-
PM2:Node.js进程管理,自带CPU、内存监控。
-
-
数据库:
-
EXPLAIN:分析SQL执行计划,判断索引是否命中。 -
慢查询日志:记录执行时间超过阈值的SQL语句。
-
数据库监控工具:如MySQL Workbench、Navicat Monitor、云服务商的RDS监控。
-
-
全链路监控(APM - Application Performance Management):
-
Datadog, SkyWalking, Jaeger, Zipkin, OpenTelemetry:用于追踪请求在分布式系统中的完整调用链,定位哪个服务或哪个环节耗时最长。
-
ELK Stack(Elasticsearch + Logstash + Kibana):集中化日志管理,用于分析应用日志和错误日志。
-
Prometheus + Grafana:开源的监控报警解决方案,用于收集和可视化系统指标。
-
三、前端性能优化实战:让用户“快人一步”
前端性能直接影响用户体验,是“用户感知”到的第一层性能。
3.1 资源加载优化:瘦身与加速
-
代码压缩与合并(Minification & Concatenation):
-
JS/CSS压缩:移除代码中的空格、注释、缩短变量名,减小文件体积。
-
文件合并:将多个小文件合并成一个大文件,减少HTTP请求数量(但HTTP/2普及后,单个大文件的优势降低)。
-
工具:Webpack、Vite、Rollup等打包工具会自动完成。
-
-
图片优化:
-
图片压缩:使用工具压缩图片,在不损失视觉质量的前提下减小文件大小。
-
选择合适格式:JPG(照片)、PNG(透明背景)、SVG(矢量图)、WebP(新一代Web图片格式,压缩率高)。
-
CDN加速(Content Delivery Network):将图片、JS、CSS等静态资源部署到全球各地的CDN节点。
-
原理:用户访问时,从离他最近的CDN节点获取资源,减少网络延迟。
-
比喻:你的网站资源不是放在一个总仓库,而是分发到全球各地的分仓库。用户从最近的分仓库拿货,当然更快。
-
-
-
懒加载(Lazy Loading)与预加载(Preloading):
-
懒加载:按需加载。对于图片、视频、组件、路由等,只有当它们进入用户视口时才加载。
- 示例:
<img>标签的loading="lazy"属性,或使用JS库(如Intersection Observer API)。
- 示例:
-
预加载:提前加载。在用户实际需要之前,预先加载未来可能需要的资源。
- 示例:
<link rel="preload" href="script.js" as="script">。
- 示例:
-
-
缓存优化(Caching):
-
HTTP缓存:合理设置HTTP响应头(如
Cache-Control: max-age=<seconds>,ETag,Last-Modified),指导浏览器缓存资源,减少重复请求。 -
Service Worker缓存:渐进式Web应用(PWA)的核心,可以更精细地控制缓存策略,实现离线访问。
-
比喻:浏览器把网站常用文件缓存到本地硬盘,下次再访问就不用再从服务器下载了。
-
3.2 渲染与交互优化:流畅体验的保证
-
减少DOM节点数与重绘/重排(Reflow/Repaint):
-
重排(Reflow/Layout):当DOM元素的尺寸、位置、布局属性发生变化时,浏览器需要重新计算所有受影响元素的几何属性。这是最耗时的操作。
-
重绘(Repaint):当DOM元素的颜色、背景色等非几何属性变化时,浏览器只需重新绘制该元素。
-
优化:
-
避免频繁的DOM操作,尽量批量修改。
-
使用CSS
transform和opacity(这些属性只触发重绘,不触发重排)。 -
避免强制同步布局(如频繁读写
offsetTop、offsetWidth等)。
-
-
-
虚拟列表(Virtual Scrolling):
-
作用:在处理大数据量列表(如几千上万条记录)时,只渲染用户可见区域的列表项,不可见区域的项不渲染或只渲染占位符。
-
优点:大幅减少DOM节点数量,提升渲染性能和滚动流畅度。
-
工具:Vue Virtual Scroll List、React-Window等。
-
-
事件委托(Event Delegation):
-
作用:将事件监听器绑定到父元素上,而不是每个子元素。当子元素事件冒泡到父元素时,通过
event.target判断是哪个子元素触发的。 -
优点:减少内存消耗,提高性能,尤其适用于动态生成的列表。
-
-
防抖(Debounce)与节流(Throttle):
-
防抖:在事件触发后,等待一段指定时间,如果在等待时间内事件再次触发,则重新计时。只有在事件停止触发一段时间后,才执行回调函数。
- 用途:输入框搜索(用户停止输入后才发起请求)、窗口resize完成。
-
节流:在事件触发后,在指定的时间间隔内只执行一次回调函数。
- 用途:滚动事件、鼠标移动事件。
-
比喻:防抖是“你尽管触发,我只执行最后一次”;节流是“你尽管触发,我每隔一段时间执行一次”。
-
-
合理使用Web Worker:
-
作用:允许JavaScript在后台线程中运行耗时的计算任务,而不会阻塞浏览器主线程(UI线程)。
-
用途:复杂的数据处理、图像处理等计算密集型任务。
-
3.3 首屏与白屏优化:用户等待的“第一印象”
-
首屏时间(First Contentful Paint, FCP):用户第一次看到页面内容的时间。
-
白屏时间:用户看到空白屏幕的时间。
-
优化策略:
-
SSR/CSR混合渲染(Server-Side Rendering / Client-Side Rendering):
-
SSR:服务器端渲染。页面在服务器端渲染好HTML字符串再发送给浏览器。
-
优点:首屏快、SEO友好。
-
缺点:服务器压力大。
-
-
CSR:客户端渲染。浏览器下载JS,JS在客户端渲染HTML。
-
优点:服务器压力小。
-
缺点:首屏白屏时间长、SEO不友好。
-
-
混合渲染(同构渲染):第一次访问时使用SSR,后续客户端路由跳转使用CSR。例如Next.js (React), Nuxt.js (Vue)。
-
-
Skeleton骨架屏:在内容加载出来之前,先显示一个页面骨架(占位符),让用户感知到内容正在加载。
-
优化路由懒加载与首屏资源拆包:
-
只在访问某个路由时才加载对应的组件JS文件。
-
将首屏必要的JS/CSS打包成一个独立的小文件,优先加载。
-
-
3.4 性能监控:实时掌握用户体验
-
接入Web Vitals:监控Google提出的核心Web指标(Largest Contentful Paint, First Input Delay, Cumulative Layout Shift),这些直接反映用户体验。
-
前端错误日志采集:集成Sentry、Fundebug等工具,实时收集和上报前端JS错误、API错误。
-
用户行为埋点与性能埋点:收集用户点击、访问路径、页面加载时间等数据,用于分析用户行为和发现性能瓶颈。
四、后端高并发与性能优化:提升服务器“承载力”
后端性能是系统承载能力的核心,其优化策略侧重于提高并发处理能力和降低资源消耗。
4.1 代码与架构优化:后端“内功心法”
-
无阻塞/异步IO(Node.js天然支持):
- Node.js的事件循环和非阻塞I/O使其天生适合处理高并发请求。确保所有I/O操作(数据库查询、文件读写、网络调用)都使用异步API。
-
连接池(Connection Pool)复用:
-
数据库连接池:维护一组预先创建好的数据库连接,请求到来时直接从池中获取,使用完毕后归还。避免频繁创建和销毁连接的开销。
-
Redis连接池:类似数据库。
-
-
业务拆分:
-
读写分离:数据库主库负责写操作,从库负责读操作,将读压力分散到多个从库。
-
微服务化:将一个庞大的单体应用拆分为多个独立的小服务。每个服务可以独立部署、扩展,避免单个服务的瓶颈影响整个系统。
-
-
限流(Rate Limiting)与熔断(Circuit Breaking):
-
限流:限制对某个API在单位时间内的访问次数,防止流量突增击垮服务。
-
熔断:当某个服务(如依赖的第三方API、数据库)出现故障或响应慢时,调用方不再发送请求给它,而是直接返回失败或降级数据,防止故障扩散,保护自身。
-
-
任务异步化:
-
消息队列(Message Queue):将耗时、非实时性强的任务(如邮件发送、图片处理、订单生成、日志写入)放入消息队列。
-
原理:后端API收到请求后,将任务数据放入队列立即返回,由独立的消费者服务从队列中拉取任务并异步处理。
-
优点:解耦请求与处理,削峰填谷(缓冲突发流量),提高API响应速度。
-
工具:RabbitMQ、Kafka、Redis队列(如Bull.js)。
-
4.2 接口限流与防刷:防止“恶意攻击”与“刷量”
-
算法:
-
固定窗口(Fixed Window):每隔一个固定时间窗口(如1分钟),重置计数器。简单但可能导致窗口边缘突发流量。
-
滑动窗口(Sliding Window):维护一个队列,记录每个请求的时间戳,动态计算窗口内的请求数。更精确。
-
令牌桶(Token Bucket):以恒定速率向桶中添加令牌,请求需要从桶中获取令牌才能执行。即使有突发流量,也不会超过桶容量。
-
漏桶(Leaky Bucket):以固定速率处理请求,即使请求突发,处理速率也不会超过桶的容量。
-
-
Redis实现高效分布式限流:
-
原理:利用Redis的原子性操作(
INCR,EXPIRE)或Lua脚本实现计数器和过期时间,进行跨多服务实例的限流。 -
示例(Node.js+Redis):
// 假设已经有 Redis 连接实例 `redis` const rateLimitMiddleware = async (req, res, next) => { const ip = req.ip; // 获取客户端IP const key = `rate_limit:${ip}`; // Redis 键 const limit = 100; // 每分钟最多100次请求 const expiry = 60; // 窗口期1分钟 (秒) try { // INCR 命令是原子性的 const currentCount = await redis.incr(key); // 计数器加1 if (currentCount === 1) { // 如果是窗口内的第一个请求,则设置过期时间 await redis.expire(key, expiry); } if (currentCount > limit) { // 请求数量超过限制 return res.status(429).json({ code: 429, message: '访问过于频繁,请稍后再试' }); } next(); // 允许请求继续 } catch (error) { console.error('限流中间件错误:', error); next(error); // 传递错误给下一个中间件 } }; app.use(rateLimitMiddleware); // 在所有需要限流的路由前使用
-
-
防刷:验证码、IP黑名单、设备指纹识别、行为分析等。
4.3 缓存策略:降低数据库压力
-
数据库/接口缓存(Redis/Memcached):
-
将查询结果缓存到Redis,下次请求直接从缓存获取。
-
策略:
-
读写分离:写操作直接到数据库,读操作优先从缓存读取。
-
缓存失效/更新:
-
主动失效:数据更新时,主动删除或更新缓存。
-
被动失效(Lazy Loading):数据过期时自动失效,或读取时发现缓存不存在才去数据库加载。
-
定时刷新:定时任务更新缓存。
-
-
-
-
本地缓存+分布式缓存:
-
本地缓存:应用实例内部的内存缓存(如Node.js的
node-cache),速度最快。 -
分布式缓存:Redis等,多实例共享。
-
-
防止缓存穿透/雪崩/击穿:参见Redis课程中的详细讲解。
4.4 异步与队列:解耦与削峰
-
消息队列:如RabbitMQ、Kafka、Redis队列(Bull.js)。
-
作用:将高耗时、非实时性强的任务(如邮件发送、图片处理、复杂报表生成、日志处理、订单生成)从主业务流程中剥离,放入队列。
-
流量削峰:在高并发场景下,将瞬时大量请求放入队列排队处理,避免直接击垮后端服务或数据库。
-
最终一致性:通过消息队列实现分布式事务的最终一致性。
-
-
Node.js集成:使用
amqplib(RabbitMQ)、kafka-node(Kafka)、bull(Redis队列)。
4.5 数据库优化:后端性能的“核心”
-
合理设计索引与分库分表:根据查询模式优化索引,对于超大数据量,进行垂直分库(按业务)和水平分表(按数据)。
-
慎用JOIN与大事务:
-
复杂JOIN操作可能导致性能问题,尽量通过反范式设计或应用程序逻辑来避免。
-
大事务会长时间锁定资源,影响并发,尽量拆分为小事务。
-
-
主从读写分离:主库处理写请求,一个或多个从库处理读请求,分散数据库压力。
-
分区分片:数据库自身的分区功能,或在应用层实现分库分表。
-
使用批量写入、延迟写:减少数据库I/O次数。
五、架构层面高可用与弹性设计:系统“永不宕机”的奥秘
除了单个组件的性能优化,整个系统的架构设计也需要考虑高可用和弹性。
5.1 负载均衡(Load Balancing)
-
作用:将客户端请求均匀地分发到多台服务器(应用实例)上,避免单点过载,提高系统吞吐量和可用性。
-
工具:Nginx(反向代理负载均衡)、LVS(Linux Virtual Server,四层负载均衡)、云负载均衡服务(如阿里云SLB、AWS ELB)。
-
负载均衡策略:轮询、权重、最少连接、IP Hash等。
-
比喻:你开了很多家分店,负载均衡器就是你的“总调度员”,把客人均匀地分配到各个分店,防止一家店挤爆。
5.2 服务冗余与容错:应对“意外”
-
多实例部署:所有核心服务都部署多份(至少两份),避免单点故障。一台服务器宕机,其他服务器可以立即接管。
-
故障自愈:通过健康检查、服务注册与发现、自动重启等机制,当服务崩溃或不健康时,自动将其从服务列表中移除并尝试重启或替换。
-
降级(Degradation):当系统负载过高或某个依赖服务不可用时,暂时关闭部分非核心功能或返回简化数据,保证核心功能的可用性。
-
熔断(Circuit Breaking):前面已提,防止故障扩散。
5.3 自动扩容与弹性伸缩:应对“洪峰”
-
原理:根据系统负载(如CPU利用率、QPS、内存使用)自动增加或减少服务器实例数量,以适应流量的变化。
-
工具:
-
容器化(Docker):封装应用,使其易于部署。
-
Kubernetes(K8s):强大的容器编排平台,支持自动扩缩容、滚动升级、服务发现等。
-
云平台Auto Scaling服务:阿里云弹性伸缩、AWS Auto Scaling Groups。
-
-
优点:
-
弹性:自动应对流量高峰和低谷,节省成本。
-
高可用:自动替换故障实例。
-
六、典型高并发场景实战:案例分析
6.1 秒杀系统优化:电商的“大考”
-
特点:极短时间内的巨量并发请求(如10万QPS),库存扣减的原子性和一致性。
-
优化策略:
-
静态化商品详情页:将商品详情页(特别是抢购前)静态化部署到CDN,减轻Web服务器压力。
-
前端限流、接口防刷:在前端和API网关层进行限流,防止无效请求。
-
Redis库存预扣减:将库存放到Redis中进行预扣减,成功扣减后再放入异步下单队列。
- 优点:Redis操作速度快,原子性(
DECRBY),承载高并发。
- 优点:Redis操作速度快,原子性(
-
异步下单队列:将成功扣减库存的请求放入消息队列,后端消费者服务再慢速地将订单写入数据库,避免数据库瞬时压力过大。
-
数据库乐观锁/悲观锁:在最终扣减数据库库存时,使用乐观锁(版本号)或悲观锁(行锁)防止超卖。
-
-
比喻:为了让几万人同时抢一个商品,你把商品展示页提前印了百万份发给所有人看,然后让大家先去一个“高速入口”排队(Redis),谁先进去先抢到商品份额,就发一张“提货券”,然后这个提货券再慢悠悠地去总仓库(数据库)里领货。
6.2 聊天/推送/实时协作:无缝的“实时沟通”
-
特点:长连接、低延迟、双向通信、高并发消息处理。
-
技术:
-
WebSocket:建立客户端和服务器之间的长连接,实现双向通信和实时推送。
-
消息分片、分组推送:将消息按用户ID或频道进行分发。
-
离线消息队列:用户不在线时,消息先存储在队列,上线后再推送。
-
-
工具:Socket.IO、WebSocket库。
6.3 实时统计与热点排行:大数据的“即时洞察”
-
特点:大量数据实时写入,实时聚合统计,高频查询排行榜。
-
技术:
-
Redis HyperLogLog:用于独立访客(UV)估算。
-
Redis Sorted Set(ZSet):用于实时排行榜。
-
数据分桶、窗口统计:将数据按时间窗口(如每小时、每天)进行聚合。
-
异步汇总:将原始数据写入日志或消息队列,再由后台服务进行聚合计算。
-
七、自动化压测与性能保障:上线前的“大演习”
7.1 压测工具:模拟“洪峰”
-
ab(Apache Benchmark):简单的HTTP压力测试工具。 -
wrk:高性能HTTP基准测试工具,支持多线程和Lua脚本。 -
JMeter:功能强大,支持多种协议(HTTP, FTP, JDBC等),支持复杂的测试场景。
-
Locust:Python编写的开源负载测试工具,可以方便地编写测试脚本。
-
k6:JavaScript编写的现代负载测试工具。
7.2 压测流程:有计划地“制造压力”
-
设计典型业务用例:模拟用户真实行为,如登录、浏览商品、添加购物车、提交订单。
-
逐步升压:从小并发数开始,逐步增加并发用户数或QPS,观察系统性能变化。
-
找出系统极限:测试系统在何种负载下开始出现响应时间急剧增加或错误率上升。
-
监控:在压测过程中,实时监控CPU/内存/带宽/响应延迟/数据库连接数等指标,定位瓶颈。
7.3 性能监控与报警:上线后的“值班医生”
-
服务端:
-
Prometheus + Grafana:开源的监控解决方案。Prometheus收集指标,Grafana进行可视化展示。
-
APM平台:如Datadog、New Relic、阿里云ARMS、腾讯云APM,提供全链路性能分析、应用健康度监控。
-
-
前端埋点、接口延迟、错误率监控:确保用户体验始终良好。
八、与全栈架构和后续进阶的衔接:性能优化的“无止境”
-
性能优化贯穿前后端、数据库、运维全链路:它不是某一个环节的责任,而是整个团队的共同目标。
-
高并发是微服务、云原生、分布式系统的核心能力:理解如何处理高并发,是学习这些高级架构的基石。
-
后续可深入学习:分布式事务(复杂场景)、服务网格(Istio)、微服务拆分与治理、异地多活(容灾)等高级架构。
九、学习建议与扩展资源:持续探索,成为性能专家
-
推荐平台:
- 牛客网高并发题库、掘金/知乎架构专栏、阿里云/腾讯云技术博客。
-
推荐书籍:
-
《高性能网站建设指南》(Steve Souders):经典前端性能优化。
-
《Redis开发与运维》、《大型网站技术架构:核心原理与案例分析》(李智慧)。
-
《分布式服务架构:原理、设计与实战》。
-
-
视频课程:极客时间“高并发系统设计40问”、“后端技术面试38讲”等。
十、课后练习与思考:挑战你的高并发思维
-
实现一个API限流中间件:
- 用Node.js+Redis实现一个API限流中间件,支持基于IP的频率限制,并支持IP白名单(白名单IP不受限)。
-
设计前端图片懒加载和缓存方案:
- 设计并实现一个前端图片懒加载方案,结合浏览器HTTP缓存,提升网页首屏加载速度。
-
压力测试:
-
选择一个你编写的后端API,使用
ab、wrk或JMeter进行压力测试。 -
逐步增加并发请求数,观察CPU、内存、响应时间、错误率的变化。
-
分析日志,找出瓶颈所在。
-
-
思考题:
-
请解释“缓存穿透”、“缓存雪崩”和“缓存击穿”的区别,并针对每种情况,给出至少两种常见的解决方案。
-
在设计一个高并发的秒杀系统时,你会如何处理库存的原子性扣减问题,并防止超卖?
-
当你的Web应用遇到CPU密集型任务时(例如,处理大量数据计算或图像处理),你会采取哪些策略来避免阻塞主线程,并保证高并发处理能力?
-
同学们,高并发与性能优化是软件工程中极具挑战性也极具价值的领域。掌握它,你就能构建出能够应对海量用户和复杂场景的健壮系统。
至此,我们已经完成了第四阶段**“软件工程与系统优化”**的第二课“高并发与性能优化实战”的所有内容。接下来,我们将迈向更宏大的系统架构——分布式系统与微服务基础。请大家稍作休息,我们稍后继续。
好的,同学们,我们继续第四阶段**“软件工程与系统优化”**的学习!上一节我们深入探讨了高并发与性能优化的各种策略,理解了如何让一个系统在面对大量请求时依然能够高效运行。现在,我们将把目光投向一个更宏大、更复杂的系统架构——分布式系统与微服务基础。
当单台服务器的性能或可用性无法满足需求时,我们就需要将应用部署到多台服务器上,让它们协同工作,对外提供统一的服务。这就是分布式系统的核心思想。而微服务,则是当下构建复杂分布式系统最流行的架构模式。这就像我们从盖一栋独立别墅,发展到建造一个由多栋功能独立、协同运作的摩天大楼组成的城市群。
课程4.3:分布式系统与微服务基础(超详细版)
一、分布式系统基本概念:化零为整的“集群”
1.1 什么是分布式系统
-
分布式系统(Distributed System):
-
含义:由多台独立的物理或虚拟服务器(节点)通过网络连接,并以协作的方式对外提供统一服务的系统。
-
核心目标:
-
高可用(High Availability, HA):部分节点故障不影响整体服务。
-
高扩展(Scalability):通过增加节点数量来提升处理能力。
-
高性能(High Performance):通过并行处理提升响应速度和吞吐量。
-
容错性(Fault Tolerance):能够从部分组件的故障中恢复。
-
灵活性与弹性:能够适应不断变化的需求和负载。
-
-
比喻:一家大公司,不再只有一个万能的总经理处理所有事务,而是有多个部门经理、项目经理,他们各自负责一块,但最终目标都是让公司正常运转。
-
1.2 分布式与单体/集群/微服务区别:架构的演进
理解分布式系统,需要先区分几种常见的系统架构模式。
| 架构类型 | 特点 | 优点 | 缺点 | 适用场景 |
|--------------|------------------------------------------|----------------------------------------|----------------------------------------|-----------------------------|
| 单体(Monolithic) | 所有功能模块打包成一个独立的应用程序,部署在一个进程中。 | 部署简单,开发上手快,调试方便。 | 耦合度高,扩展性差,难以独立升级。 | 小型项目,初创公司,业务简单。|
| 集群(Clustering) | 多个单体应用实例部署在多台服务器上,通过负载均衡对外提供服务。 | 提高可用性、吞吐量,一定程度上实现扩展。 | 功能模块仍耦合,扩展受限于单体性能。 | 中小型项目,需要简单的水平扩展。|
| 分布式(Distributed System) | 将一个大系统按功能或数据拆分成多个独立的模块/子系统,部署在不同节点上,通过网络通信协作。 | 提升可用性、扩展性、性能、容错性。 | 复杂度高,维护难,一致性挑战。 | 高并发、大数据、复杂业务系统。|
| 微服务(Microservices) | 分布式架构的一种具体实现,将大系统拆分成更小、更自治、可独立部署的服务。 | 极致的解耦、独立开发/部署/扩展、技术栈多样化。 | 开发/运维复杂度更高,服务治理挑战。 | 大型、复杂、需要快速迭代的业务。|
1.3 分布式系统的挑战:从简单到复杂的“代价”
虽然分布式系统带来了巨大的优势,但也引入了新的复杂性和挑战。
-
网络延迟与不可靠:不同节点之间通过网络通信,网络抖动、延迟、丢包都是常态,这需要更复杂的通信协议和容错机制。
-
数据一致性:数据在多个节点间冗余存储时,如何保证所有节点的数据都是一致的?这是核心难题。
-
服务发现与通信:当服务数量庞大且动态变化时,服务之间如何发现彼此并进行通信?
-
分布式事务:一个业务操作涉及多个独立服务的数据修改时,如何保证这些修改的原子性?
-
容错与恢复:某个节点或服务故障时,如何确保系统能够自动恢复并继续提供服务?
-
分布式锁与协作:在分布式环境下,如何实现对共享资源的互斥访问?
二、分布式系统关键原理:应对挑战的“理论基石”
2.1 CAP理论:一致性、可用性与分区容错性的“不可能三角”
-
概念:CAP理论指出,在一个分布式系统中,你无法同时满足以下三点:
-
一致性(Consistency, C):所有节点在同一时刻看到的数据都是一致的。
-
可用性(Availability, A):系统在任何时候都能对外提供服务,每个请求都能得到响应(不保证响应的数据是最新的)。
-
分区容错性(Partition Tolerance, P):系统在网络分区(节点之间无法通信)发生时,仍能继续对外提供服务。
-
-
权衡:在一个真实的分布式网络中,分区(P)是必然发生的(网络可能中断)。因此,系统设计者必须在一致性(C)和可用性(A)之间进行权衡。
-
CP系统:追求强一致性,牺牲可用性。当网络分区时,为了保证数据一致,部分服务会停止响应。
- 例子:传统关系型数据库集群(如MySQL主从,主节点故障不可写)、ZooKeeper、Etcd。
-
AP系统:追求高可用性,牺牲强一致性,追求最终一致性。当网络分区时,系统仍能对外提供服务,但可能读到旧数据。
- 例子:多数NoSQL数据库(如Cassandra、DynamoDB)。
-
-
老师提示:没有绝对的C或A,只有在特定场景下的权衡和取舍。
2.2 BASE理论:柔性事务的“指导原则”
-
概念:BASE理论是对CAP理论中AP系统的延伸,强调在牺牲强一致性后,如何实现最终的一致性。
-
基本可用(Basically Available):分布式系统在出现故障时,允许损失部分可用性(如响应时间延长、功能降级),但核心服务必须保持可用。
-
软状态(Soft State):允许系统中的数据存在中间状态,这个中间状态不影响系统的整体可用性。
-
最终一致性(Eventually Consistent):系统中的所有数据副本,在经过一段时间的同步后,最终会达到一致状态。
-
-
比喻:你发了一个微信朋友圈,可能不是所有朋友瞬间就能看到,但最终都会看到。这就是最终一致性。
-
用途:BASE理论指导了多数NoSQL数据库和微服务架构中的柔性事务设计。
2.3 一致性协议:保证数据和状态的“统一”
为了在分布式系统中实现数据和状态的一致性,出现了多种复杂的一致性协议。
-
分布式ID生成:
-
问题:在分布式环境下,如何生成全局唯一且趋势递增的ID?
-
解决方案:
-
UUID/GUID:通用唯一标识符,但无序,不适合作为主键索引。
-
数据库自增ID + 步长:预先分配ID段或设置不同步长。
-
雪花算法(Snowflake Algorithm):Twitter开源的分布式ID生成算法,生成64位ID,包含时间戳、机器ID、数据中心ID、序列号,保证唯一性和趋势递增。
-
Redis原子操作:利用Redis的
INCRBY命令。
-
-
-
分布式锁:
-
问题:多个服务实例同时尝试修改同一共享资源时,如何保证互斥性?
-
解决方案:基于Redis、ZooKeeper、Etcd等实现。
-
Redis:利用
SET key value NX PX milliseconds(原子性地设置键并带过期时间,如果键不存在)。 -
ZooKeeper/Etcd:利用其临时节点和顺序节点特性实现分布式锁。
-
-
-
共识协议(Consensus Protocols):
-
问题:在分布式系统中,多个节点如何就某个值或操作达成一致?
-
解决方案:
-
Paxos:理论上最复杂、最安全的一致性算法。
-
Raft:更易于理解和实现的强一致性算法。
-
-
应用:ZooKeeper、Etcd、Consul等分布式协调服务都实现了Raft或Paxos的变种,用于管理分布式配置、服务注册发现、Leader选举等。
-
2.4 服务发现与注册:动态变化的“通讯录”
-
问题:在微服务架构中,服务实例是动态变化的(自动扩缩容、上线下线),一个服务如何找到它依赖的另一个服务的具体网络地址?
-
概念:
-
服务注册(Service Registration):服务提供者(服务实例)在启动时,将其网络地址和元数据注册到服务注册中心。
-
服务发现(Service Discovery):服务消费者通过服务注册中心查询它所依赖的服务实例的网络地址。
-
-
实现方式:
-
客户端服务发现:客户端直接查询注册中心获取服务地址,然后自己进行负载均衡。
-
服务端服务发现:客户端请求负载均衡器或API网关,由它们从注册中心获取服务地址并转发请求。
-
-
常用组件:
-
Consul (HashiCorp):提供服务发现、健康检查、KV存储。
-
Etcd (CoreOS/CNCF):轻量级分布式K-V存储,强一致性,常用于服务发现、配置管理。
-
ZooKeeper (Apache):老牌的分布式协调服务,提供配置管理、命名服务、分布式锁等。
-
Eureka (Netflix):Spring Cloud生态中的服务注册发现组件。
-
Nacos (Alibaba):支持服务发现、配置管理、动态DNS服务。
-
三、微服务架构设计:精细化管理的“小而美”
3.1 什么是微服务
-
微服务(Microservices):
-
含义:一种架构风格,旨在将一个大型、复杂的单体应用程序,按照业务功能拆分为多个小型、独立、自治、松耦合的服务。
-
特点:
-
单一职责:每个微服务只负责一个特定的业务功能(如用户服务、订单服务、商品服务)。
-
独立部署:每个微服务可以独立开发、测试、部署、升级,不影响其他服务。
-
松耦合:服务之间通过轻量级通信机制(如HTTP REST API、消息队列)进行交互。
-
技术多样性:不同微服务可以使用不同的编程语言、框架、数据库(Polyglot Persistence)。
-
自治性:每个服务拥有自己的数据存储,并负责自己的生命周期。
-
-
-
比喻:将一个巨大的、所有功能都在一起的“万能刀”,拆分成一套由多个功能专一、独立组合的“瑞士军刀”。
3.2 微服务优缺点
-
优点:
-
解耦业务:提高代码的可维护性,降低修改代码的风险。
-
独立扩展:可以根据不同服务的负载需求独立扩容,节省资源。
-
敏捷交付:小团队可以独立开发和部署,加速产品迭代。
-
技术多样性:可以为每个服务选择最适合的技术栈。
-
容错性:单个服务故障不会导致整个系统瘫痪。
-
-
缺点:
-
开发复杂度高:增加了服务间通信、数据一致性、分布式事务、服务治理等复杂性。
-
运维压力大:服务数量剧增,部署、监控、日志、故障排查变得复杂。
-
分布式事务管理更难。
-
网络开销增加:服务间通信引入网络延迟。
-
3.3 微服务核心组件:服务治理的“工具箱”
为了有效管理和协调大量的微服务,需要一系列核心组件。
-
API网关(API Gateway):
-
作用:作为所有客户端请求的统一入口。它负责路由请求到正确的微服务、请求聚合、认证鉴权、限流、日志记录等。
-
工具:Kong、Nginx、Spring Cloud Gateway、Envoy、API7。
-
比喻:公司的前台或总机,所有外部电话都打到这里,然后由前台转接到相应的部门。
-
-
服务注册与发现:前面已详细讲解。
-
配置中心(Configuration Center):
-
作用:集中管理所有微服务的配置信息,支持配置动态更新,无需重启服务。
-
工具:Nacos、Apollo、Spring Cloud Config、Consul KV。
-
-
链路追踪(Distributed Tracing):
-
作用:在分布式系统中,一个请求可能经过多个微服务。链路追踪系统可以跟踪请求的完整调用链,记录每个服务的耗时,帮助开发者快速定位性能瓶颈和故障点。
-
工具:Jaeger、Zipkin、SkyWalking、OpenTelemetry。
-
比喻:快递的每一个环节都扫码记录,你可以追踪包裹到哪里了,在哪里停留了多久。
-
-
熔断(Circuit Breaking)与降级(Degradation):
-
熔断:当某个依赖的服务出现故障或响应超时时,立即“熔断”对其的调用,快速失败,避免雪崩效应。
-
降级:当系统负载过高或某个非核心服务不可用时,暂时关闭部分功能,保证核心功能可用。
-
工具:Netflix Hystrix(已停止维护)、Alibaba Sentinel。
-
-
消息队列(Message Queue):
-
作用:服务之间进行异步通信和解耦。用于削峰填谷、最终一致性事务、事件驱动。
-
工具:RabbitMQ、Kafka、ActiveMQ、RocketMQ。
-
3.4 微服务通信方式:服务间的“对话协议”
-
同步HTTP/REST:
-
特点:最常见、最简单的通信方式。一个服务调用另一个服务的REST API。
-
优点:简单直观,易于理解和调试。
-
缺点:同步阻塞,如果被调用方响应慢或故障,调用方也会受影响;性能开销相对较大(HTTP头解析)。
-
-
异步消息队列:
-
特点:服务A发送消息到消息队列,服务B从消息队列消费消息。
-
优点:解耦(发布者和订阅者互不依赖),削峰填谷,实现最终一致性。
-
缺点:引入消息队列的复杂性,需要保证消息的可靠投递和幂等性。
-
-
gRPC/Protobuf:
-
gRPC:基于HTTP/2的RPC(远程过程调用)框架。
-
Protobuf(Protocol Buffers):Google开发的语言无关、平台无关、可扩展的序列化结构数据的方法。
-
优点:高性能(二进制协议、多路复用)、支持多种语言、强类型定义。
-
用途:适合微服务内部之间的高效通信。
-
四、分布式系统开发与部署实践:从理论到落地
4.1 服务拆分原则:如何“切割”大蛋糕?
这是微服务设计的核心,也是最难的部分。
-
按领域/业务边界拆分:最常用的方法。将业务上独立的模块(如用户、订单、商品、支付、库存)拆分为独立的服务。
-
保持服务高内聚,低耦合:
-
高内聚:一个服务只专注于完成一个业务功能,所有相关逻辑都在服务内部。
-
低耦合:服务之间依赖性最小,通过明确的API接口通信,减少直接依赖。
-
-
公共功能抽象为独立服务:将跨业务的通用功能(如认证鉴权、消息通知、文件存储、搜索)抽象为独立的微服务。
4.2 分布式事务处理:保证跨服务操作的“一致性”
由于微服务的数据是独立的,一个业务操作可能涉及多个服务的数据修改,传统的关系型数据库事务无法直接跨服务。
-
两阶段提交(2PC, Two-Phase Commit):
-
原理:协调者(Coordinator)向所有参与者(Participant)发送准备(Prepare)请求,参与者执行操作但暂不提交。所有参与者都准备好后,协调者再发送提交(Commit)请求。
-
优点:可以实现强一致性。
-
缺点:效率低,性能差,单点故障风险高(协调者故障),存在阻塞。
-
-
本地消息表/事件最终一致性:
-
原理:
-
服务A在执行业务操作的同时,向本地数据库的本地消息表写入一条消息。
-
服务A提交本地事务(业务操作+消息写入)。
-
独立的消息发送者组件(或定时任务)扫描本地消息表,将消息发送到消息队列。
-
服务B从消息队列消费消息,执行自己的业务操作,并提交本地事务。
-
-
优点:实现最终一致性,不影响主业务性能,常用于微服务架构。
-
缺点:增加了系统复杂性,需要确保消息的可靠投递和幂等性。
-
-
Saga模式:
-
原理:将一个长事务分解为一系列短事务。如果其中一个短事务失败,则通过执行一系列**补偿事务(Compensating Transaction)**来撤销之前的操作,从而回滚整个长事务。
-
优点:适用于长事务,无2PC的阻塞问题,可以处理跨多个服务的事务。
-
缺点:实现复杂,补偿逻辑难以管理。
-
4.3 配置与注册中心实践:动态管理“服务参数”
-
Nacos/Consul/Etcd:
-
配置管理:将应用的配置集中存储,支持配置热更新(无需重启服务),灰度发布配置。
-
服务注册发现:服务实例启动时自动向注册中心注册,心跳机制保持健康状态,关闭时自动注销。服务消费者从注册中心获取服务地址。
-
-
应用启动时自动注册、健康检查、动态发现依赖服务:
-
每个微服务启动时,通过客户端库(如Spring Cloud Consul、Nacos SDK)自动注册到配置中心。
-
注册中心会定期对服务实例进行健康检查。
-
服务间调用时,通过注册中心动态发现依赖服务的可用实例。
-
4.4 API网关与统一鉴权:外部请求的“守门人”
-
Kong、Nginx、Spring Cloud Gateway、API7等:
-
统一入口:所有客户端请求都先经过API网关。
-
路由转发:将请求转发到正确的后端微服务。
-
统一鉴权:在网关层进行用户认证(如JWT校验),将用户信息透传给下游微服务,减轻每个微服务的认证压力。
-
限流、熔断:在网关层对请求进行限流和熔断,保护后端服务。
-
请求聚合:将多个微服务的响应聚合成一个响应返回给客户端。
-
日志、监控:在网关层统一记录请求日志和监控。
-
灰度发布、蓝绿部署:通过网关的路由规则实现流量的细粒度控制。
-
4.5 容器化与自动化部署:DevOps的“基础设施”
-
Docker封装服务:将每个微服务及其所有依赖(语言运行时、库等)打包成一个独立的、可移植的Docker镜像。
-
Kubernetes(K8s)实现弹性伸缩与自动恢复:
-
部署:K8s负责在集群中的节点上调度和运行Docker容器。
-
自动扩缩容(Autoscaling):根据CPU利用率、内存使用或自定义指标,自动增加或减少Pod(容器)的数量。
-
服务发现:K8s内置Service机制,提供服务发现和负载均衡。
-
故障自愈:当容器崩溃或节点故障时,K8s会自动重启容器或将容器调度到其他健康节点。
-
滚动升级(Rolling Update):平滑地更新服务版本,不中断服务。
-
-
CI/CD自动化测试与发布(GitHub Actions、Jenkins):
-
Git触发:代码推送到Git仓库后,自动触发CI/CD流水线。
-
CI(持续集成):自动构建、运行单元测试、集成测试、生成Docker镜像并推送到镜像仓库。
-
CD(持续部署/交付):自动从镜像仓库拉取镜像,更新K8s集群中的部署(Deployment),实现服务升级。
-
到这里,我们已经深入了解了分布式系统和微服务架构的复杂性,以及如何通过各种组件和技术策略来构建、管理和部署这些大规模系统。
好的,同学们,我们继续分布式系统与微服务基础的学习!上一节我们全面探讨了分布式系统的核心概念、CAP理论、微服务架构的优缺点和主要组件。现在,我们将把目光投向如何在Node.js生态中实践微服务,以及高可用与容错的策略。
Node.js的单线程、事件驱动特性使其天然适合作为轻量级、高并发微服务的载体。同时,构建分布式系统,除了追求高性能,更要关注在部分组件故障时,整个系统依然能够稳定运行的能力。
五、Node.js与微服务集成实践:用JS构建“小而美”的服务
Node.js的轻量级和高并发特性使其成为构建微服务的优秀选择,尤其是在需要快速开发、I/O密集型和前后端语言统一的场景下。
5.1 Node.js实现REST微服务
-
核心思想:
-
将大型应用按照业务领域(如用户、订单、产品、评论等)拆分成多个独立的Node.js服务。
-
每个服务都是一个独立的Express或Koa应用,监听独立的端口。
-
每个服务可以拥有自己独立的数据库(或数据库中的独立表/集合),实现数据的独立性。
-
-
服务间通信:
-
HTTP/REST调用:最常见的通信方式。一个微服务通过HTTP客户端(如
axios)调用另一个微服务的REST API。- 示例:订单服务在创建订单时,需要调用用户服务来获取用户详情,调用产品服务来查询商品信息和扣减库存。
-
gRPC:对于服务内部之间需要高性能、强类型通信的场景,可以使用gRPC(基于HTTP/2和Protobuf)。
-
-
示例:假设你拆分了一个用户服务(User Service)和一个订单服务(Order Service)。
// --- user-service/app.js --- const express = require('express'); const app = express(); app.use(express.json()); const USER_PORT = 3001; let users = [{ id: 1, username: 'alice', email: 'alice@example.com' }]; // 模拟用户数据库 app.get('/users/:id', (req, res) => { const user = users.find(u => u.id === parseInt(req.params.id)); if (user) { res.json({ code: 0, data: user }); } else { res.status(404).json({ code: 404, message: 'User not found' }); } }); app.post('/users', (req, res) => { const newUser = { id: users.length + 1, ...req.body }; users.push(newUser); res.status(201).json({ code: 0, data: newUser }); }); app.listen(USER_PORT, () => console.log(`User Service running on port ${USER_PORT}`)); // --- order-service/app.js --- const express = require('express'); const axios = require('axios'); // npm install axios const app = express(); app.use(express.json()); const ORDER_PORT = 3002; const USER_SERVICE_URL = 'http://localhost:3001'; // 用户服务的地址 let orders = []; // 模拟订单数据库 app.post('/orders', async (req, res) => { const { userId, productId, quantity } = req.body; try { // 1. 调用用户服务获取用户详情 const userResponse = await axios.get(`${USER_SERVICE_URL}/users/${userId}`); const user = userResponse.data.data; if (!user) { return res.status(404).json({ code: 404, message: 'User not found' }); } console.log(`用户 ${user.username} 正在创建订单...`); // 2. 模拟调用产品服务扣减库存 (这里省略实际的产品服务调用) // const productResponse = await axios.post(`${PRODUCT_SERVICE_URL}/products/deduct`, { productId, quantity }); // if (productResponse.data.code !== 0) { // return res.status(400).json({ code: 400, message: 'Stock not enough' }); // } // 3. 创建订单 const newOrder = { id: orders.length + 1, userId: user.id, productId, quantity, status: 'pending', createdAt: new Date() }; orders.push(newOrder); res.status(201).json({ code: 0, data: newOrder, message: 'Order created' }); } catch (error) { console.error('Error creating order:', error.message); res.status(500).json({ code: 500, message: 'Failed to create order', error: error.message }); } }); app.get('/orders/:id', (req, res) => { const order = orders.find(o => o.id === parseInt(req.params.id)); if (order) { res.json({ code: 0, data: order }); } else { res.status(404).json({ code: 404, message: 'Order not found' }); } }); app.listen(ORDER_PORT, () => console.log(`Order Service running on port ${ORDER_PORT}`));部署运行:分别在两个终端中运行
node user-service/app.js和node order-service/app.js。
5.2 消息队列集成:解耦异步任务
-
作用:将服务之间紧密的同步调用转换为异步通信,实现服务的解耦。
-
工具:
-
RabbitMQ:经典消息队列,支持多种协议,功能强大。Node.js客户端
amqplib。 -
Kafka:分布式流处理平台,高吞吐量,持久化。Node.js客户端
kafka-node。 -
Redis Queue:利用Redis列表作为轻量级队列。Node.js库
bull、kue。
-
-
示例:订单创建后,发布一个“订单已创建”的消息到消息队列,由独立的通知服务、物流服务、库存服务等订阅并处理,实现最终一致性。
// --- order-service/app.js (伪代码,集成消息队列) --- const amqp = require('amqplib'); // npm install amqplib const RABBITMQ_URL = 'amqp://localhost'; // RabbitMQ 地址 // ... (其他 Express 代码) app.post('/orders', async (req, res) => { // ... (创建订单逻辑) // 订单创建成功后,发布消息 try { const conn = await amqp.connect(RABBITMQ_URL); const channel = await conn.createChannel(); const queueName = 'order_created_events'; await channel.assertQueue(queueName, { durable: true }); // 持久化队列 const msg = JSON.stringify({ orderId: newOrder.id, userId: newOrder.userId }); channel.sendToQueue(queueName, Buffer.from(msg), { persistent: true }); // 持久化消息 console.log(`[Order Service] Published order created event: ${msg}`); await channel.close(); await conn.close(); } catch (error) { console.error('Failed to publish message to RabbitMQ:', error); // 考虑回滚订单或日志记录,保证最终一致性 } res.status(201).json({ code: 0, data: newOrder, message: 'Order created' }); }); // --- notification-service/app.js (伪代码,消费消息) --- const amqp = require('amqplib'); const RABBITMQ_URL = 'amqp://localhost'; async function consumeOrderEvents() { try { const conn = await amqp.connect(RABBITMQ_URL); const channel = await conn.createChannel(); const queueName = 'order_created_events'; await channel.assertQueue(queueName, { durable: true }); console.log(" [*] Waiting for messages in %s. To exit press CTRL+C", queueName); channel.consume(queueName, (msg) => { if (msg.content) { const orderEvent = JSON.parse(msg.content.toString()); console.log(` [x] Received order event: Order ID ${orderEvent.orderId}, User ID ${orderEvent.userId}`); // 实际业务逻辑:发送通知邮件或短信给用户 // await sendNotificationEmail(orderEvent.userId, orderEvent.orderId); channel.ack(msg); // 确认消息已处理 } }, { noAck: false // 需要手动确认 }); } catch (error) { console.error('Error consuming messages:', error); } } consumeOrderEvents();
5.3 API网关集成:统一入口与流量控制
-
作用:在客户端和微服务之间增加一个API网关层。客户端只与网关交互,网关负责路由请求到后端不同的微服务。
-
工具:可以自建(用Express/Koa实现)或使用专业的API网关(如Kong、Nginx、Envoy、Spring Cloud Gateway)。
-
功能:统一鉴权、限流、日志、请求聚合、路由转发、蓝绿部署/灰度发布。
// --- api-gateway/app.js (伪代码) --- const express = require('express'); const { createProxyMiddleware } = require('http-proxy-middleware'); // npm install http-proxy-middleware const app = express(); const GATEWAY_PORT = 3000; // 假设用户认证中间件 function authMiddleware(req, res, next) { // ... (JWT 验证逻辑) req.user = { id: 1, username: 'gateway_user' }; // 验证成功后注入用户 next(); } // 将 /api/users 路由到用户服务 app.use('/api/users', authMiddleware, createProxyMiddleware({ target: 'http://localhost:3001', changeOrigin: true, pathRewrite: { '^/api/users': '/users' } // 重写路径 })); // 将 /api/orders 路由到订单服务 app.use('/api/orders', authMiddleware, createProxyMiddleware({ target: 'http://localhost:3002', changeOrigin: true, pathRewrite: { '^/api/orders': '/orders' } })); // ... 其他路由和错误处理 app.listen(GATEWAY_PORT, () => console.log(`API Gateway running on port ${GATEWAY_PORT}`));
六、分布式系统的高可用与容错:确保系统“不死机”
高可用(High Availability)是指系统在面对硬件故障、软件错误、网络中断等异常情况时,仍能持续提供服务的能力。
6.1 故障自愈:系统“自我修复”
-
自动重启:当进程或容器崩溃时,进程管理器(如PM2)或容器编排系统(如Kubernetes)会自动重启它们。
-
故障转移(Failover):当主节点(Master)或主服务故障时,备用节点(Standby)或从节点(Slave)自动接管其职责。
- 例子:Redis Sentinel模式、数据库主从切换。
-
健康检查(Health Checks):负载均衡器或容器编排系统定期向服务实例发送请求,检查其是否健康。不健康的实例会被自动从服务列表中移除,直到恢复正常。
6.2 熔断与降级:保护“核心功能”
-
熔断(Circuit Breaking):
-
原理:当对某个依赖服务的调用失败率达到一定阈值时,客户端不再尝试调用该服务,而是直接快速失败,避免大量请求堆积在故障服务上,保护自身和服务提供者。
-
状态:断开(Open)、半断开(Half-Open)、闭合(Closed)。
-
比喻:家里电路短路,断路器(熔断器)会自动跳闸,保护电器不被烧毁。等检修好,再手动合闸。
-
-
降级(Degradation):
-
原理:在系统资源不足或依赖服务不可用时,暂时关闭或简化部分非核心功能,以保证核心功能的可用性。
-
例子:电商网站在秒杀时关闭评论功能、商品推荐功能,只保留购买功能;新闻网站在图片加载失败时只显示文字。
-
6.3 链路追踪与监控:全局视野的“侦察兵”
-
链路追踪(Distributed Tracing):
-
作用:追踪一个请求在分布式系统中穿梭的完整路径和耗时,帮助开发者快速定位哪个服务或哪个方法是性能瓶颈。
-
工具:Jaeger、Zipkin、SkyWalking、OpenTelemetry。
-
原理:在每个服务调用时,传递一个唯一的追踪ID,并在日志中记录。
-
-
监控:
-
指标监控:收集和展示每个服务的CPU、内存、网络、QPS、响应时间、错误率等指标。
-
日志聚合:将所有服务的日志集中到一个平台,便于搜索、过滤和分析。
-
工具:Prometheus + Grafana、ELK Stack、云厂商的监控服务。
-
七、典型分布式/微服务架构案例:成功的“蓝图”
7.1 电商平台微服务化
-
服务拆分:用户服务、商品服务、订单服务、支付服务、库存服务、评价服务、搜索服务、通知服务、API网关。
-
特点:
-
各服务独立数据库、独立部署、独立扩缩容。
-
API网关作为统一入口,进行认证鉴权、限流。
-
消息队列保证订单创建、库存扣减、通知发送的最终一致性。
-
Redis用于缓存、分布式锁、秒杀预扣减。
-
7.2 实时协作与推送系统
-
服务拆分:用户服务、文档服务、协作服务、消息服务、推送服务。
-
特点:
-
大量使用WebSocket长连接进行实时通信。
-
分布式消息总线(Kafka/RabbitMQ)用于消息分发和处理。
-
数据存储和状态同步需要考虑最终一致性。
-
高并发下的连接管理和消息路由。
-
八、与全栈/云原生/架构进阶的衔接:未来的“康庄大道”
-
分布式与微服务是现代互联网架构的基础:理解这些,你才能设计和构建符合时代需求的复杂系统。
-
云原生(Cloud Native)进一步提升弹性和自动化:Kubernetes、服务网格(Istio)、Serverless等技术,都是在分布式和微服务基础上,更进一步地利用云计算的优势。
-
架构师需掌握核心能力:分布式一致性、CAP权衡、微服务拆分与治理、高可用、容灾、可观测性等。
九、学习建议与扩展资源:持续学习,突破自我
-
推荐文档:
-
微服务架构模式(Microservices.io):Martin Fowler的经典网站。
-
Spring Cloud官方文档:Java生态,但很多微服务概念通用。
-
Node.js微服务实践框架(如Moleculer、NestJS):了解Node.js微服务生态。
-
-
推荐书籍:
-
《微服务设计》(Sam Newman):微服务入门经典。
-
《分布式系统原理与范型》(分布式系统的“圣经”)。
-
《大型网站技术架构:核心原理与案例分析》(李智慧):国内大规模系统架构经典。
-
-
视频课程:极客时间《微服务架构实战》、《分布式系统30讲》等。
-
云厂商架构案例:阿里云、腾讯云、AWS等官方技术博客和架构实践。
十、课后练习与思考:挑战你的分布式思维
-
用Node.js实现一个基础的微服务项目:
-
实现两个独立的Node.js微服务:例如“用户服务”和“产品服务”。
-
用户服务提供用户的注册、登录、获取详情API。
-
产品服务提供产品的创建、查询、扣减库存API。
-
尝试让订单服务(或其他服务)通过HTTP调用这两个服务,完成一个简单的业务流程(例如用户购买产品,需要查询用户、扣减库存)。
-
-
搭建本地Consul/Etcd/Nacos:
-
尝试在你的本地机器或虚拟机上搭建一个Consul/Etcd/Nacos实例。
-
修改你编写的微服务,使其能在启动时自动向这个注册中心注册。
-
当一个服务关闭后,观察注册中心的服务列表变化。
-
-
思考题:
-
微服务架构中,服务拆分过细和过粗会分别带来哪些问题?你认为如何才能找到一个合理的拆分粒度?
-
在分布式系统中,如何保证数据的一致性?请结合CAP理论和BASE理论,解释你在实际项目中会如何进行权衡和选择?
-
你认为分布式系统与微服务架构在哪些场景下是“过度设计”?哪些场景下是“必要且最优”的?
-
同学们,分布式系统与微服务是现代软件架构的精髓。掌握它们,你就能从构建单体应用迈向设计和管理复杂、大规模的互联网系统。这是你从一个全栈开发者向架构师进阶的关键一步。
至此,我们已经完成了第四阶段**“软件工程与系统优化”**的第三课“分布式系统与微服务基础”的所有内容。接下来,我们将进入更前沿、更高效的部署和运维方式——云原生与DevOps基础。请大家稍作休息,我们稍后继续。
好的,同学们,我们继续第四阶段**“软件工程与系统优化”**的学习!上一节我们深入探讨了分布式系统与微服务架构,理解了如何构建大规模、高可用、高扩展的复杂系统。现在,我们将把目光投向一个融合了这些理念,并将其推向极致的现代软件开发和运维模式——云原生(Cloud Native)与DevOps。
云原生和DevOps是当今IT领域最热门的话题之一。它们不仅仅是技术,更是一套理念、文化和方法论,旨在让企业能够更快、更可靠、更大规模地交付软件。这就像我们已经掌握了如何盖摩天大楼,现在要学习如何用现代化的机械、工厂化生产、以及高效的团队协作,快速、自动化地建造和管理整个“智能城市群”。
课程4.4:云原生与DevOps基础(超详细版)
一、云原生与DevOps概述:软件开发运维的“新范式”
1.1 云原生(Cloud Native)是什么?
-
定义:云原生是一种构建和运行应用程序的方法,它充分利用了云计算模型的优势。
-
核心理念/支柱:
-
容器化(Containerization):将应用程序及其所有依赖打包到独立的、可移植的容器中(如Docker)。
-
微服务(Microservices):将应用程序分解为小型、独立的服务,每个服务可独立部署和扩展。
-
DevOps:开发(Development)和运维(Operations)的集成,通过自动化和文化变革,实现软件的快速、可靠交付。
-
持续交付/集成(CI/CD):自动化构建、测试和部署流程,频繁地将代码变更发布到生产环境。
-
-
目标:
-
弹性伸缩:根据负载自动扩容/缩容。
-
高可用性:部分故障不影响整体服务。
-
自动化:从代码提交到部署的全流程自动化。
-
快速迭代:缩短软件从开发到上线的周期。
-
最大化云资源价值:充分利用云的弹性、按需付费特性。
-
-
比喻:云原生就像是专门为“云”这个“工厂”设计的一套“生产体系”。它生产出来的产品(应用)自带包装盒(容器),可以独立运行(微服务),且整个生产过程高度自动化(CI/CD),生产团队紧密协作(DevOps),确保产品快速、高质量地交付。
1.2 DevOps理念:开发与运维的“融合”
-
DevOps(Development + Operations):
-
含义:DevOps是一种文化、运动或实践,旨在促进开发(Development)和运维(Operations)团队之间的协作和沟通。
-
核心价值:
-
持续集成(CI):频繁将代码集成到主干,并自动运行测试。
-
持续交付(CD):将代码变更自动构建、测试、部署到生产环境。
-
自动化测试:减少人工干预,提高测试效率和质量。
-
基础设施即代码(Infrastructure as Code, IaC):用代码管理和配置基础设施。
-
监控与反馈:实时监控系统运行状况,及时发现问题并快速反馈给开发团队。
-
-
-
目标:
-
加速交付:缩短软件发布周期。
-
提高可靠性:减少部署错误,提高系统稳定性。
-
改善协作:打破开发与运维之间的壁垒。
-
降低风险:通过自动化和早期发现问题。
-
-
比喻:以前开发和运维是两个独立的部门,开发把代码扔给运维就不管了,运维部署遇到问题再找开发。DevOps让他们紧密合作,共同负责软件从“生”到“死”的整个生命周期。
二、容器化与Docker基础:云原生的“最小单位”
容器化是云原生的核心技术之一,它解决了应用程序在不同环境中运行时的一致性问题。
2.1 容器与虚拟机的区别:轻量与隔离的艺术
| 特性 | 虚拟机(VM) | Docker容器(Container) |
|--------------|--------------------------------------------|----------------------------------------------|
| 抽象层 | 在硬件层虚拟出多个操作系统。 | 在操作系统层进行隔离。 |
| 资源占用 | 每个VM包含完整的OS、内核和应用程序,资源开销大(GB级内存)。 | 共享主机OS内核,只包含应用和其依赖,资源开销小(MB级内存)。 |
| 启动速度 | 慢(分钟级)。 | 快(秒级)。 |
| 隔离性 | 强(OS级别隔离)。 | 较弱(共享内核,但有独立命名空间和资源限制)。 |
| 可移植性 | VM镜像较大,移植相对麻烦。 | 容器镜像轻量,高度可移植。 |
| 部署方式 | 操作系统级部署,通常需要管理Guest OS。 | 应用级部署,轻量化。 |
- 比喻:虚拟机就像在你的电脑上安装多台独立的“电脑”,每个“电脑”都有完整的操作系统。Docker容器就像在你的电脑上开辟多个“独立房间”,每个房间里只放着你需要运行的“应用程序”,它们共享你的“房子”(操作系统)的基础设施。
2.2 Docker核心概念:容器世界的“基石”
-
镜像(Image):
-
含义:一个轻量级、独立的、可执行的软件包,包含运行应用程序所需的一切:代码、运行时、系统工具、系统库以及配置。镜像是只读的。
-
比喻:预先打包好的“快递包裹”,里面包含了所有送达目的地后能直接运行的东西。
-
-
容器(Container):
-
含义:镜像的运行时实例。它是一个隔离的、轻量级的环境,应用程序在其中运行。每个容器都有自己的文件系统、进程空间、网络接口。
-
比喻:拆开快递包裹并运行起来的“应用程序”。
-
-
Docker引擎(Docker Engine):
- 含义:客户端-服务器应用程序,包含Docker守护进程(daemon)、REST API接口和命令行界面(CLI)。守护进程负责构建、运行和管理Docker容器。
-
仓库(Registry):
-
含义:用于存储和分发Docker镜像的服务。
-
常见:Docker Hub(公共仓库)、私有Registry(如Harbor、Registry)。
-
比喻:存放所有快递包裹(镜像)的“仓库”。
-
-
Dockerfile:
-
含义:一个文本文件,包含用户指令,用于自动构建Docker镜像。
-
比喻:制作快递包裹的“说明书”或“菜谱”。
-
-
分层存储(Layered Storage):
-
原理:Docker镜像由一系列只读的**层(Layers)**组成。每个层代表Dockerfile中的一条指令。当容器运行时,会在最上层添加一个可写层。
-
优点:
-
节省空间:多个镜像可以共享相同的底层层。
-
加速构建:修改Dockerfile时,只会重建发生变化及以上层。
-
-
比喻:你做三明治,底层是面包片,然后加芝士,再加火腿。如果你只改芝士,那面包片和火腿层就不用动,只改芝士那层。
-
2.3 常用Docker命令与实战:操作你的“容器”
-
镜像操作:
-
docker pull <image_name>:<tag>:从Registry拉取镜像。- 示例:
docker pull ubuntu:20.04(拉取Ubuntu 20.04镜像)
- 示例:
-
docker images:查看本地所有镜像。 -
docker rmi <image_id_or_name>:删除本地镜像。
-
-
容器操作:
-
docker run [OPTIONS] <image> [COMMAND] [ARG...]:创建并运行一个容器。-
-d(detached):后台运行容器。 -
-p <host_port>:<container_port>:端口映射,将主机端口映射到容器端口。 -
--name <container_name>:为容器指定名称。 -
示例:
docker run -d -p 80:80 --name mynginx nginx:alpine(后台运行一个Nginx容器,将主机80端口映射到容器80端口,并命名为mynginx)
-
-
docker ps [-a]:查看正在运行的容器(-a查看所有容器,包括已停止的)。 -
docker stop <container_id_or_name>:停止容器。 -
docker start <container_id_or_name>:启动已停止的容器。 -
docker restart <container_id_or_name>:重启容器。 -
docker rm <container_id_or_name>:删除容器(容器必须已停止)。 -
docker logs <container_id_or_name>:查看容器的日志输出。 -
docker exec -it <container_id_or_name> /bin/bash:进入正在运行的容器内部,通常用于调试。
-
-
构建自定义镜像:
-
Dockerfile示例 (Node.js应用):# Stage 1: Build the application (构建阶段) FROM node:18-alpine AS builder # 基于Node.js 18的Alpine Linux镜像 WORKDIR /app # 设置工作目录为/app COPY package.json . # 复制 package.json 文件 COPY yarn.lock . # 复制 lock 文件 (如果使用yarn) RUN yarn install --production # 安装生产依赖 (或者 npm install --production) COPY . . # 复制所有项目文件到容器 RUN yarn build # 运行前端/后端应用的构建命令 (例如 Vue/React 打包,或者编译 TypeScript) # Stage 2: Run the application (运行阶段) # 对于前端静态应用,可以使用Nginx;对于后端应用,直接使用Node.js运行时 FROM nginx:alpine # 对于静态前端,使用轻量级Nginx作为生产Web服务器 COPY --from=builder /app/dist /usr/share/nginx/html # 将构建阶段生成的dist目录复制到Nginx默认服务目录 EXPOSE 80 # 暴露容器的80端口 CMD ["nginx", "-g", "daemon off;"] # 运行Nginx,并保持前台运行 # 如果是Node.js后端应用,运行阶段可以这样: # FROM node:18-alpine # WORKDIR /app # COPY package.json . # COPY yarn.lock . # RUN yarn install --prod # COPY . . # EXPOSE 3000 # 假设后端监听3000端口 # CMD ["node", "app.js"] # 启动Node.js应用 -
构建命令:在Dockerfile所在目录运行。
docker build -t my-node-app:1.0 .(-t指定镜像名称和标签,.表示Dockerfile在当前目录)
-
2.4 数据持久化与挂载:容器数据的“生命周期”
容器在停止或删除后,其内部的数据也会丢失。为了持久化数据,需要使用卷(Volume)或绑定挂载(Bind Mount)。
-
绑定挂载(Bind Mount):
-
原理:将主机文件系统上的文件或目录直接挂载到容器内的指定路径。
-
用途:开发调试(修改主机文件,容器内立即生效)、共享配置文件。
-
示例:
docker run -v /host/path:/container/path ...
-
-
数据卷(Volume):
-
原理:由Docker管理的文件系统,独立于容器的生命周期。即使容器被删除,数据卷也会保留。
-
优点:更推荐用于生产环境的数据持久化,更安全、更易管理。
-
创建卷:
docker volume create mydata -
使用卷:
docker run -v mydata:/container/path ...
-
2.5 网络管理:容器间的“桥梁”
Docker提供了多种网络模式,允许容器之间以及容器与主机之间进行通信。
-
默认网络:
-
bridge:默认桥接网络,容器可以互相通信,也可以通过主机访问外部网络。 -
host:容器共享主机的网络栈,性能最好,但隔离性差。 -
none:容器不分配任何网络接口,完全隔离。
-
-
自定义网络:
-
创建网络:
docker network create mynet -
优点:提供更好的隔离性,容器可以通过名称互相解析(内置DNS),无需知道IP地址。
-
示例:
docker network create my_app_net docker run -d --name db --network my_app_net mongo:latest docker run -d --name backend --network my_app_net -p 3000:3000 my-node-app:1.0 # 后端容器可以通过 'db' 这个服务名直接访问 MongoDB
-
-
端口映射:
docker run -p <host_port>:<container_port>,将容器的内部端口暴露给主机。
2.6 Docker Compose:多容器应用的“编排器”
-
Docker Compose:
-
含义:一个用于定义和运行多容器Docker应用程序的工具。你可以在一个YAML文件(
docker-compose.yml)中配置应用程序的所有服务、网络和卷。 -
作用:简化了复杂多服务应用的启动、停止和管理。
-
比喻:你有一个完整的项目,包含了前端、后端、数据库、Redis等多个服务。Docker Compose就像一份“总指挥令”,你只需要一条命令,就能把所有这些服务一起启动起来,并自动建立好它们之间的网络连接。
-
-
docker-compose.yml示例 (全栈应用):version: '3.8' # Docker Compose 文件格式版本 services: frontend: # 前端服务 build: context: ./frontend # Dockerfile 所在的目录 dockerfile: Dockerfile.frontend # 前端 Dockerfile ports: - "80:80" # 映射主机80端口到容器80端口 networks: - my_app_network # 加入自定义网络 backend: # 后端服务 build: context: ./backend # 后端 Dockerfile 所在的目录 dockerfile: Dockerfile.backend # 后端 Dockerfile ports: - "3000:3000" environment: # 环境变量 NODE_ENV: production PORT: 3000 DATABASE_URL: mongodb://mongodb:27017/my_app_db # 连接到MongoDB服务,使用其服务名 JWT_SECRET: your_production_jwt_secret # 生产环境密钥 depends_on: # 依赖关系,backend 依赖 mongodb 服务 - mongodb networks: - my_app_network mongodb: # MongoDB 数据库服务 image: mongo:latest # 使用官方 MongoDB 镜像 ports: - "27017:27017" # 映射端口 (可选,通常只需内部网络访问) volumes: - mongodb_data:/data/db # 数据持久化到命名卷 networks: - my_app_network redis: # Redis 缓存服务 image: redis:alpine # 使用轻量级 Redis 镜像 ports: - "6379:6379" # 映射端口 (可选) volumes: - redis_data:/data # 数据持久化到命名卷 networks: - my_app_network volumes: # 定义命名卷 mongodb_data: redis_data: networks: # 定义自定义网络 my_app_network: driver: bridge -
常用命令:
-
docker-compose up [-d] [--build]:启动所有服务(-d后台运行,--build重新构建镜像)。 -
docker-compose down:停止并删除所有服务和网络。 -
docker-compose ps:查看服务状态。 -
docker-compose logs:查看所有服务的日志。
-
三、Kubernetes(K8s)基础:容器编排的“巨头”
当应用规模扩大到数十、数百个微服务,运行在成百上千台服务器上时,Docker Compose就显得力不从心了。这时,就需要更强大的容器编排系统来自动化管理整个集群。**Kubernetes(K8s)**是目前事实上的容器编排标准。
3.1 为什么用K8s:大规模容器管理的“操作系统”
-
自动化部署与扩展:自动将容器部署到集群中的节点,并根据负载自动扩缩容。
-
服务发现与负载均衡:自动管理服务之间的发现和请求分发。
-
自我修复:自动重启失败的容器,替换不健康的节点。
-
滚动更新与回滚:在不中断服务的情况下更新应用版本,并支持快速回滚。
-
配置管理与密钥管理。
-
资源管理:有效利用集群资源。
-
比喻:Docker Compose是单机版的“乐高积木拼装说明书”,而Kubernetes是超大型“乐高积木工厂”的“中央控制系统”,它能自动化生产、部署、监控、维护所有乐高产品。
3.2 K8s核心对象:K8s的“语言词汇”
K8s通过定义一系列的API对象来描述集群的期望状态。
-
Pod(最小部署单元):
-
含义:K8s中能够创建、部署和管理的最小计算单元。一个Pod包含一个或多个紧密关联的容器(这些容器共享网络和存储)。
-
比喻:一个“家庭”,家庭成员(容器)共享一个房子(Pod)里的资源。
-
-
Deployment(部署):
-
含义:用于管理无状态应用(如Web服务)的API对象。它声明了Pod的期望数量、如何更新Pod(滚动更新)等。
-
作用:实现Pod的自动扩缩容、滚动升级、回滚。
-
比喻:一个“项目经理”,负责管理你这个“应用”(Pod)需要多少个实例,以及如何安全地升级版本。
-
-
Service(服务):
-
含义:用于定义一组Pod的逻辑抽象。它提供一个稳定的虚拟IP地址和端口,作为访问Pod的入口。
-
作用:实现服务发现和负载均衡。客户端通过Service访问,无需知道后端Pod的实际IP。
-
比喻:一个“公司电话号码”,所有客户都拨打这个号码,然后公司内部会自动把电话转接到空闲的员工(Pod)那里。
-
-
ConfigMap / Secret(配置与密钥管理):
-
ConfigMap:用于存储非敏感的配置数据,如环境变量、配置文件。
-
Secret:用于存储敏感数据,如数据库密码、API密钥。它们会被加密存储。
-
-
Ingress(入口):
-
含义:管理从集群外部访问集群内Service的HTTP和HTTPS路由。
-
作用:提供统一的外部访问入口、URL路由、SSL终止、虚拟主机等。
-
比喻:数据中心的“大门”,根据来访者的目的地(URL)将其引导到正确的服务(Service)那里。
-
-
Namespace(命名空间):
-
含义:在K8s集群内创建的虚拟集群。
-
作用:用于资源隔离(如开发、测试、生产环境)、权限管理,避免资源名称冲突。
-
比喻:一个大工厂里划分为不同的“车间”,每个车间有自己的员工和设备,互不干扰。
-
3.3 典型K8s部署YAML:K8s的“配置文件”
K8s通过YAML(或JSON)文件来声明性地描述你期望的系统状态。
# deployment.yaml - 定义一个Deployment来管理你的Node.js后端应用
apiVersion: apps/v1 # API版本
kind: Deployment # 资源类型:Deployment
metadata:
name: my-backend-app # Deployment的名称
labels:
app: my-backend # 标签,用于Service选择Pod
spec:
replicas: 3 # 期望的Pod副本数量,即运行3个后端实例
selector:
matchLabels:
app: my-backend # 选择器,用于匹配哪些Pod归这个Deployment管理 (基于标签)
template: # Pod模板,定义了Deployment创建的每个Pod的规格
metadata:
labels:
app: my-backend # Pod的标签
spec:
containers: # 定义Pod中包含的容器
- name: backend-container # 容器名称
image: your_docker_registry/my-backend-app:v1.0 # 容器镜像地址和版本
ports:
- containerPort: 3000 # 容器内部监听的端口
env: # 环境变量
- name: NODE_ENV
value: production
- name: DATABASE_URL # 从ConfigMap或Secret加载
valueFrom:
secretKeyRef:
name: my-app-secrets # Secret的名称
key: database_url # Secret中的键
resources: # 资源限制与请求
requests: # 请求资源量,K8s会保证分配这些资源
memory: "128Mi"
cpu: "100m" # 100毫核 = 0.1 CPU核
limits: # 限制资源量,容器不会超过这些限制
memory: "256Mi"
cpu: "500m"
--- # --- 分隔符,可以在一个文件中定义多个K8s资源
# service.yaml - 定义一个Service来暴露你的后端应用
apiVersion: v1 # API版本
kind: Service # 资源类型:Service
metadata:
name: my-backend-service # Service的名称
spec:
selector:
app: my-backend # 选择器,将流量转发到带有 'app: my-backend' 标签的Pod
ports:
- protocol: TCP
port: 80 # Service暴露给外部的端口
targetPort: 3000 # 目标Pod内部的端口
type: ClusterIP # Service类型:
# ClusterIP (默认): 只能在集群内部访问
# NodePort: 通过节点IP+端口从外部访问 (测试用)
# LoadBalancer: 通过云提供商的负载均衡器暴露 (生产用)
# ExternalName: 将服务映射到外部DNS名称
- 部署命令:
kubectl apply -f deployment.yaml(kubectl是K8s的命令行工具)