隨着互聯網的發展,網站應用的規模不斷擴大。需求的激增,帶來的是技術上的壓力。系統架構也所以不斷的演進、升級、迭代。從單一應用,到垂直拆分,到分佈式服務,到SOA,以及如今火熱的微服務架構,還有在Google帶領下來勢洶涌的Service Mesh。咱們究竟是該乘坐微服務的船隻駛向遠方,仍是偏安一隅得過且過?php
其實生活不止眼前的苟且,還有詩和遠方。因此咱們今天就回顧歷史,看一看系統架構演變的歷程;把握如今,學習如今最火的技術架構;展望將來,爭取成爲一名優秀的Java工程師。前端
當網站流量很小時,只需一個應用,將全部功能都部署在一塊兒,以減小部署節點和成本。此時,用於簡化增刪改查工做量的數據訪問框架(ORM)是影響項目開發的關鍵。java
存在的問題:mysql
當訪問量逐漸增大,單一應用沒法知足需求,此時爲了應對更高的併發和業務需求,咱們根據業務功能對系統進行拆分:程序員
優勢:web
缺點:算法
當垂直應用愈來愈多,應用之間交互不可避免,將核心業務抽取出來,做爲獨立的服務,逐漸造成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。此時,用於提升業務複用及整合的分佈式調用是關鍵。spring
優勢:sql
缺點:數據庫
SOA :面向服務的架構
當服務愈來愈多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增長一個調度中心基於訪問壓力實時管理集羣容量,提升集羣利用率。此時,用於提升機器利用率的資源調度和治理中心(SOA)是關鍵
之前出現了什麼問題?
服務治理要作什麼?
缺點:
前面說的SOA,英文翻譯過來是面向服務。微服務,彷佛也是服務,都是對系統進行拆分。所以二者很是容易混淆,但其實卻有一些差異:
微服務的特色:
微服務結構圖:
不管是微服務仍是SOA,都面臨着服務間的遠程調用。那麼服務間的遠程調用方式有哪些呢?
常見的遠程調用方式有如下2種:
RPC:Remote Produce Call遠程過程調用,相似的還有RMI。自定義數據格式,基於原生TCP通訊,速度快,效率高。早期的webservice,如今熱門的dubbo,都是RPC的典型表明
Http:http實際上是一種網絡傳輸協議,基於TCP,規定了數據傳輸的格式。如今客戶端瀏覽器與服務端通訊基本都是採用Http協議,也能夠用來進行遠程服務調用。缺點是消息封裝臃腫,優點是對服務的提供和調用方沒有任何技術限定,自由靈活,更符合微服務理念。
如今熱門的Rest風格,就能夠經過http協議來實現。
若是大家公司所有采用Java技術棧,那麼使用Dubbo做爲微服務架構是一個不錯的選擇。
相反,若是公司的技術棧多樣化,並且你更青睞Spring家族,那麼SpringCloud搭建微服務是不二之選。在咱們的項目中,咱們會選擇SpringCloud套件,所以咱們會使用Http方式來實現服務間調用。
既然微服務選擇了Http,那麼咱們就須要考慮本身來實現對請求和響應的處理。不過開源世界已經有不少的http客戶端工具,可以幫助咱們作這些事情,例如:
接下來,不過這些不一樣的客戶端,API各不相同
Spring提供了一個RestTemplate模板工具類,對基於Http的客戶端進行了封裝,而且實現了對象與json的序列化和反序列化,很是方便。RestTemplate並無限定Http的客戶端類型,而是進行了抽象,目前經常使用的3種都有支持:
首先在項目中註冊一個RestTemplate
對象,能夠在啓動類位置註冊:
@SpringBootApplication
public class HttpDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HttpDemoApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
複製代碼
在測試類中直接@Autowired
注入:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpDemoApplication.class)
public class HttpDemoApplicationTests {
@Autowired
private RestTemplate restTemplate;
@Test
public void httpGet() {
// 調用springboot案例中的rest接口
User user = this.restTemplate.getForObject("http://localhost/user/1", User.class);
System.out.println(user);
}
}
複製代碼
學習完了Http客戶端工具,接下來就能夠正式學習微服務了。
微服務是一種架構方式,最終確定須要技術架構去實施。
微服務的實現方式不少,可是最火的莫過於Spring Cloud了。爲何?
SpringCloud是Spring旗下的項目之一,官網地址:http://projects.spring.io/spring-cloud/
Spring最擅長的就是集成,把世界上最好的框架拿過來,集成到本身的項目中。
SpringCloud也是同樣,它將如今很是流行的一些技術整合到一塊兒,實現了諸如:配置管理,服務發現,智能路由,負載均衡,熔斷器,控制總線,集羣狀態等等功能。其主要涉及的組件包括:
架構圖:
以上只是其中一部分。
由於Spring Cloud不一樣其餘獨立項目,它擁有不少子項目的大項目。因此它的版本是版本名+版本號 (如Angel.SR6)。
版本名:是倫敦的地鐵名
版本號:SR(Service Releases)是固定的 ,大概意思是穩定版本。後面會有一個遞增的數字。
因此 Edgware.SR3就是Edgware的第3個Release版本。
咱們在項目中,會是以Finchley的版本。
其中包含的組件,也都有各自的版本,以下表:
Component | Edgware.SR3 | Finchley.RC1 | Finchley.BUILD-SNAPSHOT |
---|---|---|---|
spring-cloud-aws | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-bus | 1.3.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-cli | 1.4.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-commons | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-contract | 1.2.4.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-config | 1.4.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-netflix | 1.4.4.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-security | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-cloudfoundry | 1.1.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-consul | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-sleuth | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-stream | Ditmars.SR3 | Elmhurst.RELEASE | Elmhurst.BUILD-SNAPSHOT |
spring-cloud-zookeeper | 1.2.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-boot | 1.5.10.RELEASE | 2.0.1.RELEASE | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-task | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.RELEASE |
spring-cloud-vault | 1.1.0.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-gateway | 1.0.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-openfeign | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
接下來,咱們就一一學習SpringCloud中的重要組件。
首先,咱們須要模擬一個服務調用的場景,搭建兩個工程:itcast-service-provider(服務提供方)和itcast-service-consumer(服務調用方)。方便後面學習微服務架構
服務提供方:使用mybatis操做數據庫,實現對數據的增刪改查;並對外提供rest接口服務。
服務消費方:使用restTemplate遠程調用服務提供方的rest接口服務,獲取數據。
咱們新建一個項目:itcast-service-provider,對外提供根據id查詢用戶的服務。
藉助於Spring提供的快速搭建工具:
next-->填寫項目信息:
next --> 添加web依賴:
添加mybatis依賴:
Next --> 填寫項目位置:
生成的項目結構,已經包含了引導類(itcastServiceProviderApplication):
依賴也已經所有自動引入:
<?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>cn.itcast.service</groupId>
<artifactId>itcast-service-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>itcast-service-provider</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<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-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 須要手動引入通用mapper的啓動器,spring沒有收錄該依賴 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
複製代碼
固然,由於要使用通用mapper,因此咱們須要手動加一條依賴:
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
複製代碼
很是快捷啊!
屬性文件,這裏咱們採用了yaml語法,而不是properties:
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis #你學習mybatis時,使用的數據庫地址
username: root
password: root
mybatis:
type-aliases-package: cn.itcast.service.pojo
複製代碼
@Table(name = "tb_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 用戶名
private String userName;
// 密碼
private String password;
// 姓名
private String name;
// 年齡
private Integer age;
// 性別,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 建立時間
private Date created;
// 更新時間
private Date updated;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
}
複製代碼
@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User>{
}
複製代碼
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) {
return this.userMapper.selectByPrimaryKey(id);
}
}
複製代碼
添加一個對外查詢的接口:
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("{id}")
public User queryById(@PathVariable("id") Long id) {
return this.userService.queryById(id);
}
}
複製代碼
啓動項目,訪問接口:http://localhost:8081/user/1
搭建itcast-service-consumer服務消費方工程。
與上面相似,這裏再也不贅述,須要注意的是,咱們調用itcast-service-provider的解耦獲取數據,所以不須要mybatis相關依賴了。
pom:
<?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>cn.itcast.service</groupId>
<artifactId>itcast-service-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>itcast-service-consumer</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<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.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>
</project>
複製代碼
首先在引導類中註冊RestTemplate
:
@SpringBootApplication
public class ItcastServiceConsumerApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ItcastServiceConsumerApplication.class, args);
}
}
複製代碼
編寫配置(application.yml):
server:
port: 80
複製代碼
編寫UserController:
@Controller
@RequestMapping("consumer/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping
@ResponseBody
public User queryUserById(@RequestParam("id") Long id){
User user = this.restTemplate.getForObject("http://localhost:8081/user/" + id, User.class);
return user;
}
}
複製代碼
pojo對象(User):
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
// 用戶名
private String userName;
// 密碼
private String password;
// 姓名
private String name;
// 年齡
private Integer age;
// 性別,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 建立時間
private Date created;
// 更新時間
private Date updated;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
}
複製代碼
由於咱們沒有配置端口,那麼默認就是8080,咱們訪問:http://localhost/consumer/user?id=1
一個簡單的遠程服務調用案例就實現了。
簡單回顧一下,剛纔咱們寫了什麼:
存在什麼問題?
其實上面說的問題,歸納一下就是分佈式服務必然要面臨的問題:
以上的問題,咱們都將在SpringCloud中獲得答案。
首先咱們來解決第一問題,服務的管理。
問題分析
在剛纔的案例中,itcast-service-provider對外提供服務,須要對外暴露本身的地址。而consumer(調用者)須要記錄服務提供者的地址。未來地址出現變動,還須要及時更新。這在服務較少的時候並不以爲有什麼,可是在如今日益複雜的互聯網環境,一個項目確定會拆分出十幾,甚至數十個微服務。此時若是還人爲管理地址,不只開發困難,未來測試、發佈上線都會很是麻煩,這與DevOps的思想是背道而馳的。
網約車
這就比如是 網約車出現之前,人們出門叫車只能叫出租車。一些私家車想作出租卻沒有資格,被稱爲黑車。而不少人想要約車,可是無奈出租車太少,不方便。私家車不少卻不敢攔,並且滿大街的車,誰知道哪一個纔是願意載人的。一個想要,一個願意給,就是缺乏引子,缺少管理啊。
此時滴滴這樣的網約車平臺出現了,全部想載客的私家車所有到滴滴注冊,記錄你的車型(服務類型),身份信息(聯繫方式)。這樣提供服務的私家車,在滴滴那裏都能找到,一目瞭然。
此時要叫車的人,只須要打開APP,輸入你的目的地,選擇車型(服務類型),滴滴自動安排一個符合需求的車到你面前,爲你服務,完美!
Eureka作什麼?
Eureka就比如是滴滴,負責管理、記錄服務提供者的信息。服務調用者無需本身尋找服務,而是把本身的需求告訴Eureka,而後Eureka會把符合你需求的服務告訴你。
同時,服務提供方與Eureka之間經過「心跳」
機制進行監控,當某個服務提供方出現問題,Eureka天然會把它從服務列表中剔除。
這就實現了服務的自動註冊、發現、狀態監控。
基本架構:
接下來咱們建立一個項目,啓動一個EurekaServer:
依然使用spring提供的快速搭建工具:
選擇依賴:EurekaServer-服務註冊中心依賴,Eureka Discovery-服務提供方和服務消費方。由於,對於eureka來講:服務提供方和服務消費方都屬於客戶端
完整的Pom文件:
<?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>cn.itcast.eureka</groupId>
<artifactId>itcast-eureka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>itcast-eureka</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RC2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
複製代碼
編寫application.yml配置:
server:
port: 10086 # 端口
spring:
application:
name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
client:
service-url: # EurekaServer的地址,如今是本身的地址,若是是集羣,須要加上其它Server的地址。
defaultZone: http://127.0.0.1:${server.port}/eureka
複製代碼
修改引導類,在類上添加@EnableEurekaServer註解:
@SpringBootApplication
@EnableEurekaServer // 聲明當前springboot應用是一個eureka服務中心
public class ItcastEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(ItcastEurekaApplication.class, args);
}
}
複製代碼
啓動服務,並訪問:http://127.0.0.1:10086
註冊服務,就是在服務上添加Eureka的客戶端依賴,客戶端代碼會自動把服務註冊到EurekaServer中。
修改itcast-service-provider工程
具體操做
參照itcast-eureka,先添加SpringCloud依賴:
<!-- SpringCloud的依賴 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
複製代碼
而後是Eureka客戶端:
<!-- Eureka客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
複製代碼
完整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>cn.itcast.service</groupId>
<artifactId>itcast-service-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>itcast-service-provider</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<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-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
複製代碼
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/heima
username: root
password: root
driverClassName: com.mysql.jdbc.Driver
application:
name: service-provider # 應用名稱,註冊到eureka後的服務名稱
mybatis:
type-aliases-package: cn.itcast.service.pojo
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka
複製代碼
注意:
在引導類上開啓Eureka客戶端功能
經過添加@EnableDiscoveryClient
來開啓Eureka客戶端功能
@SpringBootApplication
@EnableDiscoveryClient
public class ItcastServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ItcastServiceApplication.class, args);
}
}
複製代碼
重啓項目,訪問Eureka監控頁面查看
咱們發現service-provider服務已經註冊成功了
接下來咱們修改itcast-service-consumer,嘗試從EurekaServer獲取服務。
方法與消費者相似,只須要在項目中添加EurekaClient依賴,就能夠經過服務名稱來獲取信息了!
<?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>cn.itcast.service</groupId>
<artifactId>itcast-service-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>itcast-service-consumer</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<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.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Eureka客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!-- SpringCloud的依賴 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
複製代碼
server:
port: 80
spring:
application:
name: service-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
複製代碼
@SpringBootApplication
@EnableDiscoveryClient // 開啓Eureka客戶端
public class ItcastServiceConsumerApplication {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ItcastServiceConsumerApplication.class, args);
}
}
複製代碼
@Controller
@RequestMapping("consumer/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient; // eureka客戶端,能夠獲取到eureka中服務的信息
@GetMapping
@ResponseBody
public User queryUserById(@RequestParam("id") Long id){
// 根據服務名稱,獲取服務實例。有多是集羣,因此是service實例集合
List<ServiceInstance> instances = discoveryClient.getInstances("service-provider");
// 由於只有一個Service-provider。因此獲取第一個實例
ServiceInstance instance = instances.get(0);
// 獲取ip和端口信息,拼接成服務地址
String baseUrl = "http://" + instance.getHost() + ":" + instance.getPort() + "/user/" + id;
User user = this.restTemplate.getForObject(baseUrl, User.class);
return user;
}
}
複製代碼
5)Debug跟蹤運行:
生成的URL:
訪問結果:
接下來咱們詳細講解Eureka的原理及配置。
Eureka架構中的三個核心角色:
服務註冊中心
Eureka的服務端應用,提供服務註冊和發現功能,就是剛剛咱們創建的itcast-eureka。
服務提供者
提供服務的應用,能夠是SpringBoot應用,也能夠是其它任意技術實現,只要對外提供的是Rest風格服務便可。本例中就是咱們實現的itcast-service-provider。
服務消費者
消費應用從註冊中心獲取服務列表,從而得知每一個服務方的信息,知道去哪裏調用服務方。本例中就是咱們實現的itcast-service-consumer。
Eureka Server即服務的註冊中心,在剛纔的案例中,咱們只有一個EurekaServer,事實上EurekaServer也能夠是一個集羣,造成高可用的Eureka中心。
服務同步
多個Eureka Server之間也會互相註冊爲服務,當服務提供者註冊到Eureka Server集羣中的某個節點時,該節點會把服務的信息同步給集羣中的每一個節點,從而實現數據同步。所以,不管客戶端訪問到Eureka Server集羣中的任意一個節點,均可以獲取到完整的服務列表信息。
動手搭建高可用的EurekaServer
咱們假設要運行兩個EurekaServer的集羣,端口分別爲:10086和10087。只須要把itcast-eureka啓動兩次便可。
1)啓動第一個eurekaServer,咱們修改原來的EurekaServer配置:
server:
port: 10086 # 端口
spring:
application:
name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
client:
service-url: # 配置其餘Eureka服務的地址,而不是本身,好比10087
defaultZone: http://127.0.0.1:10087/eureka
複製代碼
所謂的高可用註冊中心,其實就是把EurekaServer本身也做爲一個服務進行註冊,這樣多個EurekaServer之間就能互相發現對方,從而造成集羣。所以咱們作了如下修改:
啓動報錯,很正常。由於10087服務沒有啓動:
2)啓動第二個eurekaServer,再次修改itcast-eureka的配置:
server:
port: 10087 # 端口
spring:
application:
name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
client:
service-url: # 配置其餘Eureka服務的地址,而不是本身,好比10087
defaultZone: http://127.0.0.1:10086/eureka
複製代碼
注意:idea中一個應用不能啓動兩次,咱們須要從新配置一個啓動器:
而後啓動便可。
3)訪問集羣,測試:
4)客戶端註冊服務到集羣
由於EurekaServer不止一個,所以註冊服務的時候,service-url參數須要變化:
eureka:
client:
service-url: # EurekaServer地址,多個地址以','隔開
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
複製代碼
10086:
10087:
服務提供者要向EurekaServer註冊服務,而且完成服務續約等工做。
服務註冊
服務提供者在啓動時,會檢測配置屬性中的:eureka.client.register-with-eureka=true
參數是否正確,事實上默認就是true。若是值確實爲true,則會向EurekaServer發起一個Rest請求,並攜帶本身的元數據信息,Eureka Server會把這些信息保存到一個雙層Map結構中。
spring.application.name
屬性locahost:service-provider:8081
服務續約
在註冊服務完成之後,服務提供者會維持一個心跳(定時向EurekaServer發起Rest請求),告訴EurekaServer:「我還活着」。這個咱們稱爲服務的續約(renew);
有兩個重要參數能夠修改服務續約的行爲:
eureka:
instance:
lease-expiration-duration-in-seconds: 90
lease-renewal-interval-in-seconds: 30
複製代碼
也就是說,默認狀況下每一個30秒服務會向註冊中心發送一次心跳,證實本身還活着。若是超過90秒沒有發送心跳,EurekaServer就會認爲該服務宕機,會從服務列表中移除,這兩個值在生產環境不要修改,默認便可。
可是在開發時,這個值有點太長了,常常咱們關掉一個服務,會發現Eureka依然認爲服務在活着。因此咱們在開發階段能夠適當調小。
eureka:
instance:
lease-expiration-duration-in-seconds: 10 # 10秒即過時
lease-renewal-interval-in-seconds: 5 # 5秒一次心跳
複製代碼
獲取服務列表
當服務消費者啓動時,會檢測eureka.client.fetch-registry=true
參數的值,若是爲true,則會拉取Eureka Server服務的列表只讀備份,而後緩存在本地。而且每隔30秒
會從新獲取並更新數據。咱們能夠經過下面的參數來修改:
eureka:
client:
registry-fetch-interval-seconds: 5
複製代碼
生產環境中,咱們不須要修改這個值。
可是爲了開發環境下,可以快速獲得服務的最新狀態,咱們能夠將其設置小一點。
服務下線
當服務進行正常關閉操做時,它會觸發一個服務下線的REST請求給Eureka Server,告訴服務註冊中心:「我要下線了」。服務中心接受到請求以後,將該服務置爲下線狀態。
失效剔除
有些時候,咱們的服務提供方並不必定會正常下線,可能由於內存溢出、網絡故障等緣由致使服務沒法正常工做。Eureka Server須要將這樣的服務剔除出服務列表。所以它會開啓一個定時任務,每隔60秒對全部失效的服務(超過90秒未響應)進行剔除。
能夠經過eureka.server.eviction-interval-timer-in-ms
參數對其進行修改,單位是毫秒,生產環境不要修改。
這個會對咱們開發帶來極大的不變,你對服務重啓,隔了60秒Eureka才反應過來。開發階段能夠適當調整,好比:10秒
自我保護
咱們關停一個服務,就會在Eureka面板看到一條警告:
這是觸發了Eureka的自我保護機制。當一個服務未按時進行心跳續約時,Eureka會統計最近15分鐘心跳失敗的服務實例的比例是否超過了85%。在生產環境下,由於網絡延遲等緣由,心跳失敗實例的比例頗有可能超標,可是此時就把服務剔除列表並不穩當,由於服務可能沒有宕機。Eureka就會把當前實例的註冊信息保護起來,不予剔除。生產環境下這頗有效,保證了大多數服務依然可用。
可是這給咱們的開發帶來了麻煩, 所以開發階段咱們都會關閉自我保護模式:(itcast-eureka)
eureka:
server:
enable-self-preservation: false # 關閉自我保護模式(缺省爲打開)
eviction-interval-timer-in-ms: 1000 # 掃描失效服務的間隔時間(缺省爲60*1000ms)
複製代碼
在剛纔的案例中,咱們啓動了一個itcast-service-provider,而後經過DiscoveryClient來獲取服務實例信息,而後獲取ip和端口來訪問。
可是實際環境中,咱們每每會開啓不少個itcast-service-provider的集羣。此時咱們獲取的服務列表中就會有多個,到底該訪問哪個呢?
通常這種狀況下咱們就須要編寫負載均衡算法,在多個實例列表中進行選擇。
不過Eureka中已經幫咱們集成了負載均衡組件:Ribbon,簡單修改代碼便可使用。
什麼是Ribbon:
接下來,咱們就來使用Ribbon實現負載均衡。
首先參照itcast-eureka啓動兩個ItcastServiceProviderApplication實例,一個8081,一個8082。
Eureka監控面板:
由於Eureka中已經集成了Ribbon,因此咱們無需引入新的依賴,直接修改代碼。
修改itcast-service-consumer的引導類,在RestTemplate的配置方法上添加@LoadBalanced
註解:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
複製代碼
修改調用方式,再也不手動獲取ip和端口,而是直接經過服務名稱調用:
@Controller
@RequestMapping("consumer/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
//@Autowired
//private DiscoveryClient discoveryClient; // 注入discoveryClient,經過該客戶端獲取服務列表
@GetMapping
@ResponseBody
public User queryUserById(@RequestParam("id") Long id){
// 經過client獲取服務提供方的服務列表,這裏咱們只有一個
// ServiceInstance instance = discoveryClient.getInstances("service-provider").get(0);
String baseUrl = "http://service-provider/user/" + id;
User user = this.restTemplate.getForObject(baseUrl, User.class);
return user;
}
}
複製代碼
訪問頁面,查看結果:
完美!
爲何咱們只輸入了service名稱就能夠訪問了呢?以前還要獲取ip和端口。
顯然有人幫咱們根據service名稱,獲取到了服務實例的ip和端口。它就是LoadBalancerInterceptor
在以下代碼打斷點:
一路源碼跟蹤:RestTemplate.getForObject --> RestTemplate.execute --> RestTemplate.doExecute:
點擊進入AbstractClientHttpRequest.execute --> AbstractBufferingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.execute:
繼續跟入:LoadBalancerInterceptor.intercept方法
繼續跟入execute方法:發現獲取了8082端口的服務
再跟下一次,發現獲取的是8081:
Ribbon默認的負載均衡策略是簡單的輪詢,咱們能夠測試一下:
編寫測試類,在剛纔的源碼中咱們看到攔截中是使用RibbonLoadBalanceClient來進行負載均衡的,其中有一個choose方法,找到choose方法的接口方法,是這樣介紹的:
如今這個就是負載均衡獲取實例的方法。
咱們注入這個類的對象,而後對其測試:
測試內容:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ItcastServiceConsumerApplication.class)
public class LoadBalanceTest {
@Autowired
private RibbonLoadBalancerClient client;
@Test
public void testLoadBalance(){
for (int i = 0; i < 100; i++) {
ServiceInstance instance = this.client.choose("service-provider");
System.out.println(instance.getHost() + ":" +instance.getPort());
}
}
}
複製代碼
結果:
符合了咱們的預期推測,確實是輪詢方式。
咱們是否能夠修改負載均衡的策略呢?
繼續跟蹤源碼,發現這麼一段代碼:
咱們看看這個rule是誰:
這裏的rule默認值是一個RoundRobinRule
,看類的介紹:
這不就是輪詢的意思嘛。
咱們注意到,這個類實際上是實現了接口IRule的,查看一下:
定義負載均衡的規則接口。
它有如下實現:
SpringBoot也幫咱們提供了修改負載均衡規則的配置入口,在itcast-service-consumer的application.yml中添加以下配置:
server:
port: 80
spring:
application:
name: service-consumer
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
service-provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
複製代碼
格式是:{服務名稱}.ribbon.NFLoadBalancerRuleClassName
,值就是IRule的實現類。
再次測試,發現結果變成了隨機: