一、概述
1 什么是分布式系统?
《分布式系统原理与范型》定义: “分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统” 分布式系统(distributed system)是建立在网络之上的软件系统。 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
分布式或者说 SOA 分布式重要的就是面向服务,说简单的分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等,拆分之后的每个服务可以部署在不同的机器上,如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上。
2 web系统发展演变?
- 单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:1、性能扩展比较难
2、协同开发问题
3、不利于升级维护
- 垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点:公用模块无法重复利用,开发性的浪费
- 分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
- 流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。
3 RPC
3.1 什么是RPC
RPC(Remote Procedure Call)即远程过程调用,通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。
为什么要 RPC ?因为,两个不同的服务器上的服务提供的方法不在一个内存空间,所以,需要通过网络编程才能传递方法调用所需要的参数。并且,方法调用的结果也需要通过网络编程来接收。但是,如果我们自己手动网络编程来实现这个调用过程的话工作量是非常大的,因为,我们需要考虑底层传输方式(TCP还是UDP)、序列化方式等等方面。
RPC 能帮助我们做什么呢? 简单来说,通过 RPC 可以帮助我们调用远程计算机上某个服务的方法,这个过程就像调用本地方法一样简单。并且!我们不需要了解底层网络编程的具体细节。
举个例子:两个不同的服务 A、B 部署在两台不同的机器上,服务 A 如果想要调用服务 B 中的某个方法的话就可以通过 RPC 来做。
一言蔽之:RPC 的出现就是为了让你调用远程方法像调用本地方法一样简单。
3.2 RPC原理
为了能够帮助小伙伴们理解 RPC 原理,我们可以将整个 RPC的 核心功能看作是下面👇 6 个部分实现的:
- 客户端(服务消费端):调用远程方法的一端。
- 客户端 Stub(桩): 这其实就是一代理类。代理类主要做的事情很简单,就是把你调用方法、类、方法参数等信息传递到服务端。
- 网络传输: 网络传输就是你要把你调用的方法的信息比如说参数啊这些东西传输到服务端,然后服务端执行完之后再把返回结果通过网络传输给你传输回来。网络传输的实现方式有很多种比如最近基本的 Socket或者性能以及封装更加优秀的 Netty(推荐)。
- 服务端 Stub(桩):这个桩就不是代理类了。我觉得理解为桩实际不太好,大家注意一下就好。这里的服务端 Stub 实际指的就是接收到客户端执行方法的请求后,去指定对应的方法然后返回结果给客户端的类。
- 服务端(服务提供端):提供远程方法的一端。
具体原理图如下,后面我会串起来将整个RPC的过程给大家说一下。
- 服务消费端(client)以本地调用的方式调用远程服务;
- 客户端 Stub(client stub) 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):RpcRequest;
- 客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端;
- 服务端 Stub(桩)收到消息将消息反序列化为Java对象:RpcRequest;
- 服务端 Stub(桩)根据RpcRequest中的类、方法、方法参数等信息调用本地的方法;
- 服务端 Stub(桩)得到方法执行结果并将组装成能够进行网络传输的消息体:RpcResponse(序列化)发送至消费方;
客户端 Stub(client stub)接收到消息并将消息反序列化为Java对象:RpcResponse,这样也就得到了最终结果。
4 Dubbo
4.1 什么是Dubbo
Apache Dubbo(incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源 Java RPC 框架。
根据Dubbo 官方文档的介绍,Dubbo 提供了六大核心能力面向接口代理的高性能RPC调用。
- 智能容错和负载均衡。
- 服务自动注册和发现。
- 高度可扩展能力。
- 运行期流量调度。
- 可视化的服务治理与运维。
简单来说就是:Dubbo 不光可以帮助我们调用远程服务,还提供了一些其他开箱即用的功能比如智能负载均衡。
4.2 设计架构
- 服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者(Consumer): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务。服务消费者从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
- 监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
调用关系说明:
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
二、基本使用
需求:
某个电商系统,订单服务需要调用用户服务获取某个用户的所有地址;
我们现在需要创建两个服务模块进行测试
模块 | 功能 |
---|---|
订单服务web模块 | 创建订单等 |
用户服务service模块 | 查询用户地址等 |
测试预期结果:
订单服务web模块在A服务器,用户服务模块在B服务器,A可以远程调用B的功能。
1 spring整合dubbo
以下使用XML 配置的方式,更多配置方式见官方文档
1.1 spring-common模块:
公共接口层(model,service,exception…),定义公共接口,也可以导入公共依赖
UserAddress.java:用户地址实体类
public class UserAddress implements Serializable {
private Integer id;
private String userAddress;
private String userId;
private String consignee;
private String phoneNum;
private String isDefault;
....
}
IOrderService.java:订单接口
public interface IOrderService {
/**
* 用户下单
* @param userId
*/
UserAddress placeOrder(int userId);
}
IUserService.java:用户接口
public interface IUserService {
/**
* 根据用户id获取用户地址
* @param userId
* @return
*/
UserAddress getUserAddById(int userId);
}
pom.xml:通用的依赖,引入dubbo和zkclient
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dubboStudy</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.10</version>
</dependency>
<!--zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
</dependencies>
</project>
1.2 spring-user模块:
用户业务,作为服务提供者
pom.xml:引入通用模块,可以使用定义的接口
<dependency>
<groupId>org.example</groupId>
<artifactId>spring-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
UserServiceImpl.xml:IUserService的实现类,供远程调用
public class UserServiceImpl implements IUserService {
@Override
public UserAddress getUserAddById(int userId) {
UserAddress userAddress = new UserAddress();
userAddress.setUserAddress("上海市宝山区");
return userAddress;
}
}
provider.xml:dubbo配置信息
<?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://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--1、指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名)-->
<dubbo:application name="spring-user"/>
<!--2、指定注册中心的位置-->
<!--<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>-->
<dubbo:registry address="zookeeper://192.168.31.136:2181"/>
<!--3、指定通信规则(通信协议? 服务端口)-->
<dubbo:protocol name="dubbo" port="20880"/>
<!--4、暴露服务 让别人调用 ref指向服务的真正实现对象-->
<bean id="userServiceImpl" class="me.nic.service.impl.UserServiceImpl"/>
<!--服务的实现-->
<dubbo:service interface="me.nic.service.IUserService" ref="userServiceImpl"/>
</beans>
ConsumerRun.java:启动:
public class ConsumerRun {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("consumer.xml");
IOrderService orderService = applicationContext.getBean(IOrderService.class);
orderService.placeOrder(1);
System.in.read();
}
}
1.3 spring-order模块:
订单业务,作为服务消费者
pom.xml:引入通用模块,可以使用定义的接口,同1.2
OrderServiceImpl.xml:IOrderService的实现类,其中远程调用userService
@Service
public class OrderServiceImpl implements IOrderService {
@Autowired
private IUserService userService;
@Override
public UserAddress placeOrder(int userId) {
// 远程调用,获取用户地址
UserAddress userAddById = userService.getUserAddById(userId);
// 下单
System.out.println("用户地址:" + userAddById.getUserAddress());
System.out.println("下单成功");
return null;
}
}
consumer.xml:dubbo配置信息
<?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:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--包扫描-->
<context:component-scan base-package="me.nic.service.impl"/>
<!--指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名)-->
<dubbo:application name="spring-order"/>
<!--指定注册中心的位置-->
<dubbo:registry address="zookeeper://192.168.31.136:2181"/>
<!--调用远程暴露的服务,生成远程服务代理-->
<dubbo:reference id="userService" interface="me.nic.service.IUserService"/>
</beans>
ProviderRun.java:启动
public class ProviderRun {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("classpath:provider.xml");
System.in.read();
}
}
1.4 效果
ProviderRun启动之后阻塞,
ConsumerRun启动之后调用orderService.placeOrder(1)
,placeOrder
中远程调用spring-user
模块中的userService.getUserAddById(userId)
获取用户地址:
用户地址:上海市宝山区
下单成功
2 springboot整合dubbo
2.1 boot-common模块:
公共接口层(model,service,exception…),定义公共接口,也可以导入公共依赖
UserAddress.java:用户地址实体类
public class UserAddress implements Serializable {
private Integer id;
private String userAddress;
private String userId;
private String consignee;
private String phoneNum;
private String isDefault;
....
}
IOrderService.java:订单接口
public interface IOrderService {
/**
* 用户下单
* @param userId
*/
UserAddress placeOrder(int userId);
}
IUserService.java:用户接口
public interface IUserService {
/**
* 根据用户id获取用户地址
* @param userId
* @return
*/
UserAddress getUserAddById(int userId);
}
pom.xml:通用的依赖,引入dubbo的starter
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>boot-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-common</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
</project>
2.2 boot-user模块:
用户业务,作为服务提供者
pom.xml:引入通用模块,可以使用定义的接口
<dependency>
<groupId>org.example</groupId>
<artifactId>spring-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
UserServiceImpl.xml:IUserService的实现类,供远程调用@Service
暴露dubbo的服务
@Service
@Component
public class UserServiceImpl implements IUserService {
@Override
public UserAddress getUserAddById(int userId) {
UserAddress userAddress = new UserAddress();
userAddress.setUserAddress("上海市宝山区");
return userAddress;
}
}
application.properties:dubbo配置信息
dubbo.application.name=boot-user
dubbo.registry.address=192.168.31.136:2181
dubbo.registry.protocol=zookeeper
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
BootUserApplication.java:启动:@EnableDubbo
: 开启基于注解的dubbo功能
@EnableDubbo
@SpringBootApplication
public class BootUserApplication {
public static void main(String[] args) {
SpringApplication.run(BootUserApplication.class, args);
}
}
2.3 boot-order模块:
订单业务,作为服务消费者
pom.xml:引入通用模块,可以使用定义的接口,同1.2
<dependency>
<groupId>com.example</groupId>
<artifactId>boot-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
OrderServiceImpl.xml:IOrderService的实现类,其中远程调用userService@Reference
:引用远程提供者服务
@Service
public class OrderServiceImpl implements IOrderService {
//引用远程提供者服务
@Reference
private IUserService userService;
@Override
public UserAddress placeOrder(int userId) {
// 远程调用,获取用户地址
UserAddress userAddById = userService.getUserAddById(userId);
return userAddById;
}
}
OrderController.java:web接口,调用OrderService:
@Controller
public class OrderController {
@Autowired
IOrderService orderService;
@RequestMapping("/initOrder")
@ResponseBody
public UserAddress initOrder(@RequestParam("uid")int userId) {
return orderService.placeOrder(userId);
}
}
application.properties:dubbo配置信息
server.port=8081
dubbo.application.name=boot-order
dubbo.registry.address=zookeeper://192.168.31.136:2181
BootOrderApplication.java:启动
@EnableDubbo
@SpringBootApplication
public class BootOrderApplication {
public static void main(String[] args) {
SpringApplication.run(BootOrderApplication.class, args);
}
}
2.4 效果
3 dubbo与springboot整合的三种方式
- 导入dubbo-starter。在application.properties配置属性,在启动类上使用@EnableDubbo开启基于注解的dubbo功能,使用@Service【暴露服务】,使用@Reference【引用服务】。这种方式无法做到方法级配置
- 保留Dubbo 相关的xml配置文件
导入dubbo-starter,在启动类上不再使用@EnableDubbo注解,而是使用@ImportResource导入Dubbo的 xml配置文件
- 使用 注解API的方式
将每一个组件手动配置到容器中(Config类),让dubbo来扫描其他的组件
三、重要配置
详细见官方文档
1 属性重写与优先级
优先级从高到低:
- JVM -D 参数:当你部署或者启动应用时,它可以轻易地重写配置,比如,改变 dubbo 协议端口;
- XML:XML 中的当前配置会重写 dubbo.properties 中的;
- Properties:默认配置,仅仅作用于以上两者没有配置时。
另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check=“false”,总是会返回引用,当服务恢复时,能自动连上。
以order-service-consumer消费者为例,在consumer.xml中添加配置
<!--配置当前消费者的统一规则, 当前所有的服务都不启动时检查-->
<dubbo:consumer check="false"></dubbo:consumer>
添加后,即使服务提供者不启动,启动当前的消费者,也不会出现错误。
3 超时
由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。
全局超时配置
<dubbo:provider timeout="5000" />
指定接口以及特定方法超时配置
<dubbo:provider interface="com.foo.BarService" timeout="2000">
<dubbo:method name="sayHello" timeout="3000" />
</dubbo:provider>
配置原则
dubbo推荐在Provider上尽量多配置Consumer端属性
- 作为服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等
- 在Provider配置后,Consumer不配置则会使用Provider的配置值,即Provider配置可以作为Consumer的缺省值。否则,Consumer会使用Consumer端的全局设置,这对于Provider不可控的,并且往往是不合理的
配置的覆盖规则:
- 方法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
4 重试次数
失败自动切换,当出现失败,重试其它服务器,但重试会带来更长延迟。可通过 retries=”2” 来设置重试次数(不含第一次)。
重试次数配置如下:
<dubbo:service retries="2" />
或
<dubbo:reference retries="2" />
或
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
5 多版本
在 Dubbo 中为同一个服务配置多个版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 然后将剩下的一半提供者升级为新版本
老版本服务提供者配置:
<dubbo:service interface="com.foo.BarService" version="1.0.0" />
新版本服务提供者配置:
<dubbo:service interface="com.foo.BarService" version="2.0.0" />
老版本服务消费者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
新版本服务消费者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
如果不需要区分版本,可以按照以下的方式配置 [^1]:
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
四、高可用
1 zookeeper宕机与dubbo直连
现象:zookeeper注册中心宕机,还可以消费dubbo暴露的服务。
原因:
- 健壮性
- 监控中心宕掉不影响使用,只是丢失部分采样数据
- 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
即使没有注册中心,也可以使用dubbo直连的方式使用生产者提供的服务:
// 假如提供userService服务的机器为127.0.0.1:20882
@Reference(url="127.0.0.1:20882")
private IUserService userService;
2 集群下dubbo负载均衡配置
维基百科对负载均衡的定义:负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动)的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件。
上面讲的大家可能不太好理解,再用通俗的话给大家说一下。 比如我们的系统中的某个服务的访问量特别大,我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。那么,如何正确选择处理该请求的服务器就很关键。假如,你就要一台服务器来处理该服务的请求,那该服务部署在多台服务器的意义就不复存在了。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题,我们从负载均衡的这四个字就能明显感受到它的意义。
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。
- Random LoadBalance(基于权重的随机负载均衡机制,缺省)
- 随机,按权重设置随机概率。
- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
- RoundRobin LoadBalance(基于权重的轮询负载均衡机制,不推荐)
- 轮循,按公约后的权重设置轮循比率。
- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
- 访问有顺序,但是受概率约束,如下面例子“1231222”
- LeastActive LoadBalance(最少活跃数-负载均衡机制)
- 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差(即调用所花时间)。
- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
- ConsistentHash LoadBalance(一致性哈希-负载均衡机制)
- 一致性 Hash,相同参数的请求总是发到同一提供者。
- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。算法参见:http://en.wikipedia.org/wiki/Consistent_hashing
- 缺省只对第一个参数 Hash,如果要修改,请配置
- 缺省用 160 份虚拟节点,如果要修改,请配置
3 整合hystrix,服务熔断与降级处理
3.1 服务降级
什么是服务降级? 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
向注册中心写入动态配置覆盖规则:
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
其中:
- mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
- 还可以改为 mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
3.2 集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
集群容错模式:
- Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=”2” 来设置重试次数(不含第一次)。
重试次数配置如下:
<dubbo:service retries="2" />
或
<dubbo:reference retries="2" />
或
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
- Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2” 来设置最大并行数。
- Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
集群模式配置:
按照以下示例在服务提供方和消费方配置集群模式
<dubbo:service cluster="failsafe" />
或
<dubbo:reference cluster="failsafe" />
3.3 整合hystrix
Hystrix 旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
3.3.1 配置spring-cloud-starter-netflix-hystrix
spring boot官方提供了对hystrix的集成,直接在pom.xml里加入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
然后在Application类上增加@EnableHystrix来启用hystrix starter:
@SpringBootApplication
@EnableHystrix
public class ProviderApplication {
3.3.2 配置Provider端
在Dubbo的Provider上增加@HystrixCommand配置,这样子调用就会经过Hystrix代理。
@Service(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") })
@Override
public String sayHello(String name) {
// System.out.println("async provider received: " + name);
// return "annotation: hello, " + name;
throw new RuntimeException("Exception to show hystrix enabled.");
}
}
3.3.3 配置Consumer端
对于Consumer端,则可以增加一层method调用,并在method上配置@HystrixCommand。当调用出错时,会走到fallbackMethod = “reliable”的调用里。
@Reference(version = "1.0.0")
private HelloService demoService;
@HystrixCommand(fallbackMethod = "reliable")
public String doSayHello(String name) {
return demoService.sayHello(name);
}
public String reliable(String name) {
return "hystrix fallback value";
}
}
五、框架原理
1 RPC原理
一次完整的RPC调用流程(同步调用,异步另说)如下:
1)服务消费方(client)调用以本地调用方式调用服务;
2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
3)client stub找到服务地址,并将消息发送到服务端;
4)server stub收到消息后进行解码;
5)server stub根据解码结果调用本地的服务;
6)本地服务执行并将结果返回给server stub;
7)server stub将返回结果打包成消息并发送至消费方;
8)client stub接收到消息,并进行解码;
9)服务消费方得到最终结果。
RPC框架的目标就是要2~8这些步骤都封装起来,这些细节对用户来说是透明的,不可见的。