SpringCloud微服务集成Dubbo

1、Dubbo介绍

Apache Dubbo 是一款易用、高性能的 WEB 和 RPC 框架,同时为构建企业级微服务提供服务发现、流量治理、可观测、认证鉴权等能力、工具与最佳实践。用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。

Dubbo官网:https://cn.dubbo.apache.org/zh-cn/

Dubbo文档:https://cn.dubbo.apache.org/zh-cn/overview/quickstart/

Dubbo GitHub地址:https://github.com/apache/dubbo

Dubbo 使用版本对应关系:https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明#2021x-分支 选择和项目相互对应的版本进行使用。这里我使用的是Dubbo 2.7.8

2、Dubbo连接注册中心

Dubbo推荐使用Zookeeper作为注册中心,Zookeeper是Apacahe Hadoop的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境。除此之外Dubbo还可以使用阿里巴巴的nacos做注册中心。Nacos作为注册中心Dubbo使用与ZooKeeper基本相同,在使用上,不同的地方只有以下两点:

  • 1、导入的依赖,配置不同;
  • 2、注解不同,ZooKeeper使用@Service、@Reference注解,Nacos使用@DubboService、@DubboReference注解;

3、Dubbo负载均衡

  • RandomLoadBalance:加权随机,默认算法,默认权重相同;
  • RoundRobinLoadBalance:加权轮询,默认权重相同;
  • LeastActiveLoadBalance:最少活跃优先+加权随机,能者多劳;
  • ConsistentHashLoadBalance:一致性Hash,确定入参,确定提供者,适用于有状态的请求;

4、Dubbo Admin下载与使用

官网地址:https://github.com/apache/dubbo-admin

Dubbo和Dubbo Admin版本说明:https://cn.dubbo.apache.org/zh-cn/blog/2019/01/07/新版-dubbo-admin-介绍/

4.1、修改配置文件

进入dubbo-admin-server的resources目录,修改application.properties文件修改配置中心。默认为zookeeper:

admin.registry.address注册中心 
admin.config-center 配置中心 
admin.metadata-report.address元数据中心

由于我使用nacos,修改注册中心为nacos。 nacos注册中心有 GROUP 和 namespace:

admin.registry.address=nacos://127.0.0.1:8848?group=DEFAULT_GROUP&namespace=public&username=nacos&password=nacos
admin.config-center=nacos://127.0.0.1:8848?group=dubbo&username=nacos&password=nacos
admin.metadata-report.address=nacos://127.0.0.1:8848?group=dubbo&username=nacos&password=nacos
//改为自己的注册中心:
admin.registry.address=nacos://localhost:8848?group=DEFAULT_GROUP&namespace=23857f22-27ac-4947-988a-1b88d4eeb807&username=nacos&password=nacos
admin.config-center=nacos://localhost:8848?group=DEFAULT_GROUP&namespace=23857f22-27ac-4947-988a-1b88d4eeb807&username=nacos&password=nacos
admin.metadata-report.address=nacos://localhost:8848?group=DEFAULT_GROUP&namespace=23857f22-27ac-4947-988a-1b88d4eeb807&username=nacos&password=nacos
4.2、Dubbo Admin项目打包

在项目根目录进行打包,跳过测试:

mvn clean package -Dmaven.test.skip=true

进入dubbo-admin-0.6.0/dubbo-admin-distribution/target 进行启动后端:

java -jar dubbo-admin-0.6.0.jar

dubbo-admin-ui 目录下执行命令启动前端:

npm run dev

这是官方项目开发环境说明,打开项目就能看到:

4.3 访问dubbo admin

浏览器输入地址进行访问,之前的dubbo-admin老版本用的是Tomcat启动的,后端端口是8080(可能会冲突),前端端口是8081

http://localhost:8081

新版的dubbo-admin用的是Netty,默认配置端口是38080,前端端口38082

http://localhost:38082 或 http://localhost:38080

用户名密码都是root

登录成功:

5、SpringCloud集成Dubbo

在SpringBoot模块中引入maven依赖:

<!-- Dubbo Spring Cloud Starter -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>

这里使用用户模块和订单模块模拟微服务使用Dubbo RPC的调用。在提供者模块中加入Dubbo依赖,在配置文件中设置dubbo连接注册中心和配置中心:

dubbo:
  application:
    name: user-service-model-provider
  protocol:
    name: dubbo
    port: -1
  provider:
    group: DEFAULT_GROUP
    version: 2.0
  #port: 20881
  registry:
    address: nacos://${nacos.address:127.0.0.1}:8848?username=nacos&password=nacos
    #配置nacos自定义命名空间
    parameters:
      namespace: 23857f22-27ac-4947-988a-1b88d4eeb807
    group: DEFAULT_GROUP
