2019年9月1日 星期日

Java 9 功能

※Module



新建一個專案,然後增加兩個 module,如上圖,Module1 和 Module2
在 Module1 增加 class M1;Module2 增加 class M2
M1 想用 M2 預設是沒辦法的,所以在兩個 module 的 src 按右鍵增加 module-info

1.M2 要給別人用,所以要匯出,都是以 package 為單位
module Module2 {
    exports xxx.ooo;
}

2.M1 要用別人的,所以要要求使用,如果要用 java 內鍵的,按下 content assist 會有提示
module Module1 {
    requires Module2;
}

3.此時還是會編譯錯誤,還得如下使用:

4.好文章


※介面可用 private 方法


※String 和 AbstractStringBuilder 

從 char[] 改成 byte[],AbstractStringBuilder 是 StringBuffer 和 StringBuilder 的抽象父類別


※jshell

java 的 shell,在安裝目錄的 bin 裡,每次要測簡單的東西,都要寫 class,然後在寫個 main 方法是很累人的,所以直接在裡面打即可
1.除了 java.lang 外,打 /imports 是預設有 import 的
2.不用 try catch
3.屬性和方法會後者蓋前者



※省略泛型和增強 try-with-resource

new Comparable<>(){
@Override
public int compareTo(Object o) {
return 0;
}
};

※在 java 7 就可以省略 <> 裡的東西了,但匿名類別不行,在這一版可以了


InputStreamReader isr = new InputStreamReader(System.in);
try(isr) {

} catch(Exception e) {

}

※在 java 7 就可以有 try () 了,但宣告都要在裡面才行,在這一版可以寫在外面,裡面寫變數名稱即可

※不能改 try 裡的東西,裡面是 final 的



※不可變的可讀集合

List.of(1, 2, 3, 4, 5);
Set.of(1, 1, 2, 2, 3);
Map.of("a", 1, "b", 2);
Map.ofEntries(Map.entry("c", 3), Map.entry("d", 4));



※Optional 增加 3 個方法

.stream
List<Integer> list = List.of(1, 2, 3);
Optional<List<Integer>> oList = Optional.ofNullable(list);
Stream<List<Integer>> stream = oList.stream();

※回傳的和 Optional 的泛型一樣

.or
Optional<Integer> op = Optional.ofNullable(null);
op = op.or(() -> Optional.of(7));
System.out.println(op.get());

※如果不是 null 就回傳,否則就回傳 7


.ifPresentOrElse
Optional<Integer> op = Optional.ofNullable(null);
op.ifPresentOrElse(x -> System.out.println("x=" + x), () -> System.out.println("hahaha"));

※回傳 x 或 hahaha


※Stream 增加 4 個方法

Stream.iterate(0, n -> n < 10, n -> ++n).forEach(System.out::println);

※這是個 overloading 方法,java 8 只有一個,要加 limit 才不會變成無限流,現在有了中間參數是 Predicate,可以不用 limit 了


Stream.of(1, 2, 6, 7, 4, 3).takeWhile(n -> n < 6).forEach(System.out::println); // 1 2
Stream.of(1, 2, 6, 7, 4, 3).dropWhile(n -> n < 6).forEach(System.out::println); // 6 7 4 3
takeWhile:從第一個開始判斷,如果條件成立( < 6),就抓,但只要條件不成立,馬上返回 (雖然後面也有條件成立的,但不管)

dropWhile:takeWhile 的相反,從第一個開始判斷,如果條件成立就刪除,但只要條件不成立,馬上返回 (雖然後面也有條件成立的,但不管)

Stream.of(null, null).count(); // 2 這個不是新增的,如果有兩個以上 (包括兩個) 的元素,不會報錯,但如果只有一個 null,會報空指針,所以可以用新方法 ofNullable,但回傳的是 0
Stream.ofNullable(null).count(); // 0



※ElementType 增加 MODULE

