dubbo系列-dubbo中的高级配置(二)

在dubbo系列的上一篇我们介绍了dubbo中的几个高级配置,本篇我们继续介绍dubbo中的高级配置。

负载均衡

在生产环境中,每个服务通常为一个集群,则需要进行负载均衡。

dubbo中的负载均衡算法

dubbo中内置了四种负载均衡算法。

  • random:随机算法,是默认的负载均衡策略。
  • roundrobin:轮询算法。按照权重进行访问,权重设置在提供者端,数值越大,权重越大。
  • leastactive:最少活跃度算法
  • consistenthash:一致性hash算法。

在dubbo中对于方法的负载均衡设置可以通过在提供者的<dubbo:service>标签中设置,也可以在消费者的<dubbo:reference>标签中设置,如下所示。

<dubbo:service interface="com.abc.service.SomeService"
                   ref="oldService" loadbalance="roundrobin"/>
<dubbo:service interface="com.abc.service.SomeService"
                   ref="oldService" loadbalance="roundrobin"/>

<!--  在消费者方指定负载均衡算法,可以在某一个service上,也可以在service中具体的method上指定  -->
<dubbo:reference id="someService" interface="com.abc.service.SomeService">
    <dubbo:method name="hello" loadbalance="random"/>
    <dubbo:method name="world" loadbalance="leastactive"/>
</dubbo:reference>

在使用一致性hash,即consistenthash轮询算法时需要注意,其余方法参数有关,相同的参数会被分配到相同的提供者;多个参数,对第一个参数进行hash操作。如果方法没有参数,则不能使用一致性hash算法。

dubbo中的集群容错

集群容错指的是,当消费者调用提供者集群时发生异常的处理方案。在dubbo中,内置了6种集群容错方式。

Failover

Failover是故障转移机制,当消费者服务调用提供者服务某个服务器无法响应时,其会尝试调用同服务的其他服务器,适用于读操作。比如,消费者调用提供者从数据库中读取数据,可以设置retires来决定其重试次数,重试次数不包含第一次的正常调用。当第一次读取数据失败时,可以进行重试,尝试调用其他服务器服务,进行故障的转移。

Failfast

Failfast即快速失败机制,当服务消费者调用服务提供者时,服务无法正确相应时,则立即报错。通常适用于非幂等性的写操作,例如新增一条数据记录。

ps:所谓幂等性,每次发出请求对服务端产生的状态影响是幂等的,任意次执行对资源本身所产生的影响和执行一次是相同的。

Failsafe

Failsafe即失败安全机制,当服务消费者调用服务提供者时,服务无法正确响应时,直接忽略本次消费请求。通常适用于不太重要的服务,如日志写入操作等。

Failback

Failback即失败自动回复机制,当服务消费者调用服务提供者,服务未正常相应,dubbo会记录本次失败记录,后面定时自动重新发送该请求。通常适用于对实时性要求不高的服务,如消息的通知等。

Forking

Foring即并行机制,消费者会同时调用服务提供者的多台服务器,当有一台服务器返回结果,即调用成功。通常适用于对实时性要求高的操作,但会造成服务器资源的浪费。

Broadcast

Broadcast即广播机制,会逐个调用服务提供者,若有一个出现失败则会报错。通常适用于通知所有提供者更新缓存、日志等信息时使用。

在dubbo中,默认的集群容错方式为故障转移机制(Failover),可以通过在服务提供者端的<dubbo:service>或者服务消费者端的<dubbo:reference>中添加cluster参数进行更改。

服务降级

何为服务降级

服务降级,当服务器压力剧增的情况下,根据当前业务情况以及流量对一些服务有策略的降低服务级别,释放更多的服务器资源,保证核心业务的正常运行。例如:在双十一0点-2点期间,淘宝不允许用户查看历史订单;在晚上11点至次日7点,12306停止售票服务。这都是典型的服务降级。

服务降级的方式

能够实现服务降级的方式由很多,常见的方式由以下几种情况:

  • 部分服务暂停。比如双十一期间淘宝的不允许用户查看历史订单,就属于暂停了这部分业务。
  • 部分服务延迟。
  • 全部服务暂停。典型的代表就是12306的夜间服务停止操作。
  • 随机拒绝服务。
服务降级与mock机制

在dubbo中服务降级采用的为mock机制,具有两种降级处理方式:Mock Null降级处理与Class Mock降级处理。在适用场景方面,Mock Null降级处理适用于没有返回值的情况,Class Mock降级处理则适用于没有返回值的情况。

在测试服务降级时,要模拟服务提供失败的情况,用不启动服务提供者的方式来模拟,只需要修改消费者端即可。Dubbo对于存在返回值和无返回值的服务降级处理方式不同,定义如下所示接口,包含一个有返回值的方法和一个无返回值的方法。

public interface UserService {
    String getUsernameById(int id);
    void addUser(String username);
}

修改消费者端<dubbo:reference>标签,添加mock属性,指定服务降级的方式,这里使用 Mock Null 服务降级处理方式。

    <dubbo:reference id="userService" check="false" mock="return null"
                     interface="com.abc.service.UserService"/>