#  registry:
#    address: zookeeper://${zookeeper.address:127.0.0.1}:2181
  metadata-report:
    address: nacos://${nacos.address:127.0.0.1}:8848?username=nacos&password=nacos
    #配置nacos自定义命名空间
    parameters:
      namespace: 23857f22-27ac-4947-988a-1b88d4eeb807

配置添加成功后,在业务模块service层,新建一个对外提供的dubbo实现类UserExternalServiceImpl,需要使用@DubboService注解,@Service注解尽量不要使用,可以使用@Componet代替。代码如下:

//@Service
@Component
@DubboService(timeout = 1000 * 10,group = "userGroup",version = "2.0")
public class UserExternalServiceImpl implements IUserExternalService {

    @Autowired
    private IUserService userService;


    @Override
    public Response selectUserAll() {
//        try {
//            TimeUnit.MILLISECONDS.sleep(1000*5);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        return Response.success(userService.selectUserAll());
    }

    @Override
    public Response insert(UserExternal userExternal) {
//        boolean flag = true;
//        if (flag == true){
//            throw new ParamException(500,"用户模块出现错误,需要回滚");
//        }
//        try {
//            TimeUnit.MILLISECONDS.sleep(20000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        User user=new User();
        BeanUtils.copyProperties(userExternal,user);
        boolean save = userService.save(user);
        if (save){
            return Response.success();
        }else {
            return Response.fail();
        }
    }
}

然后还需要在新建一个专门存dubbo的对外接口服务模块用interface-module名称,在该模块中新建一个IUserExternalService接口,该接口实现在用户模块中的UserExternalServiceImpl实现类:

public interface IUserExternalService {

    Response<List<UserExternal>> selectUserAll();

    Response insert(UserExternal user);
}

现在我们的接口提供方已经编写完成了,接下来开始编写接口使用方也就是消费者。在订单模块中引入dubbo依赖,在配置文件中将dubbo连接注册中心和配置中心:

dubbo:
  application:
    name: order-service-model-consumer
  consumer:
    group: DEFAULT_GROUP
    version: 2.0
  protocol:
    name: dubbo
    port: -1
  registry:
    address: nacos://${nacos.address:127.0.0.1}:8848?username=nacos&password=nacos
    #配置nacos自定义命名空间
    parameters:
      namespace: 23857f22-27ac-4947-988a-1b88d4eeb807
#  registry:
#    address: zookeeper://${zookeeper.address:127.0.0.1}:2181
  cloud:
    subscribed-services: user-service-model-provider
  metadata-report:
    address: nacos://${nacos.address:127.0.0.1}:8848?username=nacos&password=nacos
    #配置nacos自定义命名空间
    parameters:
      namespace: 23857f22-27ac-4947-988a-1b88d4eeb807

下面我们需要引入刚刚新建存dubbo接口模块的依赖包,然后就可以使用该接口了。首先建一个IDubboUserService的接口实现DubboUserServiceImpl类,意思是这个类是专门存放通过dubbo接口调用用户模块的业务类,后续在订单模块中处理用户模块信息都可以在该业务类中进行处理。

IDubboUserService类代码:

public interface IDubboUserService {

    List<UserExternal> selectUserAll();

}

DubboUserServiceImpl业务类代码,需要在该类中使用@DubboReference(group = "userGroup",version = "2.0")注解注入IUserExternalService接口信息,通过Dubbo RPC实现远程调用,注意group 和version 需要和提供方相互对应,不然会注入失败:

@Service
@Slf4j
public class DubboUserServiceImpl implements IDubboUserService {

    @DubboReference(group = "userGroup",version = "2.0")
    private IUserExternalService userExternalService;

    @Override
    public List<UserExternal> selectUserAll() {
        //添加blog
        Blog blog = new Blog();
        blog.setUid(UUID.randomUUID().toString());
        blog.setTitle("dubbo测试Test");
        blog.setContent("啊");
        blog.setSummary("12");
        blog.setTagUid("3c16b9093e9b1bfddbdfcb599b23d835");
        blogService.insert(blog);
        //处理相关逻辑
        Response<List<UserExternal>> response = userExternalService.selectUserAll();
        UserExternal user = new UserExternal();
        user.setUserName("dubbo测试Test");
        user.setAccount("system");
        user.setEmail("dubbo@gemail.com");
        Response insert = userExternalService.insert(user);
        System.out.println(insert);
        return response.getModel();
    }
}

通过上面的代码,就可以实现服务模块与模块之间的远程调用了。使用Dubbo在订单模块调用用户模块就和调用其他业务类代码一样通过依赖注入就可以了,是不是非常方便。