使用在 module-info 裡,例子可看這篇最下面

2019年8月15日 星期四

java 的 ~、^、<<、>>、>>>、<<=、>>=、>>>=



final byte x = 40;
final byte y = -40;
    
System.out.println(~x); // -41
System.out.println(~y); // 39
    
System.out.println(5 ^ 6); // 3
System.out.println(x ^ y); // -16
System.out.println(Integer.toBinaryString(x)); // 101000
System.out.println(Integer.toBinaryString(y)); // 11111111111111111111111111011000
System.out.println(Integer.toBinaryString(-16)); // 11111111111111111111111111110000
    
System.out.println(x << 2); // 160
System.out.println(x >> 2); // 10
System.out.println(x >>> 2); // 10
    
System.out.println(y << 2); // -160
System.out.println(y >> 2); // -10
System.out.println(y >>> 2); // 1073741814
System.out.println(y >>> 4); // 268435453

※這裡的符號都會轉換成二進制,可用 Integer.toBinaryString 或 Long.toBinaryString 查看,這兩個差在 32 位和64 位


40 的二進制:(前面沒有視同 0,總共 32 或 64 位) 10 1000
-40 的二進制:1111 1111 1111 1111 1111 1111 1101 1000


※正二進制轉負二進制

二進制取反 +1
如:-40 的二進制就是 40 的二進制取反 +1
40 -> 10 1000 取反 --> 01 0111 加 1 -> (前面很多1)  01 1000

※負的二進制轉成 10 進制

取反後 +1 的 10 進制乘 -1

※~ 取反(簡單公式就是 (x+1)*-1)

正數:二進制+1後轉10進制,然後乘-1
負數:二進制取反
.40 轉二進制 -> 10 1000 -> 10 1001 = 41 * -1 -> -41

.-40 轉二進制 -> (前面很多1) 01 1000 取反->10 0111 = 39



※ ^ XOR 互相排斥,一正一反為 true,兩正兩反為 false

.5 ^ 6
5 -> 101
6-> 110
XOR 後,為 011 -> 3

.40 ^ -40
40 -> 101000
-40 -> 1111 1111 1111 1111 1111 1111 1101 1000
XOR 後,為 (前面很多1) 11 0000

驗證:
(前面很多1) 11 0000 取反 -> 1111 + 1 -> 1 0000 -> 16 * -1 = -16

※有兩個數想要互換,但不能用中間的 temp 變數,有以下兩種方法

一個數對另一個數互斥兩次,值不會變
var x = 66;
var y = 77;
x = x ^ y;
y = x ^ y;
x = x ^ y;
要小心 x 和 y 的值一樣時,不能用這招,直接 return 即可
----------------------------------
先取得兩數的和再進行減法運算
var x = 66;
var y = 77;
x = x + y;
y = x - y;
x = x - y;

要小心 x+y 超過類型的範圍就不行了


※ << 左移運算符

.40 -> 10 1000
<< 2 往左二位,就相當於在最右邊增加 2 個 0
1010 0000 -> 2 的 7 次方 + 2 的 5 次方 -> 128 + 32 = 160
以十進位來說, <<2 就是乘 2 的 2 次方;<<3 就是乘 2 的 3 次方

.-40 ->  (前面很多1)  01 1000 -> 0110 0000

驗證:
(前面很多1) 0110 0000 取反 -> 1001 1111 + 1 -> 1010 0000 -> 32 + 128 -> 160 * -1 = -160


可以背快速的用法:
例一:40 << 4:4 表示 2 的 4 次方,結果為 40 *16
例二:1 << 3:1 * 8

※ >> 右移運算符

.40 -> 10 1000
>> 2 往右二位,就相當於最右邊刪除 2 位,如果是正數,最左邊補 2 個 0
1010 = 10
以十進位來說, >>2 就是除 2 的 2 次方;>>3 就是除 2 的 3 次方,如果有小數點都是無條件捨去

