前言
灰度发布这个词对很多同学来说并不陌生,灰度的概念其实是非常广泛的,不能仅仅停留在服务器层面
举例来说,你开发的软件上个版本是V1.0,本次即将发布的是V2.0,但稍有经验的同学应该都知道,如果新版本的功能改动非常大,但是这种改动还必须要经过用户的使用检验,任何的产品都可能存在bug,为了发布出去之后一旦有问题能够快速回退,切换到之前稳定的版本,这个过程就需要用到灰度发布;
类似的场景还有很多,大家熟悉的游戏公测也是如此,先让一部分种子用户试用新的版本,一旦当公测达到了预期的标准,然后将新版本的服务全部放开
关于灰度发布的理论方面的扩展本篇不过多展开,网上可以查询的资料非常多,本篇以实战开发微服务中遇到的灰度发布场景,以一个实际的技术解决方案为例来进行说明,如果利用dubbo的版本号实现后端服务的灰度发布
前置准备
zk服务,如果已经安装过,只需要启动即可
本篇技术栈
springboot+dubbo
dubbo版本号说明
使用过dubbo做微服务治理的同学对dubbo的版本号控制应该不陌生,dubbo提供了版本号帮助开发者实现对不同版本号下服务的精细控制
举例来说,已经发不出去的dubbo服务版本号是1.0.0,属于稳定版的服务,而2.0.0尚在开发中还没有完成,于是对外提供出去的版本号仍旧是1.0.0,而内部各个服务间依赖的版本号可以使用2.0.0,这个就是版本号的一个实际使用场景
工程环境搭建
演示工程结构如下所示,对使用过dubbo的同学对这个结构应该不陌生,就不做过多说明了
1、pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.congge</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
2、consumer端配置文件
#服务名称
spring.application.name=consumer
#服务启动端口号
server.port=8100
## Dubbo 服务名称 ==> java.lang.reflect.InvocationTargetException: null 没有这项配置会报错,无法注册到zookeeper
dubbo.application.name=consumer
## Dubbo 服务端口号
dubbo.protocol.port=20888
## Dubbo 服务注册地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
## Dubbo 服务扫描包路径
dubbo.scan.base-packages=com.congge
## Dubbo 服务是否注册到注册中心
dubbo.registry.register=true
dubbo.consumer.version=1.0.0
dubbo.consumer.check=false
3、provider端配置文件
#项目启动端口号
server.port=8101
# Dubbo 服务消费者配置
dubbo.application.name=provider1
# Dubbo 扫描包的路径
dubbo.scan.basePackages=com.congge.service.impl
# Dubbo 注册id
dubbo.registry.id=provider1
# Dubbo 服务端口号
dubbo.protocol.port=20881
# Dubbo 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.provider.version=1.0.0
这里只列举了其中一个provider的配置文件,另一个基本一样,注意本篇是演示多个provider基于同一个服务接口提供不同版本,最后的dubbo.provider.version这个配置的版本号需要不一样
4、dubbo-api中提供一个服务接口
public interface SoftService {
String softInfo(String name);
}
5、dubbo-provider中的服务实现
import com.alibaba.dubbo.config.annotation.Service;
import com.congge.service.SoftService;
@Service
public class SoftServiceImpl implements SoftService {
@Override
public String softInfo(String name) {
return "v1 :" + name;
}
}
6、dubbo-consumer中提供一个web接口
这里为了演示版本号的不同,使用了两个不同的实现类,在不同的实现类调用不同的provider的服务
ProviderService1
import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service
public class ProviderService1 {
@Reference(version = "1.0.0")
private SoftService softService;
public String getSoftInfo(String info){
return softService.softInfo(info);
}
}
ProviderService2
@Service
public class ProviderService2 {
@Reference(version = "2.0.0")
private SoftService softService;
public String getSoftInfo(String info){
return softService.softInfo(info);
}
}
SoftController
import com.congge.service.ProviderService1;
import com.congge.service.ProviderService2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/softs")
public class SoftController {
@Autowired
private ProviderService1 providerService1;
@Autowired
private ProviderService2 providerService2;
//http://localhost:8100/softs/get/info?info=hello&version=1
@GetMapping("/get/info")
public String softInfo(@RequestParam("info")String info,int version){
if(version==1){
return providerService1.getSoftInfo(info);
}
if(version==2){
return providerService2.getSoftInfo(info);
}
return "default";
}
}
启动3个工程,并提前启动zookeeper服务,浏览器分别访问:
- http://localhost:8100/softs/get/info?info=hello&version=1
- http://localhost:8100/softs/get/info?info=hello&version=2
效果展示
以上就演示了基于版本号控制不同的流量访问不同的服务的目的,事实上,在真实的环境中,接口是由前端进行调用的,那么可以把相关的规则设定好,由前端读取后端的配置文件,即为了达到不同版本灰度发布的目的,将控制灰度的接口通过配置文件的方式写入到中央配置文件中,一旦需要切换,我们只需要修改配置文件既可以达到快速切换服务的目的
基于权重的灰度发布控制
上面讲述了使用版本号对不同服务接口的控制,但某些情况下,有这么一种场景,我们不想直接从一个版本切换到另一个版本,这个毕竟代价有点大,那么可以考虑,使用同一个服务接口,一部分流量打到之前的服务上,一部分服务打到新的服务上,这个该怎么实现呢?
这个场景可以考虑使用dubbo提供的权重配置打到目的
仍然以上面搭建的工程为例,我们只需要在dubbo-provider1 和 dubbo-provider2 中添加关于权重的配置,并将版本号置为相同即可
dubbo-provider1 配置文件
#项目启动端口号
server.port=8101
# Dubbo 服务消费者配置
dubbo.application.name=provider1
# Dubbo 扫描包的路径
dubbo.scan.basePackages=com.congge.service.impl
# Dubbo 注册id
dubbo.registry.id=provider1
# Dubbo 服务端口号
dubbo.protocol.port=20881
# Dubbo 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.provider.version=1.0.0
dubbo.provider.weight=2
dubbo-provider2 配置文件
#项目启动端口号
server.port=8102
# Dubbo 服务消费者配置
dubbo.application.name=provider2
# Dubbo 扫描包的路径
dubbo.scan.basePackages=com.congge.service.impl
# Dubbo 注册id
dubbo.registry.id=provider2
# Dubbo 服务端口号
dubbo.protocol.port=20882
# Dubbo 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.provider.version=1.0.0
dubbo.provider.weight=9
dubbo-cnsumer端接口改造
新增一个方法
@Reference
private SoftService softService;
//http://localhost:8100/softs/common/info?info=hello
@GetMapping("/common/info")
public String softCommonInfo(@RequestParam("info") String info) {
return softService.softInfo(info);
}
再次启动3个模块的服务,浏览器反复调用下面的接口:
- http://localhost:8100/softs/common/info?info=hello
通过后台打印日志的对比,可以发现,由于dubbo-provider2给的权重比dubbo-provider1更高,相同的环境下,被调用的概率将会更大
利用这种特性,我们可以设想,为了保险起见,新上线的服务可以考虑分配少量的权重,而让更多的流量打到之前的版本上面,这样经过一段时间的生产测试,逐步将新版的服务权重提高,以打到服务的灰度发布的目的
需要源码的同学可前往下载:https://download.csdn.net/download/zhangcongyi420/24656803
本篇到此结束,最后感谢观看!