1.概述
dubbo是一个高性能、轻量级的开源分布式服务框架,早期由阿里巴巴进行开源。它提供了服务注册、发现、调用和负载均衡等分布式服务治理功能,为分布式开发提供了极大便利。dubbo核心概念包括:Provider(消费提供者)、Consumer(服务消费者)、Registry(注册中心)、Monitor(监控中心)等,注册中心是dubbo服务治理的核心组件,dubbo依赖注册中心的协调实现服务(地址)发现,自动化的服务发现是微服务实现动态扩缩容、负载均衡、流量治理的基础。dubbo的提供的注册中心有很多:Multicast注册中心、Zookeeper注册中心、Redis注册中心、Simple注册中心、Nacos注册中心等,本文主要介绍springboot服务整合dubbo,并实用nacos作为注册中心来进行服务注册。
2.dubbo核心概念
2.1 dubbo架构图
节点名称 | 说 明 |
---|---|
Provider | 暴露服务的服务提供方 ,向注册中心注册提供自己服务 |
Consumer | 调用远程服务的服务消费方,从提供者地址列表中,基于负载均衡算法,选择提供者进行调用 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Registry | 服务注册与发现的注册中心,返回服务提供者地址列表给消费者 |
Container | 服务运行容器,负责启动、加载和运行服务提供者 |
图中的虚线表明服务消费者(Consumer)和服务提供者(Provider)通过异步的方式发送消息至Monitor。Consumer和Provider会将信息存放在本地磁盘,平均1min会发送一次信息。Monitor在这个架构中是可选的,并且是需要单独配置的,且Monitor的运行状态(正常或异常)不会影响服务的正常调用。
2.2 dubbo注册中心
dubbo提供的注册中心有:Multicast注册中心、Zookeeper注册中心、Redis注册中心、Simple注册中心、Nacos注册中心等。
注册中心 | 说明 |
---|---|
Multicast注册中心 | 不需要任何中心节点,只要广播地址,就可以互相发现 |
Zookeeper注册中心 | 是一个树形的目录服务,支持变更推送,可靠性较高,是dubbo早期使用最多的注册中心 |
Redis注册中心 | redis注册中心使用key/map结构存储数据结构,使用redis的publish/subscribe事件通知数据变更 |
Simple注册中心 | simple注册中心本身就是一个普通的dubbo服务,可以减少不必要依赖,使整体通讯方式一致 |
Nacos注册中心 | 由Alibaba开源的实现动态服务发现、服务配置、服务元数据及流量管理,支持注册中心、配置中心分离和合并部署 |
3.案例代码
3.1 服务关系图
dubbo-api模块:该模块定义服务者和消费者之间的契约,描述了服务的能力和行为,UserProviderAPI是一个dubbo接口,定义了服务的insert和queryById方法;
dubbo-provider模块:服务提供者,实现了 UserProviderAPI接口。通过 dubbo中的@Service方法(dubbo
3.0以及以后是@DubboService),该服务会注册到注册中心(Nacos),供消费者发现并调用;
dubbo-consumer模块:服务消费者,通过 @Reference(dubbo 3.0以及以后是@DubboReference) 注解引用了 UserProviderAPI接口。dubbo会自动处理服务的发现、负载均衡等,使得消费者可以透明调用远程服务。
dubbo注册中心:使用 Nacos 作为 Dubbo 的注册中心。注册中心是微服务治理的关键组件之一,它负责服务的注册、发现和管理。服务提供者在启动时向注册中心注册自己的服务信息,而消费者通过注册中心获取服务提供者的信息,从而实现动态的服务调用。
代码目录结构如下:
3.2 核心代码
本文使用的dubbo版本为2.7.5,springboot的版本为2.3.7,springcloud的版本为2.2.3,具体代码如下:
3.2.1 dubbo-api模块
1.pom文件
<parent>
<artifactId>dubbo</artifactId>
<groupId>com.eckey.lab</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-api</artifactId>
<dependencies>
<dependency>
<groupId>com.eckey.lab</groupId>
<artifactId>nacos-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2.UserProviderAPI接口
import com.eckey.lab.common.Result;
import com.eckey.lab.interfaces.bean.UserDTO;
public interface UserProviderAPI {
Result insert(UserDTO user);
Result<UserDTO> queryById(Integer id);
}
3.UserDTO实体类
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
@Data
public class UserDTO implements Serializable {
@NotNull(message = "userName不能为空")
private String userName;
@NotNull(message = "age不能为空")
private Integer age;
private String nickName;
@NotNull(message = "gender不能为空")
private String gender;
private Date createTime;
private Date modifyTime;
private static final long serialVersionUID = 1L;
}
3.2.2 dubbo-provider模块
1.pom文件
<parent>
<artifactId>dubbo</artifactId>
<groupId>com.eckey.lab</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-provider</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--java验证框架-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>com.eckey.lab</groupId>
<artifactId>nacos-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.eckey.lab</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
2.UserProviderServiceImpl类
import com.alibaba.fastjson.JSON;
import com.eckey.lab.common.Result;
import com.eckey.lab.dubbo.bean.User;
import com.eckey.lab.dubbo.dao.UserDao;
import com.eckey.lab.interfaces.UserProviderAPI;
import com.eckey.lab.interfaces.bean.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
/**
* @Author: ChengLiang
* @CreateTime: 2023-11-13 10:11
* @Description: TODO
* @Version: 1.0
*/
@Slf4j
@Service(version = "1.0.0", timeout = 6000)
@Component
public class UserProviderServiceImpl implements UserProviderAPI {
@Resource
private UserDao userDao;
@Override
public Result insert(UserDTO user) {
log.info("添加user入库数据:{}", JSON.toJSONString(user));
if (user == null) {
log.error("user不为空");
return Result.buildDataError("user不为空");
}
try {
user.setCreateTime(new Date());
user.setModifyTime(new Date());
User userInsert = new User();
userInsert.setUserName(user.getUserName());
userInsert.setNickName(user.getNickName());
userInsert.setGender(user.getGender());
userInsert.setAge(user.getAge());
userInsert.setCreateTime(user.getCreateTime());
userInsert.setModifyTime(user.getModifyTime());
userDao.insert(userInsert);
log.info("数据添加成功:{}", JSON.toJSONString(user));
} catch (Exception e) {
log.error("添加数据异常:{}", e);
return Result.buildDataError("添加数据异常");
}
return Result.buildResultSuccess();
}
@Override
public Result<UserDTO> queryById(Integer id) {
if (id == null) {
log.error("id不能为空");
return Result.buildDataError("id不能为空");
}
final User user = userDao.selectByPrimaryKey(id);
final UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
log.info("查询结果为:{}", JSON.toJSONString(userDTO));
return Result.buildDataSuccess(userDTO);
}
}
3.application.properties
server.port=9090
spring.application.name=dubbo-provider
spring.cloud.nacos.server-addr=http://123.213.45.103:8848
spring.datasource.username=
spring.datasource.password=
spring.datasource.url=jdbc:mysql://192.168.154.10:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&autoReconnect=true&failOverReadOnly=false&maxReconnects=1000&initialTimeout=30
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
dubbo.application.name=dubbo-provider
dubbo.application.version=1.0.0
dubbo.registry.address=
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.scan.base-packages=com.eckey.lab.dubbo.interfaces
mybatis.mapper-locations=classpath:/mybatis/*.xml
4.UserDao类
import com.eckey.lab.dubbo.bean.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao {
int deleteByPrimaryKey(Integer id);
int insert(User record);
int insertSelective(User record);
User selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(User record);
int updateByPrimaryKey(User record);
}
5.UserDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.eckey.lab.dubbo.dao.UserDao">
<resultMap id="BaseResultMap" type="com.eckey.lab.dubbo.bean.User">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="user_name" jdbcType="VARCHAR" property="userName" />
<result column="age" jdbcType="INTEGER" property="age" />
<result column="nick_name" jdbcType="VARCHAR" property="nickName" />
<result column="gender" jdbcType="VARCHAR" property="gender" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="modify_time" jdbcType="TIMESTAMP" property="modifyTime" />
</resultMap>
<sql id="Base_Column_List">
id, user_name, age, nick_name, gender, create_time, modify_time
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from user
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.eckey.lab.dubbo.bean.User" useGeneratedKeys="true">
insert into user (user_name, age, nick_name,
gender, create_time, modify_time
)
values (#{userName,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{nickName,jdbcType=VARCHAR},
#{gender,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{modifyTime,jdbcType=TIMESTAMP}
)
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.eckey.lab.dubbo.bean.User" useGeneratedKeys="true">
insert into user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userName != null">
user_name,
</if>
<if test="age != null">
age,
</if>
<if test="nickName != null">
nick_name,
</if>
<if test="gender != null">
gender,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="modifyTime != null">
modify_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userName != null">
#{userName,jdbcType=VARCHAR},
</if>
<if test="age != null">
#{age,jdbcType=INTEGER},
</if>
<if test="nickName != null">
#{nickName,jdbcType=VARCHAR},
</if>
<if test="gender != null">
#{gender,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
#{createTime,jdbcType=TIMESTAMP},
</if>
<if test="modifyTime != null">
#{modifyTime,jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.eckey.lab.dubbo.bean.User">
update user
<set>
<if test="userName != null">
user_name = #{userName,jdbcType=VARCHAR},
</if>
<if test="age != null">
age = #{age,jdbcType=INTEGER},
</if>
<if test="nickName != null">
nick_name = #{nickName,jdbcType=VARCHAR},
</if>
<if test="gender != null">
gender = #{gender,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=TIMESTAMP},
</if>
<if test="modifyTime != null">
modify_time = #{modifyTime,jdbcType=TIMESTAMP},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.eckey.lab.dubbo.bean.User">
update user
set user_name = #{userName,jdbcType=VARCHAR},
age = #{age,jdbcType=INTEGER},
nick_name = #{nickName,jdbcType=VARCHAR},
gender = #{gender,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=TIMESTAMP},
modify_time = #{modifyTime,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=INTEGER}
</update>
</mapper>
3.2.3 dubbo-consumer模块
pom文件与dubbo-provider模块一致,核心模块为DubboConsumerServiceImpl类,具体如下:
1.DubboConsumerServiceImpl类
import com.alibaba.fastjson.JSON;
import com.eckey.lab.common.Result;
import com.eckey.lab.consumer.bean.UserVO;
import com.eckey.lab.consumer.service.DubboConsumerService;
import com.eckey.lab.interfaces.UserProviderAPI;
import com.eckey.lab.interfaces.bean.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
/**
* @Author: ChengLiang
* @CreateTime: 2023-11-13 10:56
* @Description: TODO
* @Version: 1.0
*/
@Slf4j
@Service
public class DubboConsumerServiceImpl implements DubboConsumerService {
@Reference(interfaceClass = UserProviderAPI.class, version = "1.0.0")
private UserProviderAPI userProviderAPI;
@Override
public Result insert(UserVO userVO) {
UserDTO userDTO = new UserDTO();
userDTO.setUserName(userVO.getUserName());
userDTO.setNickName(userVO.getNickName());
userDTO.setAge(userVO.getAge());
userDTO.setGender(userVO.getGender());
userDTO.setCreateTime(userVO.getCreateTime());
userDTO.setModifyTime(userVO.getModifyTime());
log.info("userDTO:{}", JSON.toJSONString(userDTO));
return userProviderAPI.insert(userDTO);
}
@Override
public Result<UserDTO> selectByKeyId(Integer id) {
Result<UserDTO> userDTOResult = userProviderAPI.queryById(id);
log.info("查询结果为;{}", JSON.toJSONString(userDTOResult.getData()));
return userDTOResult;
}
}
2.application.properties
server.port=9091
spring.application.name=dubbo-consumer
spring.cloud.nacos.server-addr=http://123.213.45.103:8848
dubbo.application.name=dubbo-consumer
dubbo.application.version=1.0.0
dubbo.registry.address=nacos://http://123.213.45.103:8848/?username=nacos&password=nacos
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881
详细代码可参考文章附录部分,在上述代码中,有一些注意事项,具体如下:
1.在dubbo-provider和dubbo-consumer的启动类上,需要添加@EnableDubbo,@EnableDubbo整合了三个注解@EnableDubboConfig、@DubboComponentScan、@EnableDubboLifecycle。@EnableDubbo的功能都是由这三个注解完成的。
@EnableDubboConfig引入类DubboConfigConfigurationRegistrar,将用于解析配置相关的类注册到spring容器;
@DubboComponentScan引入类DubboComponentScanRegistrar,用于指定@Service扫描路径;
@EnableDubboLifecycle引入类DubboLifecycleComponentRegistrar,注册了两个监听器到spring容器。
一般需要配置@DubboComponentScan来定义@Service的扫描路径。如果不配置@DubboComponentScan,默认使用@EnableDubbo注解的类的包路径。
2.UserProviderServiceImpl中的注解@Service引入的是org.apache.dubbo.config.annotation.Service;
3.配置文件中,需要指定nacos的登录账户和密码,否则会报错。
3.3 测试结果
nacos注册页面上的服务列表如下:
调用服务消费者的接口如下:
服务消费者和提供者的日志如下:
4.小结
1.dubbo的注册中心有很多,本文基于nacos来进行演示,nacos的安装可查看我的博文:SpringCloud源码探析(二)-Nacos注册中心;
2.在dubbo框架中,需要先定义接口模块(定义提供者输出能力,消费者需要能力),服务提供者实现能力并注册到注册中心,供消费者进行消费;
3.dubbo和spring cloud alibaba之间有版本之间的对应关系,具体可查询dubbo官网或spring cloud官网进行对比。
5.参考文献
- https://zhuanlan.zhihu.com/p/638826433
- https://blog.csdn.net/java_cpp_/article/details/128051413
- https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/quick-start/api/
- https://juejin.cn/post/7159776981771354119
- https://developer.aliyun.com/article/808571
6.附录
https://gitee.com/Marinc/nacos/tree/master/dubbo