高性能的分布式服务框架 Dubbo

时间:2022-12-08 21:27:12
我思故我在,提问启迪思考!
1. 什么是Dubbo?
  官网:http://dubbo.io/,DUBBO是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及作为SOA服务治理的方案,它是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。<目前的dubbo社区已停止维护和更新>,它的核心功能包括:
  • #remoting:远程通讯基础,提供对多种NIO框架抽象封装,包括“同步转异步”和“请求-响应”模式的信息交换方式。
  • #cluster: 服务框架核心,提供基于接口方法的远程过程调用,包括多协议支持,并提供软负载均衡和容错机制的集群支持。
  • #registry: 服务注册中心,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

2. dubbo是如何使用来提供服务的?
高性能的分布式服务框架 Dubbo
  web应用架构的发展,从单一的ORM架构(一个应用,将所有功能部署在一起,减少部署的节点和成本)到MVC架构(拆分出互相不相干的几个应用),再从MVC框架到RPC框架(将业务中的核心业务抽取出来,做为独立的服务,渐渐形成服务中心),从RPC架构到SOA架构(增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率)。
  Dubbo有自己的服务中心,写好的服务可以注册到服务中心,客户端从服务中心寻找服务,然后再到相应的服务提供者机器获取服务。通过服务中心可以实现集群、负载均衡、高可用(容错) 等重要功能。服务中心一般使用zookeeper实现,也有redis和其他一些方式。以使用zookeeper作为服务中心为例,服务提供者启动后会在zookeeper的/dubbo节点下创建提供的服务节点,包含服务提供者ip、port等信息。服务提供者关闭时会从zookeeper中移除对应的服务。服务使用者会从注册中心zookeeper中寻找服务,同一个服务可能会有多个提供者,Dubbo会帮我们找到合适的服务提供者。
3. dubbo是如何做集群容错的?

  当服务调用失败时(比如响应超时),根据我们的业务不同,可以使用不同的策略来应对这种失败。比如我们调用的服务是一个查询服务,不会修改数据库,那么可以给该服务设置容错方式为failover <失败重试方式>,当调用失败时,自动切换到其他服务提供者去调用,当失败次数超过指定重试次数,那么就抛出错误。如果服务是更新数据的服务,那就不能使用失败重试的方式了, 因为这样可能产生数据重复修改的问题,比如调用提供者A的插入用户方法,但是该方法业务逻辑复杂,执行过程很慢,导致响应超时,那么此时如果再去调用另外一个服务提供者的插入用户方法,将会又重复插入同一个用户。对于这种类型的服务,可以使用容错方式为failfast,如果第一次调用失败,立即报错,不需要重试。

另外还有下面几种容错类型,failsafe 出现错误,直接忽略,不重试也不报错; failback 失败后不报错,会将该失败请求,定时重发,适合消息通知类型的服务。forking,并行调用多个服务器,只要在某一台提供者上面成功,那么方法返回,适合实时性要求较高的查询服务,但是要牺牲性能。因为每台服务器会做同一个操作;broadcast 广播调用所有服务提供者,逐个调用,任意一台报错则报错。适合与更新每台提供者上面的缓存,这种类型的服务。

  案例:我们在点我吧的APP消息推送就是采用的failback方式,点我吧的短信通知则采用的是failover方式。

4. dubbo是如何做负载均衡的?{几种负载均衡的策略}
  当同一个服务有多个提供者在提供服务时, 客户端如何正确的选择提供者实现负载均衡dubbo也给我们提供了几种方案:random,随机选提供者,并可以给提供者设置权重;roundrobin,轮询选择提供者,leastactive,最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。consistenthash 一致性hash,相同参数的请求发到同一台机器上。
  关于一致性hash的几点学习,我们知道普通的哈希算法在散列的时候,可能产生间隔,并不能使数据均与分布开来。一致性哈希算法正是基于哈希算法的缺点所引入的解决方案。一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义:1、平衡性(Balance):平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。2、单调性(Monotonicity):单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。3、分散性(Spread):在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。4、负载(Load):负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同 的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。在分布式集群中,对机器的添加删除,或者机器故障后自动脱离集群这些操作是分布式集群管理最基本的功能。如果采用常用的hash(object)%N算法,那么在有机器添加或者删除后,很多原有的数据就无法找到了,这样严重的违反了单调性原则。接下来主要讲解一下一致性哈希算法是如何设计的:

高性能的分布式服务框架 Dubbo

如上图所示,是环形的hash空间,按照常用的hash算法来将对应的key哈希到一个具有2^32次方个桶的空间中,即0~(2^32)-1的数字空间中。现在我们可以将这些数字头尾相连,想象成一个闭合的环形。我们把数据通过hash算法处理后映射到环上,现在我们将object1、object2、object3、object4四个对象通过特定的Hash函数计算出对应的key值,然后散列到Hash环上。Hash(object1) = key1;Hash(object2) = key2;Hash(object3) = key3;Hash(object4) = key4;

 高性能的分布式服务框架 Dubbo
普通hash求余算法最为不妥的地方就是在有机器的添加或者删除之后会照成大量的对象存储位置失效,这样就大大的不满足单调性了。下面来分析一下一致性哈希算法是如何处理的?它通过按顺时针迁移的规则,首先保证其它对象还保持原有的存储位置。当通过节点的添加和删除的时候,一致性哈希算法在保持了单调性的同时,还是数据的迁移达到了最小,这样的算法对分布式集群来说是非常合适的,避免了大量数据迁移,减小了服务器的的压力。
5. dubbo的多协议方式?
  dubbo提供了多种协议给用户选择,如dubbo、hessian、rmi 。并可为每个服务指定不同的传输协议,粒度可以细化到方法,不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议。
  使用Dubbo进行远程调用实现服务交互,它支持多种协议,如Hessian、HTTP、RMI、Memcached、Redis、Thrift等等。由于Dubbo将这些协议的实现进行了封装了,无论是服务端(开发服务)还是客户端(调用服务),都不需要关心协议的细节,只需要在配置中指定使用的协议即可,从而保证了服务提供方与服务消费方之间的透明。另外,如果我们使用Dubbo的服务注册中心组件,这样服务提供方将服务发布到注册的中心,只是将服务的名称暴露给外部,而服务消费方只需要知道注册中心和服务提供方提供的服务名称,就能够透明地调用服务,后面我们会看到具体提供服务和消费服务的配置内容,使得双方之间交互的透明化。
高性能的分布式服务框架 Dubbo
 
  如图所示的应用场景,服务方提供一个搜索服务,对服务方来说,它基于SolrCloud构建了搜索服务,包含两个集群,ZooKeeper集群和Solr集群,然后在前端通过Nginx来进行反向代理,达到负载均衡的目的。服务消费方就是调用服务进行查询,给出查询条件。
  基于上面的示例场景,我们打算使用ZooKeeper集群作为服务注册中心。注册中心会暴露给服务提供方和服务消费方,所以注册服务的时候,服务先提供方只需要提供Nginx的地址给注册中心,但是注册中心并不会把这个地址暴露给服务消费方。
 public interface SolrSearchService {
String search(String collection, String q, ResponseType type, int start, int rows);
public enum ResponseType {JSON, XML}
}
  其中,provider的服务设计:provider所发布的服务组件,包含了一个SolrCloud集群,在SolrCloud集群前端又加了一个反向代理层,使用Nginx来均衡负载。Provider的搜索服务系统,设计如下图所示:
  高性能的分布式服务框架 Dubbo
 
     如图所示,实际nginx中将请求直接转发到内部的Web Servers上,在这个过程中,使用ZooKeeper来进行协调:从多个分片(Shard)服务器上并行搜索,最后合并结果。一共配置了3台Solr服务器,因为SolrCloud集群中每一个节点都可以接收搜索请求,然后由整个集群去并行搜索。最后,我们要通过Dubbo服务框架来基于已有的系统来开发搜索服务,并通过Dubbo的注册中心来发布服务。首选需要实现服务接口,实现代码如下所示:
 package org.shirdrn.platform.dubbo.service.rpc.server;

 import java.io.IOException;