.-40 -> (前面很多1)  01 1000 ->  (前面很多1) 0110,負數最左邊是補 1

驗證:
(前面很多1) 0110 取反 -> 1001 + 1 -> 1010 = 10 * -1 = -10


※左、右移運算符的應用可看這篇




※ >>> 無符號右移運算符 (就是只有正數,正數結果和 >> 一樣)

※就算宣告成 byte,結果還是 32 位,也不會報錯,可以正常使用

※因為只有正數,左邊一定是 0,至於有幾個 0,要看 >>> 3 給 3 那就是 3 個 0;右邊的處理和 >> 一樣,刪 3 個最右邊的

※>> 右移運算符的負數是最左邊補 1,無符號是補 0,最左邊補0後,換算時就不是負的了,所以說只有正數,不用再取反+1了

-40 -> (前面很多1)  01 1000

.>>> 2,就相當於最右邊刪除 2 位,最左邊 2 個改 0
‭0011 1111 1111 1111 1111 1111 1111 0110‬
最左邊的 0 可以刪除


.>>> 4,就相當於最右邊刪除 4 位,最左邊 4 個改 0
‭0000 1111 1111 1111 1111 1111 1111 1101
最左邊的 0 可以刪除‬,最下面小算盤的圖就是省略了

從最右邊的 2的0次方到最左邊的 2 的 31 次方,一個一個加起來就是 10 進位了,但這個用人工算太累了,可用小算盤,如下:




※<<=、>>=、>>>=


int x = 40 << 2;

int o = 40;
o <<= 2; // o = o << 2;

此時 x 和 o 是一樣的意思,一定要分兩行,否則編譯錯誤,右移和無符號右移也是一樣,這在 jdk7 的 HashMap 原碼看到的

2019年7月27日 星期六

設定中心 ( SpringCloud 2.x 七)

因為每一個微服務都有一個 application.yml,這裡是做一個統合的管理

※Server 端設定


※新增一個全新的專案,放在 github 上,裡面就放一個 application.yml,內容如下:
spring:
  profiles:
    active: dev
---
spring:
  profiles: dev
  application:
    name: xxx-dev
---
spring:
  profiles: uat
  application:
    name: xxx-uat
---
spring:
  profiles: test
  application:
    name: xxx-test


※ 連 --- 也不能省略

※spring.profiles.active 還可以配合 @Profile 使用



※新增一個 module,增加 pom

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>




※application.yml
server:
  port: 1111
spring:
  application:
    name: config-center
  cloud:
    config:
      server:
        git:
          uri: https://github.com/bruce12452002/SpringCloudConfigCenterPractice.git # git網址


※和 uri 同層的還有 username 和 password,是 github 的帳號和密碼,但本來 git clone 就不用打帳號密碼,所以沒有要 push 的話,可以不用寫

※uri 一打,啟動後的控製台就沒有預設的 8888 port 了

※main 方法增加 @EnableConfigServer,然後啟動測試看看

※hosts 增加 127.0.0.1       config.ooo1111 來模擬

※打上如下的網址,即可看到結果:
http://config.ooo1111:1111/application-dev.yml
http://config.ooo1111:1111/master/application-dev.yml

spring:
  application:
    name: xxx-dev
  profiles:
    active: dev



http://config.ooo1111:1111/application-uat.yml
spring:
  application:
    name: xxx-uat
  profiles:
    active: dev



http://config.ooo1111:1111/application-xxx.yml
spring:
  profiles:
    active: dev

※格式不是亂打的,看官網,label 是分支的意思



※Client 端設定


※在github 增加 abcxxx.yml,內容如下:
spring:
  profiles:
    active: dev
---
server:
  port: 8001
spring:
  profiles: dev
  application:
    name: my-config-dev
eureka:
  client:
    service-url:
      defaultZone: http://xxx.ooo9051:9051/eureka
---
server:
  port: 8002
