2019年7月12日 星期五

向註冊中心註冊與發現服務 ( SpringCloud 2.x 一)

※POM

※父POM

<packaging>pom</packaging>
<modules>
    <module>provider</module>
    <module>consumer</module>
    <module>api</module>
    <module>eureka</module>
</modules>
    
<groupId>scp</groupId>
<artifactId>SpringCloudPractice</artifactId>
<version>1.0-SNAPSHOT</version>
    
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring-cloud-release.version>Greenwich.RELEASE</spring-cloud-release.version>
</properties>
    
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud-release.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.5.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
    
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>


※生產者和消費者向註冊中心註冊,api 為生產者和消費者共用的方法


※consumer pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
    
<artifactId>consumer</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <artifactId>api</artifactId>
        <groupId>scp</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>



※provider pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
    
<artifactId>provider</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <artifactId>api</artifactId>
        <groupId>scp</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>



※api pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
    
<artifactId>api</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>
    
<build>
    <finalName>cyc</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <classifier>exec</classifier>
            </configuration>
        </plugin>
    </plugins>
</build>

※因為 api 有程式碼給 consumer 和 provider 使用,maven install 會失敗,所以要在 api 加上 plugin 才可以

※eureka pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
    
<artifactId>eureka</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        <!--<artifactId>spring-cloud-netflix-eureka-server</artifactId>没有 starter 是错的-->
    </dependency>
</dependencies>




※java 類別


※provider

package controller;
    
@RestController
public class TestController {
    @GetMapping("/testGet")
    public ApiBean get() {
        System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        ApiBean ab = new ApiBean();
        ab.setId(1);
        ab.setName("xxx");
        return ab;
    }
}



※consumer

package controller;
    
@RestController
@Import(RestTemplate.class)
public class consumerController {
    @Resource
    private RestTemplate restTemplate;
    
    @GetMapping("/xxx")
    public ApiBean get() {
        return restTemplate.getForObject("http://localhost:9001" + "/testGet", ApiBean.class);
    }
}


※因為 RestTemplate 是內鍵的 class,並沒有類似 @Component 這種註解,spring 不知道,所以用了 @Import 將它註冊到 spring

※api

package scp;
    
@Data
public class ApiBean implements Serializable {
    private int id;
    private String name;
}




※eureka

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




※application.yml


※provider

server:
  port: 9001
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9051/eureka



※consumer

server:
  port: 80
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9051/eureka



※eureka

server:
  port: 9051
eureka:
  client:
    register-with-eureka: false     # 不註冊自己
    fetch-registry: false     # 取得註冊資訊,因為自己就是註冊中心,不需要取得註冊資訊
    service-url:
      defaultZone: http://localhost:${server.port}/eureka/   # Eureka Server 的位址


※ fetch-registry 一定要 false 才有辦法啟動,否則會報 Cannot execute request on any known server

※ register-with-eureka 如果註冊自己可以順利啟動成功,只是沒什麼意義,感覺就像麥當勞的員工自己也在排隊買漢堡一樣

※server.port 如果給 0,會隨機產生一個,看控製台即可知道


※遇到的問題

invalid LOC header
jar 檔簽名壞了,從倉庫刪除後重新下載即可

Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package
main 方法要有 package,還有另外一個錯誤是要有 main 方法,但錯誤訊息很明顯,所以一個專案放一個有 package 的 main 方法就可以了,方法裡什麼都不寫也行



※Eureka 註冊中心

 啟動後會有現都是 UNKNOWN,所以 consumer 和 provider 還要在 application.yml 加上 spring.application.name,這樣 eureka 才會分辯的出來,修改後如下,eureka 會變成全部大寫英文


 如果想改右邊的 Status 顯示的名稱,還得在 consumer 和 provider 加上 eureka.instance.instance-id,修改後如下,這裡 eureka 不會變成大寫


上上一張圖的左下角有網址,是滑鼠在 status 的 consumer 或 provider 上時顯示的,我記得不改是 localhost 之類的,但不知哪一版改了,如果是 localhost 想改成 ip,還可以加上 eureka.instance.prefer-ip-address: true 就可以了
還有一個很像的設定 eureka.instance.ip-address: ,因為有可能有多張網卡,可以寫死其中一張網卡的 IP,如果兩個都設定,最終註冊在 eureka 會是這裡的設定



※啟動順序 

必需先啟動 eureka,再啟動 provider,最後是 consumer
如果 eureka 在啟動前有其他的專案先啟動了,eureka 抓不到
如果 consumer 在 provider 之前,我試的結果沒問題,但理論上是先有提供者,這樣消費者才抓的到
如果已經照順序啟動好了,將 provider 或 consumer 的 spring.application.name 名字換掉,只重啟改掉的專案,會發現 eureka 會有紅色的警告,因為有個 CAP 理論

