SpringCloud之入門到跑路系列(一) - 服務註冊、負載均衡

  • 2019 年 10 月 8 日
  • 筆記

「 技不壓身,come on gays。」 —— 23號老闆

近年來,所謂「容器化」、「微服務」的關鍵字眼在互聯網的業內出現頻次頗多,也漸漸成為了企業級開發項目的必備良藥。所以,作為一位後端程式猿,就不得不跟緊潮流,著手了解,進行開發和深入(主要是為了不被拉入「裁員」大軍

)。因此,我們有必要對上述的開發技術有所了解。今天,我們就以一個簡單的Demo對Springcloud中的服務註冊、負載均衡做一個介紹。

一、概念引入

什麼是微服務?

首先微服務並沒有一個官方的定義,想要直接描述微服務比較困難。從對比傳統的Web應用對比來看,微服務的架構手段可以簡單理解為將傳統的大型Web項目根據不同的業務或者模組劃分,拆分成多個小型應用,進而通過拆分後的多個子系統共同組建運行,組合成為一個大的整體系統對外提供服務。微服務概念產生之後,解決的一大首要難題,就是服務與服務之間的解耦。

多子系統組成的租車應用

選用SpringCloud的原因

開箱即用,提供各種默認配置來簡化項目配置。對於中小企業來講,使用門檻較低。Spring Cloud來源於Spring,向上兼容,有其高效開發、穩定運行的保證。其次,SpringCloud社區非常活躍,從16年1.x版本到如今的2.x系列,相比於其它框架,Spring Cloud對微服務周邊環境的支援力度最大。因此,綜合考慮時間和成本而言,大部分企業和公司,樂於接收這樣的開源腳手架進行開發。

Spring Cloud 組織架構圖

Spring Cloud Eureka

Eureka是Spring Cloud Netflix項目下的服務治理模組。而Spring Cloud Netflix項目是Spring Cloud的子項目之一,主要內容是對Netflix公司一系列開源產品的包裝,它為Spring Boot應用提供了自配置的Netflix OSS整合。通過一些簡單的註解,開發者就可以快速的在應用中配置一下常用模組並構建龐大的分散式系統。

Eureka的功能特點:實現服務治理

二、開始擼程式碼

0.項目結構

1、新建Maven項目,編寫父級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>com.bboyhan.cloud</groupId>      <artifactId>spring-cloud-project</artifactId>      <version>1.0-SNAPSHOT</version>      <packaging>pom</packaging>      <modules>          <module>provider-user</module>          <module>discovery-eureka</module>          <module>consumer-user</module>      </modules>      <properties>          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>          <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>          <java.version>1.8</java.version>      </properties>      <parent>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-parent</artifactId>          <version>1.4.2.RELEASE</version>      </parent>      <dependencyManagement>          <dependencies>              <dependency>                  <groupId>org.springframework.cloud</groupId>                  <artifactId>spring-cloud-dependencies</artifactId>                  <version>Camden.SR3</version>                  <type>pom</type>                  <scope>import</scope>              </dependency>          </dependencies>      </dependencyManagement>      <build>          <plugins>              <plugin>                  <groupId>org.springframework.boot</groupId>                  <artifactId>spring-boot-maven-plugin</artifactId>              </plugin>          </plugins>      </build>  </project>

2、編寫MVC服務

這裡使用的是Jpa、H2,方便直接讀取,小夥伴可以根據自己的項目情況進行簡單更改。

(1)table.sql,自行插入table數據。

drop table user_login if exists;  CREATE TABLE `user_login` (     `id` bigint(20) NOT NULL AUTO_INCREMENT,    `login` varchar(100) NOT NULL,    `pwd` varchar(100) NOT NULL,    `reg_date` datetime DEFAULT NULL,    `date` timestamp NULL DEFAULT NULL,    PRIMARY KEY (`id`),    UNIQUE KEY `login_UNIQUE` (`login`)  ) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;

(2)provider-user模組的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">      <parent>          <artifactId>spring-cloud-project</artifactId>          <groupId>com.bboyhan.cloud</groupId>          <version>1.0-SNAPSHOT</version>      </parent>      <modelVersion>4.0.0</modelVersion>      <artifactId>provider-user</artifactId>      <properties>          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>          <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>          <java.version>1.8</java.version>      </properties>      <dependencies>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-data-jpa</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-web</artifactId>          </dependency>          <dependency>              <groupId>com.h2database</groupId>              <artifactId>h2</artifactId>              <scope>runtime</scope>          </dependency>          <dependency>              <groupId>org.springframework.cloud</groupId>              <artifactId>spring-cloud-starter-eureka</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-actuator</artifactId>          </dependency>      </dependencies>  </project>

(3)controller,提供一個查詢user的介面(/user/{id}),其它程式碼略。

package com.bboyhan.cloud.controller;  import com.bboyhan.cloud.entity.UserLogin;  import com.bboyhan.cloud.service.UserService;  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.RestController;  /**   * @Author: bboyHan   * @Date: 2019/1/13 17:53   */  @RestController  public class UserController {        @Autowired     private UserService userService;     @GetMapping("/user/{id}")      public UserLogin getUser(@PathVariable Long id) {          return userService.getUser(id);      }  }

(4)provider-user模組的application.yml。