spring:
  profiles: uat
  application:
    name: my-config-uat
eureka:
  client:
    service-url:
      defaultZone: http://xxx.ooo9051:9051/eureka
---
server:
  port: 8003
spring:
  profiles: test
  application:
    name: my-config-test
eureka:
  client:
    service-url:
      defaultZone: http://xxx.ooo9051:9051/eureka




※新建一個 model,加入 pom
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>


※父 pom 已經有 spring-cloud-starter-config,所以不用加


※寫一個測試的方法
@RestController
public class ClientConfigInfo {
    @Value("${spring.application.name}")
    private String appName;
    
    @Value("${eureka.client.service-url.defaultZone}")
    private String eurekaServerName;
    
    @Value("${server.port}")
    private Integer port;
    
    @GetMapping("/configInfo")
    public String getConfigInfo() {
        StringBuilder sb = new StringBuilder();
        sb.append("appName=").append(appName)
        .append(", eurekaServerName=").append(eurekaServerName)
        .append(", port=").append(port);
        return sb.toString();
    }
}




※以下只能寫在 bootstrap.yml
spring:
  cloud:
    config:
      name: abcxxx # 讀 github 的 yml,但不能寫副檔名
      profile: dev
      label: master
      uri: http://config.ooo1111:1111  # 找 config 伺服器





※測試

hosts 增加 127.0.0.1       config-client.ooo 模擬,不能用「_」,會報錯

啟動時控製台出現以下訊息:


 ※表示連到了 8003,然後在網址打 http://config-client.ooo:8003/configInfo,即可看到結果

※修改 bootstrap.yml 裡的 spring.cloud.config.profile,只要 github 有的,重新啟動後即可抓到新的設定

Zuul ( SpringCloud 2.x 六)

增加一個 model

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>



server:
  port: 9099
    
spring:
  application:
    name: my-zuul
    
eureka:
  client:
    service-url:
      defaultZone: http://xxx.ooo9051:9051/eureka,http://xxx.ooo9052:9052/eureka,http://xxx.ooo9053:9053/eureka
  instance:
    instance-id: bruce-zuul
    prefer-ip-address: true
    nonSecurePort: ${server.port}
    
info:
  xxx.ooo: xxoo.aa
  name: zuul.info
  jdk_version: @java.version@
  version: @version@
  chi_test: 梅山小路用9099
  ppp: @aaa.bbb@
    