consumer的运行示例如下所示。mock情况下,消费者调用有返回值的方法时,返回结果为null,而没有返回值的方法则没有任何显示。

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-consumer.xml");
        UserService service = (UserService) ac.getBean("userService");

        System.out.println("开始执行");
        // 有返回值的方法降级结果为null
        String usernameById = service.getUsernameById(3);
        System.out.println(usernameById);

        // 无返回值的方法降级结果是无任何显示
        service.addUser("China");
        System.out.println("执行结束");
    }

通过上面的示例可以看到,使用mock null时,有返回值的方法只会返回null,而没有返回值的方法则没有任何显示,很不又要的样子。Class Mock的服务降级解决了这两个问题,在Mock Null的基础上进行改造,在UserService接口同目录下定义一个UserServiceMock,这里的Mock Class的命名方法需要满足以下规则:业务接口简单类名 + Mock。该类要实现业务接口, 而方法的内容即为服务降级后要要执行的代码逻辑,如下所示。

public class UserServiceMock implements UserService {

    @Override
    public String getUsernameById(int id) {
        return "没有该用户:" + id;
    }

    @Override
    public void addUser(String username) {
        System.out.println("添加该用户失败:" + username);
    }
}

修改消费者端<dubbo:reference>标签,如下所示。

    <dubbo:reference id="userService" check="false" mock="true"
                     interface="com.abc.service.UserService"/>

consumer的运行示例与Mock Null相同,运行结果后如下所示。

没有该用户:3
添加该用户失败:China

服务限流

在dubbo中同样提供了服务限流的两种方法:直接限流和间接限流。直接限流,通过对连接的数量直接限制来达到限流的目的,超时限制则会让消费者等待,直到等待超时,或者获取到服务。间接限流,通过一些非连接数量设置的间接手段来达到限流的目的

直接限流
executes限流

该属性仅能设置在提供者端。可以设置为接口级别,也可以设置为方法级别。

<!-- 限制当前接口中的每个方法并发连接数不超过10 -->
    <dubbo:service interface="com.abc.service.SomeService"
                   ref="zhifubaoService" executes="10"/>
    <!-- 限制当前接口中hello方法服务并发数量不超过10 -->
    <dubbo:service interface="com.abc.service.SomeService"
                   ref="weixinService">
        <dubbo:method name="hello" executes="10"/>
    </dubbo:service>
accepts限流

该属性仅可设置在提供者端的dubbo:protocol/与dubbo:provider/标签中,用于对指定协议连接数的限制。前者基于协议连接数量限定的,而后者是针对服务本身在使用某种协议时最多接受多少个消费者连接。

    <!-- 限制当前接口在dubbo协议下并发连接数不超过10 -->
    <dubbo:protocol name="dubbo" port="20880" accepts="10"/>
    <dubbo:provider protocol="dubbo" accepts="10"/>

actives限流

actives限流方式既可以在提供者方设置也可以在消费者方设置,同时消费者调用的长连接、短连接意义有所不同。

  • 在提供者方设置时,若客户端与当前服务的连接为长连接,则表示当前服务的一个长连接可以并行处理请求的数目;若客户端与当前服务的连接为短连接时,则表示当前服务可以同时处理的短连接的数量。
  • 在消费者方设置时,若连接为长连接,则表示当前客户端一个长连接能够并发提交请求的个数,并没有长连接数目的限制;若连接为短连接,则表示当前客户端能够同时提交的短连接的数目。
 <!-- 限制当前接口中的每个接口的并发执行能力不超过10 -->
    <dubbo:service interface="com.abc.service.SomeService"
                   ref="zhifubaoService" actives="10"/>
    <!-- 限制当前接口中hello方法并发数量不超过10 -->
    <dubbo:service interface="com.abc.service.SomeService"
                   ref="weixinService">
        <dubbo:method name="hello" actives="10"/>
    </dubbo:service>

    <!-- 限制当前接口中的每个方法提交的请求不超过10 -->
    <dubbo:reference interface="com.abc.service.SomeService" id="zhifubaoService" actives="10"/>
    <!-- 限制当前接口中hello方法提交的连接数不超过10 -->
    <dubbo:reference interface="com.abc.service.SomeService" id="weixinService">
        <dubbo:method name="hello" actives="10"/>
    </dubbo:reference>
connections限流

connections限流与actives限流效果大体相同,同样可以设置在服务提供者端和服务消费者端。connnections无论设置在消费者端还是提供者端,无论是长连接还是短连接,其都表示最多支持的连接个数。

间接限流

上面所说的直接限流是通过直接限制服务的连接数来达到效率的目的;而间接限流则是通过一些非连接数量设置的间接手段来达到限流的目的。

延迟连接

延迟连接用于减少长连接数。只有当消费者端的调用发起时,才创建长连接,所以其是设置在消费者端的。由于其仅作用于长连接,所以其仅对于 Dubbo 服务暴露协议生效。

注意,该属性仅可设置在消费者端的dubbo:reference/与dubbo:consumer/标签 中,且也不能设置为方法级别。

    <!-- 设置当前消费者在该接口上方法均采用延迟连接 -->
    <dubbo:reference id="userService" check="false" layer="true"
                     interface="com.abc.service.UserService"/>
    <dubbo:consumer layer="true"/>
