9.1 Dubbo基本原理 9.2 Dubbo相關問題 9.3 Dubbo應用及簡單源碼實現 9.4 Dubbo SPI基本原理
9.1 Dubbo基本原理
簡單說說RPC
遠程調用計算機通訊協議,比如HTTP/TCP等都是
簡單就是序列化數據發送,對方解序列化數據業務處理,在序列化響應信息返回發送
傳統的演變下Dubbo功能
用Nginx配置IP地址進行負載均衡,但是人工成本巨大
Dubbo提供了所有服務端相關信息註冊到註冊中心,一切自動化負載均衡,開發者只需要關心業務
Dubbo提供
服務開發(RPC應用開發)
服務軟負載均衡
服務依賴管理
服務監控
服務治理
Dubbo整體架構
註冊中心,服務提供者(容器),服務消費者,監控中心
註冊中心
消費者和提供者只在啓動時和註冊中心交互,負責保存服務名與服務地址映射,服務地址變動會主動通知服務消費者。
服務提供者掛了,註冊中心通過長連接感知並立刻服務消費者。註冊中心和監控中心掛了,不影響已經運行的提供者和消費者,消費者有本地緩存。但是提供者掛了,消費者則無法再用會無限等待
服務提供者(容器)
1 提供服務接口API
2 完成實現類
3 註冊服務(遠程和本地註冊)
4 暴露服務(啓動tomcat)
服務消費者
啓動時從註冊中心拿服務地址並本地緩存
負載均衡選出一個服務地址進行服務調用
監控中心
統計服務調用次數和調用時間,內存彙總每1分鐘發一次到監控中心。除監控中心外,其餘都是長連接
Dubbo調用工作原理
1 Proxy工廠 invoke(Dubbo還是Tomcat)
2 到客服端
3 到傳輸層下(header和body)
header是codec編解碼,一般是字符串形式,請求的相關源信息
body序列化的數據
4 到服務端
5 Dispather請求分發
6 線程池處理
7 執行業務
Dubbo下Zookeeper註冊中心原理
9.2 Dubbo相關問題
1 一般使用什麼註冊中心,還有別的選擇嗎
可配置Multicast和Zookeeper
1 Multicast 廣播方法
使用單播發送提供者信息給消費者,爲了減少廣播量。所以想多個消費者同時拿到同一個提供者,消費者需要配置unicast=false在multicast後面
工作原理
提供方啓動時廣播自己地址
消費者啓動時廣播訂閱請求
互相交互
特點
不用啓動中心節點,只用組播地址,組播有網絡結構限制,只適合小規模
2 Zookeeper
curator客戶端,可集羣,xml內配置多個地址
3 也可以直接配置提供者地址,直連提供者
2
核心配置有哪些-xml內配置信息
服務調用是阻塞的嗎-默認是
Dubbo推薦使用什麼協議
推薦Dubbo協議,Netty實現。也有hessian,http等協議
如何解決服務調用鏈過長問題
默認使用的什麼通信框架,還有別的選擇嗎
解釋了這些問題
Dubbo Monitor實現原理
在調用消費者和提供者之前都會先走filter鏈,filter鏈中有Monitorfilter
Dubbo支持分佈式事務嗎
Dubbo自己不提供,但是允許整合支持分佈式事務。比如加入我上一章寫的seata來解決分佈式事務
9.3 Dubbo應用及簡單源碼實現
應用
啓動Dubbo三種方式
1
~ applicationContext = ~ ApplicationContext("spring/dubbo-provider.xml");
applicationContext.start();
2 註解啓動
@EnableDubbo(scanBasePackages="xxx.Provider")//掃描這些包下實現類
@PropertySource("classpath:xxx")//和xml一樣配置內容
~ applicationContext = ~ ApplicationContext("AnnoationProviderConfiguration.class");
applicationContext.start();
3 API啓動
提供者xml配置內容
<dubbo:application name="demo-provider"/>
<dubbo:registry address="zookeeper://127.0.0.1/"> //這裏也可以配置廣播模式multicast
<dubbo:protocol name="dubbo"/> //使用dubbo協議,也可以用hessian,http等
<bean id="demoService" class="xxx.DemoServiceImpl"/> //提供實現類
<dubbo:service interface="xxx.DemoService" ref="demoService"/> //體現類對應的接口,本地註冊並且暴露服務給給消費者
消費者xml配置內容
<dubbo:application name="demo-consumer"/>
<dubbo:registry address="zookeeper://xxx.xxx.xxx.xxx/"> //這裏也可以配置廣播模式multicast
<bean reference id="demoService" check="false"/> //提供實現類
<dubbo:interface="xxx.DemoService"/>
等
消費者直接正常調用應用
簡單源碼實現
這裏主要幾乎http協議的tomcat實現, 代理工廠模式轉變netty或者tomcat,netty具體實現換湯不換藥
提供者簡單實現
1 提供和實現API 即實現HelloService接口
2 暴露接口
public class Provider{
~ main ~{
1 本地註冊(服務名,實現類)
LocalRegister.register(HelloService.class.getName(),HelloServiceImpl.class);
2 遠程註冊,如上傳到Zookeeper
URL url = new URL("localhost",8080);//機器地址可能集羣,所以用list
List<URL> RemoteMapRegister.register(HelloService.class.getName(),url);
3 按照我們自己的實現類,啓動tomcat,也可以用netty啓動。基於xml所選擇協議
//HttpServer httpServer = new ~;
//httpServer.start("localhost",8080);
Protocol protocol = ProtocolFactory.getProtocol();
protocol.start(url);
}
}
//要序列化URL,封裝地址
public class URL implements Serializable{
String hostname;
Integer port;
構造函數
}
//按照我們自己需要的方法啓動tomcat
public class HttpServer{
//使用maven依賴加入的tomcat
Tomcat tomcat = new Tomcat();
//根據Tomcat層級依次構造,給tomcat設置connector/engine/host/context/wrapper
...
//設置我們自己實現的servlet
tomcat.addServlet(contextPath,"dispatcher",new DispatcherServlet());//我們自己實現的DispatcherServlet
//配置結束,啓動tomcat
tomcat.start();
tomcat.getServer().await();
}
//實現自己需要的servlet
public class DispatcherServlet extends HttpServlet{
@Override
~ service(HttpServletRequest request, HttpServletResponse response){
//返回自己實現的servlet handler
~ new HttpServerHandler().handler(request,response);
}
}
//實現自己需要的servlet handler
public class HttpServerHandler{
~ handler(HttpServletRequest req, HttpServletResponse resp){
//處理請求,返回結果
//從本地註冊表中拿到class實現類,利用反射返回相應結果給消費者
InputSteam inputStream = req.getInputStream();
Object ois = ObjectInputStream(inputStream);//接受的數據轉成object
Invocation invocation = ois.readObject();//封裝的調用對象參數類,在下面實現了
Class implClass = LocalRegister.get(invocation.getInterfaceName());//拿到需要接口名稱
Method method = implClass.getMethod(invocation.getMethodName,invocation.getMethodTypes);
String result = (String) Method.invoke(implClass.newInstance,invocation.getParams());
IOUtils.write(result,resp.getOutputStream()); //返回httpClient
}
}
//本地註冊表
public class LocalRegister{
~ Map<String,Class> map = new ~;
~ void register ~ -> map.put(interfaceName,implClass);
~ Class get(String interfaceName);
}
//遠程註冊表
//在這裏用map,消費者和提供者不同進程下map數據會不一致。Dubbo則可使用Zookeeper解決不同機器下,類似map的數據一致性
public class RemoteMapRegister{
map<String,List<URL>> map = new ~;
register(String interfaceName,URL url)
URL random(String interfaceName) //利用接口名,map.get(interfaceName)後負載均衡隨機算法從list集羣中拿一個url
}
消費者簡單實現
public class Consumer{
~ main ~{
//HttpClient httpClient = new ~;
//Invocation invocation = new ~(HelloService.class.getName(),"sayHello",new class[](String.class));
//String result = httpClient.send("localhost",8080,invocation);
Protocol protocol = ProtocolFactory.getProtocol();//代理工廠,netty則返回netty處理對象,http就是HttpClient
HelloService helloService = Proxy.getProxy(HelloService.class);
}
}
public class HttpClient{
public String send(String hostName, Integer port,Invocation invocation){
URL url = new URL("http",hostName,port,"/");
...
HttpURLConnection.setRequestMethod("POST");
HttpURLConnection.setDoOutput(true);
HttpURLConnection.getOutputStream();轉成object給oos
oos.flush();//發送http封裝的對象給提供者,等待提供者HttpServerHandler處理請求
oos.close();
//接受HttpServerHandler的response
InputSteam inputStream = HttpURLConnection.getInputStream();
String result = IOUtils.toString(inputStream);
return resutl;
}
}
//封裝調用對象參數
public class Invocation implement Serializable{
String interfaceName;
String methodName;
Class[] paramTypes;
Object[] params;
}
public class ProxyFactory{
public static<T> getProxy(Class interfaceClass){
Proxy.newProxyInstance(xxx) 返回一個代理類
@Override
invoke(){
1 從Method.getMethod中拿sayHello,new Class[]這些參數,不寫死
2 URL = 利用RemoteMapResiger.random(interfaceClass.getName()),負載均衡拿port
3 利用這些參數調用
HttpClient httpClient = new ~;
Invocation invocation = new ~(HelloService.class.getName(),"sayHello",new class[](String.class));
String result = httpClient.send("localhost",8080,invocation);
}
}
}
在netty中也經常用自定義協議+編解碼器,來處理粘包拆包問題
Duboo和Http協議不用手動改代碼,直接裝配
public interface Protocol{
void start(URL url);
String send(URL url, Invocation invocation);
}
public class DubboProtocol implement Protocol{
@Override
~ start ~{
new NettyServer().start(url.getHostName(),url.getPort());
}
@Override
~ send ~{
return new NettyClient<>().sned(url.getHostName(),url.getPort(),invocation)
}
}
public class HttpProtocol implement Protocol{
一樣
}
public class ProtocolFactory{
public static Protocol getProtocol(URL url){
工廠模式拿new HttpProtocol, new DubboProtocol
}
}
總結
說那麼一大串,簡單來說
1 提供者本地註冊,遠程註冊。
2 消費者從遠程註冊中找提供者
3 兩者之間實現自定義的傳遞類對象+序列化解序列化,達成交互
9.4 Dubbo SPI基本原理
Java SPI擴展機制
resources下META-INF.services下
創建framework.Protocol,
寫protocol.dubbo.DubboProtocol則用dubbo Netty
寫Http則用Http Tomcat
Dubbo SPI擴展機制
加入依賴注入,AOP等,性能也比JAVA SPI更好
Java SPI中無法寫多個自己選擇其中一個,Dubbo可以多個命名變量,通過變量名選擇自己需要那個
如Dubbo:Protocol.dubbo.Dubbo.Protocol 加了Dubbo這個key
Dubbo SP核心
AOP = wrapper包類,重寫方法添加before after,包裝類配置到META-INF下
IOC = 如@Autowired注入接口,URL封裝注入的實現類,URL參數制定哪個實現類
Dubbo SPI
@SPI //調用Dubbo SPI
pubLic interface Car{
@Adaptive(value="carType") //從url參數獲取key carType
public void getColor(URL url);
}
public class CarDemo{
~ main ~{
~ e = ExtensionLoader.getExtensionLoader(Car.class) //對應Java的ServiceLoader
Car redCar = e.getExtension("red");
//URL參數注入IOC
Map<String,String> map = new ~
map.put("carType","black");
URL url = new URL("","",0,map);
driver.driveCar(url);
}
}
META-INF.services 文件配置
red:xxx.impl.RedCar
black:xxx.impl.BlackCar
ExtensionLoader.java 大致源碼
一個map工廠,解析文件,加載文件內對應key和實現類的關係map
1 getExtensionLoader(Car.class) 返回 ObjectFactory 實現類/代理類
2 用雙Null+鎖校驗生成Car接口單例對象
3 WETA-INF.service 路徑下找實現類
4 檢驗類是否合法
5 cacheAdaptiveClass(clazz) 看是否使用IOC
6 cacheWrapperClass(clazz) 看是否使用AOP
7 處理依賴注入IOC
8 處理AOP
9 包裝類嵌套實例生成