赞
踩
> 灰色发布是在用户无感的情况下完成产品的升级系统迭代新功能的无感操作过程.
> 灰色发布实现方式有多种的,其一基于Eureka元数据(Metadata)的一种方式.
> 在Eureka里面,一共有两种元数据:
> 1)标准元数据:这种元数据是服务的各种注册信息,比如:ip、端口、服务健康信息、续约信息等,存储于专门为服务
> 开辟的注册表中,用于其它组件取用以实现整个微服务生态。
> 2)自定义元数据:自定义元数据是使用`eureka.instance.metadata-map.<key>=<value>`来配置的,其内部其实
> 就是维护来一个Map来保存自定义元数据信息,可以配置在远端服务,随服务一并注册保存在Eureka注册表中,对微服
> 务生态的任何行为都没有影响。
>'以下基于Eureka的自定义元数据来完成灰度发布的操作。'
>'它的原理是通过获取Eureka实例信息,并鉴别元数据的含义,在分别进行路由规则下的负载均衡。'
>"首先要在Zuul Server引入一个开源项目包:"
> github地址:https://github.com/jmnarloch/ribbon-discovery-filter-spring-cloud-starter
<!-Zuul-Server服务工程-引入依赖-> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>io.jmnarloch</groupId> <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId> <version>2.1.0</version> </dependency> </dependencies>
<!-Client-a服务工程-设置Metadata(元数据)-> server: port: 7070 spring: profiles: node1 application: name: client-a eureka: client: serviceUrl: defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8888}/eureka/ instance: prefer-ip-address: true metadata-map: host-mark: running-host --- server: port: 7071 spring: profiles: node2 application: name: client-a eureka: client: serviceUrl: defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8888}/eureka/ instance: prefer-ip-address: true metadata-map: host-mark: running-host --- server: port: 7072 spring: profiles: node3 application: name: client-a eureka: client: serviceUrl: defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8888}/eureka/ instance: prefer-ip-address: true metadata-map: host-mark: gray-host
>> 7070与7071端口运行的是稳定的线上服务,将它们的'host-mark:'设置成状态是'running-host',
>> 需要上线的<灰度端口:7072>,'host-mark:'设置成状态是'gray-host'.
最后要达到的效果是:-由于服务名称都为'Client-a',
>> 但是在某一个值的作用下,部分请求被分发到7070与7071实例上,也就是'host-mark:'设置成状态是'running-host'节点;
>> 另一部分则发送到7072实例,'host-mark:'设置成状态是'gray-host'的节点。
"Client-a服务需要启动3个实例-如下:"-
<!-'Client-a服务工程-Main程序启动类'->
@SpringBootApplication
@EnableDiscoveryClient
public class ClientAApplication {
//--spring.profiles.active=node1,每次启动将node1改为node2或node3,就能启动多个服务实例
public static void main(String[] args) {
SpringApplication.run(ClientAApplication.class, "--spring.profiles.active=node3");
} }
<!-'Zuul-Server服务工程-自定义过滤器(最重要)'-> public class GrayFilter extends ZuulFilter { @Override public String filterType() {return PRE_TYPE;} @Override public int filterOrder() {return PRE_DECORATION_FILTER_ORDER - 1;} @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); return !ctx.containsKey(FORWARD_TO_KEY) && !ctx.containsKey(SERVICE_ID_KEY); } /** 此过滤器的目的: * 将‘Header’里面的“gray_mark”作为指标, * 如果“gray_mark”等于“enable”的条件下; * 将该请求路由到节点“gray-host”上, * 如果不等于或者没有这个指标,就路由到其它节点. * RibbonFilterContextHolder是该项目的核心类, * 它定义了基于MetaData(元数据)的一种负载均衡机制. */ @Override public Object run() throws ZuulException { HttpServletRequest request = RequestContext.getCurrentContext().getRequest(); String mark = request.getHeader("gray_mark"); if (!StringUtils.isEmpty(mark) && "enable".equals(mark)) { RibbonFilterContextHolder.getCurrentContext() .add("host-mark", "gray-host"); } else { RibbonFilterContextHolder.getCurrentContext() .add("host-mark", "running-host"); } return null; } }
<!-'Zuul-Server服务工程-Main程序启动类'->
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class, args);}
@Bean
public GrayFilter grayFilter(){ return new GrayFilter();}
}
>测试:
> 依次启动服务工程'Eureka-server'、'Zuul-server'、'Client-a',
> 需要修改'Client-a'服务工程中`.yml`配置文件中的`spring.profile=node为node2或node3`
> 启动三次服务工程,并使用Postman调用接口:
<!-'Client-a服务工程-Controller(控制器层)'-> @RestController public class TestController { @GetMapping("/add") public String add(Integer a, Integer b, HttpServletRequest request){ return "service client-a, port: " + request.getServerPort() + ", result=" + (a + b); } @GetMapping("/a/add") public Integer aadd(Integer a, Integer b){return a + b;} @GetMapping("/sub") public Integer sub(Integer a, Integer b){return a - b;} @GetMapping("/mul") public String mul(Integer a, Integer b){ System.out.println("进入client-a!");return "client-a-" + a * b;} @GetMapping("/div") public Integer div(Integer a, Integer b){return a / b;} }
> "3个Client-a服务工程:",请求的'Headers:'中不传'gray_mark'的结果.如上图1或图2.
> 请求的'Headers:'中传入'gray_mark'时候,请求只会路由到端口为:7072的服务工程上,因符合自定义过滤器条件指针.
> Zuul作为一个网关中间件,面临文件上传的考验,它的上传功能是从SpringBoot承袭过来的,所以,需要SpringBoot相关配置.
<!-Zuul-server服务工程-设置对上传文件的一些阀值限定,以及对熔断/负载均衡超时做延长-> spring: application: name: zuul-server servlet: #spring boot2.0之前是http multipart: enabled: true # 使用http multipart上传处理 max-file-size: 100MB # 设置单个文件的最大长度,默认1M,如不限制配置为-1 max-request-size: 100MB # 设置最大的请求文件的大小,默认10M,如不限制配置为-1 file-size-threshold: 1MB # 当上传文件达到1MB的时候进行磁盘写入 location: / # 上传的临时目录 server: port: 5555 eureka: client: serviceUrl: defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8888}/eureka/ instance: prefer-ip-address: true ##### Hystrix默认超时时间为1秒,如果要上传大文件,为避免超时,稍微设大一点 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 30000 ribbon: ConnectTimeout: 3000 ReadTimeout: 30000
<!-'Zuul-server服务工程-Controller(控制器层)'-> @Controller public class ZuulUploadController { /** * -提供上传接口来,接收前端传来的文件 * @return 上传文件的绝对路径 */ @PostMapping("/upload") @ResponseBody public String uploadFile(@RequestParam(value = "file", required = true) MultipartFile file) throws IOException{ byte[] bytes = file.getBytes(); File fileToSave = new File(file.getOriginalFilename()); FileCopyUtils.copy(bytes, fileToSave); return fileToSave.getAbsolutePath(); } }
>> 依次启动Eureka-server、Zuul-server服务工程 测试.文件名乱码情况:
>> Zuu内部默认使用是Spring MVC来上传文件,对于中文字符处理有点瑕疵,
>> 需要在请求路径前加上/zuul 就可以改为Zuul Servlet来上传文件,并且它还带一个缓冲区.
>> 路径更变为:http://localhost:5555/zuul/upload
> 'Zuul内部默认使用Ribbon来调用远程服务,所以由于Ribbon的原因,部署好所以组件之后,'
> '第一次经过Zuul的调用往往会去注册中心读取服务注册表,初始化Ribbon负载信息,这是一种懒加载策略。'
> '懒加载策略的过程是极其耗时的,为了避免此问题,应启动Zuul的饥饿加载应用程序上下文信息.'
> '开启饥饿加载只需添加配置即可-如下:'
<!-Zuul-server服务工程->
zuul:
retryable: true #开启重试,默认为false,需要手动开启
ribbon:
eager-load:
enabled: false #开启饥饿加载
> 场景需求是这样的:在客户端对Zuul发送Post请求之后,由于某些原因,请求到下游的服务之前,需要对请求体进行修改,
> 常见的是对form-data参数的增减,对appliction/json的修改,对请求体做UpperCase等.
> 在Zuul中通过新增一个PRE类型的Filter(过滤器)对请求体进行修改.代码如下:
<!-'Zuul-server服务-添加@Configuration配置类对请求体修改'-> @Configuration public class ModifyRequestEntityFilter extends ZuulFilter { @Override public String filterType() {eturn PRE_TYPE;} /*** * 设定PERT类型Filter的次序为最后一级 */ @Override public int filterOrder() {return PRE_DECORATION_FILTER_ORDER + 1;// =6 } /*** * 设定Filter执行的特定条件 */ @Override public boolean shouldFilter() {return true;} @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); request.getParameterMap(); Map<String, List<String>> requestQueryParams = ctx.getRequestQueryParams(); if (requestQueryParams == null){ requestQueryParams = new HashMap<>(); } //这里添加新增参数的value,注意,只取list的0位 ArrayList<String> arrayList = new ArrayList<>(); arrayList.add("1wwww"); requestQueryParams.put("test", arrayList); ctx.setRequestQueryParams(requestQueryParams); return null; } }
> Spring Cloud中各个组件之间使用的通信协议都是HTTP,而HTTP客户端使用的是Apache公司的HttpClient.
> 但是由于其难以扩展等诸多原因,已被许多技术栈弃用。Square公司开发的OkHttp正在逐渐被接受.
> 它具备以下优点:
> 1.'支持SPDY,可以用于并合多个对于同一个Host的请求.'
> 2.'使用连接复用机制有效减少资源消耗.'
> 3.'使用GZIP压缩减少数据量传输.'
> 4.'对响应缓存,避免重复的网络请求.'
> -在Zuul中使用OkHttp替换HttpClient,首先需要引入OkHttp依赖包,代码如下:
<!-Zuul-server服务工程->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
>> '然后,在配置文件中禁用HttpClient并开启OkHttp即可-'
<!-Zuul-server服务工程->
#禁用HttpClient并开启OkHttp
ribbon:
httpclient:
enabled: false
okhttp:
enabled: true
>> '在Spring Cloud中有多种发送HTTP请求的方式可以与Zuul结合,Ribbon、Feigbn或者RestTemplate。'
>> '但是无论那种,都可能出现请求失败的情况,启用Zuul中的重试机制是配合Spring Retry与Ribbon来使用的。'
>> "因此需要引入Spring Retry 依赖包,代码如下:"
<!-Zuul-server服务工程->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
>> '随后设置重试具体配置,代码如下:'
<!-Zuul-server服务工程-> zuul: retryable: true #开启重试,默认为false,需要手动开启 ribbon: eager-load: enabled: false #开启饥饿加载 routes: client-a: path: /client/** serviceId: client-a #禁用HttpClient并开启OkHttp ribbon: httpclient: enabled: false okhttp: enabled: true #重试机制配置 ConnectTimeout: 3000 ReadTimeout: 60000 MaxAutoRetries: 1 #对第一次请求的服务的重试次数 MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务) OkToRetryOnAllOperations: true #内部默认已开启,列出说明这个参数比较重要
>> '也可以对单个映射规则进行重试:zuul.routes.<route>.retryable=true'
>> 需要注意的是,在某些对幂等要求比较高对场景下,要慎用重试机制,因为如果没有相关处理好的话,会出现幂等问题。
> "场景这样的,Zuul构建网关的时候,遇到一个问题:在Zull中对请求做了一些处理,需要把处理结果发给下游服务,"
> "但不能影响请求体的原始特性,解决这个问题呢,在Zuul中一个RequestContext类中其中方法中可以解决-如下:"
<!-Zuul-server服务工程-> @Configuration public class HeaderDeliverFilter extends ZuulFilter { @Override public String filterType() {return PRE_TYPE;} @Override public int filterOrder() {return PRE_DECORATION_FILTER_ORDER + 1;// =6} @Override public boolean shouldFilter() {return true;} /** * 如下方式,可以动态增加一个Header来传递给下游服务使用,十分实用 * 这种方式与敏感头相反,需要注意传递信息的安全性. */ @Override public Object run() throws ZuulException { RequestContext context = RequestContext.getCurrentContext(); context.addZuulRequestHeader("result", "to next service"); return null; } }
Zuul的Filter链非常重要,它是一个网关处理业务的灵魂所在。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。