22
33import com .alibaba .nacos .api .exception .NacosException ;
44import com .alibaba .nacos .api .naming .NamingService ;
5- import com .alibaba .nacos .api .naming .pojo .Instance ;
6- import com .alibaba .nacos .api .naming .listener .EventListener ;
75import com .alibaba .nacos .api .naming .listener .Event ;
6+ import com .alibaba .nacos .api .naming .listener .EventListener ;
87import com .alibaba .nacos .api .naming .listener .NamingEvent ;
8+ import com .alibaba .nacos .api .naming .pojo .Instance ;
99import com .xiaoyu .rpc .common .extension .ExtensionLoader ;
1010import com .xiaoyu .rpc .core .config .RpcConfig ;
1111import com .xiaoyu .rpc .core .loadbalancer .LoadBalancer ;
1212import com .xiaoyu .rpc .core .registry .ServiceDiscovery ;
13- import lombok .extern .slf4j .Slf4j ;
1413import org .slf4j .Logger ;
1514import org .slf4j .LoggerFactory ;
1615
1716import java .net .InetSocketAddress ;
1817import java .util .List ;
1918import java .util .Map ;
20- import java .util .Set ;
2119import java .util .concurrent .ConcurrentHashMap ;
22- import java .util .stream . Collectors ;
20+ import java .util .concurrent . atomic . AtomicBoolean ;
2321
2422public class NacosServiceDiscovery implements ServiceDiscovery {
2523 private static final Logger log = LoggerFactory .getLogger (NacosServiceDiscovery .class );
2624
2725 private final NamingService namingService ;
2826 private final LoadBalancer loadBalancer ;
29- // 本地缓存,用于容错和防抖
30- private static final java .util .Map <String , List <Instance >> serviceCache = new java .util .concurrent .ConcurrentHashMap <>();
31- // 已订阅的服务集合
32- private static final java .util .Set <String > subscribedServices = java .util .concurrent .ConcurrentHashMap .newKeySet ();
27+ private final Map <String , List <Instance >> serviceCache = new ConcurrentHashMap <>();
28+ private final Map <String , EventListener > subscriptions = new ConcurrentHashMap <>();
29+ private final AtomicBoolean closed = new AtomicBoolean (false );
3330
3431 public NacosServiceDiscovery () {
3532 this (NacosUtils .getNacosNamingService (),
@@ -44,73 +41,96 @@ public NacosServiceDiscovery() {
4441
4542 @ Override
4643 public InetSocketAddress lookupService (String serviceName ) {
44+ if (closed .get ()) {
45+ throw new IllegalStateException ("NacosServiceDiscovery 已关闭" );
46+ }
47+
4748 try {
48- // 第一次查找时订阅服务变更
49- if (subscribedServices .add (serviceName )) {
50- // add 返回 true 说明此前未订阅,避免同一个服务被重复订阅
51- subscribeService (serviceName );
52- }
49+ ensureSubscribed (serviceName );
5350
54- // 优先从 Nacos 拉取最新实例列表
5551 List <Instance > instances = namingService .getAllInstances (serviceName );
56-
57- if (instances .isEmpty ()) {
58- log .warn ("Nacos 返回实例列表为空,尝试使用本地缓存: {}" , serviceName );
59- instances = serviceCache .get (serviceName );
60- } else {
61- // 更新本地缓存
62- serviceCache .put (serviceName , instances );
63- }
64-
6552 if (instances == null || instances .isEmpty ()) {
66- log .error ("未找到服务且本地无缓存: {}" , serviceName );
53+ // 注册中心明确返回空实例,代表当前服务已下线;不能继续使用旧缓存。
54+ serviceCache .remove (serviceName );
6755 throw new RuntimeException ("未找到服务: " + serviceName );
6856 }
6957
70- // 转换 Instance 列表为 String 列表 (ip:port)
71- List <String > addressList = instances .stream ()
72- .map (instance -> instance .getIp () + ":" + instance .getPort ())
73- .collect (java .util .stream .Collectors .toList ());
74-
75- // 负载均衡选择
76- String targetAddress = loadBalancer .select (addressList );
77- log .info ("负载均衡选择服务地址: {}" , targetAddress );
78-
79- String [] array = targetAddress .split (":" );
80- return new InetSocketAddress (array [0 ], Integer .parseInt (array [1 ]));
81-
58+ updateCache (serviceName , instances );
59+ return selectAddress (instances );
8260 } catch (NacosException e ) {
83- log . error ( "获取服务实例时发生网络异常,尝试回滚到本地缓存:" , e );
84- // Nacos 短暂不可用时,优先用最近一次成功拉取到的实例兜底
61+ // 只有 Nacos 网络/协议异常时,才允许使用最后一次成功结果容错。
62+ log . error ( "获取服务实例时发生 Nacos 异常,尝试使用最近一次成功缓存: {}" , serviceName , e );
8563 List <Instance > cachedInstances = serviceCache .get (serviceName );
8664 if (cachedInstances != null && !cachedInstances .isEmpty ()) {
87- List <String > addressList = cachedInstances .stream ()
88- .map (instance -> instance .getIp () + ":" + instance .getPort ())
89- .collect (java .util .stream .Collectors .toList ());
90- String targetAddress = loadBalancer .select (addressList );
91- String [] array = targetAddress .split (":" );
92- return new InetSocketAddress (array [0 ], Integer .parseInt (array [1 ]));
65+ return selectAddress (cachedInstances );
9366 }
9467 throw new RuntimeException ("服务发现失败且无缓存可用: " + serviceName , e );
9568 }
9669 }
9770
98- /**
99- * 订阅服务变更,实现本地缓存的实时更新
100- */
101- private void subscribeService (String serviceName ) throws NacosException {
102- namingService .subscribe (serviceName , new EventListener () {
103- @ Override
104- public void onEvent (Event event ) {
105- if (event instanceof NamingEvent ) {
106- NamingEvent namingEvent = (NamingEvent ) event ;
107- List <Instance > instances = namingEvent .getInstances ();
108- log .info ("监听到服务变更,更新本地缓存: {} -> 实例数 {}" , serviceName , instances .size ());
109- if (instances != null && !instances .isEmpty ()) {
110- serviceCache .put (serviceName , instances );
71+ private void ensureSubscribed (String serviceName ) throws NacosException {
72+ if (subscriptions .containsKey (serviceName )) {
73+ return ;
74+ }
75+
76+ synchronized (subscriptions ) {
77+ if (subscriptions .containsKey (serviceName )) {
78+ return ;
79+ }
80+
81+ EventListener listener = new EventListener () {
82+ @ Override
83+ public void onEvent (Event event ) {
84+ if (event instanceof NamingEvent ) {
85+ List <Instance > instances = ((NamingEvent ) event ).getInstances ();
86+ updateCache (serviceName , instances );
87+ log .info ("监听到服务变更,更新本地缓存: {} -> 实例数 {}" ,
88+ serviceName , instances == null ? 0 : instances .size ());
11189 }
11290 }
91+ };
92+ namingService .subscribe (serviceName , listener );
93+ subscriptions .put (serviceName , listener );
94+ }
95+ }
96+
97+ void updateCache (String serviceName , List <Instance > instances ) {
98+ if (instances == null || instances .isEmpty ()) {
99+ serviceCache .remove (serviceName );
100+ } else {
101+ serviceCache .put (serviceName , List .copyOf (instances ));
102+ }
103+ }
104+
105+ private InetSocketAddress selectAddress (List <Instance > instances ) {
106+ List <String > addressList = instances .stream ()
107+ .map (instance -> instance .getIp () + ":" + instance .getPort ())
108+ .toList ();
109+ String targetAddress = loadBalancer .select (addressList );
110+ String [] array = targetAddress .split (":" );
111+ return new InetSocketAddress (array [0 ], Integer .parseInt (array [1 ]));
112+ }
113+
114+ @ Override
115+ public void close () {
116+ if (!closed .compareAndSet (false , true )) {
117+ return ;
118+ }
119+
120+ subscriptions .forEach ((serviceName , listener ) -> {
121+ try {
122+ namingService .unsubscribe (serviceName , listener );
123+ } catch (NacosException e ) {
124+ log .warn ("取消 Nacos 服务订阅失败: {}" , serviceName , e );
113125 }
114126 });
127+ subscriptions .clear ();
128+ serviceCache .clear ();
129+
130+ try {
131+ namingService .shutDown ();
132+ } catch (NacosException e ) {
133+ log .warn ("关闭 Nacos NamingService 失败" , e );
134+ }
115135 }
116136}
0 commit comments