转载和引用,请注明原文出处! Fork me on GitHub
结局很美妙的事,开头并非如此!

dubbo系列二:dubbo常用功能总结

准备工作:

(1)启动zookeeper作为dubbo的注册中心

(2)新建一个maven的生产者web工程dubbo-provider-web和一个maven的消费者web工程dubbo-consumer-web

(3)在pom.xml文件里面引入如下依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.study</groupId>
    <artifactId>dubbo-provider-web</artifactId>
    <packaging>jar</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>dubbo-provider-web Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <properties>
        <spring.version>4.3.10.RELEASE</spring.version>
    </properties>

    <dependencies>
        <!-- 添加dubbo依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.5.3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 添加zk客户端依赖 -->
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>
        <!-- spring相关 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> 
            <version>${org.springframework.version}</version> </dependency> -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <!-- 日志相关依赖 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.10</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.0.0.GA</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>4.2.0.Final</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>5.0.0.Alpha2</version>
        </dependency>
        
    </dependencies>
    <build>
        <finalName>dubbo-provider-web</finalName>
    </build>
</project>

1. 注解配置

dubbo可以使用注解在生产者端暴露服务接口和在消费端引用接口,只需要在生产者和消费者的配置文件里面配置扫描包路径即可,而不用在xml里面配置需要暴露和引用的接口

扫描包路径的配置

<!-- 扫描注解包路径,多个包用逗号分隔,不填pacakge表示扫描当前ApplicationContext中所有的类 -->
    <dubbo:annotation package="com.study.service" />

1.1 在生产者dubbo-provider-web和消费者dubbo-consumer-web新建一个dubbo注解测试的接口

package com.study.service;

/**
 * 
* @Description: dubbo注解测试的接口
* @author leeSmall
* @date 2018年10月23日
*
 */
public interface AnnotationDubboTest {
    public String eat(String param);
}

1.2 在生产者dubbo-provider-web新建一个dubbo注解测试的接口的实现类

package com.study.service;

import com.alibaba.dubbo.config.annotation.Service;

/**
 * 
* @Description: dubbo注解测试的接口的实现类
* @author leeSmall
* @date 2018年10月23日
*
 */
@Service(timeout = 1000000, version = "1.2.3")
public class AnnotationDubboTestImpl implements AnnotationDubboTest {
    
    public String eat(String param) {
        System.out.println("-----------AnnotationDubboTestImpl service test------------"
                + param);
        return "-----------AnnotationDubboTestImpl service test------------";
    }
    
}

1.3 在消费端dubbo-consumer-web新建一个测试的control

/**
 * 
 * @Description: dubbo消费端测试control
 * @author leeSmall
 * @date 2018年10月23日
 *
 */
@Controller
@RequestMapping("/common")
public class CommonController implements ApplicationContextAware {
    
    private static Logger logger = Logger.getLogger(CommonController.class);
    
    @Reference(check = false, timeout = 100000, version = "1.2.3")
    AnnotationDubboTest annotationdubbo;
    
    @RequestMapping("/annotationdubbo")
    public @ResponseBody String annotationdubbo() {
        annotationdubbo.eat("我是dubbo的注解测试control");
        return "annotationdubbo";
    }

}

1.4 在tomcat8080和tomcat8081分别启动生产者和消费者,在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/annotationdubbo访问查看效果

生产者端:

浏览器:

2. 启动时检查

Dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成,以便上线时,能及早发现问题,默认 check="true" 。
可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
另外,如果你的Spring容器是懒加载的,或者通过API编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到null引用,如果 check="false" ,总是会返回引用,当服务恢复时,能自动连上。

关闭某个服务的启动时检查 (没有提供者时报错):

<dubbo:reference interface="com.foo.BarService" check="false" />

关闭所有服务的启动时检查 (没有提供者时报错):

<dubbo:consumer check="false" />

3. 集群容错

3.1 Failover Cluster

失败自动切换,缺省值,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
配置如下:
生产者:
<dubbo:service retries="2" />
消费者:
<dubbo:reference retries="2" />
消费者具体到调用生产者的哪个方法:
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>

3.2 Failfast Cluster

快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录
配置如下:
生产者:
<dubbo:service cluster="failfast" />
消费者:
<dubbo:reference cluster="failfast" />

3.3 Failsafe Cluster

失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作
配置如下:
生产者:
<dubbo:service cluster="failsafe" />
消费者:
<dubbo:reference cluster="failsafe" />

3.4 Failback Cluster

失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作
配置如下:
生产者:
<dubbo:service cluster="failback" />
消费者:
<dubbo:reference cluster="failback" />

3.5 Forking Cluster

并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
配置如下:
生产者:
<dubbo:service cluster=“forking" />
消费者:
<dubbo:reference cluster=“forking" />

3.6 Broadcast Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息
配置如下:
生产者:
<dubbo:service cluster="broadcast" />
消费者:
<dubbo:reference cluster="broadcast" />

4. 负载均衡

4.1 Random LoadBalance

随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

配置如下:

<dubbo:service interface="..." loadbalance="random" />
或:
<dubbo:reference interface="..." loadbalance="random" />
或:
<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="random"/>
</dubbo:service>
或:
<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="random"/>
</dubbo:reference>

4.2 RoundRobin LoadBalance

轮循,按公约后的权重设置轮循比率。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

配置如下:

<dubbo:service interface="..." loadbalance="roundrobin" />
或:
<dubbo:reference interface="..." loadbalance="roundrobin" />
或:
<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
或:
<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>

4.3 ConsistentHash LoadBalance

