dubbo使用和配置讲解

 

1. 分布式系统中相关概念

1.1 互联网项目特点及目标

1.1.1 特点:

  • 用户多

  • 流量大、并发高

  • 海量数据

  • 易受攻击

  • 功能繁琐

  • 变更快

 

1.1.2 指标及相关目标

互联网项目三高目标:高并发、高可用(99.999%)、高可拓展

其他可伸缩安全性敏捷性

1.2 集群&分布式

集群:很多人一起干,干一样的事。一个业务模块,部署在多个服务器上。是一个横向概念。

分布式:很多人一起干,干不一样的事。这些不一样的事,合起来是一件大事。

一个大的业务系统,拆分为多个小的不同的业务模块,分别部署在各个机器上。是一个纵向的概念。

 

集群和分布式能初步实现互联网项目的三高目标。

  • 生活中的分布式和集群

  • 开发中的分布式和集群

1.3. 软件架构的演进过程

 

 

 

 

 

 

1.3.1 工作中如何选用软件架构

如果要使用并实现微服务的架构,整体的技术成本、人力成本、硬件成本、维护成本都会很高。

  • 公司/项目非常小,非常适合使用单体架构

  • 中型的公司/项目,可以采用垂直/SOA架构

  • 大型的公司/项目(25+人维护开发一个项目),考虑微服务系统架构

 

1.3.2 Dubbo&SpringCloud

Dubbo主要是为了解决SOA架构下,服务治理的中间件。

SpringCloud主要的应用场景就是在微服务的架构之下。

 

 

2. Dubbo简单概述

2.1 概念

webservice拆分出来分别部署到不同的服务器上,dubbo为了解决远程调用时候的各种问题的,底层是基于长连接的方式实现RPC。

Dubbo是一个基于Spring开发的小项目,可以和Spring无缝整合。

 

Dubbo是阿里巴巴公司开源的一个高性能、轻量级的 Java RPC 框架。

• 致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案。

了解:

2011年,阿里巴巴开源了自己的SOA服务化治理方案的核心框架Dubbo,SOA架构开始在国内流行。做的是横向拆分。

在开源之前已经在阿里巴巴内部广泛使用,开源之后,由于他出色的表现,很快被很多大公司使用,eg:当当、网易考拉等等。

dubbo的前身是基于Spring开发的一个项目,所以可以和Spring无缝衔接,中间停更过一段时间,后来又开始更新并捐给了Apache。

最重要的,他有中文版本的文档,特别友好。通过快速入门体验dubbo的特点,通过API 文档可以轻松的学习dubbo中所有的内容。

 

 

2.2 Zookeeper注册中心

2.2.1 简单介绍

Zookeeper 是 Apache Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用。对应官网围挡说明位置:

文档/Dubbo2.7/用户文档/参考手册/注册中心参考手册

 

 

2.2.2 安装

linuxwindows使用的是同一个安装包,绿色,解压可用。

windows下安装
  1. 解压,存放在开发软件目录(路径中不要用中文、空格等特殊符号)

  2. conf目录,复制zoo_sample.cfgzoo.cfg

  3. 修改配置文件中如下两点:

    # 数据保存路径,建议在安装目录新建一个数据文件夹,并在此 指定
    dataDir=D:\Develop\soft\apache-zookeeper-3.5.6-bin\zkData

    # netty port
    # netty服务(web容器)的端口,建议修改为非8080端口
    admin.serverPort=8888
  4. 双击bin目录下 zkServer.cmd即可启动,出现如下内容即为启动成功。默认端口2181。

2.3 Dubbo架构及执行流程

 

 

3 Dubbo快速入门

 

3.1 分模块编写单体架构项目

分模块开发项目,两个模块中分别包含web层和service

dubbo-service

dubbo-web

 

3.1.1dubbo-service