5、dubbo使用Sentinel进行限流和异常兜底

需要引入Maven依赖:

        <!-- 在dubbo中使用 Sentinel 需要添加下面依赖 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-apache-dubbo-adapter</artifactId>
        </dependency>

由于限流和兜底是消费方要处理的事情,所以我们只需要在订单模块中引入上面依赖即可。在DubboUserServiceImpl中,通过@SentinelResource注解处理,代码如下:

@Service
@Slf4j
public class DubboUserServiceImpl implements IDubboUserService {

    @DubboReference(group = "userGroup",version = "2.0")
    private IUserExternalService userExternalService;

    @Autowired
    private IBlogService blogService;

//    @PostConstruct
//    private void initFlowRules(){
//        System.out.println("Sentinel initFlowRules start===");
//        List<FlowRule> rules = new ArrayList<>();
//        FlowRule rule = new FlowRule();
//        rule.setResource(IDubboUserService.class.getName());
//        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//        // Set limit QPS to 20.
//        rule.setCount(20);
//        rules.add(rule);
//        FlowRuleManager.loadRules(rules);
//        System.out.println("Sentinel initFlowRules end====");
//    }


    @Override
    @SentinelResource(value = "com.itmy.user.service.IUserExternalService:selectUserAll()", //当前方法的路径
            blockHandler = "selectUserAll",
            blockHandlerClass = CustomerBlockHandler.class, //触发限流 走该类的blockHandler = "selectUserAll"方法
            fallback = "selectUserAllFallback",
            fallbackClass = UserFallback.class, //dubbo调用接口异常,走该类的  fallback = "selectUserAllFallback"方法
            exceptionsToIgnore = {IllegalArgumentException.class})
    //fallback 负责业务异常 blockHandler限流方法 exceptionsToIgnore 报该异常fallback不处理
    @GlobalTransactional(rollbackFor = Exception.class,timeoutMills = 30000,name = "order_tx_group")  //seata事务注解,目前没有使用后面会在seata博客中介绍。
    public List<UserExternal> selectUserAll() {
        //添加blog
        Blog blog = new Blog();
        blog.setUid(UUID.randomUUID().toString());
        blog.setTitle("dubbo事务测试Test");
        blog.setContent("dubbo事务测试Test啊的服务器打");
        blog.setSummary("12");
        blog.setTagUid("3c16b9093e9b1bfddbdfcb599b23d835");
        blogService.insert(blog);
        //处理相关逻辑
        Response<List<UserExternal>> response = userExternalService.selectUserAll();
//        boolean flag = true;
//        if (flag == true){
//            throw new ParamException(500,"用户模块出现错误,需要回滚");
//        }
        UserExternal user = new UserExternal();
        user.setUserName("dubbo事务");
        user.setAccount("system");
        user.setEmail("dubbo@gemail.com");
        Response insert = userExternalService.insert(user);
        System.out.println(insert);
        return response.getModel();
    }
}

CustomerBlockHandler处理限流的相关代码:

@Slf4j
public class CustomerBlockHandler {

    /**
     * 查询用户热点限流测试
     * @param name
     * @param email
     * @param exception
     * @return
     */
    public static Response selectUserBlockException(@RequestParam(value = "name",required = false) String name,
                                                    @RequestParam(value = "email",required = false) String email,
                                                    BlockException exception){
        log.error("CustomerBlockHandler|selectUserBlockException is fail");
        return Response.fail(FallbackErrorEnum.USER_MODULE_FALL);
    }

    /**
     * 查询限流
     * @return
     */
    public static Response redisFindBlockException(BlockException exception){
        log.error("添加订单 redis|添加用户 redis信息,调用接口被限流。。。。。");
        return Response.fail(FallbackErrorEnum.REDIS_FIND_FALL);
    }


    public static List<UserExternal> selectUserAll(BlockException exception){
        log.error("添加订单|添加用户信息,触发限流控制。。。。。");
        throw new ParamException(600,"添加用户信息异常:"+exception.getMessage());
    }

}

UserFallback异常处理:

@Slf4j
public class UserFallback {

    public static List<UserExternal> selectUserAllFallback(Throwable throwable) {
        log.error("添加订单|添加用户信息异常,触发熔断兜底操作。");
        throw new ParamException(600,"添加用户信息异常,触发兜底操作");
    }
}

6、总结

SpringCloud集成Dubbo到目前为止就介绍完毕了,希望本博客对你有所帮助。目前只介绍了如何使用dubbo,dubbo还有需多需要去学习的地方,让我们持续学习新的知识,来应对工作中的各种问题。