粘连连接

粘连连接限制的不是流量,而是流向。所谓粘连连接是指,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。粘滞连接将自动开启延迟连接,以减少长连接数。粘连连接仅能设置在消费者端,其可以设置为接口级别,也可以设置为方法级别。

    <!-- 设置当前消费者在该接口上方法均采用延迟连接 -->
    <dubbo:reference id="userService" check="false" sticky="true"
                     interface="com.abc.service.UserService"/>
    <dubbo:reference id="userService" check="false"
                     interface="com.abc.service.UserService">
        <dubbo:method name="hello" sticky="true" />
    </dubbo:reference>
负载均衡

若将负载均衡策略设置为leastactive的方式,可以使消费者调用并发数量最少的提供者,来达到限流的目的。所以,该方式限制的也不是流量,而是流向。当然,可以设置在消费者端,亦可设置在提供者端;可以设置在接口级别,亦可设置在方法级别。

 <dubbo:service interface="com.abc.service.SomeService"
                   ref="zhifubaoService" loadbalance="leastactive"/>

声明式缓存

为了进一步提高消费者对用户的响应速度,减轻提供者的压力,Dubbo 提供了基于结果 的声明式缓存。该缓存是基于消费者端的,所以使用很简单,只需修改消费者配置文件,与提供者无关。

该缓存是缓存在消费者端内存中的,一旦缓存创建,即使提供者宕机也不会影响消费者 端的缓存。

由上面的描述可以总结出声明式缓存适合使用的场景:对于不经常改版或者根本不改变的数据适合使用声明式缓存,如字典项和菜单数据。仅修改消费者配置文件,如下所示,添加cache="true"的标签。

    <dubbo:reference id="someService" cache="true"
                     interface="com.abc.service.SomeService"/>

修改RunConsumer类内容如下,四次调用service的hello方法,分别输出Tom和Jerry。

    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-consumer.xml");
    SomeService service = (SomeService) ac.getBean("someService");
    
    System.out.println(service.hello("Tom"));
    System.out.println(service.hello("Jerry"));
    System.out.println(service.hello("Tom"));
    System.out.println(service.hello("Jerry"));

执行上述代码后,得到如下结果。可以发现服务提供者端只输出了一次Tom和Jerry的调用结果,这就是神明是缓存的作用。缓存在消费者端的内存中创建,再次获取则将从内存中读取。

执行提供者的hello() Tom
执行提供者的hello() Jerry

若一个接口中只有部分方法需要缓存,则可使用方法级别的缓存。相对于此,前面的缓
存称为服务级别缓存。仅需修改配置文件。

默认缓存 1000 个结果

声明式缓存中可以缓存多少个结果呢? 在声明式缓存中,默认可以缓存1000个结果。若超出1000,将采用“最近最少使用策略”,即LRU策略来删除缓存,以保证最热的数据被缓存。注意,该删 除缓存的策略不能修改。下面我们来测试一下缓存1000个的例子,修改RunConsumer类内容如下。

    ApplicationContext ac =
            new ClassPathXmlApplicationContext("spring-consumer.xml");
    SomeService service = (SomeService) ac.getBean("someService");

    // 1000次不同的消费结果,将占满1000个缓存空间
    for (int i=1; i<=1000; i++) {
        service.hello("i==" + i);
    }

    // 第1001次不同的消费结果,会将第1个缓存内容挤出去
    System.out.println(service.hello("Tom"));

    // 本次消费会从调用提供者,说明原来的第1个缓存的确被挤出去了
    // 本次消费结果会将原来("i==2")的缓存结果挤出去
    System.out.println(service.hello("i==1"));

    // 本次消费会直接从缓存中获取
    System.out.println(service.hello("i==3"));

在上面的代码中已经做了较为详细的注释,运行测试,得到如下结果。从结果中可以看到实验现象确实如注释所示。

执行提供者的hello() i==997
执行提供者的hello() i==998
执行提供者的hello() i==999
执行提供者的hello() i==1000
执行提供者的hello() Tom
执行提供者的hello() i==1

多注册中心

在生产环境中,我们的一个服务可能需要在不同的机房中发布,而不同的机房所用的注册中心不同,这就是多注册中心的应用场景。

服务暴露延迟

如果我们的服务启动过程需要 warmup 事件,就可以使用 delay 进行服务延迟暴露。
只需在服务提供者的dubbo:service/标签中添加 delay 属性。其值可以有三类:

  • 若为正数,则单位为毫秒,表示在提供者对象创建完毕后的指定时间后再发布服务;
  • 默认值为 0,表示当前提供者创建完毕后马上暴露服务。
  • 若值为-1,则表示在 Spring 容器初始化完毕后再暴露服务。

对于默认值为0和值为-1的时候,大家可能会有所混淆。默认值为0时,当服务提供者被实例化后,马上就会暴露服务;而当值为-1的时候,需要等待所有的singletype类型的bean创建完成后统一进行暴露。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值