實驗?zāi)康?/h2>
- 利用RMI技術(shù)構(gòu)建遠程會話:2個客戶端發(fā)送消息到服務(wù)器端妥曲,服務(wù)器端把相應(yīng)的消息中轉(zhuǎn)發(fā)送給對應(yīng)的客戶贾费,進行遠程對話钦购。
- 使用Dubbo框架實現(xiàn)此功能。
RMI實現(xiàn)
1.定義此服務(wù)的接口
public interface Message extends java.rmi.Remote{
public List<String> getMessageById(int id) throws RemoteException;
public void sendMessageById(String mess,int sourse,int destination) throws RemoteException;
}
2.定義服務(wù)器端的接口實現(xiàn)
public class MessageImpl implements Message,Serializable{
//用一個hash表存取用戶的信息褂萧,key即用戶的id
Map<Integer, List<String>> message=new HashMap<>();
//獲取指定用戶的信息
public List<String> getMessageById(int id) throws RemoteException
{
return message.get(id);
}
//某用戶給指定用戶發(fā)送消息
public void sendMessageById(String mess,int sourse,int destination) throws RemoteException
{
if(message.get(destination)!=null)
{
List<String> old=message.get(destination);
old.add(mess);
}
else
{
List<String> messageList=new ArrayList<>();
messageList.add(mess);
message.put(destination, messageList);
}
}
}
3.構(gòu)建服務(wù)器:將服務(wù)綁定到指定端口(也可隨機)并發(fā)布押桃,同時開一個注冊表的端口,將服務(wù)添加到注冊表中导犹。
疑問:為啥服務(wù)端口可以和注冊端口一樣而不報錯唱凯?
public class Server {
public static void main(String[] args) throws RemoteException, AlreadyBoundException {
MessageImpl message=new MessageImpl();
Message stub=(Message) UnicastRemoteObject.exportObject(message, 3333);//將服務(wù)綁定到端口3333上
Registry registry=LocateRegistry.createRegistry(3333);//用端口3333開一個注冊表
registry.bind("Message", stub);//將服務(wù)添加到注冊表
System.out.println("bind success! listen on port 3333....");
//查看注冊表中的服務(wù)
for(int i=0;i<registry.list().length;i++)
{
System.out.println(registry.list()[i]);
}
}
}
UnicastRemoteObject.exportObject(message, 3333)這個步驟很關(guān)鍵,它將此服務(wù)發(fā)布到3333端口上谎痢。只有通過這一步磕昼,才會生成遠程對象的存根,如果省去這一步直接把MessageImpl對象綁定到注冊表上节猿,當(dāng)客戶端調(diào)用此服務(wù)時得到的將不會是服務(wù)器端對象的存根票从,二是根據(jù)傳來的類信息在客戶端本地創(chuàng)建一個MessageImpl對象.如:
客戶端代碼:
String url="rmi://localhost:3333/Message";
Message stub=(Message) Naming.lookup(url);
System.out.println(stub.getClass());
服務(wù)器端不省去UnicastRemoteObject.exportObject(message, 3333)時漫雕,客戶端輸出:
class com.sun.proxy.$Proxy0
$Proxy0即為存根對象的類型
服務(wù)器端改寫成
MessageImpl message=new MessageImpl();
//Message stub=(Message) UnicastRemoteObject.exportObject(message, 3333);//將服務(wù)綁定到端口3333上
Registry registry=LocateRegistry.createRegistry(3333);//用端口3333開一個注冊表
registry.bind("Message", message);//將服務(wù)添加到注冊表
客戶端輸出:
4.編寫客戶端程序:定義兩個客戶,id為1,2峰鄙,分別隔1s,2s給對方發(fā)消息并查看自己的新消息浸间。
(只貼出client1的代碼)
public class Client1 {
public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException {
int index=0;
String url="rmi://localhost:3333/Message";
Message stub=(Message) Naming.lookup(url);
while(true)
{
List<String> messageList=stub.getMessageById(1);
if(messageList!=null)
{
while(index<messageList.size())
{
System.out.println(messageList.get(index));
index++;
}
}
stub.sendMessageById("hello,client2!", 1, 2);
try{
Thread.sleep(1000);
}
catch(Exception e){
System.exit(0);//退出程序
}
}
}
}
5.運行服務(wù)器后,同時運行客戶端1,2
注意:
服務(wù)端需要實現(xiàn)遠程接口吟榴,因為服務(wù)端需要具體的遠程對象綁定相應(yīng)的服務(wù)魁蒜。但是服務(wù)所提供的功能可以在客戶端實現(xiàn),而對于服務(wù)器卻是透明的吩翻。具體如下:
服務(wù)器提供功能的模板接口TFunction兜看,并將其作為遠程對象方法Compute的參數(shù),Compute進行TFunction方法的調(diào)用狭瞎。
客戶端用相應(yīng)的類Function實現(xiàn)接口TFunction铣减,同時實現(xiàn)Serializable接口。
基于對象串行化技術(shù)脚作,客戶端將實例Function1作為參數(shù)傳入葫哗,調(diào)用遠程對象的方法Compute武花。
這樣就使得客戶端將計算的類傳給服務(wù)器端河闰,讓服務(wù)端調(diào)用,實現(xiàn)了簡單的云計算服務(wù)押搪。
(參考o(jì)racle官網(wǎng)實例:https://docs.oracle.com/javase/tutorial/rmi/client.html)亿扁。
Dubbo框架實現(xiàn)
Dubbo架構(gòu)
Dubbo注冊中心
對于服務(wù)提供方捺典,它需要發(fā)布服務(wù),而且由于應(yīng)用系統(tǒng)的復(fù)雜性从祝,服務(wù)的數(shù)量襟己、類型也不斷膨脹;
對于服務(wù)消費方牍陌,它最關(guān)心如何獲取到它所需要的服務(wù)擎浴,而面對復(fù)雜的應(yīng)用系統(tǒng),需要管理大量的服務(wù)調(diào)用毒涧。
而且贮预,對于服務(wù)提供方和服務(wù)消費方來說,他們還有可能兼具這兩種角色契讲,即既需要提供服務(wù)仿吞,有需要消費服務(wù)。
通過將服務(wù)統(tǒng)一管理起來捡偏,可以有效地優(yōu)化內(nèi)部應(yīng)用對服務(wù)發(fā)布/使用的流程和管理唤冈。服務(wù)注冊中心可以通過特定協(xié)議來完成服務(wù)對外的統(tǒng)一
Dubbo提供的注冊中心有如下幾種類型可供選擇:
- Multicast注冊中心
- Zookeeper注冊中心
- Redis注冊中心
- Simple注冊中心
本例采用Zookeeper注冊中心,為什么采用Zookeeper?
Zookeeper是一個分布式的服務(wù)框架,是樹型的目錄服務(wù)的數(shù)據(jù)存儲银伟,能做到集群管理數(shù)據(jù) 你虹,這里能很好的作為Dubbo服務(wù)的注冊中心凉当。
Dubbo能與Zookeeper做到集群部署,當(dāng)提供者出現(xiàn)斷電等異常停機時售葡,Zookeeper注冊中心能自動刪除提供者信息看杭,當(dāng)提供者重啟時,能自動恢復(fù)注冊數(shù)據(jù)挟伙,以及訂閱請求
安裝完成后楼雹,進入到bin目錄,并且啟動zkServer.cmd尖阔,這個腳本中會啟動一個java進程:
(注:需要先啟動zookeeper后贮缅,后續(xù)dubbo demo代碼運行才能使用zookeeper注冊中心的功能)
Dubbo優(yōu)勢
- 透明化的遠程方法調(diào)用
像調(diào)用本地方法一樣調(diào)用遠程方法;只需簡單配置介却,沒有任何API侵入谴供。 - 軟負載均衡及容錯機制
可在內(nèi)網(wǎng)替代nginx lvs等硬件負載均衡器。 - 服務(wù)注冊中心自動注冊 & 配置管理
不需要寫死服務(wù)提供者地址齿坷,注冊中心基于接口名自動查詢提供者ip桂肌。
使用類似zookeeper等分布式協(xié)調(diào)服務(wù)作為服務(wù)注冊中心,可以將絕大部分項目配置移入zookeeper集群永淌。 - 服務(wù)接口監(jiān)控與治理
實現(xiàn)流程
1.創(chuàng)建dubbo-api的MAVEN項目(有獨立的pom.xml崎场,用來打包供提供者消費者使用)。
public interface DemoService {
List<String> getMessageById(int id);
void sendMessageById(String mess,int sourse,int destination);
}
2.創(chuàng)建dubbo-provider的MAVEN項目(有獨立的pom.xml遂蛀,用來打包供消費者使用)谭跨。
實現(xiàn)服務(wù)接口
public class DemoServiceImpl implements DemoService {
Map<Integer, List<String>> message=new HashMap<>();
public List<String> getMessageById(int id)
{
return message.get(id);
}
public void sendMessageById(String mess,int sourse,int destination)
{
if(message.get(destination)!=null)
{
List<String> old=message.get(destination);
old.add(mess);
}
else
{
List<String> messageList=new ArrayList<>();
messageList.add(mess);
message.put(destination, messageList);
}
}
}
配置聲明暴露服務(wù),將服務(wù)注冊到Zookeeper上
<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">
<!--定義了提供方應(yīng)用信息李滴,用于計算依賴關(guān)系螃宙;在 dubbo-admin 或 dubbo-monitor 會顯示這個名字,方便辨識-->
<dubbo:application name="demotest-provider" owner="programmer" organization="dubbox"/>
<!--使用 zookeeper 注冊中心暴露服務(wù)所坯,注意要先開啟 zookeeper-->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!-- 用dubbo協(xié)議在20880端口暴露服務(wù) -->
<dubbo:protocol name="dubbo" port="20880" />
<!--使用 dubbo 協(xié)議實現(xiàn)定義好的 api.PermissionService 接口-->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" protocol="dubbo" />
<!--具體實現(xiàn)該接口的 bean-->
<bean id="demoService" class="com.alibaba.dubbo.demo.impl.DemoServiceImpl"/>
</beans>
provider開啟服務(wù)
public class Provider {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
System.out.println(context.getDisplayName() + ": here");
context.start();
System.out.println("服務(wù)已經(jīng)啟動...");
System.in.read();
}
}
3.創(chuàng)建dubbo-consumer的MAVEN項目
通過Spring配置引用遠程服務(wù)
<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="demotest-consumer" owner="programmer" organization="dubbox"/>
<!--向 zookeeper 訂閱 provider 的地址谆扎,由 zookeeper 定時推送-->
<dubbo:registry address="zookeeper://localhost:2181"/>
<dubbo:reference id="messageService" interface="com.alibaba.dubbo.demo.DemoService"/>
</beans>
啟動Consumer,調(diào)用遠程服務(wù)
public class Consumer {
public static void main(String[] args) {
//測試常規(guī)服務(wù)
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("consumer.xml");
context.start();
System.out.println("consumer start");
DemoService demoService = context.getBean(DemoService.class);
System.out.println("consumer");
int index=0;
while(true)
{
List<String> messageList=demoService.getMessageById(1);
if(messageList!=null)
{
while(index<messageList.size())
{
System.out.println(messageList.get(index));
index++;
}
}
demoService.sendMessageById("hello,client2!", 1, 2);
try{
Thread.sleep(1000);
}
catch(Exception e){
System.exit(0);//退出程序
}
}
}
}
4.同時運行consumer1,consumer2
參考博文https://blog.csdn.net/noaman_wgs/article/details/70214612