zuul:
  routes:
    mycloud: # 隨便寫
      serviceId: PROVIDER1
      path: /xxx/**
  # prefix: /abc
  # ignored-services: PROVIDER1 # 大寫微服務名稱


 ※ 有些版本訪問 http://zuul.ooo9099:9099/PROVIDER1/testGet 是可以成功的,但這樣就暴露了微服務名稱了,所以可以設定 ignored-services,如果全部都不給訪問,可以用「"*"」

※serviceId 會被 path 取而代之



※main 方法加入三個 annotation 即可

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient

※模擬用戶發 request 到 zuul,所以在 hosts 增加 127.0.0.1 zuul.ooo9099



※測試

開啟 eureka -> provider (9001 port) -> zuul

然後在網址打上 http://zuul.ooo9099:9099/xxx/testGet 即可訪問

又如果有加 prefix,那就要換成如下的網址
http://zuul.ooo9099:9099/abc/xxx/testGet


※遇到的問題

application.yml 一定要配 zuul,可能其他版本可以,但我試的結果就是不行

2019年7月21日 星期日

Hystrix ( SpringCloud 2.x 五)

 Hystrix 就像保險絲一樣,可以處理異常


※server 端的異常


※1.複製 provider 後,增加 pom

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>


※這樣才有 @EnableCircuitBreaker、@HystrixCommand 可用

※順便改個 yml,instance.instance-id: bruceProvider-hystrix



※2.增加 provider-hystrix 的 controller

@RestController
public class TestController {
    @GetMapping("/testGet")
    public ApiBean get() {
        ApiBean ab = new ApiBean();
        ab.setId(1);
        ab.setName("xxx9001");
        return ab;
    }
    
    @HystrixCommand(fallbackMethod = "xxx")
    @GetMapping("/testHystrix/{id}")
    public ApiBean getXxxById(@PathVariable("id") Integer id) { // throws ParseException {
        if (id == 1) {
            ApiBean ab = new ApiBean();
            ab.setId(1);
            ab.setName("xxx9001");
            return ab;
        } else {
            throw new RuntimeException("耶!掛了");
            // throw new ParseException("xxx", -1);
        }
    }
    
    private ApiBean xxx(@PathVariable("id") Integer id) {
        ApiBean ab = new ApiBean();
        ab.setId(-1);
        ab.setName("沒有" + id);
        return ab;
    }
}


※只要拋異常都會被 xxx 方法所接收並回傳

※main 方法要加 @EnableCircuitBreake,但加 @EnableHystrix也可以,因為這個註解裡面也用 @EnableCircuitBreake,所以二選一即可

※如果很多方法都要用同一個,可以用全域的方式,如下:
一. 在方法上加 @HystrixCommand,但不寫 fallbackMethod
二. 在 class 上加 @DefaultProperties(defaultFallback="方法名") 即可
本來的 @HystrixCommand(fallbackMethod="方法名") 並不會被影響



※3.consumerFegin 也增加呼叫對應的方法


@RestController
public class ConsumerController {
    @Resource
    private MyService myService;
    
    @GetMapping("/xxx")
    public ApiBean get() {
        return myService.get();
    }
    
    @GetMapping("/ooo/{id}")
    public ApiBean getHystrix(@PathVariable("id") Integer id) {
        return myService.get(id);
    }
}





※4.api 也增加對應的方法

@FeignClient("PROVIDER1")
public interface MyService {
    @GetMapping("/testGet")
    ApiBean get();
    
    @GetMapping("/testHystrix/{id}")
    ApiBean get(@PathVariable("id") Integer id);
}


※使用 http://localhost/ooo/1 是正常的,但只要不是 1,都會出現 hystrix 提供的功能



※client 端的異常


由於 server 端的做法太過於高耦合了,在 client 端可以針對介面做處理,就算 server 端掛了,還是可以儘量的顯示友好的訊息

※1.因為要解耦,所以不要 @HystrixCommand,連 xxx 方法也不需要了,然後在 api 專案的 @FeignClient 有個 fallbackFactory,指定一個類別,然後做異常處理

@FeignClient(name = "PROVIDER1", fallbackFactory = MyFallbackFactory.class)
public interface MyService {
    @GetMapping("/testGet")
    ApiBean get();
    
    @GetMapping("/testHystrix/{id}")
    ApiBean get(@PathVariable("id") Integer id);
}





※2.異常處理類
@Component
public class MyFallbackFactory implements FallbackFactory<MyService> {
    @Override
    public MyService create(Throwable throwable) {
        return new MyService() {
            @Override
            public ApiBean get() {
                return null;
            }
    
            @Override
            public ApiBean get(Integer id) {
                ApiBean ab = new ApiBean();
                ab.setId(-1);
                ab.setName("沒有" + id);
                return ab;
            }
        };
    }
}


※記得要有 @Component 之類的註解,否則出錯時會出現 feign.FeignException: status 500 reading MyService#get(Integer) 的錯誤訊息


※3.cousumerfeign 的 yml 要加上 hystrix.enabled: true


※4.cousumerfeign 啟動類別
@SpringBootApplication
@ComponentScan({"controller", "service"})
@EnableEurekaClient
@EnableFeignClients("service")
public class ConsumerFeignMain {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerFeignMain.class, args);
    }
}


※注意 @ComponentScan 要加上 api MyFallbackFactory 的包名,只要這個沒加或 yml 沒設定為 true,在啟動時都會出現 No fallbackFactory instance of type class service.MyFallbackFactory found for feign client PROVIDER1

※測試時和 server 端一樣,但是多一個,將 provider 關閉時,還是能出現 client 端設定的訊息,不管是不是 1 都是這個結果,所以訊息可以改得好一點的



※Hystrix 儀錶版


就是監控 hystrix 的

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>


※新增一個 module,然後增加 pom

※yml 很簡單,就是 port 而已,server.port: 9010



@SpringBootApplication
@EnableHystrixDashboard
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}


※啟動類別增加 @EnableHystrixDashboard

※訪問 http://localhost:9010/hystrix 就可看到帶刺的豬

測試:開啟 eureka -> provider_hystrix -> consumerFeign -> hystrix_dashboard

被監視的 provider_hystrix 要加 management.endpoints.web.exposure.include=*,否則點下 Monitor Stream 的按鈕時,中間會有 Unable to connect to Command Metric Stream. 的紅字,成功是 Loading ...

※以下沒試出來
1.上圖反白的網址是要監控的網址,所以是 provider_hystrix的,改成 http://localhost:9001/actuator/hystrix.stream,然後開啟新網頁貼上會看到網頁一直再跑,我試的結果居是出現問我要不要下載

2.但第1項是文字介面的,所以在上圖可打網址的地方貼上1的網址就可看到圖形介面

3.用 consumerFeign 訪問 provider_hystrix 即可看到 dashboard 的球變大

2019年7月20日 星期六

Feign ( SpringCloud 2.x 四)

和 Ribbon 都是客户端的負載均衡,Ribbon 用 RestTemplate 封裝 HTTP 的請求;Feign 在這個基礎上,加上大家都熟悉的接口式編程

基本的 consumer 已經很複雜了,所以複製 consumer 成為一個新 module,刪除ribbon相關的內容(兩個 class 和 @RibbonClien,連 ConfigRestTemplate也不要了)


1.api 和 consumerFeign 的 pom
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>


※這樣才有 @FeignClient 和 @EnableFeignClients 可用


2.
@FeignClient("PROVIDER1")
public interface MyService {
    @GetMapping("/testGet")
    ApiBean get();
}


※@FeignClient 指定想訪問的 spring.application.name


3.
@SpringBootApplication
@ComponentScan("controller")
@EnableEurekaClient
@EnableFeignClients("service")
public class ConsumerFeignMain {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerFeignMain.class, args);
    }
}


※@EnableFeignClients 指定 api 裡的包路徑,只要錯了,啟動時就會出現 A component required a bean of type 'service.MyService' that could not be found.


4.
@RestController
public class ConsumerController {
//    private static final String PROVIDER_URI = "http://localhost:9001";
/*    private static final String PROVIDER_URI = "http://"+ "provider1".toUpperCase(); // 網址列不分大小寫,但還是以 eureka 為準的好
    @Resource
    private RestTemplate restTemplate;
    
    @GetMapping("/xxx")
    public ApiBean get() {
        return restTemplate.getForObject(PROVIDER_URI + "/testGet", ApiBean.class);
    }
*/
    
    @Resource
    private MyService myService;
    
    @GetMapping("/xxx")
    public ApiBean get() {
        return myService.get();
    }
}


