到目前为止,dubbo 集成 zookeeper 解决了服务注册以及服务动态感知的问题。那么 当服务端存在多个节点的集群时,zookeeper 上会维护不同集群节点,对于客户端而 言,他需要一种负载均衡机制来实现目标服务的请求负载。通过负载均衡,可以让每 个服务器节点获得适合自己处理能力的负载。
负载均衡可以分为软件负载和硬件负载,在实际开发中,我们基础软件负载比较多, 比如 nginx,硬件负载现在用得比较少而且有专门的人来维护。
Dubbo 里面默认就集成了负载均衡的算法和实现,默认提供了 4 中负载均衡实现。
配置的属性名称: roundrobin/random/leastactive/ consistenthash
<dubbo:service interface="..." loadbalance="roundrobin" />
<dubbo:reference interface="..." loadbalance="roundrobin" />
可以在服务端配置,也可以在客户端配置。 如果是基于注解,配置如下 @Service(loadbalance = "roundrobin") public class HelloServiceImpl implements IHelloService{
@Reference(loadbalance = "random") IHelloService helloService;
权重随机算法,根据权重值进行随机负载
它的算法思想很简单。假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为 10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区 间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过 随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个 区间上。比如数字 3 会落到服务器 A 对应的区间上,此时返回服务器 A 即可。权重 越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会 有更大的概率落到此区间内。只要随机数生成器产生的随机数分布性很好,在经过多 次选择后,每个服务器被选中的次数比例接近其权重比例
最少活跃调用数算法,活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求这个是比较科学的负载均衡算法。
每个服务提供者对应一个活跃数 active。初始情况下,所有服务提供者活跃数均为 0。 每收到一个请求,活跃数加 1,完成请求后则将活跃数减 1。在服务运行一段时间后, 性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服 务提供者能够优先获取到新的服务请求
hash 一致性算法,相同参数的请求总是发到同一提供者 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者, 不会引起剧烈变动
加权轮询算法 所谓轮询是指将请求轮流分配给每台服务器。举个例子,我们有三台服务器 A、B、C。 我们将第一个请求分配给服务器 A,第二个请求分配给服务器 B,第三个请求分配给 服务器 C,第四个请求再次分配给服务器 A。这个过程就叫做轮询。轮询是一种无状 态负载均衡算法,实现简单,适用于每台服务器性能相近的场景下。但现实情况下, 我们并不能保证每台服务器性能均相近。如果我们将等量的请求分配给性能较差的服 务器,这显然是不合理的。因此,这个时候我们需要对轮询过程进行加权,以调控每 台服务器的负载。经过加权后,每台服务器能够得到的请求数比例,接近或等于他们 的权重比。比如服务器 A、B、C 权重比为 5:2:1。那么在 8 次请求中,服务器 A 将 收到其中的 5 次请求,服务器 B 会收到其中的 2 次请求,服务器 C 则收到其中的 1次请求
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-simple</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.3.2</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>
<!--JAX-RS CXF / Jersey /RESTEasy-->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.8.1.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>4.0.0.Final</version>
</dependency>
@Path("/pay")
public interface IPayService {
//暴露出去的服务
//完成支付的功能
@GET
@Path("/{info}")
String pay(@PathParam("info") String info);
}
public class PayServiceImpl implements IPayService{
//执行支付的服务
@Override
public String pay(String info) {
System.out.println("execute pay:"+info);
return "Hello Dubbo :"+info;
}
}
在 resources/META-INF/spring 下创建 application.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="pay-service"/>
<!--配置注册中心-->
<dubbo:registry address="N/A"/>
<dubbo:service interface="cn.moremind.IPayService" ref="payService"/>
<dubbo:service interface="cn.moremind.IQueryService" ref="queryService"/>
<bean id="payService" class="cn.moremind.PayServiceImpl"/>
<bean id="queryService" class="cn.moremind.QueryServiceImpl"/>
<dubbo:protocol name="dubbo" port="20880"/>
</beans>
public class App
{
public static void main( String[] args ) throws IOException {
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/application.xml"});
applicationContext.start();
System.in.read();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="order-service"/>
<!--配置注册中心-->
<dubbo:registry address="N/A"/>
<dubbo:reference id="payService" interface="cn.moremind.IPayService"
url="dubbo://127.0.0.1:20880/cn.moremind.IPayService"/>
</beans>
public class App
{
public static void main( String[] args )
{
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext(new String[]{"application.xml"});
IPayService payService = (IPayService) applicationContext.getBean("payService");
String rs = payService.pay("Test");
System.out.println(rs);
//applicationContext.start();
System.out.println( "Hello World!" );
}
}
public void start() {
String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
if (StringUtils.isEmpty(configPath)) {
configPath = DEFAULT_SPRING_CONFIG;
}
context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"), false);
context.refresh();
context.start();
}
Spring Container :自动加载 META-INF/spring 目录下的所有 Spring 配置。
logback Container :自动装配 logback 日志
Log4j Container:自动配置 log4j 的配置
Dubbo 提供了一个 Main.main 快速启动相应的容器,默认情况下,只会启 动 spring 容器
默认情况下,spring 容器,本质上,就是加在 spring ioc 容器,然后启动 一个 netty 服务实现服务的发布,所以并没有特别多的黑科技,下面是 spring 容器启动的代码
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
如果是 zookeeper 集群,则配置的方式是 address="zookeeper://ip:host?backup=ip:host,ip:host"
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="order-service"/>
<!--配置注册中心-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:reference id="payService" interface="cn.moremind.IPayService"/>
</beans>
dubbo 每次都要连 zookeeper?
答案很显然是通过缓存实现 在消费端的配置文件中指定如下路径.
<dubbo:registry id="zookeeper" address="zookeeper://192.168.13.102:2181" file="d:/dubbo�server" />
其他注册中心的实现,核心本质是一样的,都是为了管理服务地址,后续会 讲到 nacos;
Dubbo 中可以支持多注册中心,有的时候,客户端需要用调用的远程服务 不在同一个注册中心上,那么客户端就需要配置多个注册中心来访问。演示 一个简单的案例
<dubbo:registry address="zookeeper://192.168.13.102:2181" id="registryCenter1"/>
<dubbo:registry address="zookeeper://192.168.13.102:2181" id="registryCenter2"/>
通过 registry 设置注册中心的 ID
<dubbo:service interface="com.gupaoedu.practice.LoginService" registry="registryCenter1" ref="loginService" />
实现的代码和服务端一样
1.当设置 时,记录失败注册和订阅请求, 后台定时重试
2.可通过 设置 zookeeper 登录信息
3.可通过 设置 zookeeper 的根节 点,默认使用 dubbo 作为 dubbo 服务注册的 namespace
Dubbo 从另一个方面来看也可以认为是一个服务治 理生态。从目前已经讲过的内容上可以看到。
1.Dubbo 可以支持市面上主流的注册中心
2.Dubbo 提供了 Container 的支持,默认提供了 3 种 container。我们可以 自行扩展
3.Dubbo 对于 RPC 通信协议的支持,不仅仅是原生的 Dubbo 协议,它还 围绕着 rmi、hessian、http、webservice、thrift、rest
有了多协议的支持,使得其他 rpc 框架的应用程序可以快速的切入到 dubbo 生态中。 同时,对于多协议的支持,使得不同应用场景的服务,可以选择合 适的协议来发布服务,并不一定要使用 dubbo 提供的长连接方式。
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-simple</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="pay-service"/>
<!--配置注册中心-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:service interface="cn.moremind.IPayService" ref="payService"/>
<dubbo:service interface="cn.moremind.IQueryService" ref="queryService" protocol="dubbo,webservice"/>
<bean id="payService" class="cn.moremind.PayServiceImpl"/>
<bean id="queryService" class="cn.moremind.QueryServiceImpl"/>
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:protocol name="webservice" port="8080" server="jetty"/>
</beans>
添加多协议支持,一个服务可以发布多种协议的支持,也可以实现不同服务 发布不同的协议
启 动 服 务 之 后 , 可 以 使 用 :http://127.0.0.1:8099/cn.moremind.IQueryService?wsdl
1.客户端的配置 jar 包依赖
2 配置多个协议支持,实现方式和服务端一致
我在用 REST 协议发布服务的时候,遇到 Dubbo 的一个坑, Dubbo 启动时 后写地方的服务直接被吞掉了,使得服务启动不了的同时,还看不到错误信 息。
(可以把 resteasy-client 包暂时不导入,看到效果) Dubbo 中的 REST(表述性资源转移)支持,是基于 JAX-RS2.0(Java API for RESTful Web Services)来实现的。
REST 是一种架构风格,简单来说就是对于 api 接口的约束,基于 URL 定位 资源,使用 http 动词(GET/POST/DELETE)来描述操作
REST 很早就提出来了,在早期开发人员为了实现 REST,会使用各种工具来 实现,比如 Servlets 就经常用来开发 RESTful 的程序。随着 REST 被越来越 多的开发人员采用,所以 JCP(Java community process)提出了 JAX-RS 规 范,并且提供了一种新的基于注解的方式来开发 RESTful 服务。有了这样的 一个规范,使得开发人员不需要关心通讯层的东西,只需要关注资源以以及 数据对象。
JAX-RS 规范的实现有:Apache CXF、Jersey(由 Sun 公司提供的 JAX-RS 的 参考实现)、RESTEasy( jboss 实现)等。
而 Dubbo 里面实现的 REST 就是基于 Jboss 提供的 RESTEasy 框架来实现的SpringMVC 中的 RESTful 实现我们用得比较多,它也是 JAX-RS 规范的一种 实现
<dubbo:protocol name="rest" port="8888" server="jetty"/>
@Path("/users"):指定访问 UserService 的 URL 相对路径是/users,即 http://localhost:8080/users
@Path("/register"):指定访问 registerUser()方法的 URL 相对路径是/register,
@Path("/pay")
public interface IPayService {
//暴露出去的服务
//完成支付的功能
@GET
@Path("/{info}")
String pay(@PathParam("info") String info);
}