1.分布式思维
集群与分布式
上图是由单机到集群、分布式的演变过程。
最早的javaweb雏形
特征:tomcat + servlet + jsp + mysql。一个war包打天下
项目结构:ssh/ssm三层结构。
javaweb的集群发展
特征:硬件机器的横向复制,对整个项目结构无影响。
javaweb的分布式发展
特征:将Service层单独分离出去,成为一个单独的项目jar。单独运行。Web服务器通过rpc框架,对分离出去的service进行调用。
javaweb的微服务发展
特征:从业务角度,细分业务为微服务,每一个微服务是一个完整的服务(从http请求到返回)。
在微服务内部,将需要对外提供的接口,包装成rpc接口,对外部开放。
2. RPC介绍及场景
RPC远程过程调用(Remote Procedure Call)
调用远程计算机上的服务,就像调用本地服务一样。
RPC实现的切入口
我们知道从本质上讲,某个JVM内的对象方法,是无法在该JVM外部被调用的。
如下代码:
1 | OrderService orderService = (OrderService) ctx.getBean("orderService"); |
orderService.getDetail(“1”)的这一句调用,是无法脱离本地 jvm 环境被调用的。因为外部在调用的时候,没办法得到orderService对象的,本地对orderService也不会认识。
但是,好在java中除了对象方法的调用之外,还有通过反射方式的调用:
1 | Method method = target.getClass().getMethod(methodName, argTypes); |
所以对于本地,只要外部传入了反射需要的目标对象orderService, 方法名称getDetail,和参数值“1”,就能通过反射调用并返回。所以对于外部来说,可以将上述的调用改成如下方式:
1 | Map<String,String> info = new HashMap(); |
对于本地来说,只要告诉我反射需要的信息,target/method/arg,就能调用本地的任何对象方法。
现在只要通过网络传输反射信息,就能通过网络的方式调用不同JVM的方法。网络通信的方法很多,如http/rmi/webservice等,选任何一种方式进行通信即可。
3.dubbo简介
dubbo的使命
上面已经说明了RPC的调用过程,即实现方式,其实在实际工作中,服务节点的RPC调用错综复杂,会变成如下所示的网状调用:
所以,我们除了关心RPC的过程调用之外,还需要考虑:
服务方是集群时,如何挑选一台服务器来响应客户端?
因网络抖动引起的调用失败,如何重试来弥补?
服务方机器的动态增减,如何能够让客户端及时了解并做出调整?
………….
Dubbo的使命,即解决上述围绕RPC过程的存在问题。
高并发RPC解决方案
基于TCP的RPC实现,阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,可以和Spring框架无缝集成。
Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Container: 服务运行容器。
4. dubbo的基础配置使用
1. xml配置方式
dubbo基础标签认识:
dubbo:application
1
2
3 服务提供方应用名称, 方便用于依赖跟踪
属性:name 即定义应用名称,如:
<dubbo:application name="dubbo-xml-server" />
dubbo:registry
注册中心配置。对应的配置类:
org.apache.dubbo.config.RegistryConfig
。同时如果有多个不同的注册中心,可以声明多个<dubbo:registry>
标签,并在<dubbo:service>
或<dubbo:reference>
的 registry属性指定使用的注册中心。属性:address 指定注册中心地址。如:
1 <dubbo:registry address="zookeeper://192.168.80.30:2181" />
dubbo:provider
服务提供者缺省值配置。对应的配置类:
org.apache.dubbo.config.ProviderConfig
。同时该标签为<dubbo:service>
和<dubbo:protocol>
标签的缺省值设置。
1 <dubbo:provider timeout="3000" />
dubbo:consumer
服务消费者缺省值配置。配置类:
org.apache.dubbo.config.ConsumerConfig
。同时该标签为<dubbo:reference>
标签的缺省值设置。
1 <dubbo:consumer timeout="3000"/>
dubbo:protocol
服务提供者协议配置。对应的配置类:
org.apache.dubbo.config.ProtocolConfig
。同时,如果需要支持多协议,可以声明多个<dubbo:protocol>
标签,并在<dubbo:service>
中通过protocol
属性指定使用的协议。属性:id 定义协议ID,可在
<dubbo:service>
使用该ID属性:name 协议名称,指定需要使用的协议
属性:port 指定服务的端口号
1
2 <dubbo:protocol id="d1" name="dubbo" port="20880" />
<dubbo:protocol id="d2" name="dubbo" port="20882" />
dubbo:service
服务提供者暴露服务配置。对应的配置类:
org.apache.dubbo.config.ServiceConfig
属性:interface 指定服务接口名
属性: ref 指定对象引用名称
属性: protocol 指定使用的协议
1 <dubbo:service interface="com.longshilee.service.OrderService" ref="orderService" protocol="d1" />
dubbo:reference
服务消费者引用服务配置。对应的配置类:
org.apache.dubbo.config.ReferenceConfig
属性:id 服务引用BeanId,对应service中的ref
属性:interface 指定服务接口名
1 <dubbo:reference id="orderService" interface="com.longshilee.service.OrderService" />
以上标签已经够搭建简单的dubbo服务使用了,更多详细的dubbo标签属性详解,请到官方文档上查看
XML标签方式使用实例
接下来使用xml标签的方式搭建一个简单的dubbo服务示例:
使用开发工具搭建一个maven项目,项目中有三个子项目,名称自取,我这的示例为:dubbo-api、dubbo-xml-server、dubbo-xml-client
dubbo-api 用于定义项目用到的接口,及所有其他项目用到的公共的类等
dubbo-xml-server 作为dubbo的服务提供方项目
dubbo-xml-client 作为dubbo的服务调用方项目
dubbo-api
项目结构:
只定义了两个需要的接口。
定义了一个sayHello方法,代码如下:
1 | package com.longshilee.service; |
1 | package com.longshilee.service; |
dubbo-xml-server
项目结构:
实现了api中的OrderService,实现类为OrderServiceImpl:
从代码可以看到我们使用了spring的@service注解,因为dubbo跟spring是无缝链接的,所以我们在使用dubbo的时候会跟spring一起用,所以我们在pom里导入了spring的相关的包。
同时我们也引入了dubbo-api,因为我们需要使用api中的OrderService.如下所示:
pom.xml
1 |
|
同时作为服务提供方,我们需要使用xml的方式配置服务信息,在resources目录下新建dubbo-server.xml:
1 | <?xml version="1.0" encoding="UTF-8"?> |
这样我们就把orderService服务注册到了注册中心zookeeper中,供其他调用方调用。
然后我们需要一个启动类将服务启动起来,所以定义了一个简单的启动类:
1 | package com.longshilee.bootstrap; |
然后运行,就能看到服务启动起来了:
1 | dubbo 服务启动成功 |
dubbo-xml-client
项目结构:
实现了api中的UserService接口,实现类为:UserServiceImpl
可以看到代码中,使用@Resource注解将orderService直接引入了进来,理论上来说,两个完全不同的项目,想通过这样子直接定义是没办法调用的,但通过dubbo,通过注册中心,就能够实现这样子的直接调用。
跟dubbo-xml-server项目一样,我们需要在pom中引入spring和dubbo的包。详见server的pom.
然后作为dubbo服务调用者,我们需要通过xml的方式,定义这是一个dubbo调用方,所以一样的道理,在resources目录下,我们新建dubbo-client.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
通过<dubbo:reference/>
标签我们定义了需要调用的服务,orderService服务。
那么这里有一个问题,我们在client项目中通过@Resource注解直接将orderService实例从springIOC容器中取出来,那这个对象是怎么来的呢,因为我们根本没有通过spring的<bean/>
标签来定义这个对象。刚才已经说了,dubbo跟spring是无缝链接的,所以dubbo底层已经将该实例加入到了springIOC中。
现在我们需要一个启动类,启动dubbo客户端:
1 | package com.longshilee.bootstrap; |
然后运行,我们就可以看到已经调用成功了,控制台打印:
1 | longshilee hello |
至此,我们的dubbo服务通过xml方式已经搭建完成了。
注解的方式使用dubbo实例
注解方式底层与xml方式一致,只是表现形式不同。目标都是将dubbo的基本配置信息注入,主要涉及到的必不可少的信息为:ApplicationConfig
,ProtocolConfig
,RegistryConfig
,service
,reference
.
为了方便,我们还是使用上面的项目工程,在dubbo-xml-server和dubbo-xml-client进行改造。
dubbo-xml-server
新建config目录,在config目录下新建DubboConfig配置类,用于配置dubbo服务端基础信息。使用
@EnableDubbo
开启注解 Dubbo 功能,其中可以加入 scanBasePackages 属性配置包扫描的路径,用于扫描并注册 bean。代码内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41package com.longshilee.config;
import com.alibaba.dubbo.config.*;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
"com.longshilee.service"}) (scanBasePackages = {
public class DubboConfig {
ApplicationConfig applicationConfig(){
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-xml-server");
return applicationConfig;
}
ProviderConfig providerConfig(){
ProviderConfig providerConfig = new ProviderConfig();
providerConfig.setTimeout(3000);
return providerConfig;
}
RegistryConfig registryConfig(){
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://192.168.80.30:2181");
return registryConfig;
}
ProtocolConfig protocolConfig(){
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setId("d1");
protocolConfig.setName("dubbo");
protocolConfig.setPort(20880);
return protocolConfig;
}
}可以看到基本与xml的标签配置信息一致,这不过上述是用代码方式配置而已。
将OrderServiceImpl上的注解
@Service
,由spring的注解换成dubbo的@Service
注解。
修改服务端的启动类。因为是使用注解方式,所以我们spring的启动类也要改成使用注解的方式启动。
1
2
3
4
5
6
7
8
9
10public class Boot {
public static void main(String[] args) throws IOException {
// ClassPathXmlApplicationContext xml = new ClassPathXmlApplicationContext("classpath:dubbo-server.xml");
// xml.start();
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(DubboConfig.class);
app.start();
System.out.println("dubbo 服务启动成功");
System.in.read();
}
}使用AnnotationConfigApplicationContext类启动,将配置类DubboConfig引入进来即可。
服务端就修改完成了,接下来修改客户端。
dubbo-xml-client
跟服务端流程基本一样。
新建config目录,在config目录下新建DubboConfig配置类,基本配置也是更xml方式一样,配置消费者端的基本信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41package com.longshilee.config;
import com.alibaba.dubbo.config.*;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
"com.longshilee.service") (scanBasePackages =
public class DubboConfig {
ApplicationConfig applicationConfig(){
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-xml-client");
return applicationConfig;
}
RegistryConfig registryConfig(){
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://192.168.80.30:2181");
return registryConfig;
}
ConsumerConfig consumerConfig(){
ConsumerConfig consumerConfig = new ConsumerConfig();
consumerConfig.setTimeout(3000);
return consumerConfig;
}
ProtocolConfig protocolConfig(){
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setId("d2");
protocolConfig.setName("dubbo");
protocolConfig.setPort(20882);
return protocolConfig;
}
}调用OrderService时,由原来使用的注解
@Resource
改成dubbo的@Reference
,来完成Bean的自动注入。
修改启动类。
1
2
3
4
5
6
7
8
9
10public class Boot {
public static void main(String[] args) {
// ClassPathXmlApplicationContext xml = new ClassPathXmlApplicationContext("classpath:dubbo-client.xml");
//// xml.start();
// UserService userService = (UserService) xml.getBean("userService");
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(DubboConfig.class);
app.start();
UserService userService = (UserService)app.getBean(UserService.class);
System.out.println(userService.sayHello());
}启动完成之后,我们就能看到控制台打印的调用信息,说明已经调用成功了。
1
longshilee hello
当然注解方式处理使用代码来设置配置信息之外,也可以使用属性文件来设置配置信息。
属性文件方式
在项目的resources目录下新建属性配置文件。如:
在dubbo-xml-server的resources下新建dubbo-server.properties配置文件。内容如下:
1 | dubbo.application.name=dubbo-xml-server |
然后修改原来的DubboConfig.java配置类,使用@PropertySource
注解将属性文件引入进来。然后将原来的信息先注释掉。
1 |
|
一样的道理在dubbo-xml-client项目的resources目录下新建dubbo-client.properties属性文件。
1 | dubbo.application.name=dubbo-xml-client |
一样的在原来的DubboConfig配置类中将属性文件引入进来:
1 |
|
这样,以属性文件的方式配置dubbo信息就完成了。然后运行,结果是跟之前一样的。