自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(224)
  • 收藏
  • 关注

原创 Nacos源码—9.Nacos升级gRPC分析八

本文详细分析了gRPC客户端和服务端的初始化过程、心跳机制、连接处理及请求映射机制。首先,gRPC客户端通过初始化与服务器建立连接,并通过心跳机制进行健康检查。服务端启动时,通过BaseGrpcServer的startServer()方法启动,并使用建造者模式创建Server对象。在处理客户端连接请求时,服务端通过connectionId与Client对象进行绑定,具体通过ConnectionManager的register()方法实现。此外,gRPC服务端通过addServices()方法将请求映射到对应

2025-05-13 23:58:21 628

原创 Nacos源码—9.Nacos升级gRPC分析七

文章主要分析了gRPC客户端的初始化、心跳机制以及服务端处理连接请求的过程。首先,gRPC客户端通过NacosNamingService的registerInstance()方法进行服务实例注册,并根据实例类型选择使用gRPC或HTTP客户端代理。gRPC客户端代理的初始化包括创建RpcClient对象、启动RPC客户端连接以及注册处理服务端推送请求的Handler。其次,gRPC客户端的心跳机制通过两个线程任务实现:一个处理连接成功或断开时的通知,另一个处理重连或健康检查。健康检查通过向服务端发送Heal

2025-05-13 23:57:22 482

原创 Nacos源码—8.Nacos升级gRPC分析六

文章主要分析了Nacos中的事件驱动架构及其实现机制。首先,服务端通过事件驱动的方式处理客户端的变化,如客户端注册、注销等事件,并通过Distro协议或Raft协议进行集群数据同步。文章详细介绍了如何处理ClientChangedEvent和ClientDeregisterServiceEvent事件,分别对应客户端注册和注销时的操作。接着,文章探讨了ServiceChangeEvent事件的处理,即服务变动时通知订阅者更新本地缓存。最后,文章总结了Nacos事件驱动架构的使用方法,包括自定义事件、订阅者以

2025-05-12 22:29:05 604

原创 Nacos源码—8.Nacos升级gRPC分析五

本文详细介绍了服务端对服务实例进行健康检查的设计逻辑、源码实现以及服务下线时的注销处理流程。健康检查通过探活机制,定期检查客户端连接的最后活跃时间,若超过20秒则将其加入待移除集合,并通过探活请求确认客户端是否下线。若探活失败,则执行注销操作,移除连接对象并发布客户端注销事件。服务下线时,客户端通过gRPC请求通知服务端,服务端处理请求后移除实例信息并发布相关事件,同步集群节点数据并更新注册表。此外,文章还分析了Nacos事件驱动架构的源码,包括事件发布、订阅者注册及通知机制的实现。

2025-05-12 22:22:48 852

原创 Nacos源码—7.Nacos升级gRPC分析四

在微服务架构中,服务变动时通知订阅的客户端以及微服务实例信息的同步是确保系统一致性和可用性的关键。当服务实例注册或注销时,服务端会发布ClientChangedEvent事件,该事件触发集群节点间的数据同步。具体流程如下: 事件发布:服务实例注册或注销时,服务端发布ClientChangedEvent事件,触发集群节点同步服务实例数据。 事件处理:DistroClientDataProcessor处理ClientChangedEvent事件,调用syncToAllServer方法,通过DistroProto

2025-05-09 23:46:38 1192

原创 Nacos源码—7.Nacos升级gRPC分析三

在微服务架构中,服务变动和订阅通知是确保服务发现和通信的关键机制。本文详细描述了Nacos服务端如何处理客户端注册和订阅事件,并通过事件发布机制通知相关客户端。当客户端注册或注销服务实例时,Nacos服务端会发布ClientRegisterServiceEvent或ClientDeregisterServiceEvent事件,并更新注册表。随后,服务端会发布ServiceChangedEvent事件,通知所有订阅该服务的客户端。对于服务订阅事件,Nacos服务端会发布ClientSubscribeServi

2025-05-09 23:45:19 1290

原创 Nacos源码—6.Nacos升级gRPC分析二

