SpringCloud微服務整合Dubbo

sowler發表於2024-03-28

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還有需多需要去學習的地方,讓我們持續學習新的知識,來應對工作中的各種問題。

相關文章