|
1 |
| -## 灰度发布 |
2 |
| -灰度发布是在多版本控制的基础上进一步扩展实现出来的项目 -> fm-cloud-graybunny,抽象出灰度服务、灰度服务实例、灰度策略、灰度决策等。支持A/B test, 金丝雀 test。 灰度策略可以从request ip, request patameter, request header等方面进行去创建,也可以根据bamboo的LoadBalanceRequestTrigger结合graybuanny的接口去扩展灰度策略和灰度决策。 |
3 |
| - |
4 |
| -##### 场景 |
5 |
| - |
6 |
| -有两个服务,共四个服务实例,分别是ServiceA-1, ServiceA-2, ServiceB-1。其中ServiceA-2是灰度实例。 |
7 |
| -场景1:所有请求头usertype:old,ip:10.217.***.***的请求或者请求头usertype:test, url 参数action:create的请求,都会被转发到的灰度服务ServiceA-2 。 |
8 |
| -场景2:ServiceA-2通过一段时间的观察,判定运行稳定,开始ServiceA-2删除灰度标记,开始和ServiceA-1一样会加入正常的负载均衡规则当中。 |
9 |
| -场景3:服务ServiceB发布新版本,ServiceB-2需要灰度注册,注册成功后所有的请求不能转发到ServiceB-2, 在为ServiceB-2设置灰度策略后,符合策略的请求才会被转发到ServiceB-2上。 |
10 |
| - |
11 |
| - |
12 |
| -##### 思路 |
13 |
| - |
14 |
| -从上面的场景分析,可以归纳出两个对象:服务实例和调用请求;服务实例的灰度管理是基础,调用请求时如何决策路由,都是根据服务实例的灰度策略去判断的。既然有灰度管理这个概念,那么从功能上分,就会有client-server之分,所以又可以从gray-client和gray-server去分析。接下来将一步一步去分析这四个方面。 |
15 |
| - |
16 |
| -* 灰度实例 |
17 |
| - |
18 |
| - |
19 |
| - |
20 |
| -实例注册:服务实例添加到灰度管理中。 |
21 |
| -实例下线:服务实例下线,从灰度管理中删除。 |
22 |
| -灰度开关:调整服务实例的灰度状态,有启用、禁用两个状态,禁用的实例不纳入灰度列表中。 |
23 |
| -灰度策略:请求是否可以被转发到该服务实例的条件,只有通过,请求才有可能会被转发到该实例上。 |
24 |
| - |
25 |
| -* 调用请求 |
26 |
| - |
27 |
| - |
28 |
| - |
29 |
| -灰度决策:根据请求的信息去匹配灰度服务实例的灰度策略,如果匹配上,会将服务实例加入到通过列表中。如果都没有匹配上,就按bamboo的路由规则去筛选非灰度的服务实例进行转发。 |
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 |
| -根据上面的思路,设计以下对象和接口。共6个接口,4个模型对象。 |
58 |
| - |
59 |
| - |
60 |
| - |
61 |
| -对象: |
62 |
| -* GrayService: 灰度服务 |
63 |
| -* GrayInstance: 灰度实例,有状态属性 |
64 |
| -* GrayPolicyGroup: 灰度策略组,有状态属性 |
65 |
| -* GrayPolicy: 灰度策略 |
66 |
| - |
67 |
| -接口: |
68 |
| -* GrayManager: |
69 |
| -灰度客户端管理器,维护灰度列表,维护自身灰度状态,创建灰度决策对象。抽象实现类AbstractGrayManager实现了基础的获取灰度列表, 创建灰度决策对象的能力。BaseGrayManger在期基础上进行了扩展,将灰度列表缓存起来,定时从灰度服务端更新灰度列表。 |
70 |
| - |
71 |
| -* InformationClient: |
72 |
| -该接口主要是负责和灰度服务端进行通信,获取灰度列表,编辑灰度实例等能力。其实现类HttpInformationClient默认使用http方式访问灰度服务端。 |
73 |
| -子类InformationClientDecorator是一个适配器类,RetryableInformationClient继承了InformationClientDecorator类,实现了重试的功能。 |
74 |
| - |
75 |
| -* GrayDecision: |
76 |
| -该接口是灰度决策,用来判断请求是否匹配灰度策略。实现了ip匹配、request parameter匹配、request header匹配、BambooRequestContext中的参数匹配器以及合并匹配等多个匹配能力。 |
77 |
| - |
78 |
| -* GrayDecisionFactory: |
79 |
| -灰度决策的工厂类,其默认实现类支持上述几种灰度决策的创建。 |
80 |
| - |
81 |
| -* GrayServiceManager: |
82 |
| -灰度服务管理类,属于服务端的类。主要是编辑服务实例,编辑灰度策略,以及维护最新的灰度列表。 |
83 |
| - |
84 |
| -* GrayBunnyServerEvictor: |
85 |
| -如果灰度服务实例下线后, 由于意外情况,没有向灰度服务端发送删除请求, 服务端会每隔一段时间调用该接口的方法,检查灰度列表中的实例是否下线,如果实例已下线,就将其从灰度列表中删除。 |
86 |
| - |
87 |
| - |
88 |
| -##### 代码实现 |
89 |
| -将模型抽象成接口和对象设计出来之后,实现思路就清晰了。 |
90 |
| - |
91 |
| -* 灰度路由: |
92 |
| -灰度路由是客户端必须要实现的能力,gray是在bamboo的基础上扩展的,所以gray的路由规则对象GrayLoadBalanceRule继承了BambooZoneAvoidanceRule, |
93 |
| -逻辑是这样的: |
94 |
| -1、 判断目标服务是否有灰度实例。 |
95 |
| -2.1、 如果没有, 执行父类逻辑。结束。 |
96 |
| -2.2、 有灰度实例,先将灰度实例和非灰度实例筛选出来。 |
97 |
| -3、 挑选灰度实例, 筛选调用请求匹配上灰度实例的策略。 |
98 |
| -4.1、 如果没有匹配的灰度实例, 将非灰度实例列表传递过去执行父类的筛选逻辑。结束。 |
99 |
| -4.2、 如果有匹配的灰度实例, 从其中按轮询的方式挑选出一个实例。结束。 |
100 |
| -```java |
101 |
| -/** |
102 |
| - * 灰度发布的负载规则 |
103 |
| - */ |
104 |
| -public class GrayLoadBalanceRule extends BambooZoneAvoidanceRule { |
105 |
| - |
106 |
| - protected CompositePredicate grayCompositePredicate; |
107 |
| - |
108 |
| - public GrayLoadBalanceRule() { |
109 |
| - super(); |
110 |
| - GrayDecisionPredicate apiVersionPredicate = new GrayDecisionPredicate(this); |
111 |
| - grayCompositePredicate = CompositePredicate.withPredicates(super.getPredicate(), |
112 |
| - apiVersionPredicate).build(); |
113 |
| - } |
114 |
| - |
115 |
| - |
116 |
| - @Override |
117 |
| - public Server choose(Object key) { |
118 |
| - ILoadBalancer lb = getLoadBalancer(); |
119 |
| - BambooRequestContext requestContext = BambooRequestContext.currentRequestCentxt(); |
120 |
| - if (requestContext != null && getGrayManager().isOpen(requestContext.getServiceId())) { |
121 |
| - GrayService grayService = getGrayManager().grayService(requestContext.getServiceId()); |
122 |
| - List<Server> servers = lb.getAllServers(); |
123 |
| - List<Server> grayServers = new ArrayList<>(grayService.getGrayInstances().size()); |
124 |
| - List<Server> normalServers = new ArrayList<>(servers.size() - grayService.getGrayInstances().size()); |
125 |
| - for (Server server : servers) { |
126 |
| - DiscoveryEnabledServer disServer = (DiscoveryEnabledServer) server; |
127 |
| - if (grayService.getGrayInstance(disServer.getInstanceInfo().getInstanceId()) != null) { |
128 |
| - grayServers.add(server); |
129 |
| - } else { |
130 |
| - normalServers.add(server); |
131 |
| - } |
132 |
| - } |
133 |
| - |
134 |
| - Optional<Server> server = grayCompositePredicate.chooseRoundRobinAfterFiltering(grayServers, key); |
135 |
| - if (server.isPresent()) { |
136 |
| - return server.get(); |
137 |
| - } else { |
138 |
| - return choose(super.getPredicate(), normalServers, key); |
139 |
| - } |
140 |
| - } |
141 |
| - return super.choose(key); |
142 |
| - } |
143 |
| - |
144 |
| - |
145 |
| - private Server choose(AbstractServerPredicate serverPredicate, List<Server> servers, Object key) { |
146 |
| - Optional<Server> server = serverPredicate.chooseRoundRobinAfterFiltering(servers, key); |
147 |
| - if (server.isPresent()) { |
148 |
| - return server.get(); |
149 |
| - } else { |
150 |
| - return null; |
151 |
| - } |
152 |
| - } |
153 |
| - |
154 |
| - |
155 |
| - public GrayManager getGrayManager() { |
156 |
| - return GrayClientAppContext.getGrayManager(); |
157 |
| - } |
158 |
| -} |
159 |
| - |
160 |
| -``` |
161 |
| - |
162 |
| -灰度决策的执行代码在GrayDecisionPredicate中 |
163 |
| - |
164 |
| -```java |
165 |
| -public class GrayDecisionPredicate extends AbstractServerPredicate { |
166 |
| - |
167 |
| - public GrayDecisionPredicate(GrayLoadBalanceRule rule) { |
168 |
| - super(rule); |
169 |
| - } |
170 |
| - |
171 |
| - @Override |
172 |
| - public boolean apply(PredicateKey input) { |
173 |
| - BambooRequestContext bambooRequestContext = BambooRequestContext.currentRequestCentxt(); |
174 |
| - if (bambooRequestContext == null || bambooRequestContext.getBambooRequest() == null) { |
175 |
| - return false; |
176 |
| - } |
177 |
| - DiscoveryEnabledServer server = (DiscoveryEnabledServer) input.getServer(); |
178 |
| - BambooRequest bambooRequest = bambooRequestContext.getBambooRequest(); |
179 |
| - List<GrayDecision> grayDecisions = |
180 |
| - getIRule().getGrayManager().grayDecision(bambooRequest.getServiceId(), server.getInstanceInfo().getInstanceId()); |
181 |
| - for (GrayDecision grayDecision : grayDecisions) { |
182 |
| - if (grayDecision.test(bambooRequest)) { |
183 |
| - return true; |
184 |
| - } |
185 |
| - } |
186 |
| - return false; |
187 |
| - } |
188 |
| - |
189 |
| - |
190 |
| - protected GrayLoadBalanceRule getIRule() { |
191 |
| - return (GrayLoadBalanceRule) this.rule; |
192 |
| - } |
193 |
| -} |
194 |
| -``` |
195 |
| - |
196 |
| -* 灰度管理: |
197 |
| -灰度管理是灰度服务端的功能,主要是维护灰度列表。其实现类DefaultGrayServiceManager有一个Map, 用来维护GrayService,key是service id。并且每隔一段时间就调用EurekaGrayServerEvictor,检查列表中的实例是否下线,将下线的服务从灰度列表中删除。 |
198 |
| - |
199 |
| -```java |
200 |
| -public class DefaultGrayServiceManager implements GrayServiceManager { |
201 |
| - |
202 |
| - |
203 |
| - private Map<String, GrayService> grayServiceMap = new ConcurrentHashMap<>(); |
204 |
| - private Timer evictionTimer = new Timer("Gray-EvictionTimer", true); |
205 |
| - |
206 |
| - //... |
207 |
| - |
208 |
| - @Override |
209 |
| - public void openForWork() { |
210 |
| - evictionTimer.schedule(new EvictionTask(), |
211 |
| - serverConfig.getEvictionIntervalTimerInMs(), |
212 |
| - serverConfig.getEvictionIntervalTimerInMs()); |
213 |
| - } |
214 |
| - |
215 |
| - @Override |
216 |
| - public void shutdown() { |
217 |
| - evictionTimer.cancel(); |
218 |
| - } |
219 |
| - |
220 |
| - |
221 |
| - protected void evict() { |
222 |
| - GrayServerContext.getGrayServerEvictor().evict(this); |
223 |
| - } |
224 |
| - |
225 |
| - |
226 |
| - class EvictionTask extends TimerTask { |
227 |
| - |
228 |
| - @Override |
229 |
| - public void run() { |
230 |
| - evict(); |
231 |
| - } |
232 |
| - } |
233 |
| - |
234 |
| -} |
235 |
| - |
236 |
| -``` |
237 |
| - |
238 |
| -EurekaGrayServerEvictor是依赖EurekaClient来检查服务实例是否下线。 |
239 |
| - |
240 |
| -```java |
241 |
| -public class EurekaGrayServerEvictor implements GrayServerEvictor { |
242 |
| - |
243 |
| - private EurekaClient eurekaClient; |
244 |
| - |
245 |
| - |
246 |
| - public EurekaGrayServerEvictor(EurekaClient eurekaClient) { |
247 |
| - this.eurekaClient = eurekaClient; |
248 |
| - } |
249 |
| - |
250 |
| - @Override |
251 |
| - public void evict(GrayServiceManager serviceManager) { |
252 |
| - Collection<GrayService> grayServices = serviceManager.allGrayService(); |
253 |
| - grayServices.forEach(grayService -> { |
254 |
| - grayService.getGrayInstances().forEach(grayInstance -> { |
255 |
| - evict(serviceManager, grayInstance); |
256 |
| - }); |
257 |
| - }); |
258 |
| - |
259 |
| - } |
260 |
| - |
261 |
| - |
262 |
| - private void evict(GrayServiceManager serviceManager, GrayInstance grayInstance) { |
263 |
| - if (isDownline(grayInstance)) { |
264 |
| - serviceManager.deleteGrayInstance(grayInstance.getServiceId(), grayInstance.getInstanceId()); |
265 |
| - } |
266 |
| - } |
267 |
| - |
268 |
| - |
269 |
| - private boolean isDownline(GrayInstance grayInstance) { |
270 |
| - Application app = eurekaClient.getApplication(grayInstance.getServiceId()); |
271 |
| - return app == null || app.getByInstanceId(grayInstance.getInstanceId()) == null; |
272 |
| - } |
273 |
| - |
274 |
| -} |
275 |
| -``` |
276 |
| - |
277 |
| - |
278 |
| - |
279 |
| -##### 使用说明 |
280 |
| -灰度发布 --> [spring-cloud-gray-samples](../spring-cloud-gray-samples/README.md) |
0 commit comments