Nacos 2.x版本在服务注册与发现机制上进行了显著优化,客户端通过gRPC发起服务注册,服务端则处理注册请求并管理服务实例。客户端进行服务发现时,借助Ribbon实现负载均衡,通过NacosServerList获取服务实例列表。服务端处理订阅请求时,首先根据Service对象读取缓存中的服务实例,然后添加订阅者并发布相关事件。Nacos客户端通过NacosNamingService进行服务发现,若本地缓存无数据,则通过gRPC请求服务端获取实例信息。服务端通过ServiceStorage和ClientS

2025-05-08 23:40:59 899

原创 Nacos源码—6.Nacos升级gRPC分析一

Nacos 2.x版本在客户端和服务端的交互方式、注册表结构以及事件驱动机制上进行了显著优化。首先,客户端和服务端的通信从HTTP升级为gRPC,提升了性能和效率。其次,注册表结构从双重Map简化为轻量级Map,减少了并发冲突的可能性。此外,Nacos 2.x大量采用事件驱动机制,如服务注册、销毁和变更等操作均通过事件通知中心处理,增强了系统的灵活性和可扩展性。客户端在启动时会自动通过gRPC发起服务注册,服务端则通过多个Map和事件处理机制来管理服务实例的注册和订阅。这些改进使得Nacos 2.x在微服务

2025-05-08 23:35:06 1332

原创 Nacos源码—5.Nacos配置中心实现分析二

在异步任务AsyncTask的run()方法中,会一直从queue中获取通知任务,以便将配置数据同步到对应的集群节点。当集群节点处理"/v1/cs/communication/dataChange"这个HTTP请求时,会调用CommunicationController的notifyConfigInfo()方法,接着调用DumpService的dump()方法将请求包装成DumpTask同步数据任务,然后调用TaskManager的addTask()方法将DumpTask同步数据任务放入map。

2025-05-07 23:13:03 833

原创 Nacos源码—5.Nacos配置中心实现分析一

但要注意共享配置文件里的配置不要和自身应用配置文件里的配置重复,因为自身应用配置文件比共享配置文件的优先级高。在调用PropertySourceLocator实现类的locateCollection()方法时,会先调用PropertySourceLocator扩展接口的locateCollection()方法,从而才会触发调用PropertySourceLocator实现类实现的locate()方法,比如调用NacosPropertySourceLocator的locate()方法。

2025-05-07 23:11:55 1126

原创 Nacos源码—4.Nacos集群高可用分析四

Follower节点拿到Leader节点返回的Instance服务实例信息后,会继续调用RaftStore.write()、PersistentNotifier.notify()这两个方法,一个将数据持久化到本地文件、一个将数据同步到内存注册表,从而最终完成以Leader节点为准的心跳请求同步数据的流程。Follower节点拿到心跳包中的key之后,发现部分key在自身节点是不存在的,那么这时Follower节点就会根据这些key向Leader节点获取Instance的详细信息进行同步。

2025-05-06 23:41:04 1102

原创 Nacos源码—4.Nacos集群高可用分析三

当Leader节点接收到一个数据写入请求时:首先会在自身的节点进行数据处理,然后马上同步给集群的其他节点,此时Leader节点的这个数据的状态是uncommit状态。假设原本的Leader节点是B,但由于B突然下线,节点A、C会重新发起投票,最终节点C成为新的Leader节点。只是节点C和节点A、B之间的数据略有差异,但不影响节点的正常使用。在心跳传输过程中,Leader节点会把最新的数据传给其他Follower节点,以保证Follower节点中的数据和Leader节点的数据是一致的。分区指的是网络分区。

2025-05-06 23:39:52 848

原创 Nacos源码—3.Nacos集群高可用分析二

如果调用ServiceManager的containService()方法时发现服务不存在,则先通过ServiceManager的createEmptyService()方法创建空的服务,然后会调用DistroProtocol的onReceive()方法注册服务实例,接着会调用DistroConsistencyServiceImpl的processData()方法进行处理,最后又会调用实例注册时的DistroConsistencyServiceImpl的onPut()方法。

2025-05-05 23:45:45 1036

原创 Nacos源码—3.Nacos集群高可用分析一

