PageSourceSearch

http://ibeetl.com/assets/index.html-Brk-zZCr.js

js ibeetl.com collected 2026-10-03 23:20:49 UTC 19,207 bytes, 1 lines download raw bytes

1import{_ as e,c as i,b as s,o as d}from"./app-CNefOTx1.js";const a="/assets/state_definition-D5NQ4Ca8.png",r="/assets/state_stateless-B4RUWKNq.png",p="/assets/state_statefull-BqOM16pp.png",l="/assets/state_statefull_slave-DxIRpRZO.png",o="/assets/state_springsession-DplwAAOy.png",n="/assets/state_request-ByoHSgZ9.png",c="/assets/state_split-Sbs9Krd9.png",h="/assets/state_zk-BpplxTHH.png",m={};function g(_,t){return d(),i("div",null,t[0]||(t[0]=[s('<h2 id="无状态和有状态服务" tabindex="-1"><a class="header-anchor" href="#无状态和有状态服务"><span>无状态和有状态服务</span></a></h2><p>本文内容较多,分为如下部分</p><ul><li>无状态服务和有状态服务定义</li><li>无状态服务应用场景</li><li>有状态服务应用场景</li><li>有无状态俩种服务的架构质量对比</li><li>实现有状态服务的挑战</li><li>有状态服务重构成无状态服务</li></ul><p><strong>警告:任何情况下都优先考虑使用无状态服务,及时你阅读本文,包括以后写的有状态服务实现模式后</strong></p><p>分布式系统里,节点服务包含了<code>业务处理</code>和<code>数据</code>俩部分, 如果数据是从持久化系统加载,则认为是此节点是状态的。 如果数据常驻于节点内,业务逻辑是从外部加载进来的,则认为是有状态节点。</p><p><img src="'+a+'" alt=""></p><p>绝大多数系统都是无状态服务,服务不需要在服务本地(JVM)记录同一个客户端(如用户,设备,或者其他作为客户端的服务等)请求的的历史状态或者记录客户端的信息,服务需要的这些数据将从数据库或者Redis,MongDB加载。这些服务类似如下</p><ul><li>登录服务,服务节点根据请求用户名,从数据库查询用户名和HASH后的密码,然后判断请求的密码是否与数据库查询的密码一致</li><li>搜索服务 ,服务节点会把查询条件转为ElasticSearch查询JSON,请求Elastic Search查询结果。</li><li>电商中库存查询,服务节点会根据商品SKU以及查询所在的地区,查询存放在Redis的库存信息。同时库存服务节点也会请求商品查询服务查询商品的名称和价格等信息。</li><li>物联网中查询家庭设备的状态,服务节点会查询俩个Redis,一个是缓存设备信息的Redis,一个设备在离线状态的Redis</li></ul><p>无状态服务的技术实现非常成熟,如果你的服务是无状态,仅仅需要增加节点既可实现系统高可用和高性能。 如下图,代理服务采用轮询(Round Robin)或则随机,对于客户端C,每个服务节点都能提供服务。如果需要扩容,简单增加服务节点4即可</p><p><img src="'+r+'" alt=""></p><p>需要注意的是,随着流量增加,而服务节点增加,导致代理服务和数据库本身可能是高可用高性能的瓶颈。因此代理服务和数据库节点也应该支持扩展节点</p><p>无状态服务的主要优点</p><table><thead><tr><th>优点</th><th>说明</th></tr></thead><tbody><tr><td>实现简单</td><td>像编写CURD代码那样简单</td></tr><tr><td>扩容简单</td><td>增加节点即可,代理服务可以使用轮询,随机等路由技术实现。</td></tr></tbody></table><p>与无状态是服务对应的是有状态服务,要求服务端记录同一个客户端的交互历史数据和状态数据,有状态服务通常从外部加载执逻辑而不是数据。比如</p><ul><li>用户前端流程表单在最终提交前保留在服务端的临时表单数据</li><li>用户登录后的会话信息放在Session,Session信息存放在JMV内存里</li><li>物联网的接入网关,需要记录长链接到此网关的设备的版本信息。网关服务有可能加载规则脚本,规则引擎会根据设备上报的数据来执行特定的业务逻辑</li><li>游戏服务,一群用户会被路由到特定的地图服务器上协作完成游戏</li><li>通过互联网协作编写一份文档</li><li>导航系统,用户的信息和旅程信息都在一个服务器上。实时导航信息也在这一台服务器上更新</li><li>Zookeeper 内存中保持的有ZTree数据结构,它通过ZAB协议保持所有节点数据都一致。</li><li>
1Redis 本身也是有个有状态服务节点,它支持在内存存储各种数据结构以及加载执行脚本。它通过从节点来实现高可用</li></ul><p>有状态需要客户端始终连接到同一个服务节点(持久化连接),需要客户端或者代理服务采取的路由策略保证同一个客户任何请求都在同一个节点执行,比如来自同一个IP的用户</p><p><img src="'+p+'" alt=""></p><p>有状态服务的主要优点如下</p><table><thead><tr><th>优点</th><th>说明</th></tr></thead><tbody><tr><td>高性能</td><td>主要避免了从数据库加载数据的延迟; 也避免了从持久化系统反复加载数据,比如节点1加载数据执行业务逻辑完毕后,当请求路由到节点2的时候,又会在节点2再次加载同样数据</td></tr><tr><td>任意状态数据结构</td><td>不再需要持久化系统保存状态数据,其状态不限制于JSON,表数据结构。</td></tr><tr><td>CAP</td><td>分布式常见的AP能得到满足外,由于数据在同一个节点,其C也能满足。需要注意,但是如果主节点宕机(或者客户端认为C宕机路由到从节点),从节点接管,则C不成立。</td></tr><tr><td>避免使用性能不佳的数据库</td><td>无状态服务的扩容,也会导致其用的数据库节点扩容。无状态节点因为不需要数据库,则省去了数据库的使用成本,以及其导致的性能瓶颈</td></tr></tbody></table><p>有状态服务不难实现,但要达成高可用目标,则难得多。 下图是一个通常有状态服务高可用的部署架构。</p><p><img src="'+l+'" alt=""></p><p>这个架构里,有非常多的技术挑战性,列表如下。 这些挑战是分布式系统高可用的深水区,后面章节简述如何攻克这些挑战。</p><table><thead><tr><th>难点</th><th>说明</th><th>举例</th></tr></thead><tbody><tr><td>持久连接</td><td>代理服务采用何种策略让客户端保持持久连接到同一个节点,包括客户端的每次请求,客户端重启,服务节点重启,以及扩容缩容后通知客户端重连新节点。如果没有代理服务,则需要客户端实现持久化连接</td><td>设备总是链接到同一个网关,游戏玩家总是链接到同一个区域服务器</td></tr><tr><td>复制</td><td>为节点增加一个从节点,数据从主节点备份到从节点。当节点宕机,,从节点接替主节点工作。从节点也可收接收只读服务请求,缓解主节点负载</td><td>Redis主从数据复制,Zk的主从数据复制</td></tr><tr><td>读写一致</td><td>如果从节点提供读服务,如何保证客户端读到最新写到此分区的数据。</td><td>阿里云Redis可以配置只有主分片提供服务以避免读写不一致。</td></tr><tr><td>分区</td><td>把有状态服务分成多个节点,解决有状态服务的存储瓶颈和访问性能。</td><td>物联网网关集群,Redis数据分片,数据库分区,Kafka分区等</td></tr><tr><td>分区再平衡</td><td>在扩容时候,如果不差钱,每个分区都再次拆分2个分区,此分区数据只需要复制到俩分区。这种扩容成本极高但数据均匀。如果扩容时候是增加少量节点。则因为持久化连接缘故,新增节点负载长期都较低。扩容效果不能立即显现出来。</td><td>阿里云的Redis扩容采用的是成倍增长,支持4,8,16....2048个分片。不支持增加少量分区</td></tr><tr><td>分布式共识</td><td>数据备份给多个节点,当主节点挂掉,采用那个节点的数据为主节点。另外,新当选的的主节点需要继续同步数据到从节点</td><td>ZK通过ZAB实现分布式共识</td></tr><tr><td>集群管理</td><td>需要感知整个集群的节点状态,其主从的工作状态。代理服务需要获取这些数据以实现路由</td><td>物联网设备的主网关,Redis集群的代理</td></tr><tr><td>业务逻辑加载</td><td>如果有状态服务的业务逻辑通过外部加载实现,如果管理这些业务逻辑</td><td>物联网的规则链,通过配置其规则节点和执行逻辑实现设备在离线,属性上报等执行逻辑。Reids作为有状态节点ï¼
1Œå¯ä»¥åŠ è½½å’Œæ‰§è¡ŒLua脚本</td></tr></tbody></table><p>基于无状态服务和有状态服务各自的优缺点,下表总结了俩种服务的架构质量</p><table><thead><tr><th>架构特点</th><th>关注点</th><th>无状态服务</th><th>有状态服务</th></tr></thead><tbody><tr><td>高可用</td><td>API网关</td><td>API网关可以实现各种负载均衡策略。如随机,轮询,负载</td><td>API网关实现较为复杂,需要识别客户端,保证每次路由到同一个有状态服务,依据信息可能是客户端IP和端口,也可能是HTTP头中包含的客户ID</td></tr><tr><td>故障恢复</td><td>重启服务即可恢复故障</td><td>有状态服务重启后,状态丢失。需要重新建立这些状态,如设备的版本信息,用户的购物车。因此故障恢复较慢这些状态需要从持久化系统加载,或者依赖客户端重置状态。</td><td></td></tr><tr><td>故障容错</td><td>无状态服务支持重试,当服务宕机,API网关可以将请求路由到另外其他服务。不存在单机故障</td><td>有状态服务宕机,状态丢失。需要在其他节点重建状态,比如有状态服务保持的有用户登录信息,当宕机后,用户再访问其他服务前,需要再次登录。</td><td></td></tr><tr><td>高性能</td><td>启动性能</td><td>无状态服务不需要加载状态,因此重启后访问较快。</td><td>有状态服务,服务重启后,需要初始化端状态,访问性能第一次较慢。重启可能造成服务响应延迟</td></tr><tr><td></td><td>访问性能</td><td>无状态服务通常将状态维护在Redis中,性能稍微慢一些,但考虑到Redis这些延迟都是毫秒级的,整体性能很高。如果状态数据维护在传统数据,则延迟较高</td><td>有状态服务在JVM内存在保持状态,访问速度更快。如游戏地图,所有玩家都在一个游戏地图(JVM)里有状态服务适合游戏,导航等场景。</td></tr><tr><td>可修改</td><td>易于修改</td><td>通常无状态服务,不需要在JVM里维护状态,实现更简单,易于修改。</td><td>在JVM里维护的状态,数据结构私有,难以修改,另外涉及到并发访问,编写代码容易出错</td></tr><tr><td></td><td>热发布</td><td>无状态服务可以采用任何热发布技术,而没有风险</td><td>有状态服务,需要把状态数据迁移到热发布后的结构,难度较大</td></tr><tr><td>可伸缩性</td><td>节点扩容</td><td>集群环境,无状态服务可以任意增加或者减少,无状态服务扩容后,API网关可以通过负载均衡策略,让负载少的这些扩容节点能很快达到满负荷</td><td>容易造成单点过载。有状态服务扩容后,由于持久化链接需要,至少少量请求能立即路由到新的节点。扩容后需要较长时间新老节点的容量才能达到一个均衡值。</td></tr></tbody></table><p>既然无状态服务有如此多的优点,除非有高性能要求,架构中应该优先使用无状态服务,如果是有状态的服务,需要改成无状态服务,这里有4个办法</p><ul><li>有状态服务的状态数据存放在Redis等更为可靠的存储介质。比如用户Session,订单,购物车等信息存放到redis,一些长期存在的数据存放到数据库。 一些高可用基础设施的改进采用了此方案,比如Kafka新一代方案<a href="https://www.toutiao.com/article/7580613850573636096/?app=news_article&amp;timestamp=1766026761&amp;use_new_style=1&amp;req_id=202512181059202FECEDBD55AB8CE8CEF8&amp;group_id=7580613850573636096&amp;req_id_new=202512181059202FECEDBD55AB8CE8CEF8&amp;chn_id=-3&amp;share_did=MS4wLjACAAAAVfSFXxfg6-hPXBlA91hyWI_ffD_1qQBvN0-ctQ0FziQ-e9QwR5XZSLS-3frGcjyh&amp;share_uid=MS4wLjABAAAAmteeVOpFKtElQwG-v4vK9j0tOsx0sCxFUFgS5VUJwUo&amp;upstream_biz=harmony_share_os&amp;origin=Wechat&amp;source=m_redirect&amp;wid=1766300898062" target="_blank" rel="noopener noreferrer">Diskless Kafka</a>的实现AutoMQ ,消息本地存储变成存储到对象存储服务里。 Nacos将配置数据放到Mysql数据库中。</li><li>服务端的状态每次都回传给客户端,客户端下次调用携带这些状态,比如JWT,Cookie</li><li>有状态服务把有状态部分单独隔离出来,把其他部分放在无状态服务里。</li><li>使用Zookeeper,数据库等强一致工具来实现投票,元数据管理,二阶段提交等,而无需自己实现</li></ul><p>
1下图是Spring Boot提供的Session实现方式,代替传统的保存会话到内存,Spring Boot 配置spring.session.store-type</p><div class="language-java line-numbers-mode" data-ext="java" data-title="java"><button class="copy" title="Copy code" data-copied="Copied"></button><pre class="shiki shiki-themes vitesse-light vitesse-dark vp-code"><code><span class="line"><span style="--shiki-light:#B07D48;--shiki-dark:#BD976A;">spring</span><span style="--shiki-light:#999999;--shiki-dark:#666666;">.</span><span style="--shiki-light:#B07D48;--shiki-dark:#BD976A;">session</span><span style="--shiki-light:#999999;--shiki-dark:#666666;">.</span><span style="--shiki-light:#B07D48;--shiki-dark:#BD976A;">store</span><span style="--shiki-light:#AB5959;--shiki-dark:#CB7676;">-</span><span style="--shiki-light:#393A34;--shiki-dark:#DBD7CAEE;">type</span><span style="--shiki-light:#999999;--shiki-dark:#666666;">=</span><span style="--shiki-light:#393A34;--shiki-dark:#DBD7CAEE;">Redis</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div></div></div><p>配置后,存在内存中的的用户会话数据将序列化后存放在redis中。</p><p><img src="'+o+'" alt=""></p><p>其他配置还允许使用数据库,Hazelcast等 存储系统。需要注意,必须确保存储系统的高性能和高可用。在我的一个项目里,用此方案把有状态服务改成无状态服务,额外引入了一个512分片,容量是1024G的Redis集群以避免系统性能问题</p><p>另外一种把有状态服务改成无状态服务的方法是服务器每次把状态回传给客户端。适合用户状态数据较少情况</p><p><img src="'+n+'" alt=""></p><p>交互过程如下</p><ul><li>服务在接受客户端请求后,将数据放回到Cookie中,如加密的用户的信息,或者订单信息</li><li>客户端下次请求,服务端从Cookie中取出的状态数据,处理请求后新的状态数据再次保存到Cookie中</li></ul><blockquote><p>在还没有无纸办公时代,我在派出所办理业务的时候,按照要求需要跑多次派出所和其他相关单位,派出所工作人员会把需要跑的单位记录在一张纸上,每次交互后的结果和剩下需要办的事情都有记录,再次去派出所,其他工作人员即使没有给我办理过业务,也会让我拿出这张纸查看,以了解我办理的进度</p></blockquote><p>第三种是在设计有状态服务时候,拆分有状态服务。这样好处是让大部分功能保持在无状态服务里。</p><p><img src="'+c+'" alt=""></p><p>第四种方法与第三种类似,使用zookeeper来管理有状态,相比于自己实现有状态服务,zookeeper/etcd这些基础中间件更为可靠</p><p><img src="'+h+'" alt=""></p><p>有状态服务的高可用涉技术实现包括大量内容,本书将用后续一节内容说明实现有状态服务的高可用有哪些模式,<strong>再次警告,有状态服务高可用实现难度较大。类似你正在实现一个Redis,Kafka这样的中间件</strong>。</p>',42)]))}const u=e(m,[["render",g],["__file","index.html.vue"]]),y=JSON.parse('{"path":"/article/statelessfull/","title":"高可用系统通4大原则之一:无状态服务vs有状态服务","lang":"en-US","frontmatter":{"title":"高可用系统通4大原则之一:无状态服务vs有状态服务","tags":["架构","新书"],"createTime":"2026/05/7 23:06:15","permalink":"/article/statelessfull/","description":"无状态和有状态服务 本文内容较多,分为如下部分 无状态服务和有状态服务定义 无状态服务应用场景 有状态服务应用场景 有无状态俩种服务的架构质量对比 实现有状态服务的挑战 有状态服务重构成无状态服务 警告:任何情况下都优先考虑使用无状态服务,及时你阅读本文,包括以后写的有状态服务实现模式后 分布式系统里,节点服务包含了业务处理和数据俩部分, 如果数据是从...","head":[["meta",{"property":"og:url","content":"http://ibeetl.com/article/statelessfull/"}],["meta",{"property":"og:site_name","content":"闲大赋"}],["meta",{"property":"og:title","content":"高可用系统通4大原则之一:无状态服务vs有状态服务"}],["meta",{"property":"og:description","content":"无状态和有状态服务 本文内容较多,分为如下部分 无状态服务和有状态服务定义 无状态服务应用场景 有状态服务应用场景 有无状态俩种服务的架构质量对比 实现有状态服务的挑战 有状态服务重构成无状态服务 警告:任何情况下都优先考虑使用无状态服务,及时你阅读本文,包括以后写的有状态服务实现模式后 分布式系统里,节点服务包含了业务处理和数据俩部分, 如果数据是从..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"en-US"}],["meta",{"property":"og:updated_time","content":"2026-05-20T04:25:34.000Z"}],["meta",{"property":"article:tag","content":"架构"}],["meta",{"property":"article:tag","content":"新书"}],["meta",{"property":"article:modified_time","content":"2026-05-20T04:25:34.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"高可用系统通4大原则之一:无状态服务vs有状态服务\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"202
16-05-20T04:25:34.000Z\\",\\"author\\":[]}"]]},"headers":[],"readingTime":{"minutes":12.75,"words":3825},"git":{"updatedTime":1779251134000,"contributors":[{"name":"lijiazhi","username":"lijiazhi","email":"[email protected]","commits":1,"avatar":"https://gravatar.com/avatar/1b5114831459724ad4acc394423b4b48ecfa4a7e0659fbe07a0ce5181a1050a7?d=retro"}]},"autoDesc":true,"filePathRelative":"article/架构/stateless_statefull.md","categoryList":[{"id":"92a2b5","sort":10000,"name":"article"},{"id":"9a9aed","sort":10005,"name":"架构"}]}');export{u as comp,y as data};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.