dubbo系列二:dubbo常用功能总结(1)
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("[email protected]"); 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访问查看效果
生产者:
消费者: