前言
在傳統的遠程調用,比如RMI、HTTP協議、WebService等確實能夠滿足遠程調用關係,但是隨着用戶量的倍增以及系統的複雜性增加,傳統的遠程調用卻滿足不了服務治理的需求:
- 地址維護
- 負載均衡
- 限流/容錯/降級
- 監控
所以在這一部分,我們引入Dubbo來實現服務治理,我們從三方面講Dubbo這個RPC遠程調用框架。
- 解開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