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 包装类嵌套实例生成