server:    port: 7901  spring:    jpa:      generate-ddl: false        show-sql: true     hibernate:        ddl-auto: none    datasource:     platform: h2      schema: classpath:schema.sql      data: classpath:data.sql    application:     name: provider-user  logging:    level:      root: INFO        org.hibernate: INFO        org.hibernate.type.descriptor.sql.BasicBinder: TRACE        org.hibernate.type.descriptor.sql.BasicExtractor: TRACE        com.itmuch: DEBUG  eureka:    client:      healthcheck:        enabled: true      serviceUrl:        defaultZone: http://root:root@localhost:8761/eureka    instance:     prefer-ip-address: true      instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}

3、編寫eureka服務

(1)discovery-eureka模組的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">      <parent>          <artifactId>spring-cloud-project</artifactId>          <groupId>com.bboyhan.cloud</groupId>          <version>1.0-SNAPSHOT</version>      </parent>      <modelVersion>4.0.0</modelVersion>      <artifactId>discovery-eureka</artifactId>      <packaging>jar</packaging>      <properties>          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>      </properties>      <dependencies>          <dependency>              <groupId>org.springframework.cloud</groupId>              <artifactId>spring-cloud-starter-eureka-server</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-security</artifactId>          </dependency>      </dependencies>  </project>

(2)discovery-eureka中的application.yml

在這裡添加了賬戶許可權(security,對應的依賴是spring-boot-starter-security),用於登陸eureka限制使用(可以不用添加)。

security:    basic:      enabled: true     user:       name: root        password: root  server:    port: 8761  eureka:    client:      register-with-eureka: false        fetch-registry: false        service-url:        defaultZone: http://root:root@localhost:8761/eureka

(3)啟動類增加註解@EnableEurekaServer

4、消費調用方

(1)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">      <parent>          <artifactId>spring-cloud-project</artifactId>          <groupId>com.bboyhan.cloud</groupId>          <version>1.0-SNAPSHOT</version>      </parent>      <modelVersion>4.0.0</modelVersion>      <artifactId>consumer-user</artifactId>      <packaging>jar</packaging>      <properties>          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>          <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>          <java.version>1.8</java.version>      </properties>      <dependencies>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-web</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.cloud</groupId>              <artifactId>spring-cloud-starter-eureka</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-actuator</artifactId>          </dependency>      </dependencies>  </project>

(2)application.yml

spring:    application:      name: consumer-user  server:    port: 8010  eureka:    client:      healthcheck:         enabled: true     serviceUrl:      defaultZone: http://root:root@localhost:8761/eureka     instance:      prefer-ip-address: true    provider-user:    ribbon:      NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

(3)controller

package com.bboyhan.cloud.controller;  import com.bboyhan.cloud.entity.User;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.cloud.client.ServiceInstance;  import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;  import org.springframework.web.bind.annotation.GetMapping;  import org.springframework.web.bind.annotation.PathVariable;  import org.springframework.web.bind.annotation.RestController;  import org.springframework.web.client.RestTemplate;  /**   * @Author: bboyHan   * @Date: 2019/1/13 18:12   */  @RestController  public class UserController {        @Autowired     private RestTemplate restTemplate;     @Autowired     private LoadBalancerClient loadBalancerClient;     @GetMapping("/rest/{id}")      public User r(@PathVariable Long id) {         ServiceInstance serviceInstance = loadBalancerClient.choose("provider-user");       System.out.println("loadbalance" + " : " + serviceInstance.getServiceId() + ":" + serviceInstance.getHost() + ":" + serviceInstance.getPort());       return restTemplate.getForObject("http://provider-user/user/" + id, User.class);     }    }

(4)User實例

package com.bboyhan.cloud.entity;  import java.util.Date;  /**   * @Author: bboyHan   * @Date: 2019/1/13 18:12   */  public class User {      private Long id;     private String login;     private String pwd;     private Date regDate;     private Date date;     //setter、getter方法  }

在消費調用方中使用了Ribbon的負載均衡,對此做一個簡單的說明。

相信大家會了解一些負載均衡的硬體或者軟體,比如F5(硬體負載均衡)、Nginx(輕量級的服務端負載均衡)。而Ribbon是一個客戶端的負載均衡器,它提供對大量的HTTP和TCP客戶端的訪問控制。

Ribbon的核心組件

1、ServerList: 用於獲取地址列表。它既可以是靜態的(提供一組固定的地址),也可以是動態的(從註冊中心中定期查詢地址列表)。

2、ServerListFilter: 僅當使用動態ServerList時使用,用於在原始的服務列表中使用一定策略過慮掉一部分地址。

3、IRule: 選擇一個最終的服務地址作為LoadBalance的結果。選擇策略有輪詢、根據響應時間加權、斷路器(當Hystrix可用時)等。

4、Ribbon在工作時首選會通過ServerList來獲取所有可用的服務列表,然後通過ServerListFilter過慮掉一部分地址,最後在剩下的地址中通過IRule選擇出一台伺服器作為最終結果。

Ribbon提供的負載均衡策略

1、簡單輪詢負載均衡(RoundRobin):以輪詢的方式依次將請求調度不同的伺服器,即每次調度執行i = (i + 1) mod n,並選出第i台伺服器。

2、隨機負載均衡 (Random): 隨機選擇狀態為UP的Server

3、加權響應時間負載均衡 (WeightedResponseTime):根據相應時間分配一個weight,相應時間越長,weight越小,被選中的可能性越低。

4、區域感知輪詢負載均衡(ZoneAvoidanceRule):複合判斷server所在區域的性能和server的可用性選擇server。Ribbon自帶負載均衡策略比較。

三、測試結果

1、在瀏覽器輸入:http://localhost:8761;

2、測試provider-user服務:http://localhost:7900/user/1;

3、測試consumer-user服務:http://localhost:8010/rest/1;

Tags: