1. 案例說明
- 建立一個商品上架的微服務項目,當服務提供者上架新商品,傳回商品ID給服務消費者
- 完整業務流程圖:
1.1 案例資料庫環境準備(使用Mysql 5.7.x)
CREATE TABLE products(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50), #商品名稱
price DOUBLE,
flag VARCHAR(2), #上架狀态
goods_desc VARCHAR(100), #商品描述
images VARCHAR(400), #商品圖檔
goods_stock INT, #商品庫存
goods_type VARCHAR(20) #商品類型
);
1.2 工程搭建
1.2.1 父工程 lagou-parent
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>lagou-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--父工程打包方式-->
<packaging>pom</packaging>
<!--spring boot 父啟動器依賴-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<!--web依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--日志依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!--測試依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<!-- Actuator可以幫助你監控和管理Spring Boot應用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<!--編譯插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<!--打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
1.2.2 公共元件微服務
-
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>lagou-parent</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>lagou-service-common</artifactId> <dependencies> <!-- 加強版的mybatis,讓具體的Mapper接口繼承BaseMapper即可完成相應功能--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <!--pojo持久化使用--> <dependency> <groupId>javax.persistence</groupId> <artifactId>javax.persistence-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies> </project>
- 實體類
package entity;
import lombok.Data;
import javax.persistence.Id;
import javax.persistence.Table;
@Data //自動生成getSet方法
@Table(name = "products")//對應資料表名稱
public class Products {
@Id //辨別主鍵
private long id;
private String name;
private double price;
private String flag;
private String goodsDesc;
private String images;
private long goodsStock;
private String goodsType;
}
1.2.3 商品微服務
-
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>lagou-parent</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>lagou-service-product</artifactId> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>lagou-service-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
- yml檔案配置資訊
server: port: 9000 # 後期該微服務多執行個體,9000(10個以内) spring: application: name: lagou-service-product datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/smd?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: 123456
- mapper接口
package com.rf.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import entity.Products; /** * 現在使用的Mybatis-plus元件是Mybatis的加強版 * 能夠與SpringBoot進行非常友好的整合,對比Mybatis架構隻有使用便捷的改變 * 沒有具體功能的改變 * 具體使用:讓具體的Mapper接口繼承BaseMapper即可 */ public interface ProductMapper extends BaseMapper<Products> { }
- service開發
package com.rf.service; import entity.Products; public interface ProductService { /** * 根據ID查找商品 * @param id * @return */ public Products queryById(Integer id); } package com.rf.service.impl; import com.rf.mapper.ProductMapper; import com.rf.service.ProductService; import entity.Products; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class ProductServiceImpl implements ProductService { @Autowired private ProductMapper productMapper; @Override public Products queryById(Integer id) { return productMapper.selectById(id); } }
- controller開發
package com.rf.controller; import com.rf.service.ProductService; import entity.Products; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/product") public class ProductController { @Autowired private ProductService productService; @GetMapping("/query/{id}") public Products queryByID(@PathVariable Integer id){ return productService.queryById(id); } }
- 啟動類
package com.rf; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.rf.mapper") public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class,args); } }
- 測試
1.2.4 頁面靜态微服務
-
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>lagou-parent</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>lagou-service-page</artifactId> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>lagou-service-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
-
server: port: 9100 # 後期該微服務多執行個體,端口從9100遞增(10個以内) spring: application: name: lagou-service-page datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/smd?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: 123456
- 編寫PageController,在PageController中調用商品微服務對應的URL
package com.rf.controller;
import entity.Products;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/page")
public class PageController {
@Autowired //封裝httpClient對象,執行HTTP請求
private RestTemplate restTemplate;
@RequestMapping("/getProduct/{id}")
public Products getProduct(@PathVariable Integer id){
String url ="http://localhost:9000/product/query/"; //URL位址寫死
Products products = restTemplate.getForObject(url + id, Products.class);
return products;
}
}
- 編寫啟動類,注入RestTemplate
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class PageApplication {
@Bean //封裝httpClient對象,執行HTTP請求
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(PageApplication.class,args);
}
}
1.3 存在問題與解決方案
- 在服務消費者中,我們把url位址寫死到代碼中,不友善後期維護===》服務管理:自動注冊與發現、狀态監管
- 服務提供者隻有一個服務,即便服務提供者形成叢集,服務消費者還需要自己實作負載均衡===》服務負載均衡
- 在服務消費者中,不清楚服務提供者的狀态===》熔斷機制
- 服務消費者調用服務提供者時候,如果出現故障能否及時發現不向使用者抛出異常頁面?===》遠端過程調用
- RestTemplate這種請求調用方式是否還有優化空間?能不能類似于Dubbo那樣玩?==-》網關攔截、路由轉發
- 這麼多的微服務統一認證如何實作?===》網關攔截、路由轉發、統一認證
- 配置檔案每次都修改好多個很麻煩!?===》集中式配置管理,配置資訊實時自動更新
2. 第一代 Spring Cloud 核心元件
網關元件Zuul性能一般,未來将退出Spring Cloud 生态圈,把GateWay劃分到第一代Spring Cloud 核心元件進行講解
一個Feign=RestTemplate+Ribbon+Hystrix三個起到的作用
2.1 Eureka服務注冊中心
- 服務注冊中心
常見的服務注冊中心包括Eureka、Nacos、Zookeeper、Consul
任何一個微服務原則上存在或支援多個服務提供者,為了支援彈性擴、縮容特性,服務提供者的數量和分布往往是動态變化的,需要引入服務注冊中心管理微服務提供者的注冊與發現
分布式微服務架構中,服務注冊中心用于存儲服務提供者位址資訊、服務釋出相關的屬性資訊,消費者通過主動查詢和被動通知的方式擷取服務提供者的位址資訊,而不再需要通過寫死方式得到提供者的位址資訊。
消費者隻需要知道目前系統釋出了那些服務,而不需要知道服務具體存在于什麼位置,這就是透明化路由。
- 步驟:
- 服務注冊中心啟動,開啟注冊與發現功能
- 服務提供者啟動,将相關服務資訊主動注冊到服務注冊中心
- 服務消費者啟動,擷取服務注冊清單資訊
- pull模式:服務消費者主動拉取可用的服務提供者清單
- push模式:服務消費者訂閱服務,當服務提供者有變化時,服務注冊中心主動推送更新後的服務清單給消費者
- 服務消費者直接調用服務提供者
- 服務注冊中心每個30s進行心跳監測,完成服務提供者的健康監控,每隔90s進行剔除檢測,當發現服務提供者失效時進行及時剔除
- 主流服務中心對比
- Zookeeper是一個分布式服務架構,是Apache Hadoop 的一個子項目,它主要是用來解決分布式應用中經常遇到的一些資料管理問題,如:統一命名服務、狀态同步服務、叢集管理、分布式應用配置項的管理等。
- 簡單來說zookeeper本質 = 存儲 + 監聽通知。
-
Zookeeper 用來做服務注冊中心,主要是因為它具有節點變更通知功能,隻要用戶端監聽相關服務節點,服務節點的所有變更,都能及時的通知到監聽用戶端,這樣作為調用方隻要使用
Zookeeper 的用戶端就能實作服務節點的訂閱和變更通知功能了,非常友善。
- 另外,Zookeeper可用性也可以,因為隻要半數以上的選舉節點存活,整個叢集就是可用的,最少節點數為3。
- Eureka由Netflix開源,并被Pivatal內建到SpringCloud體系中,它是基于 RestfulAPI 風格開發的服務注冊與發現元件。
- Consul是由HashiCorp基于Go語言開發的支援多資料中心分布式高可用的服務釋出和注冊服務軟體, 采用Raft算法保證服務的一緻性,且支援健康檢查。
- Nacos是一個更易于建構雲原生應用的動态服務發現、配置管理和服務管理平台。
- 簡單來說Nacos 就是 注冊中心 + 配置中心的組合,幫助我們解決微服務開發必會涉及到的服務注冊與發現,服務配置,服務管理等問題。
- Nacos 是 Spring Cloud Alibaba 核心元件之一,負責服務注冊與發現,還有配置
- Zookeeper是一個分布式服務架構,是Apache Hadoop 的一個子項目,它主要是用來解決分布式應用中經常遇到的一些資料管理問題,如:統一命名服務、狀态同步服務、叢集管理、分布式應用配置項的管理等。
元件名 | 語言 | CAP | 對外暴露接口 |
Eureka | Java | AP(自我保護機制,保證可用) | HTTP |
Consul | Go | CP | HTTP/DNS |
Zookeeper | 用戶端 | ||
Nacos | 支援AP/CP切換 |
- CAP定理又稱CAP原則,指的是在一個分布式系統中,Consistency(一緻性)、 Availability(可用性)、Partition tolerance(分區容錯性),最多隻能同時三個特性中的兩個,三者不可兼得。
- P:分區容錯性:分布式系統在遇到某節點或網絡分區故障的時候,仍然能夠對外提供滿足一緻性或可用性的服務(一定的要滿足的)
- C:資料一緻性:all nodes see the same data at the same time
- A:高可用:Reads and writes always succeed
- Eureka架構
- Eureka互動流程
Eureka 包含兩個元件:Eureka Server 和 Eureka Client,Eureka Client是一個Java用戶端,用于簡化與Eureka Server的互動;Eureka Server提供服務發現的能力,各個微服務啟動時,會通過Eureka Client向Eureka Server 進行注冊自己的資訊(例如網絡資訊),Eureka Server會存儲該服務的資訊;
-
- 圖中us-east-1c、us-east-1d,us-east-1e代表不同的區也就是不同的機房
- 圖中每一個Eureka Server都是一個叢集
-
圖中Application Service作為服務提供者向Eureka Server中注冊服務,Eureka Server接受到注冊事件會在叢集和分區中進行資料同步,Application Client作為消費端(服務消費者)可以從Eureka
Server中擷取到服務注冊資訊,進行服務調用
- 微服務啟動後,會周期性地向Eureka Server發送心跳(預設周期為30秒,預設Eureka Server90S會将還沒有續約的給剔除)以續約自己的資訊
- Eureka Server在一定時間内沒有接收到某個微服務節點的心跳,Eureka Server将會登出該微服務節點(預設90秒)
- 每個Eureka Server同時也是Eureka Client,多個Eureka Server之間通過複制的方式完成服務注冊清單的同步
- Eureka Client會緩存Eureka Server中的資訊。即使所有的Eureka Server節點都宕掉,服務消費者依然可以使用緩存中的資訊找到服務提供者(自我保護機制)
- Eureka通過心跳檢測、健康檢查和用戶端緩存等機制,提高系統的靈活性、可伸縮性和高可用性。
- 搭建Eureka Server服務注冊中心
- 搭建Eureka Server服務 lagou-cloud-eureka ,lagou-parent中引入Spring Cloud 依賴
<!--lagou-parent中引入Spring Cloud 依賴--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
- lagou-cloud-eureka工程pom.xml中引入依賴
<!--在父工程的pom檔案中手動引入jaxb的jar,因為Jdk9之後預設沒有加載該子產品,Eureka Server使用到,是以需要手動導入,否則EurekaServer服務無法啟動--> <!--引入Jaxb,開始--> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.2.11</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.2.11</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.2.10-b140310.1920</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency>
<dependencies> <!--Eureka server依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies>
- 在yml檔案中配置Eureka server服務端口,服務名等資訊
#Eureka server服務端口 server: port: 9200 spring: application: name: lagou-cloud-eureka-server # 應用名稱,會在Eureka中作為服務的id辨別 (serviceId) eureka: instance: hostname: localhost client: service-url: # 用戶端與EurekaServer互動的位址,如果是叢集,也需要寫其它Server的位址 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ register-with-eureka: false # 自己就是服務不需要注冊自己,預設為true fetch-registry: false #自己就是服務不需要從Eureka Server擷取服務資訊,預設為true,置為false
- 編寫啟動類,聲明目前服務為Eureka注冊中心
package com.rf; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer //服務注冊中心,是一個EurekaServer public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class,args); } }
- 通路http://127.0.0.1:9200,如果看到如下頁面(Eureka注冊中心背景),則表明EurekaServer釋出成功
- 商品微服務和頁面靜态化微服務注冊到Eureka
- pom檔案中添加Eureka Client依賴
<!--Eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
-
-
- yml配置Eureka服務端資訊
-
server:
port: 9100 # 後期該微服務多執行個體,端口從9100遞增(10個以内)
spring:
application:
name: lagou-service-page
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/smd?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
eureka:
instance:
#使用ip注冊,否則會使用主機名注冊了(此處考慮到對老版本的相容,新版本經過實驗都是ip)
prefer-ip-address: true
#自定義執行個體顯示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${sping.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
client:
service-url: # eureka server的路徑
defaultZone: http://localhost:9200/eureka/
fetch-registry: true
register-with-eureka: true
-
-
- 修改啟動類
-
package com.rf;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@MapperScan("com.rf.mapper")
@EnableDiscoveryClient
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class,args);
}
}
- 搭建Eureka Server 高可用叢集
在生産環境中,我們會配置Eureka Server叢集實作高可用。Eureka Server叢集之中的節點通過點對點(P2P)通信的方式共享服務系統資料庫。
我們開啟兩台 Eureka Server 以搭建叢集,Eureka配置server叢集時需要執行host位址,需要修改個人電腦中host位址:
127.0.0.1 LagouCloudEurekaServerA
127.0.0.1 LagouCloudEurekaServerB
将lagou-cloud-eureka複制一份為lagou-cloud-eureka9201,修改 lagou-cloud-eureka-server 工程中的yml配置檔案
#Eureka server服務端口
server:
port: 9200
spring:
application:
name: lagou-cloud-eureka-server # 應用名稱,會在Eureka中作為服務的id辨別 (serviceId)
eureka:
instance:
hostname: LagouCloudEurekaServerA
client:
service-url: # 用戶端與EurekaServer互動的位址,如果是叢集,也需要寫其它Server的位址
defaultZone: http://LagouCloudEurekaServerB/9201/eureka/
fetch-registry: false #自己就是服務不需要從Eureka Server擷取服務資訊,預設為true,置為false
register-with-eureka: false # 自己就是服務不需要注冊自己,預設為true
9201
#Eureka server服務端口
server:
port: 9201
spring:
application:
name: lagou-cloud-eureka-server # 應用名稱,會在Eureka中作為服務的id辨別
(serviceId)
eureka:
instance:
hostname: LagouCloudEurekaServerB
client:
register-with-eureka: true
fetch-registry: true
serviceUrl:
defaultZone: http://LagouCloudEurekaServerA:9200/eureka
商品微服務和頁面靜态微服務
server:
port: 9100 # 後期該微服務多執行個體,端口從9100遞增(10個以内)
spring:
application:
name: lagou-service-page
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/smd?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
eureka:
instance:
#使用ip注冊,否則會使用主機名注冊了(此處考慮到對老版本的相容,新版本經過實驗都是ip)
prefer-ip-address: true
#自定義執行個體顯示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
client:
service-url: # eureka server的路徑
#把 eureka 叢集中的所有 url 都填寫了進來,也可以隻寫一台,因為各個 eureka server 可以同步系統資料庫
defaultZone: http://LagouCloudEurekaServerA:9200/eureka/,http://LagouCloudEurekaServerB:9201/eureka/
fetch-registry: true
register-with-eureka: true
改造頁面靜态化微服務:之前是直接通過RestTemplate寫死URL進行調用,現在通過Eureka方式進行調用
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
//@EnableEurekaClient 說明是EurekaClient,不能支援其他的服務注冊中心
@EnableDiscoveryClient //說明是服務注冊中心的用戶端,可以支援不同的服務注冊中心
public class PageApplication {
@Bean //封裝httpClient對象,執行HTTP請求
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(PageApplication.class,args);
}
}
- Eureka中繼資料有兩種:标準中繼資料和自定義中繼資料。
- 标準中繼資料:主機名、IP位址、端口号等資訊,這些資訊都會被釋出在服務系統資料庫中,用于服務之間的調用。
-
- 自定義中繼資料:可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存儲格式。這些中繼資料可以在遠端用戶端中通路。
package com.rf.controller;
import entity.Products;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RestController
@RequestMapping("/page")
public class PageController {
@Autowired //封裝httpClient對象,執行HTTP請求
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/getProduct/{id}")
public Products getProduct(@PathVariable Integer id){
List<ServiceInstance> instances = discoveryClient.getInstances("lagou-service-product"); //叢集中服務會有多個
ServiceInstance serviceInstance = instances.get(0);
String host = serviceInstance.getHost();
int port = serviceInstance.getPort();
String url="http://"+host+":"+port+"/product/query/";
// String url ="http://localhost:9000/product/query/"; //URL位址寫死
Products products = restTemplate.getForObject(url + id, Products.class);
Map<String, String> metadata = serviceInstance.getMetadata();
Set<Map.Entry<String, String>> entries = metadata.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
return products;
}
}
server:
port: 9000 # 後期該微服務多執行個體,9000(10個以内)
spring:
application:
name: lagou-service-product
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/smd?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
eureka:
instance:
#使用ip注冊,否則會使用主機名注冊了(此處考慮到對老版本的相容,新版本經過實驗都是ip)
prefer-ip-address: true
#自定義執行個體顯示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
metadata-map:
ip: 192.168.200.128
port: 10000
user: rf
pwd: 123456
client:
service-url: # eureka server的路徑
#把 eureka 叢集中的所有 url 都填寫了進來,也可以隻寫一台,因為各個 eureka server 可以同步系統資料庫
defaultZone: http://LagouCloudEurekaServerA:9200/eureka/,http://LagouCloudEurekaServerB:9201/eureka/
fetch-registry: true
register-with-eureka: true
2.2 Ribbon負載均衡
- 負載均衡一般分為服務端負載均衡和用戶端負載均衡
- 服務端負載均衡:比如Nginx、F5這些,請求到達伺服器之後由這些負載均衡器根據一定的算法将請求路由到目标伺服器處理
- 用戶端負載均衡:比如Ribbon,服務消費者用戶端會有一個伺服器位址清單,調用方在請求前通過一定的負載均衡算法選擇一個伺服器進行通路,負載均衡算法的執行是在請求用戶端進行
- Ribbon:Netflix釋出的負載均衡器,Eureka一般配合Ribbon進行使用,Ribbon利用從Eureka中讀取到服務資訊,在調用服務提供者提供的服務時,會根據一定的算法進行負載
- 複制商品微服務9001,在9000和9001編寫Controller,傳回服務執行個體端口,Page微服務中通過負載均衡政策調用lagou-service-product的controller
- 在微服務中使用Ribbon不需要額外導入依賴坐标,微服務中引入過eureka-client相關依賴,會自動引入Ribbon相關依賴坐标,可以直接在RestTemplate上添加對應注解
package com; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication //@EnableEurekaClient 說明是EurekaClient,不能支援其他的服務注冊中心 @EnableDiscoveryClient //說明是服務注冊中心的用戶端,可以支援不同的服務注冊中心 public class PageApplication { @Bean //封裝httpClient對象,執行HTTP請求 @LoadBalanced //開啟Ribbon負載均衡 public RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(PageApplication.class,args); } }