本章我们介绍Dubbo的常用高级特性。
Java对象 -> 序列化 -> 流数据
流数据 -> 反序列化 -> Java对象
依赖一个公共的相同模块即保证序列化版本号在序列和反序列时相同。
public class User {
private int id;
private String username;
private String password;
public User() {
}
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2)在dubbo-interface里去添加一个查询用户的接口,首先需要引入dubbo-pojo的依赖。
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.itheimagroupId>
<artifactId>dubbo-interfaceartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<maven.compiler.encoding>UTF-8maven.compiler.encoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>com.itheimagroupId>
<artifactId>dubbo-pojoartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
import com.itheima.pojo.User;
public interface UserService {
String sayHello();
/**
* 查询用户
*/
User findUserById(int id);
}
3)在dubbo-service中实现findUserById方法
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
// 将这个类提供的方法(服务)对外发布。将访问地址(ip、端口、访问路径)注册到ZK注册中心
@Service
public class UserServiceImpl implements UserService {
public String sayHello() {
return "Hello Dubbo RPC Zookeeper!~";
}
@Override
public User findUserById(int id) {
User user = new User(1, "zhangsan", "123");
return user;
}
}
4)在dubbo-web中实现调用服务消费
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@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);
}
}
此时interface模块依赖pojo模块,web模块依赖service模块、web和service模块依赖interface模块。interface和pojo上都有修改,都需要进行mvn clean install。
重启dubbo-service和dubbo-web服务,访问http://localhost:8000/user/find.do?id=1
这个原因写的很明确,就是pojo实体类上需要实现Serializable接口,因为Java对象的数据需要在不同的机器上通过流来传输的。
5)所有的实体类都实现Serializable接口
import java.io.Serializable;
/*
注意!!!
将来所有的pojo类都需要实现serializable接口
*/
public class User implements Serializable {
private int id;
private String username;
private String password;
public User() {
}
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
注意:被依赖的Java工程改动后,一定要install重新安装。
6)对修改的服务进行clean install重新安装,重启相关依赖服务,重新访问
我们可以进行测试一下,将Linux服务器上的ZK关掉。
再次访问老的服务地址,是可以访问的。但是如果注册中心挂了,新的服务就无法注册了,还是需要进行修复的。
我们来模拟一下超时情况。在@Service
中配置timeout
超时时间)、retries
超时后重试的次数(默认2次)。
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
// 将这个类提供的方法(服务)对外发布。将访问地址(ip、端口、访问路径)注册到ZK注册中心
@Service(timeout = 3000, retries = 0) // 当前服务3秒超时,超时后,重试0次
public class UserServiceImpl implements UserService {
public String sayHello() {
return "Hello Dubbo RPC Zookeeper!~";
}
@Override
public User findUserById(int id) {
User user = new User(1, "zhangsan", "123");
// 模拟数据库查询耗时5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return user;
}
}
在Controller服务调用方模拟线程异步调用1秒打印一个数字。
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Reference
private UserService userService;
@RequestMapping("/sayHello")
public String sayHello() {
return userService.sayHello();
}
/**
* 根据id查询用户信息
* @param id
* @return
*/
int i = 1;
@RequestMapping("/find")
public User find(int id) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println(i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
return userService.findUserById(id);
}
}
重启服务,访问测试
发现打印了4个数字然后报错timeout,第4个数字是因为service的线程睡眠时间影响,所以大概可以认为用了3秒触发超时。
@Reference(timeout = 1000)
private UserService userService;
注意:如果两边都配置了服务超时时间,那么consumer优先provider,服务调用方的超时时间会覆盖服务提供方。
建议:在服务提供者上配置超时时间,因为定义服务的人写具体service逻辑时才能估计服务耗时多久。
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
// 将这个类提供的方法(服务)对外发布。将访问地址(ip、端口、访问路径)注册到ZK注册中心
@Service(timeout = 3000, retries = 2) // 当前服务3秒超时,超时后,重试2次,一共3次
public class UserServiceImpl implements UserService {
public String sayHello() {
return "Hello Dubbo RPC Zookeeper!~";
}
int i = 1;
@Override
public User findUserById(int id) {
System.out.println("该服务一共被调用:" + i++ + "次。");
User user = new User(1, "zhangsan", "123");
// 模拟数据库查询耗时5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return user;
}
}
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service(version = "v1.0") // 指定当前服务的版本
public class UserServiceImpl implements UserService {
public String sayHello() {
return "Hello Dubbo RPC Zookeeper!~";
}
@Override
public User findUserById(int id) {
System.out.println("old version is v1.0");
User user = new User(1, "zhangsan", "123");
return user;
}
}
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service(version = "v2.0") // 指定当前服务的版本
public class UserServiceImpl2 implements UserService {
public String sayHello() {
return "Hello Dubbo RPC Zookeeper!~";
}
@Override
public User findUserById(int id) {
System.out.println("new version is v2.0");
User user = new User(2, "lisi", "456");
return user;
}
}
web服务消费者注入指定版本的service。
@Reference(version = "v1.0") // 注入指定版本的service
private UserService userService;
@Reference(version = "v2.0") // 注入指定版本的service
private UserService userService;
负载均衡策略(4种):
Dubbo官方文档:https://cn.dubbo.apache.org/zh-cn/overview/core-features/load-balance/
负载均衡是在服务器集群部署环境下操作的,我们可以让同一个服务启动三次来模拟多台服务器集群。但是需要注意:端口需要改变,否则会冲突。
服务器配置如下,每修改完一次端口启动一个服务。
默认值为20880
qos.port默认值为22222
@Service(weight = 100)
@Service(weight = 200)
@Service(weight = 100)
@Reference(loadbalance = "random")
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service(weight = 100)
public class UserServiceImpl implements UserService {
public String sayHello() {
return "1.....";
}
@Override
public User findUserById(int id) {
User user = new User(1, "zhangsan", "123");
return user;
}
}
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service(weight = 200)
public class UserServiceImpl implements UserService {
public String sayHello() {
return "2.....";
}
@Override
public User findUserById(int id) {
User user = new User(1, "zhangsan", "123");
return user;
}
}
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service(weight = 100)
public class UserServiceImpl implements UserService {
public String sayHello() {
return "3.....";
}
@Override
public User findUserById(int id) {
User user = new User(1, "zhangsan", "123");
return user;
}
}
在消费者配置接口负载均衡策略,4种策略名称可以通过搜索AbstractLoadBalance的4种实现类中定义的name值查看。
@Reference(loadbalance = "random") // 设置负载均衡策略
private UserService userService;
可能会遇到的问题:maven无法下载源码
通过IDEA进行下载源码,点进源码的.class文件,IDE会自动弹出Download Sources or Choose Sources当点击Download Sources时,会报如下错误:
Cannot download sources Sources not found for: org.apache.xxx
解决方法:通过命令行终端进入到工程文件目录里(pom.xml文件所在目录),执行如下命令:
mvn dependency:resolve -Dclassifier=sources
接下来他就会下载jar和源码了
通过权重配比,访问后可以发现,服务器1、2、3的访问概率大概为25%、50%、25%,实现了第一种Random按权重随机的负载均衡。
RoundRobin:按权重轮询。
当weight分别为100、200、100时,访问4次的顺序可能为1,2,3,2。
LeastActive:最少活跃调用数,相同活跃数的随机。
每个服务维护一个活跃数计数器。当A机器开始处理请求,该计数器加1,此时A还未处理完成。若处理完毕则计数器减1。而B机器接受到请求后很快处理完毕。那么A,B的活跃数分别是1,0。当又产生了一个新的请求,则选择B机器去执行(B活跃数最小),这样使慢的机器A收到少的请求。
ConsistentHash:—致性Hash,相同参数的请求总是发到同一提供者。
集群容错模式:
这里演示第一种集群容错模式Failover Cluster失败重试。
和之前的启动设置一样,也是设置不同端口启动三台服务器。
默认超时时间timeout为1秒,重试次数为2次。这里让服务器1和2模拟超时情况,服务器3上的服务是不超时的。
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service
public class UserServiceImpl implements UserService {
public String sayHello() {
return "hello dubbo";
}
@Override
public User findUserById(int id) {
System.out.println("1.....");
User user = new User(1, "zhangsan", "123");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return user;
}
}
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service
public class UserServiceImpl implements UserService {
public String sayHello() {
return "hello dubbo";
}
@Override
public User findUserById(int id) {
System.out.println("2.....");
User user = new User(1, "zhangsan", "123");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return user;
}
}
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service
public class UserServiceImpl implements UserService {
public String sayHello() {
return "hello dubbo";
}
@Override
public User findUserById(int id) {
System.out.println("3.....");
User user = new User(1, "zhangsan", "123");
return user;
}
}
搜索Cluster的实现类FailoverCluster,
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
// 配置集群容错模式为failover失败重试(默认)
@Reference(cluster = "failover")
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);
}
}
启动三个服务器和web服务,测试访问。
服务器1超时,服务器2重试。
服务器2超时,服务器3重试。
服务器3未超时执行完毕,返回查询数据。
其中中间过程web服务会报一个远程调用超时错误。
在并发量较高的情况下,服务器的资源将要被跑满,此时就可以关闭掉一些不重要的服务,比如为了让支付服务不受到影响,可选择关闭广告与日志服务,释放掉一些资源,降低服务器压力,即对服务进行降级处理。
服务降级方式:
mock=force:return null
表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。mock=fail:return null
表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。@Reference(mock = "force:return null") // 暴力返回null,不再调用userService服务
private UserService userService;
@Reference(mock = "fail:return null") // 失败后返回null,如服务超时报错
private UserService userService;