3.1 服務治理
先來思考一個問題,通過上一章的操作,我們已經實現微服務之間的呼叫,但是我們把服務提供者的網路地址(ip,埠)等硬編碼到了程式碼中,這種做法存在許多問題:
- 一旦服務提供者地址變化,就需要手工修改程式碼
- 一旦是多個服務提供者,無法實現負載均衡功能
- 一旦服務變得越來越多,人工維護呼叫關係困難
那麼應該怎麼解決問題呢,這時候和就需要通過註冊中心動態的實現服務治理
什麼是服務治理:
服務治理是為微服務架構中最核心的基本模組,用於實現各個微服務的自動化註冊與發現
- 服務註冊:在服務治理框架中,都會構建一個註冊中心,每個服務單元向註冊中心登記自己提供服務的詳細資訊,並在註冊中心形成一張服務的清單,服務註冊中心需要以心跳的方式去監測清單中的服務是否可用,如果不可用,需要在服務清單中剔除不可用的服務
- 服務發現:服務呼叫方向服務註冊中心諮詢服務,並獲取所有服務的例項清單,實現對具體服務例項的訪問
通過上面的呼叫圖會發現,除了微服務,還有一個元件是服務註冊中心,它是微服務架構中非常重要的一個元件,在微服務架構裡主要起到協調者的一個作用,註冊中心一般包含如下功能:
-
服務發現
- 服務註冊:儲存服務提供者和服務呼叫者的資訊
- 服務訂閱:服務呼叫者訂閱服務提供者的資訊,註冊中心向訂閱者推送提供者的資訊
-
服務配置
- 配置訂閱:服務提供者和服務呼叫者訂閱微服務相關的配置
- 配置下發:主動將配置推送給服務提供者和服務呼叫者
-
服務健康監測
- 監測服務提供者的健康情況,如果發現異常,執行服務剔除
常見的註冊中心
-
Zookeeper
Zookeeper十一個分散式協調服務框架,是Apache Hadoop 的一個子專案,它主要是用來解決分散式應用中經常遇到的一些資料管理問題,如:統一命名服務、狀態同步服務、叢集管理、分散式應用配置項的管理等
-
Eureka
Erueka是SpringCloud Netflix的重要元件,主要作用就是做服務註冊與發現。但是現在已經閉源
-
Consul
Consul是基於GO語言開發的開源工具,主要面向分散式,服務話的系統提供服務註冊、服務發現和配置管理的功能。Consul的功能都很實用,其中包括:服務註冊與發現、健康檢查、Key/Value儲存、多資料中心和分散式一致性保證等特徵。Consul本身只是一個二進位制的可執行檔案,所以安裝和部署都非常簡單,只需要從官網下載後,執行對應的啟動指令碼即可
-
Nacos
Nacos是一個更易於構建雲原生應用的動態服務發現、配置管理和服務管理平臺。它是SpringCloud Alibaba 元件之一,負責服務註冊發現和服務配置,可以這樣認為 Naocs=Eureka+Config
3.2 Nacos簡介
Nacos致力於幫助您發現、配置和管理微服務。Nacos提供了一組簡單易用的特性集,幫助您快速實現動態服務發現、服務配置、服務後設資料及流量管理
從上面的介紹就可以看出,nacos的作用就是就是一個註冊中心,用來管理註冊上來的各個微服務
3.3 Nacos實戰入門
接下來,我們就在現有的環境中加入nacos,並將我們的兩個服務註冊上去。
3.3.1 搭建nacos環境
第一步:安裝nacos
下載地址:https://github.com/alibaba/nacos/releases
下載zip格式的安裝包,然後進行解壓縮操作
第二步:啟動nacos
# 切換目錄
cd nacos/bin
# 命令啟動
startup.cmd -m standalone
第三步:訪問nacos
瀏覽器輸入:http://localhost:8848/nacos,即可訪問服務,預設賬號密碼是nacos/nacos
3.3.2 將商品微服務註冊到nacos
接下來開始修改shop-product模組的程式碼,將其註冊到nacos服務上
-
在pom.xml中新增nacos依賴
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>demo</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web-services</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.79</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <!--整合Spring Cloud--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR3</version> <type>pom</type> <scope>import</scope> </dependency> <!--整合Spring Cloud Alibaba--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
-
新增配置
spring.application.name=shop-product server.port=8090 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
-
在主類上新增@EnableDiscoveryClient
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @author chao.z */ @SpringBootApplication @EnableDiscoveryClient public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
-
啟動服務,觀察nacos的控制皮膚中是否有註冊上來的商品微服務
3.3.3 將訂單微服務註冊到nacos上
3.4 實現服務呼叫的負載均衡
3.4.1什麼是負載均衡
通俗的講,負載均衡就是將負載(工作任務,訪問請求)進行分攤到多個操作單元(伺服器,元件)上進行執行
根據負載均衡發生位置的不同,一版分為服務端負載均衡和客戶端負載均衡
服務端負載均衡指的時發生在服務提供者一方,比如常見的nginx負載均衡
而客戶端負載均衡指的是發生在服務請求的一方,也就是在傳送請求之前已經選好了由哪個例項處理請求
我們在微服務呼叫關係中,一版會選擇客戶端負載均衡,也就是在服務呼叫的乙方決定服務由哪個提供者執行
3.4.2 自定義實現負載均衡
- 通過idea 在啟動一個shop-product微服務,設定其埠為8082
- 通過nacos檢視微服務的啟動情況
-
修改shop-order的程式碼
@RestController @Slfj public class OrderController{ @Autowired private RestTemplate restTemplate; @Autowired private OrderService orderService; @Autowired private DiscoveryClient discoveryClient; @GetMapping("order/prod/{pid}") public Order order(@Pathvariable("pid") Integer pid){ List<ServiceInstance> instances = discoveryClient.getInstances("service-product"); int index = new Random().nextInt(instances.size()); ServiceInstance serviceInstance = instances.get(index); String url = serviceInstance.getHost() + ":" + serviceInstance.getPort(); Product product = restTemplate.getForObject("http://"+ url + "/product/" + pid ,Product.class); Order order = new Order(); order.setUid(1); order.setUsername("測試使用者"); order.setPid(product.getPname()); order.setPprice(product.getPprice()); order.setNum(1); orderService.save(order); return order; } }
-
啟動兩個服務提供者和一個服務消費者,多訪問幾次消費者測試效果
3.4.3 基於Ribbon實現負載均衡
Ribbon 是SpringCloud的一個元件,它可以讓我們使用一個註解就能輕鬆的搞定負載均衡
第一步: 在RestTemplate 剩生成方法上新增@LoadBalanced註解
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
第二步:修改服務呼叫的方法
@RestController
@Slfj
public class OrderController{
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderService orderService;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("order/prod/{pid}")
public Order order(@Pathvariable("pid") Integer pid){
String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
Product product = restTemplate.getForObject("http://"+ url + "/product/" + pid ,Product.class);
Order order = new Order();
order.setUid(1);
order.setUsername("測試使用者");
order.setPid(product.getPname());
order.setPprice(product.getPprice());
order.setNum(1);
orderService.save(order);
return order;
}
}
Ribbon支援的負載均衡策略
Ribbon內建了多種負載均衡策略,內部負載均衡的頂級介面為com.netflix.loadbalancer.IRule,具體的負載策略如下圖所示:
我們通過修改配置類調整Ribbon的負載均衡策略,具體程式碼如下:
service-product: #呼叫的提供者的名稱
ribbon:
NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule
3.5基於Feign實現負載均衡
3.5.1什麼是Feign
Feign是Spring Cloud提供的一個宣告式的偽Http客戶端,它使得呼叫遠端服務就想呼叫本地服務一樣簡單,只需要建立一個藉口並新增一個註解即可
Nacos很好的相容了Feign,Feign 預設整合了Ribbon,所以在Nacos下使用Feign預設就實現了負載均衡的效果
3.5.2Feign的使用
-
接入Feign的依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.3.RELEASE</version> </dependency>
-
在主類上新增@Feign的註解
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients // 開啟Fegin public class OrderApplication{}
-
建立一個service,並使用Fegin實現微服務呼叫
//宣告呼叫的提供者的name @FeignClient("service-product") public insterface ProductService{ // 指定呼叫提供者的哪個方法 //@FeignClient+A@GetMapping 就是一個完整的請求路徑 http://service-product/product/{pid} @Getmapping(value = "/product/{pid}") Product findByPid(@PathVariable("pid") Integer pid); }
-
修改controller程式碼,並啟動驗證
@RestController @Sl4j public class OrderController{ @Autowired private OrderService orderService; @Autowired private ProductService productService; @GetMapping("order/prod/{pid}") public Order order(@Pathvariable("pid") Integer pid){ Product product = productService.findByPid(pid); Order order = new Order(); order.setUid(1); order.setUsername("測試使用者"); order.setPid(product.getPname()); order.setPprice(product.getPprice()); order.setNum(1); orderService.save(order); return order; } }
-
重啟Order 微服務,檢視效果