- 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
- 📕系列专栏:Spring源码、JUC源码、Kafka原理、分布式技术原理
- 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
- 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
- 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀
文章目录
- Dubbo
- 多配置中心
- 多协议支持
- Dubbo的负载均衡
- Random(默认)
- roundrobin (轮询)
- 一致性hash 负载均衡
- 最小活跃度
- shortestreponse loadbalance
- Dubbo常见的配置讲解
- 启动时检查
- 主机绑定的问题
- 配置优先级问题
- 集群容错
- Failover(默认)
- failfast cluster
- failsafe cluster
- failback cluster
- forking cluster
- broadcast cluster
- Dubbo 泛化
- 服务降级
Dubbo
多配置中心
spring-boot-dubbo-sample-consumer
dubbo.registries.hunan.address=nacos://192.168.216.128:8848
dubbo.registries.shanghai.address=zookeeper://192.168.216.128:2181
dubbo.registries.shanghai.timeout=10000
spring-boot-dubbo-sample-provider
dubbo.registries.hunan.address=nacos://192.168.216.128:8848
dubbo.registries.shanghai.address=zookeeper://192.168.216.128:2181
dubbo.registries.shanghai.timeout=10000
@DubboService(registry = {"shanghai","hunan"})
public class SayHelloServiceImpl implements ISayHelloService {
@Override
public String sayHello(String msg) {
return "Hello,"+msg+" GuPaoEdu.cn";
}
}
此时我们的服务会注册在两个地方,这也就意味着,不仅可以在zk上看到我们的服务,还可以在nacos上看到我们的服务。
如果我们的消费者也配置了两个注册中心,那么应该调用哪个注册中心呢?
@RestController
public class SayController {
@DubboReference(registry = {"shanghai","hunan"},)
ISayHelloService sayHelloService;
@GetMapping("/say")
public String say(){
return sayHelloService.sayHello("Mic");
}
}
实际上也是针对多个注册中心做负载均衡。
可以通过设置权重的方式,或者设置默认优先选择
dubbo.registries.shanghai.address=zookeeper://192.168.216.128:2181
dubbo.registries.shanghai.timeout=10000
dubbo.registries.shanghai.zone=shanghai
dubbo.registries.shanghai.weight=100
dubbo.registries.hunan.address=nacos://192.168.216.128:8848
dubbo.registries.hunan.weight=10
dubbo.registries.hunan.preferred=true
还有就是版本路由,由生产者@DubboService()中设置了版本,然后消费者@DubboRederence中选择对应版本即可。
多协议支持
Dubbo的多协议支持是指它可以同时支持多种不同的通信协议,例如dubbo协议、HTTP协议、WebService协议等。这意味着Dubbo可以在不同的场景下灵活地选择最合适的通信协议,以满足不同服务之间的通信需求。
Dubbo的多协议支持使得它可以适用于各种不同的应用场景,无论是内部服务调用还是与外部系统交互,都能够选择最合适的通信协议来进行通信。这种灵活性和可扩展性使得Dubbo成为一个非常强大的分布式服务框架。
添加jar包依赖
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.13.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>3.13.0.Final</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.19.v20190610</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>9.4.19.v20190610</version>
</dependency>
修改配置文件
dubbo.protocol.name=dubbo
dubbo.protocol.port=-1
# Netty ->
dubbo.protocols.dubbo.name=dubbo
dubbo.protocols.dubbo.port=-1
# 设置为-1 会基于20880 这个端口往上加
# jetty (配置了Rest协议)
dubbo.protocols.rest.name=rest
dubbo.protocols.rest.port=-1
dubbo.protocols.rest.server=jetty
@DubboService(protocol = {"dubbo","rest"})
如果想按照rest风格发布
@Path("/")
public interface ISayHelloService {
@GET
@Path("/say")
String sayHello(String msg);
}
当Dubbo配置了多协议支持时,会根据请求的协议来选择相应的协议进行处理。如果请求使用的是dubbo协议,那么Dubbo会选择dubbo协议进行处理;如果请求使用的是rest协议,那么Dubbo会选择rest协议进行处理。这样可以根据不同的需求选择合适的协议进行通信。
假设我们有一个Dubbo服务提供者,配置了同时支持dubbo和rest协议,如下所示:
@DubboService(protocol = {"dubbo","rest"})
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "Hello, " + name;
}
}
在这个例子中,HelloServiceImpl类使用@DubboService注解标记为Dubbo服务,并配置了同时支持dubbo和rest协议。
现在假设有一个消费者通过dubbo协议调用HelloServiceImpl的sayHello方法:
HelloService helloService = // 获取HelloService代理对象
String result = helloService.sayHello("Alice");
在这种情况下,Dubbo会选择dubbo协议进行处理,因为消费者使用的是dubbo协议进行调用。
而如果有另一个消费者通过rest协议调用HelloServiceImpl的sayHello方法:
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("http://localhost:8080/sayHello?name=Alice", String.class);
在这种情况下,Dubbo会选择rest协议进行处理,因为消费者使用的是rest协议进行调用。
因此,根据不同的请求协议,Dubbo会选择相应的协议进行处理。
Dubbo的负载均衡
在Dubbo的整个架构图里面,服务注册是里面很核心的一个环节,服务提供者可以有多个注册进来,那么服务消费者处就会拿到多个地址,那么这个情况喜爱,consumer通过invoke去调用的时候,应该调用哪个地址呢?这是这里面必然要考虑的问题。
测试
spring-boot-dubbo-sample-provider
@DubboService(registry = {"shanghai","hunan"},
protocol = {"dubbo","rest"},
loadbalance = "random")
public class SayHelloServiceImpl implements ISayHelloService {
@Value("${dubbo.protocols.dubbo.port}")
private Integer port;
@Override
public String sayHello(String msg) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Msg:"+System.currentTimeMillis());
return "[version1.0]-"+port+"Hello,"+msg+" GuPaoEdu.cn";
}
}
spring.application.name=spring-boot-dubbo-sample-provider
dubbo.protocol.name=dubbo
dubbo.protocol.port=-1
# Netty ->
dubbo.protocols.dubbo.name=dubbo
dubbo.protocols.dubbo.port=-1
# 设置为-1 会基于20880 这个端口往上加
# jetty
dubbo.protocols.rest.name=rest
dubbo.protocols.rest.port=-1
dubbo.protocols.rest.server=jetty
同时启动两个生产者服务,因为 dubbo.protocols.dubbo.port=-1 设置为了 -1,所以会基于20880 这个端口往上加。
然后进行测试,就能检测到时间戳数据打印在不同的生产者实例上。
Random(默认)
它的算法思想很简单。假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3,2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上。比如数字3会落到服务器 A 对应的区间上,此时返回服务器 A即可。权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会有更大的概率落到此区间内。只要随机数生成器产生的随机数分布性很好,在经过多次选择后,每个服务器被选中的次数比例接近其权重比例。
这种思想可以应用到抽奖算法中。
roundrobin (轮询)
所谓轮询是指将请求轮流分配给每台服务器。举个例子,我们有三台服务器 A、B、C。我们将第一个请求分配给服务器 A,第二个请求分配给服务器 B,第三个请求分配给服务器 C,第四个请求再次分配给服务器 A。这个过程就叫做轮询。轮询是一种无状态负载均衡算法,实现简单,适用于每台服务器性能相近的场景下。但现实情况下,我们并不能保证每台服务器性能均相近。如果我们将等量的请求分配给性能较差的服务器,这显然是不合理的。因此,这个时候我们需要对轮询过程进行加权,以调控每台服务器的负载。经过加权后,每台服务器能够得到的请求数比例,接近或等于他们的权重比。比如服务器A、B、C 权重比为 5:2:1。那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的2次请求,服务器 C 则收到其中的1次请求
一致性hash 负载均衡
一致性哈希负载均衡是一种在分布式系统中实现负载均衡的算法,它通过将请求映射到一致性哈希环上的节点来实现负载均衡。在Dubbo中,一致性哈希负载均衡的实现可以通过以下步骤来实现:
- 建立一致性哈希环:首先,需要将所有的服务提供者节点映射到一致性哈希环上,可以使用一致性哈希算法将节点的IP地址或者其他标识映射到一个固定的哈希环上。
- 根据请求的哈希值选择节点:当有请求到来时,需要计算请求的哈希值,并根据哈希值在一致性哈希环上选择对应的节点作为服务提供者。
- 负载均衡:一致性哈希负载均衡算法会选择离请求哈希值最近的节点作为服务提供者,这样可以保证相同的请求会被映射到同一个节点上,从而实现负载均衡。
在Dubbo中,一致性哈希负载均衡算法可以通过自定义路由规则或者使用Dubbo内置的一致性哈希负载均衡策略来实现。通过配置相应的路由规则或者使用内置的一致性哈希负载均衡策略,可以实现一致性哈希负载均衡算法来均衡分布式系统中的请求负载。
这样设计的好处是,{hash(parameter) % 3} =0,1,2 变化到 {hash(parameter) % 6} =0,1,2,3,4,5 可能会产生数据震荡,所以采用环来设计,都去取余 2^32 - 1
这样做以后无论增加 还是 删减节点,其本身都能去找最近的点。
但是也会存在一种情况,就是数据分布的问题,因为数据不一定分布的很均匀,比如只有两个节点,只占用了整个哈希环的四分之一。
此时就可以增加虚拟节点,将B节点再分发一些节点出来即可,去保证数据分布的均匀性。
而Dubbo中 一致性hash负载均衡是基于 参数进行hash取模,默认根据第一个参数。
最小活跃度
根据目标集群服务器列表,处理性能最高的,权重也越高。处理性能较低的,权重也比较低,主要是根据服务器的性能,动态去提升负载均衡的均衡性。
在Dubbo的负载均衡中,最小活跃度是通过统计每个服务提供者的活跃调用数来实现的。活跃调用数指的是当前服务提供者正在处理的调用数,通过统计活跃调用数可以了解每个服务提供者的负载情况。最小活跃度负载均衡策略会选择活跃调用数最小的服务提供者来处理请求,以实现负载均衡。这样可以确保请求被分配到负载较低的服务提供者上,从而提高系统的性能和稳定性。
具体的实现是:根据请求处理的吞吐量 -> 发起一次请求(开始),计数器+1、 一次请求处理完成,计数器-1
shortestreponse loadbalance
在Dubbo中,ShortestResponse负载均衡策略是通过选择平均响应时间最短的服务提供者来处理请求的。该负载均衡策略会根据服务提供者的平均响应时间来进行选择,以确保请求被分配给响应速度最快的服务提供者,从而提高系统的性能和用户体验。
实现ShortestResponse负载均衡策略的关键在于收集并统计每个服务提供者的响应时间,然后根据这些数据来选择最佳的服务提供者。Dubbo框架会在运行时收集服务提供者的响应时间,并根据这些数据进行动态调整和选择,以实现ShortestResponse负载均衡策略。这样可以确保请求被分配到响应速度最快的服务提供者上,从而提高系统的性能和稳定性。
Dubbo常见的配置讲解
启动时检查
服务提供者
dubbo.registries.shanghai.address=zookeeper://192.168.216.128:2181
dubbo.registries.shanghai.timeout=10000
dubbo.registries.shanghai.default=true
# 服务启动的时候,如果注册中心有问题,那么服务就启动失败
dubbo.registries.shanghai.check=true
# 如果为false,即使注册中心不可用,还是能启动成功,但是服务不可用而已
服务消费者
@DubboReference(registry = {"shanghai","hunan"},
protocol = "dubbo",
loadbalance = "consistenthash",
check = false)
比如说项目中存在服务依赖的情况,如果开启了检查,那么注定会启动失败,可以先关闭检查,然后就可以启动成功,等到都注册到注册中心了,就可以去使用了。核心就是可以解决循环依赖的问题。
主机绑定的问题
查找 环境变量中是否存在启动参数 [DUBBO_IP_TO_BIND] = 服务注册的ip
读取配置文件 dubbo.protocols.dubbo.host= 服务注册的ip
如果这两个都没有
或通过 InetAddress.getLocalHost().getHostAddress() 获得本机ip地址
但是如果通过验证发现该ip地址不对,通过Socket去连接注册中心,从而获取本机IP
如果还不对,会轮询本机的网卡,直到找到合适的IP地址
上面获取到的ip地址是bindip,如果需要作为服务注册中心的ip, DUBBO_IP_TO_REGISTRY -dDUBBO_IP_TO_REGISTRY=ip
配置优先级问题
服务端和客户端的配置都可以生效,但是存在一个优先级的问题。
dubbo是基于url驱动的。
服务端配置的信息,都会装载到ulr上
dubbo%3A%2F%2F192.168.1.104%3A20880%2Fcom.gupaoedu.springboot.dubbo.ISayHelloSer
vice%3Fanyhost%3Dtrue%26application%3Dspring-boot-dubbo-sampleprovider%26cluster%3Dfailover%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dt
rue%26generic%3Dfalse%26interface%3Dcom.gupaoedu.springboot.dubbo.ISayHelloServi
ce%26metadatatype%3Dremote%26methods%3DsayHello%26pid%3D16488%26release%3D2.7.7%26side%3Dprov
ider%26timestamp%3D1596895175686
那么这些信息是不是客户端会拿到,消费者调用的时候,是从zk上拿到这个地址,然后再去解析这个配置。
此时要分为两种情况:
客户端没有配置:以服务端配置为主
客户端有配置:
- 方法层面的配置要优先于接口层面的配置, 接口层面的配置要优先于全局配置
- 如果级别一样,以客户端的配置优先,服务端次之
@RestController
public class SayController {
@DubboReference(registry = {"shanghai","hunan"},
protocol = "dubbo",
loadbalance = "consistenthash",
mock = "com.gupaoedu.springboot.dubbo.springbootdubbosampleconsumer.MockSayHelloService",
timeout = 500,
cluster = "failfast",check = false,methods = {
@Method(loadbalance = "",name ="" )
},retries = 5)
ISayHelloService sayHelloService;
@GetMapping("/say")
public String say(){
return sayHelloService.sayHello("Mic");
}
}
集群容错
Dubbo的集群容错是指在分布式系统中,当某个服务节点出现故障或不可用时,Dubbo能够通过一定的机制保证系统的稳定性和可用性。
Failover(默认)
失败自动重试(重试其他服务器), 失败自动切换
@DubboService(cluster="failover",retires=2)
failfast cluster
快速失败,立马报错。
failsafe cluster
安全失败,出现了异常,直接吞掉(比如日志,出现了些许日常,也不怎么影响)
failback cluster
失败自动恢复,记录失败请求,定时重发(可以在保证最终一致性的时候借鉴,先记录到失败记录,到时候定时重新执行就可)
forking cluster
并行调用多个服务节点,只要其中一个成功返回,那么就直接返回结果
broadcast cluster
广播调用。一个请求调用所有的服务提供者。只要其中一个节点报错,那么就认为这个请求失败
Dubbo 泛化
在前面的演示案例中,我们每次去发布一个服务,必然会先定义一个接口,并且把这个接口放在一个api的jar包中,给到服务调用方来使用。本质上,对于开发者来说仍然是面向接口编程,而且对于使用者来说,可以不需要关心甚至不需要知道这个接口到底是怎么触发调用的。
而泛化调用就是说服务消费者和服务提供者之间并没有这样的公共服务接口。
public interface IDemoService {
String getTxt();
}
@DubboService(protocol = {"dubbo"})
public class DemoService implements IDemoService{
@Override
public String getTxt() {
return "Hello Gupaoedu.cn/8.8";
}
}
@RestController
public class DemoController {
@DubboReference(interfaceName = "com.gupaoedu.springboot.dubbo.springbootdubbosampleprovider.services.IDemoService",generic = true,check = false)
GenericService genericService; // 在这里Dubbo提供了一个泛化接口
@GetMapping("/demo")
public String demo(){
Map<String,Object> user=new HashMap<>();
user.put("",""); //key表达user对象中的属性,value表达属性的值
return genericService.$invoke("getTxt",new String[0],null).toString();
}
}
服务降级
public class MockSayHelloService implements ISayHelloService{
@Override
public String sayHello(String msg) {
return "触发服务降级";
}
}
@RestController
public class SayController {
@DubboReference(registry = {"shanghai","hunan"},
protocol = "dubbo",
loadbalance = "consistenthash",
mock = "com.gupaoedu.springboot.dubbo.springbootdubbosampleconsumer.MockSayHelloService",
timeout = 500,
cluster = "failfast",check = false,methods = {
@Method(loadbalance = "",name ="" )
},retries = 5)
ISayHelloService sayHelloService;
@GetMapping("/say")
public String say(){
return sayHelloService.sayHello("Mic");
}
}
当500ms没有返回,则快速失败,失败后调用mock