而其他的集群节点,并不会去执行对该Service的心跳健康检查。当Nacos服务端也就是Service的init()方法执行完成心跳健康检查任务后,ServiceManager的init()方法会有一个定时任务,同步检查结果到其他节点。在ServiceController的serviceStatus()方法中,如果通过对比入参和注册表的ServiceChecksum后,发现服务状态发生了改变,那么就会调用ServiceManager.addUpdatedServiceToQueue()方法。

2025-05-05 23:44:02 963

原创 Nacos源码—2.Nacos服务注册发现分析四

Nacos服务端处理服务下线的入口是InstanceController的deregister()方法,然后会调用ServiceManager的removeInstance()方法移除注册表里的实例,也就是调用ServiceManager的substractIpAddresses()方法。在Service的updateIPs()方法中:会先调用Cluster的updateIps()方法通过写时复制机制去修改注册表,然后调用PushService的serviceChanged()方法发布服务变动事件。

2025-04-29 23:53:01 761

原创 Nacos源码—2.Nacos服务注册发现分析三

在nacos-client的NacosNamingService的selectInstances()方法中:首先会调用HostReactor的getServiceInfo()方法获取服务实例列表,然后调用HostReactor的getServiceInfo0()方法尝试从本地缓存获取,接着调用HostReactor的updateServiceNow()方法查询并更新缓存,也就是调用HostReactor的updateService()方法查询并更新缓存。:找出哪些Instance服务实例是不健康的。

2025-04-29 23:52:10 906

原创 Nacos源码—1.Nacos服务注册发现分析二

在这个register()方法的最后,会调用Notifier的addTask()方法,也就是把key、action包装成Pair对象,放入到一个BlockingQueue里。在ServiceManager的addInstance()方法中:首先构建出要注册的服务实例对应的服务的key,然后使用synchronized锁住要注册的服务实例对应的服务,接着获取要注册的服务实例对应的服务的最新服务实例列表,最后执行DelegateConsistencyServiceImpl的put()方法更新服务实例列表。

2025-04-28 23:17:59 769

原创 Nacos源码—1.Nacos服务注册发现分析一

调用AbstractAutoServiceRegistration的onApplicationEvent()方法时,首先会调用AbstractAutoServiceRegistration的bind()方法,然后调用AbstractAutoServiceRegistration的start()方法,接着调用AbstractAutoServiceRegistration的register()方法发起注册,也就是调用this.serviceRegistry的register()方法完成服务注册的具体工作。

2025-04-28 23:15:06 1154

原创 Nacos简介—4.Nacos架构和原理三

Server之间都是对等的,任何⼀个Server在处理配置数据的写入请求时,会先将数据持久化到DB,持久化成功后该Server再异步通知其他Server到DB中拉取最新的配置数据。如果所有服务都需要注册中心去主动探测,由于服务的数量会远远大于注册中心的数量,那么注册中心的任务量将很巨大,所以最好都采用服务主动上报的方式进行健康检查。但由于长连接和心跳续约的存在,可能导致实例的生命周期刚被终止移除,马上又因为心跳和长连接的补偿请求,再次开启实例的生命周期,给人⼀种注销失败的假象。

2025-04-27 23:44:13 1055

原创 Nacos简介—4.Nacos架构和原理二

后者则是⼀个事件接口,当MemberLookup需要进行成员节点信息更新时,会将当前最新的成员节点信息通知给ServerMemberManager,具体的节点管理方式,则是隐藏到具体的MemberLookup实现中。在对容量的评估时,不仅要评估现有的服务规模,也要预测未来3到5年的扩展规模。阿里的中间件在内部支撑着集团百万级别服务实例,在容量上遇到的挑战可以说不会小于任何互联网公司,这个容量不仅仅意味着整体注册的实例数,也同时包含单个服务的实例数、整体的订阅者的数目以及查询QPS等。

2025-04-27 23:41:14 1333

原创 Nacos简介—4.Nacos架构和原理一

并且在实现具体的⼀致性协议时,采用了插件可插拔的形式,进⼀步将⼀致性协议的逻辑,从服务注册发现、配置管理模块中解耦。用于进行租户粒度的配置隔离。Nacos中的某个配置集的ID,配置集ID是划分配置的维度之⼀。服务注册发现、配置管理模块依然要在显式处理⼀致性协议的读写逻辑,以及要自己实现⼀个对接⼀致性协议的存储,这并不优雅。因此为了解决这个问题,必然要对Nacos的⼀致性协议进行抽象以及下沉,使一致性协议成为内核模块的功能,让服务注册发现模块只充当计算角色,同时为配置模块去外部数据库存储打下架构基础。

