定义
灰度公布就是已一种平滑过渡的形式来公布,通过切换线上新旧版本之间的路由权重,逐渐从旧版本切换到新版本;比方要上线新性能,首先只是更新大量的服务节点,通过路由权重,让少部分用户体验新版本,如果没有什么问题,再更新所有服务节点;这样能够在呈现问题把影响面降到最低,保障了零碎的稳定性。
灰度公布
一个零碎往往有接入层比方nginx(Openresty),网关层比方zuul,以及服务层比方各种rpc框架;在这几层都有路由性能,也就是说这几层都能够做灰度;接入层能够应用nginx+lua来实现灰度,网关层zuul能够联合ribbon来实现灰度,rpc框架如dubbo自身提供了路由性能能够间接做灰度解决;上面看看具体如何去实现;
接入层灰度
接入层咱们这里应用性能更弱小的Openresty,而后应用lua进行路由转发,相干的路由策略能够配置在分布式缓存redis外面,当然也能够长久化到数据库外面;
- 筹备
筹备一台Openresty,两台web服务器tomcat(端口别离是8081,8082),以及redis;为了不便模仿在redis外面配置白名单,如果在白名单外面就走8082,不在则走8081;
- Openresty配置
须要在Openresty中配置反对lua,以及相干路由的lua脚本,nginx.conf配置如下:
http { ... lua_package_path "/lualib/?.lua;;"; #lua 模块 lua_package_cpath "/lualib/?.so;;"; #c模块 upstream tomcat1 { server 127.0.0.1:8081; } upstream tomcat2 { server 127.0.0.1:8082; } server { listen 80; server_name localhost; location / { content_by_lua_file lua/gray.lua; } location @tomcat1 { proxy_pass http://tomcat1; } location @tomcat2 { proxy_pass http://tomcat2; } } ...}
配置了所有申请都会通过lua目录下的gray.lua脚本,如下所示:
local redis = require "resty.redis";local redis_obj = redis:new();redis_obj:set_timeout(2000);local ok,err = redis_obj:connect("127.0.0.1", 6379);if not ok then ngx.say("failed to connect redis ",err); return;end--获取申请iplocal_ip = ngx.var.remote_addr;--redis中获取白名单local whitelist = redis_obj:get("whitelist");--判断是否在白名单而后转到对应服务if string.find(whitelist,local_ip) == nil then ngx.exec("@tomcat1");else ngx.exec("@tomcat2");endlocal ok,err = redis_obj:close();
Openresty内置的功能模块能够间接连贯redis,而后从redis外面取出白名单,看以后的申请ip是否在白名单内,而后做简略的路由性能;能够动静批改redis外面的白名单,实时更新。
localhost:0>set whitelist 127.0.0.1"OK"localhost:0>get whitelist"127.0.0.1"
- 启动测试
别离启动tomcat1,tomcat2以及Openresty,拜访http://localhost即可,能够动静批改redis外面的白名单,而后拜访查看后果验证。
网关层灰度
网关层已zuul为例,zuul的灰度须要批改ribbon的负载策略,就是依据eureka的metadata进行自定义元数据,而后批改ribbon的策略规定;
- 筹备
测试服务别离筹备两台端口别离为:8765,8766,application.yml配置如下:
server: port: 8765eureka: instance: metadata-map: route: 1
同时筹备申请地址/hiGray,返回值为route1;
server: port: 8766eureka: instance: metadata-map: route: 2
同时筹备申请地址/hiGray,返回值为route2;用于辨别是否走了灰度服务器;而后在zuul端须要引入一个插件:
<dependency> <groupId>io.jmnarloch</groupId> <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId> <version>2.1.0</version></dependency>
而后须要筹备一个pre类型的filter,具体如下:
@Configurationpublic class GrayFilter extends ZuulFilter { @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String ip = request.getRemoteAddr(); //ipv6本地地址,也就是127.0.0.1 if ("0:0:0:0:0:0:0:1".equals(ip)) { RibbonFilterContextHolder.getCurrentContext() .add("route", "1"); } else { RibbonFilterContextHolder.getCurrentContext() .add("route", "2"); } return null; } ...}
以上也是应用白名单为例子,这里为了不便就没有把白名单配置在redis外面,配置的白名单地址为ipv6:0:0:0:0:0:0:0:1,如果是白名单地址则路由到8765端口服务,否则为8766端口服务;
- 测试
别离启动eureka-server,两个eureka-client,以及zuul网关,拜访网关地址即可;别离通过127.0.0.1和本地ip拜访即可测试;
服务层灰度
服务器已rpc框架dubbo为例,dubbo自身提供了各种路由规定包含:条件路由,脚本路由等,这里同样应用脚本路由为例,脚本路由规定反对JDK 脚本引擎的所有脚本,比方:javascript, jruby, groovy 等,这里应用缺省的JavaScript为例;
- 筹备
注册核心zookeeper,两台Provider能够在本地别离指定端口为20881和20882,消费者,以及上面重点介绍的路由脚本:
function gray_rule(invokers, context) { var tag = context.getAttachment("tag"); var result = new java.util.ArrayList(invokers.size()); if(tag == "gray"){ for (i = 0; i < invokers.size(); i ++) { if (invokers.get(i).getUrl().getPort()==20881) { result.add(invokers.get(i)); } } } else { for (i = 0; i < invokers.size(); i ++) { if (invokers.get(i).getUrl().getPort()==20882) { result.add(invokers.get(i)); } } } return result;} (invokers,context)
dubbo在运行脚本的时候会传入三个参数别离是:invokers,Invocation以及RpcContext.getContext();通过在生产端在RpcContext中设置tag:
RpcContext.getContext().setAttachment("tag", "gray");
这样就能够在脚本中进行判断,tag为gray的生产端才走20881端口的服务端,其余走20882服务端;
以上的脚本须要注册到zookeeper中,手动注册代码如下,当然也能够应用dubbo提供的dubbo-admin来设置路由脚本:
URL registryUrl = URL.valueOf("zookeeper://127.0.0.1:2181");ZookeeperRegistryFactory zookeeperRegistryFactory = new ZookeeperRegistryFactory();zookeeperRegistryFactory.setZookeeperTransporter(new CuratorZookeeperTransporter());Registry zookeeperRegistry = (ZookeeperRegistry) zookeeperRegistryFactory.createRegistry(registryUrl);URL routerURL = URL.valueOf("script://0.0.0.0/com.dubboApi.DemoService?category=routers&dynamic=false");routerURL = routerURL.addParameter("rule",URL.encode("(..JavaScript脚本..)"));zookeeperRegistry.register(routerURL); // 注册
具体能够参考官网文档:旧路由规定
- 测试
启动zookeeper,而后别离启动两台生产者,启动消费者时通过批改tag而后察看路由;
总结
本文别离从接入层,网关层,服务层这三层简要的介绍了通过路由规定来实现灰度公布;已每层比拟典型的中间件来介绍具体如何去实现简略的灰度公布;总体来说就是应用中间件的路由性能,动静加载内部自定义的一些路由策略脚本,以此来达到灰度公布的目标。
代码地址
Dubbo
Spring-Cloud
感激关注
能够关注微信公众号「 回滚吧代码」,第一工夫浏览,文章继续更新;专一Java源码、架构、算法和面试。