分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。 分布式系统(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的话是肯定不能处理大量请求的,当大量请求访问服务器时,会使得大量线程阻塞等待业务逻辑的完成。
Netty基本原理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对象中。