2025-04-27 23:36:43 1218

原创 Nacos简介—3.Nacos的配置简介

Label是Nacos抽象出的Entity属性,Label定义为一个描述Entity属性的K-V键值对,Label的key和value的取值范围一般都是预先定义好的。实体的标签的变更事件。为了保证实体事件携带的变更信息是最新的,该事件只会包含变更实体的标识以及变更事件的类型,不会包含变更标签的值。Nacos的这个实体会有很多属性,比如IP的机房信息,服务的版本信息等。Nacos约定CMDB的抽象调用接口,然后由各企业添加自己的CMDB插件,无需任何代码上的重新构建,即可在运行状态下对接上企业的CMDB。

2025-04-26 23:45:38 995

原创 Nacos简介—2.Nacos的原理简介

某个节点宕机后,该节点的数据不会全部不可用,可能会丢失部分数据。对Nacos集群节点进行随机读的时候,由于每个节点只负责处理部分数据,所以可能出现读取不到刚向集群注册的数据的随机读问题。Nacos集群中的每个节点,虽然通过写路由只写入由自己处理的数据,但同时也会定期执行同步任务,把本节点负责的数据同步到其他节点,最终每个节点都会存储全量的集群数据。新加入Nacos集群的节点会轮询Nacos集群的所有节点,然后发送请求出去拉取各节点的数据,所以Nacos集群的每个节点上都会有所有已注册的服务实例的数据。

2025-04-25 23:46:16 818

原创 Nacos简介—1.Nacos使用简介

保护阈值,可以设置为0~1之间的比例。如果健康的服务实例比例太低,则把不健康的服务实例也返回服务调用方。此时会有很多请求交给不健康的服务实例来处理,会导致请求失败,但可以避免健康的服务实例出现因为请求流量过大而被压垮的问题。一种是临时的服务实例,一种是持久化的服务实例。临时的服务实例默认会每隔5s上报一次心跳给Nacos,Nacos如果15s没收到心跳就标记该服务实例为不健康,Nacos如果超过30s没收到心跳就摘除这个服务实例。假设有10个服务实例,每个服务实例的极限QPS是700,当前QPS是500。

2025-04-24 23:04:47 1695

原创 Sentinel源码—9.限流算法的实现对比二

其次通过WarmUpController的coolDownTokens()方法获取最新的令牌数,接着利用CAS来保证更新令牌桶的线程安全性,最后通过减去上一秒通过的QPS数得到目前令牌桶剩余的令牌数来更新。当系统启动时,桶内令牌数最大,令牌生成速率最低,允许的QPS最低。Guava中的预热是通过控制令牌的生成时间来实现的,Sentinel中的预热则是通过控制每秒通过的请求数来实现的。注意:Guava中的预热是通过控制令牌的生成时间来实现的,Sentinel中的预热是通过控制每秒通过的请求数来实现的。

2025-04-23 22:10:55 1098

原创 Sentinel源码—9.限流算法的实现对比一

也就是无需等前面的请求完全被处理完,才确定后面的请求被处理的时间。如果在此时(currentTime)通过当前请求,则当前请求的通过时间就比它最早的预期通过时间(expectedTime)要早,即当前请求和最近通过的请求的时间间隔变小了,比最小间隔时间costTime还小,所以此时必然会超QPS阈值。同时,为了避免等待的各个并发线程被同时唤醒,可以利用原子变量的addAndGet()方法 + 假设等待请求已被通过的方式,实现需要等待的并发请求进行睡眠等待的时间都不一样,从而实现并发请求排队等待的效果。

2025-04-23 22:09:48 896

原创 Sentinel源码—8.限流算法和设计模式总结二

在Sentinel中,使用适配器模式将不同框架和库的接口适配为统一的接口,如SphU类。fireEntry()方法相当于AOP在执行完before()方法后调用pjp.proceed()方法,也就是调用责任链上的下一个节点的entry()方法。fireExit()方法相当于AOP在执行完exit()方法后调用pjp.proceed()方法,也就是调用责任链上的下一个节点的exit()方法。entry()方法相当于AOP的before()方法,也就是入口方法,因此责任链执行时会调用entry()方法。

