SpringBoot整合Dubbo
1. 创建基础工程
我们创建一个maven工程,假设命名为dubbo05,在该工程下,创建一个模板,假设模板名为dubbo-api
创建成功后,在该模板下引入下列依赖:
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
模板的目录如下:
Product.java
@Data
@TableName(value = "shop_product")
public class Product implements Serializable {
@TableId(type = IdType.AUTO,value = "pid")
private Integer pid;
@TableField(value = "pname")
private String pname;
@TableField(value = "pprice")
private Double pprice;
@TableField(value = "stock")
private Integer stock;
}
User.java
@Data
@TableName(value = "shop_user")
public class User implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField(value = "username")
private String username;
@TableField(value = "password")
private String password;
@TableField(value = "telephone")
private String telephone;
@TableField(exist = false)
private List<Product>productList;
}
ResultVO.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResultVO <T> implements Serializable {
private Integer code;
private String msg;
private T data;
public ResultVO(Integer code,String msg){
this.code=code;
this.msg=msg;
}
}
注意,上面的类都要实现序列化,因为Dubbo在远程调用时,会进行序列化
ResultVOUtil.java
public class ResultVOUtil {
public static ResultVO success(){
return new ResultVO<>(200,"操作成功");
}
public static ResultVO success(Object data){
return new ResultVO<>(200,"操作成功",data);
}
public static ResultVO error(Integer code,String msg){
return new ResultVO<>(code,msg);
}
}
ProductService.java
public interface ProductService {
ResultVO getProductById(Integer id);
ResultVO saveProduct(Product product);
}
UserService.java
public interface UserService {
ResultVO getUserById(int id);
}
application.yml
spring:
datasource:
username: root
password: 3fa4d180
url: jdbc:mysql://localhost:3306/young1?useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: mapper/*.xml
2. 创建dubbo-provider和dubbo-consumer模板
先启动zookeeper
创建模板的过程在上面演示过了,新建的两个模板导入的依赖都如下:
<dependencies>
<dependency>
<groupId>com.young</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
</dependencies>
目录结构如下:
数据库的内容如下:
我们先看dubbo-provider模板
ProductMapper.java
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}
ProductServicImpl.java
package com.young.dubbo.provider.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.young.dubbo.api.entity.Product;
import com.young.dubbo.api.service.ProductService;
import com.young.dubbo.api.util.ResultVOUtil;
import com.young.dubbo.api.vo.ResultVO;
import com.young.dubbo.provider.mapper.ProductMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Service
@Component
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
public ResultVO getProductById(Integer id) {
Product product=productMapper.selectById(id);
return ResultVOUtil.success(product);
}
@Override
public ResultVO saveProduct(Product product) {
int addNum=productMapper.insert(product);
if(addNum<=0){
return ResultVOUtil.error(400,"添加商品失败");
}else{
Map<String,Object>res=new HashMap<>();
res.put("addNum",addNum);
res.put("id",product.getPid());
return ResultVOUtil.success(res);
}
}
}
ProviderApplication.java
package com.young.dubbo.provider;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class ProviderApplication {
public static void main(String[]args){
SpringApplication.run(ProviderApplication.class,args);
}
}
ProviderApplicationTest.java
package com.young.dubbo.provider;
import com.young.dubbo.api.entity.Product;
import com.young.dubbo.api.service.ProductService;
import com.young.dubbo.api.vo.ResultVO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class ProviderApplicationTest {
@Resource
private ProductService productService;
@Test
public void testGet(){
ResultVO resultVO=productService.getProductById(1);
System.out.println(resultVO);
}
@Test
public void testAdd(){
Product product=new Product();
product.setPname("魅族");
product.setPprice(2000.0);
product.setStock(100);
ResultVO resultVO=productService.saveProduct(product);
System.out.println(resultVO);
}
}
application.yml
server:
port: 8081
dubbo:
application:
name: dubbo-provider
registry:
address: zookeeper://127.0.0.1:2181
protocol: zookeeper
protocol:
name: dubbo
port: 20881
monitor:
protocol: registry
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/young1?useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: mapper/*.xml
测试ProviderApplicationTest.java的testGet方法和testAdd方法
接下来是dubbo-consumer模块
UserMapper.java
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
UserServiceImpl.java
package com.young.dubbo.consumer.service.impl;
import com.young.dubbo.api.entity.User;
import com.young.dubbo.api.service.UserService;
import com.young.dubbo.api.util.ResultVOUtil;
import com.young.dubbo.api.vo.ResultVO;
import com.young.dubbo.consumer.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public ResultVO getUserById(int id) {
User user=userMapper.selectById(id);
return ResultVOUtil.success(user);
}
}
ConsumerApplication.java
package com.young.dubbo.consumer;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class ConsumerApplication {
public static void main(String[]args){
SpringApplication.run(ConsumerApplication.class,args);
}
}
ConsumerApplicationTest.java
package com.young.dubbo.consumer;
import com.young.dubbo.api.entity.User;
import com.young.dubbo.api.service.UserService;
import com.young.dubbo.api.vo.ResultVO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ConsumerApplicationTest {
@Autowired
private UserService userService;
@Test
public void testGet(){
ResultVO resultVO=userService.getUserById(1);
System.out.println(resultVO);
}
}
application.yml
server:
port: 8082
dubbo:
application:
name: dubbo-consumer
registry:
address: zookeeper://127.0.0.1:2181
protocol:
name: dubbo
port: 20882
monitor:
protocol: registry
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/young1?useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: mapper/*.xml
测试testGet方法
3.整合Dubbo
在上面的步骤都没问题时,我们可以确定数据库能正常访问,接下来假设我们要在UserServiceImpl中,调用ProductService来获取商品信息,具体操作如下
package com.young.dubbo.consumer.service.impl;
import com.alibaba.dubbo.config.annotation.Reference;
import com.young.dubbo.api.entity.Product;
import com.young.dubbo.api.entity.User;
import com.young.dubbo.api.service.ProductService;
import com.young.dubbo.api.service.UserService;
import com.young.dubbo.api.util.ResultVOUtil;
import com.young.dubbo.api.vo.ResultVO;
import com.young.dubbo.consumer.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
public class UserServiceImpl implements UserService {
@Reference
private ProductService productService;
@Autowired
private UserMapper userMapper;
@Override
public ResultVO getUserById(int id) {
User user=userMapper.selectById(id);
Product product1=(Product) productService.getProductById(1).getData();
Product product2=(Product) productService.getProductById(2).getData();
user.setProductList(Arrays.asList(product1,product2));
return ResultVOUtil.success(user);
}
}
我们注意到,ProductServiceImpl类上用到的Service注解,引入的是
我们在dubbo-consumer下,在创建一个controller软件包,在该包下创建UserController.java
package com.young.dubbo.consumer.controller;
import com.young.dubbo.api.service.UserService;
import com.young.dubbo.api.util.ResultVOUtil;
import com.young.dubbo.api.vo.ResultVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class UserController {
@Resource
private UserService userService;
@GetMapping("/getUserById/{id}")
public ResultVO getUserById(@PathVariable(value = "id",required = true)Integer id){
return userService.getUserById(id);
}
}
接下来先运行dubbo-provider项目,在dubbo-consumer项目,然后访问http://localhost:8082/getUserById/1
整合成功!
4.设置超时时间和重试
我们先修改ProductServiceImpl.java,模拟网络延迟
@Service
@Component
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
public ResultVO getProductById(Integer id) {
try{
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
Product product=productMapper.selectById(id);
return ResultVOUtil.success(product);
}
@Override
public ResultVO saveProduct(Product product) {
int addNum=productMapper.insert(product);
if(addNum<=0){
return ResultVOUtil.error(400,"添加商品失败");
}else{
Map<String,Object>res=new HashMap<>();
res.put("addNum",addNum);
res.put("id",product.getPid());
return ResultVOUtil.success(res);
}
}
}
接着重新运行项目,访问http://localhost:8082/getUserById/1
这里提示超时了,这是因为dubbo默认的超时时间为1秒,我们将进一步修改ProductServiceImpl.java,设置超时时间为4秒
@Service(timeout = 4000)
@Component
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
public ResultVO getProductById(Integer id) {
try{
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
Product product=productMapper.selectById(id);
return ResultVOUtil.success(product);
}
@Override
public ResultVO saveProduct(Product product) {
int addNum=productMapper.insert(product);
if(addNum<=0){
return ResultVOUtil.error(400,"添加商品失败");
}else{
Map<String,Object>res=new HashMap<>();
res.put("addNum",addNum);
res.put("id",product.getPid());
return ResultVOUtil.success(res);
}
}
}
重新访问http://localhost:8082/getUserById/1,大概6秒后返回结果
我们重新修改ProductServicImpl.java,设置重试次数为4次,超时时间为1秒
Service(timeout = 1000,retries = 4)
@Component
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
public ResultVO getProductById(Integer id) {
System.out.println("---------getProductById-----------");
try{
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
Product product=productMapper.selectById(id);
return ResultVOUtil.success(product);
}
@Override
public ResultVO saveProduct(Product product) {
int addNum=productMapper.insert(product);
if(addNum<=0){
return ResultVOUtil.error(400,"添加商品失败");
}else{
Map<String,Object>res=new HashMap<>();
res.put("addNum",addNum);
res.put("id",product.getPid());
return ResultVOUtil.success(res);
}
}
}
重新访问http://localhost:8082/getUserById/1
查看提供方(ProviderApplication)的控制台
这里再第一次请求失败后,会重试4次,不过因为我们设置的超时时间短于3秒,所有都访问失败
5. 负载均衡方式
我们修改ProviderServiceImpl.java
@Service(timeout = 1000,retries = 4)
@Component
public class ProductServiceImpl implements ProductService {
@Value("${server.port}")
private Integer port;
@Autowired
private ProductMapper productMapper;
@Override
public ResultVO getProductById(Integer id) {
System.out.println("getProductById:"+port);
Product product=productMapper.selectById(id);
return ResultVOUtil.success(product);
}
@Override
public ResultVO saveProduct(Product product) {
int addNum=productMapper.insert(product);
if(addNum<=0){
return ResultVOUtil.error(400,"添加商品失败");
}else{
Map<String,Object>res=new HashMap<>();
res.put("addNum",addNum);
res.put("id",product.getPid());
return ResultVOUtil.success(res);
}
}
}
先把项目都停掉,点击编辑配置
选择ProviderApplication,点击添加VM选项
复制两个ProviderApplication,然后把端口分别改为9002和9003
ProviderApplication
ProviderApplication2
ProviderApplication3
然后运行ConsumerApplication
然后多次访问http://localhost:8082/getUserById/1
查看控制台
可以发现,默认使用的负载均衡是随机的,我们可以修改UserServiceImpl来使用轮询的方法
@Component
public class UserServiceImpl implements UserService {
@Reference(loadbalance = "roundrobin")
private ProductService productService;
@Autowired
private UserMapper userMapper;
@Override
public ResultVO getUserById(int id) {
User user=userMapper.selectById(id);
Product product1=(Product) productService.getProductById(1).getData();
Product product2=(Product) productService.getProductById(2).getData();
user.setProductList(Arrays.asList(product1,product2));
return ResultVOUtil.success(user);
}
}
重新启动ConsumerApplication,多次访问
6.宕机
我们把ProviderApplication2和ProviderApplication3都关闭,然后把zookeeper也停掉,此时再访问http://localhost:8082/getUserById/1
发现依然能访问成功,这是因为虽然zookeeper停了,但是我们的服务有缓存,也就是说,即使没有注册中心,我们也可以通过这个缓存,去得知我们需要的服务在哪个位置,然后再去请求,这体现了Dubbo的高可用性