zoukankan      html  css  js  c++  java
  • dubbo入门笔记

    为什么要使用dubbo

    • 服务治理框架
    • 服务的监控
    • 服务的注册发现
    • 服务的通信
    • 服务的容错
    • 服务的负载均衡

    Dubbo整合spring来使用

    首先我们创建两个module,一个是spring-dubbo和dubbo-api , dubbo-api主要是存放需要发布出去的接口,spring-dubbo包含了:

    • spring-dubbo-client #消费者
    • spring-dubbo-server-nacos #把自己的服务注册到(nacos)注册中心中。

    整体的pom.xml依赖引入

    spring-dubbo的pom.xml如下,因为是spring-dubbo-client, spring-dubbo-server-nacos的父类,所以这两个模块也是引入了这些依赖

        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.apache.dubbo</groupId>
                    <artifactId>dubbo</artifactId>
                    <version>2.7.8</version>
                </dependency>
            </dependencies>
        </dependencyManagement>    
    
    <dependencies>
            <!-- 注册中心nacos的相关配置-->
            <dependency>
                <groupId>com.alibaba.nacos</groupId>
                <artifactId>nacos-client</artifactId>
                <version>1.3.2</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-registry-nacos</artifactId>
                <version>2.7.8</version>
            </dependency>
                <!-- dubbo的相关配置-->
              <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
            </dependency>
            <!-- 接口model的引入-->
            <dependency>
                <groupId>com.onion</groupId>
                <artifactId>dubbo-api</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
    
        </dependencies>
    

    接口模块

    首先先看一下dubbo-api模块,主要是提供了两个接口是IUserService,IOrderService

    /**
     * 配合注册中心使用的一个接口
     * @Author: gyc
     * @Date: 2020/10/10 17:38
     */
    public interface IUserService {
    
        String getUser(String userId);
    
        String loginUser(String userId);
    }
    
    

    提供者模块

    在spring-dubbo-server-nacos中,实现IUserService的接口

    /**
     * @Author: gyc
     * @Date: 2020/10/10 17:47
     */
    public class UserService implements IUserService {
        public String getUser(String userId) {
            return "getUser: " + userId;
        }
    
        public String loginUser(String userId) {
            return "loginUser: " + userId;
        }
    }
    
    

    测试demo类:

    /**
     * @Author: gyc
     * @Date: 2020/10/12 9:10
     */
    public class App {
    
        public static void main(String[] args) throws IOException {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-provider.xml"});
            context.start();
            System.in.read(); // 按任意键退出
        }
    }
    

    dubbo提供者的配置文件

    <?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://dubbo.apache.org/schema/dubbo"
           xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    
        <!-- 提供方应用信息,用于计算依赖关系 -->
        <dubbo:application name="spring-dubbo-server-nacos"  />
            
        <!--    可以使用N/A来直连-->
        <!--<dubbo:registry address="N/A" />-->
    
        <!--  使用nacos作为注册中心  -->
         <dubbo:registry address="nacos://127.0.0.1:8848" />
    
        <!-- 用dubbo协议,port为-1的时候,从20880开始递增1,直到找到没有冲突的端口 -->
    
        <dubbo:protocol name="dubbo" port="-1" />
    
        <!-- 声明需要暴露的服务接口 -->
        <dubbo:service interface="com.onion.service.IUserService" ref="userService" />
    
        <!-- 和本地bean一样实现服务 -->
        <bean id="userService" class="com.onion.service.UserService" />
    </beans>
    

    注意:如果提供者服务不注册到注册中心的话,是需要配置<dubbo:registry address="N/A" />,把注册中心的地址设为N/A,不然的话会报错

    消费者模块

    消费者的测试用例:

    /**
     * @author gyc
     * @date 2020/10/11
     */
    
    public class App {
    
        public static void main(String[] args) throws IOException {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"META-INF/spring/dubbo-demo-consumer.xml"});
            context.start();
            //利用nacos注册中心调用userService
            IUserService userService = (IUserService)context.getBean("userService"); // 获取远程服务代理
            String hello = userService.getUser("admn"); // 执行远程方法
            System.out.println( hello ); // 显示调用结果
    
            //利用直接连接暴露的服务
    //        IOrderService orderService = (IOrderService)context.getBean("orderService"); // 获取远程服务代理
    //        String orderId = orderService.getOrderById("orderId"); // 执行远程方法
    //        System.out.println( orderId ); // 显示调用结果
            System.in.read(); // 按任意键退出
        }
    
    
    }
    

    消费者的配置文件:

    <?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://dubbo.apache.org/schema/dubbo"
           xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    
        <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
        <dubbo:application name="spring-dubbo-client"  />
    
        <!-- 以nacos为注册中心-->
        <dubbo:registry address="nacos://127.0.0.1:8848" />
    
        <!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
        <dubbo:reference id="userService" interface="com.onion.service.IUserService" />
        
            <!-- 可以通过url来直接指定服务提供者的地址 -->
        <dubbo:reference id="orderService" interface="com.onion.service.IOrderService" url="dubbo://127.0.0.1:20881/com.onion.service.IOrderService" />
        
    </beans>
    

    这里我们可以看到服务提供者的地址格式为:

    dubbo://127.0.0.1:20881/com.onion.service.IOrderService
    

    http : // 服务器的IP: 容器的端口/ mapping

    总结

    dubbo依赖主要是:

    • dubbo
    • nacos-client

    Dubbo整合springboot

    首先我们创建两个module,一个是spring-boot-dubbo和dubbo-api ,跟上面Dubbo整合spring的一样,spring-boot-dubbo包含了:

    • spring-boot-dubbo-consumer
    • spring-boot-dubbo-provider

    整体的pom.xml引入

        <properties>
            <java.version>1.8</java.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <spring-boot.version>2.3.0.RELEASE</spring-boot.version>
            <dubbo.version>2.7.8</dubbo.version>
        </properties>
        
         <dependencies>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
                <version>${spring-boot.version}</version>
            </dependency>
            <dependency>
                <groupId>com.onion</groupId>
                <artifactId>dubbo-api</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba.nacos</groupId>
                <artifactId>nacos-client</artifactId>
                <version>1.3.2</version>
            </dependency>
    
        </dependencies>
    

    接口模块

    接口模块,跟上面Dubbo整合spring的一样

    服务提供者模块

    spring-boot-dubbo-provider 的实现类如下:

    @DubboService //提供IUserService这个接口的服务
    public class UserService implements IUserService {
    
    
        public String getUser(String userId) {
            return "spring boot getUserId:" + userId;
        }
    
        public String loginUser(String userId) {
            return "spring boot loginUser:" + userId;
        }
    }
    
    

    测试类启动:

    @SpringBootApplication
    public class App {
    
        public static void main(String[] args) {
            SpringApplication.run(App.class,args);
        }
    }
    
    

    application.yml的配置:

    dubbo:
      application:
        name: spring-boot-dubbo-provider
      protocol:
        name: dubbo
        port: -1
      registry:
        address:  nacos://127.0.0.1:8848
      scan:
        base-packages: com.onion.service     
    

    服务消费者模块

    我们使用web方式来测试,引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
    </dependencies>
    

    constroller:

    /**
     * @author gyc
     * @date 2020/10/12
     */
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @DubboReference //注入服务提供者
        private IUserService userService;
    
    
        @GetMapping("/getUser")
        String getUser(@RequestParam("userId") String userId){
            return  userService.getUser(userId);
        }
    
        @PostMapping("/loginUser")
        String loginUser(@RequestParam("userId") String userId){
            return  userService.loginUser(userId);
    
        }
    
    }
    

    application.yml

    dubbo:
      application:
        name: spring-boot-dubbo-consumer
      protocol:
        name: dubbo
        port: -1
      registry:
        address:  nacos://127.0.0.1:8848
    server:
      port: 9001
    
    

    总结

    依赖的话主要是:

    • dubbo-spring-boot-starter
    • nacos-client

    配置的参数跟dubbo整合spring一样的,只不过这里是使用application来配置而已

    Dubbo整合springcloud

    ​ 首先我们创建两个module,一个是spring-cloud-dubbo和dubbo-api ,dubbo-api跟上面Dubbo整合spring的一样,spring-cloud-dubbo包含了:

    • spring-cloud-dubbo-consumer
    • spring-cloud-dubbo-provider

    整体的pom.xml引入

        <dependencies>
    		
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-dubbo</artifactId>
                <version>2.2.1.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                <version>2.2.1.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>com.onion</groupId>
                <artifactId>dubbo-api</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    

    如果报httpclient依赖问题可以加上一下依赖: (因为我有一台电脑测试的时候就,出现了说缺少httpclient依赖)

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.8</version>
        </dependency>
    

    接口模块

    接口模块,跟上面Dubbo整合spring的一样

    服务提供者模块

    spring-cloud-dubbo-provider 的实现类如下:

    /**
     * @author gyc
     * @date 2020/10/13
     */
    @DubboService
    public class UserService implements  IUserService {
    
        public String getUser(String userId) {
            return "spring cloud getUserId:" + userId;
        }
    
        public String loginUser(String userId) {
            return "spring cloud loginUser:" + userId;
        }
    }
    
    

    启动类配置:

    /**
     * @Author: gyc
     * @Date: 2020/10/12 15:28
     */
    @SpringBootApplication
    @EnableDiscoveryClient   //注册到注册中心的开关
    public class ProviderApp {
    
        public static void main(String[] args) {
            SpringApplication.run(ProviderApp.class,args);
        }
    }
    

    applicaiton.yml配置文件:

    spring:
      cloud:
        nacos:
          discovery:
            register-enabled: true      
            server-addr: 127.0.0.1:8848
      application:
        name: spring-cloud-dubbo-provider
    
    dubbo:
      application:
        name: spring-cloud-dubbo-provider
      protocol:
        name: dubbo    #协议名字
        port: -1
      scan:
        base-packages: com.onion.service
    
    
    • dubbo.scan.base-packages : 指定 Dubbo 服务实现类的扫描基准包
    • dubbo.protocol : Dubbo 服务暴露的协议配置,其中子属性 name 为协议名称,port 为协议端口( -1 表示自增端口,从 20880 开始)
    • spring.cloud.nacos.discovery : Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口

    服务消费者模块

    我们使用web方式来测试,引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
    </dependencies>
    

    controller类:

    @RestController
    @RequestMapping("/user")
    public class UserController {
    
    
        @DubboReference
        private IUserService userService;
    
    
        @GetMapping("/getUser")
        String getUser(@RequestParam("userId") String userId){
    
            return  userService.getUser(userId);
        }
    
        @PostMapping("/loginUser")
        String loginUser(@RequestParam("userId") String userId){
            return  userService.loginUser(userId);
    
        }
    
    
    }
    
    

    启动类:

    /**
     * @Author: gyc
     * @Date: 2020/10/13 10:45
     */
    @SpringBootApplication
    @EnableDiscoveryClient
    public class ConsumerApp {
    
        public static void main(String[] args) {
            SpringApplication.run(ConsumerApp.class,args);
        }
    }
    

    application.yml配置文件

    spring:
      application:
        name: spring-cloud-dubbo-consumer
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
    
    dubbo:
      application:
        name: spring-cloud-dubbo-consumer
      protocol:
        name: dubbo
        port: -1
    
    server:
      port: 9101
    

    总结

    • 对于依赖的话,dubbo和nacos的主要依赖如下:
      • spring-cloud-starter-dubbo
      • spring-cloud-starter-alibaba-nacos-discovery
    • 启动类需要,添加@EnableDiscoveryClient注解
    • application.yml 使用spring.cloud.nacos.discovery指定注册中心的地址, 因为和spring-cloud整合了,所以还是通过spring-cloud注册到注册中心的步骤来做。

    Dubbo支持多传输协议

    使用Rest协议(tomcat作为嵌入式的servlet)和Dubbo协议共用。这个案例是结合spring-boot来的

    这里主要有三个模块

    • dubbo-rest-provider 服务的提供者
    • dubbo-rest-api 提供rest-api接口 接口类为ISayHelloService
    • dubbo-api 提供dubbo接口,接口类为UserService

    依赖引入:

    Rest(servlet嵌入式的依赖)

    	<!-- dubbo和spring boot和nacos 所需要的依赖 -->
    <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
                <version>${spring-boot.version}</version>
            </dependency>
            <dependency>
                <groupId>com.onion</groupId>
                <artifactId>dubbo-api</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba.nacos</groupId>
                <artifactId>nacos-client</artifactId>
                <version>1.3.2</version>
            </dependency>
    
    
    	<!-- rest 所需要的依赖 -->
    <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>resteasy-jaxrs</artifactId>
                <version>${resteasy_version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>resteasy-client</artifactId>
                <version>${resteasy_version}</version>
    
            </dependency>
            <dependency>
                <groupId>javax.validation</groupId>
                <artifactId>validation-api</artifactId>
                <version>${validation_version}</version>
            </dependency>
            
    
    	<!-- tomcat作为servlet的依赖 -->
    
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-core</artifactId>
                <version>8.5.31</version>
            </dependency>
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-logging-juli</artifactId>
                <version>8.5.2</version>
            </dependency>
    
    
    

    注意: dubbo如果使用rest协议,默认使用jetty的。想用jetty作为嵌入式的servlet的话需要加入以下的依赖

    <!-- jetty作为servlet的依赖 -->
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>9.4.19.v20190610</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-servlet</artifactId>
      <version>9.4.19.v20190610</version>
    </dependency>
    

    接口模块:

    定义一个restApi的接口

    @Path("/")
    public interface ISayHelloService {
    
        @GET
        @Path("/say")
        public String say();
    
    }
    

    服务提供者模块

    application.yml配置文件

    dubbo:
      application:
        name: spring-boot-dubbo-provider
      registry:
        address:  nacos://127.0.0.1:8848
      scan:
        base-packages: com.onion.service
      protocols: 
        rest:
          name: rest
          port: 5566
          server: tomcat   #如果是jetty的话,这里填jetty
        dubbo:
          name: dubbo
          port: -1
    
    
    • dubbo.protocols 可以配置多个协议

    实现类:

    @DubboService(protocol = "rest")
    public class SayHelloService implements ISayHelloService {
        public String say() {
            return "say ge hi";
        }
    }
    
    
    @DubboService(protocol = "dubbo")
    public class UserService implements IUserService {
    
        public String getUser(String userId) {java
            return "spring boot getUserId:" + userId;
        }
    
        public String loginUser(String userId) {
            return "spring boot loginUser:" + userId;
        }
    }
    

    这里可以通过@DubboService(protocol = "") 来指定传输协议。

    现象展示

    可以使用浏览器调用rest协议的信息,地址是:http://127.0.0.1:5566/say

    image-20201018172031620

    查看nacos中注册上来的信息

    dubbo:

    image-20201018171826580

    rest:

    image-20201018171753463

    ​ 可以看到protocol是不同的一个是dubbo,一个是rest,而且rest协议还是告诉你用的sever是什么,上图就是server=tomcat

    参考文档:

    http://dubbo.apache.org/zh-cn/blog/dubbo-rest.html

    Dubbo的负载均衡(引用官网的解析)

    引用官网的地址是:

    http://dubbo.apache.org/zh-cn/docs/source_code_guide/loadbalance.html

    RandomLoadBalance(加权随机算法)

    ​ RandomLoadBalance 是加权随机算法的具体实现,它的算法思想很简单。假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上。比如数字3会落到服务器 A 对应的区间上,此时返回服务器 A 即可。权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会有更大的概率落到此区间内。只要随机数生成器产生的随机数分布性很好,在经过多次选择后,每个服务器被选中的次数比例接近其权重比例。比如,经过一万次选择后,服务器 A 被选中的次数大约为5000次,服务器 B 被选中的次数约为3000次,服务器 C 被选中的次数约为2000次。

    缺点:

    ​ 当然 RandomLoadBalance 也存在一定的缺点,当调用次数比较少时,Random 产生的随机数可能会比较集中,此时多数请求会落到同一台服务器上。这个缺点并不是很严重,多数情况下可以忽略。RandomLoadBalance 是一个简单,高效的负载均衡实现,因此 Dubbo 选择它作为缺省实现。

    LeastActiveLoadBalance(最小活跃数负载均衡)

    ​ LeastActiveLoadBalance 翻译过来是最小活跃数负载均衡。活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求。此时应优先将请求分配给该服务提供者。在具体实现中,每个服务提供者对应一个活跃数 active。初始情况下,所有服务提供者活跃数均为0。每收到一个请求,活跃数加1,完成请求后则将活跃数减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求、这就是最小活跃数负载均衡算法的基本思想。除了最小活跃数,LeastActiveLoadBalance 在实现上还引入了权重值。所以准确的来说,LeastActiveLoadBalance 是基于加权最小活跃数算法实现的。举个例子说明一下,在一个服务提供者集群中,有两个性能优异的服务提供者。某一时刻它们的活跃数相同,此时 Dubbo 会根据它们的权重去分配请求,权重越大,获取到新请求的概率就越大。如果两个服务提供者权重相同,此时随机选择一个即可

    ConsistentHashLoadBalance(一致性 hash 算法)

    一致性 hash 算法由麻省理工学院的 Karger 及其合作者于1997年提出的,算法提出之初是用于大规模缓存系统的负载均衡。它的工作过程是这样的,首先根据 ip 或者其他的信息为缓存节点生成一个 hash,并将这个 hash 投射到 [0, 232 - 1] 的圆环上。当有查询或写入请求时,则为缓存项的 key 生成一个 hash 值。然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。大致效果如下图所示,每个缓存节点在圆环上占据一个位置。如果缓存项的 key 的 hash 值小于缓存节点 hash 值,则到该缓存节点中存储或读取缓存项。比如下面绿色点对应的缓存项将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项最终会存储到 cache-4 节点中。

    img

    下面来看看一致性 hash 在 Dubbo 中的应用。我们把上图的缓存节点替换成 Dubbo 的服务提供者,于是得到了下图:

    img

    这里相同颜色的节点均属于同一个服务提供者,比如 Invoker1-1,Invoker1-2,……, Invoker1-160。这样做的目的是通过引入虚拟节点,让 Invoker 在圆环上分散开来,避免数据倾斜问题。所谓数据倾斜是指,由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。比如:

    consistent-hash-data-incline

    如上,由于 Invoker-1 和 Invoker-2 在圆环上分布不均,导致系统中75%的请求都会落到 Invoker-1 上,只有 25% 的请求会落到 Invoker-2 上。解决这个问题办法是引入虚拟节点,通过虚拟节点均衡各个节点的请求量。

    RoundRobinLoadBalance(加权轮询负载均衡)

    本节,我们来看一下 Dubbo 中加权轮询负载均衡的实现 RoundRobinLoadBalance。在详细分析源码前,我们先来了解一下什么是加权轮询。这里从最简单的轮询开始讲起,所谓轮询是指将请求轮流分配给每台服务器。举个例子,我们有三台服务器 A、B、C。我们将第一个请求分配给服务器 A,第二个请求分配给服务器 B,第三个请求分配给服务器 C,第四个请求再次分配给服务器 A。这个过程就叫做轮询。轮询是一种无状态负载均衡算法,实现简单,适用于每台服务器性能相近的场景下。但现实情况下,我们并不能保证每台服务器性能均相近。如果我们将等量的请求分配给性能较差的服务器,这显然是不合理的。因此,这个时候我们需要对轮询过程进行加权,以调控每台服务器的负载。经过加权后,每台服务器能够得到的请求数比例,接近或等于他们的权重比。比如服务器 A、B、C 权重比为 5:2:1。那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的2次请求,服务器 C 则收到其中的1次请求。

    集群容错

    简介

    ​ 为了避免单点故障,现在的应用通常至少会部署在两台服务器上。对于一些负载比较高的服务,会部署更多的服务器。这样,在同一环境下的服务提供者数量会大于1。对于服务消费者来说,同一环境下出现了多个服务提供者。这时会出现一个问题,服务消费者需要决定选择哪个服务提供者进行调用。另外服务调用失败时的处理措施也是需要考虑的,是重试呢,还是抛出异常,亦或是只打印异常等。为了处理这些问题,Dubbo 定义了集群接口 Cluster 以及 Cluster Invoker。集群 Cluster 用途是将多个服务提供者合并为一个 Cluster Invoker,并将这个 Invoker 暴露给服务消费者。这样一来,服务消费者只需通过这个 Invoker 进行远程调用即可,至于具体调用哪个服务提供者,以及调用失败后如何处理等问题,现在都交给集群模块去处理。集群模块是服务提供者和服务消费者的中间层,为服务消费者屏蔽了服务提供者的情况,这样服务消费者就可以专心处理远程调用相关事宜。比如发请求,接受服务提供者返回的数据等。这就是集群的作用。

    使用方式

    @DubboService(cluster="failover") cluster中填写相应的容错方式,就可以使用了。

    容错方式

    Dubbo 主要提供了这样几种容错方式:

    • Failover Cluster - 失败自动切换

      @DubboService(cluster="failover",retires=2) //retires代表重试的次数

      这个需要,服务端提供幂等性的操作,不然可能会出现重复调用

    • Failfast Cluster - 快速失败,立马报错

    • Failsafe Cluster - 失败安全, 出现异常,直接吞掉

    • Failback Cluster - 失败自动恢复,记录失败请求,定时重发

    • Forking Cluster - 并行调用多个服务提供者,只要其中一个成功返回,那么就直接返回结果.

    服务降级

    服务降级只需要在配置@DubboReference(mock = "")就可以了。mock里面带上类的名字

    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @DubboReference(mock = "com.onion.fallback.UserFallback")
        private IUserService userService;
    
    	...
    
    
    }
    
    public class UserFallback implements IUserService {
    
        public String getUser(String userId) {
            return "getUser::服务降级返回";
        }
    
        public String loginUser(String userId) {
            return "loginUser::服务降级返回";
        }
    }
    
    

    服务注册的地址问题

    ServiceConfig类中的findConfigedHosts方法获取ip信息:

    • DUBBO_DUBBO_IP_TO_BIND 环境变量
    • 配置文件中:dubbo.protocol.host
    • 配置文件中:dubbo.provider.host
    • InetAddress.getLocalHost().getHostAddress()
    • 通过socket连接注册到注册中心的URL,来得到IP
    • 通过遍历本机各个网卡,得到合适的网卡IP

    这上面得到hostToBind(本机绑定的ip),优先级是从上到下。

    hostToRegistry(注册到注册中心的ip)的获取(优先级是从上到下)

    • DUBBO_DUBBO_IP_TO_REGISTRY 环境变量
    • hostToRegistry = hostToBind;

    如果DUBBO_DUBBO_IP_TO_REGISTRY 环境变量为空则取hostToBind(本机绑定的ip)。

    ServiceConfig的源码分析:

    public class ServiceConfig<T> extends ServiceConfigBase<T> {
     
    private String findConfigedHosts(ProtocolConfig protocolConfig, List<URL> registryURLs, Map<String, String> map) {
            boolean anyhost = false;
        	//如果协议是dubbo的话,获取环境中的DUBBO_DUBBO_IP_TO_BIND 
            String hostToBind = this.getValueFromConfig(protocolConfig, "DUBBO_IP_TO_BIND");
            if (hostToBind != null && hostToBind.length() > 0 && NetUtils.isInvalidLocalHost(hostToBind)) {
                throw new IllegalArgumentException("Specified invalid bind ip from property:DUBBO_IP_TO_BIND, value:" + hostToBind);
            } else {
                if (StringUtils.isEmpty(hostToBind)) {
                    //获取配置文件中:dubbo.protocol.host
                    hostToBind = protocolConfig.getHost();
                    if (this.provider != null && StringUtils.isEmpty(hostToBind)) {
                        //获取配置文件中:dubbo.provider.host
                        hostToBind = this.provider.getHost();
                    }
    
                    if (NetUtils.isInvalidLocalHost(hostToBind)) {
                        anyhost = true;
    
                        try {
                            logger.info("No valid ip found from environment, try to find valid host from DNS.");
                            //利用Api来获取InetAddress.getLocalHost().getHostAddress()
                            hostToBind = InetAddress.getLocalHost().getHostAddress();
                        } catch (UnknownHostException var22) {
                            logger.warn(var22.getMessage(), var22);
                        }
    
                        if (NetUtils.isInvalidLocalHost(hostToBind)) {
                            if (CollectionUtils.isNotEmpty(registryURLs)) {
                                Iterator var6 = registryURLs.iterator();
    
                                label185:
                                while(true) {
                                    URL registryURL;
                                    do {
                                        if (!var6.hasNext()) {
                                            break label185;
                                        }
    
                                        registryURL = (URL)var6.next();
                                    } while("multicast".equalsIgnoreCase(registryURL.getParameter("registry")));
    
                                    try {
                                        Socket socket = new Socket();
                                        Throwable var9 = null;
    
                                        try {
                                            //通过socket连接注册到注册中心的URL,来得到IP
                                            SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
                                            socket.connect(addr, 1000);
                                            hostToBind = socket.getLocalAddress().getHostAddress();
                                            break;
                                        } catch (Throwable var21) {
                                            var9 = var21;
                                            throw var21;
                                        } finally {
                                      	...
                                    } catch (Exception var24) {
                                        logger.warn(var24.getMessage(), var24);
                                    }
                                }
                            }
    
                            if (NetUtils.isInvalidLocalHost(hostToBind)) {
                                //通过遍历本机各个网卡,得到合适的网卡IP
                                hostToBind = NetUtils.getLocalHost();
                            }
                        }
                    }
                }
    
                map.put("bind.ip", hostToBind);
                 //获取DUBBO_DUBBO_IP_TO_REGISTRY 环境变量,
                String hostToRegistry = this.getValueFromConfig(protocolConfig, "DUBBO_IP_TO_REGISTRY");
                if (hostToRegistry != null && hostToRegistry.length() > 0 && NetUtils.isInvalidLocalHost(hostToRegistry)) {
                    throw new IllegalArgumentException("Specified invalid registry ip from property:DUBBO_IP_TO_REGISTRY, value:" + hostToRegistry);
                } else {
                    //如果DUBBO_DUBBO_IP_TO_REGISTRY 环境变量为空得话,那么就把hostToRegistry设为hostToBind
                    if (StringUtils.isEmpty(hostToRegistry)) {
                        hostToRegistry = hostToBind;
                    }
    
                    map.put("anyhost", String.valueOf(anyhost));
                    return hostToRegistry;
                }
            }
        }
    
        private String getValueFromConfig(ProtocolConfig protocolConfig, String key) {
            String protocolPrefix = protocolConfig.getName().toUpperCase() + "_";
            String value = ConfigUtils.getSystemProperty(protocolPrefix + key);
            if (StringUtils.isEmpty(value)) {
                value = ConfigUtils.getSystemProperty(key);
            }
    
            return value;
        }
    
    }
    
  • 相关阅读:
    Win10删除anaconda重装
    anaconda python no module named 'past'的解决方法
    detectMultiScale 读取冲突的一个解决方法
    [原] Android快速开发框架-AndroidFine,GitHub开源
    [原] Android 自定义View 密码框 例子
    [原]发布一个jQuery提示框插件,Github开源附主站,jquery.tooltips.js
    [原] Jenkins Android 自动打包配置
    [原] Android性能优化方法
    阿里云9折推荐码:0LGVW2
    [原]那些年整理的Linux常用命令,简单明了
  • 原文地址:https://www.cnblogs.com/dabenxiang/p/13898031.html
Copyright © 2011-2022 走看看