灰度实战(五):SpringCloud灰度(1)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/u012829124/article/details/94896149

强烈推荐一个大神的人工智能的教程:http://www.captainbed.net/zhanghan

【前言】

       在上四篇博文中讲解了Apollo如何动态配置,以及Apollo的对灰度发布支持;在本篇博文中为大家带来我们项目(Spring Cloud)的灰度实战。

【SpringCloud灰度实战】

         一、项目简介

               1、项目目录:

gray-cloud
├── gray-eureka -- 注册中心
├── gray-service-sms -- 短信服务代码
└── gray-zuul -- 网关

               2、项目架构图:

               3、网关路由分发图:

         二、代码展示

               1、重要代码展示---灰度拦截器(GrayRedirectZuulFilter)

/*
 * Copyright (c) 2019. [email protected] All Rights Reserved.
 * 项目名称:灰度实战
 * 类名称:GrayRedirectZuulFilter.java
 * 创建人:张晗
 * 联系方式:[email protected]
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://blog.csdn.net/zhanghan18333611647
 */

package com.zhanghan.zuul.filter;


import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.zhanghan.zuul.bean.GrayBean;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.StreamUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.nio.charset.Charset;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

public class GrayRedirectZuulFilter extends ZuulFilter {


    @Autowired
    private GrayBean grayBean;

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return grayBean.isEnabled();
    }

    @Override
    public Object run() {

        if (shouldBeRedirected()) {
            RequestContext.getCurrentContext().setSendZuulResponse(false);
            String redirectUrl = generateRedirectUrl(grayBean.getSuffix());

            sendRedirect(redirectUrl);
        }

        return null;
    }

    /**
     * 是否为灰度请求 依据条件同时满足1和2: 1.请求在灰度列表中 2.companyNo为配置的灰度companyNo
     *
     * @return
     */
    private boolean shouldBeRedirected() {

        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();


        if (grayBean.isEnabled()) {

            if (grayBean.getGraylist().isEmpty()) {
                return false;
            }

            String requestUrl = request.getRequestURI();

            if (grayBean.getGraylist().contains(requestUrl.split("/")[1])) {

                Object companyNo = getParment(request, "companyNo");

                if (null != companyNo && companyNo.toString().equals(grayBean.getCompanyNo())) {
                    return true;
                }

            }

        }
        return false;
    }

    /**
     * 获取post请求的body
     *
     * @param request
     * @param parmentKey
     * @return
     */
    private static Object getParment(HttpServletRequest request, String parmentKey) {

        try {

            String charset = request.getCharacterEncoding();

            InputStream inputStream = request.getInputStream();

            String body = StreamUtils.copyToString(inputStream, Charset.forName(charset));

            if (StringUtils.isEmpty(body)) {
                return null;
            }

            JSONObject json = JSONObject.fromObject(body);

            return json.get(parmentKey);

        } catch (Exception e) {
            return null;
        }

    }

    /**
     * 获取重定向URL
     *
     * @param graySuffix 灰度后缀
     * @return
     */
    private static String generateRedirectUrl(String graySuffix) {

        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        String queryParams = request.getQueryString();
        Object originalRequestPath = request.getRequestURI();

        String[] modifiedRequestPathArr = (originalRequestPath.toString().split("/", 3));
        modifiedRequestPathArr[1] = modifiedRequestPathArr[1] + graySuffix;

        StringBuilder stringBuilder = new StringBuilder();
        for (String str : modifiedRequestPathArr) {
            if (StringUtils.isNotEmpty(str)) {
                stringBuilder.append("/");
                stringBuilder.append(str);
            }
        }

        return stringBuilder.toString() + (queryParams == null ? "" : ("?" + queryParams));
    }

    /**
     * 进行重定向
     *
     * @param redirectUrl 重定向URL
     */
    private static void sendRedirect(String redirectUrl) {

        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletResponse response = ctx.getResponse();
        HttpServletRequest request = ctx.getRequest();

        try {
            if (request.getMethod().toUpperCase().equals("POST")) {
                response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
            } else {
                response.setStatus(HttpStatus.MOVED_PERMANENTLY.value());
            }
            response.setHeader(HttpHeaders.LOCATION, redirectUrl);
            response.flushBuffer();

        } catch (Exception ex) {
        }

    }


}

               2、启动类(启用GrayRedirectZuulFilter)

扫描二维码关注公众号,回复: 7594674 查看本文章
/*
 * Copyright (c) 2019. [email protected] All Rights Reserved.
 * 项目名称:灰度实战
 * 类名称:ZhZuulApplication.java
 * 创建人:张晗
 * 联系方式:[email protected]
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://blog.csdn.net/zhanghan18333611647
 */

package com.zhanghan.zuul;

import com.zhanghan.zuul.filter.GrayRedirectZuulFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZhZuulApplication {

    public static void main(String[] args) {

        SpringApplication.run(ZhZuulApplication.class, args);
    }


    /**
     * 重定向方式的Filter---性能低
     */
    @Bean
    public GrayRedirectZuulFilter grayRedirectZuulFilter() {
        return new GrayRedirectZuulFilter();
    }

    /**
     * 直接跳转方式的Filter---性能高建议采用
     *
     * @return
     */
//    @Bean
//    public GrayDirectZuulFilter grayDirectZuulFilter() {
//        return new GrayDirectZuulFilter();
//    }


}

               3、其他代码

                     灰度实战:https://github.com/dangnianchuntian/gray

                     项目模块:gray-cloud

         三、项目部署

               1、Apollo配置中心配置

                   (1)eureka注册中心配置

