Dubbo如何实现像本地方法一样调用远程方法的?
Dubbo 实现像本地方法一样调用远程方法的核心技术是动态代理。Dubbo 使用 JDK 动态代理或者字节码增强技术,生成一个代理类,该代理类实现了本地接口,具有本地接口的所有方法。在调用本地接口方法时,会通过代理类的 invoke 方法将请求转发到远程服务提供者上。
生成代理类,Dubbo 在启动时会扫描配置文件(注解)中指定的服务接口,并根据服务接口生成一个代理类。这个代理类实现了服务接口,并且在调用服务接口的方法时,会将参数封装成请求消息,然后通过网络传输给服务提供方。
序列化与反序列化,为了在网络上发送和接收数据,Dubbo将方法调用的参数和返回值进行序列化(转换成字节序列)和反序列化(从字节序列还原数据)。Dubbo目前支持多种序列化协议:Dubbo支持多种序列化协议,如Hessian、Java自带的序列化、JSON等,以适应不同的性能和兼容性需求。
网络通信,Dubbo 支持多种通信协议,包括 Dubbo 协议、HTTP 协议、Hessian 协议等。在配置文件中指定了要使用的通信协议后,Dubbo 会根据协议的不同,选择不同的序列化方式,将请求消息序列化成二进制流并发送给服务提供方。
服务注册与发现,Dubbo使用注册中心(如Zookeeper、Nacos等)管理服务的提供者和消费者信息。服务提供者在启动时将自己提供的服务注册到注册中心,服务消费者通过注册中心查找所需的服务并获取服务提供者的地址。
负载均衡,Dubbo 支持多种负载均衡算法,包括轮询、随机、加权随机、最小活跃数等。在客户端发起调用时,Dubbo 会根据负载均衡算法选择一台服务提供方进行调用。
远程服务执行,当客户端发起远程调用后,服务提供方接收到请求后,会根据请求中的服务接口名和方法名,找到对应的实现类和方法,并将请求消息反序列化成参数列表,最终调用服务实现类的方法,并将执行结果序列化成响应消息返回给客户端。
Dubbo实现服务调用的过程是什么样的?
Dubbo的整体架构中,有多个角色,分别是服务提供者,服务调用者以及服务注册中心。一次完整的服务调用过程其实要分为服务注册、服务发现和服务调用三个过程。
1、服务注册:服务提供者在启动时,会向注册中心注册自己提供的服务,并将服务相关的信息(如服务名称、版本号、IP地址、端口号、协议、权重等)一并注册。Dubbo支持多种注册中心,包括ZooKeeper、Redis、Multicast、Simple等。一旦服务注册成功,服务提供者就可以等待服务调用请求的到来。
2、服务发现:服务调用者在启动时,需要向注册中心订阅自己所需的服务,注册中心会将服务提供者列表返回给服务调用者。当过程中,如果服务提供者列表发生变化,那么Dubbo会通知客户端进行变更。
3、服务调用:服务调用者需要根据负载均衡策略选择一个服务提供者,之后,就可以发送服务调用请求了。Dubbo支持多种通信协议和序列化方式。Dubbo客户端将调用请求序列化成二进制数据,并使用网络协议发送给服务提供者,服务提供者将调用请求反序列化后,调用目标方法并将结果序列化成二进制数据返回给服务调用者。在整个调用过程中
Dubbo会对服务调用进行监控,包括调用次数、调用时间、响应时间、异常次数、异常信息等,以便于服务提供者和服务调用者进行故障排查和性能调优。
扩展知识
连接方式
Dubbo提供了多种通信协议和通信方式,包括dubbo、http、hessian等,对于不同的通信方式,Dubbo也提供了不同的通信模型。对于服务提供者和服务消费者之间的通信,可以使用长连接模式或短连接模式,可以使用NIO或者Netty等高性能的通信框架,还可以设置心跳等机制来保持连接的稳定性。
对于服务提供者和注册中心之间的通信,Dubbo同样提供了多种注册中心实现,包括ZooKeeper、Redis、Multicast和Simple等,这些注册中心实现了不同的通信协议和通信方式。
在使用ZooKeeper作为注册中心时,Dubbo会通过ZooKeeper的长连接来注册服务和订阅服务提供者列表,并通过ZooKeeper提供的watch机制来监听服务提供者列表的变化。而在使用Redis作为注册中心时,则会使用Redis提供的订阅-发布机制来进行服务注册和发现。
Dubbo支持哪些负载均衡策略?
Dubbo 支持多种负载均衡策略,允许服务消费者根据不同的场景和需求选择适合的策略。在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。
Dubbo 提供的是客户端负载均衡,即由 Consumer 通过负载均衡算法得出需要将请求提交到哪个 Provider 实例。
目前支持的负载均衡算法如下:
加权随机负载均衡(Random Load Balance)
●随机负载均衡策略会从所有可用的提供者中随机选择一个。权重设置高的提供者被选中的概率更大,可以通过权重来调整每个服务提供者的负载。
●适用场景:适用于各个服务提供者之间处理能力大致相等的情况。
轮询负载均衡(Round Robin Load Balance)
●轮询负载均衡策略会按顺序逐一选择每个服务提供者。类似于随机负载均衡,它也支持权重设置。
●适用场景:适合处理能力均匀且相对稳定的服务环境。
最少活跃调用数负载均衡(Least Active Load Balance)
●这种策略优先调用当前活跃调用数最少的提供者。如果有多个提供者具有相同的活跃调用数,会随机选择一个。活跃调用数指的是某个提供者当前正在处理的调用数量。
●适用场景:适用于执行时间长短不一的服务调用,可以较好地处理突发请求,避免某一节点因长时间任务过多而响应缓慢。
最短响应优先负载均衡(ShortestResponseLoadBalance)
●最短响应优先负载均衡会跟踪每个服务提供者的最近响应时间,并优先选择那些响应时间最短的服务提供者。
●适用场景:适用于存在资源需求高且处理时间可能因负载而波动的应用
一致性哈希负载均衡(Consistent Hash Load Balance)
●一致性哈希负载均衡通过哈希算法将调用请求映射到某个服务提供者上,同一请求总是映射到相同的提供者。这种策略非常适合有状态的服务。
●适用场景:非常适合需要会话亲和性(Session Affinity)的场景,例如,在一个用户的会话期内需要由同一个服务实例来维护状态0。
服务发现是获取服务提供者的地址信息,路由则是将服务请求路由到指定的服务提供者上。
Dubbo的缓存机制了解吗?
Dubbo提供了缓存机制,其主要作用是缓存服务调用的响应结果,减少重复调用服务的次数,提高调用性能。
Dubbo支持了服务端结果缓存和客户端结果缓存。
服务端缓存是指将服务端方法的返回结果缓存到内存中,以便下次请求时可以直接从缓存中获取结果,而不必再调用服务方法。服务端缓存可以提高响应速度和系统吞吐量。Dubbo提供了三种服务端缓存的实现方式:
●LRU Cache: 使用基于LRU(最近最少使用)算法的缓存,当缓存空间满时,会将最近最少使用的缓存清除掉。
●Thread Local Cache: 使用线程本地缓存,即每个线程都拥有一个缓存实例,缓存结果只对当前线程可见。
●Concurrent Map Cache: 使用基于ConcurrentMap的缓存,支持并发读写,相对LRU Cache和Thread Local Cache来说,缓存效率更高。
客户端缓存是指客户端将调用远程服务方法的返回结果缓存到内存中,以便下次请求时可以直接从缓存中获取结果,而不必再调用远程服务方法。消费端缓存可以提高系统的响应速度和降低系统的负载。Dubbo提供了两种消费端缓存的实现方式:
●LRU Cache: 使用基于LRU算法的缓存,当缓存空间满时,会将最近最少使用的缓存清除掉。
●Thread Local Cache: 使用线程本地缓存,即每个线程都拥有一个缓存实例,缓存结果只对当前线程可见。
需要注意的是,缓存虽好用,使用需谨慎,过度依赖缓存可能会出现数据不一致的问题。
为什么RPC要比HTTP更快一些?
其实,RPC的设计目的就是用于高效的内部服务通信,他通常优化了数据传输和序列化过程,目的是减少网络延迟和提高性能。而HTTP的设计是一种更通用的协议,用于Web文档传输,它在灵活性和可访问性上进行了优化,而不是仅仅专注于性能。
展开来说主要在以下几个方面,RPC做出了很多事情。
轻量级序列化协议,RPC通常使用更高效的数据序列化格式(如Protocol Buffers、Thrift等),这些格式专门为性能和效率设计,它们比HTTP标准使用的文本格式(如JSON、XML)更紧凑、解析更快。
网络协议更优,RPC的网络通信协议通常被设计的更轻量(如Netty),他一般不需要像HTTP那样有很复杂的Header信息,从而不需要传输太多的数据。
长连接,虽然RPC和HTTP都是基于TCP的,但是RPC可以使用长连接和更有效的连接管理策略,如gRPC还是基于HTTP/2实现,这可以减少建立连接的开销,并允许多个请求在同一连接上有效地复用。虽然HTTP/1.1也有keep-alive机制,HTTP/2也有很多优化。但是RPC只在企业内部用,所以兼容性更好,而HTTP新版的普及程度并不太高。
定制优化,RPC框架通常允许更深层次的定制和优化,比如调整底层传输细节、序列化方式和错误处理机制。而HTTP作为一个标准化的Web协议,其灵活性和定制能力可能较低,特别是在面向性能的场景中。
内部网络,RPC通常应用于企业内部,内部网络交互链路更短,而HTTP在公网上进行通信,一次交互需要经过多个中间节点的转换。
什么场景只能用HTTP,不能用RPC?
1、在异构系统(跨语言和跨平台),HTTP具有更好的兼容性,因为HTTP是一种通用的协议,几乎所有的编程语言和操作系统都支持HTTP协议,而不是所有的编程语言和操作系统都支持相同的RPC协议。
2、RPC适合用在企业内部,要求使用同一套注册中心进行服务治理,如果是跨组织,或者跨公司,这种情况只能用更加通用的HTTP进行通信。
扩展知识
RPC有什么好处?
性能好:RPC在传输效率上通常比HTTP更高,此外,RPC可以使用更紧凑的数据格式,如Protocol Buffers和Thrift,可以更有效地利用网络带宽和存储空间。
安全性:目前,Dubbo等RPC框架主要应用在企业内部之间的系统调用,而内部系统之间调用的话安全性就更有保障一些。
调用简单:RPC可以帮我们像调用本地方法一样调用远程代码,而HTTP调用需要拼接Body、Header等等,过于复杂。
什么是RPC,和HTTP有什么区别?
RPC 是Remote Procedure Call的缩写,译为远程过程调用。要想实现RPC通常需要包含传输协议和序列化协议的实现。
而我们熟知的HTTP,他的中文名叫超文本传输协议,所以他就是一种传输协议。所以,我们可以认为RPC和HTTP并不是同一个维度的两个概念。只不过他们都是可以作为远程调用的,所以经常拿来对比。
RPC的具体实现上,可以像HTTP一样,基于TCP协议来实现,也可以直接基于HTTP协议实现。
RPC主要用于公司内部服务之间的互相调用,所以他性能消耗低,传输效率高,服务治理方便。而HTTP主要用于对外的异构环境,浏览器调用,APP接口调用,第三方接口调用等等。
实现RPC需要用到的技术
一个成熟的RPC框架需要考虑的问题有很多,这里只介绍实现一个远程调用需要用到的基本技术,感兴趣的朋友可以找一些开源的RPC框架代码来看下。
- 动态代理
生成 client stub和server stub需要用到 **Java 动态代理技术 **,我们可以使用JDK原生的动态代理机制,可以使用一些开源字节码工具框架 如:CgLib、Javassist等。
- 序列化 为了能在网络上传输和接收 Java对象,我们需要对它进行序列化和反序列化操作。
可以使用Java原生的序列化机制,但是效率非常低,推荐使用一些开源的、成熟的序列化技术,例如:protobuf、Thrift、hessian、Kryo、Msgpack
NIO
- 通讯协议 当前很多RPC框架都直接基于netty这一IO通信框架,比如阿里巴巴的HSF、dubbo,Hadoop Avro,推荐使用Netty 作为底层通信框架。
- 服务注册中心 可选技术: Redis、Zookeeper、Consul、Etcd
ElasticSearch为什么快?
典型回答
Elasticsearch是一个高性能、分布式搜索引擎,它之所以快,主要有以下几个原因:
1 分布式存储:Elasticsearch使用分布式存储技术,将数据存储在多个节点上,从而减少单个节点的压力,提高整体性能。
2 索引分片:Elasticsearch把每个索引划分成多个分片,这样可以让查询操作并行化,从而提高查询速度。
3 全文索引:Elasticsearch使用了高效的全文索引技术,把文档转化成可搜索的结构化数据,使得搜索操作快速高效。
4 倒排索引:Elasticsearch支持倒排索引这种数据结构,倒排索引将文档中的每个词与该词出现在哪些文档中进行映射,并存储这些信息。当搜索请求发生时,ES可以快速查找包含所有搜索词的文档,从而返回结果。
5 索引优化:Elasticsearch通过索引优化技术,可以使查询速度更快。例如,它支持索引覆盖、索引下推等优化技术,使得查询速度更快。
6 预存储结果:Elasticsearch在插入数据时,对数据进行预处理,把结果预存储到索引中,从而在查询时不需要再重新计算,提高查询速度。
7 高效的查询引擎:Elasticsearch使用了高效的查询引擎,支持各种类型的查询,并对复杂查询提供了优化策略,从而提高查询速度。
8 异步请求处理:ES使用了异步请求处理机制,能够在请求到达时立即返回,避免长时间的等待,提高用户体验。
9 内存存储:ES使用了内存存储技术,能够在读写数据时大大减少磁盘访问次数,提高数据存储和查询效率。
总之,Elasticsearch快的原因在于它使用了各种高效的技术,使得数据存储、查询、处理都变得更加高效,从而实现了快速的搜索体验。
倒排索引是什么?
典型回答
在 ElasticSearch 中,倒排索引是一种常用的索引结构,用于快速搜索文档中的某个词汇。
倒排索引的结构与传统的索引结构相反,传统的索引结构是由文档构成的,每个文档包含了若干个词汇,然后根据这些词汇建立索引。
而倒排索引是由词汇构成的,每个词汇对应了若干个文档,然后根据这些文档建立索引。
对于一个包含多个词汇的文档,倒排索引会将每个词汇作为一个关键字(Term),然后记录下该词汇所在的文档编号(Document ID)及该词汇在文档中的位置(Term Position)。这样,当用户输入一个关键字时,就可以快速地查找到包含该关键字的文档编号,然后通过文档编号再查找到对应的文档内容。
倒排索引的优点在于它可以快速定位包含关键字的文档,而且可以支持复杂的搜索操作,如词组搜索、通配符搜索等。同时,由于倒排索引是由词汇构成的,因此在进行数据分析和统计时也非常有用。在 ElasticSearch 中,倒排索引是一种非常重要的索引结构,它被广泛应用于搜索引擎、日志分析、推荐系统等领域。
当用户输入一个关键词时,就可以快速查找到包含该关键字的文档编号 然后通过文档编号再查找到对应的文档内容
倒排索引建立过程
ES中的倒排索引建立过程主要有2个步骤,分别是分词、建立倒排索引
比如我们现在有三份文档内容,分别是
id | content |
1 | 深入理解Java核心技术—Hollis |
2 | 深入理解Java虚拟机—周志明 |
3 | Java编程思想—布鲁斯·埃克尔 |
分词
在倒排索引建立过程中,首先需要将文档中的原始文本分解成一个个词项(Term)。Elasticsearch 中默认使用标准分词器(Standard Analyzer)进行分词。
以上三个文本内容,我们经过分词之后,就会包含了"深入"、"理解"、"Java"、"核心"、"技术"、"编程"、"思想"、"Hollis"、"周志明"、"布鲁斯·埃克尔"等词
生成倒排索引
将分开的词,当做索引,与对应的文档ID进行关联,形成倒排表。
在生成了倒排表后,还会对倒排表进行压缩,减少空间占用。常用的压缩算法包括Variable Byte Encoding和Simple9等。最后再将压缩后的倒排表存储在磁盘中,以便后续的搜索操作能够快速地访问倒排表。
如何保证ES和数据库的数据一致性?
典型回答
在业务中,我们通常需要把数据库中的数据变更同步到ES中,那么如何保证数据库和ES的一致性呢?通常有以下几种做法:
双写
在代码中,对数据库和ES进行双写,并且先操作本地数据库,后操作ES,而且还需要把两个操作放到一个事务中:
@Transactional(rollbackFor = Exception.class)
public void update(OrderDTO orderDTO) {
//更新本地数据库
updateDb(orderDTO);
//远程更新ES
updateEs(orderDTO);
}
在以上逻辑中,如果写数据库成功,写ES失败,那么事务会回滚。
如果写数据库成功,写ES超时,实际上ES操作成功,这时候数据库会回滚,导致数据不一致。这时候需要重试来保证最终一致性。
这个方案的好处就是简单,容易实现。并且实时性比较高。
缺点首先是需要改代码,有侵入性,还有就是存在不一致的情况。并且在本地事务中发生了外调(外部调用,调ES),大大拖长了事务,白白占用数据库链接,影响整体的吞吐量。
MQ异步消费
在应用中,如果我要更新数据库了,那么就抛一个消息出去,然后数据库和ES各自有一个监听者,监听消息之后各自去做数据变更,如果失败了就基于消息的重试在重新执行。
或者像之前那个方案一样,先操作数据库,然后异步通知ES去更新,这时候就可以借助本地消息表的方式来保证最终一致性了。
这个方案的好处是用了MQ,起到了解耦的作用,而且还做到了异步,提升了整体性能。
缺点就是MQ可能存在延迟,并且需要引入新的中间件,复杂度有所提升。
扫表定时同步
如果是ES中的数据变更的实时性要求不高,可以考虑定时任务扫表, 然后批量更新ES。
这个方案优点是没有侵入性,数据库的写操作处不需要改代码。
缺点是实时性很差,并且轮询可能存在性能问题、效率问题以及给数据库带来压力。
监听binlog同步
还有一种方案,就是可以利用数据库变更时产生的binlog来更新ES。通过监听binlog来更新ES中的数据,也有成熟的框架可以做这样的事情
好处就是对业务代码完全没有侵入性,业务也非常解耦,不需要关心这个ES的更新操作。
缺点就是需要基于binlog监听,需要引入第三方框架。存在一定的延迟。
总结一下,目前业内比较流行的方案是基于binlog监听的这种,首先一般业务量小的业务也不太需要用ES,所以用了ES的团队,一般并不太会关心引入新框架的复杂度问题,而且ES这种搜索,一般来说,毫秒级的延迟都是可以接受的,所以,综合来讲,基于canal做数据同步的方案,是比较合适的。