Java-分布式框架-Dubbo-3

一、分布式项目开发与联调

接口暴露与引用
image.png

暴露接口的通常做法是 接口与实现分离,服务端将 接口、模型、异常 等统一放置于一个模块,实现置于另一个模块。调用方通过Maven进行引用。

注意:在分布式项目中,不会把整个服务提供方打包成JAR并提供给消费端,而是选择单独把接口与实体类打包成JAR提供给消费端。

自动化构建与协作

当项目越来越多,服务依懒关系越发复杂的时候,为了提高协作效率,必须采用自动化工具 完成 接口从编写到构建成JAR包,最后到引用的整个过程。


image.png

流程描述:

  1. 服务提供者项目发人员编写Client 接口
  2. push 至远程仓库
  3. jenkins 构建指定版本
  4. jenkins Deploye 至私服仓库 nexus
  5. 服务消费者项目开发人员基于maven 从私服务仓库下载
接口平滑升级

在项目迭代过程当中, 经常会有多个项目依懒同一个接口,如下图 项目B、C都依懒了项目A当中的接口1,此时项目B业务需要,需要接口1多增加一个参数,升级完成后。项目B能正确构建上线,项目C却不行。


image.png

解决办法与原则:

  1. 接口要做到向下兼容:接口参数尽量以对象形式进行封装。Model属性只增不删,如果需要作废,可以添加@Deprecated 标识。
  2. 如果出现了不可兼容的变更,则必须通知调用方整改,并制定上线计划。
-- 不推荐,兼容性差
public interface UserService {
    User getUser(Integer id, string name);
}
-- 推荐
public interface UserService {
    User getUser(UserParam param);
    public static class UserParam
    {
        Integer id;
        string name;
    }
}

二、Dubbo控制管理后台使用

Dubbo 控制后台的安装
#从github 中下载dubbo 项目
git clone https://github.com/apache/incubator-dubbo.git
#更新项目
git fetch
#临时切换至 dubbo-2.5.8 版本
git checkout dubbo-2.5.8
#进入 dubbo-admin 目录
cd dubbo-admin
#mvn 构建admin war 包
mvn clean pakcage -DskipTests
#得到 dubbo-admin-2.5.8.war 即可直接部署至Tomcat
#修改 dubbo.properties 配置文件
dubbo.registry.address=zookeeper://127.0.0.1:2181

三、Dubbo注册中心详解

为了到达服务集群动态扩容的目的,注册中心存储了服务的地址信息与可用状态信息,并实时推送给订阅了相关服务的客户端。


image.png

一个完整的注册中心需要实现以下功能:

  1. 接收服务端的注册与客户端的引用,即将引用与消费建立关联,并支持多对多。
  2. 当服务非正常关闭时能即时清除其状态
  3. 当注册中心重启时,能自动恢复注册数据,以及订阅请求
  4. 注册中心本身的集群
Zookeeper 注册中心

关于Zookeeper 注册中心同样需要了解其存储结构和更新机制。
Zookeper是一个树型的目录服务,本身支持变更推送相比redis的实现Publish/Subscribe功能更稳定。

image.png

注意:其中叶子节点为临时节点。

源码解析
image.png

注意:UserService是一个代理对象,由ReferenceConfig引用对象生成,并把ClusterInvoker、RegistryDirectory赋予给它。

四、Dubbo调用模块

dubbo调用模块核心功能是发起一个远程方法的调用并顺利拿到返回结果,其体系组成如下:

  1. 透明代理:通过动态代理技术,屏蔽远程调用细节以提高编程友好性。这里dubbo 使用了 javassist作为代理实现。
  2. 负载均衡:当有多个提供者是,如何选择哪个进行调用的负载算法。
  3. 容错机制:当服务调用失败时采取的策略
  4. 调用方式:支持同步调用、异步调用
负载均衡

Dubbo 目前官方支持以下负载均衡策略:

  1. 随机(random):按权重设置随机概率。此为默认算法.
  2. 轮循 (roundrobin):按公约后的权重设置轮循比率。
  3. 最少活跃调用数(leastactive):相同活跃数的随机,活跃数指调用前后计数差。
  4. 一致性Hash(consistenthash ):相同的参数总是发到同一台机器,默认初始化160个虚拟点,相对hash取余的方式,一致性Hash避免了单一服务过热以及节点数量变化后全局乱套的缺点。


    image.png
容错