# Spring cloud config
spring.application.name=zh_eureka
server.port=8090

# Eureka config for discovery
eureka.instance.metadataMap.group=eureka-group
eureka.client.serviceUrl.defaultZone=http://172.16.11.230:8090/eureka/
eureka.instance.preferIpAddress=true

                   (2)短信业务配置

spring.application.name = service_sms
server.port = 1100
eureka.client.serviceUrl.defaultZone = http://172.16.11.230:8090/eureka/
eureka.instance.preferIpAddress = true

                   (3)zuul网关配置

spring.application.name = zh_zuul
server.port = 8099

zuul.debug.request = true

zuul.routes.servicesms.path = /service_sms/**
zuul.routes.servicesms.serviceId = service_sms
zuul.routes.servicesms_gray.path = /service_sms_gray/**
zuul.routes.servicesms_gray.serviceId = service_sms_gray

eureka.client.serviceUrl.defaultZone = http://172.16.11.230:8090/eureka/
management.endpoints.web.exposure.include = *
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 60000
feign.hystrix.enabled = false
ribbon.ReadTimeout = 60000
ribbon.ConnectTimeout = 5000

# 灰度相关配置  close gray:false  open gray:true
gray.bean.enabled = false
gray.bean.suffix = _gray
gray.bean.companyNo = dmsd
gray.bean.graylist[0] = service_sms

                2、部署相关服务

                   (1)部署eureka注册中心服务(服务器172.16.11.230)

                       a.启动命令

nohup java -jar -Xmx128m  -Dapollo_meta=http://172.16.11.226:8088 -Dapp_ip="gray_cloud" -Dapollo_namespaces=eureka gray-eureka.jar &

                       b.启动正常验证

                   (2)部署短信业务服务

                       a.部署 172.16.11.223 服务器

                            ①启动命令:

# 通过sid区分不同服务器,以验证请求是分发到哪台服务器
nohup java -jar -Xmx128m -Dsid=sms-223  -Dapollo_meta=http://172.16.11.226:8088 -Dapp_ip="gray_cloud" -Dapollo_namespaces=sms gray-service-sms.jar &

                            ②启动正常验证:

                       b.部署172.16.11.230  服务器

                            ①启动命令:

# 通过sid区分不同服务器,以验证请求是分发到哪台服务器
nohup java -jar -Xmx128m -Dsid=sms-230  -Dapollo_meta=http://172.16.11.226:8088 -Dapp_ip="gray_cloud" -Dapollo_namespaces=sms gray-service-sms.jar &

                            ②启动正常验证:

                   (3)部署zuul网关服务(服务器172.16.11.223)

                     a.启动命令

nohup java -jar -Xmx128m -Dapollo_meta=http://172.16.11.226:8088 -Dapp_ip="gray_cloud" -Dapollo_namespaces=zuul gray-zuul.jar &

                     b.启动正常验证

        三、演示灰度

                1、正常情况:

                   (1)访问示意图

                   (2)验证(默认zuul分发策略为轮循)

                     a.单数次访问(请求均落在223机器上) 

                     b.双数次访问(请求均落在230机器上) 

                2、项目升级(灰度发布;将230置为灰度服务)

                   (1)访问最终示意图

                   (2)灰度步骤

                     a.修改230服务名称(将230服务的流量摘除)

                            ①本步操作示意图

                            ②操作步骤:

                                  (①)在Apollo上进行灰度发布(详细步骤参考上篇博文《灰度实战(四):Apollo配置中心(4)》)

                                  (②)查看eureka发现230服务名已经更改

                                  (③)这时多次访问zuul发现请求一直落在223上:

                     b. 更新230代码(将短信代码由V1.0升级到V2.0)

                          ①本步操作示意图

                          ②操作步骤:一般由公司运维执行(从git上拉最新的代码,打包,发布)

                     c.将230服务挂载到网关上(此时正常流量全部分发到223服务器上,灰度流量【本例灰度流量标记companyNo=dmsd  可在zuul配置文件中灵活配置】分发到230上)

                        ①访问示意图

                        ②操作步骤

                                  (①)打开zuul灰度开关

                                  (②)正常流量验证:正常流量一直分发到223服务器

                                  (③)灰度流量验证:灰度流量一直分发到230服务器【本例灰度流量标记companyNo=dmsd  可在zuul配置文件中灵活配置】

                   d.灰度测试完毕后(一般为测试发起请求),说明新的代码在生产环境没有问题;

                   e.让230服务接收正常流量

                        ①本步操作示意图

                        ②操作步骤

                                  (①)在Apollo的sms中撤销230的灰度发布

                                  (②)在Apollo的zuul中将灰度开关关闭

                                  (③)此时再访问发现请求又换成轮循

                                          ·一次访问落在223机器上

                                          ·一次访问落在230机器上 

                   g.摘掉223的流量

                        ①本步操作示意图

                        ②操作步骤(针对223进行灰度)

               h.升级223代码(示意图)

               i.让223重新接收请求

                        ①本步操作示意图

                        ②操作步骤

【总结】

         1、本节为大家演示SpringCloud的灰度。

         2、在接下来会为大家演示本版本的升级版---zuul接收到灰度请求时,将重定向优化为直接调用至灰度服务。

猜你喜欢

转载自blog.csdn.net/u012829124/article/details/94896149