Consistency(一致性)
Availability(可用性)
Partition tolerance(分区容错性)

目前不可能全部做到,會犧牲一個


eureka 保證 AP
zookeeper 保證 CP

長時間沒有人連 eureka 會報這個警告,或者我將名稱換掉也會,因為他要保證可用性,有可能只是網路不順,寧可記錄錯的資料也不要刪掉,而預設過了 90 秒後,真的都沒有人連了,才會刪除,如果不喜歡這個功能,可以關掉
eureka.server.enable-self-preservation: false


※actuator/info

上面的 Status 一點進去就掛了,這裡是設定裡面的資訊

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
-----------------------------
<build>
    <finalName>SpringCloudPractice</finalName>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <configuration>
                <delimiters>
                    <delimit>@</delimit>
                </delimiters>
            </configuration>
        </plugin>
    </plugins>
</build>


※這裡的 pom 是全域的

※delimit 裡面的符號是用在註冊進 eureka 的專案要用到的,前後用這個符號包起來即可

※以下以 provider 為例,在 application.yml 增加如下的設定

info:
  xxx.ooo: xxoo.aa
  name: provider.info
  jdk_version: @java.version@
  version: @version@
  chi_test: 梅山小路用


※info 下的 key 和 value 都可以隨便亂打,但 @ 包起來的部分會去 pom 抓,有自己的就抓自己的,沒有就抓父 pom 的,雖然 provider 有 api,但不會去抓,如果都沒有,啟動會報錯




※服務發現


使用 DiscoveryClient 可以發現 Eureka 上註冊的服務

@Resource
private DiscoveryClient client;
    
@GetMapping("/discovery")
public DiscoveryClient discoveryTest() {
    List<String> services = client.getServices();
    System.out.println("所有服務名稱=" + services);
    
    // List<ServiceInstance> instances = client.getInstances("provider1".toUpperCase()); // 一定要大寫
    services.forEach(serviceName -> {
        List<ServiceInstance> instances = client.getInstances(serviceName);
        instances.forEach(s -> {
            System.out.println("host=" + s.getHost()); // 192.168.254.104
            System.out.println("instanceId=" + s.getInstanceId()); // bruceProvider1
            System.out.println("scheme=" + s.getScheme()); // null
            System.out.println("serviceId=" + s.getServiceId()); // PROVIDER1
            System.out.println("port=" + s.getPort()); //  9001
            System.out.println("uri=" + s.getUri()); // http://192.168.254.104:9001
    
            Map<String, String> metadata = s.getMetadata();
            metadata.forEach((k, v) -> {
                System.out.print("key=" + k); // management.port
                System.out.println(", value=" + v); // 9001
            });
        });
        System.out.println("------------------------------------");
    });
    return client;
}


※在 provider 加上上面的程式碼

※注意 DiscoveryClient是
org.springframework.cloud.client.discovery 的包

※在 provider 的啟動類上加上 @EnableDiscoveryClient,但我試的結果,不加也可以



private static final String PROVIDER_URI = "http://localhost:9001";
    
@GetMapping("/consumer/discovery")
public Object discovery() {
    return restTemplate.getForObject(PROVIDER_URI + "/discovery", Object.class);
}


※provider 最後是要給 consumer 的,所以這裡新增這一段程式碼,但回傳值我給 DiscoveryClient 後,打上網址 localhost/consumer/discovery 會報錯



※Eureka 增加帳號密碼

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>


※在 Eureka server 增加 security 的 POM

※application.yml 增加 spring.security.user,下一層有 name 和 password 可以設定帳號密碼

※如果有集群,每個都要加 pom 和設定密碼,也可以設定不一樣的帳密

※如果只有加 pom,連網址就會出現打帳密的視窗,但因為沒有帳密進不去


@SpringBootApplication
@EnableEurekaServer
public class EurekaMain extends WebSecurityConfigurerAdapter {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain.class, args);
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // http.csrf().disable(); // 不處理 CSRF 攻擊
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }
}


※繼承 WebSecurityConfigurerAdapter 後,覆寫 configure,參數是 HttpSecurity 的

※也可以自己寫一個類別繼承 WebSecurityConfigurerAdapte

※不繼承,server 端不會有問題,差在 client 端要連的時候會報 Cannot execute request on any known server 的錯

※用戶端連 eureka 時,只要在 application.yml 的 defaultZone 改一下就可以,如
http://xxx:ooo@xxx.ooo9051:9051/eureka
就是在原本的網址加上「帳號:密碼@」

沒有留言:

張貼留言