一致性 Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
缺省只对第一个参数 Hash,如果要修改,请配置 
<dubbo:parameter key="hash.arguments"value="0,1" />
缺省用160份虚拟节点,如果要修改,
请配置 <dubbo:parameter key="hash.nodes" value="320" />
说明:
hash.arguments:当进行调用时候根据调用方法的哪几个参数生成key,并根据key来通过一致性hash算法来选择调用结点。例如调用方法invoke(String s1,String s2); 若hash.arguments为1(默认值),则仅取invoke的参数1(s1)来生成hashCode。
hash.nodes:为结点的副本数

<dubbo:service interface="..." loadbalance="consistenthash" />
或:
<dubbo:reference interface="..." loadbalance="consistenthash" />
或:
<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="consistenthash"/>
</dubbo:service>
或:
<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="consistenthash"/>
</dubbo:reference>

4.4 LeastActive LoadBalance

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

<dubbo:service interface="..." loadbalance="leastactive" />
或:
<dubbo:reference interface="..." loadbalance="leastactive" />
或:
<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="leastactive"/>
</dubbo:service>
或:
<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="leastactive"/>
</dubbo:reference>

5. 服务分组

当一个接口有多个实现时,可以用group区分要调用的服务。

5.1 服务分组配置方式实现:

在生产者dubbo-provider-web和消费者dubbo-consumer-web分别新建一个接口

package com.study.test.service;

public interface DubboTestService {
    public String eat(String param);
}

在生产者dubbo-provider-web新建两个DubboTestService接口的实现类

实现类1:

package com.study.test.service;

public class DubboTestServiceImpl implements DubboTestService {
    
    public String eat(String param) {
        
        System.out.println("-----------dubbo service test DubboTestServiceImpl ------------" + param);
        return "-----------dubbo service test DubboTestServiceImpl ------------";
    }
    
}

实现类2:

package com.study.test.service;

public class DubboTestService1Impl implements DubboTestService {
    
    public String eat(String param) {
        
        System.out.println("-----------dubbo service test DubboTestService1Impl------------" + param);
        return "-----------dubbo service test DubboTestService1Impl------------";
    }
    
}

在生产者dubbo-provider-web的applicationProvider.xml配置分组

    <!-- 服务分组 -->
    <bean id="dubboTestServiceImpl1" class="com.study.test.service.DubboTestServiceImpl"/>
    <bean id="dubboTestServiceImpl2" class="com.study.test.service.DubboTestService1Impl"/>
    <dubbo:service interface="com.study.test.service.DubboTestService" ref="dubboTestServiceImpl1" group="dubboTestServiceImpl1"/>
    <dubbo:service interface="com.study.test.service.DubboTestService" ref="dubboTestServiceImpl2" group="dubboTestServiceImpl2"/>

在消费者dubbo-consumer-web的applicationConsumer.xml配置调用的分组

    <!--服务分组  -->
    <dubbo:reference id="dubboTestServiceImpl1" interface="com.study.test.service.DubboTestService" check="false" retries="4" cluster="failover" group="dubboTestServiceImpl1"/>
    <dubbo:reference id="dubboTestServiceImpl2" interface="com.study.test.service.DubboTestService" check="false" retries="4" cluster="failover" group="dubboTestServiceImpl2"/>
    

在消费者dubbo-consumer-web的CommonController.java里面创建服务分组测试代码

//服务分组示例begin
    @Autowired
    @Qualifier("dubboTestServiceImpl1")
    DubboTestService dubboService1;
    
    @Autowired
    @Qualifier("dubboTestServiceImpl2")
    DubboTestService dubboService2;
    
    @RequestMapping("/dubboTest")
    public @ResponseBody String dubboTest() {
        dubboService1.eat("服务分组示例!");
        dubboService2.eat("服务分组示例!");
        return "dubboTest";
    }
    //服务分组示例end

在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/dubboTest访问查看效果

生产者:

浏览器:

5.2 服务分组注解方式实现:

 在生产者dubbo-provider-web和消费者dubbo-consumer-web分别新建一个接口

package com.study.service;

public interface UserService {
    
    public String login(String param);
    
}

在生产者dubbo-provider-web新建两个UserService接口的实现类

实现类1:

package com.study.service;

import org.apache.log4j.Logger;

import com.alibaba.dubbo.config.annotation.Service;

@Service(version = "1.0.2", group = "user2")
public class UserService2Impl implements UserService {
    
    private static Logger logger = Logger.getLogger(UserService2Impl.class);
    
    public String login(String param) {
        logger.info("UserService2Impl.login  begin!22222");
        return "用户已经登录成功!·~~~~~~~~~~~~~~~~~~";
    }
    
    
}

 实现类2:

package com.study.service;

import org.apache.log4j.Logger;

import com.alibaba.dubbo.config.annotation.Service;

@Service(version = "1.0.2", group = "user1")
public class UserServiceImpl implements UserService {
    
    private static Logger logger = Logger.getLogger(UserServiceImpl.class);
    
    public String login(String param) {
        logger.info("UserServiceImpl.login  begin!");
        return "用户已经登录成功!·~~~~~~~~~~~~~~~~~~";
    }
    
}

 在消费者dubbo-consumer-web的CommonController.java里面创建服务分组测试代码

//服务分组注解实现示例begin
    @Reference(version = "1.0.2", check = false, group = "user1")
    UserService usrService;
    
    @Reference(version = "1.0.2", check = false, group = "user2")
    UserService usr2Service;
    
    @RequestMapping("/grouplogin1")
    public @ResponseBody String login() {
        logger.info(usrService.login("服务分组注解实现"));
        logger.info(usr2Service.login("服务分组注解实现"));
        return "成功";
    }
    //服务分组注解实现示例end

在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/grouplogin1访问查看效果

生产者:

 

消费者:

6. 多版本

