尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
阿里二面:使用 Nacos 做注册中心怎么做优雅发布?
今天来聊一聊使用 Nacos 做注册中心怎么做优雅发布。跟其他的注册中心一样Nacos 作为注册中心的使用如下图Service Provider 启动后注册到 Nacos ServerService Consumer 则从 Nacos Server 拉取服务列表根据一定算法选择一个 Service Provider 来发送请求。1.优雅要求对于优雅发布要求是 Service Provider 上线注册到 Nacos后服务能够正常地接收和处理请求而 Service Provider 停服后则不会再收到请求。这就有两个要求优雅上线Service Provider 发布完成之前Service Consumer 不应该从服务列表中拉取到这个服务地址优雅下线Service Provider 下线后Service Consumer 不会从服务列表中拉取到这个服务地址。解决了这两个问题优雅发布就可以做到了。2.搭建环境搭建环境是为了看 Nacos 日志通过日志找到对应的源代码。本文搭建的环境如下图2.1 启动 provider启动 springboot-provider 的应用注册到 Nacos启动日志如下2023-06-11 18:58:10,120 [main] [INFO] com.alibaba.nacos.client.naming - [BEAT] adding beat: BeatInfo{port8083, ip192.168.31.94, weight1.0, serviceNameDEFAULT_GROUPspringboot-provider, clusterDEFAULT, metadata{management.endpoints.web.base-path/actuator, management.port18082, preserved.register.sourceSPRING_CLOUD, management.address127.0.0.1}, scheduledfalse, period5000, stoppedfalse} to beat map. 2023-06-11 18:58:10,121 [main] [INFO] com.alibaba.nacos.client.naming - [REGISTER-SERVICE] public registering service DEFAULT_GROUPspringboot-provider with instance: Instance{instanceIdnull, ip192.168.31.94, port8083, weight1.0, healthytrue, enabledtrue, ephemeraltrue, clusterNameDEFAULT, serviceNamenull, metadata{management.endpoints.web.base-path/actuator, management.port18082, preserved.register.sourceSPRING_CLOUD, management.address127.0.0.1}} 2023-06-11 18:58:10,133 [main] [INFO] com.alibaba.cloud.nacos.registry.NacosServiceRegistry - nacos registry, DEFAULT_GROUP springboot-provider 192.168.31.94:8083 register finished 2023-06-11 18:58:10,221 [main] [INFO] org.springframework.boot.web.embedded.tomcat.TomcatWebServer - Tomcat initialized with port(s): 18082 (http) 2023-06-11 18:58:10,222 [main] [INFO] org.apache.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler [http-nio-127.0.0.1-18082] 2023-06-11 18:58:10,223 [main] [INFO] org.apache.catalina.core.StandardService - Starting service [Tomcat] 2023-06-11 18:58:10,223 [main] [INFO] org.apache.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.21] 2023-06-11 18:58:10,239 [main] [INFO] org.apache.catalina.core.ContainerBase.[Tomcat-1].[localhost].[/] - Initializing Spring embedded WebApplicationContext 2023-06-11 18:58:10,239 [main] [INFO] org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 99 ms 2023-06-11 18:58:10,268 [main] [INFO] org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver - Exposing 22 endpoint(s) beneath base path /actuator 2023-06-11 18:58:10,336 [main] [INFO] org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler [http-nio-127.0.0.1-18082] 2023-06-11 18:58:10,340 [main] [INFO] org.springframework.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 18082 (http) with context path 2023-06-11 18:58:10,342 [main] [INFO] boot.Application - Started Application in 7.051 seconds (JVM running for 7.874) 2023-06-11 18:58:10,358 [main] [INFO] com.alibaba.nacos.client.config.impl.ClientWorker - [fixed-39.105.183.91_8848] [subscribe] springboot-provider.propertiesDEFAULT_GROUP 2023-06-11 18:58:10,359 [main] [INFO] com.alibaba.nacos.client.config.impl.CacheData - [fixed-39.105.183.91_8848] [add-listener] ok, tenant, dataIdspringboot-provider.properties, groupDEFAULT_GROUP, cnt1 2023-06-11 18:58:10,359 [main] [INFO] com.alibaba.nacos.client.config.impl.ClientWorker - [fixed-39.105.183.91_8848] [subscribe] springboot-provider-dev.propertiesDEFAULT_GROUP 2023-06-11 18:58:10,359 [main] [INFO] com.alibaba.nacos.client.config.impl.CacheData - [fixed-39.105.183.91_8848] [add-listener] ok, tenant, dataIdspringboot-provider-dev.properties, groupDEFAULT_GROUP, cnt1 2023-06-11 18:58:10,360 [main] [INFO] com.alibaba.nacos.client.config.impl.ClientWorker - [fixed-39.105.183.91_8848] [subscribe] springboot-providerDEFAULT_GROUP 2023-06-11 18:58:10,360 [main] [INFO] com.alibaba.nacos.client.config.impl.CacheData - [fixed-39.105.183.91_8848] [add-listener] ok, tenant, dataIdspringboot-provider, groupDEFAULT_GROUP, cnt1 2023-06-11 18:58:10,639 [RMI TCP Connection(1)-192.168.31.94] [INFO] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet dispatcherServlet 2023-06-11 18:58:10,839 [com.alibaba.nacos.client.naming.updater] [INFO] com.alibaba.nacos.client.naming - [BEAT] adding beat: BeatInfo{port8083, ip192.168.31.94, weight1.0, serviceNameDEFAULT_GROUPspringboot-provider, clusterDEFAULT, metadata{management.endpoints.web.base-path/actuator, management.port18082, preserved.register.sourceSPRING_CLOUD, management.address127.0.0.1}, scheduledfalse, period5000, stoppedfalse} to beat map. 2023-06-11 18:58:10,840 [com.alibaba.nacos.client.naming.updater] [INFO] com.alibaba.nacos.client.naming - modified ips(1) service: DEFAULT_GROUPspringboot-providerDEFAULT - [{instanceId:192.168.31.94#8083#DEFAULT#DEFAULT_GROUPspringboot-provider,ip:192.168.31.94,port:8083,weight:1.0,healthy:true,enabled:true,ephemeral:true,clusterName:DEFAULT,serviceName:DEFAULT_GROUPspringboot-provider,metadata:{management.endpoints.web.base-path:/actuator,management.port:18082,preserved.register.source:SPRING_CLOUD,management.address:127.0.0.1},ipDeleteTimeout:30000,instanceHeartBeatInterval:5000,instanceHeartBeatTimeOut:15000}] 2023-06-11 18:58:10,841 [com.alibaba.nacos.client.naming.updater] [INFO] com.alibaba.nacos.client.naming - current ips:(1) service: DEFAULT_GROUPspringboot-providerDEFAULT - [{instanceId:192.168.31.94#8083#DEFAULT#DEFAULT_GROUPspringboot-provider,ip:192.168.31.94,port:8083,weight:1.0,healthy:true,enabled:true,ephemeral:true,clusterName:DEFAULT,serviceName:DEFAULT_GROUPspringboot-provider,metadata:{management.endpoints.web.base-path:/actuator,management.port:18082,preserved.register.source:SPRING_CLOUD,management.address:127.0.0.1},ipDeleteTimeout:30000,instanceHeartBeatInterval:5000,instanceHeartBeatTimeOut:15000}]我们再看下 Nacos 的日志这里看的文件 naming-server.log日志如下2023-06-11 18:58:09,723 INFO Client connection 192.168.31.94:51885#true connect 2023-06-11 18:58:10,105 INFO Client change for service Service{namespacepublic, groupDEFAULT_GROUP, namespringboot-provider, ephemeraltrue, revision1}, 192.168.31.94:8083#true 2023-06-11 18:58:18,204 INFO Client connection 192.168.31.94:60850#true disconnect, remove instances and subscribersspringboot-provider 启动成功后从Nacos 管理后台可以看到下图2.2 provider 下线服务下线后Nacos 日志如下2023-06-11 19:01:03,375 INFO Client connection 192.168.31.94:51885#true disconnect, remove instances and subscribers 2023-06-11 19:01:05,048 INFO [AUTO-DELETE-IP] service: Service{namespacepublic, groupDEFAULT_GROUP, namespringboot-provider, ephemeraltrue, revision2}, ip: {ip:192.168.31.94,port:8083,healthy:false,cluster:DEFAULT,extendDatum:{management.endpoints.web.base-path:/actuator,management.port:18082,preserved.register.source:SPRING_CLOUD,management.address:127.0.0.1,customInstanceId:192.168.31.94#8083#DEFAULT#DEFAULT_GROUPspringboot-provider},lastHeartBeatTime:1686481231604,metadataId:192.168.31.94:8083:DEFAULT} 2023-06-11 19:01:05,048 INFO Client remove for service Service{namespacepublic, groupDEFAULT_GROUP, namespringboot-provider, ephemeraltrue, revision2}, 192.168.31.94:8083#true 2023-06-11 19:01:08,379 INFO Client connection 192.168.31.94:8083#true disconnect, remove instances and subscribers2.3 服务调用在 springboot-consumer 上跑一个单元测试的用例用 FeignClient 调用下面的方法FeignClient(value springboot-provider, configuration FeignMultipartSupportConfig.class) public interface FeignAsEurekaClient { PostMapping(/employee/save) String saveEmployeebyName(RequestBody Employee employee); }日志如下2023-06-11 19:15:47,694 [main] [INFO] org.springframework.test.context.transaction.TransactionContext - Began transaction (1) for test context [DefaultTestContext5bf0d49 testClass TestFeignAsEurekaClient, testInstance boot.service.TestFeignAsEurekaClient10683d9d, testMethod testPostEmployByFeignTestFeignAsEurekaClient, testException [null], mergedContextConfiguration [WebMergedContextConfiguration5b7a5baa testClass TestFeignAsEurekaClient, locations {}, classes {class boot.Application, class boot.Application}, contextInitializerClasses [], activeProfiles {}, propertySourceLocations {}, propertySourceProperties {org.springframework.boot.test.context.SpringBootTestContextBootstrappertrue, server.port0}, contextCustomizers set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer166fa74d, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer588df31b, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer7fad8c79, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer10b48321], resourceBasePath src/main/webapp, contextLoader org.springframework.boot.test.context.SpringBootContextLoader, parent [null]], attributes map[org.springframework.test.context.web.ServletTestExecutionListener.activateListener - false]]; transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager693676d]; rollback [true] 2023-06-11 19:15:47,941 [main] [INFO] com.netflix.config.ChainedDynamicProperty - Flipping property: springboot-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit 2147483647 2023-06-11 19:15:47,962 [main] [INFO] com.netflix.loadbalancer.BaseLoadBalancer - Client: springboot-provider instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:namespringboot-provider,current list of Servers[],Load balancer statsZone stats: {},Server stats: []}ServerList:null 2023-06-11 19:15:47,969 [main] [INFO] com.netflix.loadbalancer.DynamicServerListLoadBalancer - Using serverListUpdater PollingServerListUpdater 2023-06-11 19:15:48,064 [main] [INFO] com.netflix.config.ChainedDynamicProperty - Flipping property: springboot-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit 2147483647 2023-06-11 19:15:48,064 [main] [INFO] com.netflix.loadbalancer.DynamicServerListLoadBalancer - DynamicServerListLoadBalancer for client springboot-provider initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:namespringboot-provider,current list of Servers[192.168.31.94:8083],Load balancer statsZone stats: {unknown[Zone:unknown; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;] },Server stats: [[Server:192.168.31.94:8083; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] ]}ServerList:com.alibaba.cloud.nacos.ribbon.NacosServerList24d998ba注意这里使用了 OpenFeign其中用到了 Ribbon 做负载均衡那就需要考虑到 Ribbon 刷新本地服务列表的时间从源代码中看刷新周期是 30s。如下图Ribbon 刷新缓存的逻辑参考下面代码public synchronized void start(final UpdateAction updateAction) { if (isActive.compareAndSet(false, true)) { final Runnable wrapperRunnable new Runnable() { Override public void run() { //... } }; scheduledFuture getRefreshExecutor().scheduleWithFixedDelay( wrapperRunnable, initialDelayMs, refreshIntervalMs,//这里定义的是30s TimeUnit.MILLISECONDS ); }//... }3.优雅发布前面第一节提到过优雅发布有两个要求优雅上线和优雅下线。Service Consumer 初始化时会从 Nacos Server 获取服务列表并更新本地缓存同时会向 Nacos Server 订阅服务列表如果 Nacos Server 上的服务列表发生变化会主动通知 Service Consumer。之后会定时默认间隔 1s 拉取服务列表并更新本地缓存。代码如下//NacosNamingService 类 public ListInstance selectInstances(String serviceName, String groupName, ListString clusters, boolean healthy, boolean subscribe) throws NacosException { ServiceInfo serviceInfo; String clusterString StringUtils.join(clusters, ,); if (subscribe) { serviceInfo serviceInfoHolder.getServiceInfo(serviceName, groupName, clusterString); if (null serviceInfo) { serviceInfo clientProxy.subscribe(serviceName, groupName, clusterString); } } else { serviceInfo clientProxy.queryInstancesOfService(serviceName, groupName, clusterString, 0, false); } return selectInstances(serviceInfo, healthy); }在订阅的代码中加入了定时更新服务列表的代码如下//NamingClientProxyDelegate 类 public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException { NAMING_LOGGER.info([SUBSCRIBE-SERVICE] service:{}, group:{}, clusters:{} , serviceName, groupName, clusters); String serviceNameWithGroup NamingUtils.getGroupedName(serviceName, groupName); String serviceKey ServiceInfo.getKey(serviceNameWithGroup, clusters); serviceInfoUpdateService.scheduleUpdateIfAbsent(serviceName, groupName, clusters); ServiceInfo result serviceInfoHolder.getServiceInfoMap().get(serviceKey); if (null result || !isSubscribed(serviceName, groupName, clusters)) { result grpcClientProxy.subscribe(serviceName, groupName, clusters); } serviceInfoHolder.processServiceInfo(result); return result; }Nacos Server 会定时每隔 5s检查 Service Provider 是否健康根据心跳来检查如果 15s 默认可以配置没有收到心跳则会把服务置为不健康并且通知 Service Consumer。代码如下//UnhealthyInstanceChecker 类 public void doCheck(Client client, Service service, HealthCheckInstancePublishInfo instance) { if (instance.isHealthy() isUnhealthy(service, instance)) { changeHealthyStatus(client, service, instance); } } private void changeHealthyStatus(Client client, Service service, HealthCheckInstancePublishInfo instance) { instance.setHealthy(false); NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service)); NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(client)); NotifyCenter.publishEvent(new HealthStateChangeTraceEvent(System.currentTimeMillis(), service.getNamespace(), service.getGroup(), service.getName(), instance.getIp(), instance.getPort(), false, client_beat)); }3.1 优雅上线优雅上线存在的问题主要在于 Service Provider 注册到 Nacos 后服务还没有完成初始化请求已经到来。这种情况主要原因是 Service Provider 启动后立刻注册 Naocs而本身提供的接口可能还没有初始化完成。这种情况的解决方法是关闭自动注册spring.cloud.nacos.discovery.registerEnabledfalse在服务初始化后使用代码手动注册代码如下Properties setting8 new Properties(); String serverIp8 127.0.0.1:8848; setting8.put(PropertyKeyConst.SERVER_ADDR, serverIp8); setting8.put(PropertyKeyConst.USERNAME, nacos); setting8.put(PropertyKeyConst.PASSWORD, nacos); NamingService inaming8 NacosFactory.createNamingService(setting7); inaming8.registerInstance(springboot-provider, 192.168.31.94, 8083);3.2 优雅下线对于正常下线Nacos Server 收到 Provider 发送的下线请求后会通知订阅的 Server Consumer而且 Consumer 也会每隔 1s 去更新本地服务列表这个过程已经非常接近优雅下线了。而对于异常下线Nacos Server 采用心跳检测机制来更新服务列表。心跳周期是 5sNacos Server 如果 15s 没收到心跳就才会将实例设置为不健康。3.2.1 正常停服正常下线的情况下最优雅的方式是先向 Nacos Server 发送下线通知发送通知一段时间比如 5s后再停服。比如增加一个 API 接口服务下线之前增加 preStopHook 函数调用这个 API 接口来实现下线。API 接口示例代码如下GetMapping(value /nacos/deregisterInstance) public String deregisterInstance() { Properties prop new Properties(); prop.setProperty(serverAddr, localhost); prop.put(PropertyKeyConst.NAMESPACE, test); NacosNamingService client new NacosNamingService(prop); client.deregisterInstance(springboot-provider, 192.168.31.94, 8083); return success; }在使用 Ribbon 的场景也需要考虑 Ribbon 更新本地缓存服务列表的机制手动下线后可以再等待 30s 后关闭服务。3.2.1 服务故障对于服务故障的情况Nacos Server 需要采用心跳来检测服务在线如果 15s 没收到心跳才会将实例设置为不健康在 30s 没收到心跳才会把这个服务从列表中删除。这个时间可以做优化设置spring.cloud.nacos.discovery.metadata.preserved.heart.beat.interval1000 #心跳间隔5s-1s spring.cloud.nacos.discovery.metadata.preserved.heart.beat.timeout3000 #超时时间15s-3s spring.cloud.nacos.discovery.metadata.preserved.ip.delete.timeout5000 #删除时间30s-5s但是Service Provider 故障情况下即使做优化配置也是很难让 Service Consumer 无感知。极端情况下可能 Provider 部分服务已经不能正常提供了但还是会向 Nacos Server 发送心跳这种情况可以采用服务本身的健康检查来通知 Nacos Server 服务下线。4 总结无论是哪一款注册中心优雅发布要解决的问题都是优雅上线和优雅下线。本文结合 Nacos 的原理讲解了 Nacos 的优雅发布希望对你有所帮助。2026年最新版百万八股面试宝典《Java》《大模型》《大并发》《大数据》本题库由资深讲师联合大厂面试官精心打造专为高级IT岗位设计【220万字八股文面试宝典领取】面试突击体系相关Java企业架构体系相关AI大模型相关HR 面试软技能设计模式相关并发编程相关网络IO与Netty相关互联网三高项目相关亿级流量多级缓存相关数据结构算法相关分布式相关分布式锁相关分布式ID相关核心源码相关大厂线上故障分析相关Docker相关Dubbo相关ElasticSearch相关GoLang相关Java基础核心JVM核心相关JVM调优底层相关Kafka相关Kubernetes相关Linux相关MongoDB相关MQ相关MySQL相关MyBatis相关MyBatisPlus相关Postgresql相关RabbitMQ相关RocketMQ相关Redis相关ShardingSphere相关Shiro相关SkyWalking相关Solr相关Spring 相关SpringBoot相关SpringCloud相关SpringMVC源码相关SpringSecurity相关Spring WebFlux相关领域驱动设计DDD相关任务调度Airflow相关Zookeeper相关HDFS面试题MapReduce面试题Yarn面试题Hive相关SQL面试题相关Scala面试题Spark面试题Flink相关Doris相关Tomcat源码相关网络安全相关运维/云原生相关
RELATED

相关推荐

手把手教你使用Nacos配置中心!

手把手教你使用Nacos配置中心!

Nacos整合了注册中心和配置中心,使用起来非常方便,这篇文章主要介绍Nacos配置中心的使用。新建配置启动Nacos服务后,进入注册中心配置页面,如下图:点击上图中红框里面的加号,进入新建配置页面,如…

📅 2026/10/29 5:33:59
context-mode 上下文管理:原理、范式与 AI 编程落地实践

context-mode 上下文管理:原理、范式与 AI 编程落地实践

在 AI 辅助编程越来越普及的今天,context-mode 这个概念逐渐从一个小众技巧变成了每个重度使用者都绕不开的课题。简单来说,context-mode 就是把 AI 的上下文窗口当作一种有限资源来管理,针对不同任务场景切换不同的上下文组织方式&#xff0…

📅 2026/10/29 5:28:58
大模型上下文治理:四种模式的原理、应用与踩坑

大模型上下文治理:四种模式的原理、应用与踩坑

很多兄弟第一次听到 context-mode 这个说法,大概率是在排查对话机器人答非所问、token 账单爆炸,或者研究大模型 API 的调参文档时遇到的。老实说,这不是一个严格意义上的官方术语,而是大家在实践中对一类问题的解决方案的统称&am…

📅 2026/10/29 5:28:58
MORE NEWS

更多资讯

📰

2024年三款主流五笔输入法深度横评:词库、皮肤与云同步

五笔输入法这个圈子,说大不大,说小也不小。我用了快十二年的五笔,从最早在旧电脑上折腾各种输入法,到后来帮身边同事配置、给家里人装机,前前后后经手的五笔方案没有二十套也有十五六套了。2024年这一年,我…

📰

AI论文写作工具书匠策的六大核心功能解析

1. 论文写作的痛点与AI解决方案论文写作一直是学术界永恒的痛点。从选题构思到文献综述,从实验设计到结果分析,每个环节都让研究者们头疼不已。特别是对于非英语母语的研究者,语言表达更是横亘在学术发表路上的一座大山。最近两年&#xff0c…

📰

数据驱动与关键字驱动自动化测试框架设计指南

1. 自动化测试框架设计概述在软件测试领域,数据驱动和关键字驱动是两种主流的自动化测试框架设计方法。这两种方法都能显著提升测试效率,但实现思路和适用场景各有不同。作为一名有多年自动化测试经验的工程师,我将分享如何从零开始设计这两种…

📰

AXI4从机写通道设计:握手时序、outstanding与RTL实现

简介:这份资源围绕AXI4总线协议展开,面向SOC设计、FPGA开发及嵌入式方向的学习者与工程师,帮助其系统理解AMBA家族中这一高性能、低功耗总线的工作机制。内容从协议特点切入,涵盖地址与控制信号同数据分阶段、非对齐传输、突发式传…

📰

MT7620A+MT7612E原理图拆解:DDR2、PCIe与射频调试关键点

简介:这份 MT7620AM7612E 路由器 AP 原理图面向无线路由器、AP 硬件设计与调试人员,也适合嵌入式硬件学习者梳理 MediaTek 无线方案。图纸围绕 MT7620A 主控与 MT7612E 5G 射频展开,覆盖 CPU、DDR2 存储器、SPI Flash、UART 串行口、PCIe、LE…

📰

OpenShell 深度解析:从事件流到插件协议,打造可编程的现代命令行环境

1. OpenShell 这个项目到底在做什么1.1 从项目名字里能读出什么OpenShell 这个项目名字拆开来看就很有意思:Open 意味着开放、开源、可扩展,Shell 意味着这是一个人与机器打交道的最底层入口。把这两个词拼在一起,它本质上指的是一个“开放的…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