-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
495 lines (285 loc) · 561 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="https://www.w3.org/2005/Atom">
<title>业精于勤荒于嬉</title>
<subtitle>行成于思毁于随</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://yoursite.com/"/>
<updated>2020-03-01T15:41:50.641Z</updated>
<id>http://yoursite.com/</id>
<author>
<name>沈小布</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>迷茫</title>
<link href="http://yoursite.com/2020/03/01/%E8%BF%B7%E8%8C%AB/"/>
<id>http://yoursite.com/2020/03/01/%E8%BF%B7%E8%8C%AB/</id>
<published>2020-03-01T15:37:58.000Z</published>
<updated>2020-03-01T15:41:50.641Z</updated>
<content type="html"><![CDATA[<p>人为什么活着?我们人生的意义是什么?人生的目的在哪里?</p><p>有时候我感觉很累,我不喜欢现在的生活,我想要的生活不过是一个院子,养一些花草和小动物,放上一张躺椅,闲暇之余能在原子里喝茶小憩。可是我知道我并不能,因为我没有时间去打理。</p><p>唉</p>]]></content>
<summary type="html">
<p>人为什么活着?我们人生的意义是什么?人生的目的在哪里?</p>
<p>有时候我感觉很累,我不喜欢现在的生活,我想要的生活不过是一个院子,养一些花草和小动物,放上一张躺椅,闲暇之余能在原子里喝茶小憩。可是我知道我并不能,因为我没有时间去打理。</p>
<p>唉</p>
</summary>
<category term="随笔" scheme="http://yoursite.com/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="前方" scheme="http://yoursite.com/tags/%E5%89%8D%E6%96%B9/"/>
</entry>
<entry>
<title>Eureka+Ribbon+Feign阶段性总结</title>
<link href="http://yoursite.com/2020/01/23/[Eureka+Ribbon+Feign%E9%98%B6%E6%AE%B5%E6%80%A7%E6%80%BB%E7%BB%93]/"/>
<id>http://yoursite.com/2020/01/23/[Eureka+Ribbon+Feign%E9%98%B6%E6%AE%B5%E6%80%A7%E6%80%BB%E7%BB%93]/</id>
<published>2020-01-23T14:05:37.000Z</published>
<updated>2020-03-01T15:33:41.384Z</updated>
<content type="html"><![CDATA[<h1 id="Eureka-Ribbon-Feign阶段性总结"><a href="#Eureka-Ribbon-Feign阶段性总结" class="headerlink" title="[Eureka+Ribbon+Feign阶段性总结]"></a>[Eureka+Ribbon+Feign阶段性总结]</h1><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>三个组件的调用关系:</p><p><img src="http://img2018.cnblogs.com/blog/799093/202001/799093-20200113101813661-1058067907.png" alt="Feign_Ribbion_Eureka互相调用过程 1.jpg"></p><a id="more"></a><p>图片看不清的话可以直接看分享出来的processon地址:</p><p><a href="https://www.processon.com/view/link/5e1577a5e4b061a80c6ca89f" target="_blank" rel="noopener">https://www.processon.com/view/link/5e1577a5e4b061a80c6ca89f</a></p><p>转载自 :<a href="https://www.cnblogs.com/wang-meng/p/12147889.html" target="_blank" rel="noopener">https://www.cnblogs.com/wang-meng/p/12147889.html</a></p>]]></content>
<summary type="html">
<h1 id="Eureka-Ribbon-Feign阶段性总结"><a href="#Eureka-Ribbon-Feign阶段性总结" class="headerlink" title="[Eureka+Ribbon+Feign阶段性总结]"></a>[Eureka+Ribbon+Feign阶段性总结]</h1><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>三个组件的调用关系:</p>
<p><img src="http://img2018.cnblogs.com/blog/799093/202001/799093-20200113101813661-1058067907.png" alt="Feign_Ribbion_Eureka互相调用过程 1.jpg"></p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Feign" scheme="http://yoursite.com/tags/Feign/"/>
</entry>
<entry>
<title>Feign 源码二:Feign动态代理构造过程</title>
<link href="http://yoursite.com/2020/01/22/[Feign%20%E6%BA%90%E7%A0%81%E4%BA%8C%EF%BC%9AFeign%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E6%9E%84%E9%80%A0%E8%BF%87%E7%A8%8B]/"/>
<id>http://yoursite.com/2020/01/22/[Feign%20%E6%BA%90%E7%A0%81%E4%BA%8C%EF%BC%9AFeign%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E6%9E%84%E9%80%A0%E8%BF%87%E7%A8%8B]/</id>
<published>2020-01-22T14:05:37.000Z</published>
<updated>2020-03-01T15:23:32.807Z</updated>
<content type="html"><![CDATA[<h1 id="Feign-源码二:Feign动态代理构造过程"><a href="#Feign-源码二:Feign动态代理构造过程" class="headerlink" title="[Feign 源码二:Feign动态代理构造过程]"></a>[Feign 源码二:Feign动态代理构造过程]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是<br> 将EnableFeignClients注解对应的配置属性注入,将FeignClient注解对应的属性注入。</p><p>最后是生成FeignClient对应的bean,注入到Spring 的IOC容器。</p><a id="more"></a><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p><strong>目录如下:</strong></p><ol><li>registerFeignClient()回顾</li><li>FeignClientFactoryBean.getObject()解析</li><li>Feign.builder()及client()构建逻辑</li><li>创建Feign动态代理实现细节</li></ol><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h4 id="registerFeignClient-回顾"><a href="#registerFeignClient-回顾" class="headerlink" title="registerFeignClient()回顾"></a>registerFeignClient()回顾</h4><p>回顾下之前的代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">registerFeignClient</span><span class="params">(BeanDefinitionRegistry registry,</span></span></span><br><span class="line"><span class="function"><span class="params"> AnnotationMetadata annotationMetadata, Map<String, Object> attributes)</span> </span>{</span><br><span class="line"> String className = annotationMetadata.getClassName();</span><br><span class="line"> BeanDefinitionBuilder definition = BeanDefinitionBuilder</span><br><span class="line"> .genericBeanDefinition(FeignClientFactoryBean<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> validate(attributes);</span><br><span class="line"> definition.addPropertyValue(<span class="string">"url"</span>, getUrl(attributes));</span><br><span class="line"> definition.addPropertyValue(<span class="string">"path"</span>, getPath(attributes));</span><br><span class="line"> String name = getName(attributes);</span><br><span class="line"> definition.addPropertyValue(<span class="string">"name"</span>, name);</span><br><span class="line"> definition.addPropertyValue(<span class="string">"type"</span>, className);</span><br><span class="line"> definition.addPropertyValue(<span class="string">"decode404"</span>, attributes.get(<span class="string">"decode404"</span>));</span><br><span class="line"> definition.addPropertyValue(<span class="string">"fallback"</span>, attributes.get(<span class="string">"fallback"</span>));</span><br><span class="line"> definition.addPropertyValue(<span class="string">"fallbackFactory"</span>, attributes.get(<span class="string">"fallbackFactory"</span>));</span><br><span class="line"> definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);</span><br><span class="line"></span><br><span class="line"> String alias = name + <span class="string">"FeignClient"</span>;</span><br><span class="line"> AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">boolean</span> primary = (Boolean)attributes.get(<span class="string">"primary"</span>); <span class="comment">// has a default, won't be null</span></span><br><span class="line"></span><br><span class="line"> beanDefinition.setPrimary(primary);</span><br><span class="line"></span><br><span class="line"> String qualifier = getQualifier(attributes);</span><br><span class="line"> <span class="keyword">if</span> (StringUtils.hasText(qualifier)) {</span><br><span class="line"> alias = qualifier;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> BeanDefinitionHolder holder = <span class="keyword">new</span> BeanDefinitionHolder(beanDefinition, className,</span><br><span class="line"> <span class="keyword">new</span> String[] { alias });</span><br><span class="line"> BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在<code>registerFeignClient()</code>方法中构造了一个BeanDefinitionBuilder对象,BeanDefinitionBuilder的主要作用就是构建一个AbstractBeanDefinition,AbstractBeanDefinition类最终被构建成一个BeanDefinitionHolder 然后注册到Spring中。</p><p>beanDefinition类为FeignClientFactoryBean,故在Spring获取类的时候实际返回的是FeignClientFactoryBean类。</p><p><code>FeignClientFactoryBean</code>作为一个实现了FactoryBean的工厂类,那么每次在Spring Context 创建实体类的时候会调用它的<code>getObject()</code>方法。</p><h3 id="FeignClientFactoryBean-getObject-解析"><a href="#FeignClientFactoryBean-getObject-解析" class="headerlink" title="FeignClientFactoryBean.getObject()解析"></a>FeignClientFactoryBean.getObject()解析</h3><p>这里直接分析<code>FeignClientFactoryBean.getObject()</code>方法,这里包含着Feign动态代理的原理。</p><p>先看下代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">public Object getObject() throws Exception {</span><br><span class="line"> // 可以类比于ribbon中的SpringClientFactory,每个服务都对应一个独立的spring容器</span><br><span class="line"> FeignContext context = applicationContext.getBean(FeignContext.class);</span><br><span class="line"> // builder中包含contract、logLevel、encoder、decoder、options等信息</span><br><span class="line"> Feign.Builder builder = feign(context);</span><br><span class="line"></span><br><span class="line"> // 如果@FeignClient注解上没有指定url,说明是要用ribbon的负载均衡</span><br><span class="line"> if (!StringUtils.hasText(this.url)) {</span><br><span class="line"> String url;</span><br><span class="line"> if (!this.name.startsWith("http")) {</span><br><span class="line"> url = "http://" + this.name;</span><br><span class="line"> }</span><br><span class="line"> else {</span><br><span class="line"> url = this.name;</span><br><span class="line"> }</span><br><span class="line"> // 这里构建的url类似于:http://serviceA</span><br><span class="line"> url += cleanPath();</span><br><span class="line"> return loadBalance(builder, context, new HardCodedTarget<>(this.type,</span><br><span class="line"> this.name, url));</span><br><span class="line"> }</span><br><span class="line"> if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {</span><br><span class="line"> this.url = "http://" + this.url;</span><br><span class="line"> }</span><br><span class="line"> String url = this.url + cleanPath();</span><br><span class="line"> Client client = getOptional(context, Client.class);</span><br><span class="line"> if (client != null) {</span><br><span class="line"> if (client instanceof LoadBalancerFeignClient) {</span><br><span class="line"> // not lod balancing because we have a url,</span><br><span class="line"> // but ribbon is on the classpath, so unwrap</span><br><span class="line"> client = ((LoadBalancerFeignClient)client).getDelegate();</span><br><span class="line"> }</span><br><span class="line"> builder.client(client);</span><br><span class="line"> }</span><br><span class="line"> Targeter targeter = get(context, Targeter.class);</span><br><span class="line"> return targeter.target(this, builder, context, new HardCodedTarget<>(</span><br><span class="line"> this.type, this.name, url));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public <T> T getInstance(String name, Class<T> type) {</span><br><span class="line"> // getContext是从SpringClientContext中获取,之前讲ribbon源码时讲过</span><br><span class="line"> // 一个serviceName都会有自己的一个SpringClientContext上下文信息</span><br><span class="line"> AnnotationConfigApplicationContext context = getContext(name);</span><br><span class="line"> if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,</span><br><span class="line"> type).length > 0) {</span><br><span class="line"> // 这里是获取到LoadBalancerFeignClient</span><br><span class="line"> return context.getBean(type);</span><br><span class="line"> }</span><br><span class="line"> return null;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>首先是<code>FeignContext</code> ,我们可以类比下ribbon中的<code>SpringClientFactory</code>, 每个服务的调用,都有一个独立的ILoadBalancer、IRule、IPing等等,每个服务都对应一个独立的spring容器,从那个独立的容器中,可以取出这个服务关联的属于自己的LoadBalancer之类的东西。</p><p>如果我们调用一个服务的话,比如ServiceA,那么这个服务就会关联一个spring容器,FeignContext就代表一个独立的容器,关联着自己独立的一些组件,例如Logger组件、Decoder组件、Encoder组件等等。</p><p>我们可以看下<code>FeignAutoConfiguration</code>中:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">@Configuration</span><br><span class="line">@ConditionalOnClass(Feign.class)</span><br><span class="line">@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})</span><br><span class="line">public class FeignAutoConfiguration {</span><br><span class="line"> @Bean</span><br><span class="line"> public FeignContext feignContext() {</span><br><span class="line"> FeignContext context = new FeignContext();</span><br><span class="line"> // configurations是一个Map结构</span><br><span class="line"> context.setConfigurations(this.configurations);</span><br><span class="line"> return context;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class FeignContext extends NamedContextFactory<FeignClientSpecification> {</span><br><span class="line"></span><br><span class="line"> public FeignContext() {</span><br><span class="line"> // FeignClientsConfiguration中会加载Encoder、Decoder、Logger等组件</span><br><span class="line"> super(FeignClientsConfiguration.class, "feign", "feign.client.name");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里可以知道FeignContext的结构,里面其实就是封装了一个服务实例(ServiceA)对应的各种组件,其中<code>FeignClientsConfiguration</code>是加载默认的组件信息配置类。</p><p>接下来还是回到<code>FeignClientFactoryBean.getObject()</code>中,接着看<code>feign()</code>方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">protected Feign.Builder feign(FeignContext context) {</span><br><span class="line"> // 从context中获取到默认Logger组件:Slf4jLogger</span><br><span class="line"> FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);</span><br><span class="line"> Logger logger = loggerFactory.create(this.type);</span><br><span class="line"></span><br><span class="line"> // 从context中找type:Feign.Builder.class 对应的组件信息</span><br><span class="line"> // 然后往builder中放入各种组件信息</span><br><span class="line"> Feign.Builder builder = get(context, Feign.Builder.class)</span><br><span class="line"> // required values</span><br><span class="line"> .logger(logger)</span><br><span class="line"> .encoder(get(context, Encoder.class))</span><br><span class="line"> .decoder(get(context, Decoder.class))</span><br><span class="line"> .contract(get(context, Contract.class));</span><br><span class="line"> // @formatter:on</span><br><span class="line"></span><br><span class="line"> configureFeign(context, builder);</span><br><span class="line"></span><br><span class="line"> return builder;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">protected <T> T get(FeignContext context, Class<T> type) {</span><br><span class="line"> // context中转载的有Logger组件信息,这里默认的是Slf4jLogger</span><br><span class="line"> T instance = context.getInstance(this.name, type);</span><br><span class="line"> if (instance == null) {</span><br><span class="line"> throw new IllegalStateException("No bean found of type " + type + " for "</span><br><span class="line"> + this.name);</span><br><span class="line"> }</span><br><span class="line"> return instance;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里是构造一个Feign.builder()对象,里面还是封装了各种组件信息。其中Feign.builder在<code>FeignClientsConfiguration</code>被初始化,一般使用的是<code>HystrixFeign.builder()</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">@Configuration</span><br><span class="line">public class FeignClientsConfiguration {</span><br><span class="line"> // 一般环境都会配置feign.hystrix.enabled = true,这里直接看HystrixFeign.builder();</span><br><span class="line"> @Configuration</span><br><span class="line"> @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })</span><br><span class="line"> protected static class HystrixFeignConfiguration {</span><br><span class="line"> @Bean</span><br><span class="line"> @Scope("prototype")</span><br><span class="line"> @ConditionalOnMissingBean</span><br><span class="line"> @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)</span><br><span class="line"> public Feign.Builder feignHystrixBuilder() {</span><br><span class="line"> return HystrixFeign.builder();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接着看<code>configureFeign()</code> 方法,这个方法是读取application.properties中的配置信息。这里有个很有趣的配置:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);</span><br><span class="line">configureUsingProperties(properties.getConfig().get(this.name), builder);</span><br></pre></td></tr></table></figure><p>如果我们配置feign,先指定一个全局配置,在指定针对于某个服务的配置,那么某个服务配置的优先级会覆盖全局的配置。</p><p>一张图总结下Feign.builder()构建的过程:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200111143408683-173345783.png" alt="02_Feign动态代理构建过程_1_-Feign.builder__构建.jpg"></p><h4 id="Feign-builder-及client-构建逻辑"><a href="#Feign-builder-及client-构建逻辑" class="headerlink" title="Feign.builder()及client()构建逻辑"></a>Feign.builder()及client()构建逻辑</h4><p>还是接着上面<code>getObject()</code> 方法去分析,上面分析完了<code>Feign.builder()</code>的构建,下面接着看看剩下的代码。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">loadBalance(builder, context, new HardCodedTarget<>(this.type,this.name, url));</span><br></pre></td></tr></table></figure><p>这里形式构造了一个<code>HardCodeTarget</code>对象,这个对象包含了接口类型(com.barrywang.service.feign.ServiceAFeignClient)、服务名称(ServiceA)、url地址(<a href="http://ServiceA),跟Feign.Builder、FeignContext,一起,传入了loadBalance()方法里去。" target="_blank" rel="noopener">http://ServiceA),跟Feign.Builder、FeignContext,一起,传入了loadBalance()方法里去。</a></p><p>接着查看<code>loadBalance()</code> 方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">protected <T> T loadBalance(Feign.Builder builder, FeignContext context,</span><br><span class="line"> HardCodedTarget<T> target) {</span><br><span class="line"> // 这里还是从context中获取feignClient数据</span><br><span class="line"> Client client = getOptional(context, Client.class);</span><br><span class="line"> if (client != null) {</span><br><span class="line"> builder.client(client);</span><br><span class="line"> Targeter targeter = get(context, Targeter.class);</span><br><span class="line"> return targeter.target(this, builder, context, target);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> throw new IllegalStateException(</span><br><span class="line"> "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">protected <T> T getOptional(FeignContext context, Class<T> type) {</span><br><span class="line"> return context.getInstance(this.name, type);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里还是从context中获取<code>Client.class</code>对应的数据,我们继续查看<code>FeignAutoConfiguration</code> 类,但是并没有发现Feign.client相关的数据,查看<code>FeignAutoConfiguration</code>的依赖,可以找到<code>FeignRibbonClientAutoConfiguration</code> ,代码如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">@ConditionalOnClass({ ILoadBalancer.class, Feign.class })</span><br><span class="line">@Configuration</span><br><span class="line">@AutoConfigureBefore(FeignAutoConfiguration.class)</span><br><span class="line">@EnableConfigurationProperties({ FeignHttpClientProperties.class })</span><br><span class="line">// 这里会import三个FeignLoadBalance配置</span><br><span class="line">@Import({ HttpClientFeignLoadBalancedConfiguration.class,</span><br><span class="line"> OkHttpFeignLoadBalancedConfiguration.class,</span><br><span class="line"> DefaultFeignLoadBalancedConfiguration.class })</span><br><span class="line">public class FeignRibbonClientAutoConfiguration {</span><br><span class="line"></span><br><span class="line"> @Bean</span><br><span class="line"> @Primary</span><br><span class="line"> @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")</span><br><span class="line"> public CachingSpringLoadBalancerFactory cachingLBClientFactory(</span><br><span class="line"> SpringClientFactory factory) {</span><br><span class="line"> return new CachingSpringLoadBalancerFactory(factory);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Bean</span><br><span class="line"> @Primary</span><br><span class="line"> @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")</span><br><span class="line"> public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(</span><br><span class="line"> SpringClientFactory factory,</span><br><span class="line"> LoadBalancedRetryPolicyFactory retryPolicyFactory,</span><br><span class="line"> LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,</span><br><span class="line"> LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {</span><br><span class="line"> return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // Options是超时相关的配置</span><br><span class="line"> @Bean</span><br><span class="line"> @ConditionalOnMissingBean</span><br><span class="line"> public Request.Options feignRequestOptions() {</span><br><span class="line"> return LoadBalancerFeignClient.DEFAULT_OPTIONS;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">@Configuration</span><br><span class="line">class DefaultFeignLoadBalancedConfiguration {</span><br><span class="line"> @Bean</span><br><span class="line"> @ConditionalOnMissingBean</span><br><span class="line"> public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,</span><br><span class="line"> SpringClientFactory clientFactory) {</span><br><span class="line"> return new LoadBalancerFeignClient(new Client.Default(null, null),</span><br><span class="line"> cachingFactory, clientFactory);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>到了这里就知道了,这里Feign.client默认应该就是<code>LoadBalancerFeignClient</code>了。</p><p>到这继续用一张图总结下:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200111143411939-293645963.png" alt="03_Feign动态代理构建过程_2_-Feign.client__构建.jpg"></p><h4 id="创建Feign动态代理实现细节"><a href="#创建Feign动态代理实现细节" class="headerlink" title="创建Feign动态代理实现细节"></a>创建Feign动态代理实现细节</h4><p>接着上面代码,默认Feign.client()为<code>LoadBalancerFeignClient</code>, 然后将client加入到builder中。接着继续跟进<code>targer</code>相关:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">protected <T> T loadBalance(Feign.Builder builder, FeignContext context,</span><br><span class="line"> HardCodedTarget<T> target) {</span><br><span class="line"> Client client = getOptional(context, Client.class);</span><br><span class="line"> if (client != null) {</span><br><span class="line"> builder.client(client);</span><br><span class="line"> // 这里又是通过Targer然后再context中获取默认配置</span><br><span class="line"> Targeter targeter = get(context, Targeter.class);</span><br><span class="line"> return targeter.target(this, builder, context, target);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> throw new IllegalStateException(</span><br><span class="line"> "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">protected <T> T get(FeignContext context, Class<T> type) {</span><br><span class="line"> T instance = context.getInstance(this.name, type);</span><br><span class="line"> if (instance == null) {</span><br><span class="line"> throw new IllegalStateException("No bean found of type " + type + " for "</span><br><span class="line"> + this.name);</span><br><span class="line"> }</span><br><span class="line"> return instance;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到,这里又是通过<code>Targeter.class</code>从context中获取对应默认Targter。我们继续通过<code>FeignAutoConfiguration</code>中进行查找:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">@Configuration</span><br><span class="line">@ConditionalOnClass(Feign.class)</span><br><span class="line">@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})</span><br><span class="line">public class FeignAutoConfiguration {</span><br><span class="line"></span><br><span class="line"> @Autowired(required = false)</span><br><span class="line"> private List<FeignClientSpecification> configurations = new ArrayList<>();</span><br><span class="line"></span><br><span class="line"> @Bean</span><br><span class="line"> public FeignContext feignContext() {</span><br><span class="line"> FeignContext context = new FeignContext();</span><br><span class="line"> context.setConfigurations(this.configurations);</span><br><span class="line"> return context;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 如果配置了feign.hystrix.HystrixFeign 则创建HystrixTargeter</span><br><span class="line"> @Configuration</span><br><span class="line"> @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")</span><br><span class="line"> protected static class HystrixFeignTargeterConfiguration {</span><br><span class="line"> @Bean</span><br><span class="line"> @ConditionalOnMissingBean</span><br><span class="line"> public Targeter feignTargeter() {</span><br><span class="line"> return new HystrixTargeter();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 如果没有配置feign.hystrix.HystrixFeign 则创建DefaultTargeter</span><br><span class="line"> @Configuration</span><br><span class="line"> @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")</span><br><span class="line"> protected static class DefaultFeignTargeterConfiguration {</span><br><span class="line"> @Bean</span><br><span class="line"> @ConditionalOnMissingBean</span><br><span class="line"> public Targeter feignTargeter() {</span><br><span class="line"> return new DefaultTargeter();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在默认情况下,feign是和hystrix整合的,<code>feign.hystrix.HystrixFeign</code>会有配置,所以这里默认Targeter使用的是<code>HystrixTargeter</code>, 在<code>loadBalance()</code>方法中执行的targeter.target()方法就是执行<code>HystrixTargeter.target()</code>方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">class HystrixTargeter implements Targeter {</span><br><span class="line"> public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,</span><br><span class="line"> Target.HardCodedTarget<T> target) {</span><br><span class="line"> // 判断Feign.builder()类型</span><br><span class="line"> if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {</span><br><span class="line"> return feign.target(target);</span><br><span class="line"> }</span><br><span class="line"> feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;</span><br><span class="line"> SetterFactory setterFactory = getOptional(factory.getName(), context,</span><br><span class="line"> SetterFactory.class);</span><br><span class="line"> if (setterFactory != null) {</span><br><span class="line"> builder.setterFactory(setterFactory);</span><br><span class="line"> }</span><br><span class="line"> Class<?> fallback = factory.getFallback();</span><br><span class="line"> if (fallback != void.class) {</span><br><span class="line"> return targetWithFallback(factory.getName(), context, target, builder, fallback);</span><br><span class="line"> }</span><br><span class="line"> Class<?> fallbackFactory = factory.getFallbackFactory();</span><br><span class="line"> if (fallbackFactory != void.class) {</span><br><span class="line"> return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 最终都会执行feign.target()方法</span><br><span class="line"> return feign.target(target);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">public abstract class Feign {</span><br><span class="line"></span><br><span class="line"> public static Builder builder() {</span><br><span class="line"> return new Builder();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * Returns a new instance of an HTTP API, defined by annotations in the {@link Feign Contract},</span><br><span class="line"> * for the specified {@code target}. You should cache this result.</span><br><span class="line"> */</span><br><span class="line"> public abstract <T> T newInstance(Target<T> target);</span><br><span class="line"></span><br><span class="line"> public static class Builder {</span><br><span class="line"></span><br><span class="line"> // 省略部分代码</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> public <T> T target(Target<T> target) {</span><br><span class="line"> return build().newInstance(target);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public Feign build() {</span><br><span class="line"> // 构建一个SynchronousMethodHandler工厂</span><br><span class="line"> SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =</span><br><span class="line"> new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,</span><br><span class="line"> logLevel, decode404);</span><br><span class="line"></span><br><span class="line"> // 构建</span><br><span class="line"> ParseHandlersByName handlersByName =</span><br><span class="line"> new ParseHandlersByName(contract, options, encoder, decoder,</span><br><span class="line"> errorDecoder, synchronousMethodHandlerFactory);</span><br><span class="line"> return new ReflectiveFeign(handlersByName, invocationHandlerFactory);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里主要是build方法,构造了一个<code>ReflectieFein</code>对象,接着看它里面的<code>newInstance()</code>方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">public <T> T newInstance(Target<T> target) {</span><br><span class="line"> // nameToHandler是@FeignClient中的方法名对应的MethodHandler对象</span><br><span class="line"> Map<String, InvocationHandlerFactory.MethodHandler> nameToHandler = targetToHandlersByName.apply(target);</span><br><span class="line"> Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = new LinkedHashMap<Method, InvocationHandlerFactory.MethodHandler>();</span><br><span class="line"> List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();</span><br><span class="line"></span><br><span class="line"> for (Method method : target.type().getMethods()) {</span><br><span class="line"> if (method.getDeclaringClass() == Object.class) {</span><br><span class="line"> continue;</span><br><span class="line"> } else if (Util.isDefault(method)) {</span><br><span class="line"> DefaultMethodHandler handler = new DefaultMethodHandler(method);</span><br><span class="line"> defaultMethodHandlers.add(handler);</span><br><span class="line"> methodToHandler.put(method, handler);</span><br><span class="line"> } else {</span><br><span class="line"> // 将具体的method作为map的key值</span><br><span class="line"> methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // JDK动态代理 返回类似于:ReflectiveFeign$FeignInvocationHandler@7642</span><br><span class="line"> // methodToHandler中包含Feign.builder()、Feign.client()等信息</span><br><span class="line"> InvocationHandler handler = factory.create(target, methodToHandler);</span><br><span class="line"> T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);</span><br><span class="line"></span><br><span class="line"> for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {</span><br><span class="line"> defaultMethodHandler.bindTo(proxy);</span><br><span class="line"> }</span><br><span class="line"> return proxy;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里就是使用了JDK动态代理,实际上返回的Feign动态代理的对象类似于:<code>ReflectiveFeign$FeignInvocationHandler@7642</code>。</p><p>这也和我们第一讲中的debug截图一致了,到了这里feign动态代理对象的生成原理都已经很清楚了。</p><p>最后debug一下,看下最终生成的动态代理对象:<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200111143414886-1806884325.png" alt="image.png"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>最后用一张图总结Feign动态代理生成的规则:</p><ol><li>生成Feign.builder(),里面包含Encoder、Decoder、Logger等组件,还有application.properties中相关的feign client配置信息</li><li>生成Feign.client(),默认为LoadBalancerFeignClient</li><li>生成默认Targter对象:HystrixTargter</li><li>builder、client、targter 通过JDK动态代理生成feign动态代理对象</li></ol><p>一张图总结:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200111143419411-331369408.png" alt="04_Feign动态代理构建过程_3_-基于JDK动态代理生成原理.jpg"></p>]]></content>
<summary type="html">
<h1 id="Feign-源码二:Feign动态代理构造过程"><a href="#Feign-源码二:Feign动态代理构造过程" class="headerlink" title="[Feign 源码二:Feign动态代理构造过程]"></a>[Feign 源码二:Feign动态代理构造过程]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是<br> 将EnableFeignClients注解对应的配置属性注入,将FeignClient注解对应的属性注入。</p>
<p>最后是生成FeignClient对应的bean,注入到Spring 的IOC容器。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Feign" scheme="http://yoursite.com/tags/Feign/"/>
</entry>
<entry>
<title>Feign 源码三:Feign结合Ribbon实现负载均衡的原理分析</title>
<link href="http://yoursite.com/2020/01/22/[Feign%20%E6%BA%90%E7%A0%81%E4%B8%89%EF%BC%9AFeign%E7%BB%93%E5%90%88Ribbon%E5%AE%9E%E7%8E%B0%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%9A%84%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90]/"/>
<id>http://yoursite.com/2020/01/22/[Feign%20%E6%BA%90%E7%A0%81%E4%B8%89%EF%BC%9AFeign%E7%BB%93%E5%90%88Ribbon%E5%AE%9E%E7%8E%B0%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%9A%84%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90]/</id>
<published>2020-01-22T14:05:37.000Z</published>
<updated>2020-03-01T15:24:05.177Z</updated>
<content type="html"><![CDATA[<h1 id="Feign-源码三:Feign结合Ribbon实现负载均衡的原理分析"><a href="#Feign-源码三:Feign结合Ribbon实现负载均衡的原理分析" class="headerlink" title="[Feign 源码三:Feign结合Ribbon实现负载均衡的原理分析]"></a>[Feign 源码三:Feign结合Ribbon实现负载均衡的原理分析]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲我们已经知道了Feign的工作原理其实是在项目启动的时候,通过JDK动态代理为每个FeignClinent生成一个动态代理。</p><p>动态代理的数据结构是:ReflectiveFeign.FeignInvocationHandler。其中包含<code>target</code>(里面是serviceName等信息)和<code>dispatcher</code>(map数据结构,key是请求的方法名,方法参数等,value是<code>SynchronousMethodHandler</code>)。</p><a id="more"></a><p>如下图所示:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200112083259578-1927295338.png" alt="image.png"></p><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p>这一讲主要是Feign与Ribbon结合实现负载均衡的原理分析。</p><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h4 id="Feign结合Ribbon实现负载均衡原理"><a href="#Feign结合Ribbon实现负载均衡原理" class="headerlink" title="Feign结合Ribbon实现负载均衡原理"></a>Feign结合Ribbon实现负载均衡原理</h4><p>通过前面的分析,我们可以直接来看下<code>SynchronousMethodHandler</code>中的代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br></pre></td><td class="code"><pre><span class="line">final class SynchronousMethodHandler implements MethodHandler {</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public Object invoke(Object[] argv) throws Throwable {</span><br><span class="line"> // 生成请求类似于:GET /sayHello/wangmeng HTTP/1.1</span><br><span class="line"> RequestTemplate template = buildTemplateFromArgs.create(argv);</span><br><span class="line"> Retryer retryer = this.retryer.clone();</span><br><span class="line"> while (true) {</span><br><span class="line"> try {</span><br><span class="line"> return executeAndDecode(template);</span><br><span class="line"> } catch (RetryableException e) {</span><br><span class="line"> retryer.continueOrPropagate(e);</span><br><span class="line"> if (logLevel != Logger.Level.NONE) {</span><br><span class="line"> logger.logRetry(metadata.configKey(), logLevel);</span><br><span class="line"> }</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Object executeAndDecode(RequestTemplate template) throws Throwable {</span><br><span class="line"> // 构建request对象:GET http://serviceA/sayHello/wangmeng HTTP/1.1</span><br><span class="line"> Request request = targetRequest(template);</span><br><span class="line"></span><br><span class="line"> if (logLevel != Logger.Level.NONE) {</span><br><span class="line"> logger.logRequest(metadata.configKey(), logLevel, request);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Response response;</span><br><span class="line"> long start = System.nanoTime();</span><br><span class="line"> try {</span><br><span class="line"> // 这个client就是之前构建的LoadBalancerFeignClient,options是超时时间</span><br><span class="line"> response = client.execute(request, options);</span><br><span class="line"> // ensure the request is set. TODO: remove in Feign 10</span><br><span class="line"> response.toBuilder().request(request).build();</span><br><span class="line"> } catch (IOException e) {</span><br><span class="line"> if (logLevel != Logger.Level.NONE) {</span><br><span class="line"> logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));</span><br><span class="line"> }</span><br><span class="line"> throw errorExecuting(request, e);</span><br><span class="line"> }</span><br><span class="line"> long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);</span><br><span class="line"></span><br><span class="line"> // 下面逻辑都是构建返回值response</span><br><span class="line"> boolean shouldClose = true;</span><br><span class="line"> try {</span><br><span class="line"> if (logLevel != Logger.Level.NONE) {</span><br><span class="line"> response =</span><br><span class="line"> logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);</span><br><span class="line"> // ensure the request is set. TODO: remove in Feign 10</span><br><span class="line"> response.toBuilder().request(request).build();</span><br><span class="line"> }</span><br><span class="line"> if (Response.class == metadata.returnType()) {</span><br><span class="line"> if (response.body() == null) {</span><br><span class="line"> return response;</span><br><span class="line"> }</span><br><span class="line"> if (response.body().length() == null ||</span><br><span class="line"> response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {</span><br><span class="line"> shouldClose = false;</span><br><span class="line"> return response;</span><br><span class="line"> }</span><br><span class="line"> // Ensure the response body is disconnected</span><br><span class="line"> byte[] bodyData = Util.toByteArray(response.body().asInputStream());</span><br><span class="line"> return response.toBuilder().body(bodyData).build();</span><br><span class="line"> }</span><br><span class="line"> if (response.status() >= 200 && response.status() < 300) {</span><br><span class="line"> if (void.class == metadata.returnType()) {</span><br><span class="line"> return null;</span><br><span class="line"> } else {</span><br><span class="line"> return decode(response);</span><br><span class="line"> }</span><br><span class="line"> } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {</span><br><span class="line"> return decode(response);</span><br><span class="line"> } else {</span><br><span class="line"> throw errorDecoder.decode(metadata.configKey(), response);</span><br><span class="line"> }</span><br><span class="line"> } catch (IOException e) {</span><br><span class="line"> if (logLevel != Logger.Level.NONE) {</span><br><span class="line"> logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);</span><br><span class="line"> }</span><br><span class="line"> throw errorReading(request, response, e);</span><br><span class="line"> } finally {</span><br><span class="line"> if (shouldClose) {</span><br><span class="line"> ensureClosed(response.body());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里主要是构建request数据,然后通过request和options去通过<code>LoadBalancerFeignClient.execute()</code>方法去获得返回值。我们可以接着看client端的调用:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">public class LoadBalancerFeignClient implements Client {</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public Response execute(Request request, Request.Options options) throws IOException {</span><br><span class="line"> try {</span><br><span class="line"> // asUri: http://serviceA/sayHello/wangmeng</span><br><span class="line"> URI asUri = URI.create(request.url());</span><br><span class="line"></span><br><span class="line"> // clientName:serviceA</span><br><span class="line"> String clientName = asUri.getHost();</span><br><span class="line"></span><br><span class="line"> // uriWithoutHost: http://sayHello/wangmeng</span><br><span class="line"> URI uriWithoutHost = cleanUrl(request.url(), clientName);</span><br><span class="line"></span><br><span class="line"> // 这里ribbonRequest:GET http:///sayHello/wangmeng HTTP/1.1 </span><br><span class="line"> FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(</span><br><span class="line"> this.delegate, request, uriWithoutHost);</span><br><span class="line"></span><br><span class="line"> // 这里面config只有两个超时时间,一个是connectTimeout:5000,一个是readTimeout:5000</span><br><span class="line"> IClientConfig requestConfig = getClientConfig(options, clientName);</span><br><span class="line"></span><br><span class="line"> // 真正执行负载均衡的地方</span><br><span class="line"> return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,</span><br><span class="line"> requestConfig).toResponse();</span><br><span class="line"> }</span><br><span class="line"> catch (ClientException e) {</span><br><span class="line"> IOException io = findIOException(e);</span><br><span class="line"> if (io != null) {</span><br><span class="line"> throw io;</span><br><span class="line"> }</span><br><span class="line"> throw new RuntimeException(e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接着我们看下<code>lbClient()</code>和<code>executeWithLoadBalancer()</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">public class LoadBalancerFeignClient implements Client {</span><br><span class="line"></span><br><span class="line"> private FeignLoadBalancer lbClient(String clientName) {</span><br><span class="line"> return this.lbClientFactory.create(clientName);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class CachingSpringLoadBalancerFactory {</span><br><span class="line"> public FeignLoadBalancer create(String clientName) {</span><br><span class="line"> if (this.cache.containsKey(clientName)) {</span><br><span class="line"> return this.cache.get(clientName);</span><br><span class="line"> }</span><br><span class="line"> IClientConfig config = this.factory.getClientConfig(clientName);</span><br><span class="line"> // 获取Ribbon ILoadBalancer信息</span><br><span class="line"> ILoadBalancer lb = this.factory.getLoadBalancer(clientName);</span><br><span class="line"> ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);</span><br><span class="line"> FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,</span><br><span class="line"> loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);</span><br><span class="line"> this.cache.put(clientName, client);</span><br><span class="line"> return client;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里是获取了<code>ILoadBalancer</code>数据,里面包含了Ribbon获取的serviceA所有服务节点信息。</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200112083303104-2075322481.png" alt="image.png"></p><p>这里已经获取到<code>ILoadBalancer</code>,里面包含serviceA服务器所有节点请求host信息。接下来就是从中负载均衡选择一个节点信息host出来。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br></pre></td><td class="code"><pre><span class="line">public abstract class AbstractLoadBalancerAwareClient<S extends ClientRequest, T extends IResponse> extends LoadBalancerContext implements IClient<S, T>, IClientConfigAware {</span><br><span class="line"></span><br><span class="line"> public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {</span><br><span class="line"> LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);</span><br><span class="line"></span><br><span class="line"> try {</span><br><span class="line"> return command.submit(</span><br><span class="line"> new ServerOperation<T>() {</span><br><span class="line"> @Override</span><br><span class="line"> public Observable<T> call(Server server) {</span><br><span class="line"> URI finalUri = reconstructURIWithServer(server, request.getUri());</span><br><span class="line"> S requestForServer = (S) request.replaceUri(finalUri);</span><br><span class="line"> try {</span><br><span class="line"> return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));</span><br><span class="line"> } </span><br><span class="line"> catch (Exception e) {</span><br><span class="line"> return Observable.error(e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> .toBlocking()</span><br><span class="line"> .single();</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> Throwable t = e.getCause();</span><br><span class="line"> if (t instanceof ClientException) {</span><br><span class="line"> throw (ClientException) t;</span><br><span class="line"> } else {</span><br><span class="line"> throw new ClientException(e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class LoadBalancerCommand<T> {</span><br><span class="line"> </span><br><span class="line"> public Observable<T> submit(final ServerOperation<T> operation) {</span><br><span class="line"> final ExecutionInfoContext context = new ExecutionInfoContext();</span><br><span class="line"> </span><br><span class="line"> if (listenerInvoker != null) {</span><br><span class="line"> try {</span><br><span class="line"> listenerInvoker.onExecutionStart();</span><br><span class="line"> } catch (AbortExecutionException e) {</span><br><span class="line"> return Observable.error(e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();</span><br><span class="line"> final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();</span><br><span class="line"></span><br><span class="line"> // Use the load balancer</span><br><span class="line"> Observable<T> o = </span><br><span class="line"> (server == null ? selectServer() : Observable.just(server))</span><br><span class="line"> .concatMap(new Func1<Server, Observable<T>>() {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 省略代码...</span><br><span class="line"></span><br><span class="line"> // selectServer是真正执行负载均衡的逻辑</span><br><span class="line"> private Observable<Server> selectServer() {</span><br><span class="line"> return Observable.create(new OnSubscribe<Server>() {</span><br><span class="line"> @Override</span><br><span class="line"> public void call(Subscriber<? super Server> next) {</span><br><span class="line"> try {</span><br><span class="line"> // loadBalancerURI是http:///sayHello/wangmeng, loadBalancerKey为null</span><br><span class="line"> Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey); </span><br><span class="line"> next.onNext(server);</span><br><span class="line"> next.onCompleted();</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> next.onError(e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class LoadBalancerContext implements IClientConfigAware {</span><br><span class="line"></span><br><span class="line"> public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {</span><br><span class="line"> String host = null;</span><br><span class="line"> int port = -1;</span><br><span class="line"> if (original != null) {</span><br><span class="line"> host = original.getHost();</span><br><span class="line"> }</span><br><span class="line"> if (original != null) {</span><br><span class="line"> Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original); </span><br><span class="line"> port = schemeAndPort.second();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 获取到ILoadBalancer,这里面有IRule的信息及服务节点所有信息</span><br><span class="line"> ILoadBalancer lb = getLoadBalancer();</span><br><span class="line"> if (host == null) {</span><br><span class="line"> // Partial URI or no URI Case</span><br><span class="line"> // well we have to just get the right instances from lb - or we fall back</span><br><span class="line"> if (lb != null){</span><br><span class="line"> // 这里就执行真正的chooseServer的逻辑了。默认的rule为ZoneAvoidanceZule</span><br><span class="line"> Server svc = lb.chooseServer(loadBalancerKey);</span><br><span class="line"> if (svc == null){</span><br><span class="line"> throw new ClientException(ClientException.ErrorType.GENERAL,</span><br><span class="line"> "Load balancer does not have available server for client: "</span><br><span class="line"> + clientName);</span><br><span class="line"> }</span><br><span class="line"> host = svc.getHost();</span><br><span class="line"> if (host == null){</span><br><span class="line"> throw new ClientException(ClientException.ErrorType.GENERAL,</span><br><span class="line"> "Invalid Server for :" + svc);</span><br><span class="line"> }</span><br><span class="line"> logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});</span><br><span class="line"> return svc;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 省略代码</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面代码已经很清晰了,这里就是真正的通过ribbon的 <code>rule.chooseServer()</code>负载均衡地选择了一个服务节点调用,debug如下:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200112083304636-736012805.png" alt="image.png"></p><p>到了这里feign与ribbon的分析也就结束了,返回请求url信息,然后得到response结果:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200112083306444-1849024119.png" alt="image.png"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>上面已经分析了Feign与Ribbon的整合,最终还是落到Ribbon中的ILoadBalancer中,使用最后使用IRule去选择对应的server数据。</p><p>下一讲 会画一个很大的图,包含Feign、Ribbon、Eureka关联的图,里面会画出每个组件的细节及依赖关系。也算是学习至今的一个总复习了。</p>]]></content>
<summary type="html">
<h1 id="Feign-源码三:Feign结合Ribbon实现负载均衡的原理分析"><a href="#Feign-源码三:Feign结合Ribbon实现负载均衡的原理分析" class="headerlink" title="[Feign 源码三:Feign结合Ribbon实现负载均衡的原理分析]"></a>[Feign 源码三:Feign结合Ribbon实现负载均衡的原理分析]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲我们已经知道了Feign的工作原理其实是在项目启动的时候,通过JDK动态代理为每个FeignClinent生成一个动态代理。</p>
<p>动态代理的数据结构是:ReflectiveFeign.FeignInvocationHandler。其中包含<code>target</code>(里面是serviceName等信息)和<code>dispatcher</code>(map数据结构,key是请求的方法名,方法参数等,value是<code>SynchronousMethodHandler</code>)。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Feign" scheme="http://yoursite.com/tags/Feign/"/>
</entry>
<entry>
<title>Feign 源码一:源码初探,通过Demo Debug Feign源码</title>
<link href="http://yoursite.com/2020/01/21/[Feign%20%E6%BA%90%E7%A0%81%E4%B8%80%EF%BC%9A%E6%BA%90%E7%A0%81%E5%88%9D%E6%8E%A2%EF%BC%8C%E9%80%9A%E8%BF%87Demo%20Debug%20Feign%E6%BA%90%E7%A0%81]/"/>
<id>http://yoursite.com/2020/01/21/[Feign%20%E6%BA%90%E7%A0%81%E4%B8%80%EF%BC%9A%E6%BA%90%E7%A0%81%E5%88%9D%E6%8E%A2%EF%BC%8C%E9%80%9A%E8%BF%87Demo%20Debug%20Feign%E6%BA%90%E7%A0%81]/</id>
<published>2020-01-21T14:05:37.000Z</published>
<updated>2020-03-01T15:22:52.416Z</updated>
<content type="html"><![CDATA[<h1 id="Feign-源码一:源码初探,通过Demo-Debug-Feign源码"><a href="#Feign-源码一:源码初探,通过Demo-Debug-Feign源码" class="headerlink" title="[Feign 源码一:源码初探,通过Demo Debug Feign源码]"></a>[Feign 源码一:源码初探,通过Demo Debug Feign源码]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是<code>DiscoveryEnableNIWSServerList</code>,同时在<code>DynamicServerListLoadBalancer</code>中会调用<code>PollingServerListUpdater</code> 进行定时更新Eureka注册表信息到<code>BaseLoadBalancer</code>中,默认30s调度一次。</p><a id="more"></a><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p>这一讲主要是讲Fegin Demo以及通过入口注解@EnableFeignCliets和@FeignClient来进行源码初探。</p><p><strong>目录如下:</strong></p><ol><li>Feign代码Demo</li><li>Feign调用原理</li><li>@EnableEurekaClient和@FeignClient注解扫描</li></ol><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h4 id="Feign代码Demo"><a href="#Feign代码Demo" class="headerlink" title="Feign代码Demo"></a>Feign代码Demo</h4><p>Fegin的Demo还是延续之前讲解的Eureka的代码。地址为:<br><a href="https://github.com/barrywangmeng/spring-cloud-learn" target="_blank" rel="noopener">https://github.com/barrywangmeng/spring-cloud-learn</a></p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200110103634690-293700167.png" alt="调用图片"><br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200110103635840-22709238.png" alt="image.png"></p><p>如上图所示,ServiceB调用ServiceA的服务,定义了一个@FeignClient标注的ServiceAFeignClient接口,里面定义了ServiceA中Controller提供的接口信息。</p><h3 id="Feign调用原理"><a href="#Feign调用原理" class="headerlink" title="Feign调用原理"></a>Feign调用原理</h3><p>接着我们可以启动所有服务,调用ServiceBController,然后在serviceAFeignClient处打上断点看一下:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200110103636390-1362332959.png" alt="image.png"></p><p>我们发现这里serviceAFeignClient显示的是:<code>ReflectiveFeign$FeignInvocationHandler@7642</code> ,这里其实是使用了动态代理,因为<code>ServiceAFeignClient</code> 是一个接口,所以这里可以猜测到底层使用的是JDK动态代理。</p><p>接着可以简单地梳理下Feign请求简单原理图:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200110103637002-1539671852.png" alt="00_Feign请求原理图.jpg"></p><h4 id="EnableEurekaClient和-FeignClient注解扫描"><a href="#EnableEurekaClient和-FeignClient注解扫描" class="headerlink" title="@EnableEurekaClient和@FeignClient注解扫描"></a>@EnableEurekaClient和@FeignClient注解扫描</h4><p>先看下@EnableFeignClients注解代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Retention</span>(RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="meta">@Target</span>(ElementType.TYPE)</span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Import</span>(FeignClientsRegistrar<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class"><span class="title">public</span> @<span class="title">interface</span> <span class="title">EnableFeignClients</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">//等价于basePackages属性,更简洁的方式</span></span><br><span class="line"> String[] value() <span class="keyword">default</span> {};</span><br><span class="line"> <span class="comment">//指定多个包名进行扫描</span></span><br><span class="line"> String[] basePackages() <span class="keyword">default</span> {};</span><br><span class="line"></span><br><span class="line"> <span class="comment">//指定多个类或接口的class,扫描时会在这些指定的类和接口所属的包进行扫描</span></span><br><span class="line"> Class<?>[] basePackageClasses() <span class="keyword">default</span> {};</span><br><span class="line"></span><br><span class="line"> <span class="comment">//为所有的Feign Client设置默认配置类</span></span><br><span class="line"> Class<?>[] defaultConfiguration() <span class="keyword">default</span> {};</span><br><span class="line"></span><br><span class="line"> <span class="comment">//指定用@FeignClient注释的类列表。如果该项配置不为空,则不会进行类路径扫描</span></span><br><span class="line"> Class<?>[] clients() <span class="keyword">default</span> {};</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接着看下@FeignClient注解代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target</span>(ElementType.TYPE)</span><br><span class="line"><span class="meta">@Retention</span>(RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> FeignClient {</span><br><span class="line"></span><br><span class="line"> <span class="comment">//指定Feign Client的名称,如果项目使用了 Ribbon,name属性会作为微服务的名称,用于服务发现</span></span><br><span class="line"> <span class="meta">@AliasFor</span>(<span class="string">"name"</span>)</span><br><span class="line"> <span class="function">String <span class="title">value</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"> <span class="comment">//用serviceId做服务发现已经被废弃,所以不推荐使用该配置</span></span><br><span class="line"> <span class="meta">@Deprecated</span></span><br><span class="line"> <span class="function">String <span class="title">serviceId</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"> <span class="comment">//指定Feign Client的serviceId,如果项目使用了 Ribbon,将使用serviceId用于服务发现,但上面可以看到serviceId做服务发现已经被废弃,所以也不推荐使用该配置</span></span><br><span class="line"> <span class="meta">@AliasFor</span>(<span class="string">"value"</span>)</span><br><span class="line"> <span class="function">String <span class="title">name</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"> <span class="comment">//为Feign Client 新增注解@Qualifier</span></span><br><span class="line"> <span class="function">String <span class="title">qualifier</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"> <span class="comment">//请求地址的绝对URL,或者解析的主机名</span></span><br><span class="line"> <span class="function">String <span class="title">url</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"> <span class="comment">//调用该feign client发生了常见的404错误时,是否调用decoder进行解码异常信息返回,否则抛出FeignException</span></span><br><span class="line"> <span class="function"><span class="keyword">boolean</span> <span class="title">decode404</span><span class="params">()</span> <span class="keyword">default</span> <span class="keyword">false</span></span>;</span><br><span class="line"> <span class="comment">//Feign Client设置默认配置类</span></span><br><span class="line"> Class<?>[] configuration() <span class="keyword">default</span> {};</span><br><span class="line"> <span class="comment">//定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现@FeignClient标记的接口。实现的法方法即对应接口的容错处理逻辑</span></span><br><span class="line"> Class<?> fallback() <span class="keyword">default</span> <span class="keyword">void</span><span class="class">.<span class="keyword">class</span></span>;</span><br><span class="line"> <span class="comment">//工厂类,用于生成fallback 类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码</span></span><br><span class="line"> Class<?> fallbackFactory() <span class="keyword">default</span> <span class="keyword">void</span><span class="class">.<span class="keyword">class</span></span>;</span><br><span class="line"> <span class="comment">//定义当前FeignClient的所有方法映射加统一前缀</span></span><br><span class="line"> <span class="function">String <span class="title">path</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"> <span class="comment">//是否将此Feign代理标记为一个Primary Bean,默认为ture</span></span><br><span class="line"> <span class="function"><span class="keyword">boolean</span> <span class="title">primary</span><span class="params">()</span> <span class="keyword">default</span> <span class="keyword">true</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接着我们看下@EnableFeignClients中注入的<br> @Import(FeignClientsRegistrar.class) 源码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FeignClientsRegistrar</span> <span class="keyword">implements</span> <span class="title">ImportBeanDefinitionRegistrar</span>,</span></span><br><span class="line"><span class="class"> <span class="title">ResourceLoaderAware</span>, <span class="title">EnvironmentAware</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// patterned after Spring Integration IntegrationComponentScanRegistrar</span></span><br><span class="line"> <span class="comment">// and RibbonClientsConfigurationRegistgrar</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> ResourceLoader resourceLoader;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Environment environment;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">FeignClientsRegistrar</span><span class="params">()</span> </span>{</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setResourceLoader</span><span class="params">(ResourceLoader resourceLoader)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.resourceLoader = resourceLoader;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//在这个重载的方法里面做了两件事情:</span></span><br><span class="line"> <span class="comment">//1.将EnableFeignClients注解对应的配置属性注入</span></span><br><span class="line"> <span class="comment">//2.将FeignClient注解对应的属性注入</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerBeanDefinitions</span><span class="params">(AnnotationMetadata metadata,</span></span></span><br><span class="line"><span class="function"><span class="params"> BeanDefinitionRegistry registry)</span> </span>{</span><br><span class="line"> <span class="comment">//注入EnableFeignClients注解对应的配置属性</span></span><br><span class="line"> registerDefaultConfiguration(metadata, registry);</span><br><span class="line"> <span class="comment">//注入FeignClient注解对应的属性</span></span><br><span class="line"> registerFeignClients(metadata, registry);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 拿到 EnableFeignClients注解 defaultConfiguration 字段的值</span></span><br><span class="line"><span class="comment"> * 然后进行注入</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">registerDefaultConfiguration</span><span class="params">(AnnotationMetadata metadata,</span></span></span><br><span class="line"><span class="function"><span class="params"> BeanDefinitionRegistry registry)</span> </span>{</span><br><span class="line"> Map<String, Object> defaultAttrs = metadata</span><br><span class="line"> .getAnnotationAttributes(EnableFeignClients<span class="class">.<span class="keyword">class</span>.<span class="title">getName</span>(), <span class="title">true</span>)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (defaultAttrs != <span class="keyword">null</span> && defaultAttrs.containsKey(<span class="string">"defaultConfiguration"</span>)) {</span><br><span class="line"> String name;</span><br><span class="line"> <span class="keyword">if</span> (metadata.hasEnclosingClass()) {</span><br><span class="line"> name = <span class="string">"default."</span> + metadata.getEnclosingClassName();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> name = <span class="string">"default."</span> + metadata.getClassName();</span><br><span class="line"> }</span><br><span class="line"> registerClientConfiguration(registry, name,</span><br><span class="line"> defaultAttrs.get(<span class="string">"defaultConfiguration"</span>));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerFeignClients</span><span class="params">(AnnotationMetadata metadata,</span></span></span><br><span class="line"><span class="function"><span class="params"> BeanDefinitionRegistry registry)</span> </span>{</span><br><span class="line"> <span class="comment">// 获取ClassPath扫描器</span></span><br><span class="line"> ClassPathScanningCandidateComponentProvider scanner = getScanner();</span><br><span class="line"> <span class="comment">// 为扫描器设置资源加载器</span></span><br><span class="line"> scanner.setResourceLoader(<span class="keyword">this</span>.resourceLoader);</span><br><span class="line"></span><br><span class="line"> Set<String> basePackages;</span><br><span class="line"> <span class="comment">// 1. 从@EnableFeignClients注解中获取到配置的各个属性值</span></span><br><span class="line"> <span class="comment">// 这里可以获取到配置的:basePackages=com.barrywang.service.feign</span></span><br><span class="line"> Map<String, Object> attrs = metadata</span><br><span class="line"> .getAnnotationAttributes(EnableFeignClients<span class="class">.<span class="keyword">class</span>.<span class="title">getName</span>())</span>;</span><br><span class="line"> <span class="comment">// 2. 注解类型过滤器,只过滤@FeignClient </span></span><br><span class="line"> AnnotationTypeFilter annotationTypeFilter = <span class="keyword">new</span> AnnotationTypeFilter(</span><br><span class="line"> FeignClient<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> <span class="comment">// 3. 从1. 中的属性值中获取clients属性的值 </span></span><br><span class="line"> <span class="keyword">final</span> Class<?>[] clients = attrs == <span class="keyword">null</span> ? <span class="keyword">null</span></span><br><span class="line"> : (Class<?>[]) attrs.get(<span class="string">"clients"</span>);</span><br><span class="line"> <span class="keyword">if</span> (clients == <span class="keyword">null</span> || clients.length == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 扫描器设置过滤器且获取需要扫描的基础包集合</span></span><br><span class="line"> scanner.addIncludeFilter(annotationTypeFilter);</span><br><span class="line"> basePackages = getBasePackages(metadata);</span><br><span class="line"> }<span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// clients属性值不为null,则将其clazz路径转为包路径</span></span><br><span class="line"> <span class="keyword">final</span> Set<String> clientClasses = <span class="keyword">new</span> HashSet<>();</span><br><span class="line"> basePackages = <span class="keyword">new</span> HashSet<>();</span><br><span class="line"> <span class="keyword">for</span> (Class<?> clazz : clients) {</span><br><span class="line"> basePackages.add(ClassUtils.getPackageName(clazz));</span><br><span class="line"> clientClasses.add(clazz.getCanonicalName());</span><br><span class="line"> }</span><br><span class="line"> AbstractClassTestingTypeFilter filter = <span class="keyword">new</span> AbstractClassTestingTypeFilter() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">match</span><span class="params">(ClassMetadata metadata)</span> </span>{</span><br><span class="line"> String cleaned = metadata.getClassName().replaceAll(<span class="string">"\\$"</span>, <span class="string">"."</span>);</span><br><span class="line"> <span class="keyword">return</span> clientClasses.contains(cleaned);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> scanner.addIncludeFilter(</span><br><span class="line"> <span class="keyword">new</span> AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3. 扫描基础包,且满足过滤条件下的接口封装成BeanDefinition</span></span><br><span class="line"> <span class="keyword">for</span> (String basePackage : basePackages) {</span><br><span class="line"> <span class="comment">// 找到basePackage下定义的@FeignClient接口列表</span></span><br><span class="line"> Set<BeanDefinition> candidateComponents = scanner</span><br><span class="line"> .findCandidateComponents(basePackage);</span><br><span class="line"> <span class="comment">// 遍历扫描到的bean定义 </span></span><br><span class="line"> <span class="keyword">for</span> (BeanDefinition candidateComponent : candidateComponents) {</span><br><span class="line"> <span class="keyword">if</span> (candidateComponent <span class="keyword">instanceof</span> AnnotatedBeanDefinition) {</span><br><span class="line"> <span class="comment">// 并校验扫描到的bean定义类是一个接口</span></span><br><span class="line"> AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;</span><br><span class="line"> AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();</span><br><span class="line"> Assert.isTrue(annotationMetadata.isInterface(),</span><br><span class="line"> <span class="string">"@FeignClient can only be specified on an interface"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取@FeignClient注解上的各个属性值</span></span><br><span class="line"> Map<String, Object> attributes = annotationMetadata</span><br><span class="line"> .getAnnotationAttributes(</span><br><span class="line"> FeignClient<span class="class">.<span class="keyword">class</span>.<span class="title">getCanonicalName</span>())</span>;</span><br><span class="line"></span><br><span class="line"> String name = getClientName(attributes);</span><br><span class="line"> <span class="comment">// 可以看到这里也注册了一个FeignClient的配置bean</span></span><br><span class="line"> <span class="comment">// 这个方法是创建了一个FeignClientFactoryBean的工厂类,里面保存@FeignClient注解的所有属性值</span></span><br><span class="line"> registerClientConfiguration(registry, name,</span><br><span class="line"> attributes.get(<span class="string">"configuration"</span>));</span><br><span class="line"> <span class="comment">// 注册bean定义到spring中</span></span><br><span class="line"> registerFeignClient(registry, annotationMetadata, attributes);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 注册bean</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">registerFeignClient</span><span class="params">(BeanDefinitionRegistry registry,</span></span></span><br><span class="line"><span class="function"><span class="params"> AnnotationMetadata annotationMetadata, Map<String, Object> attributes)</span> </span>{</span><br><span class="line"> <span class="comment">// 1.获取类名称,也就是本例中的FeignService接口</span></span><br><span class="line"> String className = annotationMetadata.getClassName();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2.BeanDefinitionBuilder的主要作用就是构建一个AbstractBeanDefinition</span></span><br><span class="line"> <span class="comment">// AbstractBeanDefinition类最终被构建成一个BeanDefinitionHolder</span></span><br><span class="line"> <span class="comment">// 然后注册到Spring中</span></span><br><span class="line"> <span class="comment">// 注意:beanDefinition类为FeignClientFactoryBean,故在Spring获取类的时候实际返回的是</span></span><br><span class="line"> <span class="comment">// FeignClientFactoryBean类</span></span><br><span class="line"> BeanDefinitionBuilder definition = BeanDefinitionBuilder</span><br><span class="line"> .genericBeanDefinition(FeignClientFactoryBean<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> validate(attributes);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3.添加FeignClientFactoryBean的属性,</span></span><br><span class="line"> <span class="comment">// 这些属性也都是我们在@FeignClient中定义的属性</span></span><br><span class="line"> definition.addPropertyValue(<span class="string">"url"</span>, getUrl(attributes));</span><br><span class="line"> definition.addPropertyValue(<span class="string">"path"</span>, getPath(attributes));</span><br><span class="line"> String name = getName(attributes);</span><br><span class="line"> definition.addPropertyValue(<span class="string">"name"</span>, name);</span><br><span class="line"> definition.addPropertyValue(<span class="string">"type"</span>, className);</span><br><span class="line"> definition.addPropertyValue(<span class="string">"decode404"</span>, attributes.get(<span class="string">"decode404"</span>));</span><br><span class="line"> definition.addPropertyValue(<span class="string">"fallback"</span>, attributes.get(<span class="string">"fallback"</span>));</span><br><span class="line"> definition.addPropertyValue(<span class="string">"fallbackFactory"</span>, attributes.get(<span class="string">"fallbackFactory"</span>));</span><br><span class="line"> definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 4.设置别名 name就是我们在@FeignClient中定义的name属性</span></span><br><span class="line"> String alias = name + <span class="string">"FeignClient"</span>;</span><br><span class="line"> AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">boolean</span> primary = (Boolean)attributes.get(<span class="string">"primary"</span>); <span class="comment">// has a default, won't be null</span></span><br><span class="line"></span><br><span class="line"> beanDefinition.setPrimary(primary);</span><br><span class="line"></span><br><span class="line"> String qualifier = getQualifier(attributes);</span><br><span class="line"> <span class="keyword">if</span> (StringUtils.hasText(qualifier)) {</span><br><span class="line"> alias = qualifier;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 5.定义BeanDefinitionHolder</span></span><br><span class="line"> BeanDefinitionHolder holder = <span class="keyword">new</span> BeanDefinitionHolder(beanDefinition, className, <span class="keyword">new</span> String[] { alias });</span><br><span class="line"> BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">validate</span><span class="params">(Map<String, Object> attributes)</span> </span>{</span><br><span class="line"> AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);</span><br><span class="line"> <span class="comment">// This blows up if an aliased property is overspecified</span></span><br><span class="line"> <span class="comment">// FIXME annotation.getAliasedString("name", FeignClient.class, null);</span></span><br><span class="line"> Assert.isTrue(</span><br><span class="line"> !annotation.getClass(<span class="string">"fallback"</span>).isInterface(),</span><br><span class="line"> <span class="string">"Fallback class must implement the interface annotated by @FeignClient"</span></span><br><span class="line"> );</span><br><span class="line"> Assert.isTrue(</span><br><span class="line"> !annotation.getClass(<span class="string">"fallbackFactory"</span>).isInterface(),</span><br><span class="line"> <span class="string">"Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient"</span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">registerClientConfiguration</span><span class="params">(BeanDefinitionRegistry registry, Object name,</span></span></span><br><span class="line"><span class="function"><span class="params"> Object configuration)</span> </span>{</span><br><span class="line"> <span class="comment">// 创建一个使用FeignClientSpecification构建的BeanDefinitionBuilder的类</span></span><br><span class="line"> BeanDefinitionBuilder builder = BeanDefinitionBuilder</span><br><span class="line"> .genericBeanDefinition(FeignClientSpecification<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> builder.addConstructorArgValue(name);</span><br><span class="line"> builder.addConstructorArgValue(configuration);</span><br><span class="line"> registry.registerBeanDefinition(</span><br><span class="line"> name + <span class="string">"."</span> + FeignClientSpecification<span class="class">.<span class="keyword">class</span>.<span class="title">getSimpleName</span>(),</span></span><br><span class="line"><span class="class"> <span class="title">builder</span>.<span class="title">getBeanDefinition</span>())</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ......</span><br><span class="line"> ......</span><br><span class="line"> ......</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这里做了两件事情:</p><ol><li>将EnableFeignClients注解对应的配置属性注入;</li><li>将FeignClient注解对应的属性注入。</li></ol><p>生成FeignClient对应的bean,注入到Spring 的IOC容器。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>在我们查看处理@EnableFeignClients和@FeignClient注解的地方,最后调用<code>registerFeignClient()</code> 会构造一个:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">BeanDefinitionBuilder definition = BeanDefinitionBuilder</span><br><span class="line"> .genericBeanDefinition(FeignClientFactoryBean<span class="class">.<span class="keyword">class</span>)</span>;</span><br></pre></td></tr></table></figure><p>所以我们后续会重点查看<code>FeignClientFactoryBean</code> 这个类。</p>]]></content>
<summary type="html">
<h1 id="Feign-源码一:源码初探,通过Demo-Debug-Feign源码"><a href="#Feign-源码一:源码初探,通过Demo-Debug-Feign源码" class="headerlink" title="[Feign 源码一:源码初探,通过Demo Debug Feign源码]"></a>[Feign 源码一:源码初探,通过Demo Debug Feign源码]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是<code>DiscoveryEnableNIWSServerList</code>,同时在<code>DynamicServerListLoadBalancer</code>中会调用<code>PollingServerListUpdater</code> 进行定时更新Eureka注册表信息到<code>BaseLoadBalancer</code>中,默认30s调度一次。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Feign" scheme="http://yoursite.com/tags/Feign/"/>
</entry>
<entry>
<title>Ribbon源码五:Ribbon源码解读汇总篇</title>
<link href="http://yoursite.com/2020/01/20/[Ribbon%E6%BA%90%E7%A0%81%E4%BA%94%EF%BC%9ARibbon%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB%E6%B1%87%E6%80%BB%E7%AF%87~]/"/>
<id>http://yoursite.com/2020/01/20/[Ribbon%E6%BA%90%E7%A0%81%E4%BA%94%EF%BC%9ARibbon%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB%E6%B1%87%E6%80%BB%E7%AF%87~]/</id>
<published>2020-01-20T14:05:37.000Z</published>
<updated>2020-03-01T15:20:10.966Z</updated>
<content type="html"><![CDATA[<h1 id="Ribbon源码五:Ribbon源码解读汇总篇"><a href="#Ribbon源码五:Ribbon源码解读汇总篇" class="headerlink" title="[Ribbon源码五:Ribbon源码解读汇总篇~]"></a>[Ribbon源码五:Ribbon源码解读汇总篇~]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="想说的话"><a href="#想说的话" class="headerlink" title="想说的话"></a>想说的话</h4><p>Ribbon比较小巧,这里是直接 读的spring cloud 内嵌封装的版本,里面的各种configuration确实有点绕,不过看看第三讲Ribbon初始化的过程总结图就会清晰很多。</p><p>紧接着会继续整理学习Feign源码相关的,敬请期待。</p><a id="more"></a><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>总结分为两个部分,一个是Riboon执行整体流程图,还一个是Ribbon初始化流程图。</p><p>Ribbon整体流程图:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200109141619258-1289351388.png" alt="08_Ribbon整体流程图.jpg"></p><p>Ribbon初始化流程图:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200109141622230-1896405065.png" alt="02_Ribbon初始化流程图.jpg"></p><h3 id="常用配置"><a href="#常用配置" class="headerlink" title="常用配置"></a>常用配置</h3><p>常用配置</p><ol><li><p>禁用 Eureka<br> 当我们在 RestTemplate 上添加 @LoadBalanced 注解后,就可以用服务名称来调用接口了,当有多个服务的时候,还能做负载均衡。</p><p>这是因为 Eureka 中的服务信息已经被拉取到了客户端本地,如果我们不想和 Eureka 集成,可以通过下面的配置方法将其禁用。<br> <code>xml #禁用 Eureka ribbon.eureka.enabled=false</code></p><p>当我们禁用了 Eureka 之后,就不能使用服务名称去调用接口了,必须指定服务地址。</p></li><li><p>配置接口地址列表<br> 上面我们讲了可以禁用 Eureka,禁用之后就需要手动配置调用的服务地址了,配置如下:<br> <code>xml #禁用 Eureka 后手动配置服务地址 ribbon-config-demo.ribbon.listOfServers=localhost:8081,localhost:8083</code></p><p>这个配置是针对具体服务的,前缀就是服务名称,配置完之后就可以和之前一样使用服务名称来调用接口了。</p></li><li><p>配置负载均衡策略<br> Ribbon 默认的策略是轮询,从我们前面讲解的例子输出的结果就可以看出来,Ribbon 中提供了很多的策略,这个在后面会进行讲解。我们通过配置可以指定服务使用哪种策略来进行负载操作。</p></li><li><p>超时时间<br> Ribbon 中有两种和时间相关的设置,分别是请求连接的超时时间和请求处理的超时时间,设置规则如下:<br> <code>xml # 请求连接的超时时间 ribbon.ConnectTimeout=2000 # 请求处理的超时时间 ribbon.ReadTimeout=5000</code></p><p>也可以为每个Ribbon客户端设置不同的超时时间, 通过服务名称进行指定:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xml ribbon-config-demo.ribbon.ConnectTimeout=2000 ribbon-config-demo.ribbon.ReadTimeout=5000</span><br></pre></td></tr></table></figure></li><li><p>并发参数<br> <code>xml #最大连接数 ribbon.MaxTotalConnections=500 #每个host最大连接数 ribbon.MaxConnectionsPerHost=500</code></p></li><li><p>重试和负载均衡相关配置<br> ```xml<br> # 对当前实例的重试次数<br> ribbon.maxAutoRetries=1<br> # 切换实例的重试次数<br> ribbon.maxAutoRetriesNextServer=3<br> # 对所有操作请求都进行重试<br> ribbon.okToRetryOnAllOperations=true<br> # 对Http响应码进行重试<br> ribbon.retryableStatusCodes=500,404,502</p><p># 负载Rule选择<br> ribbon-config-demo.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.BestAvailableRule<br> ```</p></li></ol>]]></content>
<summary type="html">
<h1 id="Ribbon源码五:Ribbon源码解读汇总篇"><a href="#Ribbon源码五:Ribbon源码解读汇总篇" class="headerlink" title="[Ribbon源码五:Ribbon源码解读汇总篇~]"></a>[Ribbon源码五:Ribbon源码解读汇总篇~]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="想说的话"><a href="#想说的话" class="headerlink" title="想说的话"></a>想说的话</h4><p>Ribbon比较小巧,这里是直接 读的spring cloud 内嵌封装的版本,里面的各种configuration确实有点绕,不过看看第三讲Ribbon初始化的过程总结图就会清晰很多。</p>
<p>紧接着会继续整理学习Feign源码相关的,敬请期待。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Ribbon" scheme="http://yoursite.com/tags/Ribbon/"/>
</entry>
<entry>
<title>Ribbon 源码四:进一步探究Ribbon的IRule和IPing</title>
<link href="http://yoursite.com/2020/01/19/[Ribbon%20%E6%BA%90%E7%A0%81%E5%9B%9B%EF%BC%9A%E8%BF%9B%E4%B8%80%E6%AD%A5%E6%8E%A2%E7%A9%B6Ribbon%E7%9A%84IRule%E5%92%8CIPing]/"/>
<id>http://yoursite.com/2020/01/19/[Ribbon%20%E6%BA%90%E7%A0%81%E5%9B%9B%EF%BC%9A%E8%BF%9B%E4%B8%80%E6%AD%A5%E6%8E%A2%E7%A9%B6Ribbon%E7%9A%84IRule%E5%92%8CIPing]/</id>
<published>2020-01-19T14:05:37.000Z</published>
<updated>2020-03-01T15:19:30.348Z</updated>
<content type="html"><![CDATA[<h1 id="Ribbon-源码四:进一步探究Ribbon的IRule和IPing"><a href="#Ribbon-源码四:进一步探究Ribbon的IRule和IPing" class="headerlink" title="[Ribbon 源码四:进一步探究Ribbon的IRule和IPing]"></a>[Ribbon 源码四:进一步探究Ribbon的IRule和IPing]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是<code>DiscoveryEnableNIWSServerList</code>,同时在<code>DynamicServerListLoadBalancer</code>中会调用<code>PollingServerListUpdater</code> 进行定时更新Eureka注册表信息到<code>BaseLoadBalancer</code>中,默认30s调度一次。</p><a id="more"></a><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p>我们知道Ribbon主要是由3个组件组成的:</p><ol><li>ILoadBalancer</li><li>IRule</li><li>IPing</li></ol><p>其中<code>ILoadBalancer</code>前面我们已经分析过了,接下来我们一起看看<code>IRule</code>和<code>IPing</code>中的具体实现。</p><p><strong>目录如下:</strong></p><ol><li>负载均衡默认Server选择逻辑</li><li>Ribbon实际执行http请求逻辑</li><li>Ribbon中ping机制原理</li><li>Ribbon中其他IRule负载算法初探</li></ol><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h4 id="负载均衡默认Server选择逻辑"><a href="#负载均衡默认Server选择逻辑" class="headerlink" title="负载均衡默认Server选择逻辑"></a>负载均衡默认Server选择逻辑</h4><p>还记得我们上一讲说过,在Ribbon初始化过程中,默认的<code>IRule</code>为<code>ZoneAvoidanceRule</code>,这里我们可以通过debug看看,从<code>RibbonLoadBalancerClient.getServer()</code> 一路往下跟,这里直接看debug结果:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200108133933694-1375783725.png" alt="image.png"></p><p>然后我们继续跟<code>ZoneAvoidanceRule.choose()</code> 方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {</span><br><span class="line"> </span><br><span class="line"> /**</span><br><span class="line"> * Method that provides an instance of {@link AbstractServerPredicate} to be used by this class.</span><br><span class="line"> * </span><br><span class="line"> */</span><br><span class="line"> public abstract AbstractServerPredicate getPredicate();</span><br><span class="line"> </span><br><span class="line"> /**</span><br><span class="line"> * Get a server by calling {@link AbstractServerPredicate#chooseRandomlyAfterFiltering(java.util.List, Object)}.</span><br><span class="line"> * The performance for this method is O(n) where n is number of servers to be filtered.</span><br><span class="line"> */</span><br><span class="line"> @Override</span><br><span class="line"> public Server choose(Object key) {</span><br><span class="line"> ILoadBalancer lb = getLoadBalancer();</span><br><span class="line"> Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);</span><br><span class="line"> if (server.isPresent()) {</span><br><span class="line"> return server.get();</span><br><span class="line"> } else {</span><br><span class="line"> return null;</span><br><span class="line"> } </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里是调用的<code>ZoneAvoidanceRule</code>的父类中的<code>choose()</code>方法,首先是拿到对应的<code>ILoadBalancer</code>,然后拿到对应的serverList数据,接着调用<code>chooseRoundRobinAfterFiltering()</code>方法,继续往后跟:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">public abstract class AbstractServerPredicate implements Predicate<PredicateKey> {</span><br><span class="line"></span><br><span class="line"> public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {</span><br><span class="line"> List<Server> eligible = getEligibleServers(servers, loadBalancerKey);</span><br><span class="line"> if (eligible.size() == 0) {</span><br><span class="line"> return Optional.absent();</span><br><span class="line"> }</span><br><span class="line"> return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private int incrementAndGetModulo(int modulo) {</span><br><span class="line"> for (;;) {</span><br><span class="line"> int current = nextIndex.get();</span><br><span class="line"> int next = (current + 1) % modulo;</span><br><span class="line"> if (nextIndex.compareAndSet(current, next) && current < modulo)</span><br><span class="line"> return current;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>到了这里可以看到<code>incrementAndGetModulo()</code>方法就是处理serverList轮询的算法,这个和<code>RoudRobinRule</code>中采用的是一样的算法,这个算法大家可以品一品,我这里也会画个图来说明下:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200108133935714-603689733.png" alt="06_Ribbon轮询算法图解.jpg"></p><p>看了图=中的例子估计大家都会明白了,这个算法就是依次轮询。这个算法写的很精简。</p><h4 id="Ribbon实际执行http请求逻辑"><a href="#Ribbon实际执行http请求逻辑" class="headerlink" title="Ribbon实际执行http请求逻辑"></a>Ribbon实际执行http请求逻辑</h4><p>我们上面知道,我们按照轮询的方式从serverList取到一个server后,那么怎么把之前原有的类似于:<code>http://ServerA/sayHello/wangmeng</code>中的ServerA给替换成请求的ip数据呢?</p><p>接着我们继续看<code>RibbonLoadBalancerClient.execute()</code>方法,这个里面<code>request.apply()</code>会做一个serverName的替换逻辑。</p><p>最后可以一步步跟到<code>RibbonLoadBalancerClient.reconstructURI()</code>,这个方法是把请求自带的getURI方法给替换了,我们最后查看<code>context.reconstructURIWithServer()</code> 方法,debug结果如图,这个里面会一步步把对应的请求url给拼接起来:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200108133940333-1790437062.png" alt="image.png"></p><h4 id="Ribbon中ping机制原理"><a href="#Ribbon中ping机制原理" class="headerlink" title="Ribbon中ping机制原理"></a>Ribbon中ping机制原理</h4><p>我们知道 Ribbon还有一个重要的组件就是ping机制,通过上一讲Ribbon的初始化我们知道,默认的IPing实现类为:<code>NIWSDiscoveryPing</code>,我们可以查看其中的<code>isAlive()</code>方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">public class NIWSDiscoveryPing extends AbstractLoadBalancerPing {</span><br><span class="line"> </span><br><span class="line"> BaseLoadBalancer lb = null; </span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"> public NIWSDiscoveryPing() {</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public BaseLoadBalancer getLb() {</span><br><span class="line"> return lb;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * Non IPing interface method - only set this if you care about the "newServers Feature"</span><br><span class="line"> * @param lb</span><br><span class="line"> */</span><br><span class="line"> public void setLb(BaseLoadBalancer lb) {</span><br><span class="line"> this.lb = lb;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public boolean isAlive(Server server) {</span><br><span class="line"> boolean isAlive = true;</span><br><span class="line"> if (server!=null && server instanceof DiscoveryEnabledServer){</span><br><span class="line"> DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server; </span><br><span class="line"> InstanceInfo instanceInfo = dServer.getInstanceInfo();</span><br><span class="line"> if (instanceInfo!=null){ </span><br><span class="line"> InstanceStatus status = instanceInfo.getStatus();</span><br><span class="line"> if (status!=null){</span><br><span class="line"> isAlive = status.equals(InstanceStatus.UP);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return isAlive;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void initWithNiwsConfig(</span><br><span class="line"> IClientConfig clientConfig) {</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里就是获取到<code>DiscoveryEnabledServer</code>对应的注册信息是否为<code>UP</code>状态。那么 既然有个ping的方法,肯定会有方法进行调度的。</p><p>我们可以查看<code>isAlive()</code>调用即可以找到调度的地方。<br> 在<code>BaseLoadBalancer</code>构造函数中会调用<code>setupPingTask()</code>方法,进行调度:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">protected int pingIntervalSeconds = 10;</span><br><span class="line"></span><br><span class="line">void setupPingTask() {</span><br><span class="line"> if (canSkipPing()) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> if (lbTimer != null) {</span><br><span class="line"> lbTimer.cancel();</span><br><span class="line"> }</span><br><span class="line"> lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,</span><br><span class="line"> true);</span><br><span class="line"> lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);</span><br><span class="line"> forceQuickPing();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里<code>pingIntervalSeconds</code>在<code>BaseLoadBalancer</code>中定义的是10s,但是在<code>initWithConfig()</code>方法中,通过传入的时间覆盖了原本的10s,这里实际的默认时间是30s。如下代码:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200108133943619-1942268787.png" alt="image.png"></p><p>我们也可以通过debug来看看:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200108133949075-879722129.png" alt="image.png"></p><p>可能大家好奇为什么要单独截图来说这个事,其实是因为网上好多博客讲解都是错的,都写的是ping默认调度时间为10s,想必他们都是没有真正debug过吧。</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200108133951750-84881497.png" alt="image.png"></p><p>还是那句话,源码出真知。</p><h4 id="Ribbon中其他IRule负载算法初探"><a href="#Ribbon中其他IRule负载算法初探" class="headerlink" title="Ribbon中其他IRule负载算法初探"></a>Ribbon中其他IRule负载算法初探</h4><ol><li><p>RoundRobinRule:系统内置的默认负载均衡规范,直接round robin轮询,从一堆server list中,不断的轮询选择出来一个server,每个server平摊到的这个请求,基本上是平均的</p><p>这个算法,说白了是轮询,就是一台接着一台去请求,是按照顺序来的</p></li><li><p>AvailabilityFilteringRule:这个rule就是会考察服务器的可用性</p><p>如果3次连接失败,就会等待30秒后再次访问;如果不断失败,那么等待时间会不断变长<br> 如果某个服务器的并发请求太高了,那么会绕过去,不再访问</p><p>这里先用round robin算法,轮询依次选择一台server,如果判断这个server是存活的可用的,如果这台server是不可以访问的,那么就用round robin算法再次选择下一台server,依次循环往复,10次。</p></li><li><p>WeightedResponseTimeRule:带着权重的,每个服务器可以有权重,权重越高优先访问,如果某个服务器响应时间比较长,那么权重就会降低,减少访问</p></li><li><p>ZoneAvoidanceRule:根据机房和服务器来进行负载均衡,说白了,就是机房的意思,看了源码就是知道了,这个就是所谓的spring cloud ribbon环境中的默认的Rule</p></li><li><p>BestAvailableRule:忽略那些请求失败的服务器,然后尽量找并发比较低的服务器来请求</p></li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>到了这里 Ribbon相关的就结束了,对于Ribbon注册表拉取及更新逻辑这里也梳理下,这里如果Ribbon保存的注册表信息有宕机的情况,这里最多4分钟才能感知到,所以spring cloud还有一个服务熔断的机制,这个后面也会讲到。</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200108133953308-926302776.png" alt="07_Ribbon默认IRule可能存在的问题.jpg"></p>]]></content>
<summary type="html">
<h1 id="Ribbon-源码四:进一步探究Ribbon的IRule和IPing"><a href="#Ribbon-源码四:进一步探究Ribbon的IRule和IPing" class="headerlink" title="[Ribbon 源码四:进一步探究Ribbon的IRule和IPing]"></a>[Ribbon 源码四:进一步探究Ribbon的IRule和IPing]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是<code>DiscoveryEnableNIWSServerList</code>,同时在<code>DynamicServerListLoadBalancer</code>中会调用<code>PollingServerListUpdater</code> 进行定时更新Eureka注册表信息到<code>BaseLoadBalancer</code>中,默认30s调度一次。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Ribbon" scheme="http://yoursite.com/tags/Ribbon/"/>
</entry>
<entry>
<title>Ribbon 源码三:Ribbon与Eureka整合原理分析</title>
<link href="http://yoursite.com/2020/01/17/[Ribbon%20%E6%BA%90%E7%A0%81%E4%B8%89%EF%BC%9ARibbon%E4%B8%8EEureka%E6%95%B4%E5%90%88%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90]/"/>
<id>http://yoursite.com/2020/01/17/[Ribbon%20%E6%BA%90%E7%A0%81%E4%B8%89%EF%BC%9ARibbon%E4%B8%8EEureka%E6%95%B4%E5%90%88%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90]/</id>
<published>2020-01-17T14:05:37.000Z</published>
<updated>2020-03-01T15:16:43.706Z</updated>
<content type="html"><![CDATA[<h1 id="Ribbon-源码三:Ribbon与Eureka整合原理分析"><a href="#Ribbon-源码三:Ribbon与Eureka整合原理分析" class="headerlink" title="[Ribbon 源码三:Ribbon与Eureka整合原理分析]"></a>[Ribbon 源码三:Ribbon与Eureka整合原理分析]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一篇讲了Ribbon的初始化过程,从<code>LoadBalancerAutoConfiguration</code> 到<code>RibbonAutoConfiguration</code> 再到<code>RibbonClientConfiguration</code>,我们找到了<code>ILoadBalancer</code>默认初始化的对象等。</p><a id="more"></a><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p>这一讲我们会进一步往下探究Ribbon和Eureka是如何结合的。</p><p>通过上一讲<code>ILoadBalancer</code> 我们已经可以拿到一个服务所有的服务节点信息了,这里面是怎么把服务的名称转化为对应的具体host请求信息的呢?</p><p>通过这一讲 我们来一探究竟</p><p><strong>目录如下:</strong></p><ol><li>EurekaClientAutoConfiguration.getLoadBalancer()回顾</li><li>再次梳理Ribbon初始化过程</li><li>ServerList实现类初始化过程</li><li>getUpdatedListOfServers()获取注册表列表分析</li><li>ribbon如何更新自己保存的注册表信息?</li></ol><h3 id="源码阅读"><a href="#源码阅读" class="headerlink" title="源码阅读"></a>源码阅读</h3><h4 id="EurekaClientAutoConfiguration-getLoadBalancer-回顾"><a href="#EurekaClientAutoConfiguration-getLoadBalancer-回顾" class="headerlink" title="EurekaClientAutoConfiguration.getLoadBalancer()回顾"></a>EurekaClientAutoConfiguration.getLoadBalancer()回顾</h4><p>上一讲我们已经深入的讲解过<code>getLoadBalancer()</code> 方法的实现,每个serviceName都对应一个自己的SpringContext上下文信息,然后通过<code>ILoadBalancer.class</code>从上下文信息中获取默认的LoadBalancer:<code>ZoneAwareLoadBalancer</code>, 我们看下这个类的构造函数:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,</span><br><span class="line"> IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,</span><br><span class="line"> ServerListUpdater serverListUpdater) {</span><br><span class="line"> super(clientConfig, rule, ping, serverList, filter, serverListUpdater);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>继续跟父类<code>DynamicServerListLoadBalancer</code>的初始化方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line">public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {</span><br><span class="line"> volatile ServerList<T> serverListImpl;</span><br><span class="line"></span><br><span class="line"> volatile ServerListFilter<T> filter;</span><br><span class="line"></span><br><span class="line"> public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,</span><br><span class="line"> ServerList<T> serverList, ServerListFilter<T> filter,</span><br><span class="line"> ServerListUpdater serverListUpdater) {</span><br><span class="line"> super(clientConfig, rule, ping);</span><br><span class="line"> this.serverListImpl = serverList;</span><br><span class="line"> this.filter = filter;</span><br><span class="line"> this.serverListUpdater = serverListUpdater;</span><br><span class="line"> if (filter instanceof AbstractServerListFilter) {</span><br><span class="line"> ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());</span><br><span class="line"> }</span><br><span class="line"> restOfInit(clientConfig);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> void restOfInit(IClientConfig clientConfig) {</span><br><span class="line"> boolean primeConnection = this.isEnablePrimingConnections();</span><br><span class="line"> // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()</span><br><span class="line"> this.setEnablePrimingConnections(false);</span><br><span class="line"> enableAndInitLearnNewServersFeature();</span><br><span class="line"></span><br><span class="line"> updateListOfServers();</span><br><span class="line"> if (primeConnection && this.getPrimeConnections() != null) {</span><br><span class="line"> this.getPrimeConnections()</span><br><span class="line"> .primeConnections(getReachableServers());</span><br><span class="line"> }</span><br><span class="line"> this.setEnablePrimingConnections(primeConnection);</span><br><span class="line"> LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @VisibleForTesting</span><br><span class="line"> public void updateListOfServers() {</span><br><span class="line"> List<T> servers = new ArrayList<T>();</span><br><span class="line"> if (serverListImpl != null) {</span><br><span class="line"> servers = serverListImpl.getUpdatedListOfServers();</span><br><span class="line"> LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",</span><br><span class="line"> getIdentifier(), servers);</span><br><span class="line"></span><br><span class="line"> if (filter != null) {</span><br><span class="line"> servers = filter.getFilteredListOfServers(servers);</span><br><span class="line"> LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",</span><br><span class="line"> getIdentifier(), servers);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> updateAllServerList(servers);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>构造方法中有个<code>restOfInit()</code>方法,进去后又会有<code>updateListOfServers()</code> 方法,看方法名就知道这个肯定是和server注册表相关的,继续往后看,<code>servers = serverListImpl.getUpdatedListOfServers();</code>,这里直接调用<code>getUpdatedListOfServers()</code>就获取到了所有的注册表信息。</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200107094239845-1846816966.jpg" alt="0.jpeg"></p><p>可以看到<code>ServerList</code>有四个实现类,这个到底是该调用哪个实现类的<code>getUpdatedListOfServers()</code>方法呢?接着往下看。</p><h4 id="再次梳理Ribbon初始化过程"><a href="#再次梳理Ribbon初始化过程" class="headerlink" title="再次梳理Ribbon初始化过程"></a>再次梳理Ribbon初始化过程</h4><p>第二讲我们已经见过Ribbon的初始化过程,并画了图整理,这里针对于之前的图再更新一下:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200107094241710-1394321382.png" alt="img"></p><p>这里主要是增加了<code>RibbonEurekaAutoConfiguration</code>和<code>EurekaRibbonClientConfiguration</code>两个配置类的初始化。</p><h4 id="ServerList实现类初始化过程"><a href="#ServerList实现类初始化过程" class="headerlink" title="ServerList实现类初始化过程"></a>ServerList实现类初始化过程</h4><p>上面已经梳理过 <code>Ribbon</code>初始化的过程,其中在<code>EurekaRibbonClientConfiguration</code> 会初始化<code>RibbonServerList</code>,代码如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">@Configuration</span><br><span class="line"> public class EurekaRibbonClientConfiguration {</span><br><span class="line"> @Bean</span><br><span class="line"> @ConditionalOnMissingBean</span><br><span class="line"> public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {</span><br><span class="line"> if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {</span><br><span class="line"> return this.propertiesFactory.get(ServerList.class, config, serviceId);</span><br><span class="line"> }</span><br><span class="line"> DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(</span><br><span class="line"> config, eurekaClientProvider);</span><br><span class="line"> DomainExtractingServerList serverList = new DomainExtractingServerList(</span><br><span class="line"> discoveryServerList, config, this.approximateZoneFromHostname);</span><br><span class="line"> return serverList;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里实际的<code>ServerList</code>实际就是<code>DiscoveryEnabledNIWSServerList</code>,我们看下这个类:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">public class DiscoveryEnabledNIWSServerList extends AbstractServerList<DiscoveryEnabledServer>{</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public abstract class AbstractServerList<T extends Server> implements ServerList<T>, IClientConfigAware {</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>所以可以看出来<code>ServerList</code> 实际就是在这里进行初始化的,上面那个<code>serverListImpl.getUpdatedListOfServers();</code>即为调用<code>DiscoveryEnabledNIWSServerList.getUpdatedListOfServers()</code> 方法了,继续往下看。</p><h4 id="getUpdatedListOfServers-获取注册表分析"><a href="#getUpdatedListOfServers-获取注册表分析" class="headerlink" title="getUpdatedListOfServers()获取注册表分析"></a>getUpdatedListOfServers()获取注册表分析</h4><p>直接看<code>DiscoveryEnabledNIWSServerList.getUpdatedListOfServers()</code>源代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">public List<DiscoveryEnabledServer> getUpdatedListOfServers(){</span><br><span class="line"> return obtainServersViaDiscovery();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {</span><br><span class="line"> List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();</span><br><span class="line"></span><br><span class="line"> if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {</span><br><span class="line"> logger.warn("EurekaClient has not been initialized yet, returning an empty list");</span><br><span class="line"> return new ArrayList<DiscoveryEnabledServer>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> EurekaClient eurekaClient = eurekaClientProvider.get();</span><br><span class="line"> if (vipAddresses!=null){</span><br><span class="line"> for (String vipAddress : vipAddresses.split(",")) {</span><br><span class="line"> // if targetRegion is null, it will be interpreted as the same region of client</span><br><span class="line"> List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);</span><br><span class="line"> for (InstanceInfo ii : listOfInstanceInfo) {</span><br><span class="line"> if (ii.getStatus().equals(InstanceStatus.UP)) {</span><br><span class="line"></span><br><span class="line"> if(shouldUseOverridePort){</span><br><span class="line"> if(logger.isDebugEnabled()){</span><br><span class="line"> logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // copy is necessary since the InstanceInfo builder just uses the original reference,</span><br><span class="line"> // and we don't want to corrupt the global eureka copy of the object which may be</span><br><span class="line"> // used by other clients in our system</span><br><span class="line"> InstanceInfo copy = new InstanceInfo(ii);</span><br><span class="line"></span><br><span class="line"> if(isSecure){</span><br><span class="line"> ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build();</span><br><span class="line"> }else{</span><br><span class="line"> ii = new InstanceInfo.Builder(copy).setPort(overridePort).build();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, isSecure, shouldUseIpAddr);</span><br><span class="line"> des.setZone(DiscoveryClient.getZone(ii));</span><br><span class="line"> serverList.add(des);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> if (serverList.size()>0 && prioritizeVipAddressBasedServers){</span><br><span class="line"> break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return serverList;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看到这里代码就已经很明显了,我们来解读下这段代码:</p><ol><li>通过eurekaClientProvider获取对应EurekaClient</li><li>通过vipAdress(实际就是serviceName)获取对应注册表集合信息</li><li>将注册信息组装成<code>DiscoveryEnabledServer</code>列表</li></ol><p>再回到<code>DynamicServerListLoadBalancer.updateListOfServers()</code> 中,这里获取到对应的DiscoveryEnabledServer list后调用<code>updateAllServerList()</code>方法,一路跟踪这里最终会调用<code>BaseLoadBalancer.setServersList()</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line">public class BaseLoadBalancer extends AbstractLoadBalancer implements</span><br><span class="line"> PrimeConnections.PrimeConnectionListener, IClientConfigAware {</span><br><span class="line"></span><br><span class="line"> @Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)</span><br><span class="line"> protected volatile List<Server> allServerList = Collections</span><br><span class="line"> .synchronizedList(new ArrayList<Server>());</span><br><span class="line"> </span><br><span class="line"> public void setServersList(List lsrv) {</span><br><span class="line"> Lock writeLock = allServerLock.writeLock();</span><br><span class="line"> logger.debug("LoadBalancer [{}]: clearing server list (SET op)", name);</span><br><span class="line"> </span><br><span class="line"> ArrayList<Server> newServers = new ArrayList<Server>();</span><br><span class="line"> writeLock.lock();</span><br><span class="line"> try {</span><br><span class="line"> ArrayList<Server> allServers = new ArrayList<Server>();</span><br><span class="line"> for (Object server : lsrv) {</span><br><span class="line"> if (server == null) {</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (server instanceof String) {</span><br><span class="line"> server = new Server((String) server);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (server instanceof Server) {</span><br><span class="line"> logger.debug("LoadBalancer [{}]: addServer [{}]", name, ((Server) server).getId());</span><br><span class="line"> allServers.add((Server) server);</span><br><span class="line"> } else {</span><br><span class="line"> throw new IllegalArgumentException(</span><br><span class="line"> "Type String or Server expected, instead found:"</span><br><span class="line"> + server.getClass());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> boolean listChanged = false;</span><br><span class="line"> if (!allServerList.equals(allServers)) {</span><br><span class="line"> listChanged = true;</span><br><span class="line"> if (changeListeners != null && changeListeners.size() > 0) {</span><br><span class="line"> List<Server> oldList = ImmutableList.copyOf(allServerList);</span><br><span class="line"> List<Server> newList = ImmutableList.copyOf(allServers); </span><br><span class="line"> for (ServerListChangeListener l: changeListeners) {</span><br><span class="line"> try {</span><br><span class="line"> l.serverListChanged(oldList, newList);</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> logger.error("LoadBalancer [{}]: Error invoking server list change listener", name, e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> if (isEnablePrimingConnections()) {</span><br><span class="line"> for (Server server : allServers) {</span><br><span class="line"> if (!allServerList.contains(server)) {</span><br><span class="line"> server.setReadyToServe(false);</span><br><span class="line"> newServers.add((Server) server);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> if (primeConnections != null) {</span><br><span class="line"> primeConnections.primeConnectionsAsync(newServers, this);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // This will reset readyToServe flag to true on all servers</span><br><span class="line"> // regardless whether</span><br><span class="line"> // previous priming connections are success or not</span><br><span class="line"> allServerList = allServers;</span><br><span class="line"> if (canSkipPing()) {</span><br><span class="line"> for (Server s : allServerList) {</span><br><span class="line"> s.setAlive(true);</span><br><span class="line"> }</span><br><span class="line"> upServerList = allServerList;</span><br><span class="line"> } else if (listChanged) {</span><br><span class="line"> forceQuickPing();</span><br><span class="line"> }</span><br><span class="line"> } finally {</span><br><span class="line"> writeLock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个过程最后用一张图总结为:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200107094242501-1185588703.png" alt="img"></p><h4 id="ribbon如何更新自己保存的注册表信息?"><a href="#ribbon如何更新自己保存的注册表信息?" class="headerlink" title="ribbon如何更新自己保存的注册表信息?"></a>ribbon如何更新自己保存的注册表信息?</h4><p>上面已经讲了 Ribbon是如何通过serviceName拉取到注册表的,我们知道EurekaClient默认是30s拉取一次注册表信息的,因为Ribbon要关联注册表信息,那么Ribbon该如何更新自己存储的注册表信息呢?</p><p>继续回到<code>DynamicSeverListLoadBalancer.restOfInit()</code>方法中:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {</span><br><span class="line"></span><br><span class="line"> protected volatile ServerListUpdater serverListUpdater;</span><br><span class="line"></span><br><span class="line"> void restOfInit(IClientConfig clientConfig) {</span><br><span class="line"> boolean primeConnection = this.isEnablePrimingConnections();</span><br><span class="line"> // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()</span><br><span class="line"> this.setEnablePrimingConnections(false);</span><br><span class="line"> enableAndInitLearnNewServersFeature();</span><br><span class="line"></span><br><span class="line"> updateListOfServers();</span><br><span class="line"> if (primeConnection && this.getPrimeConnections() != null) {</span><br><span class="line"> this.getPrimeConnections()</span><br><span class="line"> .primeConnections(getReachableServers());</span><br><span class="line"> }</span><br><span class="line"> this.setEnablePrimingConnections(primeConnection);</span><br><span class="line"> LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void enableAndInitLearnNewServersFeature() {</span><br><span class="line"> LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());</span><br><span class="line"> serverListUpdater.start(updateAction);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>重点查看<code>enableAndInitLearnNewServersFeature()</code>方法,从名字我们就可以看出来这意思为激活和初始化学习新服务的功能,这里实际上就启动<code>serverListUpdater</code>中的一个线程。</p><p>在最上面Ribbon初始化的过程中我们知道,在<code>RibbonClientConfiguration</code>中默认初始化的<code>ServerListUpdater</code>为 <code>PollingServreListUpdater</code>,我们继续跟这个类的start方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">public synchronized void start(final UpdateAction updateAction) {</span><br><span class="line"> if (isActive.compareAndSet(false, true)) {</span><br><span class="line"> final Runnable wrapperRunnable = new Runnable() {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> if (!isActive.get()) {</span><br><span class="line"> if (scheduledFuture != null) {</span><br><span class="line"> scheduledFuture.cancel(true);</span><br><span class="line"> }</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> try {</span><br><span class="line"> updateAction.doUpdate();</span><br><span class="line"> lastUpdated = System.currentTimeMillis();</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> logger.warn("Failed one update cycle", e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(</span><br><span class="line"> wrapperRunnable,</span><br><span class="line"> initialDelayMs,</span><br><span class="line"> refreshIntervalMs,</span><br><span class="line"> TimeUnit.MILLISECONDS</span><br><span class="line"> );</span><br><span class="line"> } else {</span><br><span class="line"> logger.info("Already active, no-op");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里只要是执行<code>updateAction.doUpdate();</code>,然后后面启动了一个调度任务,默认30s执行一次。</p><p>继续往后跟<code>doUpdate()</code>方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {</span><br><span class="line"> protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {</span><br><span class="line"> @Override</span><br><span class="line"> public void doUpdate() {</span><br><span class="line"> updateListOfServers();</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里又调用了之前通过serviceName获取对应注册服务列表的方法了。</p><p>总结到一张图如下:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200107094242964-1147020595.png" alt="注册表服务"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>本文主要是重新梳理了Ribbon的初始化过程,主要是几个Configure初始化的过程,然后是Ribbon与Eureka的整合,这里也涉及到了注册表的更新逻辑。</p><p>看到这里真是被Spring的各种AutoConfigure绕晕了,哈哈,但是最后分析完 还是觉得挺清晰的,对于复杂的业务画张流程图还挺容易理解的。</p>]]></content>
<summary type="html">
<h1 id="Ribbon-源码三:Ribbon与Eureka整合原理分析"><a href="#Ribbon-源码三:Ribbon与Eureka整合原理分析" class="headerlink" title="[Ribbon 源码三:Ribbon与Eureka整合原理分析]"></a>[Ribbon 源码三:Ribbon与Eureka整合原理分析]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一篇讲了Ribbon的初始化过程,从<code>LoadBalancerAutoConfiguration</code> 到<code>RibbonAutoConfiguration</code> 再到<code>RibbonClientConfiguration</code>,我们找到了<code>ILoadBalancer</code>默认初始化的对象等。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Ribbon" scheme="http://yoursite.com/tags/Ribbon/"/>
</entry>
<entry>
<title>Ribbon 源码二:Ribbon初始化流程及ILoadBalancer</title>
<link href="http://yoursite.com/2020/01/16/[Ribbon%20%E6%BA%90%E7%A0%81%E4%BA%8C%EF%BC%9A%E9%80%9A%E8%BF%87Debug%E6%89%BE%E5%87%BARibbon%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B%E5%8F%8AILoadBalancer%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90]/"/>
<id>http://yoursite.com/2020/01/16/[Ribbon%20%E6%BA%90%E7%A0%81%E4%BA%8C%EF%BC%9A%E9%80%9A%E8%BF%87Debug%E6%89%BE%E5%87%BARibbon%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B%E5%8F%8AILoadBalancer%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90]/</id>
<published>2020-01-16T14:05:37.000Z</published>
<updated>2020-03-01T15:16:15.802Z</updated>
<content type="html"><![CDATA[<h1 id="Ribbon-源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析"><a href="#Ribbon-源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析" class="headerlink" title="[Ribbon 源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析]"></a>[Ribbon 源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析]</h1><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲讲了Ribbon的基础知识,通过一个简单的demo看了下Ribbon的负载均衡,我们在RestTemplate上加了@LoadBalanced注解后,就能够自动的负载均衡了。</p><a id="more"></a><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p>这一讲主要是继续深入<code>RibbonLoadBalancerClient</code>和Ribbon+Eureka整合的方式。</p><p>上文我们已经知道调用<code>RestTemplate</code>时,会在其上面加上一个<code>LoadBalancerInterceptor</code>拦截器,其中会先执行<code>LoadBalancerClient.execute()</code>方法。</p><p>这里我们会有一个疑问,默认的<code>LoadBalancerInterceptor</code>和<code>LoadBalancerClient</code>都是什么呢?他们分别在哪里进行初始化的呢?</p><p>带着这些疑问我们来往前递推下Ribbon初始化过程,相信看完下面的分析后,这些问题也就迎刃而解了。</p><p><strong>目录如下:</strong></p><ol><li>从XXXAutoConfig来追溯Ribbon初始化过程</li><li>ZoneAwareLoadBalancer原理分析</li></ol><h4 id="从XXXAutoConfig来追溯Ribbon初始化过程"><a href="#从XXXAutoConfig来追溯Ribbon初始化过程" class="headerlink" title="从XXXAutoConfig来追溯Ribbon初始化过程"></a>从XXXAutoConfig来追溯Ribbon初始化过程</h4><p>在第一篇文章我们已经分析了,和<code>LoadBalanced</code>类同目录下有一个<code>LoadBalancerAutoConfiguration</code>类,这个是我们最先找到的<strong>负载均衡</strong>自动配置类。</p><h5 id="LoadBalancerAutoConfiguration作用"><a href="#LoadBalancerAutoConfiguration作用" class="headerlink" title="LoadBalancerAutoConfiguration作用"></a>LoadBalancerAutoConfiguration作用</h5><p>这个配置类主要是为调用的<code>RestTemplate</code>调用时添加<code>LoadBalancerInterceptor</code>过滤器,里面还有其他一些重试的配置,这个后面再看。</p><p>查看此类的依赖,可以追踪到:<code>RibbonAutoConfiguration</code>, 如图所示:</p><p><a href="https://pic.downk.cc/item/5e0f0cfe76085c32898dd24a.jpg" target="_blank" rel="noopener"><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200106100452510-906620037.jpg" alt="img"></a></p><h5 id="RibbonAutoConfiguration作用"><a href="#RibbonAutoConfiguration作用" class="headerlink" title="RibbonAutoConfiguration作用"></a>RibbonAutoConfiguration作用</h5><ol><li>初始化SpringClientFactory</li><li>初始化LoadBalancerClient: RibbonLoadBalancerClient</li></ol><p>其中在<code>SpringClientFactory</code>构造函数中有如下代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {</span><br><span class="line"></span><br><span class="line"> public SpringClientFactory() {</span><br><span class="line"> super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看到这里实际上会初始化<code>RibbonClientConfiguration</code>配置类,接着往下看。</p><h5 id="RibbonClientConfiguration作用"><a href="#RibbonClientConfiguration作用" class="headerlink" title="RibbonClientConfiguration作用"></a>RibbonClientConfiguration作用</h5><ol><li>初始化ribbonRule: <strong>ZoneAvoidanceRule</strong></li><li>初始化ribbonPing:<strong>DummyPing</strong></li><li>初始化ribbonServerList:<strong>ConfigurationBasedServerList</strong></li><li>初始化ServerListUpdater:<strong>new PollingServerListUpdater(config)</strong></li><li>初始化ILoadBalancer:<strong>ZoneAwareLoadBalancer</strong></li><li>初始化ribbonServerListFilter:<strong>ZonePreferenceServerListFilter</strong></li><li>初始化ribbonLoadBalancerContext:<strong>RibbonLoadBalancerContext</strong></li><li>初始化serverIntrospector:<strong>DefaultServerIntrospector</strong></li></ol><p>最后总结为下面一张图所示:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200106100453720-879797546.png" alt="22.png"></p><h4 id="ZoneAwareLoadBalancer原理分析"><a href="#ZoneAwareLoadBalancer原理分析" class="headerlink" title="ZoneAwareLoadBalancer原理分析"></a>ZoneAwareLoadBalancer原理分析</h4><p>我们上面已经知道了Ribbon的大致流程,这里我们可以看到默认的<code>ILoadBalancer</code> 为<code>ZoneAwareLoadBalancer</code>,还是回到之前<code>RibbonLoadBalancerClient.execute()</code> 方法中去,看下这里方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {</span><br><span class="line"> ILoadBalancer loadBalancer = getLoadBalancer(serviceId);</span><br><span class="line"> Server server = getServer(loadBalancer);</span><br><span class="line"> if (server == null) {</span><br><span class="line"> throw new IllegalStateException("No instances available for " + serviceId);</span><br><span class="line"> }</span><br><span class="line"> RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,</span><br><span class="line"> serviceId), serverIntrospector(serviceId).getMetadata(server));</span><br><span class="line"></span><br><span class="line"> return execute(serviceId, ribbonServer, request);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里第一行代码会获取一个<code>ILoadBalancer</code> 我们其实已经知道了,这里默认的<code>ILoadBalancer</code> 为<code>ZoneAwareLoadBalancer</code>。</p><p>我们接着看下 <code>RibbonLoadBalancerClient</code> 中的<code>getLoadBalancer()</code> 方法具体是怎么获取这个默认的LoadBalancer的。</p><p>这里面使用的是<code>SpringClientFactory.getLoadBalancer()</code> 方法,然后一直往里面跟, 最后调用到 <code>NameContextFactory.java</code> 中:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">public abstract class NamedContextFactory<C extends NamedContextFactory.Specification></span><br><span class="line"> implements DisposableBean, ApplicationContextAware {</span><br><span class="line"></span><br><span class="line"> private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();</span><br><span class="line"></span><br><span class="line"> public <T> T getInstance(String name, Class<T> type) {</span><br><span class="line"> AnnotationConfigApplicationContext context = getContext(name);</span><br><span class="line"> if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,</span><br><span class="line"> type).length > 0) {</span><br><span class="line"> return context.getBean(type);</span><br><span class="line"> }</span><br><span class="line"> return null;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> protected AnnotationConfigApplicationContext getContext(String name) {</span><br><span class="line"> if (!this.contexts.containsKey(name)) {</span><br><span class="line"> synchronized (this.contexts) {</span><br><span class="line"> if (!this.contexts.containsKey(name)) {</span><br><span class="line"> this.contexts.put(name, createContext(name));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return this.contexts.get(name);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>对每个服务名称,你要调用的每个服务,对应的服务名称,都有一个对应的spring的ApplicationContext容器,ServiceA对应着一个自己的独立的spring的ApplicationContext容器</p><p>比如说要获取这个ServiceA服务的LoadBalancer,那么就从ServiceCA服务对应的自己的ApplicationContext容器中去获取自己的LoadBalancer即可</p><p>如果是另外一个ServiceC服务,那么又是另外的一个spring APplicationContext,然后从里面获取到的LoadBalancer都是自己的容器里的LoadBalancer</p><p>可以通过debug 查看到下图返回的LoadBanlancer信息。这里就不在多赘述。</p><p><a href="https://pic.downk.cc/item/5e0f0cfe76085c32898dd24a.jpg" target="_blank" rel="noopener"><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200106100452510-906620037.jpg" alt="img"></a></p><p><a href="https://pic.downk.cc/item/5e0f175976085c32898ecf2e.jpg" target="_blank" rel="noopener"><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200106100455787-963069050.jpg" alt="img"></a></p><p>上面最后图片可以看到,实例化出来的instance是<code>ZoneAwareLoadBalancer</code> , 这个类继承自<code>DynamicServerListLoadBalancer</code>,顺带看下类结构:</p><p><a href="https://pic.downk.cc/item/5e0f1d9b76085c32898f5c27.jpg" target="_blank" rel="noopener"><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200106100456442-1721326471.jpg" alt="img"></a></p><p>到了这里就算是分析完了,再深究<code>ZoneAwareLoadBalancer</code> 就到了和Eureka整合相关的了,这一部分放到下一讲继续讲解了。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>用一张图做最后的总结:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200106100457063-580433286.png" alt="img"></p>]]></content>
<summary type="html">
<h1 id="Ribbon-源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析"><a href="#Ribbon-源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析" class="headerlink" title="[Ribbon 源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析]"></a>[Ribbon 源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析]</h1><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲讲了Ribbon的基础知识,通过一个简单的demo看了下Ribbon的负载均衡,我们在RestTemplate上加了@LoadBalanced注解后,就能够自动的负载均衡了。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Ribbon" scheme="http://yoursite.com/tags/Ribbon/"/>
</entry>
<entry>
<title>Ribbon 源码一:Ribbon概念理解及Demo调试</title>
<link href="http://yoursite.com/2020/01/15/[Ribbon%20%E6%BA%90%E7%A0%81%E4%B8%80%EF%BC%9ARibbon%E6%A6%82%E5%BF%B5%E7%90%86%E8%A7%A3%E5%8F%8ADemo%E8%B0%83%E8%AF%95]/"/>
<id>http://yoursite.com/2020/01/15/[Ribbon%20%E6%BA%90%E7%A0%81%E4%B8%80%EF%BC%9ARibbon%E6%A6%82%E5%BF%B5%E7%90%86%E8%A7%A3%E5%8F%8ADemo%E8%B0%83%E8%AF%95]/</id>
<published>2020-01-15T14:05:37.000Z</published>
<updated>2020-03-01T15:16:17.908Z</updated>
<content type="html"><![CDATA[<h1 id="Ribbon-源码一:Ribbon概念理解及Demo调试"><a href="#Ribbon-源码一:Ribbon概念理解及Demo调试" class="headerlink" title="[Ribbon 源码一:Ribbon概念理解及Demo调试]"></a>[Ribbon 源码一:Ribbon概念理解及Demo调试]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>前面文章已经梳理清楚了Eureka相关的概念及源码,接下来开始研究下Ribbon的实现原理。</p><p>我们都知道Ribbon在spring cloud中担当<strong>负载均衡</strong>的角色, 当两个Eureka Client互相调用的时候,Ribbon能够做到调用时的负载,保证多节点的客户端均匀接收请求。(这个有点类似于前端调用后端时Nginx做的负载均衡)</p><a id="more"></a><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p>本讲主通过一个简单的demo来了解ribbon内部实现,这里主要是对ribbon有个宏观的认识,后续篇章会一步步通过debug的方式对ribbon的细节做一个全面的讲解。</p><p><strong>目录如下:</strong></p><ol><li>一个demo来看看ribbon是做什么的</li><li>@LoadBalanced初探</li><li>LoadBalancerAutoConfiguration初探</li><li>RibbonLoadBalancerClient初探</li></ol><h3 id="Ribbon正文"><a href="#Ribbon正文" class="headerlink" title="Ribbon正文"></a>Ribbon正文</h3><h4 id="一个demo来看看ribbon是做什么的"><a href="#一个demo来看看ribbon是做什么的" class="headerlink" title="一个demo来看看ribbon是做什么的"></a>一个demo来看看ribbon是做什么的</h4><p>首先看下我们这里的demo,目录结构如下:</p><p><a href="https://pic.downk.cc/item/5e0eac8f76085c32897e7ab3.png" target="_blank" rel="noopener"><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200105082029929-1729026027.png" alt="img"></a></p><p>这里有3个模块,eurekaServer作为注册中心,serviceA和serviceB分别作为EurekaClient。</p><p>代码地址上传到了自己的git:<br> <a href="https://github.com/barrywangmeng/spring-cloud-learn" target="_blank" rel="noopener">https://github.com/barrywangmeng/spring-cloud-learn</a></p><h4 id="ribbon相关的类结构信息"><a href="#ribbon相关的类结构信息" class="headerlink" title="ribbon相关的类结构信息"></a>ribbon相关的类结构信息</h4><ol><li>启动了eureka client如下:<br> <strong>服务A 2个</strong>: 一个端口号为8087,另一个为8088<br> <strong>服务B 1个</strong><br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200105082033179-443484132.png" alt="image.png"></li><li>查看注册中心Dashboard<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200105082033567-1400489503.png" alt="image.png"></li><li>服务B调用服务A中的接口<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200105082034121-82344400.jpg" alt="调用"></li><li>查看负载均衡情况<br> 第一次调用服务B的greeting方法:<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200105082036089-380230309.png" alt="image.png"></li><li>第二次调用服务A的greeting方法:<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200105082037662-612781460.png" alt="image.png"></li></ol><p>这里可以看到服务A调用的时候加了一个注解: <code>@LoadBalanced</code></p><p>服务B第一次调用到了服务A的<strong>8088</strong>那个节点<br> 服务B第二次调用到了服务A的<strong>8087</strong>那个节点</p><p>这里就可以证明使用<code>@LoadBalanced</code> 自动对我们的http请求加了负载均衡,接下来我们就用<code>@LoadBalanced</code>来一步步往下看。</p><h4 id="LoadBalanced初探"><a href="#LoadBalanced初探" class="headerlink" title="@LoadBalanced初探"></a>@LoadBalanced初探</h4><p>接下来看下<code>@LoadBalanced</code>的源码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">/** * Annotation to mark a RestTemplate bean to be configured to use </span><br><span class="line">a LoadBalancerClient * @author Spencer Gibb */@Target({ ElementType.FIELD, ElementType.PARAMETER, </span><br><span class="line">ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic @interface LoadBalanced {}</span><br></pre></td></tr></table></figure><p>这里主要看注释,这里意思是使用这个注解后可以将普通的<code>RestTemplate</code> 使用 <code>LoadBalanceClient</code> 这个类去处理。</p><p>接着我们看下<code>LoadBalanced</code>相关的配置。</p><h4 id="LoadBalancerAutoConfiguration初探"><a href="#LoadBalancerAutoConfiguration初探" class="headerlink" title="LoadBalancerAutoConfiguration初探"></a>LoadBalancerAutoConfiguration初探</h4><p>我们知道,springboot + springcloud 对应的组件都会有相应的XXXAutoConfigure配置类,同理,我们在<code>LoadBalanced</code>同级包下可以找到对应的AutoConfigure类:<code>LoadBalancerAutoConfiguration</code>, 先看下类的定义:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">@Configuration</span><br><span class="line">@ConditionalOnClass(RestTemplate.class)</span><br><span class="line">@ConditionalOnBean(LoadBalancerClient.class)</span><br><span class="line">@EnableConfigurationProperties(LoadBalancerRetryProperties.class)</span><br><span class="line">public class LoadBalancerAutoConfiguration {</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看到这里有个 <code>@ConditionalOnClass(RestTemplate.class)</code>,这个含义是 只有存在<code>RestTemplate</code> 这个类的时该配置才会生效。</p><p>接着看<code>LoadBalancerAutoConfiguration</code>中的一些方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">public class LoadBalancerAutoConfiguration { </span><br><span class="line"> @LoadBalanced</span><br><span class="line"> @Autowired(required = false)</span><br><span class="line"> private List<RestTemplate> restTemplates = Collections.emptyList();</span><br><span class="line"></span><br><span class="line"> @Bean</span><br><span class="line"> public SmartInitializingSingleton loadBalancedRestTemplateInitializer(</span><br><span class="line"> final List<RestTemplateCustomizer> customizers) {</span><br><span class="line"> return new SmartInitializingSingleton() {</span><br><span class="line"> @Override</span><br><span class="line"> public void afterSingletonsInstantiated() {</span><br><span class="line"> for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {</span><br><span class="line"> for (RestTemplateCustomizer customizer : customizers) {</span><br><span class="line"> customizer.customize(restTemplate);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们可以看下<code>loadBalancedRestTemplateInitializer</code> 方法,这个里面会遍历<code>restTemplates</code>然后调用<code>customize()</code> 方法进行特殊处理。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">public class LoadBalancerAutoConfiguration { </span><br><span class="line"> @LoadBalanced</span><br><span class="line"> @Autowired(required = false)</span><br><span class="line"> private List<RestTemplate> restTemplates = Collections.emptyList();</span><br><span class="line"></span><br><span class="line"> @Configuration</span><br><span class="line"> @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")</span><br><span class="line"> static class LoadBalancerInterceptorConfig {</span><br><span class="line"> @Bean</span><br><span class="line"> public LoadBalancerInterceptor ribbonInterceptor(</span><br><span class="line"> LoadBalancerClient loadBalancerClient,</span><br><span class="line"> LoadBalancerRequestFactory requestFactory) {</span><br><span class="line"> return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Bean</span><br><span class="line"> @ConditionalOnMissingBean</span><br><span class="line"> public RestTemplateCustomizer restTemplateCustomizer(</span><br><span class="line"> final LoadBalancerInterceptor loadBalancerInterceptor) {</span><br><span class="line"> return new RestTemplateCustomizer() {</span><br><span class="line"> @Override</span><br><span class="line"> public void customize(RestTemplate restTemplate) {</span><br><span class="line"> List<ClientHttpRequestInterceptor> list = new ArrayList<>(</span><br><span class="line"> restTemplate.getInterceptors());</span><br><span class="line"> list.add(loadBalancerInterceptor);</span><br><span class="line"> restTemplate.setInterceptors(list);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里面是为每一个<code>restTemplate</code> 添加一个<code>loadBalancerInterceptor</code> 拦截器,紧接着看一下<code>LoadBalancerInterceptor.java</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {</span><br><span class="line"></span><br><span class="line"> private LoadBalancerClient loadBalancer;</span><br><span class="line"> private LoadBalancerRequestFactory requestFactory;</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,</span><br><span class="line"> final ClientHttpRequestExecution execution) throws IOException {</span><br><span class="line"> final URI originalUri = request.getURI();</span><br><span class="line"> String serviceName = originalUri.getHost();</span><br><span class="line"> Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);</span><br><span class="line"> return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里面就很简单了,将serviceName(这里就是对应我们demo中的:ServiceA)和request、body、excution等组成的新的request传递给<code>LoadBalancerClient</code>,然后调用其中的<code>execute</code>,这个方法的实现继续往下看<code>RibbonLoadBalancerClient</code></p><h4 id="RibbonLoadBalancerClient初探"><a href="#RibbonLoadBalancerClient初探" class="headerlink" title="RibbonLoadBalancerClient初探"></a>RibbonLoadBalancerClient初探</h4><p>接下来再看一下 <code>LoadBalanceClient</code> :</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">public interface LoadBalancerClient extends ServiceInstanceChooser {</span><br><span class="line"></span><br><span class="line"> <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;</span><br><span class="line"></span><br><span class="line"> <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;</span><br><span class="line"></span><br><span class="line"> URI reconstructURI(ServiceInstance instance, URI original);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个接口只有一个实现类:<code>RibbonLoadBalancerClient</code>, 那么我们继续看实现类中的execute方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {</span><br><span class="line"> ILoadBalancer loadBalancer = getLoadBalancer(serviceId);</span><br><span class="line"> Server server = getServer(loadBalancer);</span><br><span class="line"> if (server == null) {</span><br><span class="line"> throw new IllegalStateException("No instances available for " + serviceId);</span><br><span class="line"> }</span><br><span class="line"> RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,</span><br><span class="line"> serviceId), serverIntrospector(serviceId).getMetadata(server));</span><br><span class="line"></span><br><span class="line"> return execute(serviceId, ribbonServer, request);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接着就可以在这个方法上愉快的debug了,我们先看看<code>ILoadBalancer</code> 是干嘛的:</p><p><a href="https://pic.downk.cc/item/5e0eb24e76085c32897f94fb.jpg" target="_blank" rel="noopener"><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200105082039639-1582989998.jpg" alt="img"></a></p><p>我们通过debug可以看到 获取的<code>ILoadBalancer</code> 已经获取到服务A所有的节点信息了,这一章就先不延伸下去了,后面会详细来说<code>ILoadBalancer</code>处理的细节。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>这一篇主要讲解了一个<code>RestTemplate</code> 加上<code>@LoadBalanced</code> 注解后是如何获取到请求服务的多个节点信息的,通过debug 我们可以很清晰的看到请求流程,最后画一个图来总结一下:</p><p><a href="https://pic.downk.cc/item/5e0eb7c876085c328980ebe1.png" target="_blank" rel="noopener"><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200105082040226-3881986.png" alt="img"></a></p>]]></content>
<summary type="html">
<h1 id="Ribbon-源码一:Ribbon概念理解及Demo调试"><a href="#Ribbon-源码一:Ribbon概念理解及Demo调试" class="headerlink" title="[Ribbon 源码一:Ribbon概念理解及Demo调试]"></a>[Ribbon 源码一:Ribbon概念理解及Demo调试]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>前面文章已经梳理清楚了Eureka相关的概念及源码,接下来开始研究下Ribbon的实现原理。</p>
<p>我们都知道Ribbon在spring cloud中担当<strong>负载均衡</strong>的角色, 当两个Eureka Client互相调用的时候,Ribbon能够做到调用时的负载,保证多节点的客户端均匀接收请求。(这个有点类似于前端调用后端时Nginx做的负载均衡)</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Ribbon" scheme="http://yoursite.com/tags/Ribbon/"/>
</entry>
<entry>
<title>Nexflix Eureka 源码十二:Eureka总结、架构图以及思维导图</title>
<link href="http://yoursite.com/2020/01/14/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%8D%81%E4%BA%8C%EF%BC%9AEureka%E6%80%BB%E7%BB%93%E3%80%81%E6%9E%B6%E6%9E%84%E5%9B%BE%E4%BB%A5%E5%8F%8A%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE]/"/>
<id>http://yoursite.com/2020/01/14/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%8D%81%E4%BA%8C%EF%BC%9AEureka%E6%80%BB%E7%BB%93%E3%80%81%E6%9E%B6%E6%9E%84%E5%9B%BE%E4%BB%A5%E5%8F%8A%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE]/</id>
<published>2020-01-14T14:05:37.000Z</published>
<updated>2020-03-01T15:13:03.726Z</updated>
<content type="html"><![CDATA[<h1 id="Nexflix-Eureka-源码十二:Eureka总结、架构图以及思维导图"><a href="#Nexflix-Eureka-源码十二:Eureka总结、架构图以及思维导图" class="headerlink" title="[Nexflix Eureka 源码十二:Eureka总结、架构图以及思维导图]"></a>[Nexflix Eureka 源码十二:Eureka总结、架构图以及思维导图]</h1><p>主要是架构图、思维导图、以及相关的资料</p><a id="more"></a><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>首先看一下整体的Eureka架构图:<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200104063201630-143273808.png" alt="13_Eureka整体架构设计.png"></p><p>总结的思维导图:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200104063205750-263272955.png" alt="思维导图.png"></p><p>附录几个对spring cloud netflix eureka分析比较好的博客:</p><p><a href="https://www.cnblogs.com/rickiyang/p/11802413.html" target="_blank" rel="noopener">Spring Cloud Eureka源码分析—服务注册</a><br><a href="https://www.cnblogs.com/rickiyang/p/11802434.html" target="_blank" rel="noopener">Spring Cloud Eureka源码分析 — client 注册流程</a><br><a href="https://www.cnblogs.com/rickiyang/p/11802441.html" target="_blank" rel="noopener">Spring Cloud Eureka配置文件详解</a></p>]]></content>
<summary type="html">
<h1 id="Nexflix-Eureka-源码十二:Eureka总结、架构图以及思维导图"><a href="#Nexflix-Eureka-源码十二:Eureka总结、架构图以及思维导图" class="headerlink" title="[Nexflix Eureka 源码十二:Eureka总结、架构图以及思维导图]"></a>[Nexflix Eureka 源码十二:Eureka总结、架构图以及思维导图]</h1><p>主要是架构图、思维导图、以及相关的资料</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Nexflix Eureka" scheme="http://yoursite.com/tags/Nexflix-Eureka/"/>
</entry>
<entry>
<title>Nexflix Eureka 源码十一:EurekaServer集群模式</title>
<link href="http://yoursite.com/2020/01/13/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%8D%81%E4%B8%80%EF%BC%9AEurekaServer%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E5%8A%A0%E5%88%86%E9%A2%98)]/"/>
<id>http://yoursite.com/2020/01/13/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%8D%81%E4%B8%80%EF%BC%9AEurekaServer%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E5%8A%A0%E5%88%86%E9%A2%98)]/</id>
<published>2020-01-13T14:05:37.000Z</published>
<updated>2020-03-01T15:13:47.215Z</updated>
<content type="html"><![CDATA[<h1 id="Nexflix-Eureka-源码十一:EurekaServer集群模式源码分析-加分题"><a href="#Nexflix-Eureka-源码十一:EurekaServer集群模式源码分析-加分题" class="headerlink" title="[Nexflix Eureka 源码十一:EurekaServer集群模式源码分析(加分题)]"></a>[Nexflix Eureka 源码十一:EurekaServer集群模式源码分析(加分题)]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲看了Eureka 注册中心的自我保护机制,以及里面提到的bug问题。这一讲主要是集群相关的源码</p><a id="more"></a><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p>本讲主要是EurekaServer集群模式的数据同步讲解,主要目录如下。</p><p><strong>目录如下:</strong></p><ol><li>eureka server集群机制</li><li>注册、下线、续约的注册表同步机制</li><li>注册表同步三层队列机制详解</li></ol><p><strong>技术亮点:</strong></p><ol><li>3层队列机制实现注册表的批量同步需求</li></ol><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h4 id="eureka-server集群机制"><a href="#eureka-server集群机制" class="headerlink" title="eureka server集群机制"></a>eureka server集群机制</h4><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200103062115508-1016632232.png" alt="image.png"></p><p>Eureka Server会在注册、下线、续约的时候进行数据同步,将信息同步到其他Eureka Server节点。</p><p>可以想象到的是,这里肯定不会是实时同步的,往后继续看注册表的同步机制吧。</p><h4 id="注册、下线、续约的注册表同步机制"><a href="#注册、下线、续约的注册表同步机制" class="headerlink" title="注册、下线、续约的注册表同步机制"></a>注册、下线、续约的注册表同步机制</h4><p>我们以Eureka Client注册为例,看看Eureka Server是如何同步给其他节点的。</p><p><code>PeerAwareInstanceRegistryImpl.java</code> :</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(<span class="keyword">final</span> InstanceInfo info, <span class="keyword">final</span> <span class="keyword">boolean</span> isReplication)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;</span><br><span class="line"> <span class="keyword">if</span> (info.getLeaseInfo() != <span class="keyword">null</span> && info.getLeaseInfo().getDurationInSecs() > <span class="number">0</span>) {</span><br><span class="line"> leaseDuration = info.getLeaseInfo().getDurationInSecs();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">super</span>.register(info, leaseDuration, isReplication);</span><br><span class="line"> replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, <span class="keyword">null</span>, isReplication);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">replicateToPeers</span><span class="params">(Action action, String appName, String id,</span></span></span><br><span class="line"><span class="function"><span class="params"> InstanceInfo info <span class="comment">/* optional */</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"> InstanceStatus newStatus <span class="comment">/* optional */</span>, <span class="keyword">boolean</span> isReplication)</span> </span>{</span><br><span class="line"> Stopwatch tracer = action.getTimer().start();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (isReplication) {</span><br><span class="line"> numberOfReplicationsLastMin.increment();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// If it is a replication already, do not replicate again as this will create a poison replication</span></span><br><span class="line"> <span class="keyword">if</span> (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">final</span> PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {</span><br><span class="line"> <span class="comment">// If the url represents this host, do not replicate to yourself.</span></span><br><span class="line"> <span class="keyword">if</span> (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> tracer.stop();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">replicateInstanceActionsToPeers</span><span class="params">(Action action, String appName,</span></span></span><br><span class="line"><span class="function"><span class="params"> String id, InstanceInfo info, InstanceStatus newStatus,</span></span></span><br><span class="line"><span class="function"><span class="params"> PeerEurekaNode node)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> InstanceInfo infoFromRegistry = <span class="keyword">null</span>;</span><br><span class="line"> CurrentRequestVersion.set(Version.V2);</span><br><span class="line"> <span class="keyword">switch</span> (action) {</span><br><span class="line"> <span class="keyword">case</span> Cancel:</span><br><span class="line"> node.cancel(appName, id);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> Heartbeat:</span><br><span class="line"> InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);</span><br><span class="line"> infoFromRegistry = getInstanceByAppAndId(appName, id, <span class="keyword">false</span>);</span><br><span class="line"> node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, <span class="keyword">false</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> Register:</span><br><span class="line"> node.register(info);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> StatusUpdate:</span><br><span class="line"> infoFromRegistry = getInstanceByAppAndId(appName, id, <span class="keyword">false</span>);</span><br><span class="line"> node.statusUpdate(appName, id, newStatus, infoFromRegistry);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> DeleteStatusOverride:</span><br><span class="line"> infoFromRegistry = getInstanceByAppAndId(appName, id, <span class="keyword">false</span>);</span><br><span class="line"> node.deleteStatusOverride(appName, id, infoFromRegistry);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable t) {</span><br><span class="line"> logger.error(<span class="string">"Cannot replicate information to {} for action {}"</span>, node.getServiceUrl(), action.name(), t);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li>注册完成后,调用<code>replicateToPeers()</code>,注意这里面有一个参数<code>isReplication</code>,如果是true,代表是其他Eureka Server节点同步的,false则是EurekaClient注册来的。</li><li><code>replicateToPeers()</code>中一段逻辑,如果<code>isReplication</code>为true则直接跳出,这里意思是client注册来的服务实例需要向其他节点扩散,如果不是则不需要去同步</li><li><code>peerEurekaNodes.getPeerEurekaNodes()</code>拿到所有的Eureka Server节点,循环遍历去同步数据,调用<code>replicateInstanceActionsToPeers()</code></li><li><code>replicateInstanceActionsToPeers()</code>方法中根据注册、下线、续约等去处理不同逻辑</li></ol><p>接下来就是真正执行同步逻辑的地方,这里主要用了三层队列对同步请求进行了batch操作,将请求打成一批批 然后向各个EurekaServer进行http请求。</p><h4 id="注册表同步三层队列机制详解"><a href="#注册表同步三层队列机制详解" class="headerlink" title="注册表同步三层队列机制详解"></a>注册表同步三层队列机制详解</h4><p>到了这里就是真正进入了同步的逻辑,这里还是以上面注册逻辑为主线,接着上述代码继续往下跟:</p><p><code>PeerEurekaNode.java</code> :</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(<span class="keyword">final</span> InstanceInfo info)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">long</span> expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);</span><br><span class="line"> batchingDispatcher.process(</span><br><span class="line"> taskId(<span class="string">"register"</span>, info),</span><br><span class="line"> <span class="keyword">new</span> InstanceReplicationTask(targetHost, Action.Register, info, <span class="keyword">null</span>, <span class="keyword">true</span>) {</span><br><span class="line"> <span class="function"><span class="keyword">public</span> EurekaHttpResponse<Void> <span class="title">execute</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> replicationClient.register(info);</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> expiryTime</span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里会执行<code>batchingDispatcher.process()</code> 方法,我们继续点进去,然后会进入 <code>TaskDispatchers.createBatchingTaskDispatcher()</code> 方法,查看其中的匿名内部类中的<code>process()</code>方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">process</span><span class="params">(ID id, T task, <span class="keyword">long</span> expiryTime)</span> </span>{</span><br><span class="line"> <span class="comment">// 将请求都放入到acceptorQueue中</span></span><br><span class="line"> acceptorQueue.add(<span class="keyword">new</span> TaskHolder<ID, T>(id, task, expiryTime));</span><br><span class="line"> acceptedTasks++;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>将需要同步的Task数据放入到<code>acceptorQueue</code>队列中。<br> 接着回到<code>createBatchingTaskDispatcher()</code>方法中,看下<code>AcceptorExecutor</code>,它的构造函数中会启动一个后台线程:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ThreadGroup threadGroup = <span class="keyword">new</span> ThreadGroup(<span class="string">"eurekaTaskExecutors"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">this</span>.acceptorThread = <span class="keyword">new</span> Thread(threadGroup, <span class="keyword">new</span> AcceptorRunner(), <span class="string">"TaskAcceptor-"</span> + id);</span><br></pre></td></tr></table></figure><p>我们继续跟<code>AcceptorRunner.java</code>:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AcceptorRunner</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">long</span> scheduleTime = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (!isShutdown.get()) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 处理acceptorQueue队列中的数据</span></span><br><span class="line"> drainInputQueues();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> totalItems = processingOrder.size();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">long</span> now = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">if</span> (scheduleTime < now) {</span><br><span class="line"> scheduleTime = now + trafficShaper.transmissionDelay();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (scheduleTime <= now) {</span><br><span class="line"> <span class="comment">// 将processingOrder拆分成一个个batch,然后进行操作</span></span><br><span class="line"> assignBatchWork();</span><br><span class="line"> assignSingleItemWork();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// If no worker is requesting data or there is a delay injected by the traffic shaper,</span></span><br><span class="line"> <span class="comment">// sleep for some time to avoid tight loop.</span></span><br><span class="line"> <span class="keyword">if</span> (totalItems == processingOrder.size()) {</span><br><span class="line"> Thread.sleep(<span class="number">10</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException ex) {</span><br><span class="line"> <span class="comment">// Ignore</span></span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> <span class="comment">// Safe-guard, so we never exit this loop in an uncontrolled way.</span></span><br><span class="line"> logger.warn(<span class="string">"Discovery AcceptorThread error"</span>, e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">drainInputQueues</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> drainAcceptorQueue();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!isShutdown.get()) {</span><br><span class="line"> <span class="comment">// If all queues are empty, block for a while on the acceptor queue</span></span><br><span class="line"> <span class="keyword">if</span> (reprocessQueue.isEmpty() && acceptorQueue.isEmpty() && pendingTasks.isEmpty()) {</span><br><span class="line"> TaskHolder<ID, T> taskHolder = acceptorQueue.poll(<span class="number">10</span>, TimeUnit.MILLISECONDS);</span><br><span class="line"> <span class="keyword">if</span> (taskHolder != <span class="keyword">null</span>) {</span><br><span class="line"> appendTaskHolder(taskHolder);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">while</span> (!reprocessQueue.isEmpty() || !acceptorQueue.isEmpty() || pendingTasks.isEmpty());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">drainAcceptorQueue</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">while</span> (!acceptorQueue.isEmpty()) {</span><br><span class="line"> <span class="comment">// 将acceptor队列中的数据放入到processingOrder队列中去,方便后续拆分成batch</span></span><br><span class="line"> appendTaskHolder(acceptorQueue.poll());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">appendTaskHolder</span><span class="params">(TaskHolder<ID, T> taskHolder)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (isFull()) {</span><br><span class="line"> pendingTasks.remove(processingOrder.poll());</span><br><span class="line"> queueOverflows++;</span><br><span class="line"> }</span><br><span class="line"> TaskHolder<ID, T> previousTask = pendingTasks.put(taskHolder.getId(), taskHolder);</span><br><span class="line"> <span class="keyword">if</span> (previousTask == <span class="keyword">null</span>) {</span><br><span class="line"> processingOrder.add(taskHolder.getId());</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> overriddenTasks++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>认真跟这里面的代码,可以看到这里是将上面的<code>acceptorQueue</code>放入到<code>processingOrder</code>, 其中<code>processingOrder</code>也是一个队列。</p><p>在<code>AcceptorRunner.java</code>的<code>run()</code>方法中,还会调用<code>assignBatchWork()</code>方法,这里面就是将<code>processingOrder</code>打成一个个batch,接着看代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">assignBatchWork</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (hasEnoughTasksForNextBatch()) {</span><br><span class="line"> <span class="keyword">if</span> (batchWorkRequests.tryAcquire(<span class="number">1</span>)) {</span><br><span class="line"> <span class="keyword">long</span> now = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">int</span> len = Math.min(maxBatchingSize, processingOrder.size());</span><br><span class="line"> List<TaskHolder<ID, T>> holders = <span class="keyword">new</span> ArrayList<>(len);</span><br><span class="line"> <span class="keyword">while</span> (holders.size() < len && !processingOrder.isEmpty()) {</span><br><span class="line"> ID id = processingOrder.poll();</span><br><span class="line"> TaskHolder<ID, T> holder = pendingTasks.remove(id);</span><br><span class="line"> <span class="keyword">if</span> (holder.getExpiryTime() > now) {</span><br><span class="line"> holders.add(holder);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> expiredTasks++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (holders.isEmpty()) {</span><br><span class="line"> batchWorkRequests.release();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> batchSizeMetric.record(holders.size(), TimeUnit.MILLISECONDS);</span><br><span class="line"> <span class="comment">// 将批量数据放入到batchWorkQueue中</span></span><br><span class="line"> batchWorkQueue.add(holders);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">hasEnoughTasksForNextBatch</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (processingOrder.isEmpty()) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 默认maxBufferSize为250</span></span><br><span class="line"> <span class="keyword">if</span> (pendingTasks.size() >= maxBufferSize) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> TaskHolder<ID, T> nextHolder = pendingTasks.get(processingOrder.peek());</span><br><span class="line"> <span class="comment">// 默认maxBatchingDelay为500ms</span></span><br><span class="line"> <span class="keyword">long</span> delay = System.currentTimeMillis() - nextHolder.getSubmitTimestamp();</span><br><span class="line"> <span class="keyword">return</span> delay >= maxBatchingDelay;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>这里加入batch的规则是:<code>maxBufferSize</code> 默认为250<br> <code>maxBatchingDelay</code> 默认为500ms,打成一个个batch后就开始发送给server端。至于怎么发送 我们接着看 <code>PeerEurekaNode.java</code>, 我们在最开始调用<code>register()</code>方法就是调用<code>PeerEurekaNode.register()</code>, 我们来看看它的构造方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">PeerEurekaNode(PeerAwareInstanceRegistry registry, String targetHost, String serviceUrl,</span><br><span class="line"> HttpReplicationClient replicationClient, EurekaServerConfig config,</span><br><span class="line"> <span class="keyword">int</span> batchSize, <span class="keyword">long</span> maxBatchingDelayMs,</span><br><span class="line"> <span class="keyword">long</span> retrySleepTimeMs, <span class="keyword">long</span> serverUnavailableSleepTimeMs) {</span><br><span class="line"> <span class="keyword">this</span>.registry = registry;</span><br><span class="line"> <span class="keyword">this</span>.targetHost = targetHost;</span><br><span class="line"> <span class="keyword">this</span>.replicationClient = replicationClient;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.serviceUrl = serviceUrl;</span><br><span class="line"> <span class="keyword">this</span>.config = config;</span><br><span class="line"> <span class="keyword">this</span>.maxProcessingDelayMs = config.getMaxTimeForReplication();</span><br><span class="line"></span><br><span class="line"> String batcherName = getBatcherName();</span><br><span class="line"> ReplicationTaskProcessor taskProcessor = <span class="keyword">new</span> ReplicationTaskProcessor(targetHost, replicationClient);</span><br><span class="line"> <span class="keyword">this</span>.batchingDispatcher = TaskDispatchers.createBatchingTaskDispatcher(</span><br><span class="line"> batcherName,</span><br><span class="line"> config.getMaxElementsInPeerReplicationPool(),</span><br><span class="line"> batchSize,</span><br><span class="line"> config.getMaxThreadsForPeerReplication(),</span><br><span class="line"> maxBatchingDelayMs,</span><br><span class="line"> serverUnavailableSleepTimeMs,</span><br><span class="line"> retrySleepTimeMs,</span><br><span class="line"> taskProcessor</span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里会实例化一个<code>ReplicationTaskProcessor.java</code>, 我们跟进去,发下它是实现<code>TaskProcessor</code>的,所以一定会执行此类中的<code>process()</code>方法,执行方法如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> ProcessingResult <span class="title">process</span><span class="params">(List<ReplicationTask> tasks)</span> </span>{</span><br><span class="line"> ReplicationList list = createReplicationListOf(tasks);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> EurekaHttpResponse<ReplicationListResponse> response = replicationClient.submitBatchUpdates(list);</span><br><span class="line"> <span class="keyword">int</span> statusCode = response.getStatusCode();</span><br><span class="line"> <span class="keyword">if</span> (!isSuccess(statusCode)) {</span><br><span class="line"> <span class="keyword">if</span> (statusCode == <span class="number">503</span>) {</span><br><span class="line"> logger.warn(<span class="string">"Server busy (503) HTTP status code received from the peer {}; rescheduling tasks after delay"</span>, peerId);</span><br><span class="line"> <span class="keyword">return</span> ProcessingResult.Congestion;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// Unexpected error returned from the server. This should ideally never happen.</span></span><br><span class="line"> logger.error(<span class="string">"Batch update failure with HTTP status code {}; discarding {} replication tasks"</span>, statusCode, tasks.size());</span><br><span class="line"> <span class="keyword">return</span> ProcessingResult.PermanentError;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> handleBatchResponse(tasks, response.getEntity().getResponseList());</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> <span class="keyword">if</span> (isNetworkConnectException(e)) {</span><br><span class="line"> logNetworkErrorSample(<span class="keyword">null</span>, e);</span><br><span class="line"> <span class="keyword">return</span> ProcessingResult.TransientError;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> logger.error(<span class="string">"Not re-trying this exception because it does not seem to be a network exception"</span>, e);</span><br><span class="line"> <span class="keyword">return</span> ProcessingResult.PermanentError;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ProcessingResult.Success;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里面是将<code>List tasks</code> 通过<code>submitBatchUpdate()</code> 发送给server端。<br> server端在<code>PeerReplicationResource.batchReplication()</code>去处理,实际上就是循环调用<code>ApplicationResource.addInstance()</code> 方法,又回到了最开始注册的方法。</p><p>到此 EurekaServer同步的逻辑就结束了,这里主要是三层队列的数据结构很绕,通过一个batchList去批量同步数据的。</p><p>注意这里还有一个很重要的点,就是Client注册时调用addInstance()方法,这里到了server端<code>PeerAwareInstanceRegistryImpl</code>会执行同步其他EurekaServer逻辑。</p><p>而EurekaServer同步注册接口仍然会调用addInstance()方法,这里难不成就死循环调用了?当然不是,addInstance()中也有个参数:<code>isReplication</code>, 在最后调用server端方法的时候如下:<code>registry.register(info, "true".equals(isReplication));</code></p><p>我们知道,EurekaClient在注册的时候<code>isReplication</code>传递为空,所以这里为false,而Server端同步的时候调用:</p><p><code>PeerReplicationResource</code>:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">private static Builder handleRegister(ReplicationInstance instanceInfo, ApplicationResource applicationResource) {</span><br><span class="line"> applicationResource.addInstance(instanceInfo.getInstanceInfo(), REPLICATION);</span><br><span class="line"> return new Builder().setStatusCode(Status.OK.getStatusCode());</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>这里的<code>REPLICATION</code> 为true</p><p>另外在<code>AbstractJersey2EurekaHttpClient</code>中发送register请求的时候,有个<code>addExtraHeaders()</code>方法,如下图:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200103062116891-1070419267.png" alt="image.png"></p><p>如果是使用的<code>Jersey2ReplicationClient</code>发送的,那么header中的<code>x-netflix-discovery-replication</code>配置则为true,在后面执行注册的<code>addInstance()</code>方法中会接收这个参数的:</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>仍然一图流,文中解析的内容都包含在这张图中了:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200103062117279-1230211095.png" alt="11_Eureka注册中心集群同步原理.png"></p>]]></content>
<summary type="html">
<h1 id="Nexflix-Eureka-源码十一:EurekaServer集群模式源码分析-加分题"><a href="#Nexflix-Eureka-源码十一:EurekaServer集群模式源码分析-加分题" class="headerlink" title="[Nexflix Eureka 源码十一:EurekaServer集群模式源码分析(加分题)]"></a>[Nexflix Eureka 源码十一:EurekaServer集群模式源码分析(加分题)]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲看了Eureka 注册中心的自我保护机制,以及里面提到的bug问题。这一讲主要是集群相关的源码</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Nexflix Eureka" scheme="http://yoursite.com/tags/Nexflix-Eureka/"/>
</entry>
<entry>
<title>Nexflix Eureka 源码十:EurekaServer自我保护机制</title>
<link href="http://yoursite.com/2020/01/11/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%8D%81%EF%BC%9AEurekaServer%E8%87%AA%E6%88%91%E4%BF%9D%E6%8A%A4%E6%9C%BA%E5%88%B6%E7%AB%9F%E7%84%B6%E6%9C%89%E8%BF%99%E4%B9%88%E5%A4%9ABug%EF%BC%9F]/"/>
<id>http://yoursite.com/2020/01/11/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%8D%81%EF%BC%9AEurekaServer%E8%87%AA%E6%88%91%E4%BF%9D%E6%8A%A4%E6%9C%BA%E5%88%B6%E7%AB%9F%E7%84%B6%E6%9C%89%E8%BF%99%E4%B9%88%E5%A4%9ABug%EF%BC%9F]/</id>
<published>2020-01-11T14:05:37.000Z</published>
<updated>2020-03-01T15:12:34.788Z</updated>
<content type="html"><![CDATA[<h1 id="Nexflix-Eureka-源码十:EurekaServer自我保护机制"><a href="#Nexflix-Eureka-源码十:EurekaServer自我保护机制" class="headerlink" title="[Nexflix Eureka 源码十:EurekaServer自我保护机制]"></a>[Nexflix Eureka 源码十:EurekaServer自我保护机制]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲主要讲了服务下线,已经注册中心自动感知宕机的服务。<br> 其实上一讲已经包含了很多EurekaServer自我保护的代码,其中还发现了1.7.x(1.9.x)包含的一些bug,但这些问题在master分支都已修复了。</p><p>服务下线会将服务实例从注册表中删除,然后放入到recentQueue中,下次其他EurekaClient来进行注册表抓取的时候就能感知到对应的哪些服务下线了。</p><p>自动感知服务实例宕机不会调用下线的逻辑,所以我们还抛出了一个问题,一个client宕机,其他的client需要多久才能感知到?通过源码我们知道 至少要180s 才能被注册中心给摘除,也就是最快180s才能被其他服务感知,因为这里还涉及读写缓存和只读缓存不一致的情况。</p><a id="more"></a><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The class that kick starts the eureka server. 负责启动Eureka server的类</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <p></span></span><br><span class="line"><span class="comment"> * 这里要注意两个关键点:</span></span><br><span class="line"><span class="comment"> * eureka server对应的配置类为:EurekaServerConfig</span></span><br><span class="line"><span class="comment"> * eureka client对应的配置类为:EurekaInstanceConfig</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * The eureka server is configured by using the configuration</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> EurekaServerConfig} specified by <em>eureka.server.props</em> in the</span></span><br><span class="line"><span class="comment"> * classpath. The eureka client component is also initialized by using the</span></span><br><span class="line"><span class="comment"> * configuration {<span class="doctag">@link</span> EurekaInstanceConfig} specified by</span></span><br><span class="line"><span class="comment"> * <em>eureka.client.props</em>. If the server runs in the AWS cloud, the eureka</span></span><br><span class="line"><span class="comment"> * server binds it to the elastic ip as specified.</span></span><br><span class="line"><span class="comment"> * </p></span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Karthik Ranganathan, Greg Kim, David Liu</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 负责EurekaServer初始化的类</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EurekaBootStrap</span> <span class="keyword">implements</span> <span class="title">ServletContextListener</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Initializes Eureka, including syncing up with other Eureka peers and publishing the registry.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span></span></span><br><span class="line"><span class="comment"> * javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">contextInitialized</span><span class="params">(ServletContextEvent event)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> initEurekaEnvironment();</span><br><span class="line"> initEurekaServerContext();</span><br><span class="line"></span><br><span class="line"> ServletContext sc = event.getServletContext();</span><br><span class="line"> sc.setAttribute(EurekaServerContext<span class="class">.<span class="keyword">class</span>.<span class="title">getName</span>(), <span class="title">serverContext</span>)</span>;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> logger.error(<span class="string">"Cannot bootstrap eureka server :"</span>, e);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"Cannot bootstrap eureka server :"</span>, e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p>本讲主要讲解注册中心一个独有的功能,如果使用Eureka作为注册中心的小伙伴可能都看过注册中心Dashboard上会有这么一段文字:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090703315-1189988611.png" alt="image.png"></p><p>那注册中心为何要做这种自我保护呢?这里要跟注册中心的设计思想相关联了,我们知道Eureka是一个高可用的组件,符合CAP架构中的A、P,如果注册中心检测到很多服务实例宕机的时候,它不会将这些宕机的数据全都剔除,会做一个判断,如果宕机的服务实例大于所有实例数的15%,那么就会开启保护模式,不会摘除任何实例(代码中是通过每分钟所有实例心跳总数和期望实例心跳总数对比)。</p><p>试想,如果没有自我保护机制,注册中心因为网络故障,收不到其他服务实例的续约 而误将这些服务实例都剔除了,是不是就出大问题了。</p><p><strong>目录如下:</strong></p><ol><li><code>evict()</code>方法解读</li><li><code>expectedNumberOfRenewsPerMin</code>计算方式</li><li><code>expectedNumberOfRenewsPerMin</code>自动更新机制</li><li>注册中心<code>Dashboard</code>显示自我保护页面实现</li><li>自我保护机制bug汇总</li></ol><p><strong>技术亮点:</strong></p><ol><li>如何计算每一分钟内的内存中的计数呢?<br> <code>MeassuredRate</code> 计算每一分钟内的心跳的次数,保存上一分钟心跳次数和当前分钟的心跳次数 后面我们会看一下这个类似怎么实现的</li></ol><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h4 id="evict-方法解读"><a href="#evict-方法解读" class="headerlink" title="evict()方法解读"></a><code>evict()</code>方法解读</h4><p>接着上一讲的内容,上一讲其实已经讲到了<code>evict()</code>的使用,我们再来说下如何一步步调入进来的:</p><p><code>EurekaBootStrap.initEurekaServerContext()</code> 中调用<code>registry.openForTraffic()</code>, 然后进入<code>PeerAwareInstanceRegistryImpl.openForTraffic()</code>方法,其中有调用<code>super.postInit()</code> 这里面直接进入到 <code>AbstractInstanceRegistry.postInit()</code>方法,这里其实就是一个定时调度任务,默认一分钟执行一次,这里会执行<code>EvictionTask</code>,在这个task里面会有一个<code>run()</code>方法,最后就是执行到了<code>evict()</code> 方法了。</p><p>这里再来看下<code>evict()</code>方法代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">public void evict(long additionalLeaseMs) {</span><br><span class="line"> logger.debug("Running the evict task");</span><br><span class="line"></span><br><span class="line"> // 是否允许主动删除宕机节点数据,这里判断是否进入自我保护机制,如果是自我保护了则不允许摘除服务</span><br><span class="line"> if (!isLeaseExpirationEnabled()) {</span><br><span class="line"> logger.debug("DS: lease expiration is currently disabled.");</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 省略服务摘除等等操作...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接着进入<code>PeerAwareInstanceRegistryImpl.isLeaseExpirationEnabled()</code>:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">public boolean isLeaseExpirationEnabled() {</span><br><span class="line"> if (!isSelfPreservationModeEnabled()) {</span><br><span class="line"> // The self preservation mode is disabled, hence allowing the instances to expire.</span><br><span class="line"> return true;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 这行代码触发自我保护机制,期望的一分钟要有多少次心跳发送过来,所有服务实例一分钟得发送多少次心跳</span><br><span class="line"> // getNumOfRenewsInLastMin 上一分钟所有服务实例一共发送过来多少心跳,10次</span><br><span class="line"> // 如果上一分钟 的心跳次数太少了(20次)< 我期望的100次,此时会返回false</span><br><span class="line"> return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里我们先解读一下,上面注释已经说得很清晰了。</p><ol><li>我们在代码中可以找到<code>this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());</code></li><li>这段的意思<code>expectedNumberOfRenewsPerMin</code> 代表每分钟期待的心跳时间,例如现在有100次心跳,然后乘以默认的心跳配比85%,这里就是nuberOfRenewsPerMinThreshold的含义了</li><li>如果上一分钟实际心跳次数小于这个值,那么就会进入自我保护模式</li></ol><p>然后是<code>getNumOfRenewsInLastMin()</code>:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> MeasuredRate renewsLastMin;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">long</span> <span class="title">getNumOfRenewsInLastMin</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> renewsLastMin.getCount();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MeasuredRate</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Logger logger = LoggerFactory.getLogger(MeasuredRate<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> AtomicLong lastBucket = <span class="keyword">new</span> AtomicLong(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> AtomicLong currentBucket = <span class="keyword">new</span> AtomicLong(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">long</span> sampleInterval;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Timer timer;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">boolean</span> isActive;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> sampleInterval in milliseconds</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">MeasuredRate</span><span class="params">(<span class="keyword">long</span> sampleInterval)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.sampleInterval = sampleInterval;</span><br><span class="line"> <span class="keyword">this</span>.timer = <span class="keyword">new</span> Timer(<span class="string">"Eureka-MeasureRateTimer"</span>, <span class="keyword">true</span>);</span><br><span class="line"> <span class="keyword">this</span>.isActive = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!isActive) {</span><br><span class="line"> timer.schedule(<span class="keyword">new</span> TimerTask() {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// Zero out the current bucket.</span></span><br><span class="line"> <span class="comment">// renewsLastMin 为1分钟</span></span><br><span class="line"> <span class="comment">// 每分钟调度一次,将当前的88次总心跳设置到lastBucket中去,然后将当前的currentBucket 设置为0 秒啊!</span></span><br><span class="line"> lastBucket.set(currentBucket.getAndSet(<span class="number">0</span>));</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> logger.error(<span class="string">"Cannot reset the Measured Rate"</span>, e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }, sampleInterval, sampleInterval);</span><br><span class="line"></span><br><span class="line"> isActive = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">stop</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (isActive) {</span><br><span class="line"> timer.cancel();</span><br><span class="line"> isActive = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns the count in the last sample interval.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">long</span> <span class="title">getCount</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> lastBucket.get();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Increments the count in the current sample interval.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">increment</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 心跳次数+1 例如说1分钟所有服务实例共发起了88次心跳</span></span><br><span class="line"> currentBucket.incrementAndGet();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最上面我们说过,<code>MeasuredRate</code>的设计是一个闪光点,看下重要的两个属性:</p><ol><li><code>lastBucket</code>: 记录上一分钟总心跳次数</li><li><code>currentBucket</code>: 记录当前最近一分钟总心跳次数</li></ol><p>首先我们看下<code>increment()</code>方法,这里看一下调用会发现在服务端处理续约<code>renew()</code>中的最后会调用此方法,使得<code>currentBucket</code>进行原子性的+1操作。</p><p>然后这里明有一个<code>start()</code>方法,这里面也是个时间调度任务,我们可以看下<code>sampleInterval</code>这个时间戳,在构造函数中被赋值,在<code>AbstractInstanceRegistry</code>的构造方法中被调用,默认时间为一分钟。</p><p>这里最重要的是<code>lastBucket.set(currentBucket.getAndSet(0));</code> 每分钟调度一次,把当前一分钟总心跳时间赋值给上一分钟总心跳时间,然后将当前一分钟总心跳时间置为0.</p><h4 id="expectedNumberOfRenewsPerMin计算方式"><a href="#expectedNumberOfRenewsPerMin计算方式" class="headerlink" title="expectedNumberOfRenewsPerMin计算方式"></a><code>expectedNumberOfRenewsPerMin</code>计算方式</h4><p>我们上一讲中已经介绍过<code>expectedNumberOfRenewsPerMin</code>的计算方式,因为这个属性很重要,所以这里再深入研究一下。</p><p>首先我们要理解这个属性的含义:期待的一分钟注册中心接收到的总心跳时间,接着看看哪几个步骤会更新:</p><ol><li>EurekaServer初始的时候会计算<br> 在<code>openForTraffic()</code> 方法的入口会有计算</li><li>服务注册调用<code>register()</code>方法是会更新</li><li>服务下线调用<code>cancel()</code>方法时会更新</li><li>服务剔除<code>evict()</code> 也应该调用,可惜是代码中并未找到调用的地方?这里其实是个bug,我们可以看后面<code>自我保护机制Bug汇总</code>中提到更多详细内容。此问题至今未修复,我们先继续往后看。</li></ol><h4 id="expectedNumberOfRenewsPerMin自动更新机制"><a href="#expectedNumberOfRenewsPerMin自动更新机制" class="headerlink" title="expectedNumberOfRenewsPerMin自动更新机制"></a><code>expectedNumberOfRenewsPerMin</code>自动更新机制</h4><p>Server端初始化上下文的时候,15分钟跑的一次定时任务:<br> <code>scheduleRenewalThresholdUpdateTask</code></p><p>入口是:<code>EurekaBootStrap.initEurekaServerContext()</code>方法,然后执行<code>serverContext.initialize()</code>方法,里面的<code>registry.init()</code>执行<code>PeerAwareInstanceRegistryImpl.init()</code>中会执行<code>scheduleRenewalThresholdUpdateTask()</code>,这个调度任务默认是每15分钟执行一次的,来看下源代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">updateRenewalThreshold</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// count为注册表中服务实例的个数</span></span><br><span class="line"> <span class="comment">// 将自己作为eureka client,从其他eureka server拉取注册表</span></span><br><span class="line"> <span class="comment">// 合并到自己本地去 将从别的eureka server拉取到的服务实例的数量作为count</span></span><br><span class="line"> Applications apps = eurekaClient.getApplications();</span><br><span class="line"> <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (Application app : apps.getRegisteredApplications()) {</span><br><span class="line"> <span class="keyword">for</span> (InstanceInfo instance : app.getInstances()) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.isRegisterable(instance)) {</span><br><span class="line"> ++count;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">synchronized</span> (lock) {</span><br><span class="line"> <span class="comment">// Update threshold only if the threshold is greater than the</span></span><br><span class="line"> <span class="comment">// current expected threshold of if the self preservation is disabled.</span></span><br><span class="line"> <span class="comment">// 这里也是存在bug的,master分支已经修复</span></span><br><span class="line"> <span class="comment">// 一分钟服务实例心跳个数(其他eureka server拉取的服务实例个数 * 2) > 自己本身一分钟所有服务实例实际心跳次数 * 0.85(阈值)</span></span><br><span class="line"> <span class="comment">// 这里主要是跟其他的eureka server去做一下同步</span></span><br><span class="line"> <span class="keyword">if</span> ((count * <span class="number">2</span>) > (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold)</span><br><span class="line"> || (!<span class="keyword">this</span>.isSelfPreservationModeEnabled())) {</span><br><span class="line"> <span class="keyword">this</span>.expectedNumberOfRenewsPerMin = count * <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">this</span>.numberOfRenewsPerMinThreshold = (<span class="keyword">int</span>) ((count * <span class="number">2</span>) * serverConfig.getRenewalPercentThreshold());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> logger.info(<span class="string">"Current renewal threshold is : {}"</span>, numberOfRenewsPerMinThreshold);</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> logger.error(<span class="string">"Cannot update renewal threshold"</span>, e);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里需要注意一点,为何上面说<code>eurekaClient.getApplications()</code>是从别的注册中心获取注册表实例信息,因为一个eurekaServer对于其他注册中心来说也是一个eurekaClient。</p><p>这里注释已经写得很清晰了,就不再多啰嗦了。</p><h4 id="注册中心Dashboard显示自我保护页面实现"><a href="#注册中心Dashboard显示自我保护页面实现" class="headerlink" title="注册中心Dashboard显示自我保护页面实现"></a>注册中心<code>Dashboard</code>显示自我保护页面实现</h4><p>还是自己先找到对应jsp看看具体代码实现:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090704517-87373756.png" alt="image.png"></p><p>这里主要是看:<code>registry.isBelowRenewThresold()</code>逻辑。</p><p><code>PeerAwareInstanceRegistryImpl.isBelowRenewThresold()</code> :</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">isBelowRenewThresold</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> ((getNumOfRenewsInLastMin() <= numberOfRenewsPerMinThreshold)</span><br><span class="line"> &&</span><br><span class="line"> ((<span class="keyword">this</span>.startupTime > <span class="number">0</span>) && (System.currentTimeMillis() > <span class="keyword">this</span>.startupTime + (serverConfig.getWaitTimeInMsWhenSyncEmpty())))) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的意思就是 <strong>上一分钟服务实例实际总心跳个数 <= 一分钟期望的总心跳实例 * 85%</strong>,而且判断 Eureka-Server 是否允许被 Eureka-Client 获取注册信息。如果都满足的话就会返回1,当前警告信息就会在dashbord上显示自我保护的提示了。</p><p><strong>这里面注意一下配置:</strong><br> <code>#getWaitTimeInMsWhenSyncEmpty()</code> :Eureka-Server 启动时,从远程 Eureka-Server 读取不到注册信息时,多长时间不允许 Eureka-Client 访问,默认是5分钟</p><h4 id="自我保护机制bug汇总"><a href="#自我保护机制bug汇总" class="headerlink" title="自我保护机制bug汇总"></a>自我保护机制bug汇总</h4><ol><li>expectedNumberOfRenewsPerMin计算方式</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.expectedNumberOfRenewsPerMin = count * <span class="number">2</span>;</span><br><span class="line"><span class="comment">// numberOfRenewsPerMinThreshold = count * 2 * 0.85 = 34 期望一分钟 20个服务实例,得有34个心跳</span></span><br><span class="line"><span class="keyword">this</span>.numberOfRenewsPerMinThreshold =</span><br><span class="line"> (<span class="keyword">int</span>) (<span class="keyword">this</span>.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());</span><br></pre></td></tr></table></figure><p>这里为何要使用<strong>count * 2</strong>?count是注册表中所有的注册实例的数量,因为作者以为用户不会修改默认续约时间(30s), 所以理想的认为这里应该乘以2就是一分钟得心跳总数了。</p><p>好在看了master 分支此问题已经修复。如下图:<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090706562-911306263.png" alt="image.png"></p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090707883-1182042213.png" alt="image.png"></p><ol><li>同理 服务注册、服务下线 都是将</li></ol><p>注册:expectedNumberOfRenewsPerMin+2<br> 下线:expectedNumberOfRenewsPerMin-2</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090709026-1204348958.png" alt="image.png"></p><p>master分支也给予修复,图片如下:<br> 服务注册:<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090711591-637529175.png" alt="image.png"></p><p>服务下线:<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090712861-351882549.png" alt="image.png"></p><ol><li><code>evict()</code>方法为何不更新<code>expectedNumberOfRenewsPerMin</code> 按常理来说这里也应该进行 -2操作的,实际上并没有更新,于是看了下master分支源码仍然没有更新,于是早上我便在<code>netflix eureka</code> git<br> 上提了一个isssue:(我蹩脚的英语大家就不要吐槽了,哈哈哈)</li></ol><p>地址为:<a href="https://github.com/Netflix/eureka/issues/1266" target="_blank" rel="noopener">Where to update the “expectedNumberOfClientsSendingRenews” when we evict a instance?</a><br> 疑问:<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090714482-150389264.png" alt="image.png"></p><p>搜索了github 发现也有人在2017年就遇到了这个问题,从最后一个回答来看这个问题依然没有解决:</p><p><a href="https://github.com/spring-cloud/spring-cloud-netflix/issues/2407" target="_blank" rel="noopener">Eureka seems to do not recalculate numberOfRenewsPerMinThreshold during evicting expired leases</a></p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090715979-1310915119.png" alt="image.png"></p><p>翻译如下:<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090717128-417363191.png" alt="image.png"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>一张图代为总结一下:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200102090718248-464926094.png" alt="08_注册中心自我保护机制原理流程图.png"></p>]]></content>
<summary type="html">
<h1 id="Nexflix-Eureka-源码十:EurekaServer自我保护机制"><a href="#Nexflix-Eureka-源码十:EurekaServer自我保护机制" class="headerlink" title="[Nexflix Eureka 源码十:EurekaServer自我保护机制]"></a>[Nexflix Eureka 源码十:EurekaServer自我保护机制]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲主要讲了服务下线,已经注册中心自动感知宕机的服务。<br> 其实上一讲已经包含了很多EurekaServer自我保护的代码,其中还发现了1.7.x(1.9.x)包含的一些bug,但这些问题在master分支都已修复了。</p>
<p>服务下线会将服务实例从注册表中删除,然后放入到recentQueue中,下次其他EurekaClient来进行注册表抓取的时候就能感知到对应的哪些服务下线了。</p>
<p>自动感知服务实例宕机不会调用下线的逻辑,所以我们还抛出了一个问题,一个client宕机,其他的client需要多久才能感知到?通过源码我们知道 至少要180s 才能被注册中心给摘除,也就是最快180s才能被其他服务感知,因为这里还涉及读写缓存和只读缓存不一致的情况。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Nexflix Eureka" scheme="http://yoursite.com/tags/Nexflix-Eureka/"/>
</entry>
<entry>
<title>Nexflix Eureka 源码九:服务下线及实例摘除</title>
<link href="http://yoursite.com/2020/01/10/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E4%B9%9D%EF%BC%9A%E6%9C%8D%E5%8A%A1%E4%B8%8B%E7%BA%BF%E5%8F%8A%E5%AE%9E%E4%BE%8B%E6%91%98%E9%99%A4%EF%BC%8C%E4%B8%80%E4%B8%AAclient%E4%B8%8B%E7%BA%BF%E5%88%B0%E5%BA%95%E5%A4%9A%E4%B9%85%E6%89%8D%E4%BC%9A%E8%A2%AB%E5%85%B6%E4%BB%96%E5%AE%9E%E4%BE%8B%E6%84%9F%E7%9F%A5%EF%BC%9F]/"/>
<id>http://yoursite.com/2020/01/10/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E4%B9%9D%EF%BC%9A%E6%9C%8D%E5%8A%A1%E4%B8%8B%E7%BA%BF%E5%8F%8A%E5%AE%9E%E4%BE%8B%E6%91%98%E9%99%A4%EF%BC%8C%E4%B8%80%E4%B8%AAclient%E4%B8%8B%E7%BA%BF%E5%88%B0%E5%BA%95%E5%A4%9A%E4%B9%85%E6%89%8D%E4%BC%9A%E8%A2%AB%E5%85%B6%E4%BB%96%E5%AE%9E%E4%BE%8B%E6%84%9F%E7%9F%A5%EF%BC%9F]/</id>
<published>2020-01-10T15:05:37.000Z</published>
<updated>2020-03-01T15:12:23.609Z</updated>
<content type="html"><![CDATA[<h1 id="Nexflix-Eureka-源码九:服务下线及实例摘除"><a href="#Nexflix-Eureka-源码九:服务下线及实例摘除" class="headerlink" title="[Nexflix Eureka 源码九:服务下线及实例摘除]"></a>[Nexflix Eureka 源码九:服务下线及实例摘除]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲我们讲了 client端向server端发送心跳检查,也是默认每30钟发送一次,server端接收后会更新注册表的一个时间戳属性,然后一次心跳(续约)也就完成了。</p><a id="more"></a><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p>这一篇有两个知识点及一个疑问,这个疑问是在工作中真真实实遇到过的。</p><p>例如我有服务A、服务B,A、B都注册在同一个注册中心,当B下线后,A多久能感知到B已经下线了呢?</p><p><strong>目录如下:</strong></p><ol><li>Client端服务实例下线通知Server端</li><li>Server端定时任务 服务摘除</li></ol><p><strong>技术亮点:定时任务错误触发时间补偿机制</strong></p><p>在Server端定时任务进行服务故障自动感知摘除的时候有一个设计很巧妙的点,时间补偿机制。</p><p>我们知道,在做定时任务的时候,基于某个固定点触发的操作都可能由于一些其他原因导致固定的点没有执行对应的操作,这时再次执行定时操作后,计算的每次任务相隔时间就会出现问题。而Eureka 这里采用了一种补偿机制,再计算时间差值的时候完美解决此问题。</p><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h4 id="Client端服务实例下线通知Server端"><a href="#Client端服务实例下线通知Server端" class="headerlink" title="Client端服务实例下线通知Server端"></a>Client端服务实例下线通知Server端</h4><p>Client下线 我们还是依照之前的原则,从<code>DiscoveryClient</code> 看起,可以看到有一个<code>shutdown()</code> 方法,然后接着跟一下这个方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@PUT</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Response <span class="title">renewLease</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params"> @HeaderParam(PeerEurekaNode.HEADER_REPLICATION)</span> String isReplication,</span></span><br><span class="line"><span class="function"> @<span class="title">QueryParam</span><span class="params">(<span class="string">"overriddenstatus"</span>)</span> String overriddenStatus,</span></span><br><span class="line"><span class="function"> @<span class="title">QueryParam</span><span class="params">(<span class="string">"status"</span>)</span> String status,</span></span><br><span class="line"><span class="function"> @<span class="title">QueryParam</span><span class="params">(<span class="string">"lastDirtyTimestamp"</span>)</span> String lastDirtyTimestamp) </span>{</span><br><span class="line"> <span class="keyword">boolean</span> isFromReplicaNode = <span class="string">"true"</span>.equals(isReplication);</span><br><span class="line"> <span class="keyword">boolean</span> isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略部分代码</span></span><br><span class="line"></span><br><span class="line"> logger.debug(<span class="string">"Found (Renew): {} - {}; reply status={}"</span> + app.getName(), id, response.getStatus());</span><br><span class="line"> <span class="keyword">return</span> response;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">renew</span><span class="params">(String appName, String id, <span class="keyword">boolean</span> isReplication)</span> </span>{</span><br><span class="line"> RENEW.increment(isReplication);</span><br><span class="line"> Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);</span><br><span class="line"> Lease<InstanceInfo> leaseToRenew = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (gMap != <span class="keyword">null</span>) {</span><br><span class="line"> leaseToRenew = gMap.get(id);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (leaseToRenew == <span class="keyword">null</span>) {</span><br><span class="line"> RENEW_NOT_FOUND.increment(isReplication);</span><br><span class="line"> logger.warn(<span class="string">"DS: Registry: lease doesn't exist, registering resource: {} - {}"</span>, appName, id);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> InstanceInfo instanceInfo = leaseToRenew.getHolder();</span><br><span class="line"> <span class="keyword">if</span> (instanceInfo != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// touchASGCache(instanceInfo.getASGName());</span></span><br><span class="line"> InstanceStatus overriddenInstanceStatus = <span class="keyword">this</span>.getOverriddenInstanceStatus(</span><br><span class="line"> instanceInfo, leaseToRenew, isReplication);</span><br><span class="line"> <span class="keyword">if</span> (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {</span><br><span class="line"> logger.info(<span class="string">"Instance status UNKNOWN possibly due to deleted override for instance {}"</span></span><br><span class="line"> + <span class="string">"; re-register required"</span>, instanceInfo.getId());</span><br><span class="line"> RENEW_NOT_FOUND.increment(isReplication);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {</span><br><span class="line"> Object[] args = {</span><br><span class="line"> instanceInfo.getStatus().name(),</span><br><span class="line"> instanceInfo.getOverriddenStatus().name(),</span><br><span class="line"> instanceInfo.getId()</span><br><span class="line"> };</span><br><span class="line"> logger.info(</span><br><span class="line"> <span class="string">"The instance status {} is different from overridden instance status {} for instance {}. "</span></span><br><span class="line"> + <span class="string">"Hence setting the status to overridden status"</span>, args);</span><br><span class="line"> instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> renewsLastMin.increment();</span><br><span class="line"> leaseToRenew.renew();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>代码也很简单,做一些资源释放,取消调度任等操作,这里主要还是关注的是通知Server端的逻辑,及Server端是如何做实例下线的。这里请求Server端请求主要看下<code>unregister</code>方法,这里是调用jersey中的<code>cancel</code> 方法,调用Server端<code>ApplicationsResource</code>中的<code>@DELETE</code> 请求。(<strong>看到这里,前面看到各种client端调用server端,都是通过请求方式来做restful风格调用的,这里不仅要感叹 妙啊</strong>)</p><p>我们到Server端看下接收请求的入口代码:</p><p><code>InstanceResource.cancelLease()</code> :</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@DELETE</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Response <span class="title">cancelLease</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params"> @HeaderParam(PeerEurekaNode.HEADER_REPLICATION)</span> String isReplication) </span>{</span><br><span class="line"> <span class="keyword">boolean</span> isSuccess = registry.cancel(app.getName(), id,</span><br><span class="line"> <span class="string">"true"</span>.equals(isReplication));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (isSuccess) {</span><br><span class="line"> logger.debug(<span class="string">"Found (Cancel): "</span> + app.getName() + <span class="string">" - "</span> + id);</span><br><span class="line"> <span class="keyword">return</span> Response.ok().build();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> logger.info(<span class="string">"Not Found (Cancel): "</span> + app.getName() + <span class="string">" - "</span> + id);</span><br><span class="line"> <span class="keyword">return</span> Response.status(Status.NOT_FOUND).build();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后接着往下跟,<code>AbstractInstanceRegistry.internalCancel</code> 方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">internalCancel</span><span class="params">(String appName, String id, <span class="keyword">boolean</span> isReplication)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> read.lock();</span><br><span class="line"> CANCEL.increment(isReplication);</span><br><span class="line"> <span class="comment">// 通过appName获取注册表信息</span></span><br><span class="line"> Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);</span><br><span class="line"> Lease<InstanceInfo> leaseToCancel = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (gMap != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 通过实例id将注册信息从注册表中移除</span></span><br><span class="line"> leaseToCancel = gMap.remove(id);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 最近取消的注册表信息队列添加该注册表信息</span></span><br><span class="line"> <span class="keyword">synchronized</span> (recentCanceledQueue) {</span><br><span class="line"> recentCanceledQueue.add(<span class="keyword">new</span> Pair<Long, String>(System.currentTimeMillis(), appName + <span class="string">"("</span> + id + <span class="string">")"</span>));</span><br><span class="line"> }</span><br><span class="line"> InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);</span><br><span class="line"> <span class="keyword">if</span> (instanceStatus != <span class="keyword">null</span>) {</span><br><span class="line"> logger.debug(<span class="string">"Removed instance id {} from the overridden map which has value {}"</span>, id, instanceStatus.name());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (leaseToCancel == <span class="keyword">null</span>) {</span><br><span class="line"> CANCEL_NOT_FOUND.increment(isReplication);</span><br><span class="line"> logger.warn(<span class="string">"DS: Registry: cancel failed because Lease is not registered for: {}/{}"</span>, appName, id);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 执行下线操作的cancel方法</span></span><br><span class="line"> leaseToCancel.cancel();</span><br><span class="line"> InstanceInfo instanceInfo = leaseToCancel.getHolder();</span><br><span class="line"> String vip = <span class="keyword">null</span>;</span><br><span class="line"> String svip = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (instanceInfo != <span class="keyword">null</span>) {</span><br><span class="line"> instanceInfo.setActionType(ActionType.DELETED);</span><br><span class="line"> <span class="comment">// 最近更新的队列中加入此服务实例信息</span></span><br><span class="line"> recentlyChangedQueue.add(<span class="keyword">new</span> RecentlyChangedItem(leaseToCancel));</span><br><span class="line"> instanceInfo.setLastUpdatedTimestamp();</span><br><span class="line"> vip = instanceInfo.getVIPAddress();</span><br><span class="line"> svip = instanceInfo.getSecureVipAddress();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 使注册表的读写缓存失效</span></span><br><span class="line"> invalidateCache(appName, vip, svip);</span><br><span class="line"> logger.info(<span class="string">"Cancelled instance {}/{} (replication={})"</span>, appName, id, isReplication);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> read.unlock();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接着看 <code>Lease.cancel</code> :</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancel</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 这里只是更新服务实例中下线的时间戳</span></span><br><span class="line"> <span class="keyword">if</span> (evictionTimestamp <= <span class="number">0</span>) {</span><br><span class="line"> evictionTimestamp = System.currentTimeMillis();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里已经加了注释,再总结下:</p><p>1、加上读锁,支持多服务实例下线<br> 2、通过appName获取注册表信息map<br> 3、通过appId移除对应注册表信息<br> 4、recentCanceledQueue添加该服务实例<br> 5、更新Lease中的服务实例下线时间<br> 6、recentlyChangedQueue添加该服务实例<br> 7、invalidateCache() 使注册表的读写缓存失效</p><p>这里针对于6、7再解释一下,我们在之前:EurekaClient服务发现之注册表抓取 精妙设计分析! 中讲过,当client端第一次进行增量注册表抓取的时候,是会从recentlyChangedQueue中获取数据的,然后放入到读写缓存,然后再同步到只读缓存,下次再获取的时候直接从只读缓存获取即可。</p><p>这里会存在一个问题,如果一个服务下线了,读写缓存更新了,但是只读缓存并未更新,30s后由定时任务刷新 读写缓存的数据到了只读缓存,这时其他客户端才会感知到该下线的服务实例。</p><p>配合文字说明这里加一个EurekaClient下线流程图,<strong>红色线是下线逻辑</strong>,<strong>黑色线是抓取注册表 感知服务下线逻辑</strong>:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200101100643770-1686507285.png" alt="07_EurekaClient下线逻辑流程.png"></p><p>记住一点,这里是正常的服务下线,走shutdown逻辑,如果一个服务突然自己宕机了,那么注册中心怎么去自动感知这个服务下线呢?紧接着往下看吧。</p><h4 id="Server端定时任务-服务摘除"><a href="#Server端定时任务-服务摘除" class="headerlink" title="Server端定时任务 服务摘除"></a>Server端定时任务 服务摘除</h4><p>举例一个场景,上面也说过,一个Client服务端自己挂掉了,并没有正常的去执行shutdown方法,那么注册中心该如何感知这个服务实例下线了并从注册表摘除这个实例呢?</p><p>我们知道,eureka靠心跳机制来感知服务实例是否还存活着,如果某个服务挂掉了是不会再发送心跳过来了,如果在一段时间内没有接收到某个服务的心跳,那么就将这个服务实例给摘除掉,认为这个服务实例以及宕机了。</p><p>这里自动检测服务实例是否宕机的入口在:<code>EurekaBootStrap</code>,eureka server在启动初始化的时候,有个方法<code>registry.openForTraffic(applicationInfoManager, registryCount)</code> 里面会有一个服务实例检测的调度任务(这个入口真的很隐蔽,网上查了别人的分析才找到),接着直接看代码吧。</p><p><code>EurekaBootStrap.initEurekaServerContext()</code> :</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initEurekaServerContext</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 省略部分代码...</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> registryCount = registry.syncUp();</span><br><span class="line"> registry.openForTraffic(applicationInfoManager, registryCount);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的代码前面看过很多次,<code>syncUp</code>是获取其他EurekaServer中注册表数据,然后拿到注册表中服务实例<code>registryCount</code>,然后和自己本地注册表服务实例数量进行对比等等。</p><p>接着是openForTraffic方法,这里会计算预期的1分钟所有服务实例心跳次数<code>expectedNumberOfRenewsPerMin</code><br> (插个眼,后面eureka server自我保护机制会用到这个属性)后面会详细讲解,而且这里设置还是有bug的。</p><p>在方法的最后会有一个:<code>super.postInit();</code> 到了这里才是真正的服务实例自动感知的调度任务逻辑。兜兜转转 在这个不起眼的地方 隐藏了这么重要的逻辑。</p><p><code>PeerAwareInstanceRegistryImpl.java</code> :</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">syncUp</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// Copy entire entry from neighboring DS node</span></span><br><span class="line"> <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; ((i < serverConfig.getRegistrySyncRetries()) && (count == <span class="number">0</span>)); i++) {</span><br><span class="line"> <span class="keyword">if</span> (i > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> logger.warn(<span class="string">"Interrupted during registry transfer.."</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> Applications apps = eurekaClient.getApplications();</span><br><span class="line"> <span class="keyword">for</span> (Application app : apps.getRegisteredApplications()) {</span><br><span class="line"> <span class="keyword">for</span> (InstanceInfo instance : app.getInstances()) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// isRegisterable:是否可以在当前服务实例所在的注册中心注册。这个方法一定返回true,那么count就是相邻注册中心所有服务实例数量</span></span><br><span class="line"> <span class="keyword">if</span> (isRegisterable(instance)) {</span><br><span class="line"> register(instance, instance.getLeaseInfo().getDurationInSecs(), <span class="keyword">true</span>);</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable t) {</span><br><span class="line"> logger.error(<span class="string">"During DS init copy"</span>, t);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> count;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">openForTraffic</span><span class="params">(ApplicationInfoManager applicationInfoManager, <span class="keyword">int</span> count)</span> </span>{</span><br><span class="line"> <span class="comment">// Renewals happen every 30 seconds and for a minute it should be a factor of 2.</span></span><br><span class="line"> <span class="comment">// 如果有20个服务实例,乘以2 代表需要40次心跳</span></span><br><span class="line"> <span class="comment">// 这里有bug,count * 2 是硬编码,作者是不是按照心跳时间30秒计算的?所以计算一分钟得心跳就是 * 2,但是心跳时间是可以自己配置修改的</span></span><br><span class="line"> <span class="comment">// 看了master源码,这一块已经改为:</span></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;</span></span><br><span class="line"><span class="comment"> * updateRenewsPerMinThreshold();</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 主要是看 updateRenewsPerMinThreshold 方法:</span></span><br><span class="line"><span class="comment"> * this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds() * serverConfig.getRenewalPercentThreshold());</span></span><br><span class="line"><span class="comment"> * 这里完全是读取用户自己配置的心跳检查时间,然后用60s / 配置时间</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">this</span>.expectedNumberOfRenewsPerMin = count * <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// numberOfRenewsPerMinThreshold = count * 2 * 0.85 = 34 期望一分钟 20个服务实例,得有34个心跳</span></span><br><span class="line"> <span class="keyword">this</span>.numberOfRenewsPerMinThreshold =</span><br><span class="line"> (<span class="keyword">int</span>) (<span class="keyword">this</span>.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());</span><br><span class="line"> logger.info(<span class="string">"Got "</span> + count + <span class="string">" instances from neighboring DS node"</span>);</span><br><span class="line"> logger.info(<span class="string">"Renew threshold is: "</span> + numberOfRenewsPerMinThreshold);</span><br><span class="line"> <span class="keyword">this</span>.startupTime = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">if</span> (count > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">this</span>.peerInstancesTransferEmptyOnStartup = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();</span><br><span class="line"> <span class="keyword">boolean</span> isAws = Name.Amazon == selfName;</span><br><span class="line"> <span class="keyword">if</span> (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {</span><br><span class="line"> logger.info(<span class="string">"Priming AWS connections for all replicas.."</span>);</span><br><span class="line"> primeAwsReplicas(applicationInfoManager);</span><br><span class="line"> }</span><br><span class="line"> logger.info(<span class="string">"Changing status to UP"</span>);</span><br><span class="line"> applicationInfoManager.setInstanceStatus(InstanceStatus.UP);</span><br><span class="line"> <span class="comment">// 此方法会做服务实例的自动摘除任务</span></span><br><span class="line"> <span class="keyword">super</span>.postInit();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li><p>关于<code>syncUp</code> 方法,这里知道它是获取其他服务注册表信息,然后获取注册实例数量就行了,后面还会有更详细的讲解。</p></li><li><p>接着<code>openForTraffic</code> 方法,第一行代码:<code>this.expectedNumberOfRenewsPerMin = count * 2;</code> 这个count是相邻注册表中所有服务实例数量,至于乘以2 是什么意思呢? 首先是这个字段的含义是:期待的一分钟所有服务实例心跳次数,因为服务续约renew 默认是30s执行一次,所以这里就想当然一分钟就乘以2了。</p></li><li><p>大家看出来了吧?这是个很明显的bug。因为续约时间是可配置的,如果手动配置成10s,那么这里乘以6才对。看了下公司代码 spring-cloud版本是<code>Finchley.RELEASE</code>, 其中以来的netflix eureka 是<code>1.9.2</code> 仍然存在这个问题。</p></li><li><p>我也翻看了master分支的代码,此bug已经修复了,修改如下:<br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200101100645809-1878647416.png" alt="image.png"><br> <img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200101100648322-1270157262.png" alt="image.png"></p><p>其实这一块还有很多bug,包括服务注册、下线 用的都是+2 -2操作,后面一篇文章会有更多讲解。</p></li></ol><p>继续看服务实例自动感知的调度任务:</p><p><code>AbstractInstanceRegistry.java</code> :</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">postInit</span><span class="params">()</span> </span>{</span><br><span class="line"> renewsLastMin.start();</span><br><span class="line"> <span class="keyword">if</span> (evictionTaskRef.get() != <span class="keyword">null</span>) {</span><br><span class="line"> evictionTaskRef.get().cancel();</span><br><span class="line"> }</span><br><span class="line"> evictionTaskRef.set(<span class="keyword">new</span> EvictionTask());</span><br><span class="line"> evictionTimer.schedule(evictionTaskRef.get(),</span><br><span class="line"> serverConfig.getEvictionIntervalTimerInMs(),</span><br><span class="line"> serverConfig.getEvictionIntervalTimerInMs());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EvictionTask</span> <span class="keyword">extends</span> <span class="title">TimerTask</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> AtomicLong lastExecutionNanosRef = <span class="keyword">new</span> AtomicLong(<span class="number">0l</span>);</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 获取补偿时间 可能大于0</span></span><br><span class="line"> <span class="keyword">long</span> compensationTimeMs = getCompensationTimeMs();</span><br><span class="line"> logger.info(<span class="string">"Running the evict task with compensationTime {}ms"</span>, compensationTimeMs);</span><br><span class="line"> evict(compensationTimeMs);</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> logger.error(<span class="string">"Could not run the evict task"</span>, e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * compute a compensation time defined as the actual time this task was executed since the prev iteration,</span></span><br><span class="line"><span class="comment"> * vs the configured amount of time for execution. This is useful for cases where changes in time (due to</span></span><br><span class="line"><span class="comment"> * clock skew or gc for example) causes the actual eviction task to execute later than the desired time</span></span><br><span class="line"><span class="comment"> * according to the configured cycle.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">long</span> <span class="title">getCompensationTimeMs</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 第一次进来先获取当前时间 currNanos=20:00:00</span></span><br><span class="line"> <span class="comment">// 第二次过来,此时currNanos=20:01:00</span></span><br><span class="line"> <span class="comment">// 第三次过来,currNanos=20:03:00才过来,本该60s调度一次的,由于fullGC或者其他原因,到了这个时间点没执行</span></span><br><span class="line"> <span class="keyword">long</span> currNanos = getCurrentTimeNano();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取上一次这个EvictionTask执行的时间 getAndSet :以原子方式设置为给定值,并返回以前的值</span></span><br><span class="line"> <span class="comment">// 第一次 将20:00:00 设置到lastNanos,然后return 0</span></span><br><span class="line"> <span class="comment">// 第二次过来后,拿到的lastNanos为20:00:00</span></span><br><span class="line"> <span class="comment">// 第三次过来,拿到的lastNanos为20:01:00</span></span><br><span class="line"> <span class="keyword">long</span> lastNanos = lastExecutionNanosRef.getAndSet(currNanos);</span><br><span class="line"> <span class="keyword">if</span> (lastNanos == <span class="number">0l</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0l</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 第二次进来,计算elapsedMs = 60s</span></span><br><span class="line"> <span class="comment">// 第三次进来,计算elapsedMs = 120s</span></span><br><span class="line"> <span class="keyword">long</span> elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);</span><br><span class="line"> <span class="comment">// 第二次进来,配置的服务驱逐间隔默认时间为60s,计算的补偿时间compensationTime=0</span></span><br><span class="line"> <span class="comment">// 第三次进来,配置的服务驱逐间隔默认时间为60s,计算的补偿时间compensationTime=60s</span></span><br><span class="line"> <span class="keyword">long</span> compensationTime = elapsedMs - serverConfig.getEvictionIntervalTimerInMs();</span><br><span class="line"> <span class="keyword">return</span> compensationTime <= <span class="number">0l</span> ? <span class="number">0l</span> : compensationTime;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">long</span> <span class="title">getCurrentTimeNano</span><span class="params">()</span> </span>{ <span class="comment">// for testing</span></span><br><span class="line"> <span class="keyword">return</span> System.nanoTime();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li>这里执行<code>postInit</code> 方法,然后执行<code>EvictionTask</code> 任务,执行时间是<code>serverConfig.getEvictionIntervalTimerInMs()</code> 默认是60s执行一次。</li><li>接着调用<code>EvictionTask</code> ,这里也加了一些注释,我们再来分析一下。<br> 2.1 首先是获取补偿时间,compenstationTimeMs,这个时间很关键<br> 2.2 调用<code>evict</code> 方法,摘除过期没有发送心跳的实例</li></ol><p>查看<code>getCompensationTimeMs</code> 方法,这里我添加了很详细的注释,这个方法主要是 为了防止 定时任务触发点,服务因为某些原因没有执行该调度任务,此时<code>elapsedMs</code> 会超过60s的,最后返回的<code>compensationTime</code> 就是实际延误且需要补偿的时间。</p><p>接着再看下<code>evict</code> 逻辑:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">evict</span><span class="params">(<span class="keyword">long</span> additionalLeaseMs)</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 是否允许主动删除宕机节点数据,这里判断是否进入自我保护机制,如果是自我保护了则不允许摘除服务</span></span><br><span class="line"> <span class="keyword">if</span> (!isLeaseExpirationEnabled()) {</span><br><span class="line"> logger.debug(<span class="string">"DS: lease expiration is currently disabled."</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> List<Lease<InstanceInfo>> expiredLeases = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"> <span class="keyword">for</span> (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {</span><br><span class="line"> Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();</span><br><span class="line"> <span class="keyword">if</span> (leaseMap != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">for</span> (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {</span><br><span class="line"> Lease<InstanceInfo> lease = leaseEntry.getValue();</span><br><span class="line"> <span class="keyword">if</span> (lease.isExpired(additionalLeaseMs) && lease.getHolder() != <span class="keyword">null</span>) {</span><br><span class="line"> expiredLeases.add(lease);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> registrySize = (<span class="keyword">int</span>) getLocalRegistrySize();</span><br><span class="line"> <span class="keyword">int</span> registrySizeThreshold = (<span class="keyword">int</span>) (registrySize * serverConfig.getRenewalPercentThreshold());</span><br><span class="line"> <span class="keyword">int</span> evictionLimit = registrySize - registrySizeThreshold;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> toEvict = Math.min(expiredLeases.size(), evictionLimit);</span><br><span class="line"> <span class="keyword">if</span> (toEvict > <span class="number">0</span>) {</span><br><span class="line"> logger.info(<span class="string">"Evicting {} items (expired={}, evictionLimit={})"</span>, toEvict, expiredLeases.size(), evictionLimit);</span><br><span class="line"></span><br><span class="line"> Random random = <span class="keyword">new</span> Random(System.currentTimeMillis());</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < toEvict; i++) {</span><br><span class="line"> <span class="comment">// Pick a random item (Knuth shuffle algorithm)</span></span><br><span class="line"> <span class="keyword">int</span> next = i + random.nextInt(expiredLeases.size() - i);</span><br><span class="line"> Collections.swap(expiredLeases, i, next);</span><br><span class="line"> Lease<InstanceInfo> lease = expiredLeases.get(i);</span><br><span class="line"></span><br><span class="line"> String appName = lease.getHolder().getAppName();</span><br><span class="line"> String id = lease.getHolder().getId();</span><br><span class="line"> EXPIRED.increment();</span><br><span class="line"> logger.warn(<span class="string">"DS: Registry: expired lease for {}/{}"</span>, appName, id);</span><br><span class="line"> internalCancel(appName, id, <span class="keyword">false</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isLeaseExpirationEnabled</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!isSelfPreservationModeEnabled()) {</span><br><span class="line"> <span class="comment">// The self preservation mode is disabled, hence allowing the instances to expire.</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这行代码触发自我保护机制,期望的一分钟要有多少次心跳发送过来,所有服务实例一分钟得发送多少次心跳</span></span><br><span class="line"> <span class="comment">// getNumOfRenewsInLastMin 上一分钟所有服务实例一共发送过来多少心跳,10次</span></span><br><span class="line"> <span class="comment">// 如果上一分钟 的心跳次数太少了(20次)< 我期望的100次,此时会返回false</span></span><br><span class="line"> <span class="keyword">return</span> numberOfRenewsPerMinThreshold > <span class="number">0</span> && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li>首先看<code>isLeaseExpirationEnabled</code> 方法,这个方法是判断是否需要自我保护的,里面逻辑其实也很简单,获取山一分钟所有实例心跳的次数和<code>numberOfRenewsPerMinThreshold</code> (期望的每分钟所有实例心跳次数x85%) 进行对比,如果大于<code>numberOfRenewsPerMinThreshold</code> 才允许摘除实例,否则进入自我保护模式。下一节会详细讲解这个方法。</li><li>如果服务实例可以被移除,接着往下看,这里是遍历所有的服务注册信息,然后一个个遍历服务实例心跳时间是否超过了对应的时间,主要看 <code>lease.isExpired(additionalLeaseMs)</code> 方法:</li></ol><p><code>Lease.isExpired()</code> :</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Checks if the lease of a given {<span class="doctag">@link</span> com.netflix.appinfo.InstanceInfo} has expired or not.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Note that due to renew() doing the 'wrong" thing and setting lastUpdateTimestamp to +duration more than</span></span><br><span class="line"><span class="comment"> * what it should be, the expiry will actually be 2 * duration. This is a minor bug and should only affect</span></span><br><span class="line"><span class="comment"> * instances that ungracefully shutdown. Due to possible wide ranging impact to existing usage, this will</span></span><br><span class="line"><span class="comment"> * not be fixed.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> additionalLeaseMs any additional lease time to add to the lease evaluation in ms.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isExpired</span><span class="params">(<span class="keyword">long</span> additionalLeaseMs)</span> </span>{</span><br><span class="line"> <span class="comment">// lastUpdateTimestamp renew成功后就会刷新这个时间,可以理解为最近一次活跃时间</span></span><br><span class="line"> <span class="comment">// 查看 Lease.renew方法:lastUpdateTimestamp = System.currentTimeMillis() + duration;</span></span><br><span class="line"> <span class="comment">// duration可以查看为:LeaseInfo中的DEFAULT_LEASE_RENEWAL_INTERVAL=90s 默认为90s</span></span><br><span class="line"> <span class="comment">// 这段逻辑为 当前时间 > 上一次心跳时间 + 90s + 补偿时间</span></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 这里先不看补偿时间,假设补偿时间为0,这段的含义是 如果当前时间大于上次续约的时间+90s,那么就认为该实例过期了</span></span><br><span class="line"><span class="comment"> * 因为lastUpdateTimestamp=System.currentTimeMillis()+duration,所以这里可以理解为 超过180是还没有续约,那么就认为该服务实例过期了</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * additionalLeaseMs 时间是一个容错的机制,也是服务保持最终一致性的一种手段,针对于定时任务 因为一些不可控原因在某些时间点没有定时执行,那么这个就是很好的容错机制</span></span><br><span class="line"><span class="comment"> * 这段代码 意思现在理解为:服务如果宕机了,那么最少180s 才会被注册中心摘除掉</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">return</span> (evictionTimestamp > <span class="number">0</span> || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里注释已经写得很清楚了,<code>System.currentTimeMillis() > lastUpdateTimestamp + duration + additionalLeaseMs</code> 如果将补偿时间记为0,那么这段代码的含义是 <strong>如果服务如果宕机了,那么最少180s 才会被注册中心摘除掉</strong></p><p>上面这段代码翻译完了,接着看一个<strong>彩蛋</strong><br> 看这段代码注释,我先谷歌翻译给大家看下:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200101100649867-1806975433.png" alt="image.png"></p><p>翻译的不是很好,我再来说下,这里说的是在<code>renew()</code> 方法中,我们写了一个bug,那里不应该多加一个duration(默认90s)时间的,加上了会导致这里duration * 2了,所以也就是至少180s才会被摘除。但是又由于修改会产生其他的问题,所以我们不予修改。</p><p>顺便看下<code>renew()</code> 做了什么错事:</p><p><img src="https://img2018.cnblogs.com/blog/799093/202001/799093-20200101100651074-1725687706.png" alt="image.png"></p><p>这里确实多给加了一个duration。</p><p>这里接着看<code>evict()</code>后面的操作:</p><ol><li>将所有需要摘除的服务实例放到<code>expiredLeases</code> 集合中去</li><li>计算服务摘除的阈值,<code>registrySizeThreshold</code> 为注册实例总数量 * 85%</li><li>计算最多可摘除的服务实例个数:总数量 - 总数量 * 85%<br> 这里实则也是一种保护机制,即使我很多服务宕机了,但是最多只能摘除15%的服务实例。</li><li>随机摘取指定的服务实例数量,然后遍历调用<code>internalCancel</code> 方法来remove宕机的服务实例, 这里就是上面讲解的服务下线调用的方法</li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>分析完了上面所有的代码 是不是有一种大跌眼镜的感觉?我们现在查看的版本确实还存在bug的,有一些bug在master中已经被修复,但仍有些存在。后面一讲会重点跟进这些问题。</p><p>接下来就回答开头抛出来的一个问题了:</p><p><strong>例如我有服务A、服务B,A、B都注册在同一个注册中心,当B下线后,A多久能感知到B已经下线了呢?</strong></p><p>答案是:最快180s才会被感知。如果有补偿时间,或者服务摘除的时候 计算随机摘除服务的时候 没有摘除此服务,那么又会等待180s 来摘除。所以这个只能说一个最块180被感知到。</p><p>这一讲还是写了很多,其实这里面包含了很多下一讲的内容,下一讲会对本讲做一个补充。敬请期待。</p>]]></content>
<summary type="html">
<h1 id="Nexflix-Eureka-源码九:服务下线及实例摘除"><a href="#Nexflix-Eureka-源码九:服务下线及实例摘除" class="headerlink" title="[Nexflix Eureka 源码九:服务下线及实例摘除]"></a>[Nexflix Eureka 源码九:服务下线及实例摘除]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲我们讲了 client端向server端发送心跳检查,也是默认每30钟发送一次,server端接收后会更新注册表的一个时间戳属性,然后一次心跳(续约)也就完成了。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Nexflix Eureka" scheme="http://yoursite.com/tags/Nexflix-Eureka/"/>
</entry>
<entry>
<title>Nexflix Eureka 源码八:服务续约源码分析</title>
<link href="http://yoursite.com/2020/01/10/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%85%AB%EF%BC%9A%E6%9C%8D%E5%8A%A1%E7%BB%AD%E7%BA%A6%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90]/"/>
<id>http://yoursite.com/2020/01/10/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%85%AB%EF%BC%9A%E6%9C%8D%E5%8A%A1%E7%BB%AD%E7%BA%A6%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90]/</id>
<published>2020-01-10T14:05:37.000Z</published>
<updated>2020-03-01T15:12:11.972Z</updated>
<content type="html"><![CDATA[<h1 id="Nexflix-Eureka-源码八:服务续约源码分析"><a href="#Nexflix-Eureka-源码八:服务续约源码分析" class="headerlink" title="[Nexflix Eureka 源码八:服务续约源码分析]"></a>[Nexflix Eureka 源码八:服务续约源码分析]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲 我们讲解了服务发现的相关逻辑,所谓服务发现 其实就是注册表抓取,服务实例默认每隔30s去注册中心抓取一下注册表增量数据,然后合并本地注册表数据,最后有个hash对比的操作。</p><a id="more"></a><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><p>今天主要是看下服务续约的逻辑,服务续约就是client端给server端发送心跳检测,告诉对方我还活着。现在很多分布式系统都会有心跳检查的机制,这里一起来学习下Eureka是怎么做心跳检查的。</p><p><strong>目录如下:</strong></p><ol><li>client端心跳检查调度任务</li><li>server端接收心跳检查,设置最后renew时间</li></ol><p>这一讲内容不太多,因为上一篇文章写全量和增量注册表信息内容有点多,所以这里将博客尽量一篇保持一个知识点,后面还会讲服务实例下线、摘除、注册中心自我保护等机制的实现原理。</p><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h4 id="client端心跳检查调度任务"><a href="#client端心跳检查调度任务" class="headerlink" title="client端心跳检查调度任务"></a>client端心跳检查调度任务</h4><p>服务实例续约代码比较简单,这里还是从<code>DiscovertClient.java</code> 开始,很多源码的入口都是在这里,因为client端初始化、注册 都是走的这里,因为前几篇文章对这个类已经分析很多了,这里只截取部分重要代码:</p><p><code>DiscovertClient.java</code> 初始化后 会继续初始化一些调度任务:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">initScheduledTasks</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (clientConfig.shouldRegisterWithEureka()) {</span><br><span class="line"> <span class="comment">// 默认也是30s</span></span><br><span class="line"> <span class="keyword">int</span> renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();</span><br><span class="line"> <span class="keyword">int</span> expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();</span><br><span class="line"> logger.info(<span class="string">"Starting heartbeat executor: "</span> + <span class="string">"renew interval is: "</span> + renewalIntervalInSecs);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Heartbeat timer</span></span><br><span class="line"> <span class="comment">// 执行heartbeatExecutor心跳检查,默认是30s</span></span><br><span class="line"> scheduler.schedule(</span><br><span class="line"> <span class="keyword">new</span> TimedSupervisorTask(</span><br><span class="line"> <span class="string">"heartbeat"</span>,</span><br><span class="line"> scheduler,</span><br><span class="line"> heartbeatExecutor,</span><br><span class="line"> renewalIntervalInSecs,</span><br><span class="line"> TimeUnit.SECONDS,</span><br><span class="line"> expBackOffBound,</span><br><span class="line"> <span class="keyword">new</span> HeartbeatThread()</span><br><span class="line"> ),</span><br><span class="line"> renewalIntervalInSecs, TimeUnit.SECONDS);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 执行线程</span></span><br><span class="line"> instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> logger.info(<span class="string">"Not registering with Eureka server per configuration"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">HeartbeatThread</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (renew()) {</span><br><span class="line"> lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">renew</span><span class="params">()</span> </span>{</span><br><span class="line"> EurekaHttpResponse<InstanceInfo> httpResponse;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, <span class="keyword">null</span>);</span><br><span class="line"> logger.debug(<span class="string">"{} - Heartbeat status: {}"</span>, PREFIX + appPathIdentifier, httpResponse.getStatusCode());</span><br><span class="line"> <span class="keyword">if</span> (httpResponse.getStatusCode() == <span class="number">404</span>) {</span><br><span class="line"> REREGISTER_COUNTER.increment();</span><br><span class="line"> logger.info(<span class="string">"{} - Re-registering apps/{}"</span>, PREFIX + appPathIdentifier, instanceInfo.getAppName());</span><br><span class="line"> <span class="keyword">long</span> timestamp = instanceInfo.setIsDirtyWithTime();</span><br><span class="line"> <span class="keyword">boolean</span> success = register();</span><br><span class="line"> <span class="keyword">if</span> (success) {</span><br><span class="line"> instanceInfo.unsetIsDirty(timestamp);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> success;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> httpResponse.getStatusCode() == <span class="number">200</span>;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> logger.error(<span class="string">"{} - was unable to send heartbeat!"</span>, PREFIX + appPathIdentifier, e);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> EurekaHttpResponse<InstanceInfo> <span class="title">sendHeartBeat</span><span class="params">(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus)</span> </span>{</span><br><span class="line"> String urlPath = <span class="string">"apps/"</span> + appName + <span class="string">'/'</span> + id;</span><br><span class="line"> Response response = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> WebTarget webResource = jerseyClient.target(serviceUrl)</span><br><span class="line"> .path(urlPath)</span><br><span class="line"> .queryParam(<span class="string">"status"</span>, info.getStatus().toString())</span><br><span class="line"> .queryParam(<span class="string">"lastDirtyTimestamp"</span>, info.getLastDirtyTimestamp().toString());</span><br><span class="line"> <span class="keyword">if</span> (overriddenStatus != <span class="keyword">null</span>) {</span><br><span class="line"> webResource = webResource.queryParam(<span class="string">"overriddenstatus"</span>, overriddenStatus.name());</span><br><span class="line"> }</span><br><span class="line"> Builder requestBuilder = webResource.request();</span><br><span class="line"> addExtraProperties(requestBuilder);</span><br><span class="line"> addExtraHeaders(requestBuilder);</span><br><span class="line"> requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE);</span><br><span class="line"> response = requestBuilder.put(Entity.entity(<span class="string">"{}"</span>, MediaType.APPLICATION_JSON_TYPE)); <span class="comment">// Jersey2 refuses to handle PUT with no body</span></span><br><span class="line"> EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo<span class="class">.<span class="keyword">class</span>).<span class="title">headers</span>(<span class="title">headersOf</span>(<span class="title">response</span>))</span>;</span><br><span class="line"> <span class="keyword">if</span> (response.hasEntity()) {</span><br><span class="line"> eurekaResponseBuilder.entity(response.readEntity(InstanceInfo<span class="class">.<span class="keyword">class</span>))</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> eurekaResponseBuilder.build();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (logger.isDebugEnabled()) {</span><br><span class="line"> logger.debug(<span class="string">"Jersey2 HTTP PUT {}/{}; statusCode={}"</span>, serviceUrl, urlPath, response == <span class="keyword">null</span> ? <span class="string">"N/A"</span> : response.getStatus());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (response != <span class="keyword">null</span>) {</span><br><span class="line"> response.close();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的流程很简单,初始化<code>DiscoveryClient</code> 后会新建一个调度任务,然后执行<code>HeartbeatThread</code>中的run方法,默认是<code>renewalIntervalInSecs</code> 30s执行一次。<br> 具体就是给Server端发送一个http请求,类似于:<code>http://localhost:8080/v2/apps/ServiceA/i-000000-1</code>, 走的是put请求。<br> 最后拿到响应结果,续约成功后会更新<code>lastSuccessfulHeartbeatTimestamp</code> 最近成功心跳检测的时间戳。</p><h4 id="server端接收心跳检查请求"><a href="#server端接收心跳检查请求" class="headerlink" title="server端接收心跳检查请求"></a>server端接收心跳检查请求</h4><p>前几篇文章已经说过,Server端接收http请求的入口在<code>eureka-core</code>模块下的 <code>resource</code>包里面,这里直接找到<code>ApplicationResource.java</code>中的<code>getInstanceInfo</code> 方法,这里直接请求的<code>InstanceResource</code> 类的构造方法,找到这个方法中的<code>@PUT</code>请求。可以直接看下代码:</p><p><code>InstanceResource.renewLease</code> +<code>AbstractInstanceRegistry.renew</code> 方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@PUT</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Response <span class="title">renewLease</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params"> @HeaderParam(PeerEurekaNode.HEADER_REPLICATION)</span> String isReplication,</span></span><br><span class="line"><span class="function"> @<span class="title">QueryParam</span><span class="params">(<span class="string">"overriddenstatus"</span>)</span> String overriddenStatus,</span></span><br><span class="line"><span class="function"> @<span class="title">QueryParam</span><span class="params">(<span class="string">"status"</span>)</span> String status,</span></span><br><span class="line"><span class="function"> @<span class="title">QueryParam</span><span class="params">(<span class="string">"lastDirtyTimestamp"</span>)</span> String lastDirtyTimestamp) </span>{</span><br><span class="line"> <span class="keyword">boolean</span> isFromReplicaNode = <span class="string">"true"</span>.equals(isReplication);</span><br><span class="line"> <span class="keyword">boolean</span> isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略部分代码</span></span><br><span class="line"></span><br><span class="line"> logger.debug(<span class="string">"Found (Renew): {} - {}; reply status={}"</span> + app.getName(), id, response.getStatus());</span><br><span class="line"> <span class="keyword">return</span> response;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">renew</span><span class="params">(String appName, String id, <span class="keyword">boolean</span> isReplication)</span> </span>{</span><br><span class="line"> RENEW.increment(isReplication);</span><br><span class="line"> Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);</span><br><span class="line"> Lease<InstanceInfo> leaseToRenew = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (gMap != <span class="keyword">null</span>) {</span><br><span class="line"> leaseToRenew = gMap.get(id);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (leaseToRenew == <span class="keyword">null</span>) {</span><br><span class="line"> RENEW_NOT_FOUND.increment(isReplication);</span><br><span class="line"> logger.warn(<span class="string">"DS: Registry: lease doesn't exist, registering resource: {} - {}"</span>, appName, id);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> InstanceInfo instanceInfo = leaseToRenew.getHolder();</span><br><span class="line"> <span class="keyword">if</span> (instanceInfo != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// touchASGCache(instanceInfo.getASGName());</span></span><br><span class="line"> InstanceStatus overriddenInstanceStatus = <span class="keyword">this</span>.getOverriddenInstanceStatus(</span><br><span class="line"> instanceInfo, leaseToRenew, isReplication);</span><br><span class="line"> <span class="keyword">if</span> (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {</span><br><span class="line"> logger.info(<span class="string">"Instance status UNKNOWN possibly due to deleted override for instance {}"</span></span><br><span class="line"> + <span class="string">"; re-register required"</span>, instanceInfo.getId());</span><br><span class="line"> RENEW_NOT_FOUND.increment(isReplication);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {</span><br><span class="line"> Object[] args = {</span><br><span class="line"> instanceInfo.getStatus().name(),</span><br><span class="line"> instanceInfo.getOverriddenStatus().name(),</span><br><span class="line"> instanceInfo.getId()</span><br><span class="line"> };</span><br><span class="line"> logger.info(</span><br><span class="line"> <span class="string">"The instance status {} is different from overridden instance status {} for instance {}. "</span></span><br><span class="line"> + <span class="string">"Hence setting the status to overridden status"</span>, args);</span><br><span class="line"> instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> renewsLastMin.increment();</span><br><span class="line"> leaseToRenew.renew();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里主要看<code>renew</code>方法, 这里看到<code>registry</code> 是一个注册表,通过appName获取对应的服务注册表信息。</p><p>这里主要还是看<code>leaseToRenew.renew()</code> 其实很简单,就是设置当前示例注册表的renew属性的<code>lastUpdateTimestamp</code> 为最新时间+duration。</p><p>至于这里的duration 我们下一讲会详细讲解,duration 和服务实例摘除有关。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>(1)DiscoveryClient初始化的时候,会去调度一堆定时任务,其中有一个就是HeartbeatThread,心跳线程</p><p>(2)在这里可以看到,默认是每隔30秒去发送一次心跳,每隔30秒执行一次HeartbeatTHread线程的逻辑,发送心跳</p><p>(3)这边的话就是去发送这个心跳,走的是EurekaHttpClient的sendHeartbeat()方法,<a href="http://localhost:8080/v2/apps/ServiceA/i-000000-1,走的是put请求" target="_blank" rel="noopener">http://localhost:8080/v2/apps/ServiceA/i-000000-1,走的是put请求</a></p><p>(4)负责承接服务实例的心跳相关的这些操作的,是ApplicationsResource,服务相关的controller。找到ApplicationResource,再次找到InstanceResource,通过PUT请求,可以找到renewLease方法。</p><p>(5)通过注册表的renew()方法,进去完成服务续约,实际进入AbstractInstanceRegistry的renew()方法</p><p>(6)从注册表的map中,根据服务名和实例id,获取一个Lease,实际的服务续约的逻辑,其实就是在Lease对象中,更新一下lastUpdateTimestamp这个时间戳,每次续约,就更新一下这个时间戳就ok了。</p>]]></content>
<summary type="html">
<h1 id="Nexflix-Eureka-源码八:服务续约源码分析"><a href="#Nexflix-Eureka-源码八:服务续约源码分析" class="headerlink" title="[Nexflix Eureka 源码八:服务续约源码分析]"></a>[Nexflix Eureka 源码八:服务续约源码分析]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲 我们讲解了服务发现的相关逻辑,所谓服务发现 其实就是注册表抓取,服务实例默认每隔30s去注册中心抓取一下注册表增量数据,然后合并本地注册表数据,最后有个hash对比的操作。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Nexflix Eureka" scheme="http://yoursite.com/tags/Nexflix-Eureka/"/>
</entry>
<entry>
<title>Nexflix Eureka 源码七:EurekaClient注册表抓取</title>
<link href="http://yoursite.com/2020/01/09/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E4%B8%83%EF%BC%9AEurekaClient%E6%B3%A8%E5%86%8C%E8%A1%A8%E6%8A%93%E5%8F%96%20%E7%B2%BE%E5%A6%99%E8%AE%BE%E8%AE%A1%E5%88%86%E6%9E%90%EF%BC%81]/"/>
<id>http://yoursite.com/2020/01/09/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E4%B8%83%EF%BC%9AEurekaClient%E6%B3%A8%E5%86%8C%E8%A1%A8%E6%8A%93%E5%8F%96%20%E7%B2%BE%E5%A6%99%E8%AE%BE%E8%AE%A1%E5%88%86%E6%9E%90%EF%BC%81]/</id>
<published>2020-01-09T14:05:37.000Z</published>
<updated>2020-03-01T15:12:02.202Z</updated>
<content type="html"><![CDATA[<h1 id="Nexflix-Eureka-源码七:EurekaClient注册表抓取"><a href="#Nexflix-Eureka-源码七:EurekaClient注册表抓取" class="headerlink" title="[Nexflix Eureka 源码七:EurekaClient注册表抓取]"></a>[Nexflix Eureka 源码七:EurekaClient注册表抓取]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲 我们通过单元测试 来梳理了EurekaClient是如何注册到server端,以及server端接收到请求是如何处理的,这里最重要的关注点是注册表的一个数据结构:<code>ConcurrentHashMap>>()</code></p><a id="more"></a><h4 id="本讲目录"><a href="#本讲目录" class="headerlink" title="本讲目录"></a>本讲目录</h4><ol><li>client端第一次注册全量抓取注册表的逻辑</li><li>server端返回注册表信息集合的多级缓存机制</li><li>server端注册表多级缓存过期机制:主动+定时+被动</li><li>client端增量抓取注册表逻辑</li></ol><p><strong>技术亮点:</strong></p><ol><li>注册表抓取的多级缓存机制</li><li>增量抓取返回的全量数据hashCode,和本地数据hashCode对比,保证数据一致性</li></ol><p>这里再啰嗦一点,之前一直吐槽EurekaClient注册的逻辑,今天看了EurekaClient注册表抓取的逻辑后,不由的感叹设计的精妙之处,这里说的精妙是指EurekaServer端对于注册表读取逻辑的设计,缓存逻辑以及增量获取时Hash一致性的判断,真的很妙,感觉又学到了不少东西。</p><h3 id="EurekaClient全量抓取注册表逻辑"><a href="#EurekaClient全量抓取注册表逻辑" class="headerlink" title="EurekaClient全量抓取注册表逻辑"></a>EurekaClient全量抓取注册表逻辑</h3><p>一直在想着怎么才能把自己看完代码后的理解用文字表达出来,这里采用一种新模式吧,先画图,然后源码,然后解读。</p><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191230100153319-2129447041.png" alt="04_EurekaClient注册表全量抓取逻辑.png"></p><p>图片看起来很简单,Client发送Http请求给Server端,Server端返回全量的注册表信息给Client端。接下来就是跟进代码一步步分析,这里先有个大概印象</p><h4 id="源码解析"><a href="#源码解析" class="headerlink" title="源码解析"></a>源码解析</h4><ol><li>Client端发送获取全量注册表请求</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Inject</span></span><br><span class="line">DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,</span><br><span class="line"> Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略很多无关代码</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (clientConfig.shouldFetchRegistry() && !fetchRegistry(<span class="keyword">false</span>)) {</span><br><span class="line"> fetchRegistryFromBackup();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">fetchRegistry</span><span class="params">(<span class="keyword">boolean</span> forceFullRegistryFetch)</span> </span>{</span><br><span class="line"> Stopwatch tracer = FETCH_REGISTRY_TIMER.start();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// If the delta is disabled or if it is the first time, get all</span></span><br><span class="line"> <span class="comment">// applications</span></span><br><span class="line"> Applications applications = getApplications();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (clientConfig.shouldDisableDelta()</span><br><span class="line"> || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))</span><br><span class="line"> || forceFullRegistryFetch</span><br><span class="line"> || (applications == <span class="keyword">null</span>)</span><br><span class="line"> || (applications.getRegisteredApplications().size() == <span class="number">0</span>)</span><br><span class="line"> || (applications.getVersion() == -<span class="number">1</span>)) <span class="comment">//Client application does not have latest library supporting delta</span></span><br><span class="line"> {</span><br><span class="line"> logger.info(<span class="string">"Disable delta property : {}"</span>, clientConfig.shouldDisableDelta());</span><br><span class="line"> logger.info(<span class="string">"Single vip registry refresh property : {}"</span>, clientConfig.getRegistryRefreshSingleVipAddress());</span><br><span class="line"> logger.info(<span class="string">"Force full registry fetch : {}"</span>, forceFullRegistryFetch);</span><br><span class="line"> logger.info(<span class="string">"Application is null : {}"</span>, (applications == <span class="keyword">null</span>));</span><br><span class="line"> logger.info(<span class="string">"Registered Applications size is zero : {}"</span>,</span><br><span class="line"> (applications.getRegisteredApplications().size() == <span class="number">0</span>));</span><br><span class="line"> logger.info(<span class="string">"Application version is -1: {}"</span>, (applications.getVersion() == -<span class="number">1</span>));</span><br><span class="line"> getAndStoreFullRegistry();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> getAndUpdateDelta(applications);</span><br><span class="line"> }</span><br><span class="line"> applications.setAppsHashCode(applications.getReconcileHashCode());</span><br><span class="line"> logTotalInstances();</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> logger.error(PREFIX + <span class="string">"{} - was unable to refresh its cache! status = {}"</span>, appPathIdentifier, e.getMessage(), e);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (tracer != <span class="keyword">null</span>) {</span><br><span class="line"> tracer.stop();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 删减掉一些代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// registry was fetched successfully, so return true</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">getAndStoreFullRegistry</span><span class="params">()</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> <span class="keyword">long</span> currentUpdateGeneration = fetchRegistryGeneration.get();</span><br><span class="line"></span><br><span class="line"> logger.info(<span class="string">"Getting all instance registry info from the eureka server"</span>);</span><br><span class="line"></span><br><span class="line"> Applications apps = <span class="keyword">null</span>;</span><br><span class="line"> EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == <span class="keyword">null</span></span><br><span class="line"> ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())</span><br><span class="line"> : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());</span><br><span class="line"> <span class="keyword">if</span> (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {</span><br><span class="line"> apps = httpResponse.getEntity();</span><br><span class="line"> }</span><br><span class="line"> logger.info(<span class="string">"The response status is {}"</span>, httpResponse.getStatusCode());</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (apps == <span class="keyword">null</span>) {</span><br><span class="line"> logger.error(<span class="string">"The application is null for some reason. Not storing this information"</span>);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + <span class="number">1</span>)) {</span><br><span class="line"> localRegionApps.set(<span class="keyword">this</span>.filterAndShuffle(apps));</span><br><span class="line"> logger.debug(<span class="string">"Got full registry with apps hashcode {}"</span>, apps.getAppsHashCode());</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> logger.warn(<span class="string">"Not updating applications as another thread is updating it already"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里就不再赘述Client端是如何一步步跟进到发请求的代码的,因为之前通过单元测试代码已经搞清楚了Server端接受请求的类是<code>ApplicationsResource.java</code>, Client端主要核心的代码也在 <code>DiscoveryClient.java</code>中。</p><p>代码还是之前看了好多遍的祖传代码,只是省略了很多内容,只展示我们需要分析的地方。<br> <code>clientConfig.shouldFetchRegistry()</code> 这个配置默认是true,然后<code>fetchRegistry</code>方法中<code>getAndStoreFullRegistry()</code>,因为第一次都是获取全量注册表信息,继续往后。</p><p><code>getAndStoreFullRegistry</code> 方法中可以看到就是发送Http请求给Server端,然后等待Server端返回全量注册表信息。</p><p>这里获取全量请求执行的是<code>eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())</code></p><p>然后再一路往下,跟踪到 <code>AbstractJersey2EurekaHttpClient.java</code>中,<code>getApplicationsInternal</code>方法,发下发送的是<code>GET</code>请求,于是到Server端<code>ApplicationsResource.java</code>中的<code>GET</code>方法<code>getContainers</code>中查看逻辑</p><h3 id="server端返回注册表信息集合的多级缓存机制"><a href="#server端返回注册表信息集合的多级缓存机制" class="headerlink" title="server端返回注册表信息集合的多级缓存机制"></a>server端返回注册表信息集合的多级缓存机制</h3><p>上面已经看了Client端 发送抓取全量注册表的逻辑,到了Server端查看<code>ApplicationsResource.java</code>中的<code>GET</code>方法<code>getContainers</code>,接着看看这部分的源码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ResponseCache responseCache;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GET</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Response <span class="title">getContainers</span><span class="params">(@PathParam(<span class="string">"version"</span>)</span> String version,</span></span><br><span class="line"><span class="function"> @<span class="title">HeaderParam</span><span class="params">(HEADER_ACCEPT)</span> String acceptHeader,</span></span><br><span class="line"><span class="function"> @<span class="title">HeaderParam</span><span class="params">(HEADER_ACCEPT_ENCODING)</span> String acceptEncoding,</span></span><br><span class="line"><span class="function"> @<span class="title">HeaderParam</span><span class="params">(EurekaAccept.HTTP_X_EUREKA_ACCEPT)</span> String eurekaAccept,</span></span><br><span class="line"><span class="function"> @Context UriInfo uriInfo,</span></span><br><span class="line"><span class="function"> @Nullable @<span class="title">QueryParam</span><span class="params">(<span class="string">"regions"</span>)</span> String regionsStr) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略部分代码</span></span><br><span class="line"></span><br><span class="line"> Key cacheKey = <span class="keyword">new</span> Key(Key.EntityType.Application,</span><br><span class="line"> ResponseCacheImpl.ALL_APPS,</span><br><span class="line"> keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> Response response;</span><br><span class="line"> <span class="keyword">if</span> (acceptEncoding != <span class="keyword">null</span> && acceptEncoding.contains(HEADER_GZIP_VALUE)) {</span><br><span class="line"> response = Response.ok(responseCache.getGZIP(cacheKey))</span><br><span class="line"> .header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)</span><br><span class="line"> .header(HEADER_CONTENT_TYPE, returnMediaType)</span><br><span class="line"> .build();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> response = Response.ok(responseCache.get(cacheKey))</span><br><span class="line"> .build();</span><br><span class="line"> }</span><br><span class="line"> CurrentRequestVersion.remove();</span><br><span class="line"> <span class="keyword">return</span> response;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里接收到Client端的请求后,会去<code>responseCache</code> 中去拿去全量的数据信息。<br> 从属性名字就可以看出来,这个是从缓存中获取数据。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">ResponseCacheImpl.java</span><br><span class="line"><span class="function">String <span class="title">get</span><span class="params">(<span class="keyword">final</span> Key key, <span class="keyword">boolean</span> useReadOnlyCache)</span> </span>{</span><br><span class="line"> Value payload = getValue(key, useReadOnlyCache);</span><br><span class="line"> <span class="keyword">if</span> (payload == <span class="keyword">null</span> || payload.getPayload().equals(EMPTY_PAYLOAD)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> payload.getPayload();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function">Value <span class="title">getValue</span><span class="params">(<span class="keyword">final</span> Key key, <span class="keyword">boolean</span> useReadOnlyCache)</span> </span>{</span><br><span class="line"> Value payload = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (useReadOnlyCache) {</span><br><span class="line"> <span class="keyword">final</span> Value currentPayload = readOnlyCacheMap.get(key);</span><br><span class="line"> <span class="keyword">if</span> (currentPayload != <span class="keyword">null</span>) {</span><br><span class="line"> payload = currentPayload;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> payload = readWriteCacheMap.get(key);</span><br><span class="line"> readOnlyCacheMap.put(key, payload);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> payload = readWriteCacheMap.get(key);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable t) {</span><br><span class="line"> logger.error(<span class="string">"Cannot get value for key : {}"</span>, key, t);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> payload;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里主要关注<code>getValue</code>方法,这里主要有两个map,一个是<code>readOnlyCacheMap</code> 另一个是<code>readWriteCacheMap</code>, 这里我们光看名字就可以知道一个是只读缓存,一个是读写缓存,这里用了两层的缓存结构,如果只读缓存不为空 则直接返回,如果为空查询可读缓存。</p><p>关于缓存的讲解 我们继续往下看。</p><h3 id="server端注册表多级缓存过期机制:主动-定时-被动"><a href="#server端注册表多级缓存过期机制:主动-定时-被动" class="headerlink" title="server端注册表多级缓存过期机制:主动+定时+被动"></a>server端注册表多级缓存过期机制:主动+定时+被动</h3><p><strong>继续看缓存相关,用到了多级缓存这里可能就会存在一些疑问:</strong></p><ol><li>两级缓存数据如何保存同步?</li><li>缓存数据如何过期?</li></ol><p>带着疑问我们来继续看源代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ConcurrentMap<Key, Value> readOnlyCacheMap = <span class="keyword">new</span> ConcurrentHashMap<Key, Value>();</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> LoadingCache<Key, Value> readWriteCacheMap;</span><br><span class="line"></span><br><span class="line">ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {</span><br><span class="line"> <span class="comment">// 省略部分代码</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">long</span> responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();</span><br><span class="line"> <span class="keyword">this</span>.readWriteCacheMap =</span><br><span class="line"> CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache())</span><br><span class="line"> .expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)</span><br><span class="line"> .removalListener(<span class="keyword">new</span> RemovalListener<Key, Value>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onRemoval</span><span class="params">(RemovalNotification<Key, Value> notification)</span> </span>{</span><br><span class="line"> Key removedKey = notification.getKey();</span><br><span class="line"> <span class="keyword">if</span> (removedKey.hasRegions()) {</span><br><span class="line"> Key cloneWithNoRegions = removedKey.cloneWithoutRegions();</span><br><span class="line"> regionSpecificKeys.remove(cloneWithNoRegions, removedKey);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> .build(<span class="keyword">new</span> CacheLoader<Key, Value>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Value <span class="title">load</span><span class="params">(Key key)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">if</span> (key.hasRegions()) {</span><br><span class="line"> Key cloneWithNoRegions = key.cloneWithoutRegions();</span><br><span class="line"> regionSpecificKeys.put(cloneWithNoRegions, key);</span><br><span class="line"> }</span><br><span class="line"> Value value = generatePayload(key);</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略部分代码</span></span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li><code>readOnlyCacheMap</code>用的是ConcurrentHashMap,线程安全的。<br> <code>readWriteCacheMap</code>用的是GuavaCache,不懂的小伙伴可以自己阅读以下,我之前的博客也有讲解这个,这个是谷歌开源的Guava项目基于内存的缓存,其内部也是实现的Map结构。</li><li>主要重点我们来看下GuavaCache,这里初始化大小是<code>serverConfig.getInitialCapacityOfResponseCache()</code> 默认是1000,也是Map的初始大小。<br> <code>expireAfterWrite</code> 刷新时间是<code>serverConfig.getResponseCacheAutoExpirationInSeconds()</code>默认时间是180s。<br> 接着是build方法,这里获取注册表信息就是用的<code>generatePayload</code>方法,如果查询readWriteCacheMap中注册表信息为空,这会执行build方法。</li></ol><p>继续跟进<code>generatePayload</code>方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Value <span class="title">generatePayload</span><span class="params">(Key key)</span> </span>{</span><br><span class="line"> Stopwatch tracer = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> String payload;</span><br><span class="line"> <span class="keyword">switch</span> (key.getEntityType()) {</span><br><span class="line"> <span class="keyword">case</span> Application:</span><br><span class="line"> <span class="keyword">boolean</span> isRemoteRegionRequested = key.hasRegions();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (ALL_APPS.equals(key.getName())) {</span><br><span class="line"> <span class="keyword">if</span> (isRemoteRegionRequested) {</span><br><span class="line"> tracer = serializeAllAppsWithRemoteRegionTimer.start();</span><br><span class="line"> payload = getPayLoad(key, registry.getApplicationsFromMultipleRegions(key.getRegions()));</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> tracer = serializeAllAppsTimer.start();</span><br><span class="line"> payload = getPayLoad(key, registry.getApplications());</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (ALL_APPS_DELTA.equals(key.getName())) {</span><br><span class="line"> <span class="keyword">if</span> (isRemoteRegionRequested) {</span><br><span class="line"> tracer = serializeDeltaAppsWithRemoteRegionTimer.start();</span><br><span class="line"> versionDeltaWithRegions.incrementAndGet();</span><br><span class="line"> versionDeltaWithRegionsLegacy.incrementAndGet();</span><br><span class="line"> payload = getPayLoad(key,</span><br><span class="line"> registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> tracer = serializeDeltaAppsTimer.start();</span><br><span class="line"> versionDelta.incrementAndGet();</span><br><span class="line"> versionDeltaLegacy.incrementAndGet();</span><br><span class="line"> payload = getPayLoad(key, registry.getApplicationDeltas());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Value(payload);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (tracer != <span class="keyword">null</span>) {</span><br><span class="line"> tracer.stop();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个代码删减了一部分,到时增量抓取注册表也会走这个逻辑,<code>ALL_APPS</code>就是全量抓取,<code>ALL_APPS_DELTA</code>就是增量抓取的意思,这里先插个眼,一会增量抓取注册表的逻辑再回头看。</p><p>上面的逻辑我们只需要关注<code>registry.getApplicationsFromMultipleRegions</code> 即可,这个是获取注册表的逻辑。接着继续往下跟代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line">AbstractInstanceRegistry.java</span><br><span class="line"><span class="function"><span class="keyword">public</span> Applications <span class="title">getApplicationsFromMultipleRegions</span><span class="params">(String[] remoteRegions)</span> </span>{</span><br><span class="line"></span><br><span class="line"> Applications apps = <span class="keyword">new</span> Applications();</span><br><span class="line"> apps.setVersion(<span class="number">1L</span>);</span><br><span class="line"> <span class="keyword">for</span> (Entry<String, Map<String, Lease<InstanceInfo>>> entry : registry.entrySet()) {</span><br><span class="line"> Application app = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (entry.getValue() != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">for</span> (Entry<String, Lease<InstanceInfo>> stringLeaseEntry : entry.getValue().entrySet()) {</span><br><span class="line"> Lease<InstanceInfo> lease = stringLeaseEntry.getValue();</span><br><span class="line"> <span class="keyword">if</span> (app == <span class="keyword">null</span>) {</span><br><span class="line"> app = <span class="keyword">new</span> Application(lease.getHolder().getAppName());</span><br><span class="line"> }</span><br><span class="line"> app.addInstance(decorateInstanceInfo(lease));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (app != <span class="keyword">null</span>) {</span><br><span class="line"> apps.addApplication(app);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (includeRemoteRegion) {</span><br><span class="line"> <span class="keyword">for</span> (String remoteRegion : remoteRegions) {</span><br><span class="line"> RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != remoteRegistry) {</span><br><span class="line"> Applications remoteApps = remoteRegistry.getApplications();</span><br><span class="line"> <span class="keyword">for</span> (Application application : remoteApps.getRegisteredApplications()) {</span><br><span class="line"> <span class="keyword">if</span> (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {</span><br><span class="line"> logger.info(<span class="string">"Application {} fetched from the remote region {}"</span>,</span><br><span class="line"> application.getName(), remoteRegion);</span><br><span class="line"></span><br><span class="line"> Application appInstanceTillNow = apps.getRegisteredApplications(application.getName());</span><br><span class="line"> <span class="keyword">if</span> (appInstanceTillNow == <span class="keyword">null</span>) {</span><br><span class="line"> appInstanceTillNow = <span class="keyword">new</span> Application(application.getName());</span><br><span class="line"> apps.addApplication(appInstanceTillNow);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (InstanceInfo instanceInfo : application.getInstances()) {</span><br><span class="line"> appInstanceTillNow.addInstance(instanceInfo);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> logger.debug(<span class="string">"Application {} not fetched from the remote region {} as there exists a "</span></span><br><span class="line"> + <span class="string">"whitelist and this app is not in the whitelist."</span>,</span><br><span class="line"> application.getName(), remoteRegion);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> logger.warn(<span class="string">"No remote registry available for the remote region {}"</span>, remoteRegion);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> apps.setAppsHashCode(apps.getReconcileHashCode());</span><br><span class="line"> <span class="keyword">return</span> apps;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里再看到 <code>registry.entrySet()</code>是不是会特别亲切?<code>Map></code> 我们上一篇讲Client注册的时候 就是将注册信息放入到registry对应这个数据结构中的,果不其然,这里拿到所有的注册信息,然后封装到<code>Applications</code> 对象中的。</p><p>这里最后<code>apps.setAppsHashCode()</code>逻辑,先插个眼 后面讲增量同步有类似的逻辑,后面再回头看。接着再回头看 返回数据后 <code>readWriteCacheMap</code> 的操作逻辑。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (shouldUseReadOnlyResponseCache) {</span><br><span class="line"> timer.schedule(getCacheUpdateTask(),</span><br><span class="line"> <span class="keyword">new</span> Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)</span><br><span class="line"> + responseCacheUpdateIntervalMs),</span><br><span class="line"> responseCacheUpdateIntervalMs);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> TimerTask <span class="title">getCacheUpdateTask</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> TimerTask() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> logger.debug(<span class="string">"Updating the client cache from response cache"</span>);</span><br><span class="line"> <span class="keyword">for</span> (Key key : readOnlyCacheMap.keySet()) {</span><br><span class="line"> <span class="keyword">if</span> (logger.isDebugEnabled()) {</span><br><span class="line"> logger.debug(<span class="string">"Updating the client cache from response cache for key : {} {} {} {}"</span>,</span><br><span class="line"> key.getEntityType(), key.getName(), key.getVersion(), key.getType());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> CurrentRequestVersion.set(key.getVersion());</span><br><span class="line"> Value cacheValue = readWriteCacheMap.get(key);</span><br><span class="line"> Value currentCacheValue = readOnlyCacheMap.get(key);</span><br><span class="line"> <span class="keyword">if</span> (cacheValue != currentCacheValue) {</span><br><span class="line"> readOnlyCacheMap.put(key, cacheValue);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable th) {</span><br><span class="line"> logger.error(<span class="string">"Error while updating the client cache from response cache for key {}"</span>, key.toStringCompact(), th);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> CurrentRequestVersion.remove();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里是起了一个调度任务,会去定时比较一级和二级缓存是否一致,如果不一致 就会用二级缓存覆盖一级缓存。这就回答了上面的第一个问题,两级缓存一致性的问题,默认30s执行一次。所以这里仍会有问题,可能缓存在30s内会存在不一致的情况,这里用的是最终一致的思想。</p><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191230100155385-1911116225.png" alt="image.png"></p><p>紧接着 读写缓存获取到数据后再去回写只读缓存,这是上面<code>ResponseCacheImpl.java</code> 的逻辑,到了这里 全量抓取注册表的代码都已经看完了,这里主要的亮点是使用了两级缓存策略来返回对应的数据。</p><p>接着整理下过期的几个机制,也是回应上面抛出的第二个问题。</p><p><strong>用一张图作为总结:</strong></p><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191230100156361-820219091.png" alt="05_EurekaServer多节缓存过期机制.png"></p><ol><li><p>主动过期<br> readWriteCacheMap,读写缓存</p><p>有新的服务实例发生注册、下线、故障的时候,就会去刷新readWriteCacheMap(在Client注册的时候,AbstractInstanceRegistry中register方法最后会有一个invalidateCache()方法)</p><p>比如说现在有一个服务A,ServiceA,有一个新的服务实例,Instance010来注册了,注册完了之后,其实必须是得刷新这个缓存的,然后就会调用ResponseCache.invalidate(),将之前缓存好的ALL_APPS这个key对应的缓存,给他过期掉</p><p>将readWriteCacheMap中的ALL_APPS缓存key,对应的缓存给过期掉</p></li><li><p>定时过期</p><p>readWriteCacheMap在构建的时候,指定了一个自动过期的时间,默认值就是180秒,所以你往readWriteCacheMap中放入一个数据过后,自动会等180秒过后,就将这个数据给他过期了</p></li><li><p>被动过期</p><p>readOnlyCacheMap怎么过期呢?<br> 默认是每隔30秒,执行一个定时调度的线程任务,TimerTask,有一个逻辑,会每隔30秒,对readOnlyCacheMap和readWriteCacheMap中的数据进行一个比对,如果两块数据是不一致的,那么就将readWriteCacheMap中的数据放到readOnlyCacheMap中来。</p><p>比如说readWriteCacheMap中,ALL_APPS这个key对应的缓存没了,那么最多30秒过后,就会同步到readOnelyCacheMap中去。</p></li></ol><h3 id="client端增量抓取注册表逻辑"><a href="#client端增量抓取注册表逻辑" class="headerlink" title="client端增量抓取注册表逻辑"></a>client端增量抓取注册表逻辑</h3><p>上面抓取全量注册表的代码已经说了,这里来讲一下增量抓取,入口还是在<code>DiscoverClient.java</code><br> 中,当初始化完<code>DiscoverClient.java</code> 后会执行一个初始化定时任务的方法<code>initScheduledTasks()</code>, 其中这个里面就会每隔30s 增量抓取一次注册表信息。</p><p>这里就不跟着这里的逻辑一步步看了,看过上面的代码后 应该会对这里比较清晰了,这里我们直接看Server端代码了。</p><p>还记的我们上面插过的眼,获取全量用的是<code>ALL_APPS</code> 增量用的是<code>ALL_APPS_DELTA</code>, 所以我们这里只看增量的逻辑就行了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (ALL_APPS_DELTA.equals(key.getName())) {</span><br><span class="line"> <span class="keyword">if</span> (isRemoteRegionRequested) {</span><br><span class="line"> tracer = serializeDeltaAppsWithRemoteRegionTimer.start();</span><br><span class="line"> versionDeltaWithRegions.incrementAndGet();</span><br><span class="line"> versionDeltaWithRegionsLegacy.incrementAndGet();</span><br><span class="line"> payload = getPayLoad(key,</span><br><span class="line"> registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> tracer = serializeDeltaAppsTimer.start();</span><br><span class="line"> versionDelta.incrementAndGet();</span><br><span class="line"> versionDeltaLegacy.incrementAndGet();</span><br><span class="line"> payload = getPayLoad(key, registry.getApplicationDeltas());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面只是截取了部分代码,这里直接看主要的逻辑<code>registry.getApplicationDeltasFromMultipleRegions</code>即可,这个和全量的方法名只有一个Deltas的区别。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Applications <span class="title">getApplicationDeltasFromMultipleRegions</span><span class="params">(String[] remoteRegions)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == remoteRegions) {</span><br><span class="line"> remoteRegions = allKnownRemoteRegions; <span class="comment">// null means all remote regions.</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">boolean</span> includeRemoteRegion = remoteRegions.length != <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (includeRemoteRegion) {</span><br><span class="line"> GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS_DELTA.increment();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> GET_ALL_CACHE_MISS_DELTA.increment();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Applications apps = <span class="keyword">new</span> Applications();</span><br><span class="line"> apps.setVersion(responseCache.getVersionDeltaWithRegions().get());</span><br><span class="line"> Map<String, Application> applicationInstancesMap = <span class="keyword">new</span> HashMap<String, Application>();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> write.lock();</span><br><span class="line"> Iterator<RecentlyChangedItem> iter = <span class="keyword">this</span>.recentlyChangedQueue.iterator();</span><br><span class="line"> logger.debug(<span class="string">"The number of elements in the delta queue is :{}"</span>, <span class="keyword">this</span>.recentlyChangedQueue.size());</span><br><span class="line"> <span class="keyword">while</span> (iter.hasNext()) {</span><br><span class="line"> Lease<InstanceInfo> lease = iter.next().getLeaseInfo();</span><br><span class="line"> InstanceInfo instanceInfo = lease.getHolder();</span><br><span class="line"> logger.debug(<span class="string">"The instance id {} is found with status {} and actiontype {}"</span>,</span><br><span class="line"> instanceInfo.getId(), instanceInfo.getStatus().name(), instanceInfo.getActionType().name());</span><br><span class="line"> Application app = applicationInstancesMap.get(instanceInfo.getAppName());</span><br><span class="line"> <span class="keyword">if</span> (app == <span class="keyword">null</span>) {</span><br><span class="line"> app = <span class="keyword">new</span> Application(instanceInfo.getAppName());</span><br><span class="line"> applicationInstancesMap.put(instanceInfo.getAppName(), app);</span><br><span class="line"> apps.addApplication(app);</span><br><span class="line"> }</span><br><span class="line"> app.addInstance(<span class="keyword">new</span> InstanceInfo(decorateInstanceInfo(lease)));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (includeRemoteRegion) {</span><br><span class="line"> <span class="keyword">for</span> (String remoteRegion : remoteRegions) {</span><br><span class="line"> RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != remoteRegistry) {</span><br><span class="line"> Applications remoteAppsDelta = remoteRegistry.getApplicationDeltas();</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != remoteAppsDelta) {</span><br><span class="line"> <span class="keyword">for</span> (Application application : remoteAppsDelta.getRegisteredApplications()) {</span><br><span class="line"> <span class="keyword">if</span> (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {</span><br><span class="line"> Application appInstanceTillNow =</span><br><span class="line"> apps.getRegisteredApplications(application.getName());</span><br><span class="line"> <span class="keyword">if</span> (appInstanceTillNow == <span class="keyword">null</span>) {</span><br><span class="line"> appInstanceTillNow = <span class="keyword">new</span> Application(application.getName());</span><br><span class="line"> apps.addApplication(appInstanceTillNow);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (InstanceInfo instanceInfo : application.getInstances()) {</span><br><span class="line"> appInstanceTillNow.addInstance(<span class="keyword">new</span> InstanceInfo(instanceInfo));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Applications allApps = getApplicationsFromMultipleRegions(remoteRegions);</span><br><span class="line"> apps.setAppsHashCode(allApps.getReconcileHashCode());</span><br><span class="line"> <span class="keyword">return</span> apps;</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> write.unlock();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里代码还是比较多的,我们只需要抓住重点即可:</p><ol><li>从<code>recentlyChangedQueue</code>中获取注册信息,从名字可以看出来 这是最近改变的client注册信息的队列</li><li>使用writeLock,因为这里是获取增量注册信息,是从队列中获取,如果不加写锁,那么获取的时候又有新数据加入队列中,新数据会获取不到的</li></ol><p>基于上面第一点,我们来看看这个队列怎么做的:</p><ol><li>数据结构:<code>ConcurrentLinkedQueue recentlyChangedQueue</code></li><li><code>AbstractInstanceRegistry.java</code>初始化的时候会启动一个定时任务,默认30s中执行一次。如果注册时间小于当前时间的180s,就会放到这个队列中</li></ol><p><code>AbstractInstanceRegistry.java</code>具体代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="title">AbstractInstanceRegistry</span><span class="params">(EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.serverConfig = serverConfig;</span><br><span class="line"> <span class="keyword">this</span>.clientConfig = clientConfig;</span><br><span class="line"> <span class="keyword">this</span>.serverCodecs = serverCodecs;</span><br><span class="line"> <span class="keyword">this</span>.recentCanceledQueue = <span class="keyword">new</span> CircularQueue<Pair<Long, String>>(<span class="number">1000</span>);</span><br><span class="line"> <span class="keyword">this</span>.recentRegisteredQueue = <span class="keyword">new</span> CircularQueue<Pair<Long, String>>(<span class="number">1000</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.renewsLastMin = <span class="keyword">new</span> MeasuredRate(<span class="number">1000</span> * <span class="number">60</span> * <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.deltaRetentionTimer.schedule(getDeltaRetentionTask(),</span><br><span class="line"> serverConfig.getDeltaRetentionTimerIntervalInMs(),</span><br><span class="line"> serverConfig.getDeltaRetentionTimerIntervalInMs());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> TimerTask <span class="title">getDeltaRetentionTask</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> TimerTask() {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> Iterator<RecentlyChangedItem> it = recentlyChangedQueue.iterator();</span><br><span class="line"> <span class="keyword">while</span> (it.hasNext()) {</span><br><span class="line"> <span class="keyword">if</span> (it.next().getLastUpdateTime() <</span><br><span class="line"> System.currentTimeMillis() - serverConfig.getRetentionTimeInMSInDeltaQueue()) {</span><br><span class="line"> it.remove();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里就能看明白了,也就是说增量抓取会获取EurekaServer端3分钟内保存的变动的Client信息。<br> 最后还有一个亮点,我们上面说过,无论是全量抓取还是增量抓取,最后都会返回一个全量注册表的hash值,代码是<code>apps.setAppsHashCode(allApps.getReconcileHashCode());</code>, 其中apps就是返回的<code>Applications</code>中的属性,最后我们再看看这个hashCode的用法。</p><p>回到<code>DiscoveryClient.java</code>, 找到<code>refreshRegistry</code> 方法,然后一路跟踪到<code>getAndUpdateDelta</code>方法,这里具体代码我就不贴了,流程如下:</p><ol><li>获取delta增量数据</li><li>根据增量数据和本地注册表数据进行合并</li><li>计算中本地注册表信息的hashCode值</li><li>如果本地hashCode值和server端返回的hashCode值不一致则再全量获取一次注册表信息</li></ol><p>最后一张图总结增量注册表抓取逻辑:</p><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191230100157542-1986591842.png" alt="06_EurekaClient增量抓取注册表流程.png"></p><h3 id="总结-amp-感悟"><a href="#总结-amp-感悟" class="headerlink" title="总结&感悟"></a>总结&感悟</h3><p>这篇文章写得有点长了,确实自己也很用心去写了,我感觉这里多级缓存机制+增量数据Hash一致性的对比方案做的很优秀,如果要我做一个数据全量+增量同步 我也会借鉴这种方案。</p><p>看源码 能够学到的就是别人的设计思想。总结的部分可以看上面的一些图,注册表抓取的源码学习就到这了,后面 还准备看下心跳机制、保护机制、集群等等一些的源码。</p><p>这里读完源码之后会发下一个问题:</p><p>假设有服务实例注册、下线、故障,要调用这个服务的其他服务,可能会过30秒之后才能感知倒,为什么呢?因为这里再获取服务注册表的时候,有一个多级缓存的机制,最多是30秒后才会去更新一级缓存。</p>]]></content>
<summary type="html">
<h1 id="Nexflix-Eureka-源码七:EurekaClient注册表抓取"><a href="#Nexflix-Eureka-源码七:EurekaClient注册表抓取" class="headerlink" title="[Nexflix Eureka 源码七:EurekaClient注册表抓取]"></a>[Nexflix Eureka 源码七:EurekaClient注册表抓取]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><h4 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h4><p>上一讲 我们通过单元测试 来梳理了EurekaClient是如何注册到server端,以及server端接收到请求是如何处理的,这里最重要的关注点是注册表的一个数据结构:<code>ConcurrentHashMap&gt;&gt;()</code></p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Nexflix Eureka" scheme="http://yoursite.com/tags/Nexflix-Eureka/"/>
</entry>
<entry>
<title>Nexflix Eureka 源码六:通过单元测试来Debug Eureka注册过程</title>
<link href="http://yoursite.com/2020/01/07/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%85%AD%EF%BC%9A%E9%80%9A%E8%BF%87%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E6%9D%A5Debug%20Eureka%E6%B3%A8%E5%86%8C%E8%BF%87%E7%A8%8B]/"/>
<id>http://yoursite.com/2020/01/07/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%85%AD%EF%BC%9A%E9%80%9A%E8%BF%87%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E6%9D%A5Debug%20Eureka%E6%B3%A8%E5%86%8C%E8%BF%87%E7%A8%8B]/</id>
<published>2020-01-07T14:05:37.000Z</published>
<updated>2020-03-01T15:11:48.515Z</updated>
<content type="html"><![CDATA[<h1 id="Nexflix-Eureka-源码六:通过单元测试来Debug-Eureka注册过程"><a href="#Nexflix-Eureka-源码六:通过单元测试来Debug-Eureka注册过程" class="headerlink" title="[Nexflix Eureka 源码六:通过单元测试来Debug Eureka注册过程]"></a>[Nexflix Eureka 源码六:通过单元测试来Debug Eureka注册过程]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>上一讲eureka client是如何注册的,一直跟到源码发送http请求为止,当时看eureka client注册时如此费尽,光是找一个regiter的地方就找了半天,那么client端发送了http请求给server端,server端是如何处理的呢?</p><p>带着这么一个疑问 就开始今天源码的解读了。</p><a id="more"></a><h3 id="源码解读"><a href="#源码解读" class="headerlink" title="源码解读"></a>源码解读</h3><h4 id="从何读起?"><a href="#从何读起?" class="headerlink" title="从何读起?"></a>从何读起?</h4><p>上一讲我们知道,跟进client注册 一直到 <code>AbstractJersey2EurekaHttpClient.register</code>方法,这里先看下其中的源码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> EurekaHttpResponse<Void> <span class="title">register</span><span class="params">(InstanceInfo info)</span> </span>{</span><br><span class="line"> String urlPath = <span class="string">"apps/"</span> + info.getAppName();</span><br><span class="line"> Response response = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 发送请求,类似于:http://localhost:8080/v2/apps/ServiceA</span></span><br><span class="line"> <span class="comment">// 发送的是post请求,服务实例的对象被打成了一个json发送,包括自己的主机、ip、端口号</span></span><br><span class="line"> <span class="comment">// eureka server 就知道了这个ServiceA这个服务,有一个服务实例,比如是在192.168.31.109、host-01、8761端口</span></span><br><span class="line"> Builder resourceBuilder = jerseyClient.target(serviceUrl).path(urlPath).request();</span><br><span class="line"> addExtraProperties(resourceBuilder);</span><br><span class="line"> addExtraHeaders(resourceBuilder);</span><br><span class="line"> response = resourceBuilder</span><br><span class="line"> .accept(MediaType.APPLICATION_JSON)</span><br><span class="line"> .acceptEncoding(<span class="string">"gzip"</span>)</span><br><span class="line"> .post(Entity.json(info));</span><br><span class="line"> <span class="keyword">return</span> anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (logger.isDebugEnabled()) {</span><br><span class="line"> logger.debug(<span class="string">"Jersey2 HTTP POST {}/{} with instance {}; statusCode={}"</span>, serviceUrl, urlPath, info.getId(),</span><br><span class="line"> response == <span class="keyword">null</span> ? <span class="string">"N/A"</span> : response.getStatus());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (response != <span class="keyword">null</span>) {</span><br><span class="line"> response.close();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>那这种情况我们肯定可以猜测,server端应该有个controller来接收此http请求,然后默默的去做一些注册的逻辑。</p><p>紧接着我们从<code>/apps/</code>这个关键词入手,进行全局搜索:</p><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191229204947283-946476056.png" alt="image.png"></p><p>全局搜索结果如下,这里可以看到很多test 调用,这里框起来的一个是不是类似于我们controller接口的调用呢?直接点进去查看,然后一步步跟进。</p><h4 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h4><p>接着上面说的,跟进<code>ApplicationResource</code>这个类,可以找到如下方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Path</span>(<span class="string">"{appId}"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> ApplicationResource <span class="title">getApplicationResource</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params"> @PathParam(<span class="string">"version"</span>)</span> String version,</span></span><br><span class="line"><span class="function"> @<span class="title">PathParam</span><span class="params">(<span class="string">"appId"</span>)</span> String appId) </span>{</span><br><span class="line"> CurrentRequestVersion.set(Version.toEnum(version));</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ApplicationResource(appId, serverConfig, registry);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个appId可以理解为我们之前传递的appName,紧接着这里是直接构造了一个<code>ApplicationResource</code>实例,接着跟进代码,进入<code>ApplicationResource</code>中我们可以看到很多<code>@GET</code>、<code>@POST</code> 等restful接口,还记得上面我们register方法中,发送的http请求用的就是POST方法,所以我们这里直接看<code>@POST</code>请求</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@POST</span></span><br><span class="line"><span class="meta">@Consumes</span>({<span class="string">"application/json"</span>, <span class="string">"application/xml"</span>})</span><br><span class="line"><span class="function"><span class="keyword">public</span> Response <span class="title">addInstance</span><span class="params">(InstanceInfo info,</span></span></span><br><span class="line"><span class="function"><span class="params"> @HeaderParam(PeerEurekaNode.HEADER_REPLICATION)</span> String isReplication) </span>{</span><br><span class="line"> logger.debug(<span class="string">"Registering instance {} (replication={})"</span>, info.getId(), isReplication);</span><br><span class="line"> <span class="comment">// validate that the instanceinfo contains all the necessary required fields</span></span><br><span class="line"> <span class="keyword">if</span> (isBlank(info.getId())) {</span><br><span class="line"> <span class="keyword">return</span> Response.status(<span class="number">400</span>).entity(<span class="string">"Missing instanceId"</span>).build();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (isBlank(info.getHostName())) {</span><br><span class="line"> <span class="keyword">return</span> Response.status(<span class="number">400</span>).entity(<span class="string">"Missing hostname"</span>).build();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (isBlank(info.getIPAddr())) {</span><br><span class="line"> <span class="keyword">return</span> Response.status(<span class="number">400</span>).entity(<span class="string">"Missing ip address"</span>).build();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (isBlank(info.getAppName())) {</span><br><span class="line"> <span class="keyword">return</span> Response.status(<span class="number">400</span>).entity(<span class="string">"Missing appName"</span>).build();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (!appName.equals(info.getAppName())) {</span><br><span class="line"> <span class="keyword">return</span> Response.status(<span class="number">400</span>).entity(<span class="string">"Mismatched appName, expecting "</span> + appName + <span class="string">" but was "</span> + info.getAppName()).build();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (info.getDataCenterInfo() == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> Response.status(<span class="number">400</span>).entity(<span class="string">"Missing dataCenterInfo"</span>).build();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (info.getDataCenterInfo().getName() == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> Response.status(<span class="number">400</span>).entity(<span class="string">"Missing dataCenterInfo Name"</span>).build();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// handle cases where clients may be registering with bad DataCenterInfo with missing data</span></span><br><span class="line"> DataCenterInfo dataCenterInfo = info.getDataCenterInfo();</span><br><span class="line"> <span class="keyword">if</span> (dataCenterInfo <span class="keyword">instanceof</span> UniqueIdentifier) {</span><br><span class="line"> String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();</span><br><span class="line"> <span class="keyword">if</span> (isBlank(dataCenterInfoId)) {</span><br><span class="line"> <span class="keyword">boolean</span> experimental = <span class="string">"true"</span>.equalsIgnoreCase(serverConfig.getExperimental(<span class="string">"registration.validation.dataCenterInfoId"</span>));</span><br><span class="line"> <span class="keyword">if</span> (experimental) {</span><br><span class="line"> String entity = <span class="string">"DataCenterInfo of type "</span> + dataCenterInfo.getClass() + <span class="string">" must contain a valid id"</span>;</span><br><span class="line"> <span class="keyword">return</span> Response.status(<span class="number">400</span>).entity(entity).build();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (dataCenterInfo <span class="keyword">instanceof</span> AmazonInfo) {</span><br><span class="line"> AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;</span><br><span class="line"> String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);</span><br><span class="line"> <span class="keyword">if</span> (effectiveId == <span class="keyword">null</span>) {</span><br><span class="line"> amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> logger.warn(<span class="string">"Registering DataCenterInfo of type {} without an appropriate id"</span>, dataCenterInfo.getClass());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> registry.register(info, <span class="string">"true"</span>.equals(isReplication));</span><br><span class="line"> <span class="keyword">return</span> Response.status(<span class="number">204</span>).build(); <span class="comment">// 204 to be backwards compatible</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由于代码不是很长,这里都给截取出来了。其实这里做的事情就很简单了。</p><ol><li>做一些常规的chek,检查注册实例<code>InstanceInfo</code>的一些基本信息</li><li>DataCenter的相关操作,这里还涉及到亚马逊云,我们直接跳过</li><li><code>registry.register(info, "true".equals(isReplication));</code> 这里才是核心的注册,我们继续往下</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(<span class="keyword">final</span> InstanceInfo info, <span class="keyword">final</span> <span class="keyword">boolean</span> isReplication)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;</span><br><span class="line"> <span class="keyword">if</span> (info.getLeaseInfo() != <span class="keyword">null</span> && info.getLeaseInfo().getDurationInSecs() > <span class="number">0</span>) {</span><br><span class="line"> leaseDuration = info.getLeaseInfo().getDurationInSecs();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">super</span>.register(info, leaseDuration, isReplication);</span><br><span class="line"> replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, <span class="keyword">null</span>, isReplication);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(InstanceInfo registrant, <span class="keyword">int</span> leaseDuration, <span class="keyword">boolean</span> isReplication)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> read.lock();</span><br><span class="line"> Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());</span><br><span class="line"> REGISTER.increment(isReplication);</span><br><span class="line"> <span class="keyword">if</span> (gMap == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">final</span> ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = <span class="keyword">new</span> ConcurrentHashMap<String, Lease<InstanceInfo>>();</span><br><span class="line"> gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);</span><br><span class="line"> <span class="keyword">if</span> (gMap == <span class="keyword">null</span>) {</span><br><span class="line"> gMap = gNewMap;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());</span><br><span class="line"> <span class="comment">// Retain the last dirty timestamp without overwriting it, if there is already a lease</span></span><br><span class="line"> <span class="keyword">if</span> (existingLease != <span class="keyword">null</span> && (existingLease.getHolder() != <span class="keyword">null</span>)) {</span><br><span class="line"> Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();</span><br><span class="line"> Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();</span><br><span class="line"> logger.debug(<span class="string">"Existing lease found (existing={}, provided={}"</span>, existingLastDirtyTimestamp, registrationLastDirtyTimestamp);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted</span></span><br><span class="line"> <span class="comment">// InstanceInfo instead of the server local copy.</span></span><br><span class="line"> <span class="keyword">if</span> (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {</span><br><span class="line"> logger.warn(<span class="string">"There is an existing lease and the existing lease's dirty timestamp {} is greater"</span> +</span><br><span class="line"> <span class="string">" than the one that is being registered {}"</span>, existingLastDirtyTimestamp, registrationLastDirtyTimestamp);</span><br><span class="line"> logger.warn(<span class="string">"Using the existing instanceInfo instead of the new instanceInfo as the registrant"</span>);</span><br><span class="line"> registrant = existingLease.getHolder();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// The lease does not exist and hence it is a new registration</span></span><br><span class="line"> <span class="keyword">synchronized</span> (lock) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.expectedNumberOfRenewsPerMin > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// Since the client wants to cancel it, reduce the threshold</span></span><br><span class="line"> <span class="comment">// (1</span></span><br><span class="line"> <span class="comment">// for 30 seconds, 2 for a minute)</span></span><br><span class="line"> <span class="keyword">this</span>.expectedNumberOfRenewsPerMin = <span class="keyword">this</span>.expectedNumberOfRenewsPerMin + <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">this</span>.numberOfRenewsPerMinThreshold =</span><br><span class="line"> (<span class="keyword">int</span>) (<span class="keyword">this</span>.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> logger.debug(<span class="string">"No previous lease information found; it is new registration"</span>);</span><br><span class="line"> }</span><br><span class="line"> Lease<InstanceInfo> lease = <span class="keyword">new</span> Lease<InstanceInfo>(registrant, leaseDuration);</span><br><span class="line"> <span class="keyword">if</span> (existingLease != <span class="keyword">null</span>) {</span><br><span class="line"> lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());</span><br><span class="line"> }</span><br><span class="line"> gMap.put(registrant.getId(), lease);</span><br><span class="line"> <span class="keyword">synchronized</span> (recentRegisteredQueue) {</span><br><span class="line"> recentRegisteredQueue.add(<span class="keyword">new</span> Pair<Long, String>(</span><br><span class="line"> System.currentTimeMillis(),</span><br><span class="line"> registrant.getAppName() + <span class="string">"("</span> + registrant.getId() + <span class="string">")"</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// This is where the initial state transfer of overridden status happens</span></span><br><span class="line"> <span class="keyword">if</span> (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {</span><br><span class="line"> logger.debug(<span class="string">"Found overridden status {} for instance {}. Checking to see if needs to be add to the "</span></span><br><span class="line"> + <span class="string">"overrides"</span>, registrant.getOverriddenStatus(), registrant.getId());</span><br><span class="line"> <span class="keyword">if</span> (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {</span><br><span class="line"> logger.info(<span class="string">"Not found overridden id {} and hence adding it"</span>, registrant.getId());</span><br><span class="line"> overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());</span><br><span class="line"> <span class="keyword">if</span> (overriddenStatusFromMap != <span class="keyword">null</span>) {</span><br><span class="line"> logger.info(<span class="string">"Storing overridden status {} from map"</span>, overriddenStatusFromMap);</span><br><span class="line"> registrant.setOverriddenStatus(overriddenStatusFromMap);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Set the status based on the overridden status rules</span></span><br><span class="line"> InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);</span><br><span class="line"> registrant.setStatusWithoutDirty(overriddenInstanceStatus);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// If the lease is registered with UP status, set lease service up timestamp</span></span><br><span class="line"> <span class="keyword">if</span> (InstanceStatus.UP.equals(registrant.getStatus())) {</span><br><span class="line"> lease.serviceUp();</span><br><span class="line"> }</span><br><span class="line"> registrant.setActionType(ActionType.ADDED);</span><br><span class="line"> recentlyChangedQueue.add(<span class="keyword">new</span> RecentlyChangedItem(lease));</span><br><span class="line"> registrant.setLastUpdatedTimestamp();</span><br><span class="line"> invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());</span><br><span class="line"> logger.info(<span class="string">"Registered instance {}/{} with status {} (replication={})"</span>,</span><br><span class="line"> registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> read.unlock();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>到了这里东西就有点多了,我们慢慢梳理。</p><ol><li>reda.lock() 这里使用的是读锁,方便多个服务实例同时来注册</li><li>这里关键信息是registry的数据结构,同时这也是保存注册实例的对象。</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry</span><br><span class="line"> = <span class="keyword">new</span> ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();</span><br></pre></td></tr></table></figure><p>ConcurrentHashMap的key是appName<br> 第二层Map的key是appId,所以数据结构格式类似于:</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> “ServiceA”: {</span><br><span class="line"> “001”: Lease<InstanceInfo>,</span><br><span class="line"> “002”: Lease<InstanceInfo>,</span><br><span class="line"> “003”: Lease<InstanceInfo></span><br><span class="line"> },</span><br><span class="line"> “ServiceB”: {</span><br><span class="line"> “001”: Lease<InstanceInfo></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li>这里面还有两个队列<code>recentRegisteredQueue</code>、<code>recentlyChangedQueue</code>,其中registerQueue默认保存最近1000条注册的实例信息。</li><li>后面就是一些状态设置之类的操作</li></ol><h4 id="注册表使用场景"><a href="#注册表使用场景" class="headerlink" title="注册表使用场景"></a>注册表使用场景</h4><p>我们注册完成之后,打开eureka 后台配置页面,可以看到自己的实例已经在页面上了,那么这个东东是如何展示的呢?</p><p>我们都知道eureka-resources模块下有很多jsp信息,点开status.jsp查看一下:</p><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191229204951371-91712911.png" alt="image.png"></p><p>这里用到了 <code>serverContext.getRegistry().getSortedApplications()</code>, 然后在通过获取的<code>Applicaiton</code> 去执行<code>app.getInstances()</code>等到了所有大的服务实例信息。</p><p>这里我们还需要回头看下<code>EurekaBootStrap</code>中的代码,看看Application是如何来的。</p><p>从<code>PeerAwareInstanceRegistryImpl.java</code>的<code>getSortedApplications()</code>一直跟到 <code>AbstractInstanceRegistry.java</code>的<code>getApplicationsFromMultipleRegions()</code>,如下图所示:</p><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191229204955155-1709796290.png" alt="image.png"></p><p>看到这里是不是就真相大白了?<br> 这里再总结一下:</p><p>在jsp代码中,拿到了EurekaServerContext,所以之前为什么要将这个东东放到一个Holder里面去,就是随时都要从这个里面去获取一些数据</p><p>然后会从EurekaServerContext,获取到注册表,PeerAwareInstanceRegistry,注册表,从里面获取所有的服务信息,从底层的map数据结构中,获取所有的服务注册的信息,遍历,封装到一个叫Application的东西里去,一个Application就代表了一个服务,里面包含很多个服务实例。</p><h4 id="Eureka的服务注册流程图"><a href="#Eureka的服务注册流程图" class="headerlink" title="Eureka的服务注册流程图"></a>Eureka的服务注册流程图</h4><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191229204956521-2000791919.png" alt="image.png"></p><h3 id="申明"><a href="#申明" class="headerlink" title="申明"></a>申明</h3>]]></content>
<summary type="html">
<h1 id="Nexflix-Eureka-源码六:通过单元测试来Debug-Eureka注册过程"><a href="#Nexflix-Eureka-源码六:通过单元测试来Debug-Eureka注册过程" class="headerlink" title="[Nexflix Eureka 源码六:通过单元测试来Debug Eureka注册过程]"></a>[Nexflix Eureka 源码六:通过单元测试来Debug Eureka注册过程]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>上一讲eureka client是如何注册的,一直跟到源码发送http请求为止,当时看eureka client注册时如此费尽,光是找一个regiter的地方就找了半天,那么client端发送了http请求给server端,server端是如何处理的呢?</p>
<p>带着这么一个疑问 就开始今天源码的解读了。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Nexflix Eureka" scheme="http://yoursite.com/tags/Nexflix-Eureka/"/>
</entry>
<entry>
<title>Nexflix Eureka 源码五:在眼花缭乱的代码中,EurekaClient是如何注册的?</title>
<link href="http://yoursite.com/2020/01/05/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E4%BA%94%EF%BC%9A%E5%9C%A8%E7%9C%BC%E8%8A%B1%E7%BC%AD%E4%B9%B1%E7%9A%84%E4%BB%A3%E7%A0%81%E4%B8%AD%EF%BC%8CEurekaClient%E6%98%AF%E5%A6%82%E4%BD%95%E6%B3%A8%E5%86%8C%E7%9A%84%EF%BC%9F]/"/>
<id>http://yoursite.com/2020/01/05/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E4%BA%94%EF%BC%9A%E5%9C%A8%E7%9C%BC%E8%8A%B1%E7%BC%AD%E4%B9%B1%E7%9A%84%E4%BB%A3%E7%A0%81%E4%B8%AD%EF%BC%8CEurekaClient%E6%98%AF%E5%A6%82%E4%BD%95%E6%B3%A8%E5%86%8C%E7%9A%84%EF%BC%9F]/</id>
<published>2020-01-05T14:05:37.000Z</published>
<updated>2020-03-01T15:11:27.125Z</updated>
<content type="html"><![CDATA[<h1 id="Nexflix-Eureka-源码五:在眼花缭乱的代码中,EurekaClient是如何注册的?"><a href="#Nexflix-Eureka-源码五:在眼花缭乱的代码中,EurekaClient是如何注册的?" class="headerlink" title="[Nexflix Eureka 源码五:在眼花缭乱的代码中,EurekaClient是如何注册的?]"></a>[Nexflix Eureka 源码五:在眼花缭乱的代码中,EurekaClient是如何注册的?]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>上一讲已经讲解了EurekaClient的启动流程,到了这里已经有6篇Eureka源码分析的文章了,看了下之前的文章,感觉代码成分太多,会影响阅读,后面会只截取主要的代码,加上注释讲解。</p><p>这一讲看的是EurekaClient注册的流程,当然也是一块核心,标题为什么会写上眼花缭乱呢?关于EurekaClient注册的代码,真的不是这么容易被发现的。</p><a id="more"></a><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><p>如果是看过前面文章的同学,肯定会知道,Eureka Client启动流程最后是初始化DiscoveryClient这个类,那么我们就直接从这个类开始分析,后面代码都只截取重要代码,具体大家可以自行参照源码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">DiscoveryClient.java</span><br><span class="line"><span class="meta">@Inject</span></span><br><span class="line">DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,</span><br><span class="line"> Provider<BackupRegistry> backupRegistryProvider) {</span><br><span class="line"> <span class="comment">// 省略部分代码...</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.applicationInfoManager = applicationInfoManager;</span><br><span class="line"> <span class="comment">// 创建一个配置实例,这里面会有eureka的各种信息,看InstanceInfo类的注释为:The class that holds information required for registration with Eureka Server </span></span><br><span class="line"> <span class="comment">// and to be discovered by other components.</span></span><br><span class="line"> InstanceInfo myInfo = applicationInfoManager.getInfo();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略部分代码...</span></span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 支持底层的eureka client跟eureka server进行网络通信的组件</span></span><br><span class="line"> eurekaTransport = <span class="keyword">new</span> EurekaTransport();</span><br><span class="line"> <span class="comment">// 发送http请求,调用restful接口</span></span><br><span class="line"> scheduleServerEndpointTask(eurekaTransport, args);</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"Failed to initialize DiscoveryClient!"</span>, e);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 初始化调度任务</span></span><br><span class="line"> initScheduledTasks();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面省略了很多代码,这段代码在之前的几篇文章也都有提及,说实话看到这里 仍然一脸闷逼,入册的入口在哪呢?不急,下面慢慢分析。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">DiscoveryClient.java</span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">initScheduledTasks</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 省略大部分代码,这段代码是初始化eureka client的一些调度任务</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// InstanceInfo replicator</span></span><br><span class="line"> <span class="comment">// 创建服务拷贝副本</span></span><br><span class="line"> instanceInfoReplicator = <span class="keyword">new</span> InstanceInfoReplicator(</span><br><span class="line"> <span class="keyword">this</span>,</span><br><span class="line"> instanceInfo,</span><br><span class="line"> clientConfig.getInstanceInfoReplicationIntervalSeconds(),</span><br><span class="line"> <span class="number">2</span>); <span class="comment">// burstSize</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 执行线程 InitialInstanceInfoReplicationIntervalSeconds默认为40s</span></span><br><span class="line"> instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面仍然是DiscoveryClient中的源码,看方法名我们知道这里肯定是初始化EurekaClient启动时的相关定时任务的。<br> 这里主要是截取了<code>instanceInfoReplicator</code>初始化和执行<code>instanceInfoReplicator.start</code>的任务,</p><p>接着我们就可以顺着这个线先看看<code>InstatnceInfoReplicator</code>是何方神圣?</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * A task for updating and replicating the local instanceinfo to the remote server. Properties of this task are:</span></span><br><span class="line"><span class="comment"> * - configured with a single update thread to guarantee sequential update to the remote server</span></span><br><span class="line"><span class="comment"> * - update tasks can be scheduled on-demand via onDemandUpdate()</span></span><br><span class="line"><span class="comment"> * - task processing is rate limited by burstSize</span></span><br><span class="line"><span class="comment"> * - a new update task is always scheduled automatically after an earlier update task. However if an on-demand task</span></span><br><span class="line"><span class="comment"> * is started, the scheduled automatic update task is discarded (and a new one will be scheduled after the new</span></span><br><span class="line"><span class="comment"> * on-demand update).</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 用于将本地instanceinfo更新和复制到远程服务器的任务。此任务的属性是:</span></span><br><span class="line"><span class="comment"> * -配置有单个更新线程以保证对远程服务器的顺序更新</span></span><br><span class="line"><span class="comment"> * -可以通过onDemandUpdate()按需调度更新任务</span></span><br><span class="line"><span class="comment"> * -任务处理的速率受burstSize的限制</span></span><br><span class="line"><span class="comment"> * -新更新总是在较早的更新任务之后自动计划任务。但是,如果启动了按需任务*,则计划的自动更新任务将被丢弃(并且将在新的按需更新之后安排新的任务)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">InstanceInfoReplicator</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里有两个关键点:</p><ol><li>此类实现了<code>Runnable</code>接口,说白了就是执行一个异步线程</li><li>该类作用是:用于将本地instanceinfo更新和复制到远程服务器的任务</li></ol><p>看完这两点,我又不禁陷入思考,我找的是eurekaClient注册过程,咋还跑到这个里面来了?不甘心,于是继续往下看。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">InstanceInfoReplicator.start()</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">(<span class="keyword">int</span> initialDelayMs)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (started.compareAndSet(<span class="keyword">false</span>, <span class="keyword">true</span>)) {</span><br><span class="line"> instanceInfo.setIsDirty(); <span class="comment">// for initial register</span></span><br><span class="line"> Future next = scheduler.schedule(<span class="keyword">this</span>, initialDelayMs, TimeUnit.SECONDS);</span><br><span class="line"> scheduledPeriodicRef.set(next);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个scheduler是一个调度任务线程池,会将this线程放入到线程池中,然后再指定时间后执行该线程的run方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">InstanceInfoReplicator.run()</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 刷新一下服务实例信息</span></span><br><span class="line"> discoveryClient.refreshInstanceInfo();</span><br><span class="line"></span><br><span class="line"> Long dirtyTimestamp = instanceInfo.isDirtyWithTime();</span><br><span class="line"> <span class="keyword">if</span> (dirtyTimestamp != <span class="keyword">null</span>) {</span><br><span class="line"> discoveryClient.register();</span><br><span class="line"> instanceInfo.unsetIsDirty(dirtyTimestamp);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable t) {</span><br><span class="line"> logger.warn(<span class="string">"There was a problem with the instance info replicator"</span>, t);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> Future next = scheduler.schedule(<span class="keyword">this</span>, replicationIntervalSeconds, TimeUnit.SECONDS);</span><br><span class="line"> scheduledPeriodicRef.set(next);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看到这里是不是有种豁然开朗的感觉?看到了<code>register</code>就感觉到希望来了,这里使用的是DiscoveryClient.register方法,其实这里我们也可以先找DiscoveryClient中的register方法,然后再反查调用方,这也是一种好的思路呀。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">DiscoveryClient.register</span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">register</span><span class="params">()</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> logger.info(PREFIX + appPathIdentifier + <span class="string">": registering service..."</span>);</span><br><span class="line"> EurekaHttpResponse<Void> httpResponse;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 回看eurekaTransport创建及初始化过程</span></span><br><span class="line"> httpResponse = eurekaTransport.registrationClient.register(instanceInfo);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> logger.warn(<span class="string">"{} - registration failed {}"</span>, PREFIX + appPathIdentifier, e.getMessage(), e);</span><br><span class="line"> <span class="keyword">throw</span> e;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (logger.isInfoEnabled()) {</span><br><span class="line"> logger.info(<span class="string">"{} - registration status: {}"</span>, PREFIX + appPathIdentifier, httpResponse.getStatusCode());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> httpResponse.getStatusCode() == <span class="number">204</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里是使用eurekaTransport.registrationClient去进行注册,我们在最开始DiscoveryClient构造方法中已经截取了eurekaTransport创建及初始化代码,这里再贴一下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 支持底层的eureka client跟eureka server进行网络通信的组件</span></span><br><span class="line">eurekaTransport = <span class="keyword">new</span> EurekaTransport();</span><br><span class="line"><span class="comment">// 发送http请求,调用restful接口</span></span><br><span class="line">scheduleServerEndpointTask(eurekaTransport, args);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">scheduleServerEndpointTask</span><span class="params">(EurekaTransport eurekaTransport,</span></span></span><br><span class="line"><span class="function"><span class="params"> AbstractDiscoveryClientOptionalArgs args)</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略大量代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果需要抓取注册表,读取其他server的注册信息</span></span><br><span class="line"> <span class="keyword">if</span> (clientConfig.shouldRegisterWithEureka()) {</span><br><span class="line"> EurekaHttpClientFactory newRegistrationClientFactory = <span class="keyword">null</span>;</span><br><span class="line"> EurekaHttpClient newRegistrationClient = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> newRegistrationClientFactory = EurekaHttpClients.registrationClientFactory(</span><br><span class="line"> eurekaTransport.bootstrapResolver,</span><br><span class="line"> eurekaTransport.transportClientFactory,</span><br><span class="line"> transportConfig</span><br><span class="line"> );</span><br><span class="line"> newRegistrationClient = newRegistrationClientFactory.newClient();</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> logger.warn(<span class="string">"Transport initialization failure"</span>, e);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 将newRegistrationClient放入到eurekaTransport中</span></span><br><span class="line"> eurekaTransport.registrationClientFactory = newRegistrationClientFactory;</span><br><span class="line"> eurekaTransport.registrationClient = newRegistrationClient;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>到了这里,可以看到eurekaTransport.registrationClient实际就是<code>EurekaHttpClient</code>,不知道是我没找对地方还是什么原因,我并没有找到具体执行的实现类。</p><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191228124301728-2031115897.png" alt="image.png"></p><p>最后网上查了下,具体执行的实现类是:<code>AbstractJersey2EurekaHttpClient</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">AbstractJersey2EurekaHttpClient.register()</span><br><span class="line"><span class="function"><span class="keyword">public</span> EurekaHttpResponse<Void> <span class="title">register</span><span class="params">(InstanceInfo info)</span> </span>{</span><br><span class="line"> String urlPath = <span class="string">"apps/"</span> + info.getAppName();</span><br><span class="line"> Response response = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 发送请求,类似于:http://localhost:8080/v2/apps/ServiceA</span></span><br><span class="line"> <span class="comment">// 发送的是post请求,服务实例的对象被打成了一个json发送,包括自己的主机、ip、端口号</span></span><br><span class="line"> <span class="comment">// eureka server 就知道了这个ServiceA这个服务,有一个服务实例,比如是在192.168.31.109、host-01、8761端口</span></span><br><span class="line"> Builder resourceBuilder = jerseyClient.target(serviceUrl).path(urlPath).request();</span><br><span class="line"> addExtraProperties(resourceBuilder);</span><br><span class="line"> addExtraHeaders(resourceBuilder);</span><br><span class="line"> response = resourceBuilder</span><br><span class="line"> .accept(MediaType.APPLICATION_JSON)</span><br><span class="line"> .acceptEncoding(<span class="string">"gzip"</span>)</span><br><span class="line"> .post(Entity.json(info));</span><br><span class="line"> <span class="keyword">return</span> anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (logger.isDebugEnabled()) {</span><br><span class="line"> logger.debug(<span class="string">"Jersey2 HTTP POST {}/{} with instance {}; statusCode={}"</span>, serviceUrl, urlPath, info.getId(),</span><br><span class="line"> response == <span class="keyword">null</span> ? <span class="string">"N/A"</span> : response.getStatus());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (response != <span class="keyword">null</span>) {</span><br><span class="line"> response.close();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>到了这里就已经真相大白了,可是 读了一通发现这个代码实在是不好理解,最后再总结一波才行。。。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>(1)DiscoveryClient构造函数会初始化EurekaClient相关的定时任务,定时任务里面会启动instanceInfo 互相复制的任务,就是InstanceInfoReplicator中的start()</p><p>(2)InstanceInfoReplicator的start()方法里面,将自己作为一个线程放到一个调度线程池中去了,默认是延迟40s去执行这个线程,还将isDirty设置为了ture</p><p>(3)如果执行线程的时候,是执行run()方法,线程</p><p>(3)先是找EurekaClient.refreshInstanceInfo()这个方法,里面其实是调用ApplicationInfoManager的一些方法刷新了一下服务实例的配置,看看配置有没有改变,如果改变了,就刷新一下;用健康检查器,检查了一下状态,将状态设置到了ApplicationInfoManager中去,更新服务实例的状态</p><p>(4)因为之前设置过isDirty,所以这里会执行进行服务注册</p><p>(5)服务注册的时候,是基于EurekaClient的reigster()方法去注册的,调用的是底层的TransportClient的RegistrationClient,执行了register()方法,将InstanceInfo服务实例的信息,通过http请求,调用eureka server对外暴露的一个restful接口,将InstanceInfo给发送了过去。这里找的是EurekaTransport,在构造的时候,调用了scheduleServerEndpointTask()方法,这个方法里就初始化了专门用于注册的RegistrationClient。</p><p>(6)找SessionedEurekaHttpClient调用register()方法,去进行注册,底层最终使用的AbstractJersey2EurekaHttpClient的register方法实现的</p><p>(7)eureka大量的基于jersey框架,在eureka server上提供restful接口,在eureka client如果要发送请求到eureka server的话,一定是基于jersey框架,去发送的http restful接口调用的请求</p><p>(8)真正执行注册请求的,就是eureka-client-jersey2工程里的AbstractJersey2EurekaHttpClient,请求<a href="http://localhost:8080/v2/apps/ServiceA,将服务实例的信息发送过去" target="_blank" rel="noopener">http://localhost:8080/v2/apps/ServiceA,将服务实例的信息发送过去</a></p><p>eureka client这一块,在服务注册的这块代码,很多槽点:</p><p>(1)服务注册,不应该放在InstanceInfoReplicator里面,语义不明朗</p><p>(2)负责发送请求的HttpClient,类体系过于复杂,导致人根本就找不到对应的Client,最后是根据他是使用jersey框架来进行restful接口暴露和调用,才能连蒙带猜,找到真正发送服务注册请求的地方</p>]]></content>
<summary type="html">
<h1 id="Nexflix-Eureka-源码五:在眼花缭乱的代码中,EurekaClient是如何注册的?"><a href="#Nexflix-Eureka-源码五:在眼花缭乱的代码中,EurekaClient是如何注册的?" class="headerlink" title="[Nexflix Eureka 源码五:在眼花缭乱的代码中,EurekaClient是如何注册的?]"></a>[Nexflix Eureka 源码五:在眼花缭乱的代码中,EurekaClient是如何注册的?]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>上一讲已经讲解了EurekaClient的启动流程,到了这里已经有6篇Eureka源码分析的文章了,看了下之前的文章,感觉代码成分太多,会影响阅读,后面会只截取主要的代码,加上注释讲解。</p>
<p>这一讲看的是EurekaClient注册的流程,当然也是一块核心,标题为什么会写上眼花缭乱呢?关于EurekaClient注册的代码,真的不是这么容易被发现的。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Nexflix Eureka" scheme="http://yoursite.com/tags/Nexflix-Eureka/"/>
</entry>
<entry>
<title>Nexflix Eureka 源码四:EurekaClient启动要经历哪些艰难险阻?</title>
<link href="http://yoursite.com/2020/01/04/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%9B%9B%EF%BC%9AEurekaClient%E5%90%AF%E5%8A%A8%E8%A6%81%E7%BB%8F%E5%8E%86%E5%93%AA%E4%BA%9B%E8%89%B0%E9%9A%BE%E9%99%A9%E9%98%BB%EF%BC%9F]/"/>
<id>http://yoursite.com/2020/01/04/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E5%9B%9B%EF%BC%9AEurekaClient%E5%90%AF%E5%8A%A8%E8%A6%81%E7%BB%8F%E5%8E%86%E5%93%AA%E4%BA%9B%E8%89%B0%E9%9A%BE%E9%99%A9%E9%98%BB%EF%BC%9F]/</id>
<published>2020-01-04T14:05:37.000Z</published>
<updated>2020-03-01T15:11:12.197Z</updated>
<content type="html"><![CDATA[<h1 id="Nexflix-Eureka-源码四:EurekaClient启动要经历哪些艰难险阻?"><a href="#Nexflix-Eureka-源码四:EurekaClient启动要经历哪些艰难险阻?" class="headerlink" title="[Nexflix Eureka 源码四:EurekaClient启动要经历哪些艰难险阻?]"></a>[Nexflix Eureka 源码四:EurekaClient启动要经历哪些艰难险阻?]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>在源码分析三、四都有提及到EurekaClient启动的一些过程。因为EurekaServer在集群模式下 自己本身就是一个client,所以之前初始化eurekaServerContext就有涉及到eurekaClient的初始化。</p><p>我们也看了EurekaClient(DiscoveryClient)初始化的过程,繁杂的启动过程让人眼花缭乱,这篇文章就专门来唠唠 里面经历的一些艰难险阻。这也会是后面client注册的一个前置文章。</p><a id="more"></a><h3 id="从ExampleEurekaClient开始说起"><a href="#从ExampleEurekaClient开始说起" class="headerlink" title="从ExampleEurekaClient开始说起"></a>从ExampleEurekaClient开始说起</h3><p>在第一讲我们就说过,eureka项目有一个examples模块的,现在看一下其中的EurekaClientExample对象:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ExampleEurekaClient</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> ApplicationInfoManager applicationInfoManager;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> EurekaClient eurekaClient;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> ApplicationInfoManager <span class="title">initializeApplicationInfoManager</span><span class="params">(EurekaInstanceConfig instanceConfig)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (applicationInfoManager == <span class="keyword">null</span>) {</span><br><span class="line"> InstanceInfo instanceInfo = <span class="keyword">new</span> EurekaConfigBasedInstanceInfoProvider(instanceConfig).get();</span><br><span class="line"> applicationInfoManager = <span class="keyword">new</span> ApplicationInfoManager(instanceConfig, instanceInfo);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> applicationInfoManager;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> EurekaClient <span class="title">initializeEurekaClient</span><span class="params">(ApplicationInfoManager applicationInfoManager, EurekaClientConfig clientConfig)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (eurekaClient == <span class="keyword">null</span>) {</span><br><span class="line"> eurekaClient = <span class="keyword">new</span> DiscoveryClient(applicationInfoManager, clientConfig);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> eurekaClient;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendRequestToServiceUsingEureka</span><span class="params">(EurekaClient eurekaClient)</span> </span>{</span><br><span class="line"> <span class="comment">// initialize the client</span></span><br><span class="line"> <span class="comment">// this is the vip address for the example service to talk to as defined in conf/sample-eureka-service.properties</span></span><br><span class="line"> String vipAddress = <span class="string">"sampleservice.mydomain.net"</span>;</span><br><span class="line"></span><br><span class="line"> InstanceInfo nextServerInfo = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> nextServerInfo = eurekaClient.getNextServerFromEureka(vipAddress, <span class="keyword">false</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> System.err.println(<span class="string">"Cannot get an instance of example service to talk to from eureka"</span>);</span><br><span class="line"> System.exit(-<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"Found an instance of example service to talk to from eureka: "</span></span><br><span class="line"> + nextServerInfo.getVIPAddress() + <span class="string">":"</span> + nextServerInfo.getPort());</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"healthCheckUrl: "</span> + nextServerInfo.getHealthCheckUrl());</span><br><span class="line"> System.out.println(<span class="string">"override: "</span> + nextServerInfo.getOverriddenStatus());</span><br><span class="line"></span><br><span class="line"> Socket s = <span class="keyword">new</span> Socket();</span><br><span class="line"> <span class="keyword">int</span> serverPort = nextServerInfo.getPort();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> s.connect(<span class="keyword">new</span> InetSocketAddress(nextServerInfo.getHostName(), serverPort));</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> System.err.println(<span class="string">"Could not connect to the server :"</span></span><br><span class="line"> + nextServerInfo.getHostName() + <span class="string">" at port "</span> + serverPort);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> System.err.println(<span class="string">"Could not connect to the server :"</span></span><br><span class="line"> + nextServerInfo.getHostName() + <span class="string">" at port "</span> + serverPort + <span class="string">"due to Exception "</span> + e);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> String request = <span class="string">"FOO "</span> + <span class="keyword">new</span> Date();</span><br><span class="line"> System.out.println(<span class="string">"Connected to server. Sending a sample request: "</span> + request);</span><br><span class="line"></span><br><span class="line"> PrintStream out = <span class="keyword">new</span> PrintStream(s.getOutputStream());</span><br><span class="line"> out.println(request);</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"Waiting for server response.."</span>);</span><br><span class="line"> BufferedReader rd = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> InputStreamReader(s.getInputStream()));</span><br><span class="line"> String str = rd.readLine();</span><br><span class="line"> <span class="keyword">if</span> (str != <span class="keyword">null</span>) {</span><br><span class="line"> System.out.println(<span class="string">"Received response from server: "</span> + str);</span><br><span class="line"> System.out.println(<span class="string">"Exiting the client. Demo over.."</span>);</span><br><span class="line"> }</span><br><span class="line"> rd.close();</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception</span>{</span><br><span class="line"> injectEurekaConfiguration();</span><br><span class="line"> ExampleEurekaClient sampleClient = <span class="keyword">new</span> ExampleEurekaClient();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// MyDataCenterInstanceConfig 就是加载eureka-client.properties配置信息,形成一个服务实例的配置EurekaInstanceConfig</span></span><br><span class="line"> <span class="comment">// 基于EurekaClient配置,构造了一个服务实例(InstanceInfo)</span></span><br><span class="line"> <span class="comment">// 基于eureka client配置和服务实例,构造了一个服务实例管理器(ApplicationInfoManager)</span></span><br><span class="line"> <span class="comment">// 读取eureka-client.properties配置文件,形成了一个eureka client的配置,接口对外提供eureka client的配置项的读取</span></span><br><span class="line"> <span class="comment">// 基于eureka client的配置,和服务实例管理器,来构造了一个EurekaClient(DiscoveryClient</span></span><br><span class="line"> <span class="comment">// ),保存了一些配置,处理服务的注册和注册表的抓取,启动了几个线程池,启动了网络通信组件,启动了一些调度任务,注册了监控项</span></span><br><span class="line"> ApplicationInfoManager applicationInfoManager = initializeApplicationInfoManager(<span class="keyword">new</span> MyDataCenterInstanceConfig());</span><br><span class="line"> EurekaClient client = initializeEurekaClient(applicationInfoManager, <span class="keyword">new</span> DefaultEurekaClientConfig());</span><br><span class="line"></span><br><span class="line"> <span class="comment">// use the client</span></span><br><span class="line"> sampleClient.sendRequestToServiceUsingEureka(client);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="comment">// shutdown the client</span></span><br><span class="line"> eurekaClient.shutdown();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * This will be read by server internal discovery client. We need to salience it.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">injectEurekaConfiguration</span><span class="params">()</span> <span class="keyword">throws</span> UnknownHostException </span>{</span><br><span class="line"> String myHostName = InetAddress.getLocalHost().getHostName();</span><br><span class="line"> String myServiceUrl = <span class="string">"http://"</span> + myHostName + <span class="string">":8080/v2/"</span>;</span><br><span class="line"></span><br><span class="line"> System.setProperty(<span class="string">"eureka.region"</span>, <span class="string">"default"</span>);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.name"</span>, <span class="string">"eureka"</span>);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.vipAddress"</span>, <span class="string">"eureka.mydomain.net"</span>);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.port"</span>, <span class="string">"8080"</span>);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.preferSameZone"</span>, <span class="string">"false"</span>);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.shouldUseDns"</span>, <span class="string">"false"</span>);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.shouldFetchRegistry"</span>, <span class="string">"true"</span>);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.serviceUrl.defaultZone"</span>, myServiceUrl);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.serviceUrl.default.defaultZone"</span>, myServiceUrl);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.awsAccessId"</span>, <span class="string">"fake_aws_access_id"</span>);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.awsSecretKey"</span>, <span class="string">"fake_aws_secret_key"</span>);</span><br><span class="line"> System.setProperty(<span class="string">"eureka.numberRegistrySyncRetries"</span>, <span class="string">"0"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里我们从main函数开始看起:</p><ol><li>注入eureka配置信息:<code>injectEurekaConfiguration();</code></li><li>读取eureka-client.properties配置文件,形成一个服务器实例的配置,基于接口对外提供实例的配置项读取<br> 这里就是涉及到我么你之前讲解的<code>DynamicPropertyFactory</code>和<code>ConfigurationManager</code>,这里可以查看<code>new MyDataCenterInstanceConfig()</code>然后一步步往后跟。</li><li>基于服务实例的配置,构造了一个服务实例(InstanceInfo)。<code>initializeApplicationInfoManager</code>中会构建InstanceInfo信息</li><li>基于服务实例的配置和服务实例,初始化服务实例管理器(ApplicationInfoManager)</li><li>基于eureka client配置,和服务实例管理器,来构造了一个EurekaClient(DiscoveryClient),保存了一些配置,处理服务的注册和注册表的抓取,启动了几个线程池,启动了网络通信组件,启动了一些调度任务,注册了监控项<br> 具体可查看 <code>EurekaClient client = initializeEurekaClient(applicationInfoManager, new DefaultEurekaClientConfig());</code></li></ol><h3 id="流程图"><a href="#流程图" class="headerlink" title="流程图"></a>流程图</h3><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191227111631564-1145037618.png" alt="02_Eureka_Client启动流程图.png"></p><h3 id="申明"><a href="#申明" class="headerlink" title="申明"></a>申明</h3>]]></content>
<summary type="html">
<h1 id="Nexflix-Eureka-源码四:EurekaClient启动要经历哪些艰难险阻?"><a href="#Nexflix-Eureka-源码四:EurekaClient启动要经历哪些艰难险阻?" class="headerlink" title="[Nexflix Eureka 源码四:EurekaClient启动要经历哪些艰难险阻?]"></a>[Nexflix Eureka 源码四:EurekaClient启动要经历哪些艰难险阻?]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>在源码分析三、四都有提及到EurekaClient启动的一些过程。因为EurekaServer在集群模式下 自己本身就是一个client,所以之前初始化eurekaServerContext就有涉及到eurekaClient的初始化。</p>
<p>我们也看了EurekaClient(DiscoveryClient)初始化的过程,繁杂的启动过程让人眼花缭乱,这篇文章就专门来唠唠 里面经历的一些艰难险阻。这也会是后面client注册的一个前置文章。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Nexflix Eureka" scheme="http://yoursite.com/tags/Nexflix-Eureka/"/>
</entry>
<entry>
<title>Nexflix Eureka 源码三:EurekaServer启动之完成上下文构建及EurekaServer总结</title>
<link href="http://yoursite.com/2020/01/03/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E4%B8%89%EF%BC%9AEurekaServer%E5%90%AF%E5%8A%A8%E4%B9%8B%E5%AE%8C%E6%88%90%E4%B8%8A%E4%B8%8B%E6%96%87%E6%9E%84%E5%BB%BA%E5%8F%8AEurekaServer%E6%80%BB%E7%BB%93]/"/>
<id>http://yoursite.com/2020/01/03/[Nexflix%20Eureka%20%E6%BA%90%E7%A0%81%E4%B8%89%EF%BC%9AEurekaServer%E5%90%AF%E5%8A%A8%E4%B9%8B%E5%AE%8C%E6%88%90%E4%B8%8A%E4%B8%8B%E6%96%87%E6%9E%84%E5%BB%BA%E5%8F%8AEurekaServer%E6%80%BB%E7%BB%93]/</id>
<published>2020-01-03T14:05:37.000Z</published>
<updated>2020-03-01T15:11:00.529Z</updated>
<content type="html"><![CDATA[<h1 id="Nexflix-Eureka-源码三:EurekaServer启动之完成上下文构建及EurekaServer总结"><a href="#Nexflix-Eureka-源码三:EurekaServer启动之完成上下文构建及EurekaServer总结" class="headerlink" title="[Nexflix Eureka 源码三:EurekaServer启动之完成上下文构建及EurekaServer总结]"></a>[Nexflix Eureka 源码三:EurekaServer启动之完成上下文构建及EurekaServer总结]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>上篇文章已经介绍了 Eureka Server上下文创建相关的Eureka Client逻辑,这一部分还是比较复杂的。接下来就讲解下Eureka Server上下文初始化最后的部分,然后加上整个Eureka Server启动的总结。</p><a id="more"></a><h3 id="initEurekaServerContext"><a href="#initEurekaServerContext" class="headerlink" title="initEurekaServerContext"></a>initEurekaServerContext</h3><p>EurekaBootStrap.initEurekaServerContext</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initEurekaServerContext</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 省略之前内容</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3、处理注册相关的事情 下面是拆解单词加深理解:</span></span><br><span class="line"> <span class="comment">// PeerAware:可以识别eureka server集群的。多个同样的东西组成一个集群,peers:集群,peer就是集群中的一个实例</span></span><br><span class="line"> <span class="comment">// InstanceRegistry:实例注册,服务实例注册。注册表,这个里面放了所有的主车道这个eureka server上的服务实例,就是一个服务实例的注册表。</span></span><br><span class="line"> <span class="comment">// PeerAwareInstanceRegistry:可以感知eureka server集群的服务实例注册表,eureka client(作为服务实例)过来注册的注册表,而且这个注册表是可以感知到eureka</span></span><br><span class="line"> <span class="comment">// server集群的,假如有一个eureka server集群的话,这里包含了其他eureka server中的服务实例注册表信息的。</span></span><br><span class="line"> PeerAwareInstanceRegistry registry;</span><br><span class="line"> <span class="keyword">if</span> (isAws(applicationInfoManager.getInfo())) {</span><br><span class="line"> registry = <span class="keyword">new</span> AwsInstanceRegistry(</span><br><span class="line"> eurekaServerConfig,</span><br><span class="line"> eurekaClient.getEurekaClientConfig(),</span><br><span class="line"> serverCodecs,</span><br><span class="line"> eurekaClient</span><br><span class="line"> );</span><br><span class="line"> awsBinder = <span class="keyword">new</span> AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager);</span><br><span class="line"> awsBinder.start();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 创建PeerAwareInstanceRegistry。最近取消的实例,最近注册的实例都会保存到registry中</span></span><br><span class="line"> registry = <span class="keyword">new</span> PeerAwareInstanceRegistryImpl(</span><br><span class="line"> eurekaServerConfig,</span><br><span class="line"> eurekaClient.getEurekaClientConfig(),</span><br><span class="line"> serverCodecs,</span><br><span class="line"> eurekaClient</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 4、处理peer节点相关的事情</span></span><br><span class="line"> <span class="comment">// peerEurekaNodes 代表了eureka server集群,peers大概来说是多个相同的实例组成的一个集群,peer就是peers中的一个实例</span></span><br><span class="line"> <span class="comment">// PeerEurekaNodes,代表的是eureka server集群</span></span><br><span class="line"> PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(</span><br><span class="line"> registry,</span><br><span class="line"> eurekaServerConfig,</span><br><span class="line"> eurekaClient.getEurekaClientConfig(),</span><br><span class="line"> serverCodecs,</span><br><span class="line"> applicationInfoManager</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 5、完成eureka-server上下文(context)的构建及初始化</span></span><br><span class="line"> serverContext = <span class="keyword">new</span> DefaultEurekaServerContext(</span><br><span class="line"> eurekaServerConfig,</span><br><span class="line"> serverCodecs,</span><br><span class="line"> registry,</span><br><span class="line"> peerEurekaNodes,</span><br><span class="line"> applicationInfoManager</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将上下文信息放到holder中</span></span><br><span class="line"> EurekaServerContextHolder.initialize(serverContext);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将eureka server集群启动起来。里面会更新eureka server集群的信息,让当前的eureka server感知到所有的其他eureka server</span></span><br><span class="line"> <span class="comment">// 然后搞一个定时调度任务,就是一个后台线程,每隔一段时间,更新eureka server集群的信息</span></span><br><span class="line"> <span class="comment">// registry.init: 基于eureka server集群的信息,来初始化注册表,将eureka server集群中所有的eureka</span></span><br><span class="line"> <span class="comment">// server的注册表信息抓取过来,放到自己本地的注册表中。都是跟eureka server集群中之间的注册表信息互换有关联的</span></span><br><span class="line"> serverContext.initialize();</span><br><span class="line"> logger.info(<span class="string">"Initialized server context"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Copy registry from neighboring eureka node</span></span><br><span class="line"> <span class="comment">// 6、处理一些善后的事情,从相邻的eureka节点拷贝注册信息</span></span><br><span class="line"> <span class="comment">// registry.syncUp(): 从相邻的一个eureka server节点拷贝注册表信息,如果拷贝失败,就找下一个</span></span><br><span class="line"> <span class="keyword">int</span> registryCount = registry.syncUp();</span><br><span class="line"> registry.openForTraffic(applicationInfoManager, registryCount);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Register all monitoring statistics.</span></span><br><span class="line"> <span class="comment">// 7、注册所有的监控统计项</span></span><br><span class="line"> EurekaMonitors.registerAllStats();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里备注比较多,基本上是每个方法 实现的目的,并没有进一步跟踪细节问题,还是之前讲到的,看源码还是要抓大放小,我们尽量先把主流程搞清楚,然后再去一步步攻克细节。</p><h3 id="EurekaServer启动流程总结"><a href="#EurekaServer启动流程总结" class="headerlink" title="EurekaServer启动流程总结"></a>EurekaServer启动流程总结</h3><p>到这里 eureka server启动的相关代码就看完了,下面有一张流程图来总结一下的:</p><p><img src="https://img2018.cnblogs.com/blog/799093/201912/799093-20191226100224554-852172976.png" alt="01_EurekaServer启动流程图.png"></p>]]></content>
<summary type="html">
<h1 id="Nexflix-Eureka-源码三:EurekaServer启动之完成上下文构建及EurekaServer总结"><a href="#Nexflix-Eureka-源码三:EurekaServer启动之完成上下文构建及EurekaServer总结" class="headerlink" title="[Nexflix Eureka 源码三:EurekaServer启动之完成上下文构建及EurekaServer总结]"></a>[Nexflix Eureka 源码三:EurekaServer启动之完成上下文构建及EurekaServer总结]</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>上篇文章已经介绍了 Eureka Server上下文创建相关的Eureka Client逻辑,这一部分还是比较复杂的。接下来就讲解下Eureka Server上下文初始化最后的部分,然后加上整个Eureka Server启动的总结。</p>
</summary>
<category term="源码" scheme="http://yoursite.com/categories/%E6%BA%90%E7%A0%81/"/>
<category term="Nexflix Eureka" scheme="http://yoursite.com/tags/Nexflix-Eureka/"/>
</entry>
</feed>