前言
在传统的远程调用,比如RMI、HTTP协议、WebService等确实能够满足远程调用关系,但是随着用户量的倍增以及系统的复杂性增加,传统的远程调用却满足不了服务治理的需求:
- 地址维护
- 负载均衡
- 限流/容错/降级
- 监控
所以在这一部分,我们引入Dubbo来实现服务治理,我们从三方面讲Dubbo这个RPC远程调用框架。
- 解开Dubbo的神秘面纱
- Dubbo常用配置
- Dubbo源码分析(上篇)
- Dubbo源码分析(中篇)
- Dubbo源码分析(下篇)
本节我们讲第一个部分:解开Dubbo的神秘面纱
架构的发展
传统互联网架构
还记得阿里最初的项目是什么样的结构么?就是LAMP(即,Linux+Apache+MySQL+PHP),Tomcat作为web容器,放置的单体项目包括整套业务的所有逻辑,用户服务,订单服务,支付服务等~
分布式架构的演进
如今互联网比较完善的体系就是将业务分离,服务分离,数据库主从设计,读写分离。
带来哪些问题
(1)当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。
此时需要一个服务注册中心,动态的注册和发现服务,使服务的位置透明。
并通过在消费方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对 F5 硬件负载均衡器的依赖,也能减少部分成本。
(2)当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。
这时,需要自动画出应用间的依赖关系图,以帮助架构师理清理关系。
(3)服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?
为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。
其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阀值,记录此时的访问量,再以此访问量乘以机器数反推总容量。
Dubbo的架构
老生常谈,随手掏来一个经典的dubbo架构图,图示上已经写的很清楚了,一共有0~5六个部分:
- start
provider服务提供者:服务启动
- register
provider服务提供者然后注册register服务
- subscribe
Consumer服务消费者:消息订阅subscribe、
- notify
注册中心会将这些服务通过notify到消费者
- invoke
invoke这条实线按照图上的说明当然同步的意思了
服务消费者随机调用一个服务地址,失败重试另一个地址
- count
这是一个监控,图中虚线表明Consumer 和Provider通过异步的方式发送消息至Monitor。Monitor在整个架构中是可选的,Monitor功能需要单独配置,不配置或者配置以后,Monitor挂掉并不会影响服务的调用。
Dubbo 案例演示
接下来,我们简单的使用一下dubbo~
- 服务端
我这里使用server-api和server-provider作为服务端两个模块
- server-api:服务调用接口
- server-provider:服务提供者
在server-api中,就是空实现,用来调用server-provider:
接口1:
public interface IGpHello {
String sayHello(String msg);
}
接口2:
public interface IDemoService {
String protocolDemo(String msg);
}
真正的实现就是在server-provider中实现:
接口1实现:
public class GpHelloImpl implements IGpHello{
@Override
public String sayHello(String msg) {
return "Hello:"+msg;
}
}
接口2实现:
public class GpHelloImpl2 implements IGpHello{
@Override
public String sayHello(String msg) {
return "Hello,i'm server 2:"+msg;
}
}
在dubbo-server的pom文件引用dubbo的依赖,zookeeper等依赖,然后创建配置文件dubbo-server.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://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--提供方信息-->
<dubbo:application name="dubbo-server" owner="mic"/>
<!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.200.111:2181"/>
<dubbo:registry id="zk2" address="zookeeper://192.168.200.112:2181"/>
<dubbo:protocol port="20880" name="dubbo"/>
<dubbo:protocol port="8080" name="hessian"/>
<dubbo:service interface="com.test.dubbo.IGpHello"
ref="gpHelloService" protocol="dubbo,hessian" registry="zk1"/>
<dubbo:service interface="com.test.dubbo.IDemoService"
ref="demoService" protocol="hessian"/>
<bean id="gpHelloService" class="com.test.dubbo.GpHelloImpl"/>
<bean id="demoService" class="com.test.dubbo.DemoService"/>
</beans>
服务端调用就加载这个配置文件:
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context=
new ClassPathXmlApplicationContext
("META-INF/spring/dubbo-server.xml");
context.start();
System.in.read(); //阻塞当前进程
}
- 客户端
配置文件:
<?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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--提供方信息-->
<dubbo:application name="dubbo-client" owner="mic"/>
<!--注册中心-->
<dubbo:registry address="zookeeper://192.168.200.111:2181?register=false" check="false" file="d:/dubbo-server"/>
<dubbo:reference id="gpHelloService"
interface="com.test.dubbo.IGpHello"
protocol="dubbo"/>
</beans>
启动:
public static void main( String[] args ) throws IOException, InterruptedException {
ClassPathXmlApplicationContext context=new
ClassPathXmlApplicationContext
("dubbo-client.xml");
/* Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class).
getExtension("defineProtocol");
System.out.println(protocol.getDefaultPort());*/
//得到IGpHello的远程代理对象
IGpHello iGpHello = (IGpHello) context.getBean("gpHelloService");
System.out.println(iGpHello.sayHello("Mic"));
Thread.sleep(4000);
System.in.read();
}
dubbo节点分析
上面的demo中,使用dubbo通信成功,我们注意到,此时,dubbo在注册中心创建了“dubbo”的节点
关于dubbo的缓存
注意到:在客户端其实是有缓存的概念的,这样不一定就让client每次都请求到zookeeper
运行后,我们在本地磁盘找到了的缓存文件
实际上,在dubbo的源码里,通过定时任务去跑数据,更新缓存,这点我们在后面的dubbo源码分析章节会讲到
服务的启动过程
我们也可以通过dubbo提供main方法启动容器
public static void main(String[] args) throws IOException {
//默认情况下会使用spring容器来启动服务
com.alibaba.dubbo.container.Main.main(
new String[]{"spring","log4j"});
}
看看源码:
public static void main(String[] args) {
try {
if (args == null || args.length == 0) {
String config = ConfigUtils.getProperty("dubbo.container", loader.getDefaultExtensionName());
args = Constants.COMMA_SPLIT_PATTERN.split(config);
}
final List<Container> containers = new ArrayList();
for(int i = 0; i < args.length; ++i) {
containers.add(loader.getExtension(args[i]));
}
logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
if ("true".equals(System.getProperty("dubbo.shutdown.hook"))) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
Iterator i$ = containers.iterator();
while(i$.hasNext()) {
Container container = (Container)i$.next();
try {
container.stop();
Main.logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
} catch (Throwable var6) {
Main.logger.error(var6.getMessage(), var6);
}
Class var3 = Main.class;
synchronized(Main.class) {
Main.running = false;
Main.class.notify();
}
}
}
});
}
Iterator i$ = containers.iterator();
while(i$.hasNext()) {
Container container = (Container)i$.next();
container.start();
logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
}
System.out.println((new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]")).format(new Date()) + " Dubbo service server started!");
} catch (RuntimeException var7) {
var7.printStackTrace();
logger.error(var7.getMessage(), var7);
System.exit(1);
}
Class var9 = Main.class;
synchronized(Main.class) {
while(running) {
try {
Main.class.wait();
} catch (Throwable var5) {
;
}
}
}
}
说明dubbo支持多容器启动
dubbo支持多协议
- RMI
- hessian
- webservice
- http
- thirft
- Dubbo(默认)
优势:
1、不需要修改原本的服务的情况下,方便协议的迁移
2、通过增加相应协议的jar包,快速发布
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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--提供方信息-->
<dubbo:application name="dubbo-server" owner="mic"/>
<!--注册中心-->
<dubbo:registry id="zk1" address="zookeeper://192.168.200.111:2181"/>
<dubbo:registry id="zk2" address="zookeeper://192.168.200.112:2181"/>
<dubbo:protocol port="20880" name="dubbo"/>
<dubbo:protocol port="8080" name="hessian"/>
<dubbo:service interface="com.test.dubbo.IGpHello"
ref="gpHelloService" protocol="dubbo,hessian" registry="zk1"/>
<dubbo:service interface="com.test.dubbo.IDemoService"
ref="demoService" protocol="hessian"/>
<bean id="gpHelloService" class="com.test.dubbo.GpHelloImpl"/>
<bean id="demoService" class="com.test.dubbo.DemoService"/>
</beans>
dubbo基于集群访问
对dubbo-server做负载均衡
后记
本节具体代码详见:
dubbo-demo