+
@@ -721,11 +721,11 @@ public DObserver{
1. Guava EventBus 的功能我们已经讲清楚了,总体上来说,还是比较简单的。接下来,我们就重复造轮子,“山寨”一个 EventBus 出来。
2. 我们重点来看,EventBus 中两个核心函数 register() 和 post() 的实现原理。弄懂了它们,基本上就弄懂了整个 EventBus 框架。下面两张图是这两个函数的实现原理图。
-
+
-
+
diff --git a/docs/design_patterns/behavior_type/设计模式-05.02-行为型-策略&职责链.md b/docs/design_patterns/behavior_type/设计模式-05.02-行为型-策略&职责链.md
index f71a974..4db8bef 100644
--- a/docs/design_patterns/behavior_type/设计模式-05.02-行为型-策略&职责链.md
+++ b/docs/design_patterns/behavior_type/设计模式-05.02-行为型-策略&职责链.md
@@ -8,7 +8,7 @@ categories:
- 05.行为型
keywords: 策略模式,职责链模式
description: 不多说,看文章
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/design_patterns/logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/design_patterns/logo.jpg'
abbrlink: 2c3cc5fd
date: 2021-08-01 15:51:58
---
@@ -1171,7 +1171,7 @@ public class SensitiveWordFilter {
Servlet Filter 是 Java Servlet 规范中定义的组件,翻译成中文就是过滤器,它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。因为它是 Servlet 规范的一部分,所以,只要是支持 Servlet 的 Web 容器(比如,Tomcat、Jetty 等),都支持过滤器功能。为了帮助你理解,我画了一张示意图阐述它的工作原理,如下所示。
-
+
在实际项目中,我们该如何使用 Servlet Filter 呢?我写了一个简单的示例代码,如下所示。添加一个过滤器,我们只需要定义一个实现 javax.servlet.Filter 接口的过滤器类,并且将它配置在 web.xml 配置文件中。Web 容器启动的时候,会读取 web.xml 中的配置,创建过滤器对象。当有请求到来的时候,会先经过过滤器,然后才由 Servlet 来处理。
@@ -1279,7 +1279,7 @@ ApplicationFilterChain 中的 doFilter() 函数的代码实现比较有技巧,
1. 刚刚讲了 Servlet Filter,现在我们来讲一个功能上跟它非常类似的东西,Spring Interceptor,翻译成中文就是拦截器。尽管英文单词和中文翻译都不同,但这两者基本上可以看作一个概念,都用来实现对 HTTP 请求进行拦截处理。
2. 它们不同之处在于,Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器。Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现。客户端发送的请求,会先经过 Servlet Filter,然后再经过 Spring Interceptor,最后到达具体的业务代码中。我画了一张图来阐述一个请求的处理流程,具体如下所示。
-
+
diff --git a/docs/design_patterns/behavior_type/设计模式-05.03-行为型-状态&迭代器.md b/docs/design_patterns/behavior_type/设计模式-05.03-行为型-状态&迭代器.md
index 8732396..0cab072 100644
--- a/docs/design_patterns/behavior_type/设计模式-05.03-行为型-状态&迭代器.md
+++ b/docs/design_patterns/behavior_type/设计模式-05.03-行为型-状态&迭代器.md
@@ -8,7 +8,7 @@ categories:
- 05.行为型
keywords: 状态模式,迭代器模式
description: 看文章
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/design_patterns/logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/design_patterns/logo.jpg'
abbrlink: 877f4ef2
date: 2021-08-02 15:51:58
---
@@ -30,7 +30,7 @@ date: 2021-08-02 15:51:58
4. 实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)。
5. 为了方便接下来的讲解,我对游戏背景做了简化,只保留了部分状态和事件。简化之后的状态转移如下图所示:
-
+
@@ -180,7 +180,7 @@ public class MarioStateMachine {
1. 实际上,上面这种实现方法有点类似 hard code,对于复杂的状态机来说不适用,而状态机的第二种实现方式查表法,就更加合适了。接下来,我们就一块儿来看下,如何利用查表法来补全骨架代码。
2. 实际上,除了用状态转移图来表示之外,状态机还可以用二维表来表示,如下所示。在这个二维表中,第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。
-
+
3. 相对于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改 transitionTable 和 actionTable 两个二维数组即可。实际上,如果我们把这两个二维数组存储在配置文件中,当需要修改状态机时,我们甚至可以不修改任何代码,只需要修改配置文件就可以了。具体的代码如下所示:
@@ -511,7 +511,7 @@ public class MarioStateMachine {
2. 在开篇中我们讲到,它用来遍历集合对象。这里说的“集合对象”也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
3. 迭代器是用来遍历容器的,所以,一个完整的迭代器模式一般会涉及**容器和容器迭代器**部分两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类。对于迭代器模式,我画了一张简单的类图,你可以看一看,先有个大致的印象。
-
+
@@ -629,7 +629,7 @@ public class Demo {
2. 结合刚刚的例子,我们来总结一下迭代器的设计思路。总结下来就三句话:迭代器中需要定义 hasNext()、currentItem()、next() 三个最基本的方法。待遍历的容器对象通过依赖注入传递到迭代器类中。容器通过 iterator() 方法来创建迭代器。
3. 这里我画了一张类图,如下所示。实际上就是对上面那张类图的细化,你可以结合着一块看。
-
+
@@ -752,7 +752,7 @@ public class Demo {
4. 为了保持数组存储数据的连续性,数组的删除操作会涉及元素的搬移。当执行到第 57 行代码的时候,我们从数组中将元素 a 删除掉,b、c、d 三个元素会依次往前搬移一位,这就会导致游标本来指向元素 b,现在变成了指向元素 c。原本在执行完第 56 行代码之后,我们还可以遍历到 b、c、d 三个元素,但在执行完第 57 行代码之后,我们只能遍历到 c、d 两个元素,b 遍历不到了。
5. 对于上面的描述,我画了一张图,你可以对照着理解。
-
+
6. 不过,如果第 57 行代码删除的不是游标前面的元素(元素 a)以及游标所在位置的元素(元素 b),而是游标后面的元素(元素 c 和 d),这样就不会存在任何问题了,不会存在某个元素遍历不到的情况了。
7. 所以,我们前面说,在遍历的过程中删除集合元素,结果是不可预期的,有时候没问题(删除元素 c 或 d),有时候就有问题(删除元素 a 或 b),这个要视情况而定(到底删除的是哪个位置的元素),就是这个意思。
@@ -778,7 +778,7 @@ public class Demo {
10. 跟删除情况类似,如果我们在游标的后面添加元素,就不会存在任何问题。所以,在遍历的同时添加集合元素也是一种不可预期行为。
11. 同样,对于上面的添加元素的情况,我们也画了一张图,如下所示,你可以对照着理解。
-
+
diff --git a/docs/design_patterns/creational/设计模式-03.01-创建型-单例.md b/docs/design_patterns/creational/设计模式-03.01-创建型-单例.md
index e9fb0c4..5e7b0db 100644
--- a/docs/design_patterns/creational/设计模式-03.01-创建型-单例.md
+++ b/docs/design_patterns/creational/设计模式-03.01-创建型-单例.md
@@ -8,7 +8,7 @@ categories:
- 03.创建型
keywords: 设计模式,单例
description: 详解了单例设计模式。
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/design_patterns/logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/design_patterns/logo.jpg'
abbrlink: b5a1ed4a
date: 2021-06-26 21:51:58
---
diff --git a/docs/design_patterns/creational/设计模式-03.02-创建型-工厂&建造者&原型.md b/docs/design_patterns/creational/设计模式-03.02-创建型-工厂&建造者&原型.md
index 73e2d34..a9c609a 100644
--- a/docs/design_patterns/creational/设计模式-03.02-创建型-工厂&建造者&原型.md
+++ b/docs/design_patterns/creational/设计模式-03.02-创建型-工厂&建造者&原型.md
@@ -10,7 +10,7 @@ categories:
- 03.创建型
keywords: 设计模式,工厂,建造者,原型
description: 详解常用的工厂模式和建造者模式,以及不常用的原型模式
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/design_patterns/logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/design_patterns/logo.jpg'
abbrlink: ba432704
date: 2021-06-27 00:51:58
---
@@ -727,7 +727,7 @@ public class BeansFactory {
1. 在平时的开发中,创建一个对象最常用的方式是,使用 new 关键字调用类的构造函数来完成。我的问题是,什么情况下这种方式就不适用了,就需要采用建造者模式来创建对象呢?你可以先思考一下,下面我通过一个例子来带你看一下。
2. 假设有这样一道设计面试题:我们需要定义一个资源池配置类 ResourcePoolConfig。这里的资源池,你可以简单理解为线程池、连接池、对象池等。在这个资源池配置类中,有以下几个成员变量,也就是可配置项。现在,请你编写代码实现这个 ResourcePoolConfig 类。
-
+
@@ -1003,7 +1003,7 @@ r.setHeight(3); // r is valid
2. 如果你熟悉的是 Java 语言,可以直接使用语言中提供的 HashMap 容器来实现。其中,HashMap 的 key 为搜索关键词,value 为关键词详细信息(比如搜索次数)。我们只需要将数据从数据库中读取出来,放入 HashMap 就可以了。
3. 不过,我们还有另外一个系统 B,专门用来分析搜索日志,定期(比如间隔 10 分钟)批量地更新数据库中的数据,并且标记为新的数据版本。比如,在下面的示例图中,我们对 v2 版本的数据进行更新,得到 v3 版本的数据。这里我们假设只有更新和新添关键词,没有删除关键词的行为。
-
+
@@ -1150,15 +1150,15 @@ public class Demo {
我们来看,在内存中,用散列表组织的搜索关键词信息是如何存储的。我画了一张示意图,大致结构如下所示。从图中我们可以发现,散列表索引中,每个结点存储的 key 是搜索关键词,value 是 SearchWord 对象的内存地址。SearchWord 对象本身存储在散列表之外的内存空间中。
-
+
浅拷贝和深拷贝的区别在于,浅拷贝只会复制图中的索引(散列表),不会复制数据(SearchWord 对象)本身。相反,深拷贝不仅仅会复制索引,还会复制数据本身。浅拷贝得到的对象(newKeywords)跟原始对象(currentKeywords)共享数据(SearchWord 对象),而深拷贝得到的是一份完完全全独立的对象。具体的对比如下图所示:
-
+
-
+
diff --git a/docs/design_patterns/design_ideas/设计模式-01.设计思想.md b/docs/design_patterns/design_ideas/设计模式-01.设计思想.md
index 706583d..bfb00e7 100644
--- a/docs/design_patterns/design_ideas/设计模式-01.设计思想.md
+++ b/docs/design_patterns/design_ideas/设计模式-01.设计思想.md
@@ -8,7 +8,7 @@ categories:
- 01.设计思想
keywords: 设计模式,设计思想
description: 设计模式第一部分-常用设计思想。
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/design_patterns/logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/design_patterns/logo.jpg'
abbrlink: c3dcce5d
date: 2021-06-07 17:21:58
---
@@ -198,7 +198,7 @@ public class Ostrich extends AbstractBird { //鸵鸟
1. 这种设计思路虽然可以解决问题,但不够优美。因为除了鸵鸟之外,不会飞的鸟还有很多,比如企鹅。对于这些不会飞的鸟来说,我们都需要重写 fly() 方法,抛出异常。这样的设计,一方面,徒增了编码的工作量;另一方面,也违背了我们之后要讲的最小知识原则(Least Knowledge Principle,也叫最少知识原则或者迪米特法则),暴露不该暴露的接口给外部,增加了类使用过程中被误用的概率。
2. 你可能又会说,那我们再通过 AbstractBird 类派生出两个更加细分的抽象类:会飞的鸟类 AbstractFlyableBird 和不会飞的鸟类 AbstractUnFlyableBird,让麻雀、乌鸦这些会飞的鸟都继承 AbstractFlyableBird,让鸵鸟、企鹅这些不会飞的鸟,都继承 AbstractUnFlyableBird 类,不就可以了吗?具体的继承关系如下图所示:
-
+
@@ -206,7 +206,7 @@ public class Ostrich extends AbstractBird { //鸵鸟
2. 是否会飞?是否会叫?两个行为搭配起来会产生四种情况:会飞会叫、不会飞会叫、会飞不会叫、不会飞不会叫。如果我们继续沿用刚才的设计思路,那就需要再定义四个抽象类(AbstractFlyableTweetableBird、AbstractFlyableUnTweetableBird、AbstractUnFlyableTweetableBird、AbstractUnFlyableUnTweetableBird)。
-
+
@@ -376,7 +376,7 @@ demofunction(client);
-
+
diff --git a/docs/design_patterns/design_principles/设计模式-02.经典设计原则-第一节[必读].md b/docs/design_patterns/design_principles/设计模式-02.经典设计原则-第一节[必读].md
index 0f9920d..3e179cd 100644
--- a/docs/design_patterns/design_principles/设计模式-02.经典设计原则-第一节[必读].md
+++ b/docs/design_patterns/design_principles/设计模式-02.经典设计原则-第一节[必读].md
@@ -8,7 +8,7 @@ categories:
- 02.经典设计原则
keywords: 设计模式,经典设计原则
description: 设计模式-经典设计原则,例如:单一职责原则,开闭原则,接口隔离原则等等。
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/design_patterns/logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/design_patterns/logo.jpg'
abbrlink: fc1c7619
date: 2021-06-13 19:21:58
---
diff --git a/docs/design_patterns/design_principles/设计模式-02.经典设计原则-第二节[必读].md b/docs/design_patterns/design_principles/设计模式-02.经典设计原则-第二节[必读].md
index 6abc470..2dee587 100644
--- a/docs/design_patterns/design_principles/设计模式-02.经典设计原则-第二节[必读].md
+++ b/docs/design_patterns/design_principles/设计模式-02.经典设计原则-第二节[必读].md
@@ -8,7 +8,7 @@ categories:
- 02.经典设计原则
keywords: 设计模式,经典设计原则
description: 设计模式-经典设计原则,例如:迪米特法则,依赖反转原则,KISS等等。
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/design_patterns/logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/design_patterns/logo.jpg'
abbrlink: 994a8ed3
date: 2021-06-20 19:21:58
---
@@ -724,7 +724,7 @@ public class UserRepo {
前面也提到,“高内聚”有助于“松耦合”,同理,“低内聚”也会导致“紧耦合”。关于这一点,我画了一张对比图来解释。图中左边部分的代码结构是“高内聚、松耦合”;右边部分正好相反,是“低内聚、紧耦合”。
-
+
diff --git a/docs/design_patterns/structural_type/设计模式-04.01-结构型-代理&桥接&装饰器&适配器.md b/docs/design_patterns/structural_type/设计模式-04.01-结构型-代理&桥接&装饰器&适配器.md
index 3e41c88..2d7b7e1 100644
--- a/docs/design_patterns/structural_type/设计模式-04.01-结构型-代理&桥接&装饰器&适配器.md
+++ b/docs/design_patterns/structural_type/设计模式-04.01-结构型-代理&桥接&装饰器&适配器.md
@@ -11,7 +11,7 @@ categories:
- 04.结构型
keywords: 设计模式,代理模式,桥接模式,装饰器模式,适配器模式
description: 对代理模式,桥接模式,装饰器模式,适配器模式这4个模式进行了比较详细的讲述。其实学习设计模式主要是为了后序看源码
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/design_patterns/logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/design_patterns/logo.jpg'
abbrlink: 926a065c
date: 2021-07-04 00:51:58
---
@@ -608,7 +608,7 @@ IUserController userController = (IUserController) proxy.createProxy(new UserCon
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:
-
+
@@ -616,7 +616,7 @@ IUserController userController = (IUserController) proxy.createProxy(new UserCon
传统方法对应的类图
-
+
1. 扩展性问题(**类爆炸**),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
2. 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
@@ -933,7 +933,7 @@ public class DriverManager {
-
+
@@ -1112,7 +1112,7 @@ public class TrivialNotification extends Notification {
### 方案一
-
+
@@ -1127,7 +1127,7 @@ public class TrivialNotification extends Notification {
前面分析到方案 1 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性(如图)
-
+
@@ -1142,7 +1142,7 @@ public class TrivialNotification extends Notification {
### 装饰器模式代码
-
+
#### Drink【抽象类-主体Component】
@@ -1390,7 +1390,7 @@ Java IO 类库非常庞大和复杂,有几十个类,负责 IO 数据的读
针对不同的读取和写入场景,Java IO 又在这四个父类基础之上,扩展出了很多子类。具体如下所示:
-
+
diff --git a/docs/design_patterns/structural_type/设计模式-04.02-结构型-门面&组合&享元.md b/docs/design_patterns/structural_type/设计模式-04.02-结构型-门面&组合&享元.md
index d0879e8..397c8e7 100644
--- a/docs/design_patterns/structural_type/设计模式-04.02-结构型-门面&组合&享元.md
+++ b/docs/design_patterns/structural_type/设计模式-04.02-结构型-门面&组合&享元.md
@@ -10,7 +10,7 @@ categories:
- 04.结构型
keywords: 设计模式,门面模式,组合模式,享元模式
description: 对代理模式,门面模式,组合模式,享元模式这3个设计模式进行了比较详细的讲述。
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/design_patterns/logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/design_patterns/logo.jpg'
abbrlink: 5ea604e0
date: 2021-07-05 16:51:58
---
@@ -75,7 +75,7 @@ date: 2021-07-05 16:51:58
### 传统方案
-
+
1. 在 ClientTest 的 main 方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程 混乱,没有清晰的过程 不利于在 ClientTest 中,去维护对子系统的操作
@@ -623,7 +623,7 @@ public class Demo {
-
+
diff --git a/docs/netty/introduction/Netty入门-第一话.md b/docs/netty/introduction/Netty入门-第一话.md
index 5d97ebc..fc90114 100644
--- a/docs/netty/introduction/Netty入门-第一话.md
+++ b/docs/netty/introduction/Netty入门-第一话.md
@@ -7,7 +7,7 @@ categories:
- 入门
keywords: Netty
description: 第一话对BIO和NIO进行了讲解,为后续做准备。
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/netty/netty_logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/netty/netty_logo.jpg'
abbrlink: 3f9283e7
date: 2021-04-08 14:21:58
---
@@ -33,7 +33,7 @@ date: 2021-04-08 14:21:58
相对简单的一个体系图
-
+
## Netty 的应用场景
@@ -67,7 +67,7 @@ date: 2021-04-08 14:21:58
## Netty 的学习资料参考
-
+
@@ -97,11 +97,11 @@ date: 2021-04-08 14:21:58
2. `Java` 共支持 `3` 种网络编程模型 `I/O` 模式:`BIO`、`NIO`、`AIO`。
3. `Java BIO`:同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。【简单示意图】
-
+
4. `Java NIO`:同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 `I/O` 请求就进行处理。【简单示意图】
-
+
5. `Java AIO(NIO.2)`:异步非阻塞,`AIO` 引入异步通道的概念,采用了 `Proactor` 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。
6. 我们依次展开讲解。
@@ -120,7 +120,7 @@ date: 2021-04-08 14:21:58
## Java BIO 工作机制
-
+
对 `BIO` 编程流程的梳理
@@ -207,7 +207,7 @@ public class BIOServer {
}
```
-
+
@@ -277,7 +277,7 @@ public class BasicBuffer {
3. `BIO` 基于字节流和字符流进行操作,而 `NIO` 基于 `Channel`(通道)和 `Buffer`(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。`Selector`(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。
4. Buffer和Channel之间的数据流向是双向的
-
+
## NIO 三大核心原理示意图
@@ -287,7 +287,7 @@ public class BasicBuffer {
关系图的说明:
-
+
1. 每个 `Channel` 都会对应一个 `Buffer`。
2. `Selector` 对应一个线程,一个线程对应多个 `Channel`(连接)。
@@ -303,17 +303,17 @@ public class BasicBuffer {
缓冲区(`Buffer`):缓冲区本质上是一个**可以读写数据的内存块**,可以理解成是一个**容器对象(含数组)**,该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。`Channel` 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 `Buffer`,如图:【后面举例说明】
-
+
### Buffer 类及其子类
1. 在 `NIO` 中,`Buffer` 是一个顶层父类,它是一个抽象类,类的层级关系图:
-
+
2. `Buffer` 类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息:
-
+
@@ -321,13 +321,13 @@ public class BasicBuffer {
3. `Buffer` 类相关方法一览
-
+
### ByteBuffer
从前面可以看出对于 `Java` 中的基本数据类型(`boolean` 除外),都有一个 `Buffer` 类型与之相对应,最常用的自然是 `ByteBuffer` 类(二进制数据),该类的主要方法如下:
-
+
## 通道(Channel)
@@ -343,7 +343,7 @@ public class BasicBuffer {
5. `FileChannel` 用于文件的数据读写,`DatagramChannel` 用于 `UDP` 的数据读写,`ServerSocketChannel` 和 `SocketChannel` 用于 `TCP` 的数据读写。
6. 图示
-
+
### FileChannel 类
@@ -444,7 +444,7 @@ public class NIOFileChannel02 {
2. 拷贝一个文本文件 `1.txt`,放在项目下即可
3. 代码演示
-
+
```java
package com.atguigu.nio;
@@ -721,7 +721,7 @@ public class ScatteringAndGatheringTest {
### Selector 示意图和特点说明
-
+
说明如下:
@@ -733,7 +733,7 @@ public class ScatteringAndGatheringTest {
### Selector 类相关方法
-
+
### 注意事项
@@ -748,7 +748,7 @@ public class ScatteringAndGatheringTest {
`NIO` 非阻塞网络编程相关的(`Selector`、`SelectionKey`、`ServerScoketChannel` 和 `SocketChannel`)关系梳理图
-
+
对上图的说明:
@@ -938,21 +938,21 @@ public static final int OP_ACCEPT = 1 << 4;
2. `SelectionKey` 相关方法
-
+
### ServerSocketChannel
1. `ServerSocketChannel` 在服务器端监听新的客户端 `Socket` 连接,负责监听,不负责实际的读写操作
2. 相关方法如下
-
+
### SocketChannel
1. `SocketChannel`,网络 `IO` 通道,**具体负责进行读写操作**。`NIO` 把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。
2. 相关方法如下
-
+
## NIO网络编程应用实例 - 群聊系统
@@ -965,7 +965,7 @@ public static final int OP_ACCEPT = 1 << 4;
5. 目的:进一步理解 `NIO` 非阻塞网络编程机制
6. 示意图分析和代码
-
+
代码:
@@ -1243,7 +1243,7 @@ socket.getOutputStream().write(arr);
### 传统 IO 模型
-
+
**DMA**:`direct memory access` 直接内存拷贝(不使用 `CPU`)
@@ -1252,19 +1252,19 @@ socket.getOutputStream().write(arr);
1. `mmap` 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。如下图
2. `mmap` 示意图
-
+
### sendFile 优化
1. `Linux2.1` 版本提供了 `sendFile` 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 `SocketBuffer`,同时,由于和用户态完全无关,就减少了一次上下文切换
2. 示意图和小结
-
+
3. 提示:零拷贝从操作系统角度,是没有 `cpu` 拷贝
4. `Linux在2.4` 版本中,做了一些修改,避免了从内核缓冲区拷贝到 `Socketbuffer` 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。具体如下图和小结:
-
+
5. 这里其实有一次 `cpu` 拷贝 `kernel buffer` -> `socket buffer` 但是,拷贝的信息很少,比如 `lenght`、`offset` 消耗低,可以忽略
diff --git a/docs/netty/introduction/Netty入门-第三话.md b/docs/netty/introduction/Netty入门-第三话.md
index f50a3bc..ad7f2ad 100644
--- a/docs/netty/introduction/Netty入门-第三话.md
+++ b/docs/netty/introduction/Netty入门-第三话.md
@@ -7,7 +7,7 @@ categories:
- 入门
keywords: Netty
description: 对前面两话一些迷惑的点进行细说,讲解handler调用机制,TCP粘包,以及用netty写一个十分简单的RPC。
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/netty/netty_logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/netty/netty_logo.jpg'
abbrlink: 429acc6d
date: 2021-04-21 17:38:58
---
@@ -25,7 +25,7 @@ date: 2021-04-21 17:38:58
1. 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码[示意图]
2. `codec`(编解码器)的组成部分有两个:`decoder`(解码器)和 `encoder`(编码器)。`encoder` 负责把业务数据转换成字节码数据,`decoder` 负责把字节码数据转换成业务数据
-
+
## Netty 本身的编码解码的机制和问题分析
@@ -54,7 +54,7 @@ date: 2021-04-21 17:38:58
8. 然后通过 `protoc.exe` 编译器根据 `.proto` 自动生成 `.java` 文件
9. `protobuf` 使用示意图
-
+
## Protobuf 快速入门实例
@@ -91,7 +91,7 @@ message Student { //会在 StudentPOJO 外部类生成一个内部类 Student,
protoc.exe --java_out=.Student.proto
将生成的 StudentPOJO 放入到项目使用
-
+
生成的StudentPOJO代码太长就不贴在这里了
@@ -676,7 +676,7 @@ public class NettyClientHandler extends ChannelInboundHandlerAdapter {
2. `ChannelHandler` 充当了处理入站和出站数据的应用程序逻辑的容器。例如,实现 `ChannelInboundHandler` 接口(或 `ChannelInboundHandlerAdapter`),你就可以接收入站事件和数据,这些数据会被业务逻辑处理。当要给客户端发送响应时,也可以从 `ChannelInboundHandler` 冲刷数据。业务逻辑通常写在一个或者多个 `ChannelInboundHandler` 中。`ChannelOutboundHandler` 原理一样,只不过它是用来处理出站数据的
3. `ChannelPipeline` 提供了 `ChannelHandler` 链的容器。以客户端应用程序为例,如果事件的运动方向是从客户端到服务端的,那么我们称这些事件为出站的,即客户端发送给服务端的数据会通过 `pipeline` 中的一系列 `ChannelOutboundHandler`,并被这些 `Handler` 处理,反之则称为入站的
-
+
> 出站,入站如果搞不清楚,看下面的**Netty的handler链的调用机制**,通过一个例子和图讲清楚
@@ -689,12 +689,12 @@ public class NettyClientHandler extends ChannelInboundHandlerAdapter {
1. 关系继承图
-
+
2. 由于不可能知道远程节点是否会一次性发送一个完整的信息,`tcp` 有可能出现粘包拆包的问题,这个类会对入站数据进行缓冲,直到它准备好被处理.【后面有说TCP的粘包和拆包问题】
3. 一个关于 `ByteToMessageDecoder` 实例分析
-
+
@@ -710,7 +710,7 @@ public class NettyClientHandler extends ChannelInboundHandlerAdapter {
> 读者可以看下这个图,带着这个图去看下面的例子。
-
+
@@ -978,11 +978,11 @@ public class MyLongToByteEncoder extends MessageToByteEncoder
+
-
+
@@ -1002,7 +1002,7 @@ public class MyLongToByteEncoder extends MessageToByteEncoder
+
@@ -1090,13 +1090,13 @@ public class MyByteToLongDecoder extends ByteToMessageDecoder {
如下图验证结果:
-
+
2. 同时又引出了一个小问题
-
+
当我们`MyClientHandler`传一个Long时,会调用我们的`MyLongToByteEncoder`的编码器。那么控制台就会打印这样一句话:**MyLongToByteEncoder encode 被调用**。但是这里并没有调用编码器,这是为什么呢?
@@ -1149,7 +1149,7 @@ public class MyByteToLongDecoder extends ByteToMessageDecoder {
ctx.writeAndFlush(Unpooled.copiedBuffer("abcdabcdabcdabcd",CharsetUtil.UTF_8));
```
-
+
@@ -1198,7 +1198,7 @@ public class MyByteToLongDecoder2 extends ReplayingDecoder
+
@@ -1249,7 +1249,7 @@ log4j.appender.stdout.layout.ConversionPattern=[%p]%C{1}-%m%n
3. 演示整合
-
+
@@ -1261,7 +1261,7 @@ log4j.appender.stdout.layout.ConversionPattern=[%p]%C{1}-%m%n
2. 由于 `TCP` 无消息保护边界,需要在接收端处理消息边界问题,也就是我们所说的粘包、拆包问题,看一张图
3. `TCP` 粘包、拆包图解
-
+
假设客户端分别发送了两个数据包 `D1` 和 `D2` 给服务端,由于服务端一次读取到字节数是不确定的,故可能存在以下四种情况:
@@ -1502,11 +1502,11 @@ public class MyClientHandler extends SimpleChannelInboundHandler
+
**Server**
-
+
@@ -1514,13 +1514,13 @@ public class MyClientHandler extends SimpleChannelInboundHandler
+
**Server**
-
+
@@ -1538,7 +1538,7 @@ public class MyClientHandler extends SimpleChannelInboundHandler
+
@@ -1996,7 +1996,7 @@ MyMessageEncoder encode 方法被调用
1. `RPC(Remote Procedure Call)`—远程过程调用,是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程
2. 两个或多个应用程序都分布在不同的服务器上,它们之间的调用都像是本地方法调用一样(如图)
-
+
过程:
@@ -2018,11 +2018,11 @@ MyMessageEncoder encode 方法被调用
3. 常见的 `RPC` 框架有:比较知名的如阿里的 `Dubbo`、`Google` 的 `gRPC`、`Go` 语言的 `rpcx`、`Apache` 的 `thrift`,`Spring` 旗下的 `SpringCloud`。
-
+
## 我们的RPC 调用流程图
-
+
**RPC 调用流程说明**
@@ -2052,7 +2052,7 @@ MyMessageEncoder encode 方法被调用
3. 创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用 `Netty` 请求提供者返回数据
4. 开发的分析图
-
+
diff --git a/docs/netty/introduction/Netty入门-第二话.md b/docs/netty/introduction/Netty入门-第二话.md
index a96fe97..7ed2153 100644
--- a/docs/netty/introduction/Netty入门-第二话.md
+++ b/docs/netty/introduction/Netty入门-第二话.md
@@ -7,7 +7,7 @@ categories:
- 入门
keywords: Netty
description: 对Netty的架构进行了解析,主要是Reactor设计模式的多种解决方案。同时讲解了Netty的核心模块组件。
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/netty/netty_logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/netty/netty_logo.jpg'
abbrlink: f846f3f
date: 2021-04-15 14:21:58
---
@@ -29,7 +29,7 @@ date: 2021-04-15 14:21:58
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
-
+
## Netty 的优点
@@ -80,7 +80,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
1. 当并发数很大,就会创建大量的线程,占用很大系统资源
2. 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在 Handler对象中的`read` 操作,导致上面的处理线程资源浪费
-
+
## Reactor 模式
@@ -97,7 +97,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
1. 基于线程池复用线程资源:不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务。(解决了当并发数很大时,会创建大量线程,占用很大系统资源)
2. 基于 `I/O` 复用模型:多个客户端进行连接,先把连接请求给`ServiceHandler`。多个连接共用一个阻塞对象`ServiceHandler`。假设,当C1连接没有数据要处理时,C1客户端只需要阻塞于`ServiceHandler`,C1之前的处理线程便可以处理其他有数据的连接,不会造成线程资源的浪费。当C1连接再次有数据时,`ServiceHandler`根据线程池的空闲状态,将请求分发给空闲的线程来处理C1连接的任务。(解决了线程资源浪费的那个问题)
-
+
@@ -105,7 +105,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
### I/O 复用结合线程池,就是 Reactor 模式基本设计思想,如图
-
+
对上图说明:
@@ -132,7 +132,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
原理图,并使用 `NIO` 群聊系统验证
-
+
### 方案说明
@@ -155,7 +155,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
### 方案说明
-
+
@@ -178,11 +178,11 @@ Netty is an asynchronous event-driven network application framework for rapid de
针对单 `Reactor` 多线程模型中,`Reactor` 在单线程中运行,高并发场景下容易成为性能瓶颈,可以让 `Reactor` 在多线程中运行
-
+
-
+
> SubReactor是可以有多个的,如果只有一个SubReactor的话那和`单 Reactor 多线程`就没什么区别了。
@@ -197,7 +197,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
### Scalable IO in Java 对 Multiple Reactors 的原理图解
-
+
### 方案优缺点说明
@@ -230,7 +230,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
`Netty` 主要基于主从 `Reactors` 多线程模型(如图)做了一定的改进,其中主从 `Reactor` 多线程模型有多个 `Reactor`
-
+
**对上图说明**
@@ -240,7 +240,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
### 工作原理示意图2 - 进阶版
-
+
@@ -248,7 +248,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
### 工作原理示意图3 - 详细版
-
+
@@ -669,9 +669,9 @@ public class NettyServerHandler extends ChannelInboundHandlerAdapter {
下面第一张图就是管道,中间会经过多个handler
-
+
-
+
说明:
@@ -917,11 +917,11 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
4. 我们经常需要自定义一个 `Handler` 类去继承 `ChannelInboundHandlerAdapter`,然后通过重写相应方法实现业务逻辑,我们接下来看看一般都需要重写哪些方法
-
+
## Pipeline 和 ChannelPipeline
@@ -931,7 +931,7 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
4. 常用方法
`ChannelPipeline addFirst(ChannelHandler... handlers)`,把一个业务处理类(`handler`)添加到链中的第一个位置`ChannelPipeline addLast(ChannelHandler... handlers)`,把一个业务处理类(`handler`)添加到链中的最后一个位置
@@ -940,7 +940,7 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
- `TestServerInitializer`和`HttpServerCodec`这些东西本身也是`handler`
- 一般来说事件从客户端往服务器走我们称为出站,反之则是入站。
@@ -950,7 +950,7 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
3. 常用方法
@@ -959,14 +959,14 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
## ChannelOption
1. `Netty` 在创建 `Channel` 实例后,一般都需要设置 `ChannelOption` 参数。
2. `ChannelOption` 参数如下:
-
+
## EventLoopGroup 和其实现类 NioEventLoopGroup
@@ -974,7 +974,7 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
4. 常用方法
`public NioEventLoopGroup()`,构造方法
@@ -985,11 +985,11 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
3. 举例说明 `Unpooled` 获取 `Netty` 的数据容器 `ByteBuf` 的基本使用
-
+
案例 1
@@ -1096,7 +1096,7 @@ public class NettyByteBuf02 {
-
+
代码如下:
@@ -1523,7 +1523,7 @@ public class MyServerHandler extends ChannelInboundHandlerAdapter {
4. 客户端浏览器和服务器端会相互感知,比如服务器关闭了,浏览器会感知,同样浏览器关闭了,服务器会感知
5. 运行界面
-
+
@@ -1724,7 +1724,7 @@ public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler
diff --git a/docs/os/操作系统-IO与零拷贝.md b/docs/os/操作系统-IO与零拷贝.md
index 2279387..a5f2838 100644
--- a/docs/os/操作系统-IO与零拷贝.md
+++ b/docs/os/操作系统-IO与零拷贝.md
@@ -9,7 +9,7 @@ categories:
- 操作系统
keywords: 操作系统,IO,零拷贝
description: 基本面试会问到的IO进行了详解,同时本篇文章也对面试以及平时工作中会看到的零拷贝进行了充分的解析。万字长文系列,读到就是赚到。
-cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@master/os/os_logo.jpg'
+cover: 'https://unpkg.zhimg.com/youthlql@1.0.0/os/os_logo.jpg'
abbrlink: e959db2e
date: 2021-04-08 15:21:58
---
@@ -90,7 +90,7 @@ date: 2021-04-08 15:21:58
4. 通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时(就是数据准备好的时候),则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。
5. 由于select函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。(一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。)
-
+
@@ -196,7 +196,7 @@ date: 2021-04-08 15:21:58
1. 来看一个标准设备(不是真实存在的,相当于一个逻辑上抽象的东西),通过它来帮助我们更好地理解设备交互的机制。可以看到一个包含两部分重要组件的设备。第一部分是向系统其他部分展现的硬件接口(interface)。同软件一样,硬件也需要一些接口,让系统软件来控制它的操作。因此,所有设备都有自己的特定接口以及典型交互的协议。
-
+
2. 第2部分是它的内部结构(internal structure)。这部分包含设备相关的特定实现,负责具体实现设备展示给系统的抽象接口。
@@ -231,11 +231,11 @@ While (STATUS == BUSY);//wait until device is done with your request
1. 没有中断时:进程1在CPU上运行一段时间(对应CPU那一行上重复的1),然后发出一个读取数据的I/O请求给磁盘。如果没有中断,那么操作系统就会简单自旋,不断轮询设备状态,直到设备完成I/O操作(对应其中的p)。当设备完成请求的操作后,进程1又可以继续运行。
-
+
2. 有了中断后:中断允许计算与I/O重叠(overlap),这是提高CPU利用率的关键。我们利用中断并允许重叠,操作系统就可以在等待磁盘操作时做其他事情。
-
+
- 在这个例子中,在磁盘处理进程1的请求时,操作系统在CPU上运行进程2。磁盘处理完成后,触发一个中断,然后操作系统唤醒进程1继续运行。这样,在这段时间,无论CPU还是磁盘都可以有效地利用。
@@ -243,7 +243,7 @@ While (STATUS == BUSY);//wait until device is done with your request
中断仍旧存在的缺点:
-
+
IO过程简述:
@@ -268,7 +268,7 @@ IO过程简述:
>
> 标准协议还有一点需要我们注意。具体来说,如果使用编程的I/O将一大块数据传给设备,CPU又会因为琐碎的任务而变得负载很重,浪费了时间和算力,本来更好是用于运行其他进程。下面的时间线展示了这个问题:
>
->
+>
>
> 进程1在运行过程中需要向磁盘写一些数据,所以它开始进行I/O操作,将数据从内存拷贝到磁盘(其中标示c的过程)。**拷贝结束后,磁盘上的I/O操作开始执行,此时CPU才可以处理其他请求。**
@@ -278,13 +278,13 @@ IO过程简述:
>
> DMA工作过程如下。为了能够将数据传送给设备,操作系统会通过编程告诉DMA引擎数据在内存的位置,要拷贝的大小以及要拷贝到哪个设备。在此之后,操作系统就可以处理其他请求了。当DMA的任务完成后,DMA控制器会抛出一个中断来告诉操作系统自己已经完成数据传输。修改后的时间线如下:
>
->
+>
>
> 从时间线中可以看到,数据的拷贝工作都是由DMA控制器来完成的。因为CPU在此时是空闲的,所以操作系统可以让它做一些其他事情,比如此处调度进程2到CPU来运行。因此进程2在进程1再次运行之前可以使用更多的CPU。
为了更好理解,看图:
-
+
过程:
@@ -302,7 +302,7 @@ IO过程简述:
场景:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。
-
+
1. 很明显发生了4次拷贝
@@ -326,7 +326,7 @@ IO过程简述:
`read()` 系统调用的过程中会把内核缓冲区的数据拷贝到用户的缓冲区里,为了减少这一步开销,我们可以用 `mmap()` 替换 `read()` 系统调用函数。`mmap()` 系统调用函数会直接把内核缓冲区里的数据映射到用户空间,这样,操作系统内核与用户空间共享缓冲区,就不需要再进行任何的数据拷贝操作。
-
+
总的来说mmap减少了一次数据拷贝,总共4次上下文切换,3次数据拷贝
@@ -336,7 +336,7 @@ IO过程简述:
`Linux2.1` 版本提供了 `sendFile` 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 `SocketBuffer`
-
+
总的来说有2次上下文切换,3次数据拷贝。
@@ -344,7 +344,7 @@ IO过程简述:
`Linux在2.4` 版本中,做了一些修改,避免了从内核缓冲区拷贝到 `Socketbuffer` 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝
-
+
diff --git a/docs/rpc/dubbo/Dubbo源码系列V1-01和02.Dubbo第一二节-基本应用与高级应用.md b/docs/rpc/dubbo/Dubbo源码系列V1-01和02.Dubbo第一二节-基本应用与高级应用.md
new file mode 100644
index 0000000..9c25b9a
--- /dev/null
+++ b/docs/rpc/dubbo/Dubbo源码系列V1-01和02.Dubbo第一二节-基本应用与高级应用.md
@@ -0,0 +1,1040 @@
+---
+title: Dubbo源码系列V1-01和02.Dubbo第一二节-基本应用与高级应用
+tags:
+ - Dubbo
+ - rpc
+categories:
+ - rpc
+ - Dubbo源码系列v1
+keywords: Dubbo,rpc
+description: 前两节合成一节
+cover: 'https://cdn.jsdelivr.net/gh/youthlql/youthlql/img/dubbo.png'
+abbrlink: d3c530c4
+date: 2021-09-11 15:21:58
+---
+
+
+
+
+
+
+
+
+# Dubbo源码
+
+
+
+## 第一节: Dubbo框架介绍
+
+### 什么是RPC
+
+**维基百科**是这么定义RPC的:
+
+
+
+> 在[分布式计算](https://zh.wikipedia.org/wiki/%E5%88%86%E5%B8%83%E5%BC%8F%E8%AE%A1%E7%AE%97)**,远程过程调用**(英语:Remote Procedure Call,缩写为 RPC)是一个计算机通信[协议](https://zh.wikipedia.org/wiki/%E7%B6%B2%E7%B5%A1%E5%82%B3%E8%BC%B8%E5%8D%94%E8%AD%B0)。该协议允许运行于一台计算机的[程序](https://zh.wikipedia.org/wiki/%E7%A8%8B%E5%BA%8F)调用另一个[地址空间](https://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4)(通常为一个开放网络的一台计算机)的[子程序](https://zh.wikipedia.org/wiki/%E5%AD%90%E7%A8%8B%E5%BA%8F),而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过**发送请求-接受回应**进行信息交互的系统。
+>
+>
+>
+> 如果涉及的软件采用[面向对象编程](https://zh.wikipedia.org/wiki/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B),那么远程过程调用亦可称作**远程调用**或**远程方法调用**,例:[Java RMI](https://zh.wikipedia.org/wiki/Java_RMI)。
+
+
+
+所以,对于Java程序员而言,RPC就是**远程方法调用**。
+
+
+
+**远程方法调用**和**本地方法调用**是相对的两个概念,本地方法调用指的是进程内部的方法调用,而远程方法调用指的是两个进程内的方法相互调用。
+
+
+
+如果实现远程方法调用,基本的就是通过网络,通过传输数据来进行调用。
+
+
+
+所以就有了:
+
+1. RPC over Http:基于Http协议来传输数据
+2. PRC over Tcp:基于Tcp协议来传输数据
+
+
+
+对于所传输的数据,可以交由RPC的双方来协商定义,但基本都会包括:
+
+1. 调用的是哪个类或接口
+2. 调用的是哪个方法,方法名和方法参数类型(考虑方法重载)
+3. 调用方法的入参
+
+
+
+所以,我们其实可以看到RPC的自定义性是很高的,各个公司内部都可以实现自己的一套RPC框架,而**Dubbo**就是阿里所开源出来的一套RPC框架。
+
+
+
+### 什么是Dubbo
+
+官网地址:[http://dubbo.apache.org/zh/](http://dubbo.apache.org/zh/)
+
+
+
+目前,官网上是这么介绍的:**Apache Dubbo 是一款高性能、轻量级的开源 Java服务框架**
+
+在几个月前,官网的介绍是:**Apache Dubbo 是一款高性能、轻量级的开源 JavaRPC框架**
+
+
+
+为什么会将**RPC**改为**服务**?
+
+
+
+Dubbo一开始的定位就是RPC,专注于两个服务之间的调用。但随着微服务的盛行,除开**服务调用**之外,Dubbo也在逐步的涉猎服务治理、服务监控、服务网关等等,所以现在的Dubbo目标已经不止是RPC框架了,而是和Spring Cloud类似想成为了一个**服务**框架。
+
+
+
+Dubbo网关参考:[https://github.com/apache/dubbo-proxy](https://github.com/apache/dubbo-proxy)(社区不是很活跃)
+
+
+
+### 基本原理
+
+
+
+
+
+### 开源RPC框架对比
+
+| 功能 | Hessian | Montan | rpcx | gRPC | Thrift | Dubbo | Dubbox | Spring Cloud |
+| ---------------- | ------- | ---------------------------- | ------ | ----------------- | ------------- | ------- | -------- | ------------ |
+| 开发语言 | 跨语言 | Java | Go | 跨语言 | 跨语言 | Java | Java | Java |
+| 分布式(服务治理) | × | √ | √ | × | × | √ | √ | √ |
+| 多序列化框架支持 | hessian | √(支持Hessian2、Json,可扩展) | √ | × 只支持protobuf) | ×(thrift格式) | √ | √ | √ |
+| 多种注册中心 | × | √ | √ | × | × | √ | √ | √ |
+| 管理中心 | × | √ | √ | × | × | √ | √ | √ |
+| 跨编程语言 | √ | ×(支持php client和C server) | × | √ | √ | × | × | × |
+| 支持REST | × | × | × | × | × | × | √ | √ |
+| 关注度 | 低 | 中 | 低 | 中 | 中 | 中 | 高 | 中 |
+| 上手难度 | 低 | 低 | 中 | 中 | 中 | 低 | 低 | 中 |
+| 运维成本 | 低 | 中 | 中 | 中 | 低 | 中 | 中 | 中 |
+| 开源机构 | Caucho | Weibo | Apache | Google | Apache | Alibaba | Dangdang | Apache |
+
+## 第二节: Dubbo的基本应用与高级应用
+
+
+
+官网:[http://dubbo.apache.org/zh/docs/v2.7/user/](http://dubbo.apache.org/zh/docs/v2.7/user/)
+
+管理台github地址:[https://github.com/apache/dubbo-admin](https://github.com/apache/dubbo-admin)
+
+
+
+Dubbo提供了很多功能,这里我们只介绍几种比较重要的,其他功能可以去Dubbo官网上查看。
+
+
+
+### 项目目录
+
+```java
+dubbo-youthlql-demo
+├── consumer/
+| ├── consumer.iml
+| ├── pom.xml
+| └── src/
+| ├── main/
+| | ├── java/
+| | | └── com/
+| | | └── youthlql/
+| | | ├── consumer/
+| | | | ├── AsyncDubboConsumerDemo.java
+| | | | ├── CallbackDubboConsumerDemo.java
+| | | | ├── ClusterDubboConsumerDemo.java
+| | | | ├── DemoServiceListenerImpl.java
+| | | | ├── GenericDubboConsumerDemo.java
+| | | | ├── LoadBalanceDubboConsumerDemo.java
+| | | | ├── MockDubboConsumerDemo.java
+| | | | ├── StubDubboConsumerDemo.java
+| | | | └── TimeoutDubboConsumerDemo.java
+| | | ├── controller/
+| | | | ├── ConsumerInterceptor.java
+| | | | └── HelloController.java
+| | | └── DubboConsumerDemo.java
+| | └── resources/
+| | └── application.yml
+| └── test/
+| └── java/
+├── dubbo-youthlql-demo.iml
+├── interface/
+| ├── interface.iml
+| ├── pom.xml
+| └── src/
+| ├── main/
+| | ├── java/
+| | | └── com/
+| | | └── youthlql/
+| | | ├── DemoService.java
+| | | ├── DemoServiceListener.java
+| | | ├── DemoServiceMock.java
+| | | └── DemoServiceStub.java
+| | └── resources/
+| └── test/
+| └── java/
+├── pom.xml
+└── provider/
+ ├── pom.xml
+ ├── provider.iml
+ └── src/
+ ├── main/
+ | ├── java/
+ | | └── com/
+ | | └── youthlql/
+ | | ├── DubboProviderDemo.java
+ | | ├── provider/
+ | | | └── service/
+ | | | ├── AsyncDemoService.java
+ | | | ├── CallBackDemoService.java
+ | | | ├── DefaultDemoService.java
+ | | | ├── GenericDemoService.java
+ | | | ├── RestDemoService.java
+ | | | └── TimeoutDemoService.java
+ | | └── UpdateBeanPostProcessors.java
+ | └── resources/
+ | ├── application.properties
+ | └── log4j.properties
+ └── test/
+ └── java/
+```
+
+
+
+项目目录要大概记住一下,后面代码层次大概心里有个数。
+
+### 负载均衡
+
+官网地址:[http://dubbo.apache.org/zh/docs/v2.7/user/examples/loadbalance/](http://dubbo.apache.org/zh/docs/v2.7/user/examples/loadbalance/)
+
+
+
+1. 消费端就这样配置
+
+```java
+ @Reference(version = "default", loadbalance = "consistenthash")
+ private DemoService demoService;
+```
+
+2. 服务端就这样配置
+
+```java
+@Service(version = "default",loadbalance = "consistenthash")
+public class DefaultDemoService implements DemoService {
+...
+}
+```
+
+3. 如果在消费端和服务端都配置了负载均衡策略,以消费端为准。
+ - 上面两个配的是一致性hash算法。
+ - 根据消费者传的参数来进行hash,多次调用,如果参数一样,那么都会负载均衡到服务提供者的同一台机器上。
+
+
+
+
+
+> 这其中比较难理解的就是**最少活跃调用数**是如何进行统计的?讲道理,最少活跃数应该是在**服务提供者端**进行统计的,服务提供者统计**有多少个请求正在执行中**。但在Dubbo中,就是**不讲道理**,它是在消费端进行统计的,为什么能在消费端进行统计?
+
+逻辑是这样的:
+
+1. 每个消费者都会从注册中心(常用的是Zookeeper)缓存所调用服务的所有提供者信息到本地,比如记为p1、p2、p3三个服务提供者,每个提供者内都有一个属性记为active,默认位0
+2. 消费者在调用服务时,如果负载均衡策略是**leastactive**
+3. 消费者端会判断缓存的所有服务提供者的active,选择最小的,如果都相同,则随机
+4. 选出某一个服务提供者后,假设是p2,Dubbo就会对p2.active+1
+5. 然后真正发出请求调用该服务
+6. 消费端收到响应结果后,对p2.active-1
+7. 这样就完成了对某个服务提供者当前活跃调用数进行了统计,并且并不影响服务调用的性能
+8. 如果由服务提供者来统计调用数反而不好统计,因为服务提供者有多个,你无法确定是哪个服务提供者统计调用数,除非你放到zookeeper这种分布式共享的数据中心,但是这样的话,每个消费者都要请求zookeeper找到需要调用的那一台服务提供者机器然后加1,在调用结束后,还要在zookeeper上进行减1操作,zookeeper明显扛不住。
+9. 由服务消费者来统计调用数的话,虽然每个消费者都有自己的一套调用数数据,调用数数据可能不一样,但是经过长时间的调用后,每个消费者自己本地存的调用数据还是能够有差不多的趋势(这里的趋势不是指数据相等)。比如说p2响应很慢,堆积了很多请求,那么每个消费者在请求多次后,短时间内都不会再请求p2
+
+
+
+### 服务超时
+
+在服务提供者和服务消费者上都可以配置服务超时时间,这两者是不一样的。
+
+消费者调用一个服务,分为三步:
+
+1. 消费者发送请求(网络传输)
+2. 服务端执行服务
+3. 服务端返回响应(网络传输)
+
+
+
+> 如果在服务端和消费端只在**其中一方**配置了timeout,那么没有歧义,表示消费端调用服务的超时时间,**消费端如果超过时间还没有收到响应结果,则消费端会抛超时异常**。但服务端不会抛异常,服务端在执行服务后,会检查**执行该服务**的时间,如果超过timeout,则会打印一个**超时日志**。服务会正常的执行完。
+
+
+
+1. 如果在服务端和消费端各配了一个timeout,那就比较复杂了,假设
+ 1. 服务执行为5s
+ 2. 消费端timeout=3s
+ 3. 服务端timeout=6s
+
+ ```java
+ //服务端
+ @Service(version = "timeout", timeout = 6000)
+ public class TimeoutDemoService implements DemoService {
+ ...
+ }
+
+ //消费端
+ @Reference(version = "timeout", loadbalance = "roundrobin",timeout = 3000)
+ private DemoService demoService;
+ ```
+
+2. 那么消费端调用服务时,消费端会收到超时异常(因为消费端超时了),服务端一切正常(服务端没有超时)。
+3. 无论何种情况,服务端的timeout配置的作用是:如果服务执行时间超过这个timeout,仅仅只是打印一个超时日志。
+
+
+
+
+
+### 集群容错
+
+官网地址:[http://dubbo.apache.org/zh/docs/v2.7/user/examples/fault-tolerent-strategy/](http://dubbo.apache.org/zh/docs/v2.7/user/examples/fault-tolerent-strategy/)
+
+
+
+> 集群容错表示:服务消费者在调用某个服务时,这个服务有多个服务提供者,在经过负载均衡后选出其中一个服务提供者之后进行调用,但调用报错后,Dubbo所采取的后续处理策略。后续处理策略就是会重试调用其它机器上的服务提供者,加上第一次调用默认是重试2次,总共调用3次。
+
+也是既可以在消费端配置,也可以在服务端配置
+
+```java
+@Reference(timeout = 1000, cluster = "failover")
+private DemoService demoService;
+```
+
+
+
+### 服务降级
+
+官网地址:[http://dubbo.apache.org/zh/docs/v2.7/user/examples/service-downgrade/](http://dubbo.apache.org/zh/docs/v2.7/user/examples/service-downgrade/)
+
+
+
+1. 服务降级表示:服务消费者在调用某个服务提供者时,如果该服务提供者报错了,所采取的备选措施。
+
+2. 集群容错和服务降级的区别在于:
+ 1. 集群容错是整个集群范围内的容错
+ 2. 服务降级是单个服务提供者的自身容错
+
+3. 下面的mock就是一种容错,意思是服务调用失败后,直接返回123
+
+```java
+@Reference(version = "timeout", timeout = 1000, mock = "fail: return 123")
+private DemoService demoService;
+```
+
+4. mock如何返回对象这种复杂数据可以看官网。
+
+### 本地存根
+
+官网地址:[http://dubbo.apache.org/zh/docs/v2.7/user/examples/local-stub/](http://dubbo.apache.org/zh/docs/v2.7/user/examples/local-stub/)
+
+
+
+本地存根,名字很抽象,但实际上不难理解,本地存根就是一段逻辑,这段逻辑是在服务消费端执行的,这段逻辑一般都是由服务提供者提供,服务提供者可以利用这种机制在服务消费者远程调用服务提供者之前或之后再做一些其他事情,比如结果缓存,请求参数验证等等。
+
+> consumer项目
+
+```java
+package com.youthlql.consumer;
+
+@EnableAutoConfiguration
+public class StubDubboConsumerDemo {
+
+
+ // @Reference(version = "timeout", timeout = 1000, stub = "com.youthlql.DemoServiceStub")
+ @Reference(version = "timeout", timeout = 1000, stub = "true")
+ private DemoService demoService;
+
+ public static void main(String[] args) throws IOException {
+ ConfigurableApplicationContext context = SpringApplication.run(StubDubboConsumerDemo.class);
+
+ DemoService demoService = context.getBean(DemoService.class);
+
+ System.out.println((demoService.sayHello("周瑜")));
+
+ }
+}
+```
+
+1. stub=true的是个默认配置
+2. 默认用接口的全限定类名+Stub去调用,也就是`com.youthlql.DemoServiceStub`
+3. 比如这个例子,当消费者去调用`demoService.sayHello("周瑜")`时,会首先调用`interface项目`下的`DemoServiceStub`的`sayHello`方法。如果调用失败就返回`"容错数据"`
+4. 如果找不到`com.youthlql.DemoServiceStub`就抛异常
+5. 注意`DemoServiceStub`这个类不一定需要写在interface项目里的,写在哪里都行,只要能通过pom.xml的maven依赖找到`com.youthlql.DemoServiceStub`你这个路径就行
+6. 也可以stub直接指定全类名,这样就可以每一个消费者都提供一个本地存根
+
+```java
+@Reference(version = "timeout", timeout = 1000, stub = "com.youthlql.DemoServiceStub")
+private DemoService demoService;
+```
+
+
+
+> interface项目
+
+```java
+package com.youthlql;
+
+public class DemoServiceStub implements DemoService {
+
+ private final DemoService demoService;
+
+ // 构造函数传入真正的远程代理对象
+ public DemoServiceStub(DemoService demoService){
+ this.demoService = demoService;
+ }
+
+ @Override
+ public String sayHello(String name) {
+ /**
+ * + * 1.本地存根和服务降级有一些类似,不过本地存根比服务降级功能要强一点,比如你可以在这个 + * 地方做一些事情 + * 2.此代码在客户端(消费端)执行, 你可以在客户端做ThreadLocal本地缓存,把服务端返回的结果缓存到消费端。或预先验证参数是否合法,等等 + *
+ * @since 2021/8/7 - 17:41 + */ + try { + return demoService.sayHello(name); // safe null + } catch (Exception e) { + // 你可以容错,可以做任何AOP拦截事项 + return "容错数据"; + } + } +} +``` + + + +### 本地伪装 + +官网地址:[http://dubbo.apache.org/zh/docs/v2.7/user/examples/local-mock/](http://dubbo.apache.org/zh/docs/v2.7/user/examples/local-mock/) + + + +本地伪装就是Mock,Dubbo中Mock的功能相对于本地存根更简单一点,Mock其实就是Dubbo中的服务降级,不同的名词罢了 + + + +### 参数回调 + + +官网地址:[http://dubbo.apache.org/zh/docs/v2.7/user/examples/callback-parameter/](http://dubbo.apache.org/zh/docs/v2.7/user/examples/callback-parameter/) + + + +> - 参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。 +> - 参数回调的用途:Dubbo 将基于长连接生成反向代理,**这样就可以从服务器端调用客户端逻辑。** + +1. 首先,如果当前服务支持参数回调,意思就是:对于某个服务接口中的某个方法,如果想支持消费者在调用这个方法时能设置回调逻辑,那么该方法就需要提供一个入参用来表示回调逻辑。 + +> interface项目 + +```java +package com.youthlql; + +import java.util.concurrent.CompletableFuture; + +public interface DemoService { + // 同步调用方法 + String sayHello(String name); + + // 异步调用方法 + default CompletableFuture+ * 1.通过下面sayhello的方法注释,我们可以知道DemoService的sayHello方法的index=2的参数是回调对象, + * 这个回调对象DemoServiceListener是Dubbo给我们生成的代理对象。 + * 2.那么Dubbo怎么知道,sayhello的index=2的参数是回调对象呢?为什么不可以是普通参数呢? + * dubbo又是怎样区分DemoService里的两个sayhello方法呢? + * 3.全都是通过@Service注解里的methods属性来标识的。通过methods属性指定name为sayHello的方法, + * 它的index=2的参数是回调对象。并且同时只支持callbacks = 3,3个回调,数目超过了会报错 + * 3.version = "callback" 这个字符串是可以随便写的,version = "call"也行 + *
+ * @author https://github.com/youthlql + * @since 2021/8/7 - 18:32 + */ +@Service(version = "callback", methods = {@Method(name = "sayHello", arguments = {@Argument(index = 2, callback = true)})}, callbacks = 3) +public class CallBackDemoService implements DemoService { + + private final Map+ * @param name + * @param key + * 1.上面两参数肯定都是消费端那边传过来的,第三个参数DemoServiceListener + * 不可能是消费者那边传过来的一个空对象吧。它实际上是dubbo生成的代理对象。 + *
+ * @since 2021/8/7 - 18:17 + */ + @Override + public String sayHello(String name, String key, DemoServiceListener callback) { + System.out.println("执行了回调服务" + name); + + callback.changed("xxxx"); + + listeners.put(key, callback); + URL url = RpcContext.getContext().getUrl(); + return String.format("%s:%s, Hello, %s", url.getProtocol(), url.getPort(), name); // 正常访问 + } + +} + +``` + + + +3. 因为Dubbo协议是基于长连接的,所以消费端在两次调用同一个方法时想指定不同的回调逻辑,那么就需要在调用时在指定一定key进行区分,这里就是d1,d2,d3。 + +```java +System.out.println(demoService.sayHello("周瑜", "d1", new DemoServiceListenerImpl1())); +System.out.println(demoService.sayHello("周瑜", "d2", new DemoServiceListenerImpl2())); +System.out.println(demoService.sayHello("周瑜", "d3", new DemoServiceListenerImpl3())); +``` + + + + + +### 异步调用 + + +官网地址:[http://dubbo.apache.org/zh/docs/v2.7/user/examples/async-call/](http://dubbo.apache.org/zh/docs/v2.7/user/examples/async-call/) + + + +理解起来比较容易,主要要理解[CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html)。只是说dubbo也可以支持java的CompletableFuture + +其他异步调用方式:[https://mp.weixin.qq.com/s/U3eyBUy6HBVy-xRw3LGbRQ](https://mp.weixin.qq.com/s/U3eyBUy6HBVy-xRw3LGbRQ) + + + +```java +package com.youthlql; + +import java.util.concurrent.CompletableFuture; + +public interface DemoService { + // 同步调用方法 + String sayHello(String name); + + // 异步调用方法 + default CompletableFuture+ * @param s 方法名字 + * @param strings 参数类型数组 + * @param objects 参数值数组 + *
+ * @since 2021/8/7 - 18:57 + */ + @Override + public Object $invoke(String s, String[] strings, Object[] objects) throws GenericException { + System.out.println("执行了generic服务"); + + return "执行的方法是" + s; + } +} + +``` + +意思就是实际暴露出去的服务依然是`com.youthlql.DemoService`,并且版本是generic。只是消费者调用的时候,最终执行的逻辑是这个`$invoke`方法。就是服务消费者你该怎么用还是怎么用,只是服务提供者后面真正执行逻辑不再是实现demoservice接口,实现sayhello方法了。而是GenericService的$invoke方法 + +### Dubbo中的REST + + + +官网地址:[http://dubbo.apache.org/zh/docs/v2.7/user/rest/](http://dubbo.apache.org/zh/docs/v2.7/user/rest/) + + + +注意Dubbo的REST也是Dubbo所支持的一种**协议**。 + + + +当我们用Dubbo提供了一个服务后,如果消费者没有使用Dubbo也想调用服务,那么这个时候我们就可以让我们的服务支持REST协议,这样消费者就可以通过REST形式调用我们的服务了。 + +注意:如果某个服务只有REST协议可用,那么该服务必须用@Path注解定义访问路径 + + + +> application.properties 即支持dubbo协议,又支持rest协议 + +```properties + # Spring boot application +spring.application.name=dubbo-provider-demo +server.port=8081 + +# Base packages to scan Dubbo Component: @org.apache.dubbo.config.annotation.Service +dubbo.scan.base-packages=com.youthlql.provider.service +dubbo.application.name=${spring.application.name} + + +## Dubbo Registry +dubbo.registry.address=zookeeper://127.0.0.1:2181 + +# Dubbo Protocol +#dubbo.protocol.name=dubbo +#dubbo.protocol.port=20880 + + +#dubbo.protocol.name=rest +#dubbo.protocol.port=8083 + +dubbo.protocols.p1.id=dubbo1 +dubbo.protocols.p1.name=dubbo +dubbo.protocols.p1.port=20881 +dubbo.protocols.p1.host=0.0.0.0 + +dubbo.protocols.p2.id=rest +dubbo.protocols.p2.name=rest +dubbo.protocols.p2.port=8083 +dubbo.protocols.p2.host=0.0.0.0 + +#dubbo.protocols.p3.id=dubbo3 +#dubbo.protocols.p3.name=dubbo +#dubbo.protocols.p3.port=20883 +#dubbo.protocols.p3.host=0.0.0.0 +``` + + + + + +```java +package com.youthlql.provider.service; + + +@Service(version = "rest", protocol = "p2") +@Path("demo") +public class RestDemoService implements DemoService { + + @GET + @Path("say") + @Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8}) + @Override + public String sayHello(@QueryParam("name") String name) { + System.out.println("执行了rest服务" + name); + + URL url = RpcContext.getContext().getUrl(); + return String.format("%s: %s, Hello, %s", url.getProtocol(), url.getPort(), name); // 正常访问 + } + +} +``` + + + +```java +package com.youthlql.provider.service; + +import com.youthlql.DemoService; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.config.annotation.Service; +import org.apache.dubbo.rpc.RpcContext; + +import java.util.concurrent.CompletableFuture; + +@Service(version = "async", protocol = "p1") +public class AsyncDemoService implements DemoService { + + @Override + public String sayHello(String name) { + System.out.println("执行了同步服务" + name); + URL url = RpcContext.getContext().getUrl(); + return String.format("%s:%s, Hello, %s", url.getProtocol(), url.getPort(), name); // 正常访问 + } + + @Override + public CompletableFuture
+
+3. **动态配置**这里,也可以很方便的替代服务提供者@service注解上标注的那些配置。管理台是实时生效的,如果改代码里的@service还需要重启服务。
+
+
+
+很多配置都可以在管理台上配。管理台上写的配置会持久化在**你配置的配置中心**里。只有注册中心里的服务提供者信息不持久化,如果注册中心是zookeeper,那么服务提供者在zk上就是临时节点。
+
+### 动态配置
+
+官网地址:[http://dubbo.apache.org/zh/docs/v2.7/user/examples/config-rule/](http://dubbo.apache.org/zh/docs/v2.7/user/examples/config-rule/)
+
+
+
+注意动态配置修改的是服务**参数**,并不能修改服务的协议、IP、PORT、VERSION、GROUP,因为这5个信息是服务的标识信息,是服务的身份证号,是不能修改的。
+
+
+
+### 服务路由
+
+官网地址:[http://dubbo.apache.org/zh/docs/v2.7/user/examples/routing-rule/](http://dubbo.apache.org/zh/docs/v2.7/user/examples/routing-rule/)
+
+> 注意所有的东西都要跟官网结合着看
+
+举个官网的例子,简单说下
+
+```yaml
+# app1的消费者只能消费所有端口为20880的服务实例
+# app2的消费者只能消费所有端口为20881的服务实例
+---
+scope: application
+force: true
+runtime: true
+enabled: true
+key: governance-conditionrouter-consumer
+conditions:
+ - application=app1 => address=*:20880 # 如果你的应用是app1,那么你只能访问20880这个端口的服务
+ - application=app2 => address=*:20881
+...
+```
+
+
+
+#### 标签路由
+
+```yaml
+# governance-tagrouter-provider应用增加了两个标签分组tag1和tag2
+# tag1包含一个实例 127.0.0.1:20880
+# tag2包含一个实例 127.0.0.1:20881
+---
+ force: false
+ runtime: true
+ enabled: true
+ key: governance-tagrouter-provider
+ tags:
+ - name: tag1
+ addresses: ["127.0.0.1:20880"]
+ - name: tag2
+ addresses: ["127.0.0.1:20881"]
+ ...
+```
+
+
+
+```java
+package com.youthlql.controller;
+
+import org.apache.dubbo.rpc.Constants;
+import org.apache.dubbo.rpc.RpcContext;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConsumerInterceptor implements HandlerInterceptor {
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+
+ // 测试账号
+ List