import java.util.HashMap;
import java.util.Map; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService;
import org.shirdrn.platform.dubbo.service.rpc.utils.QueryPostClient;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class SolrSearchServer implements SolrSearchService { private static final Log LOG = LogFactory.getLog(SolrSearchServer.class);
private String baseUrl;
private final QueryPostClient postClient;
private static final Map<ResponseType, FormatHandler> handlers = new HashMap<ResponseType, FormatHandler>(0);
static {
handlers.put(ResponseType.XML, new FormatHandler() {
public String format() {
return "&wt=xml";
}
});
handlers.put(ResponseType.JSON, new FormatHandler() {
public String format() {
return "&wt=json";
}
});
}
public SolrSearchServer() {
super();
postClient = QueryPostClient.newIndexingClient(null);
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String search(String collection, String q, ResponseType type,
int start, int rows) {
StringBuffer url = new StringBuffer();
       url.append(baseUrl).append(collection).append("/select?").append(q);
       url.append("&start=").append(start).append("&rows=").append(rows);
url.append(handlers.get(type).format());
LOG.info("[REQ] " + url.toString());
return postClient.request(url.toString());
}
interface FormatHandler {
String format();
}
public static void main(String[] args) throws IOException {
String config = SolrSearchServer.class.getPackage().getName().replace('.', '/') + "/search-provider.xml";
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config);
context.start();
System.in.read();
}
}
对应的Dubbo配置文件为search-provider.xml,内容如下所示:
 <?xml version="1.0" encoding="UTF-8"?>
<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-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="search-provider" />
<dubbo:registry address="zookeeper://slave1:2188?backup=slave3:2188,slave4:2188" />
<dubbo:protocol name="dubbo" port="20880" />
<bean id="searchService" class="org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer">
<property name="baseUrl" value="http://nginx-lbserver/solr-cloud/" />
</bean>
<dubbo:service interface="org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService" ref="searchService" />
</beans>

上面,Dubbo服务注册中心指定ZooKeeper的地址:zookeeper://slave1:2188?backup=slave3:2188,slave4:2188,使用Dubbo协议。配置服务接口的时候,可以按照Spring的Bean的配置方式来配置,注入需要的内容,我们这里指定了搜索集群的Nginx反向代理地址http://nginx-lbserver/solr-cloud/。

6. dubbo的使用特点? 
  dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。
7. 总结
  dubbo是高性能的分布式服务框架,它提供了服务治理的解决方案。它适用于(公司内部)跨部门跨项目远程调用的场景,
高性能的分布式服务框架 Dubbo
    dubbo稳定而高效,不仅支持大规模服务集群的动态扩展,平滑升级,而且支持load balance,fail over,支持服务质量分级管理,dubbo简化了服务配置,减少DB连接数。
  dubbo可以做什么,dubbo是基于NIO和长连接的远程调用实现,dubbo提供的服务暴露和导入,对应用方的代码无侵入性,dubbo提供多协议支持,如RMI、Hessian、tbr等,dubbo提供了fail over机制,当某个provider出现异常后,会自动切换provider,dubbo提供了load balance机制,采用了权重+随机或轮询算法,服务列表动态更新,如步骤三。dubbo解决了服务治理问题,包括provider和consumer之间的匹配问题,dubbo提供了服务的注册和订阅功能,dubbo的灵活的路由规则设置,且支持服务列表动态推送功能,且提供了基于web的管理功能。
/*
* 使用Dubbo服务接口
*/ // 首先,在API包中定义服务接口,同时部署于Provider端和Consumer端
public interface HelloService {
public String sayHello();
} // 其次,在服务端的Provider实现代码
public class HelloServiceImpl implements HelloService {
public String sayHello() {
return "Welcome to dubbo!";
}
} // 配置:提供者暴露服务;消费者消费服务 /*provider端服务实现类*/
<bean id="helloService" class="com.alibaba.hello.impl.HelloServiceImpl" /> /*provider端暴露服务*/
<dubbo:service interface="com.alibaba.hello.HelloService" version="1.0.0" ref="helloService"/> /*consumer端引入服务*/
<dubbo:reference id="helloService" interface="com.alibaba.hello.HelloService" version="1.0.0" /> /*consumer端使用服务*/
<bean id="xxxAction" class="com.alibaba.xxx.XxxAction" ><property name="helloService" ref="helloService" /></bean>
我们对dubbo的性能做出测试,如下:
 高性能的分布式服务框架 Dubbo