1 引包
不用spring的包,用dubbo原生的包,添加包完成之后,刷新maven工程
1.1 dubbo的包
com.alibaba
dubbo
2.6.5
org.springframework
spring
1.2 zookeeper的包
com.101tec
zkclient
0.10
org.slf4j
slf4j-api
org.slf4j
slf4j-log4j12
log4j
log4j
1 dubbo是什么?
是一个分布式的服务框架——>微服务
2 dubbo能做什么
2.1 透明化的远程方法调用,调用远程方法就跟调用本地方法一样
2.2 软负载均衡及容错机制,服务集群,动态增加、减少服务器
2.3 服务自动注册与发现
3 dubbo与http的区别
3.1 http好比普通话,rpc好比团伙内部黑话
讲普通话的好处就是谁都听得懂,谁都会讲
讲黑话好处是更精简、保密、可定制;坏处就是要求client也要懂,并且大家都说一种黑话了,再换就困难了
4 为什么要用rpc
4.1 因为良好的rpc是面向服务的封装,针对服务的可用性和效率都做了优化,单纯使用http调用则缺少这些特性
4.2 http可以过防火墙,可以利用各种丰富的分析工具,http是文本协议,适合短连接,http跨网络易扩展
4.3 dubbo通讯层用的netty走socket,socket是二进制协议,安全性更高,数据有效率更高
5 socket在http的下一层
6 dubbo的架构
第一层:
Registry 注册中心
第二层:
Consumer 客户端
Provider 服务提供者
Container 容器
第三层:
monitor 监控
6.1 服务提供者Provider发布一个服务后会把这个服务注册到注册中心Registry
可以在注册中心看有哪些服务(就是一个服务管理的地方)
服务行右侧的no Consumer就是还没有客户端连接到该服务上
6.2 同样一个Consumer想要消费(调用一个服务的时候),该Consumer也会注册到注册中心上,并且之间是长连接的关系
7 dubbo的配置
dubbo是采用全spring配置方式,对应用没有任何api的侵入,只需要加载dubbo的配置即可(就是引包即可),dubbo基于spring的schema扩展进行加载
7.1 schema是.xml文件中最上面的那部分,如下图:xml文件中能使用哪些标签都是通过下图中的schema定义的
8 举例
房产拥有者 = 服务提供者(因为他有房源)
中介 = 注册中心
买房的人 = 客户端(服务的消费方)
房产拥有者将房子挂在中介
买房的人也是去中介找房子
中介将房产拥有者与买房的人联系起来
9 注册中心
注册中心是zookeeper,也是一个中间件,用来注册服务于负载均衡的
zookeeper通过心跳机制可以检测宕机的(挂掉的、已经停止运行的)服务器,并将挂掉的服务器IP与服务对应关系从列表中删除
10 dubbo的调用
10.1 集成spring(研发人员调用dubbo接口都是采用集合spring的调用方式)
10.2 generic泛化调用
10.3 Telnet调用
作为服务的发布者都是采用第一种方式发布
作为服务的消费者可以用3种方式调用
dubbo比较难的是架构方面,不是如何调用
第一步:引jar包
提供接口应该提供什么
提供一个SDKclient给调用的人,这个SDKclient相当于一个jar包,将该SDKclient放入公司的伺服机地址,就是maven仓库
调用的人应该怎么做
要想调用别人的这个服务,就把这个jar包添加到pom文件中,与服务提供者确定好调用什么接口,以及注册中心和组(这些在注册中心中是能找到的)
第二步:创建一个xml文件
在resources文件下创建一个MyConsumer.xml文件
调用字符串参数的服务(类、接口)
Http请求是用IP+端口+路径确定唯一
dubbo是用interface+group+version确定坐标,确定唯一
创建一个测试类,启动容器,调用该bean中的方法
import com.longteng.service.DubboTestService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class MySpringDubbo {
ApplicationContext context;
@BeforeClass
public void before(){
context=new ClassPathXmlApplicationContext("MyConsumer.xml");
}
@Test
public void dubboTest(){
//这个bean的名称就是xml中bean的id
DubboTestService dubboTestService=(DubboTestService)context.getBean("MydubboTest");
//调用该服务的sayHello方法
String s=dubboTestService.sayHello("你好dubbo");
System.out.println(s);
}
}
调用对象参数的服务
import com.alibaba.fastjson.JSON;
import com.longteng.domain.ResponseMessage;
import com.longteng.domain.User;
import com.longteng.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class MySpringDubbo {
ApplicationContext context;
@BeforeClass
public void before(){
context=new ClassPathXmlApplicationContext("MyConsumer.xml");
}
@Test
public void dubboUserTest(){
UserService userService=(UserService) context.getBean("MydubboUser");
//该User类是发布服务中决定的,不是自己工程里的User类,就是说这里的传参是发布服务者决定的
com.longteng.domain.User user=new User();
user.setId(1);
ResponseMessage responseMessage=userService.getUser(user);
//对象转json
System.out.println(JSON.toJSONString(responseMessage));
}
}
使用泛化调用,研发要做反序列化处理,很影响性能,但是作为测试调用几个是没有关系的,属于少量的调用
spring调用引入jar包的方式有局限性,所以需要泛化调用;泛化调用时不用xml文件的,不用引入依赖的,注释掉因为spring调用引入的包
1 将引入的包注释,因为泛化调用不需要引包
2 泛化调用要先知道参数类型,如果不知道的话就可以用第3中调用方式获取,接口名、参数列表、参数类型
在测试类里封装一个方法,将xml里的配置移植到该代码,与xml里的配置是相同,只是用代码体现了,xml里的配置是不灵活的,在服务启动的时候就配读取加载了,写在代码里,动态性较强
/**
* GenericService是import com.alibaba.dubbo.rpc.service.GenericService;包里的,别引错包
* GenericService:通用的服务,是一个代理接口,用于代理请求的接口,就是说要调用哪个接口是不知道的
* 用GenericService来代理请求的接口类
* 参数1:接口名
* 参数2:组
* 参数3:版本
*/
public GenericService dubboClient(String interfaceName,String group,String version){
/**
* ApplicationConfig是import com.alibaba.dubbo.config.ApplicationConfig;的包,别引错包
* setName:自定义应用名
* RegistryConfig是import com.alibaba.dubbo.config.RegistryConfig;的包,别引错包
* setAddress就是连接到注册中
*/
ApplicationConfig applicationConfig=new ApplicationConfig();
applicationConfig.setName("dubboConsumer");
RegistryConfig registryConfig=new RegistryConfig();
//设置接口信息,配置一个泛型,预期是代理类型GenericService
ReferenceConfig referenceConfig =new ReferenceConfig();
//配置自定义的应用中心名称实例
referenceConfig.setApplication(applicationConfig);
//配置连接自定义应用中心的地址实例
referenceConfig.setRegistry(registryConfig);
//添加接口名称,参数为本方法的参数1
referenceConfig.setInterface(interfaceName);
//声明为泛化调用
referenceConfig.setGeneric(true);
//添加组,本方法的参数2
referenceConfig.setGroup(group);
//添加版本,本方法的参数3
referenceConfig.setVersion(version);
//这个不是必须的,因为阿里云服务器变IP的原因
referenceConfig.setUrl("dubbo://47.105.55.243:20880");
//相当于(GenericService)context.getBean(referenceConfig);就相当于getBean
return referenceConfig.get();
}
测试类,调用上面的方法,发起调用(参数是对象类型)
@Test
public void userServiceGenericTest(){
/**
* 调用上面封装的方法,传入对应参数
* 参数1:调用的服务(接口名或者类名)
* 参数2:组
* 参数3:版本
* 调用之前获取到参数GenericService
*/
GenericService genericService=dubboClient
("com.longteng.service.UserService",
"longteng" ,
"1.0" );
//String类型的数组,用于下面真正执行的参数2
//com.longteng.domain.User是要调用的方法的入参类型,这个是自定义类型
//如果是多个参数那么new String的长度就根据实际参数个数填写,paramterTypes[1]="第二个参数的参数类型"例如:com.longteng.domain.String,底下的new Object[1]的长度也是根据实际长度写,第二个参数就是args[1]="第二个参数"
String[] paramterTypes=new String[1];
paramterTypes[0]="com.longteng.domain.User";
//在Java里任何自定义对象都可以使用Map代替
//Object类型的数组,用于下面真正执行的参数3
Map map=new HashMap();
map.put("id",1 );
Object [] args=new Object[1];
args[0]=map;
/**
* 真正执行调用,有3个参数
* 参数1:要调用的方法名称
* 参数2:调用方法的参数类型
* 参数3:调用方法需要传的参数
*/
Object returnObject=genericService.$invoke("getUser",paramterTypes ,args );
System.out.println(returnObject);
}
调用String类型的参数
@Test
public void dubboTestServiceGenericTest(){
/**
* 调用上面封装的dubboClient方法,传入3个参数
* 参数1:服务名
* 参数2:组
* 参数3:版本
*/
GenericService genericService=dubboClient
("com.longteng.service.DubboTestService",
"longteng",
"1.0");
//调用的方法只有一个参数,是String类型,传入的参数是hello dubbo
String[] parameterTypes=new String[1];
parameterTypes[0]="java.lang.String";
Object []args=new Object[1];
args[0]="hello dubbo";
//执行调用,参数1:方法名;参数2:参数类型;参数3:参数
Object returnObject=genericService.$invoke("sayHello",parameterTypes , args);
System.out.println(returnObject);
}
用Telnet查看服务器上发布了哪些接口
cmd命令行
如果执行连接服务提示:不是内部命令
控制面板——所有控制面板项(切换成大图标就能看到)——程序和功能——打开或关闭Windows功能——勾选Telnet客户端
Telnet服务器IP地址+端口 连接到dubbo服务上
例:
telnet 47.105.55.243 20880 连接到dubbo服务
ls 显示服务列表
ls -l 显示服务详细信息列表
ls XxxService 显示服务的方法列表
ls -l XxxService 显示服务方法列表的详细信息:可以看到方法的具体入参类型,返回类型
调用方法
Invoke
invoke XxxService.xxxMethed({“prop”:“value”})
XxxService:调用的服务名(接口名、类名)
xxxMethed:调用的方法名
{“prop”:“value”}:调用的参数,只能传json,如果入参是一个引用类型(对象)则要把对象转为json,多个参数用逗号隔开
例:
invoke com.longteng.Service.UserService.getUser({"id":1})
invike com.longteng.Service.DubboTestService,sayHello("hello dubbo")
telnet用代码如何调用
要使用Telnet Scoket调用 创建一个TelnetScoket类,该类在资料文件里有
该方式实现的是短链接,意味着每次只能创建一个请求,执行第二个请求telnetSocket.send结果为Socket is closed
测试类
@Test
public void telnetTest(){
try {
//创建TelnetScoket实例,连接服务,相当于在命令行执行telnet 47.105.55.243 20880
TelnetSocket telnetSocket=new TelnetSocket("47.105.55.243",20880);
//设置调用后的返回为GBK编码,不然返回时乱码
telnetSocket.setReadEncoding("GBK");
//map入参转json
Map map=new HashMap();
map.put("id",1 );
String param=JSON.toJSONString(map);
String cmd="invoke com.longteng.service.UserService.getUser("+param+")";
String result=telnetSocket.send(cmd);
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void telnetTest(){
try {
TelnetSocket telnetSocket=new TelnetSocket("47.105.55.243",20880);
telnetSocket.setReadEncoding("GBK");
//与在命令行执行命令是一样的
String s=telnetSocket.send("ls -l com.longteng.service.UserService");
System.out.println(s);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void zk(){
//是import org.I0Itec.zkclient.ZkClient;包里的,别引错包
ZkClient zkClient=new ZkClient("47.105.55.243:2181");
//要获取的是zookeeper注册中上com.longteng.service.DubboTestService接口的信息(IP、端口、方法、group和version),用循环获取
List dubboList =zkClient.getChildren("/dubbo/com.longteng.service.DubboTestService/providers");
for(String s:dubboList){
try {
String url= URLDecoder.decode(s,"UTF-8" );
System.out.println(url);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
这样就清晰明了了
1 用ZkClient 获取注册zookeeper上服务的IP、端口、方法、group和version(因为Telnet调用要有服务的IP、端口)
2 用Telnet获取服务拥有的方法、入参列表、入参类型(因为泛化调用需要这些信息才能调用成功)
一层层递进