pom.xml

  <properties>
       <spring.version>5.1.9.RELEASE</spring.version>
   </properties>

   <dependencies>
       <!-- servlet3.0规范的坐标 -->
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>javax.servlet-api</artifactId>
           <version>3.1.0</version>
           <scope>provided</scope>
       </dependency>
       <!--spring的坐标-->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context</artifactId>
           <version>${spring.version}</version>
       </dependency>
       <!--springmvc的坐标-->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-webmvc</artifactId>
           <version>${spring.version}</version>
       </dependency>

       <!--日志-->
       <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-api</artifactId>
           <version>1.7.21</version>
       </dependency>
       <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-log4j12</artifactId>
           <version>1.7.21</version>
       </dependency>
   </dependencies>

 

applicationcontext.xml

<context:component-scan base-package="com.yuyy.service" />

 

日志配置文件log4j.properties

# 略

 

 

UserServiceImpl及其接口

public interface UserService {
   public String sayHello();
}

@Service
public class UserServiceImpl implements UserService {

   public String sayHello() {
       return "hello dubbo!~";
  }
}

 

3.1.1dubbo-web

pom.xml

    <groupId>com.yuyy</groupId>
   <artifactId>dubbo-web</artifactId>
   <version>1.0-SNAPSHOT</version>
   <packaging>war</packaging>


   <properties>
       <spring.version>5.1.9.RELEASE</spring.version>
   </properties>

   <dependencies>
       <!-- servlet3.0规范的坐标 -->
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>javax.servlet-api</artifactId>
           <version>3.1.0</version>
           <scope>provided</scope>
       </dependency>
       <!--spring的坐标-->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context</artifactId>
           <version>${spring.version}</version>
       </dependency>
       <!--springmvc的坐标-->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-webmvc</artifactId>
           <version>${spring.version}</version>
       </dependency>

       <!--日志-->
       <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-api</artifactId>
           <version>1.7.21</version>
       </dependency>
       <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-log4j12</artifactId>
           <version>1.7.21</version>
       </dependency>

       <!--依赖service模块-->
       <dependency>
           <groupId>com.yuyy</groupId>
           <artifactId>dubbo-service</artifactId>
           <version>1.0-SNAPSHOT</version>
       </dependency>

   </dependencies>


   <build>
       <plugins>
           <!--tomcat插件-->
           <plugin>
               <groupId>org.apache.tomcat.maven</groupId>
               <artifactId>tomcat7-maven-plugin</artifactId>
               <version>2.1</version>
               <configuration>
                   <port>8000</port>
                   <path>/</path>
               </configuration>
           </plugin>
       </plugins>
   </build>

 

springmvc.xml

<mvc:annotation-driven/>
<context:component-scan base-package="com.yuyy.controller"/>

<!--dubbo的配置-->
<!--配置项目的名称-->
<dubbo:application name="dubbo_web">
       <dubbo:parameter key="qos.port" value="33333"/>
</dubbo:application>
<!--配置注册中心的地址-->
<dubbo:registry address="zookeeper://192.168.23.190:2181"/>
<!--配置dubbo包扫描-->
<dubbo:annotation package="com.yuyy.controller"/>

 

web.xml

<servlet>
   <servlet-name>springmvc</servlet-name>
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
   <!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载-->
   <init-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>classpath:spring/springmvc.xml</param-value>
   </init-param>
</servlet>

<servlet-mapping>
   <servlet-name>springmvc</servlet-name>
   <url-pattern>*.do</url-pattern>
</servlet-mapping>

UserController.java

@RestController
@RequestMapping("/user")
public class UserController {

    //注入Service
    //@Autowired//本地注入
    private UserService userService;


    @RequestMapping("/sayHello")
    public String sayHello(){
        return userService.sayHello();
    }

}

 

 

 

3.2 改造service成为服务提供者

3.2.1 添加依赖

<properties>
    <dubbo.version>2.7.4.1</dubbo.version>
    <zookeeper.version>4.0.0</zookeeper.version>
</properties>

<!--Dubbo的起步依赖,版本2.7之后统一为org.apache.dubbo -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>${dubbo.version}</version>
</dependency>
<!--ZooKeeper客户端实现 
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>${zookeeper.version}</version>
</dependency>
-->
<!--ZooKeeper客户端实现 -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>${zookeeper.version}</version>
</dependency>

 