当一个接口实现出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
1)在低压力时间段,先升级一半提供者为新版本
2)再将所有消费者升级为新版本
3)然后将剩下的一半提供者升级为新版本
老版本服务提供者配置:
<dubbo:service interface="com.foo.BarService" version="1.0.0" />

新版本服务提供者配置:
<dubbo:service interface="com.foo.BarService" version="2.0.0" />

老版本服务消费者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />

新版本服务消费者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />

如果不需要区分版本,可以按照以下的方式配置 :
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />

7. 参数验证

在生产者dubbo-provider-web和消费者dubbo-consumer-web的pom.xml里面分别引入如下依赖:

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.0.0.GA</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>4.2.0.Final</version>
        </dependency>

在生产者dubbo-provider-web和消费者dubbo-consumer-web分别新建一个参数验证接口和一个参数验证实体

参数验证接口:

package com.study.service;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

/**
 * 
 * @Description: 参数验证接口
 *  缺省可按服务接口区分验证场景,如:@NotNull(groups = ValidationService.class)
 * @author leeSamll
 * @date 2018年10月23日
 *
 */
public interface ValidationService { 

    void save(ValidationParameter parameter);
    
    void update(ValidationParameter parameter);
    
    void delete(
            @Min(1) long id,
            @NotNull @Size(min = 2, max = 16) @Pattern(regexp = "^[a-zA-Z]+$") String operator);
    
    // 与方法同名接口,首字母大写,用于区分验证场景,如:@NotNull(groups = ValidationService.Save.class),可选
    @interface Save {
    } 
    
    // 与方法同名接口,首字母大写,用于区分验证场景,如:@NotNull(groups = ValidationService.Update.class),可选
    @interface Update {
    } 
    
}

参数验证实体:

package com.study.service;

import java.io.Serializable;
import java.util.Date;

import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

/**
 * 
 * @Description: 参数验证实体
 * @author leeSamll
 * @date 2018年10月23日
 *
 */
public class ValidationParameter implements Serializable {
    
    private static final long serialVersionUID = 7158911668568000392L;
    
    // 不允许为空
    @NotNull
    // 长度或大小范围
    @Size(min = 2, max = 20)
    private String name;
    
