《分布式系统原理与范型》定义:
“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”
分布式系统(distributed system)是建立在网络之上的软件系统。
架构的发展是由最初的单一应用架构构建的,一般就是ORM框架方便数据库操作。
不过随着系统越来越复杂,单一应用架构会变得难以维护,所以架构逐渐演变出了垂直应用架构,所谓垂直应用架构其实就是安装业务模板进行拆分,比如可以安装业务将一个电商系统分为订单模块,用户信息管理模块,商品管理模块等等,这时候MVC框架就派上用场,MVC框架可以协助系统更好的按业务拆分,不过业务拆分后虽然是比单一应用架构更好维护了。
不过随着系统越来约复杂,发现很多共用的模块很难复用起来,这时候分布式服务架构登场了,分布式架构是将一些核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,当应用需要时,就去服务中心调服务就可以,而实现这种服务注册的肯定是RPC框架了。
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率,这时候就需要流动计算架构(SOA)[ Service Oriented Architecture],用于提高机器利用率的资源调度,SOA是一个治理中心,综上所述,到目前,软件系统架构演变经历了:单一应用架构->垂直应用架构->分布式应用架构->流动计算架构,下面Dubbo官网的图片可以很好的描述
RPC概念
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。
RPC核心模块
RPC有两个核心模块:通信和序列化
Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
官网:
http://dubbo.apache.org/
Dubbo的服务治理:
Dubbo原理图片,图片来自Dubbo官网:
Dubbo角色:
Provider:暴露服务的服务提供者
Container:服务运行的容器
Consumer:调用远程服务的消费者
Registry:服务注册和发现的注册中心
Minitor:统计服务调用次数和时间的监控中心
调用过程:
下面根据我的理解说明一下
0:服务器容器负责启动、加载、运行服务提供者
1:服务提供者在启动后就可以向注册中心暴露服务
2:服务消费者在启动后就可以向注册中心订阅想要的服务
3:注册中心向服务消费者返回服务调用列表
4:服务消费者基于软负载均衡算法调用服务提供者的服务,这个服务提供者有可能是一个服务提供者列表,调用那个服务提供者就是根据负载均衡来调用了
5:服务提供者和服务消费者定时将保存在内存中的服务调用次数和服务调用时间推送给监控中心
搭建Zookeeper,首先是搭建分布式架构的注册中心Zookeeper,当然也可以用Redis等等来做服务注册中心,不过本博客只介绍Zookeeper的,因为没有linux服务器,所以只介绍window版的搭建
1、下载Zookeeper:
网址 https://archive.apache.org/dist/zookeeper/zookeeper-3.4.13/
2、解压Zookeeper
解压Zookeeper之后,运行bin目录里的zkServer.cmd,发现报错了,提示找不到配置文件,所以需要继续步骤3
3、配置Zookeeper
因为Zookeeper的conf文件夹下面只提供zoo_sample.cfg文件,需要自己修改命名为zoo.cfg
对于配置文件需要注意:
dataDir=./ 临时数据存储的目录(可写相对路径)
clientPort=2181 zookeeper的端口号
ok,简单在zkCli.cmd敲几个命令测试一下:
ls /:列出zookeeper根下保存的所有节点
create –e /testNode 12345678:创建一个testNode节点,值为12345678
get /testNode:获取/testNode节点的值
搭建了服务注册中心后,就需要搭建Dubbo-admin了,最近看了一下,dubbo的Github项目已经进行了更新,管理平台已经做了比较大的改动,而我学习的时候,平台是比较简单的,所以本dubbo-admin搭建是以旧版master的为准,不过以学习为目的的,只需要知道具体原理和操作技巧就可以
因为我搭建时候(ps:不是博客写作时间),dubbo还没做比较大改动,所以我以比较旧的版本为例子,现在新的具体参考dubbo官方的教程,本博客只是做记录
修改 src\main\resources\application.properties 指定zookeeper地址
mvn clean package -Dmaven.test.skip=true
maven打包之后,就去target里找到jar,然后cmd运行
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
运行成功之后,访问: http://127.0.0.1:7001,输入默认的账号密码root/root,登录成功
经典例子:
某个电商系统,订单服务需要调用用户服务获取某个用户的所有地址;
我们现在 需要创建两个服务模块进行测试
模块 | 功能 |
---|---|
订单服务模块 | 创建订单等 |
用户服务模块 | 查询用户地址等 |
创建工程:
建议将服务接口,服务模型,服务异常等均放在 API 包中,因为服务模型及异常也是 API 的一部分,同时,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。
创建一个API工程,将实体类和接口都放在api工程
maven新建一个shop-api-common工程:
用户地址DTO类:
package com.test.dubbo.bean;
import java.io.Serializable;
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();
}
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;
}
}
用户信息服务接口:
package com.test.dubbo.service;
import java.util.List;
import com.test.dubbo.bean.UserAddress;
/**
* 用户服务
*/
public interface UserService {
/**
* 按照用户id返回所有的收货地址
* @param userId
* @return
*/
public List getUserAddressList(String userId);
}
订单信息服务接口:
package com.test.dubbo.service;
import java.util.List;
import com.test.dubbo.bean.UserAddress;
public interface OrderService {
/**
* 初始化订单
* @param userId
*/
public List initOrder(String userId);
}
ok,创建好api工程
要实现服务提供,配置文件主要需要配置如下:
Dubbo提供者加载过程(Dubbo容器的启动):
Spring加载xml配置之后暴露服务的过程:
Exporter方法主要是打开socket的监听,接收客户的请求
ok,理解了上面的理论知识后,继续创建一个user-service-provider工程:
com.alibaba
dubbo
2.6.2
org.apache.curator
curator-framework
2.12.0
package com.test.dubbo.service.impl;
import java.util.Arrays;
import java.util.List;
import com.test.dubbo.bean.UserAddress;
import com.test.dubbo.service.UserService;
public class UserServiceImpl implements UserService {
//@Override
public List getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市昌平区", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区", "1", "王老师", "010-56253825", "N");
return Arrays.asList(address1,address2);
}
}
package com.test.dubbo;
import java.io.IOException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("provider.xml");
ioc.start();
System.in.read();
}
}
另外一种是调dubbo的Main类,启动:
dubbo的main函数:
com.alibaba.dubbo.container.Main
com.alibaba.dubbo.container.spring.SpringContainer
服务注册成功,可以去dubbo-admin看
查看服务接口的详细信息:
然后服务已经注册了,现在创建一个消费者工程order-service-comsumer
com.alibaba
dubbo
2.6.2
org.apache.curator
curator-framework
2.12.0
package com.test.dubbo.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.test.dubbo.bean.UserAddress;
import com.test.dubbo.service.OrderService;
import com.test.dubbo.service.UserService;
/**
* 让服务消费者去注册中心订阅服务提供者的服务地址
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
@Override
public List initOrder(String userId) {
System.out.println("用户id:"+userId);
//查询用户的收货地址
List addressList = userService.getUserAddressList(userId);
for (UserAddress userAddress : addressList) {
System.out.println(userAddress.getUserAddress());
}
return addressList;
}
}
package com.test.dubbo;
import java.io.IOException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.test.dubbo.service.OrderService;
public class MainApplication {
@SuppressWarnings("resource")
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("consumer.xml");
OrderService orderService = applicationContext.getBean(OrderService.class);
orderService.initOrder("1");
System.out.println("调用成功....");
System.in.read();
}
}
调用成功,console打印:
用户id:1
北京市昌平区
深圳市宝安区
调用成功....