※使用 feign 後,可以不用寫服務名

※預設是輪詢算法,可以將上一篇的 MyCustomRibbonRule 複製過來,改變自己想要的算法或內鍵的其他算法

2019年7月14日 星期日

Ribbon ( SpringCloud 2.x 三)

Ribbon 為客戶端的負載均衡,所以會改 consumer

@Configuration
public class ConfigRestTemplate {
    @Bean
    // @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}


※新增一個類別,因為要使用到 @LoadBananced 這個註解,只好放棄使用 @Import,但在使用前先註解,確保之前的程式是可以跑的


// @Import(RestTemplate.class)
public class consumerController {
    // private static final String PROVIDER_URI = "http://localhost:9001";
    private static final String PROVIDER_URI = "http://"+ "provider1".toUpperCase();
    // ...
}


※確保之前的程式能跑後,使用 ribbon 要三步
1.打開 @LoadBananced
2.訪問路徑改成 provider 的 spring.application.name 名稱,大小寫都可以,但 eureka 是大寫,最好都用一樣的

※以上缺一都會報錯

※不需要 ribbon 的 jar 包,eureka-client 已經有依賴了,這個 eureka-client 在第一篇已經加過了

※遇到的問題:

如果出現 Request URI does not contain a valid hostname 的錯,表示 spring.application.name 的名字找不到,不要忘了要加 http://
另外一個是 name 名稱不能有「_」,都會報這樣的錯

這個專案在隔天 run 時,居然出現找不到 PROVIDER1 的錯,結果 mvn clean install 就解決了



※測試 ribbon 預設的模式

新增兩個專案:
1.複製 pom.xml
2.複製 啟動類別和 controller,為了測試區別,/xxx 的內容三支都修改 ab.setName("xxx9002"); // 9001-9003
3.instance.instance-id 修改不同的名稱,但 spring.application.name 一樣
4.http://localhost/xxx 每重整一次會發現是有順序性的,如第一次循環是 132,就會一直按 132 的方式循環

畫面如下:
可看見 PROVIDER1 有三個實例



※改變預設模式


@Configuration
public class ConfigRestTemplate {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
    