Dubbo 官方目前支持以下容错策略:

  1. 失败自动切换:调用失败后基于retries=“2” 属性重试其它服务器
  2. 快速失败:快速失败,只发起一次调用,失败立即报错。
  3. 勿略失败:失败后勿略,不抛出异常给客户端。
  4. 失败重试:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作
  5. 并行调用: 只要一个成功即返回,并行调用指定数量机器,可通过 forks="2" 来设置最大并行数。
  6. 广播调用:广播调用所有提供者,逐个调用,任意一台报错则报错
异步调用

异步调用是指发起远程调用之后获取结果的方式。

  1. 同步等待结果返回(默认)
  2. 异步等待结果返回
  3. 不需要返回结果
    Dubbo 中关于异步等待结果返回的实现流程如下图:


    image.png
demoService.sayHello1("han");
Future<Object> future1 = RpcContext.getContext().getFuture();  //底层ThreadLocal
demoService.sayHello2("han2");
Future<Object> future2 = RpcContext.getContext().getFuture();
Object r1 = null, r2 = null;
// wait 直到拿到结果 获超时
r1 = future1.get();  //同步
// wait 直到拿到结果 获超时
r2 = future2.get();  //同步

注意:有返回值的时候,future.get()方法执行的是同步,提升效率的关键在于多个future.get()方法同时执行时,全部完成的时间是future.get()方法最慢的那一个,而不是多个future.get()方法的总和。
注意:同步调用底层也使用了Future,与异步不同的是,同步对异步多了一层包装,里面使用了future.get()方法,一个线程死循环在访问结果是否有值,一个线程在检测线程是否超时。

五、Dubbo 调用非典型使用场景

泛化提供

是指不通过接口的方式直接将服务暴露出去。通常用于Mock框架或服务降级框架实现。
模拟出来的通用服务提供方

public class MockService implements GenericService {
    private String target;

    public MockService(String target) {
        this.target = target;
    }

    // 通用方法
    @Override
    public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
        if (target.equals("com.lin.client.UserService") && method.equals("getUser")) {
            HashMap<Object, Object> map = new HashMap<>();
            map.put("id", 1);
            map.put("name", "克里斯");
            return map;
        } 
        return null;
    }
}
public class DubboServer {
    public static void main(String[] args) throws IOException {
        ApplicationConfig applicationConfig = new ApplicationConfig("sample-app");
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo");
        protocolConfig.setSerialization("fastjson");
        protocolConfig.setPort(-1);//20880
        RegistryConfig registryConfig = new RegistryConfig("zookeeper://192.168.0.147:2181");

        ServiceConfig serviceConfig = new ServiceConfig();
        serviceConfig.setInterface("com.tuling.client.UserService");
        //serviceConfig.setRef(new UserServiceImpl());
        setMock("com.lin.client.UserService");
        serviceConfig.setRegistry(registryConfig);
        serviceConfig.setProtocol(protocolConfig);
        serviceConfig.setApplication(applicationConfig);
        serviceConfig.export();
        System.out.println("服务已暴露");
        System.in.read();
    }

    public static void setMock(ServiceConfig serviceConfig, String server) {
        serviceConfig.setRef(new MockService(server));
    }
}
隐示传参

是指通过非常方法参数传递参数,类似于http 调用当中添加cookie值。通常用于分布式追踪框架的实现。使用方式如下 :

//客户端隐示设置值
RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐
//服务端隐示获取值
String index = RpcContext.getContext().getAttachment("index"); 
令牌验证

通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者


image.png

使用:

<!--随机token令牌,使用UUID生成-->
<dubbo:provider interface="com.foo.BarService" token="true" />
过滤器

类似于 WEB 中的Filter ,Dubbo本身提供了Filter 功能用于拦截远程方法的调用。其支持自定义过滤器与官方的过滤器使用:

<dubbo:provider  filter="accesslog" accesslog="logs/dubbo.log"/>

以上配置 就是 为 服务提供者 添加 日志记录过滤器, 所有访问日志将会集中打印至 accesslog 当中。
自定义过滤器:

  • 编写过滤器
package com.tuling.dubbo;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
@Activate(group = {CommonConstants.PROVIDER})
public class ProviderHelloFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("log========>");
        return invoker.invoke(invocation);
    }
}
  • 添加扩展点
# 文件路径
META-INF/dubbo/org.apache.dubbo.rpc.Filter
#内容:
helloFilter=com.tuling.dubbo.ProviderHelloFilter

六、调用内部实现源码分析

分析代理类

在调用服务端时,是接口的形式进行调用,该接口是Duboo 动态代理之后的实现,通过反编译工具可以查看到其具体实现:
因为类是代理生成,所以采用arthas工具来反编译,具体操作如下:

#运行 arthas
java -jar arthas-boot.jar
#扫描类
sc *.proxy0
#反编译代理类
jad com.alibaba.dubbo.common.bytecode.proxy0

反编译的代码如下:

package org.apache.dubbo.common.bytecode;

import com.alibaba.dubbo.rpc.service.EchoService;
import com.tuling.client.User;
import com.tuling.client.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
import org.apache.dubbo.common.bytecode.ClassGenerator;

public class proxy0 implements ClassGenerator.DC, EchoService, UserService {
    public static Method[] methods;
    private InvocationHandler handler;
    public List findUser(String string, String string2) {
        Object[] arrobject = new Object[]{string, string2};
        Object object = this.handler.invoke(this, methods[0], arrobject);
        return (List)object;
    }
    public User getUser(Integer n) {
        Object[] arrobject = new Object[]{n};
        Object object = this.handler.invoke(this, methods[1], arrobject);
        return (User)object;
    }
    @Override
    public Object $echo(Object object) {
        Object[] arrobject = new Object[]{object};
        Object object2 = this.handler.invoke(this, methods[2], arrobject);
        return object2;
    }
    public proxy0() {
    }
    public proxy0(InvocationHandler invocationHandler) {
        this.handler = invocationHandler;
    }
}

可看出其代理实现了 UserService 接口。并且基于InvocationHandler 进行代理。实际类是 InvokerInvocationHandler 并且其中之属性为Invoker.。也就是说最终会调用Invoker进行远程调用。

Dubbo调用流程
image.png
//------7协议 调用
doInvoke:77, DubboInvoker {org.apache.dubbo.rpc.protocol.dubbo}
invoke:155, AbstractInvoker {org.apache.dubbo.rpc.protocol}
//------6异步转同步
invoke:52, AsyncToSyncInvoker {org.apache.dubbo.rpc.protocol} // 异步转同步 ,返回结果之前进行阻塞调用线程
//----- 5过滤器链
invoke:92, MonitorFilter {org.apache.dubbo.monitor.support}  // 过滤链-> 监控器
invoke:54, FutureFilter {org.apache.dubbo.rpc.protocol.dubbo.filter}    //过滤链-> 回调参数
invoke:14, ProviderHelloFilter {com.tuling.dubbo}  // 过滤链-> 自定义过滤器
invoke:60, ConsumerContextFilter {org.apache.dubbo.rpc.filter} // 过滤链-> 消费者环境初始化
//------4集群处理
doInvoke:82, FailoverClusterInvoker {org.apache.dubbo.rpc.cluster.support} // 集服-失败重试
invoke:248, AbstractClusterInvoker {org.apache.dubbo.rpc.cluster.support} //
//----- 3Mock服务
invoke:78, MockClusterInvoker {org.apache.dubbo.rpc.cluster.support.wrapper} // mock 服务
//----- 2动态代理 --透明化
invoke:55, InvokerInvocationHandler {org.apache.dubbo.rpc.proxy}// 代理的中间接口
getUser:-1, proxy0 {org.apache.dubbo.common.bytecode} // 代理对象
//----- 1调用客户端
main:53, DubboClient {com.tuling.dubbo}  // 客户端

协议-->注册协议--->MockClusterInvoker--->ClusterInvoker--->RegistryDirectory--->DubboProtcol->FilterChain-->DubboInvoker
image.png

注意:核心在于运用了责任链模式与spi扩展点的技术。

七、RPC 协议

在一个典型RPC的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件,其中RPC协议就指明了程序如何进行网络传输和序列化 。也就是说一个RPC协议的实现就等于一个非透明的远程调用实现,如何做到的的呢?


image.png
dubbo 支持的RPC协议列表
image.png
dubbo协议结构
image.png
  • magic:类似java字节码文件里的魔数,用来判断是不是dubbo协议的数据包。魔数是常量0xdabb,用于判断报文的开始。
  • flag:标志位, 一共8个地址位。低四位用来表示消息体数据用的序列化工具的类型(默认hessian),高四位中,第一位为1表示是request请求,第二位为1表示双向传输(即有返回response),第三位为1表示是心跳ping事件。
  • status:状态位, 设置请求响应状态,dubbo定义了一些响应的类型。具体类型见 com.alibaba.dubbo.remoting.exchange.Response
  • invoke id:消息id, long 类型。每一个请求的唯一识别id(由于采用异步通讯的方式,用来把请求request和返回的response对应上)
  • body length:消息体 body 长度, int 类型,即记录Body Content有多少个字节。


    image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容