3.2.2 修改成web项目

修改pompackaing打包方式为war,添加tomcat插件

<packaging>war</packaging>

<!--tomcat插件-->
<plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.1</version>
    <configuration>
       <!-- 端口一定要和web模块的端口不同,避免端口冲突 -->
        <port>9000</port>
        <path>/</path>
    </configuration>
</plugin>

 

3.2.3 发布服务

service层服务实现类上修改如下(注意service注解的包,是dubbo的)

import org.apache.dubbo.config.annotation.Service;

@Service//将这个类提供的方法(服务)对外发布。将访问的地址 ip,端口,路径注册到注册中心中
public class UserServiceImpl implements UserService {

    public String sayHello() {
        return "hello dubbo hello!~";
    }
}

 

 

3.2.4 配置dubbo服务相关配置

spring核心配置文件中配置。命名空间可以让idea提示引入,但是要注意不要引错。

<!--<context:component-scan base-package="com.yuyy.service" />-->

<!--dubbo的配置-->
<!--1.配置项目的名称,唯一-->
<dubbo:application name="dubbo-service"/>
<!--2.配置注册中心的地址-->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!--3.配置dubbo组件扫描-->
<dubbo:annotation package="com.yuyy.service.impl" />

 

 

 

3.3 改造web成为服务消费者

3.3.1 添加依赖

<properties>
    <dubbo.version>2.7.4.1</dubbo.version>
    <zookeeper.version>4.0.0</zookeeper.version>
</properties>

<!--Dubbo的起步依赖,版本2.7之后统一为rg.apache.dubbo -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>${dubbo.version}</version>
</dependency>
<!--ZooKeeper客户端实现 -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>${zookeeper.version}</version>
</dependency>
<!--ZooKeeper客户端实现 -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>${zookeeper.version}</version>
</dependency>

 

3.3.2 添加service层接口

解除了web模块对本地service模块的依赖,UserController中还是用到了UserService类型的对象。

所以临时解决方案是在当前模块中添加了一个和service模块中一样的一个UserService接口:

public interface UserService {
    public String sayHello();
}

 

 

3.3.3 寻找服务

web层服务实现类controller类上修改如下(注意@Reference注解的包,是dubbo的)

@RestController
@RequestMapping("/user")
public class UserController {

    //注入Service
    //@Autowired//本地注入

    /*
        1. 从zookeeper注册中心获取userService的访问url
        2. 进行远程调用RPC
        3. 将结果封装为一个代理对象。给变量赋值

     */

    @Reference//远程注入
    private UserService userService;


    @RequestMapping("/sayHello")
    public String sayHello(){
        return userService.sayHello();
    }

}

 

 

3.3.4 配置dubbo服务相关配置

命名空间可以让idea提示引入,但是要注意不要引错。

<!--原有的组件扫描和注解驱动保持不变-->
<mvc:annotation-driven/>
<context:component-scan base-package="com.yuyy.controller"/>

<!--dubbo的配置-->
<!--1.配置项目的名称,唯一-->
<dubbo:application name="dubbo-web" >
    <dubbo:parameter key="qos.port" value="33333"/>
</dubbo:application>
<!--2.配置注册中心的地址-->
<dubbo:registry address="zookeeper://192.168.149.135:2181"/>
<!--3.配置dubbo包扫描-->
<dubbo:annotation package="com.yuyy.controller" />

 

3.2.5 QOS服务端口冲突

同一台电脑上启动多个Dubbo服务(无论是服务提供者,还是消费者),都会启动一个QOS(quality of Server)服务,占用相同端口22222,修改任意一个即可。

 

 

3.4 抽取共性代码

服务提供者模块和服务消费者模块都使用到了UserService,那就抽取到一个单独的模块,并把UserService接口在该模块中定义,该模块打包方式是jar(默认值)。