2025-04-22 23:52:06 990

原创 Sentinel源码—8.限流算法和设计模式总结一

延迟计算指的是不需要单独的线程来定时生成令牌或从漏桶中定时取请求,而是由调用限流器的线程自己来计算是否有足够的令牌以及需要sleep的时间。这种算法的一个重要特性是:无论请求的接收速率如何变化,请求的处理速率始终是稳定的,这就确保了系统的负载不会超过预设的阈值。当桶中等待的请求数超过桶的容量后,后续的请求就不再加入桶中。由于以固定的速率处理请求,所以可以有效地平滑和整形流量,避免流量的突发和波动,类似于消息队列的削峰填谷的作用。每来一个请求,请求数加一,如果请求数超过最大限制,就拒绝该请求。

2025-04-22 23:51:05 924

原创 Sentinel源码—7.参数限流和注解的实现二

下面的代码为sayHello()方法添加了@SentinelResource注解,并指定了资源名称为sayHello以及熔断降级时的回调方法fallback()。这样在请求sayHello()方法后,就可以在Sentinel Dashboard上看到此资源,然后就可以针对此资源进行一系列的规则配置了。利用Spring AOP拦截@SentinelResource注解,最后调用SphU.entry()方法来进行处理。

2025-04-21 22:50:42 319

原创 Sentinel源码—7.参数限流和注解的实现一

可以设置一个规则,根据用户ID来限制每个用户的查询频率,将限流的维度从资源维度细化到参数维度,从而实现每个用户每10秒只能查询接口1次。比如希望影院工作人员可以每秒查询10次,老板可以每秒查询100次,而购票者则只能每10秒查询一次,其中工作人员的userId值为100和200,老板的userId值为9999,那么可以如下配置:需要注意限流阈值是以秒为单位的,所以需要乘以统计窗口时长10。参数限流允许根据不同的参数条件设置不同的流量控制规则,这种方式非常适合处理特定条件下的请求,因为能更加精细地管理流量。

2025-04-21 22:49:52 1295

原创 Sentinel源码—6.熔断降级和数据统计的实现二

调用ArrayMetric的addPass()进行数据统计的逻辑如下:首先通过LeapArray的currentWindow()方法获取当前时间所在的样本窗口,然后调用MetricBucket的addPass()方法统计并存储数据到样本窗口中。如果当前样本窗口的起始时间大于计算出的样本窗口起始时间,则说明计算出的样本窗口已过时,要将原来的样本窗口替换为新样本窗口。滑动窗口不会指定固定的时间窗口起点与终点,而是将处理请求的时间点作为该请求对应时间窗口的终点,起点则是向前距离该终点一个时间窗口长度的时间点。

2025-04-20 21:55:14 886

原创 Sentinel源码—6.熔断降级和数据统计的实现一

比如在执行ExceptionCircuitBreaker的onRequestComplete()方法中,会先统计异常数errorCount和总请求数totalCount,然后根据熔断降级的规则判断是否达到打开或关闭熔断器的阈值,最后执行比如AbstractCircuitBreaker的transformToOpen()方法打开熔断器。但完成对请求的规则验证后,则会调用Entry的exit()方法,而Entry的exit()方法最终就会执行到DegradeSlot的exit()方法。

2025-04-20 21:54:01 1008

原创 Sentinel源码—5.FlowSlot借鉴Guava的限流算法二

结合如下的图(令牌的发放时间间隔随着已存储的令牌不同而不同)可知:maxPermits - thresholdPermits就是梯形的高,stableIntervalMicros + coldIntervalMicros就是梯形的两个底的和。该等待时间又会分为两部分进行计算:第一部分是获取预热阶段的令牌的耗时,第二部分是获取稳定阶段的令牌的耗时。SmoothBursty应对突发流量是有前提条件的,只有在令牌桶内有存储的令牌情况下,才会放行相应的突发流量,而令牌桶内的已存储令牌是低流量时省下来的。

2025-04-19 22:14:36 938

原创 Sentinel源码—5.FlowSlot借鉴Guava的限流算法一