    @NotNull(groups = ValidationService.Save.class)
    // 保存时不允许为空,更新时允许为空 ,表示不更新该字段
    @Pattern(regexp = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$")
    private String email;
    
    // 最小值
    @Min(18)
    // 最大值
    @Max(100)
    private int age;
    
    // 必须为一个过去的时间
    @Past
    private Date loginDate;
    
    // 必须为一个未来的时间
    @Future
    private Date expiryDate;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getEmail() {
        return email;
    }
    
    public void setEmail(String email) {
        this.email = email;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
    
    public Date getLoginDate() {
        return loginDate;
    }
    
    public void setLoginDate(Date loginDate) {
        this.loginDate = loginDate;
    }
    
    public Date getExpiryDate() {
        return expiryDate;
    }
    
    public void setExpiryDate(Date expiryDate) {
        this.expiryDate = expiryDate;
    }
    
}

在生产者dubbo-provider-web新建ValidationService接口的实现类

package com.study.service;

/**
 * 
 * @Description: 参数验证接口实现类
 * @author leeSamll
 * @date 2018年10月23日
 *
 */
public class ValidationServiceImpl implements ValidationService {
    
    public void save(ValidationParameter parameter) {
        System.out.println("save");
    }
    
    public void update(ValidationParameter parameter) {
    }
    
    public void delete(long id, String operator) {
        System.out.println("delete");
    }
    
}

在生产者dubbo-provider-web的applicationProvider.xml配置参数验证接口:

    <!--参数验证begin  -->
    <bean id="validationService" class="com.study.service.ValidationServiceImpl"/>
    <dubbo:service interface="com.study.service.ValidationService" ref="validationService"
        validation="true"/>
    <!--参数验证end  -->

在消费者dubbo-consumer-web的applicationConsumer.xml配置调用的参数验证接口

    <!--参数验证begin  -->
    <dubbo:reference id="validationService" interface="com.study.service.ValidationService"
        validation="true"/>
    <!--参数验证end  -->

在消费者dubbo-consumer-web的CommonController.java里面创建参数验证测试代码

    //参数验证begin
    @Autowired
    ValidationService validationService;
    
    @RequestMapping("/validation")
    public @ResponseBody String validation() {
        // Save OK
        ValidationParameter parameter = new ValidationParameter();
        parameter.setName("leeSmall");
        parameter.setEmail("leeSmall@qq.com");
        parameter.setAge(50);
        parameter.setLoginDate(new Date(System.currentTimeMillis() - 1000000));
        parameter.setExpiryDate(new Date(System.currentTimeMillis() + 1000000));
        validationService.save(parameter);
        System.out.println("Validation Save OK");
        
        // Save Error
        try {
            parameter = new ValidationParameter();
            validationService.save(parameter);
            System.err.println("Validation Save ERROR");
        }
        catch (RpcException e) {
            ConstraintViolationException ve = (ConstraintViolationException)e.getCause();
            Set<ConstraintViolation<?>> violations = ve.getConstraintViolations();
            System.out.println(violations);
        }
        
        // Delete OK
        validationService.delete(2, "abc");
        System.out.println("Validation Delete OK");
        
        // Delete Error
        try {
            validationService.delete(0, "abc");
            System.err.println("Validation Delete ERROR");
        }
        catch (RpcException e) {
            ConstraintViolationException ve = (ConstraintViolationException)e.getCause();
            Set<ConstraintViolation<?>> violations = ve.getConstraintViolations();
            System.out.println(violations);
        }
        return "OK";
    }
    //参数验证end

在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/validation访问查看效果

生产者:

消费者:

8. 结果缓存

缓存类型
lru 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。

LRU的缺省cache.size为1000,执行1001次,会把最开始请求的缓存结果清除掉

在生产者dubbo-provider-web和消费者dubbo-consumer-web分别新建一个接口

package com.study.service;

/**
 * 
 * @Description: 结果缓存接口
 * @author leeSmall
 * @date 2018年10月24日
 *
 */
public interface CacheService {
    
    String findCache(String id);
    
}

在生产者dubbo-provider-web新建CacheService接口的实现类

package com.study.service;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 
 * @Description: 结果缓存接口实现类
 * @author leeSmall
 * @date 2018年10月24日
 *
 */
public class CacheServiceImpl implements CacheService {
    
    private final AtomicInteger i = new AtomicInteger();
    
    public String findCache(String id) {
        System.out.println("request: " + id + ", response: "
                + i.getAndIncrement());
        return "request: " + id + ", response: " + i.getAndIncrement();
    }
}

在生产者dubbo-provider-web的applicationProvider.xml配置结果缓存接口

    <!--结果缓存begin  -->              
    <bean id="cacheService" class="com.study.service.CacheServiceImpl"/>
    <dubbo:service interface="com.study.service.CacheService" ref="cacheService"/>
    <!--结果缓存end  -->

在消费者dubbo-consumer-web的applicationConsumer.xml配置调用的缓存接口

    <!--结果缓存begin  -->
    <dubbo:reference id="cacheService" interface="com.study.service.CacheService" cache="lru"/>
    <!--结果缓存end  -->

在消费者dubbo-consumer-web的CommonController.java里面创建缓存接口测试代码

    //结果缓存begin
    @Autowired
    CacheService cacheService;
    
    @RequestMapping("/cache")
    public @ResponseBody String cache() {
        // 测试缓存生效,多次调用返回同样的结果。(服务器端自增长返回值)
        String fix = null;
        for (int i = 0; i < 5; i++) {
            String result = cacheService.findCache("0");
            if (fix == null || fix.equals(result)) {
                System.out.println("OK: " + result);
            }
            else {
                System.err.println("ERROR: " + result);
            }
            fix = result;
            try {
                Thread.sleep(500);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        // LRU的缺省cache.size为1000,执行1001次,会把第一次请求的缓存结果清除掉
        for (int n = 0; n < 1001; n++) {
            String pre = null;
            for (int i = 0; i < 10; i++) {
                String result = cacheService.findCache(String.valueOf(n));
                if (pre != null && !pre.equals(result)) {
                    System.err.println("ERROR: " + result);
                }
                pre = result;
            }
        }
        
        // 测试LRU有移除最开始的一个缓存项
        String result = cacheService.findCache("0");
        if (fix != null && !fix.equals(result)) {
            System.out.println("OK: " + result);
        }
        else {
            System.err.println("ERROR: " + result);
        }
        return "OK";
    }
   //结果缓存end

 在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/cache访问查看效果:

测试缓存生效,多次调用返回同样的结果。(服务器端自增长返回值):

生产者:

消费者:

LRU的缺省cache.size为1000,执行1001次,会把第一次请求的缓存结果清除掉:

 生产者:

 

测试LRU有移除最开始的一个缓存项:

 生产者:

 

消费者:

 

9. 使用泛化调用

泛化接口调用方式主要用于客户端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用 Map表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过GenericService 调用所有服务实现。

泛化调用就是服务消费者端因为某种原因并没有该服务接口,这个原因有很多,比如是跨语言的,一个PHP工程师想调用某个java接口,他并不能按照你约定,去写一个个的接口,Dubbo并不是跨语言的RPC框架,但并不是不能解决这个问题,这个PHP程序员搭建了一个简单的java web项目,引入了dubbo的jar包,使用dubbo的泛化调用,然后利用web返回json,这样也能完成跨语言的调用。泛化调用的好处之一就是这个了。
好了,简而言之,泛化调用,最直接的表现就是服务消费者不需要有任何接口的实现,就能完成服务的调用。

这个功能一般不用,因为代码的可读性很差

在生产者dubbo-provider-web新建一个接口:

package com.study.service;

/**
 * 
 * @Description: 泛化调用接口
 * @author leeSmall
 * @date 2018年10月23日
 *
 */
public interface DemoService {
    String eat(Long id);
}

在生产者dubbo-provider-web新建两个DemoService接口的实现类

package com.study.service;

/**
 * 
 * @Description: 泛化调用接口实现类
 * @author leeSmall
 * @date 2018年10月23日
 *
 */
public class DemoServiceImpl implements DemoService {
    public String eat(Long id) {
        System.out.println("泛化调用");
        return "泛化调用" + id;
    }
}

在生产者dubbo-provider-web的applicationProvider.xml配置泛化调用接口

    <!--泛化调用begin-->
    <dubbo:service interface="com.study.service.DemoService" ref="demoService" protocol="dubbo"/>
    <bean id="demoService" class="com.study.service.DemoServiceImpl"/>
    <!--泛化调用end-->

在消费者dubbo-consumer-web的applicationConsumer.xml配置调用的泛化调用接口

    <!--泛化调用begin -->
    <dubbo:reference id="demoService" interface="com.study.service.DemoService" generic="true"/>
    <!--泛化调用end -->

在消费者dubbo-consumer-web的CommonController.java里面创建泛化调用测试代码

    //泛化调用begin
    @RequestMapping("/fanhua")
    public @ResponseBody String fanhuayinyong() {
        GenericService demoService = (GenericService)applicationContext.getBean("demoService");
        Object result = demoService.$invoke("eat", new String[] { "java.lang.Long" }, new Object[]{ 1L });
        System.out.println(result);
        return "OK";
    }
    //泛化调用end

在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/fanhua访问查看效果

生产者:

消费者:

10. 实现泛化调用

泛接口实现方式主要用于服务器端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的远程服务Mock框架,可通过实现GenericService接口处理所有服务请求。 

在生产者dubbo-provider-web新建dubbo提供的泛化接口的实现类:

package com.study.service;

import com.alibaba.dubbo.rpc.service.GenericException;
import com.alibaba.dubbo.rpc.service.GenericService;

/**
 * 
 * @Description: dubbo提供的泛化接口的实现类
 * @author leeSmall
 * @date 2018年10月24日
 *
 */
public class MyGenericService implements GenericService {
    
    public Object $invoke(String method, String[] parameterTypes, Object[] args)
            throws GenericException {
        System.out.println("泛化调用实现!");
        return "泛化调用实现";
    }
    
}

 在生产者dubbo-provider-web的applicationProvider.xml配置泛化接口的实现类

    <!--实现泛化调用begin  -->
    <bean id="genericService" class="com.study.service.MyGenericService" />
    <dubbo:service interface="com.alibaba.dubbo.rpc.service.GenericService" ref="genericService" />
    <!--实现泛化调用end  -->

 在消费者dubbo-consumer-web的applicationConsumer.xml配置调用的泛化接口的实现

    <!--实现泛化调用begin  -->
    <dubbo:reference id="genericService" interface="com.alibaba.dubbo.rpc.service.GenericService"/>
    <!--实现泛化调用end  -->

在消费者dubbo-consumer-web的CommonController.java里面创建泛化接口的实现测试代码

    //实现泛化调用begin
    @RequestMapping("/fanhuashixian")
    public @ResponseBody String fanhuashixian() {
        GenericService barService = (GenericService)applicationContext.getBean("genericService");
        Object result = barService.$invoke("login",
                new String[] {"java.lang.String"},
                new Object[] {"World"});
        System.out.println(result);
        return "OK";
    }
    //实现泛化调用end

在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/fanhuashixian访问查看效果

生产者:

 

消费者:

 

11. 回声测试

回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控。
所有服务自动实现EchoService接口,只需将任意服务引用强制转型为EchoService,即可使用。

直接使用第8个功能点(8.结果缓存)的接口进行回声测试的实现

 在消费者dubbo-consumer-web的CommonController.java里面创建回声测试的测试代码:

    //回声测试begin
    @RequestMapping("/huisheng")
    public @ResponseBody String huisheng() {
        CacheService barService = (CacheService)applicationContext.getBean("cacheService");
        EchoService echoService = (EchoService)barService; // 强制转型为EchoService
        // 回声测试可用性
        String status = (String)echoService.$echo("OK");
        if(status.equals("OK")) {
            System.out.println("缓存接口服务可用");
        }
        assert (status.equals("OK"));
        return "OK";
    }
    //回声测试end

在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/huisheng访问查看效果

 消费者:

12. 异步调用

 基于NIO的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。

在生产者dubbo-provider-web和消费者dubbo-consumer-web分别新建一个接口:

package com.study.service;

/**
 * 
 * @Description: 异步调用接口
 * @author leeSmall
 * @date 2018年10月24日
 *
 */
public interface AsyncService {
    
    String sayHello(String name);
    
}

在生产者dubbo-provider-web新建AsyncService接口的实现类

package com.study.service;


/**
 * 
 * @Description: 异步调用接口实现类
 * @author leeSmall
 * @date 2018年10月24日
 *
 */
public class AsyncServiceImpl implements AsyncService {
    
    public String sayHello(String name) {
        for (int i = 0; i < 5; i++) {
            System.out.println("async provider received: " + name);
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return "hello, " + name;
    }
    
}

 在生产者dubbo-provider-web的applicationProvider.xml配置异步调用接口

    <!--异步调用begin  -->
    <bean id="asyncServiceImpl" class="com.study.service.AsyncServiceImpl"/>
    <dubbo:service interface="com.study.service.AsyncService" ref="asyncServiceImpl"/>
    <!--异步调用end  -->

在消费者dubbo-consumer-web的applicationConsumer.xml配置调用的异步调用接口

    <!--异步调用begin  -->
    <dubbo:reference id="asyncServiceImpl" interface="com.study.service.AsyncService"  timeout="1200000">
        <dubbo:method name="sayHello" async="true"/>      
    </dubbo:reference>
    <!--异步调用end  -->

在消费者dubbo-consumer-web的CommonController.java里面创建异步调用测试代码

    //异步调用begin
    @Autowired
    AsyncService asyncService;
    
    
    @RequestMapping("/async")
    public @ResponseBody String async() {
        String result = asyncService.sayHello("我是异步调用客户端");
        System.out.println(result);
        // 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
        Future<String> fooFuture = RpcContext.getContext().getFuture();
        // 如果result已返回,直接拿到返回值,否则线程wait住,等待result返回后,线程会被notify唤醒
        try {
            //调用get方法会阻塞在这里直到拿到结果
            result = fooFuture.get();
            System.out.println(result);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        catch (ExecutionException e) {
            e.printStackTrace();
        }
        return "OK";
    }
    //异步调用end

 在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/async访问查看效果:

生产者:

 

消费者:

 

13. 参数回调

 参数回调方式与调用本地callback或listener相同,只需要在Spring的配置文件中声明哪个参数是callback 类型即可。Dubbo将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。适用于服务端完成某个动作以后通知客户端

 在生产者dubbo-provider-web和消费者dubbo-consumer-web分别新建一个参数回调监听接口和参数回调接口

 参数回调监听接口:

package com.study.callback;

/**
 * 
 * @Description: 参数回调监听接口
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public interface CallbackListener {
    
    void changed(String msg);
    
}

 参数回调接口:

package com.study.callback;

/**
 * 
 * @Description: 参数回调接口
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public interface CallbackService {
    
    void addListener(String key, CallbackListener listener);
    
}

在生产者dubbo-provider-web新建CallbackService接口的实现类

package com.study.callback;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 
 * @Description: 参数回调接口实现类
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public class CallbackServiceImpl implements CallbackService {
    
    private final Map<String, CallbackListener> listeners = new ConcurrentHashMap<String, CallbackListener>();
    
    public CallbackServiceImpl() {
        Thread t = new Thread(new Runnable() {
            public void run() {
                while (true) {
                    try {
                        for (Map.Entry<String, CallbackListener> entry : listeners.entrySet()) {
                            try {
                                entry.getValue()
                                        .changed(getChanged(entry.getKey()));
                            }
                            catch (Throwable t) {
                                listeners.remove(entry.getKey());
                            }
                        }
                        Thread.sleep(5000); // 定时触发变更通知
                    }
                    catch (Throwable t) { // 防御容错
                        t.printStackTrace();
                    }
                }
            }
        });
        t.setDaemon(true);
        t.start();
    }
    
    public void addListener(String key, CallbackListener listener) {
        listeners.put(key, listener);
        listener.changed(getChanged(key)); // 发送变更通知
    }
    
    private String getChanged(String key) {
        return "Changed: "
                + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    }
    
}

 在生产者dubbo-provider-web的applicationProvider.xml配置参数回调接口

    <!--参数回调begin  -->
    <bean id="callbackService" class="com.study.callback.CallbackServiceImpl"/>
    <dubbo:service interface="com.study.callback.CallbackService" ref="callbackService"
                   connections="1" callbacks="1000">
        <dubbo:method name="addListener">
            <dubbo:argument index="1" callback="true"/>
            <!--也可以通过指定类型的方式-->
            <!--<dubbo:argument type="com.demo.CallbackListener" callback="true" />-->
        </dubbo:method>
    </dubbo:service>
    <!--参数回调end  -->

 在消费者dubbo-consumer-web的applicationConsumer.xml配置调用的参数回调接口

    <!--参数回调begin  -->
    <dubbo:reference id="callbackService" interface="com.study.callback.CallbackService"/>
    <!--参数回调end  -->

在消费者dubbo-consumer-web的CommonController.java里面创建参数回调接口测试代码

    //参数回调begin
    @Autowired
    CallbackService callbackService;
    
    @RequestMapping("/callback")
    public @ResponseBody String callback() {
        callbackService.addListener("foo.bar", new CallbackListener() {
            public void changed(String msg) {
                System.out.println("callback1:" + msg);
            }
        });
        return "OK";
    }
    //参数回调end

 在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/callback访问查看效果:

生产者:

 

消费者:

 14. 事件通知

在调用之前、调用之后、出现异常时,会触发 oninvoke、onreturn、onthrow 三个事件,可以配置当事件发生时,通知哪个类的哪个方法 。

在生产者dubbo-provider-web和消费者dubbo-consumer-web分别新建一个接口

package com.study.event;

/**
 * 
 * @Description: 事件通知接口参与接口
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public interface Common {
    public String eat(String param);
}

在生产者dubbo-provider-web新建Common接口的实现类

package com.study.event;

/**
 * 
 * @Description: 事件通知接口参与接口实现类
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public class CommonImpl implements Common {
    
    public String eat(String param) {
        System.out.println("CommonImpl eat");
        return "CommonImpl eat";
    }
    
}

在生产者dubbo-provider-web的applicationProvider.xml配置事件通知接口参与接口

    <!-- 事件通知接口参与接口begin -->
    <bean id="commonImpl" class="com.study.event.CommonImpl"/>
    <dubbo:service interface="com.study.event.Common" ref="commonImpl"/>
    <!-- 事件通知接口参与接口end -->

在消费者dubbo-consumer-webl创建事件通知接口

package com.study.event;

/**
 * 
 * @Description: 事件通知接口
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public interface Notify {
    
    //调用之前
    public void oninvoke(String name); 
    
    //调用之后
    public void onreturn(String msg);
    
    // 出现异常
    public void onthrow(Throwable ex);
}

在消费者dubbo-consumer-web创建事件通知接口实现类

package com.study.event;

/**
 * 
 * @Description: 事件通知接口实现类
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public class NotifyImpl implements Notify {
    
    //调用之前
    public void oninvoke(String msg) {
        System.out.println("======oninvoke======, param: " + msg);
    }
    
    //调用之后
    public void onreturn(String msg) {
        System.out.println("======onreturn======, param: " + msg);
    }
    
     // 出现异常
    public void onthrow(Throwable ex) {
        System.out.println("======onthrow======, param: " + ex);
    }

    
    
}

在消费者dubbo-consumer-web的applicationConsumer.xml配置调用事件通知接口参与接口和事件通知接口

    <!--事件通知begin  -->
    <bean id ="demoCallback" class = "com.study.event.NotifyImpl" />
    <dubbo:reference id="commonImpl" interface="com.study.event.Common" >
        <dubbo:method name="eat" async="false" oninvoke="demoCallback.oninvoke" onreturn = "demoCallback.onreturn" onthrow=
            "demoCallback.onthrow" />
    </dubbo:reference>
    <!--事件通知end  -->

在消费者dubbo-consumer-web的CommonController.java里面创建事件通知接口测试代码

    //事件通知begin
    @Autowired
    Common common;
    
    
    @RequestMapping("/event")
    public @ResponseBody String event() {
        String result = common.eat("jdksk");
        System.out.println(result);
        return "OK";
    }
    //事件通知end

 在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/cache访问查看效果:

生产者:

 

消费者:

 

 

 15. 本地存根

 远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在API中带上Stub,客户端生成Proxy实例,会把Proxy通过构造函数传给Stub ,然后把Stub暴露给用户,Stub可以决定要不要去调Proxy。

 在生产者dubbo-provider-web和消费者dubbo-consumer-web分别新建一个接口

package com.study.stub;

/**
 * 
 * @Description: 本地存根接口
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public interface StubService {
    String stub(String param);
}

在生产者dubbo-provider-web新建StubService接口的实现类

package com.study.stub;

/**
 * 
 * @Description: 本地存根接口实现类
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public class StubServiceImpl implements StubService {
    
    public String stub(String param) {
        System.out.println("provider StubServiceImpl stub");
        return "provider StubServiceImpl stub";
    }
    
}

在生产者dubbo-provider-web的applicationProvider.xml配置本地存根接口

    <!--本地存根begin  -->
    <bean id="stubServiceImpl" class="com.study.stub.StubServiceImpl"/>
    <dubbo:service interface="com.study.stub.StubService" ref="stubServiceImpl"/>
    <!--本地存根end  -->

在消费者dubbo-consumer-web创建本地存根Proxy实例

package com.study.stub;

/**
 * 
 * @Description: 本地存根Proxy实例
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public class LocalStubServiceProxy implements StubService {
    
    private StubService stubService;
    
    public LocalStubServiceProxy(StubService stubService) {
        this.stubService = stubService;
    }
    
    public String stub(String arg0) {
        System.out.println("我是localstub,我就相当于一个过滤器,在代理调用远程方法的时候,我先做一下拦截工作");
        
        try {
            return stubService.stub("xxx");
        }
        catch (Exception e) {
            System.out.println("远程调用出现异常了,我是本地stub,我要对远程服务进行伪装,达到服务降级的目的");
            return "远程调用出现异常了,我是本地stub,我要对远程服务进行伪装,达到服务降级的目的";
        }
    }
    
}

在消费者dubbo-consumer-web的applicationConsumer.xml配置本地存根接口和stub对应的本地存根Proxy实例

    <!--本地存根begin  -->
    <dubbo:reference id="stubServiceImpl" interface="com.study.stub.StubService" stub="com.study.stub.LocalStubServiceProxy"/>
    <!--本地存根end  -->

 在消费者dubbo-consumer-web的CommonController.java里面创建本地存根接口测试代码

    //本地存根begin
    @Autowired
    StubService stub;
    
    @RequestMapping("/stub")
    public @ResponseBody String stub() {
        String result = stub.stub("eee");
        System.out.println(result);
        return "OK";
    }
    //本地存根end 

 在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/stub访问查看效果:

生产者:

 

 

 消费者:

 

16. 本地伪装

本地伪装 通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过Mock数据返回授权失败。
在spring配置文件中按以下方式配置:
<dubbo:service interface="com.foo.BarService" mock="com.foo.BarServiceMock" />

1)Mock是Stub的一个子集,便于服务提供方在客户端执行容错逻辑,因经常需要在出现RpcException(比如网络失败,超时等)时进行容错,而在出现业务异常(比如登录用户名密码错误)时不需要容错,如果用Stub,可能就需要捕获并依赖RpcException类,而用Mock就可以不依赖RpcException,因为它的约定就是只有出现RpcException时才执行。
2)在interface旁放一个Mock实现,它实现BarService接口,并有一个无参构造函数

 在生产者dubbo-provider-web和消费者dubbo-consumer-web分别新建一个接口

package com.study.mock;

/**
 * 
 * @Description: 本地伪装接口
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public interface MockService {
    String mock(String param);
}

在生产者dubbo-provider-web新建MockService接口的实现类

package com.study.mock;

/**
 * 
 * @Description: 本地伪装接口实现类
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public class MockServiceImpl implements MockService {
    
    public String mock(String param) {
        System.out.println("provider MockServiceImpl mock");
        return "provider MockServiceImpl mock";
    }
    
}

在生产者dubbo-provider-web的applicationProvider.xml配置本地伪装接口

    <!--本地伪装begin  -->
    <bean id="mockServiceImpl" class="com.study.mock.MockServiceImpl"/>
    <dubbo:service interface="com.study.mock.MockService" ref="mockServiceImpl"/>
    <!--本地伪装end  -->

在消费者dubbo-consumer-web创建本地伪装代理实例

package com.study.mock;

/**
 * 
 * @Description: 本地伪装代理实例
 * @author leeSmall
 * @date 2018年10月25日
 *
 */
public class LocalMockProxyService implements MockService {
    
    public String mock(String arg0) {
        System.out.println("local mock  做一下容错处理,这个就是服务降级");
        return "local mock  做一下容错处理,这个就是服务降级";
    }
}

在消费者dubbo-consumer-web的applicationConsumer.xml配置调用的本地伪装接口和本地伪装代理实例

    <!--本地伪装begin  -->
    <dubbo:reference id="mockServiceImpl" interface="com.study.mock.MockService" mock="com.study.mock.LocalMockProxyService"/>
    <!--本地伪装end  -->

 在消费者dubbo-consumer-web的CommonController.java里面创建本地伪装接口测试代码

    //本地伪装begin
    @Autowired
    MockService mock;
    
    @RequestMapping("/mock")
    public @ResponseBody String mock() {
        String result = mock.mock("vvvv");
        System.out.println(result);
        return "OK";
    }
    //本地伪装end

 在浏览器输入地址http://localhost:8081/dubbo-consumer-web/common/mock访问查看效果:

 生产者:

消费者:

 

 

 停掉生产者,再次在浏览器访问查看效果:

 消费者:

 

17. 延迟暴露

延迟到Spring初始化完成后,再暴露服务,规避spring的加载死锁问题
生产者单个服务接口配置:

<dubbo:service delay="-1" />

生产者全局服务接口配置:

<dubbo:provider deplay=”-1” />

源码实现方式:ApplicationListener<ContextRefreshEvent> 监控ContextRefreshEvent事件

18. 并发控制

服务端配置:
限制com.foo.BarService的每个方法,服务器端并发执行(或占用线程池线程数)不能超过10个:

<dubbo:service interface="com.foo.BarService" executes="10" />

限制com.foo.BarService的sayHello方法,服务器端并发执行(或占用线程池线程数)不能超过10个:

<dubbo:service interface="com.foo.BarService">
<dubbo:method name="sayHello" executes="10" />
</dubbo:service>

客户端配置:
限制com.foo.BarService的每个方法,每个客户端并发执行(或占用连接的请求数)不能超过10个:

<dubbo:service interface="com.foo.BarService" actives="10" />
<dubbo:reference interface="com.foo.BarService" actives="10" />

限制com.foo.BarService的sayHello方法,每个客户端并发执行(或占用连接的请求数)不能超过10个:

<dubbo:service interface="com.foo.BarService">
<dubbo:method name="sayHello" actives="10" />
</dubbo:service>


<dubbo:reference interface="com.foo.BarService">
<dubbo:method name="sayHello" actives="10" />
</dubbo:service>

如果<dubbo:service>和<dubbo:reference>都配了actives, <dubbo:reference>优先

19. 连接控制

服务端连接控制
限制服务器端接受的连接不能超过10个

<dubbo:provider protocol="dubbo" accepts="10" />
<dubbo:protocol name="dubbo" accepts="10" />

客户端连接控制
限制客户端服务使用连接不能超过10个

<dubbo:reference interface="com.foo.BarService" connections="10" />
<dubbo:service interface="com.foo.BarService" connections="10" />

延迟连接
延迟连接用于减少长连接数。当有调用发起时,再创建长连接。

<dubbo:protocol name="dubbo" lazy="true" />

注意:该配置只对使用长连接的dubbo协议生效。

粘滞连接
粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。
粘滞连接将自动开启延迟连接,以减少长连接数。

<dubbo:protocol name="dubbo" sticky="true" />

20. 令牌验证

通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者

可以在生产者(客户端不需要配置token,只需要从注册中心获取服务时获取即可)全局设置开启令牌验证:

<!--随机token令牌,使用UUID生成-->
<dubbo:provider interface="com.foo.BarService" token="true" />
<!--固定token令牌,相当于密码-->
<dubbo:provider interface="com.foo.BarService" token="123456" />

21. 路由规则 

路由规则:决定一次dubbo服务调用的目标服务器,分为条件路由规则和脚本路由规则,并且支持可扩展。
向注册中心写入路由规则的操作通常由监控中心或治理中心的页面完成
示例:
registry.register(URL.valueOf("condition://0.0.0.0/com.foo.BarService?category=routers
&dynamic=false&rule=" + URL.encode("host = 10.20.153.10 => host = 10.20.153.11") + "));
示例属性说明:
condition:// 表示路由规则的类型,支持条件路由规则和脚本路由规则,可扩展,必填。
0.0.0.0 表示对所有 IP 地址生效,如果只想对某个 IP 的生效,请填入具体 IP,必填。
com.foo.BarService 表示只对指定服务生效,必填。
category=routers 表示该数据为动态配置类型,必填。
dynamic=false 表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,必填。
enabled=true 覆盖规则是否生效,可不填,缺省生效。
force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 flase 。
runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为true ,需要注意设置会影响调用的性能,可不填,缺省为flase。
priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为0。
rule=URL.encode("host = 10.20.153.10 => host = 10.20.153.11") 表示路由规则的内容,必填。

条件路由规则

基于条件表达式的路由规则
示例:host = 10.20.153.10 => host = 10.20.153.11
示例说明:
=> 之前的为消费者匹配条件,所有参数和消费者的 URL 进行对比,当消费者满足匹配
条件时,对该消费者执行后面的过滤规则。
=> 之后为提供者地址列表的过滤条件,所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表。
如果匹配条件为空,表示对所有消费方应用,如:=> host != 10.20.153.11
如果过滤条件为空,表示禁止访问,如:host = 10.20.153.10 =>

表达式参数支持:
服务调用信息,如:method,argument等,暂不支持参数路由
URL本身的字段,如:protocol,host,port等,以及URL上的所有参数,如:application,organization等

条件支持:
等号 = 表示"匹配",如: host = 10.20.153.10
不等号 != 表示"不匹配",如: host != 10.20.153.10

值支持:
以逗号 , 分隔多个值,如: host != 10.20.153.10,10.20.153.11
以星号 * 结尾,表示通配,如: host != 10.20.*
以美元符 $ 开头,表示引用消费者参数,如: host = $host

白名单:
host != 10.20.153.10,10.20.153.11 =>
黑名单:
host = 10.20.153.10,10.20.153.11 =>

 

读写分离:
method = find*,list*,get*,is* => host = 172.22.3.94,172.22.3.95,172.22.3.96
method != find*,list*,get*,is* => host = 172.22.3.97,172.22.3.98
前后台分离:
application = bops => host = 172.22.3.91,172.22.3.92,172.22.3.93
application != bops => host = 172.22.3.94,172.22.3.95,172.22.3.96

22. 服务降级

可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
向注册中心写入动态配置覆盖规则:
mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
还可以改为 mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。

 

示例代码获取地址

 

 参考文章:

dubbo官方文档:http://dubbo.apache.org/zh-cn/docs/user/quick-start.html

Dubbo基本特性之泛化调用:https://www.cnblogs.com/flyingeagle/p/8908317.html

 

posted @ 2018-10-27 16:02  小不点啊  阅读(5305)  评论(0编辑  收藏  举报