分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。 分布式系统(distributed system)是建立在网络之上的软件系统。
随着互联网的发展,网站的应用规模不断扩大,常规的垂直应用架构已经无法应对,分布式服务架构以及流动计算机架构势在必行,亟需一个治理系统确保架构有条不紊的进行。
当网站流量很小时,只需要一个应用,将所有的功能都部署在一起,以减少部署节点和成本,此时,用于 简化增删改查工作量的数据访问框架(ORM) 是关键。
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成不相干的几个应用,以提升效率。此时,用于 加速前端页面开发的web框架(MVC) 是关键。
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前段应用能更快的速度响应多变的市场需求,此时,用于 提高业务复用及整合的分布式框架(RPC) 是关键。
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现。此时需要增加一个调度中心基于访问压力实施管理集群容量,提高集群利用率。此时, 用于提高计算机利用率的资源调度和治理中心(SOA:Service Oriented Architecture) 是关键
RPC(Remote Procedure Call)是指远程过程调用,是一种进程间的通信方式,它是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)过程或函数,而不用程序员显示的编码这个远程调用的细节,即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
首先 client 通过 client stub (可理解为 client 与 server 交互的助手)与 server 端建立 socket 连接,然后通过网络将需要调用 server 端的方法信息(如方法名、参数列表、返回值类型等信息)传递给 server 端的 server stub , server stub 收到信息后将这些信息传递给 server , server 使用信息里的参数执行方法,得到返回值后将返回值交给 server stub , server stub 在通过 socket 连接将返回值传递给 client 端中的 client stub , client stub 将从 server 中得到的返回值交给 client 。
Apache Dubbo 是一款 高性能、轻量级 的开源Java PRC框架,它提供了三大核心能力: 面向接口的远程方法调用 、 智能容错和负载均衡 以及 服务自动注册和发现 。
Dubbo官方文档中建议使用Zookeeper注册中心。
首先进入进入下载页面
下载Dubbo监控中心
下载完成之后修改配置文件信息,如图所示,如果Zookeeper是单机模式,修改 application.properties 文件中的 dubbo.registry.address 为zookeeper部署的ip地址。
如果Zookeeper是以集群的模式启动的,修改 application.properties 文件中的 dubbo.registry.address 为zookeeper集群的部署的ip地址(我的集群是三台Zookeeper节点的集群,IP地址分别为 192.168.51.101、192.168.51.101、192.168.51.101 )。v
修改完成之后使用终端(Windows下使用cmd)打包
首先进入dubbo-admin目录
执行 mvn clean package命令打包(前提是电脑已经配置了MAVEN)
若打包结束后如下图,则表示打包成功,
打好的包会放在dubbo-admin目录下的target目录下。
incubator-dubbo-ops-master 根目录下,然后在终端中执行Java命令运行jar包( 前提是Zookeeper已经启动 )。
localhost:7001 进入,账号密码均为root。
dubboHelloWorld 并设置 groupId 为 com.gmall Module 起名为 gmall-interface gmall-interface 中创建 com.gmall.bean.UserAddress 实体类 package com.gmall.bean;
import java.io.Serializable;
/**
* 用户地址
*
* @author lfy
*/
public class UserAddress implements Serializable {
private Integer id;
private String userAddress; //用户地址
private String userId; //用户id
private String consignee; //收货人
private String phoneNum; //电话号码
private String isDefault; //是否为默认地址 Y-是 N-否
public UserAddress() {
super();
// TODO Auto-generated constructor stub
}
public UserAddress(Integer id, String userAddress, String userId, String consignee, String phoneNum,
String isDefault) {
super();
this.id = id;
this.userAddress = userAddress;
this.userId = userId;
this.consignee = consignee;
this.phoneNum = phoneNum;
this.isDefault = isDefault;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getConsignee() {
return consignee;
}
public void setConsignee(String consignee) {
this.consignee = consignee;
}
public String getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(String phoneNum) {
this.phoneNum = phoneNum;
}
public String getIsDefault() {
return isDefault;
}
public void setIsDefault(String isDefault) {
this.isDefault = isDefault;
}
}
复制代码
gmall-interface 中创建 com.gmall.service.UserService 和 com.gmall.service.OrderService 接口 package com.gmall.service;
import java.util.List;
import com.gmall.bean.UserAddress;
/**
* 用户服务
*
* @author lfy
*/
public interface UserService {
/**
* 按照用户id返回所有的收货地址
*
* @param userId
* @return
*/
public List<UserAddress> getUserAddressList(String userId);
}
复制代码
package com.gmall.service;
import java.util.List;
import com.gmall.bean.UserAddress;
public interface OrderService {
/**
* 初始化订单
*
* @param userId
*/
public List<UserAddress> initOrder(String userId);
}
复制代码
Module 起名为 user-service-provider 并创建 com.gmall.service.impl.UserServiceImpl 实现 UserService 。 package com.gmall.service.impl;
import java.util.Arrays;
import java.util.List;
import com.gmall.bean.UserAddress;
import com.gmall.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserServiceImpl.....old...");
// TODO Auto-generated method stub
UserAddress address1 = new UserAddress(1, "山东省济南市数娱广场A座", "1", "邢老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "山东省济南市数娱广场A座", "1", "王老师", "010-56253825", "N");
/*try {
Thread.sleep(4000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
return Arrays.asList(address1, address2);
}
}
复制代码
Module 起名为 order-service-consumer 并创建 com.gmall.service.impl.OrderServiceImpl 实现 OrderService 。 package com.gmall.service.impl;
import com.gmall.bean.UserAddress;
import com.gmall.service.OrderService;
import com.gmall.service.UserService;
import java.util.List;
public class OrderServiceImpl implements OrderService {
UserService userService;
public List<UserAddress> initOrder(String userId) {
List<UserAddress> addressList = userService.getUserAddressList(userId);
System.out.println(addressList);
return null;
}
}
复制代码
user-service-provider 和 order-service-consumer 并没有创建 UserService 和 OrderService 接口,所以需要在这两个 Module 中的 pom 文件中导入 gmall-interface 。 <dependencies>
<dependency>
<groupId>com.gmall</groupId>
<artifactId>gmall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
复制代码
order-service-consumer 中的 OrderServiceImpl 调用的是 UserService ,是一个接口,它的实现类有可能存在于别的项目或者别的服务器中,所以此时肯定是无法运行的,所以此时需要用Dubbo来改造进行远程调用。 (1):导入 Dubbo 依赖,Dubbo依赖中包含了相关的 Spring 的 jar 包,所以在使用时可以使用 Spring 的方式使用 Dubbo 。
<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
复制代码
(2):由于注册中心使用的是 Zookeeper ,所以还需要导入 Zookeeper 客户端的依赖, dubbo2.6 之前的版本引入 zkclient 操作 zookeeper ,而 dubbo 2.6 及以后的版本引入 curator 操作 zookeeper 。
<dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency> 复制代码
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
复制代码
(3):通过 Spring 配置服务提供者(在 user-service-provider 中配置 provider.xml )
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
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">
<!-- 1.指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名) -->
<dubbo:application name="user-service-provider"/>
<!-- 2.指定注册中心的位置 -->
<dubbo:registry address="zookeeper://192.168.51.101:2181?backup=192.168.51.102:2181,192.168.51.103:2181"/>
<!-- 3.指定通信规则(通信协议和通信端口) -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 4.暴露服务 interface:需要暴露的接口 ref:指向服务的真正的实现对象 -->
<dubbo:service interface="com.gmall.service.UserService" ref="userServiceImpl"/>
<!-- 服务的实现 -->
<bean id="userServiceImpl" class="com.gmall.service.impl.UserServiceImpl"/>
</beans>
复制代码
(4):测试
package com.gmall.test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class MainApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
context.start();
System.in.read();
}
}
复制代码
resource 目录下的 provider.xml 配置文件,运行启动起来之后会加载配置文件,然后读取需要连向的 Zookeeper 和需要暴露的服务。服务启动之后刷新 localhost:7001 ,若绿色标注处的服务数和应用数为1,则表示测试成功。
(1):和配置服务提供者相同导入需要的依赖 (2):通过Spring配置服务消费者(在 order-service-consumer 配置 consumer.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"
xmlns:context="http://www.springframework.org/schema/context"
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
">
<context:component-scan base-package="com.gmall.service.impl"/>
<!-- 1.指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名) -->
<dubbo:application name="order-service-consumer"/>
<!-- 2.指定注册中心的位置 -->
<dubbo:registry address="zookeeper://192.168.51.101:2181?backup=192.168.51.102:2181,192.168.51.103:2181"/>
<!-- 声明需要调用的远程服务的接口,生成远程服务代理 -->
<dubbo:reference interface="com.gmall.service.UserService" id="userService"/>
</beans>
复制代码
OrderServiceImpl 类中可以使用 @Autowired 自动注入将 UserService 注入进来,同时 OrderServiceImpl 类名上可以加上 @Service 注解 (3):测试
package com.gmall.test;
import com.gmall.service.OrderService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class MainApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
OrderService orderService = context.getBean(OrderService.class);
orderService.initOrder("1");
System.out.println("调用完成..");
System.in.read();
}
}
复制代码
consumer.xml 配置文件,读取 OrderService 对象,然后调用 initOrder 方法。这里可以调用的原因是在 consumer.xml 配置文件中使用 <context:component-scan base-package="com.gmall.service.impl"/> 语句自动注入了 OrderService 的实现类,并且在 consumer.xml 文件中声明了远程服务的接口 userService ,所以在 initOrder 中的 userService 会远程调用 getUserAddressList 方法。并且在刚才的服务者配置中已经暴露出了UserServiceImpl实现类,所以可以直接调用。 localhost:7001 ,可以看到服务消费者数为1
-分别修改 main 和 test 下的 dubbo.properties 文件
dubbo-monitor-simple 文件夹使用 mvn package 打包。
//使得服务生产者和服务消费者都连接上监控中心 <dubbo:monitor protocol="registry"/> 复制代码
然后在创建好的项目中依次创建三个Module,名字为GroupId和项目的一致,名字依次为:boot-dubbo-interface、boot-order-service-consumer、boot-user-service-provider。
boot-gmall-interface中依次创建下图接口和类,内容直接复制上个例子中。
#指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名) dubbo.application.name=user-service-provider #指定注册中心的位置 dubbo.registry.address=zookeeper://192.168.51.101:2181?backup=192.168.51.102:2181,192.168.51.103:2181 #指定注册中心类型 dubbo.registry.protocol=zookeeper #指定通信规则(通信协议和通信端口) dubbo.protocol.name=dubbo dubbo.protocol.port=20880 #服务生产者连接监控中心 dubbo.mointor.protocol=registry 复制代码
#因为这是一个web应用,而且8080端口被Dubbo的监控中心占用,所以需要修改默认端口 server.port=8082 dubbo.application.name=boot-order-service-consumer dubbo.registry.address=zookeeper://192.168.51.101:2181?backup=192.168.51.102:2181,192.168.51.103:2181 dubbo.monitor.protocol=registry 复制代码
然后在该项目的启动类上添加@EnableDubbo注解。
测试,依次启动服务生产者和服务消费者。
浏览器输入localhost:8080/initOrder?uid=1,并访问localhost:8080,出现下图所示结果即表示测试成功。
Dubbo的基本配置在Dubbo的官方文档写的很清楚,包括基于XML的配置以及在SpringBoot中的配置
Dubbo官网
比如某个接口设计出了新版本的升级,但是不保证其稳定性,可以让系统中一部分节点使用新版本,其余节点还是使用老版本,若使用非常稳定,再让剩余的节点使用新功能。可以使用version属性设置接口的版本。
通过配置远程服务后,客户端通常只剩下了接口,而实现全在服务器端,但是服务提供方想再客户端也执行部分逻辑,比如:做ThreadLocal缓存、提前验证参数,调用失败后伪造容错数据等等。此时就需要在API中带上Stub,客户端生成Proxy实例,会把Proxy通过构造函数传递给Stub,然后吧Stub暴露给用户,Stub绝对要不要调用Proxy。
在接口包中定义一个接口实现类,且实现类中声明一个接口对象并且声明有参构造,构造起传入的其实是真正的远程代理对象,然后在实现接口的方法中可以进行数据验证等操作。
之后在xml文件中使用stub属性配置,属性值是实现类的全类名。
在SpringBoot中可以通过如下配置
可以使用Reference的loadbalance属性来实现 可以在localhost:7001中设置服务提供者的权重
基于权重的随机负载均衡机制:Random LoadBalance 随机,按照权重设置随机率。比如orderService想要远程调用userService,而userService分别在三台机器上,我们可以给每台机器设置权重,比如三台机器的权重依次为100、200、50,则总权重为350,则选择第一台的概率就是100/350.
RoundRobin LoadBalance:基于权重的轮训负载均衡机制 轮询,按照公约后的权重设置轮询比率。如果没有权重,还是userService分别在三台机器上,那么三台机器轮流轮流处理服务消费者的请求,加上权重后,三台机器权重分别为100、200、50,则三台机器占总权重的比例分别为2/7、4/7、1/7, 三台机器轮流处理服务消费者的请求,前三次请求轮流执行,第四次和第五分别到一和二号机器,但是第六次请求不会让第三台机器处理,因为三号机器权重比是1/7,而一号机器已经处理了两次请求,所以第六次请求会让第二台机器处理。存在慢的提供者累计请求的问题,比如第二台机器很慢但是没宕机,当请求调用到第二台就卡在哪里,久而久之,所有的请求都会卡在调用第二台上。
LeastActive LoadBalance:最少活跃数负载均衡机制。还是三台服务器处理服务消费者的请求,比如三台服务器上一次处理请求所花费的时间分别为100ms、1000ms、300ms,则这一次请求回去上一次处理请求时间最短的机器,所以这次一号服务器处理这次请求。
ConsistentHash LoadBalance:一致性Hash负载均衡机制。
当服务器压力剧增的情况下,根据实际业务及流量,对一些服务和页面有策略的不处理或者换种简单的方式处理,从而释放服务器资源以保证核心交易正常或搞笑的运行。
<dubbo:servrice cluster="failsafe"> //或者 <dubbo:servrice cluster="failsafe"> 复制代码
hystrix是指通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和段路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
首先导入hystrix依赖
在SpringBoot运行主类中添加@EnableHystrix表示用注解方式使用Hystrix。
在服务生产者需要被Hystrix代理的服务(方法)上添加@HystrixCommand。
在服务消费者的SpringBoot主类中添加@EnableHystrix表示用注解方式使用Hystrix
在服务消费者需要远程调用服务生产者的对象出使用@HystrixCommand(fallbackMethod = "hello"),表示该方法出错了,调用fallbackMethod中的回调函数进行容错。
Netty是一个异步事件驱动的网络应用程序框架,是基于NIO的多路复用模型实现的。
BIO又称为Blocking IO,是阻塞的IO,如下图,假设这是一个服务器,当有网络请求时,会开启一个socket处理请求,并进行相应的业务逻辑处理,在业务逻辑没有处理完成之前Thread是不会得到释放的,所以使用BIO的话是肯定不能处理大量请求的,当大量请求访问服务器时,会使得大量线程阻塞等待业务逻辑的完成。
Business业务逻辑层
RPC层:完成远程调用的层 (1):config配置层:对外配置接口,以ServiceConfig,ReferenceConfig为中心,可以直接初始化配置类,也可以通过spring解析配置生成配置类。 (2):proxy服务代理层:服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton, 以ServiceProxy为中心,扩展接口为ProxyFactory。 (3):registry注册中心层:封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory,Registry、RegistryService。 (4):cluster路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster,Directory,Router,LoadBalance。 (5):monitor监控层:RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory,Monitor,MonitorService。 (6):protocol远程调用层:封装RPC调用,以Invocation,Result为中心,扩展接口为Protocol,Invoker,Exporter。
Remoting:解决远程通信的层。 (1):exchange信息交换层:封装请求响应模式,同步转异步,以Request,Response为中心,扩展接口为Exchanger,ExchangeChannel,ExchangeClient,ExchangeServer。 (2):transport网络传输层:抽象mina和Netty为统一接口,以Message为中心,扩展接口为Channel,Transporter,Client,Server,Codec。 (3):serialize数据序列化层:可复用的一些工具,扩展接口为Serialization,ObjectInput,ObjectOutput,ThreadPoll。
我们将Dubbo的配置写在了xml配置文件中,而Dubbo的配置文件是一个Spring的配置文件,启动Dubbo项目也是以Spring的方式启动的,Spring通过总接口BeanDefinitionParser来解析xml配置文件中的标签。该接口是Spring的解析器。而DubboBeanDefinitionParser是Dubbo的标签解析器。DubboBeanDefinitionParser通过parse方法方法解析标签。每一个标签有与之对应的Config对象,所以说解析标签的目的就是将标签中的每一个属性解析处理,放入对应的Config对象中。