如果下一个请求的预期到达时间实际上已经过去了,并且假设下次请求期望到达的时间点是past,现在的时间点是now。在RateLimiter处于长时间未被使用的状态下:如果是利用不足导致的,那么应该让存储的令牌比新鲜的令牌发放得更快。延迟计算指的是不需要单独的线程来定时生成令牌或者从漏桶中定时获取请求,而是由调用限流器的线程自己计算是否有足够的令牌以及需要sleep的时间。接下来假设这个函数是一条水平线:如果其高度恰好为1/QPS,则函数的效果不存在,因为此时表示以与新鲜的令牌完全相同的成本提供存储的令牌。

2025-04-19 22:12:30 766

原创 Sentinel源码—4.FlowSlot实现流控的原理二

如果在此时(currentTime)通过当前请求,则当前请求的通过时间就比它最早的预期通过时间(expectedTime)要早,即当前请求和最近通过的请求的时间间隔变小了,比最小间隔时间costTime还小,所以此时必然会超QPS阈值。WarmUpController的核心原理是:首先根据当前时间和上一个时间窗口通过的QPS同步令牌桶内的令牌数,然后比较桶内令牌数和告警值来计算当前时间窗口允许通过的告警QPS,最后比较当前请求下的QPS是否大于允许通过的告警QPS来决定限流。

2025-04-17 23:02:43 1499

原创 Sentinel源码—4.FlowSlot实现流控的原理一

当FlowSlot的entry()方法对请求进行流控规则验证时,会调用规则检查器FlowRuleChecker的checkFlow()方法进行检查,最终会通过FlowRule的getRater()方法获取流控规则对应的流量整形控制器,然后调用TrafficShapingController的canPass()方法对请求进行检查。也就是testOrder被流控的时机就是当testPay的QPS达到3的时候,3并不是testOrder所访问的次数,而是testPay这个接口被访问的次数。

2025-04-17 23:01:24 933

原创 Sentinel源码—3.ProcessorSlot的执行过程二

当AuthorityDemo调用AuthorityRuleManager的loadRules()方法加载规则时,便会执行DynamicSentinelProperty的updateValue()方法,也就是会触发执行LISTENER的configUpdate()方法加载权限规则到一个map中,即执行RulePropertyListener的loadAuthorityConf()方法加载规则,从而完成黑白名单权限控制规则的加载和初始化。当配置发生变化时,就可以遍历监听器集合然后调用回调方法进行处理。

2025-04-16 23:22:12 1155

原创 Sentinel源码—3.ProcessorSlot的执行过程一

由于ClusterNode会统计某个资源在全部Context下的调用数据,它是按照集群中的资源维度进行调用数据统计的,而StatisticSlot的entry()调用DefaultNode的方法统计单机下的资源时,会顺便调用ClusterNode的方法来统计集群下的资源调用,所以通过ClusterNode就可以获取集群中某个资源的调用数据。每当一个线程处理包含某些资源的接口请求时,会调用SphU的entry()方法去创建并管控该接口中涉及的Entry资源访问对象。

2025-04-16 23:21:05 935

原创 Sentinel源码—2.Context和处理链的初始化二

需要注意:即便是多个线程访问同样的资源(ResourceWrapper对象的属性一样),多个线程也会对应多个Entry对象(Entry对象之间的基本属性一样),多个线程也会对应多个Context对象(使用ThreadLocal存放Context对象),从而多个Entry对象会对应各自的Context对象。SpiLoader的load()方法在读取SPI文件时,会按order属性对Slot进行排序,并将排好序的ProcessorSlot实现类放入sortedClassList中。

2025-04-15 21:38:49 1255

原创 Sentinel源码—2.Context和处理链的初始化一

如果获取到的Context为空,也就是当前线程没有绑定Context,那么就调用InternalContextUtil的internalEnter()方法创建一个Context对象,也就是调用ContextUtil的trueEnter()方法创建一个Context对象,并把这个Context对象放入ThreadLocal线程变量contextHolder中。在处理一个请求对应的一个资源时或者多个资源时,这些资源的操作必须要建立在一个Context环境下,而且每一个资源的操作必须要通过Entry对象来完成。

2025-04-15 21:37:50 717

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除