SpringBoot integrates Dubbo and Nacos

1. Overview

Dubbo is a high-performance, lightweight open source distributed service framework, which was early open sourced by Alibaba. It provides distributed service management functions such as service registration, discovery, invocation and load balancing, which provides great convenience for distributed development. The core concepts of dubbo include: Provider (consumer provider), Consumer (service consumer), Registry (registration center), Monitor (monitoring center), etc. The registration center is the core component of dubbo service governance. Dubbo relies on the coordination of the registration center to implement services. (Address) discovery, automated service discovery is the basis for microservices to achieve dynamic expansion and contraction, load balancing, and traffic management. Dubbo provides many registration centers: Multicast registration center, Zookeeper registration center, Redis registration center, Simple registration center, Nacos registration center, etc. This article mainly introduces the springboot service integration with dubbo, and uses nacos as the registration center for service registration.

2.dubbo core concepts

2.1 dubbo architecture diagram

Node name Description
Provider The service provider that exposes the service registers with the registration center to provide its own services
Consumer The service consumer that calls the remote service, from the provider address list , based on the load balancing algorithm, select the provider to call
Monitor Monitoring center that counts the number of calls and call time of the service
Registry The registration center for service registration and discovery, returns the service provider address list to consumers
Container Service running container is responsible for starting, loading and running service providers

The dotted lines in the figure indicate that the service consumer (Consumer) and the service provider (Provider) send messages to the Monitor asynchronously. Consumers and Providers will store information on local disks and send information once every 1 minute on average. Monitor is optional in this architecture and needs to be configured separately, and the running status of Monitor (normal or abnormal) will not affect the normal invocation of the service.

2.2 dubbo registration center

The registration centers provided by dubbo include: Multicast registration center, Zookeeper registration center, Redis registration center, Simple registration center, Nacos registration center, etc.

Registration Center Description
Multicast Registration Center No central node is needed, as long as the broadcast address is used, they can discover each other
Zookeeper registration center is a tree-shaped directory service that supports Change push has high reliability and is the most used registration center in the early days of Dubbo
Redis Registration Center The redis registration center uses key/map structure to store data Structure, use the publish/subscribe event of redis to notify data changes
Simple registration center The simple registration center itself is an ordinary dubbo service, which can reduce unnecessary Necessary dependencies to make the overall communication method consistent
Nacos Registration Center Open sourced by Alibaba to realize dynamic service discovery, service configuration, service metadata and traffic management , supports separate and merged deployment of registration center and configuration center

3.Case code

3.1 Service relationship diagram

dubbo-api module: This module defines the contract between the server and the consumer and describes the capabilities and behavior of the service. UserProviderAPI is a dubbo interface that defines the insert and queryById methods of the service;
dubbo-provider module: Service provider, which implements the UserProviderAPI interface. Through the @Service method in dubbo (dubbo
3.0 and later is @DubboService), the service will be registered in the registration center (Nacos) for consumers to discover and call;
dubbo-consumer module: Service consumer, which references the UserProviderAPI interface through the @Reference (dubbo 3.0 and later is @DubboReference) annotation. Dubbo will automatically handle service discovery, load balancing, etc., allowing consumers to transparently call remote services.
Dubbo Registration Center: Use Nacos as the Dubbo registration center. The registration center is one of the key components of microservice governance. It is responsible for the registration, discovery and management of services. The service provider registers its service information with the registration center at startup, and the consumer obtains the service provider’s information through the registration center, thereby realizing dynamic service invocation.

The code directory structure is as follows:

3.2 Core Code

The dubbo version used in this article is 2.7.5, the springboot version is 2.3.7, and the springcloud version is 2.2.3. The specific code is as follows:

3.2.1 dubbo-api module

1.pom file

 <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 interface

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 entity class

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 cannot be empty")
    private String userName;

    @NotNull(message = "age cannot be null")
    private Integer age;

    private String nickName;

    @NotNull(message = "gender cannot be empty")
    private String gender;

    private Date createTime;

    private Date modifyTime;

    private static final long serialVersionUID = 1L;
}

3.2.2 dubbo-provider module

1.pom file

<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 verification framework-->
        <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 class

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("Add user data into the database: {}", JSON.toJSONString(user));
        if (user == null) {<!-- -->
            log.error("user is not empty");
            return Result.buildDataError("user is not empty");
        }
        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("Data added successfully: {}", JSON.toJSONString(user));
        } catch (Exception e) {<!-- -->
            log.error("Add data exception: {}", e);
            return Result.buildDataError("Add data exception");
        }
        return Result.buildResultSuccess();
    }

    @Override
    public Result<UserDTO> queryById(Integer id) {<!-- -->
        if (id == null) {<!-- -->
            log.error("id cannot be empty");
            return Result.buildDataError("id cannot be empty");
        }
        final User user = userDao.selectByPrimaryKey(id);
        final UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(user, userDTO);
        log.info("The query result is: {}", 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 & amp;characterEncoding=UTF-8 & amp;allowMultiQueries=true & amp;autoReconnect=true & amp;failOverReadOnly=false & amp;maxReconnects=1000 & amp;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 class

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 module

The pom file is consistent with the dubbo-provider module. The core module is the DubboConsumerServiceImpl class, as follows:
1.DubboConsumerServiceImpl class

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("The query result is;{}", 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 & amp;password=nacos
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881

For detailed code, please refer to the appendix of the article. In the above code, there are some precautions, as follows:

1. On the startup classes of dubbo-provider and dubbo-consumer, you need to add @EnableDubbo. @EnableDubbo integrates three annotations @EnableDubboConfig, @DubboComponentScan, and @EnableDubboLifecycle. The functions of @EnableDubbo are completed by these three annotations.
@EnableDubboConfig introduces the class DubboConfigConfigurationRegistrar to register classes related to parsing configuration to the spring container;
@DubboComponentScan introduces the class DubboComponentScanRegistrar, which is used to specify the @Service scan path;
@EnableDubboLifecycle introduces the class DubboLifecycleComponentRegistrar and registers two listeners to the spring container.
Generally, @DubboComponentScan needs to be configured to define the scan path of @Service. If @DubboComponentScan is not configured, the package path of the class annotated with @EnableDubbo is used by default.
2. The annotation @Service in UserProviderServiceImpl introduces org.apache.dubbo.config.annotation.Service;
3. In the configuration file, you need to specify the nacos login account and password, otherwise an error will be reported.

3.3 Test results

The service list on the nacos registration page is as follows:

The interface for calling service consumers is as follows:

The logs of service consumers and providers are as follows:

4. Summary

1. There are many dubbo registration centers. This article is based on nacos for demonstration. For nacos installation, please see my blog post: SpringCloud Source Code Analysis (2) – Nacos Registration Center;
2. In the dubbo framework, the interface module needs to be defined first (defining the provider’s output capabilities and consumers’ required capabilities). The service provider implements the capabilities and registers them in the registration center for consumers to consume;
3. There is a correspondence between versions between dubbo and spring cloud alibaba. For details, you can check dubbo official website or spring cloud official website for comparison.

5. References

  1. https://zhuanlan.zhihu.com/p/638826433
  2. https://blog.csdn.net/java_cpp_/article/details/128051413
  3. https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/quick-start/api/
  4. https://juejin.cn/post/7159776981771354119
  5. https://developer.aliyun.com/article/808571

6.Appendix

https://gitee.com/Marinc/nacos/tree/master/dubbo