Spring Cloud Zookeeper 分佈式服務框架搭建常見問題


1 子項目如何不繼承主項目,而繼承最新的 Spring Boot 依賴?

對於一個多模塊的項目而言,新建的項目通常是繼承主項目,但是新建的子項目也可以選擇繼承最新的 SpringBoot 項目,具體操作如下:

子項目 pom.xml 文件中指定最新的 SpringBoot parent 模塊進行繼承:

./cloud-zookeeper-provider/pom.xml
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/>
    </parent>

父項目 pom.xml 文件添加子模塊的 module:

./pom.xml
    <modules>
        <module>cloud-zookeeper-provider</module>
    </modules>

在父項目 pom.xmlmodule 中添加之後,使用 maven 命令可以將子模塊一起打包

2 子項目繼承主項目,然後選擇不同的 SpringBoot 版本,是否可行?

理論上是可行的,但是實際操作不行。具體看下圖:

pom.xml

操作的 pom.xml 文件爲: ./cloud-zookeeper-provider/pom.xml

將主項目作爲父項目進行繼承,更改 spring-boot-starter-web 的版本爲當前最新版 2.2.4.RELEASE,但是實際的 Maven 依賴卻依舊是主項目的版本,即 2.0.6.RELEASE

3 Zookeeper 的 Maven 版本選擇

官方文檔推薦的 ZookeeperMaven 依賴使用方式爲:

        <!-- Zookeeper -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>${zookeeper.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

具體可參考:

Spring Cloud Zookeeper

作者在示例中使用的版本是 3.4.12, 2018 年 4 月推出的,這不是最新版,3.4 系列的最新版本爲 3.4.14,2019 年 4 月推出的,當前(2020年3月) 的最新版爲 3.5.7,2020 年 2 月推出的。

在實際操作中,使用 3.4.12 版本,連接 zookeeper 耗時不到 3 秒,基本都是秒連;使用 3.4.14 版本,連接 zookeeper 需要耗時 60 秒;使用 3.5.7 版本根本連接不上 zookeeper

使用 3.4.14 版本的 SpringBoot 項目啓動日誌:

2020-03-01 16:33:20.630  INFO 50249 --- [  restartedMain] org.apache.zookeeper.ZooKeeper           : Initiating client connection, connectString=172.16.140.10:2181 sessionTimeout=60000 watcher=org.apache.curator.ConnectionState@29fd1024
2020-03-01 16:33:20.650  INFO 50249 --- [  restartedMain] o.a.c.f.imps.CuratorFrameworkImpl        : Default schema

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.4.RELEASE)

2020-03-01 16:34:20.651  INFO 50249 --- [16.140.10:2181)] org.apache.zookeeper.ClientCnxn          : Opening socket connection to server 172.16.140.10/172.16.140.10:2181. Will not attempt to authenticate using SASL (unknown error)
2020-03-01 16:34:20.674  INFO 50249 --- [16.140.10:2181)] org.apache.zookeeper.ClientCnxn          : Socket connection established to 172.16.140.10/172.16.140.10:2181, initiating session
2020-03-01 16:34:20.683  INFO 50249 --- [16.140.10:2181)] org.apache.zookeeper.ClientCnxn          : Session establishment complete on server 172.16.140.10/172.16.140.10:2181, sessionid = 0x1000575d4b10047, negotiated timeout = 40000
2020-03-01 16:34:20.689  INFO 50249 --- [ain-EventThread] o.a.c.f.state.ConnectionStateManager     : State change: CONNECTED

根據日誌可以看出從 2020-03-01 16:33:20 開始初始化 zookeeper 連接到 2020-03-01 16:34:20 成功連接,中間消耗了 60 秒,雖然項目可以運行,但是連接耗時太多,每次啓動項目需要太長時間,因此不建議使用該版本

使用 3.5.7 版本 SpringBoot 項目啓動日誌:

2020-03-01 16:43:53.931 ERROR 50516 --- [tor-Framework-0] o.a.c.f.imps.CuratorFrameworkImpl        : Background operation retry gave up

org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss
	at org.apache.zookeeper.KeeperException.create(KeeperException.java:102) ~[zookeeper-3.5.7.jar:3.5.7]
	at org.apache.curator.framework.imps.CuratorFrameworkImpl.checkBackgroundRetry(CuratorFrameworkImpl.java:844) [curator-framework-4.0.1.jar:4.0.1]
	at org.apache.curator.framework.imps.CuratorFrameworkImpl.performBackgroundOperation(CuratorFrameworkImpl.java:972) [curator-framework-4.0.1.jar:4.0.1]
	at org.apache.curator.framework.imps.CuratorFrameworkImpl.backgroundOperationsLoop(CuratorFrameworkImpl.java:925) [curator-framework-4.0.1.jar:4.0.1]
	at org.apache.curator.framework.imps.CuratorFrameworkImpl.access$300(CuratorFrameworkImpl.java:73) [curator-framework-4.0.1.jar:4.0.1]
	at org.apache.curator.framework.imps.CuratorFrameworkImpl$4.call(CuratorFrameworkImpl.java:322) [curator-framework-4.0.1.jar:4.0.1]
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) [na:1.8.0_191]
	at java.util.concurrent.FutureTask.run(FutureTask.java) [na:1.8.0_191]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_191]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_191]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]

2020-03-01 16:43:53.931 ERROR 50516 --- [tor-Framework-0] o.a.c.f.imps.CuratorFrameworkImpl        : Background retry gave up

org.apache.curator.CuratorConnectionLossException: KeeperErrorCode = ConnectionLoss
	at org.apache.curator.framework.imps.CuratorFrameworkImpl.performBackgroundOperation(CuratorFrameworkImpl.java:954) [curator-framework-4.0.1.jar:4.0.1]
	at org.apache.curator.framework.imps.CuratorFrameworkImpl.backgroundOperationsLoop(CuratorFrameworkImpl.java:925) [curator-framework-4.0.1.jar:4.0.1]
	at org.apache.curator.framework.imps.CuratorFrameworkImpl.access$300(CuratorFrameworkImpl.java:73) [curator-framework-4.0.1.jar:4.0.1]
	at org.apache.curator.framework.imps.CuratorFrameworkImpl$4.call(CuratorFrameworkImpl.java:322) [curator-framework-4.0.1.jar:4.0.1]
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) [na:1.8.0_191]
	at java.util.concurrent.FutureTask.run(FutureTask.java) [na:1.8.0_191]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_191]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_191]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]

根據日誌可以看出 3.5 系列的 zookeeper 連接方式已經發生了改變,與當前SpringBoot 的版本中包含的相關依賴是不兼容的,因此切不可貿然使用最新版本

4 SpringBoot Zookeeper 項目正在運行時刪除 Zookeeper 的節點會怎麼樣?

SpringBoot Zookeeper 項目在啓動的時候會向 Zookeeper 中註冊節點,節點的名稱即爲 Spring Boot application.yml配置文件中的 spring.application.name 屬性

本着不作死就不會死的心態,作者將正在運行的 cloud-zookeeper-provider 項目註冊的 zookeeper 節點進行刪除,然後看看項目是否會自動重新註冊節點,然而實際情況是不會,而且項目還會報錯。

具體操作流程如下:

  • 1 啓動 cloud-zookeeper-provider 項目
  • 2 查詢 cloud-zookeeper-provider 註冊的 zookeeper 節點,校驗節點是否成功註冊,結果是節點註冊成功
  • 3 請求 cloud-zookeeper-provider 項目的接口 http://127.0.0.1:8100/api/cloud/zookeeper/hello?name=dakhfak ,驗證服務是否可用,結果是可用
  • 4 刪除 cloud-zookeeper-provider 註冊的 zookeeper 節點(刪除命令: rmr /services/cloud-zookeeper-provider)
  • 5 再次請求 cloud-zookeeper-provider 項目的接口 http://127.0.0.1:8100/api/cloud/zookeeper/hello?name=dakhfak ,驗證服務是否可用,結果是可用
  • 6 啓動 cloud-zookeeper-feign 項目,用於調用 cloud-zookeeper-provider 服務,項目可成功啓動
  • 7 請求 cloud-zookeeper-feign 項目的接口 http://127.0.0.1:8102/api/cloud/zookeeper/feign/sayHello?name=dada666 ,校驗 cloud-zookeeper-providerzookeeper 服務是否可調用,結果是拋出異常,拋出的異常如下:
com.netflix.client.ClientException: Load balancer does not have available server for client: cloud-zookeeper-provider
	at com.netflix.loadbalancer.LoadBalancerContext.getServerFromLoadBalancer(LoadBalancerContext.java:483) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
	at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:184) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
	at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:180) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
	at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]

根據日誌可以看出 cloud-zookeeper-provider 服務已經不存在,從而導致系統異常。cloud-zookeeper-provider 項目在沒有重啓的情況下並沒有在 zookeeper 節點刪除之後重新創建新節點

5 Feign 調用 Spring Cloud Zookeeper 服務只能設置一種請求方式

在 Cloud Zookeeper 服務註冊中心項目中 ,服務允許兩種請求方式,即 GET 和 POST,但是在使用 Feign 進行調用時,只能設置成一種,具體代碼如下:

服務註冊中心的控制層:

./cloud-zookeeper-provider/src/main/java/com/ljq/demo/springboot/cloud/zookeeper/provider/controller/CloudZookeeperProviderController.java
    @RequestMapping(value = "/hello", method = {RequestMethod.GET, RequestMethod.POST},
            produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<String> hello(@RequestParam("name") String name) {

hello 方法對應的接口是允許使用 GET 和 POST 方法進行請求的

服務調用者-Feign 客戶端:

./cloud-zookeeper-feign/src/main/java/com/ljq/demo/springboot/cloud/zookeeper/feign/service/CloudZookeeperFeignService.java
    @GetMapping(value = "/api/cloud/zookeeper/hello", produces = {MediaType.APPLICATION_JSON_VALUE})
    String sayHello(@RequestParam("name") String name);

這裏調用遠程 cloud-zookeeper-provider 的服務,只能指定一種請求方式,如果按照上邊的方式 method = {RequestMethod.GET, RequestMethod.POST 允許了兩種方式請求的話,會拋出異常

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'cloudZookeeperFeignController': Unsatisfied dependency expressed through field 'cloudZookeeperFeignService'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.ljq.demo.springboot.cloud.zookeeper.feign.service.CloudZookeeperFeignService': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Method sayHello can only contain 1 method field. Found: [GET, POST]

6 使用 Feign 調用遠程 Zookeeper 服務提示 FeignException$BadRequest: status 400

很多人會納悶,方法,參數,請求地址都沒有問題,爲什麼還會拋出 BadRequest 異常呢?這裏有一個很難讓人想到的問題,通常在寫 Controller 中方法時,很多人不會專門寫 @requestParam 註解來指定參數的名稱,請求依然可以成功,但是在使用 Feign 進行遠程調用的時候就必須使用 @requestParam 註解指定請求參數名稱

./cloud-zookeeper-feign/src/main/java/com/ljq/demo/springboot/cloud/zookeeper/feign/service/CloudZookeeperFeignService.java
    @GetMapping(value = "/api/cloud/zookeeper/hello", produces = {MediaType.APPLICATION_JSON_VALUE})
    String sayHello(@RequestParam("name") String name);

7 使用 Feign 調用遠程 Zookeeper 服務提示 FeignException$MethodNotAllowed: status 405

方法不被允許,這個問題就比較好排查了,通常出現在服務註冊中心的接口允許的請求方式與 Feign 調用的請求方式不一致導致的,如註冊中心的接口只允許 POST 方式請求,而 Feign 調用時爲 GET 方式。

Feign 調用遠程 Zookeeper 服務時與服務端接口請求方式保持一致即可。

參考文檔: 【feign】解決–feign.FeignException$MethodNotAllowed: status 405 reading

8 Spring Cloud Zookeeper 的 @LoadBalanced 註解是什麼?有什麼用?不用可不可以?

@LoadBalanced 是用於客戶端負載均衡的註解,可以根據 URL 解析來確定使用哪一個服務實例。

具體說明可參考: 由springcloud ribbon的 @LoadBalanced註解的使用理解

不用可不可以?答案是不行,否則默認使用的是 http 請求

異常日誌如下:

2020-03-01 20:42:18.411 ERROR 55042 --- [nio-8101-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://cloud-zookeeper-provider/api/cloud/zookeeper/hello": cloud-zookeeper-provider; nested exception is java.net.UnknownHostException: cloud-zookeeper-provider] with root cause

java.net.UnknownHostException: cloud-zookeeper-provider

9 Spring Cloud Config 配置中心的配置文件有哪些注意事項?

  • 1 中文需要進行 Unicode 轉碼
  • 2 不要對配置信息添加引號,否則引號也被算作配置的一部分
  • 3 若同時存在同名的 propertiesyml 配置文件,則只能讀取到同名的 properties 文件中的配置信息
  • 4 如果配置中心的配置與項目中的本地配置名衝突,則會在項目啓動時覆蓋本地的配置
  • 5 如果使用 Git 倉庫存儲配置信息,那麼 Spring Cloud Config Server 職能讀取到已經提交的配置信息
  • 6 配置中心的配置更新不能及時更新到客戶端。直接調用 Cloud Config Server 端配置可獲取到最新配置,但是 Client 端不能讀取到最新配置(關於這個問題,可通過 webHook 或 Spring Cloud Bus 解決,參考: Spring Cloud(七):配置中心(Git 版與動態刷新)【Finchley 版】 )

X Github 源碼

Gtihub 源碼地址 : https://github.com/Flying9001/springBootDemo

個人公衆號:404Code,分享半個互聯網人的技術與思考,感興趣的可以關注.
404Code

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章