两个模块都引入接口模块的依赖,直接使用其中的接口即可。

 

使用

Dubbo占用的端口

服务端口:20880

qos默认端口:22222(如果启动了多个dubbo项目,需要修改成不一样)

zookeeper默认端口:2181

zookeeper中web容器netty的默认端口:8080(建议修改成其他的)

 

 

4. Dubbo-admin

4.1 安装及启动

按照文档安装即可,网络不好的情况下,可以使用安装好的jar包,直接启动,双击bat即可启动,使用8080端口访问。

4.2 使用

4.2.1 元数据

我们需要打开我们的提供者provider配置文件加入下面配置

    <!-- 元数据配置 -->
    <dubbo:metadata-report address="zookeeper://localhost:2181" />

重新启动生产者,再次打开Dubbo-Admin

4.2.2 默认账密

登录地址:http://localhost:8080,默认账密:root/root

 

5. 高级特性

5.1 序列化

对象想要存储到硬盘持久化保存,或者通过网络在多个项目间传输,需要实现序列化接口(Serializable

/**
 * 注意!!!
 *  将来所有的pojo类都需要实现Serializable接口
 */
public class User implements Serializable {
    private int id;
    private String username;
    private String password;
}

 

服务提供者,添加方法查询到一个User对象:

@Service//将这个类提供的方法(服务)对外发布。将访问的地址 ip,端口,路径注册到注册中心中
public class UserServiceImpl implements UserService {

    public User findUserById(int id) {
        //查询User对象
        User user = new User(1,"zhangsan","123");
        return user;
    }
    public String sayHello() {
        return "hello dubbo hello!~";
    }
}

 

服务消费者,添加一个方法,调用服务消费者对象获取查询到的User对象

@RestController
@RequestMapping("/user")
public class UserController {

    @Reference//远程注入
    private UserService userService;

    @RequestMapping("/sayHello")
    public String sayHello(){
        return userService.sayHello();
    }

    /**
     * 根据id查询用户信息
     * @param id
     * @return
     */
    @RequestMapping("/find")
    public User find(int id){
        return userService.findUserById(id);
    }
}

 

问题:SpringMVC响应对象到浏览器的时候,User对象为什么不需要序列化?

 

5.2 地址缓存

面试题

如果项目启动了然后注册中心挂掉了,会影响整个项目的运行吗?

不会影响已有的正常服务的调用,但是无法发现并调用新的服务。

 

原理是什么呢?底层原理就是因为地址缓存。

Dubbo服务消费者在第一次调用服务生产者的时候,会从注册中心获取服务提供者地址等信息,保存到本地,以后调用则不需要访问再次注册中心;但是当原有服务提供者更新信息或者新的服务将无法完成注册或更新。

 

5.3 超时

本质为了降低consumer服务器压力,避免带来非常严重的雪崩问题。

要根据实际情况,设置的尽量短一些;根据业务情况尽量控制在5s内。

  1. consumer性能消耗完毕之后可能会产生雪崩。因为消费者请求线程不释放(不设超时时间,且provider出问题不能给出响应),大量占用。

  2. provider只要接受某个请求之后,就必须把流程执行完,或者过了超时时间记录一个warn级别的错误日志。消费端撤销了请求也不会影响服务端的执行。所以要定期查看warn日志,避免大量错误堆积。

  3. consumer超时时间尽量在5s以内,否则消费端服务器线程压力会很大,具体实现要根据服务端响应的平均时长设置。

 

@Service或@Reference注解的timeout属性可以设置超时时间,单位是毫秒,默认值是1000ms。建议在服务提供者端设置超时时间。

retries retry

 

5.4 重试

consumer的请求超时之后,为了保证QOS会重试多次,尽量得到响应数据;默认为2次,最多共访问3次。

provider出现阻塞响应慢的时候,重试机制会造成provider端请求压力倍增;而且provider只要接受一个请求之后,就必须把流程执行完,会造成当前服务器压力倍增,极易造成雪崩。

所以在dubbo优化策略中往往会优化设置重试次数为0;

要多关注前台响应情况和后台providerwarn级别的日志。

//@Service//将该类的对象创建出来,放到Spring的IOC容器中  bean定义
//将这个类提供的方法(服务)对外发布。将访问的地址 ip,端口,路径注册到注册中心中
@Service(timeout = 3000,retries = 2)//当前服务3秒超时,超时后最多重试2次,一共3次
public class UserServiceImpl implements UserService {

    int i = 1;
    public String sayHello() {
        return "hello dubbo hello!~";
    }


    public User findUserById(int id) {
        System.out.println("服务被调用了:"+i++);
        //查询User对象
        User user = new User(1,"zhangsan","123");
        //数据库查询很慢,查了5秒

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return user;
    }
}

 

 

5.5 多版本管理

灰度发布:当provider发布新的版本,会让一部分用户先使用新功能,用户反馈没问时,再将所有用户迁移到新功能。

dubbo 中使用version 属性来设置和调用同一个接口的不同版本。

A消费者

public class UserController1 {
	@Reference(version="1.0")
    private UserService userService;
}

 

B消费者

public class UserController2 {
	@Reference(version="2.0")
    private UserService userService;
}

 

生产者版本1

@Service(version = "v1.0")
public class UserServiceImpl implements UserService {

    public String sayHello() {}

    public User findUserById(int id) {}
}

 

生产者版本2

@Service(version = "v2.0")
public class UserServiceImpl implements UserService {

    public String sayHello() {}

    public User findUserById(int id) {}
}

 

 

 

 

5.6 负载均衡

当服务提供者provider以集群的方式提供服务时;某个请求到达后,到底要哪个provider提供服务,就要根据负载均衡策略分配。

配置方式,在消费者远程注入的注解上,通过loadbalance属性配置(策略名称在AbstractLoadBance的实现类中有定义):

public class UserController {
	@Refrence(loadbalance="random|roundRobin")
    private UserService userService;
}

负载均衡策略共有4个:

  1. random:默认值,按照权重随机分配。权重默认值为100。但有随机性。

  2. RoundRobin:按权限轮训,1/2/3/2。按照顺序调用,受权重影响。

  3. LeastActive:最少活跃调用数,相同活跃数随机。当前负载最低的提供者优先调用。

    某个请求被当前provider调用开始前,计数器+1,调用结束后计数器-1。会优先调用当前计数器的值中最小的provider为本次请求提供服务。

  4. ConsistentHash:一直性hash,相同参数的请求总是发到同一提供者 user/1 user/1000

 

5.7 集群容错

当请求到达服务提供者provider集群时,如果请求超时失败了,该请求如何处理的策略。

  1. 默认failover,失败重试,根据设定的重试次数重试其他机器,默认两次,每次失败换个机器。但是之前的超时会报错。

    通常用于读操作,但重试会带来更长延迟。

  2. Failfast:快速失败,只发起一次调用,立即报错。通常用于写操作,比如新增记录。

  3. Failsafe:安全失败,失败时忽略。通常用于写入审计日志等不重要的操作。

  4. Failback:失败自动回复,后台记录失败请求,定时重发。通常用于必须成功的重要内容,例如消息通知等。

  5. Forking:并发调用多个服务,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。

  6. Broadcast:广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

在服务消费者注入提供者的地方@Reference(cluster="failover")

找到Cluster接口,实现类中有每个方案的名字。

public class UserController {
    @Reference(cluster = FailoverCluster.NAME)//远程注入,通过类常量的方式避免不必要的错误
    private UserService userService;
}

 

5.8 服务降级

可以通过服务降级功能临时屏蔽某个出错的非关键服务。

 

mock="force:return null":消费方对该服务的调用直接返回null,即不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。

mock="fail:return null":表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。

 

在远程注入注解上添加属性实现

public class UserController {
    @Reference(mock="force:return null")//远程注入
    private UserService userService;
}

 

posted @ 2021-01-08 23:22  余一洋  阅读(748)  评论(0编辑  收藏  举报