    @Bean
    public IRule myRibbonRule() {
    // return new RoundRobinRule();
    // return new RandomRule();
        return new RetryRule();
    }
}

※在自訂的 ConfigRestTemplate 增加 IRule 的回傳 Bean,RoundRobinRule 是預設的、
RandomRule 是隨機的、RetryRule 和預設的很像,差在如果其中有 provider 掛了就不一樣了,假設是 132 一直循環,然後 2 掛了,那就會是 13掛、13掛、經過幾次之後,就只會有13而已,可以將三個 provider 啟好後,關閉其中一個測試

※也可以寫個 class,然後回傳 IRule



※自定義規則



@Configuration
public class MyCustomRibbonRule {
    @Bean
    public IRule myRibbonRule() {
        // return new RandomRule();
        return new CalcRibbonRule();
    }
}


※改變預設模式的 @Bean 要註解或改名,不然會有 2 個 IRule


public class CalcRibbonRule extends AbstractLoadBalancerRule {
    private int currentIndex = 1; //  PROVIDER1 的機器號碼
    
    private Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;
    
        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers(); // 到的有幾台機器
            List<Server> allList = lb.getAllServers(); // 全部有幾台機器
    
            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }
    
            // 主要邏輯在這
            if (upList.size() % 2 == 1) {
                server = upList.get(currentIndex);
                currentIndex++;
                System.out.println("size==>" + upList.size());
                System.out.println("currentIndex==>" + currentIndex);
                if (currentIndex >= serverCount) {
                    currentIndex = 1;
                }
            }
    
            if (server == null) {
                Thread.yield();
                continue;
            }
    
            if (server.isAlive()) {
                return (server);
            }
            server = null;
            Thread.yield();
        }
        return server;
    }
    
    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }
    
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {}
}


※我想不到什麼好規則,所以就是奇數的機器才去訪問,但並不是 9051 和 9053 這兩台機器的意思,機器的順序不是我們決定的,假設是 132,那就是 1 和 2

※本來是將 CalcRibbonRule 寫在 MyCustomRibbonRule 裡面,成為內部類別,但訪問的時候報錯了,NoSuchMethodException: controller.MyCustomRibbonRule$CalcRibbonRule.

※最後 consumer 的 main 方法要加上 @RibbonClient(value = "PROVIDER1", configuration = MyCustomRibbonRule.class) 即可