Spring Cloud是一系列框架的有序集合。它利用Spring Boot的開發便利性巧妙地簡化了分佈式系統基礎設施的開發,如服務發現註冊、配置中心、消息總線、負載均衡、斷路器、數據監控等,都可以用Spring Boot的開發風格做到一鍵啓動和部署。Spring Cloud並沒有重複製造輪子,它只是將各家公司開發的比較成熟、經得起實際考驗的服務框架組合起來,通過Spring Boot風格進行再封裝屏蔽掉了複雜的配置和實現原理,最終給開發者留出了一套簡單易懂、易部署和易維護的分佈式系統開發工具包(百度)。
Spring Cloud 五大組件
- 服務註冊與發現——Netflix Eureka
-
負載均衡:
- 客戶端負載均衡——Netflix Ribbon
- 服務端負載均衡:——Feign(其也是依賴於Ribbon,只是將調用方式RestTemplete 更改成Service 接口)
- 斷路器——Netflix Hystrix
- 服務網關——Netflix Zuul
- 分佈式配置——Spring Cloud Config
什麼是微服務?
微服務(Microservice Architecture) 是近幾年流行的一種架構思想,關於它的概念很難一言以蔽之。
究竟什麼是微服務呢?我們在此引用ThoughtWorks 公司的首席科學家 Martin Fowler 於2014年提出的一段話:
原文:https://martinfowler.com/articles/microservices.html
漢化:https://www.cnblogs.com/liuning8023/p/4493156.html
- 就目前而言,對於微服務,業界並沒有一個統一的,標準的定義。
- 但通常而言,微服務架構是一種架構模式,或者說是一種架構風格,它體長將單一的應用程序劃分成一組小的服務,每個服務運行在其獨立的自己的進程內,服務之間互相協調,互相配置,爲用戶提供最終價值,服務之間採用輕量級的通信機制(HTTP)互相溝通,每個服務都圍繞着具體的業務進行構建,並且能狗被獨立的部署到生產環境中,另外,應儘量避免統一的,集中式的服務管理機制,對具體的一個服務而言,應該根據業務上下文,選擇合適的語言,工具(Maven)對其進行構建,可以有一個非常輕量級的集中式管理來協調這些服務,可以使用不同的語言來編寫服務,也可以使用不同的數據存儲。
再來從技術維度角度理解下:
- 微服務化的核心就是將傳統的一站式應用,根據業務拆分成一個一個的服務,徹底地去耦合,每一個微服務提供單個業務功能的服務,一個服務做一件事情,從技術角度看就是一種小而獨立的處理過程,類似進程的概念,能夠自行單獨啓動或銷燬,擁有自己獨立的數據庫。
微服務與微服務架構
微服務
強調的是服務的大小,它關注的是某一個點,是具體解決某一個問題/提供落地對應服務的一個服務應用,狹義的看,可以看作是IDEA中的一個個微服務工程,或者Moudel。IDEA 工具裏面使用Maven開發的一個個獨立的小Moudel,它具體是使用SpringBoot開發的一個小模塊,專業的事情交給專業的模塊來做,一個模塊就做着一件事情。強調的是一個個的個體,每個個體完成一個具體的任務或者功能。
微服務架構
一種新的架構形式,Martin Fowler 於2014年提出。
微服務架構是一種架構模式,它體長將單一應用程序劃分成一組小的服務,服務之間相互協調,互相配合,爲用戶提供最終價值。每個服務運行在其獨立的進程中,服務與服務之間採用輕量級的通信機制**(如HTTP)互相協作,每個服務都圍繞着具體的業務進行構建,並且能夠被獨立的部署到生產環境中,另外,應儘量避免統一的,集中式的服務管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具(如Maven)**對其進行構建。
微服務優點
- 單一職責原則;
- 每個服務足夠內聚,足夠小,代碼容易理解,這樣能聚焦一個指定的業務功能或業務需求;
- 開發簡單,開發效率高,一個服務可能就是專一的只幹一件事;
- 微服務能夠被小團隊單獨開發,這個團隊只需2-5個開發人員組成;
- 微服務是松耦合的,是有功能意義的服務,無論是在開發階段或部署階段都是獨立的;
- 微服務能使用不同的語言開發;
- 易於和第三方集成,微服務允許容易且靈活的方式集成自動部署,通過持續集成工具,如jenkins,Hudson,bamboo;
- 微服務易於被一個開發人員理解,修改和維護,這樣小團隊能夠更關注自己的工作成果,無需通過合作才能體現價值;
- 微服務允許利用和融合最新技術;
- 微服務只是業務邏輯的代碼,不會和HTML,CSS,或其他的界面混合;
- 每個微服務都有自己的存儲能力,可以有自己的數據庫,也可以有統一的數據庫;
微服務的缺點
- 開發人員要處理分佈式系統的複雜性;
- 多服務運維難度,隨着服務的增加,運維的壓力也在增大;
- 系統部署依賴問題;
- 服務間通信成本問題;
- 數據一致性問題;
- 系統集成測試問題;
- 性能和監控問題;
微服務技術棧有那些?
| **微服務技術條目** | 落地技術 |
| -------------------- | ------------------------------------------------ |
| 服務開發 | SpringBoot、Spring、SpringMVC等 |
| 服務配置與管理 | Netfix公司的Archaius、阿里的Diamond等 |
| 服務註冊與發現 | Eureka、Consul、Zookeeper等 |
| 服務調用 | Rest、PRC、gRPC |
| 服務熔斷器 | Hystrix、Envoy等 |
| 負載均衡 | Ribbon、Nginx等 |
| 服務接口調用(客戶端調用服務的簡化工具) | Fegin等 |
| 消息隊列 | Kafka、RabbitMQ、ActiveMQ等 |
| 服務配置中心管理 | SpringCloudConfig、Chef等 |
| 服務路由(API網關) | Zuul等 |
| 服務監控 | Zabbix、Nagios、Metrics、Specatator等 |
| 全鏈路追蹤 | Zipkin、Brave、Dapper等 |
| 數據流操作開發包 | SpringCloud Stream(封裝與Redis,Rabbit,Kafka等發送接收消息) |
| 時間消息總棧 | SpringCloud Bus |
| 服務部署 | Docker、OpenStack、Kubernetes等 |
爲什麼選擇SpringCloud作爲微服務架構
-
選型依據
- 整體解決方案和框架成熟度
- 社區熱度
- 可維護性
- 學習曲線
-
當前各大IT公司用的微服務架構有那些?
- 阿里:dubbo+HFS
- 京東:JFS
- 新浪:Motan
-
噹噹網:DubboX
…
-
各微服務框架對比
| 功能點/服務框架 | Netflix/SpringCloud | Motan | gRPC | Thri t | Dubbo/DubboX |
| —————— | ———————————————————————————— | —————————————————- | ————————— | ———— | —————————— |
| 功能定位 | 完整的微服務框架 | RPC框架,但整合了ZK或Consul,實現集羣環境的基本服務註冊發現 | RPC框架 | RPC框架 | 服務框架 |
| 支持Rest | 是,Ribbon支持多種可拔插的序列號選擇 | 否 | 否 | 否 | 否 |
| 支持RPC | 否 | 是(Hession2) | 是 | 是 | 是 |
| 支持多語言 | 是(Rest形式) | 否 | 是 | 是 | 否 |
| 負載均衡 | 是(服務端zuul+客戶端Ribbon),zuul-服務,動態路由,雲端負載均衡Eureka(針對中間層服務器) | 是(客戶端) | 否 | 否 | 是(客戶端) |
| 配置服務 | Netfix Archaius,Spring Cloud Config Server 集中配置 | 是(Zookeeper提供) | 否 | 否 | 否 |
| 服務調用鏈監控 | 是(zuul),zuul提供邊緣服務,API網關 | 否 | 否 | 否 | 否 |
| 高可用/容錯 | 是(服務端Hystrix+客戶端Ribbon) | 是(客戶端) | 否 | 否 | 是(客戶端) |
| 典型應用案例 | Netflix | Sina | Google | Facebook | |
| 社區活躍程度 | 高 | 一般 | 高 | 一般 | 2017年後重新開始維護,之前中斷了5年 |
| 學習難度 | 中等 | 低 | 高 | 高 | 低 |
| 文檔豐富程度 | 高 | 一般 | 一般 | 一般 | 高 |
| 其他 | Spring Cloud Bus爲我們的應用程序帶來了更多管理端點 | 支持降級 | Netflix內部在開發集成gRPC | IDL定義 | 實踐的公司比較多 |
SpringCloud和SpringBoot的關係
- SpringBoot專注於開發方便的開發單個個體微服務;
- SpringCloud是關注全局的微服務協調整理治理框架,它將SpringBoot開發的一個個單體微服務,整合並管理起來,爲各個微服務之間提供:配置管理、服務發現、斷路器、路由、爲代理、事件總棧、全局鎖、決策競選、分佈式會話等等集成服務;
- SpringBoot可以離開SpringCloud獨立使用,開發項目,但SpringCloud離不開SpringBoot,屬於依賴關係;
- SpringBoot專注於快速、方便的開發單個個體微服務,SpringCloud關注全局的服務治理框架;
Dubbo 和 SpringCloud技術選型
1. 分佈式+服務治理Dubbo
目前成熟的互聯網架構,應用服務化拆分 + 消息中間件
2. Dubbo 和 SpringCloud對比
可以看一下社區活躍度:
https://github.com/spring-cloud
對比結果:
| | Dubbo | SpringCloud |
| ------ | ------------- | ---------------------------- |
| 服務註冊中心 | Zookeeper | Spring Cloud Netfilx Eureka |
| 服務調用方式 | RPC | REST API |
| 服務監控 | Dubbo-monitor | Spring Boot Admin |
| 斷路器 | 不完善 | Spring Cloud Netfilx Hystrix |
| 服務網關 | 無 | Spring Cloud Netfilx Zuul |
| 分佈式配置 | 無 | Spring Cloud Config |
| 服務跟蹤 | 無 | Spring Cloud Sleuth |
| 消息總棧 | 無 | Spring Cloud Bus |
| 數據流 | 無 | Spring Cloud Stream |
| 批量任務 | 無 | Spring Cloud Task |
最大區別:Spring Cloud 拋棄了Dubbo的RPC通信,採用的是基於HTTP的REST方式
嚴格來說,這兩種方式各有優劣。雖然從一定程度上來說,後者犧牲了服務調用的性能,但也避免了上面提到的原生RPC帶來的問題。而且REST相比RPC更爲靈活,服務提供方和調用方的依賴只依靠一紙契約,不存在代碼級別的強依賴,這個優點在當下強調快速演化的微服務環境下,顯得更加合適。
品牌機和組裝機的區別
社區支持與更新力度的區別
**總結:**二者解決的問題域不一樣:Dubbo的定位是一款RPC框架,而SpringCloud的目標是微服務架構下的一站式解決方案。
SpringCloud能幹嘛?
- Distributed/versioned configuration 分佈式/版本控制配置
- Service registration and discovery 服務註冊與發現
- Routing 路由
- Service-to-service calls 服務到服務的調用
- Load balancing 負載均衡配置
- Circuit Breakers 斷路器
- Distributed messaging 分佈式消息管理
- …
自學參考書:
- SpringCloud Netflix 中文文檔:https://springcloud.cc/spring-cloud-netflix.html
- SpringCloud 中文API文檔(官方文檔翻譯版):https://springcloud.cc/spring-cloud-dalston.html
- SpringCloud中國社區:http://springcloud.cn/
- SpringCloud中文網:https://springcloud.cc
SpringCloud版本選擇
大版本說明
| SpringBoot | SpringCloud | 關係 |
| ---------- | ----------------- | ---------------------------------- |
| 1.2.x | Angel版本(天使) | 兼容SpringBoot1.2x |
| 1.3.x | Brixton版本(布里克斯頓) | 兼容SpringBoot1.3x,也兼容SpringBoot1.4x |
| 1.4.x | Camden版本(卡姆登) | 兼容SpringBoot1.4x,也兼容SpringBoot1.5x |
| 1.5.x | Dalston版本(多爾斯頓) | 兼容SpringBoot1.5x,不兼容SpringBoot2.0x |
| 1.5.x | Edgware版本(埃奇韋爾) | 兼容SpringBoot1.5x,不兼容SpringBoot2.0x |
| 2.0.x | Finchley版本(芬奇利) | 兼容SpringBoot2.0x,不兼容SpringBoot1.5x |
| 2.1.x | Greenwich版本(格林威治) | |
實際開發版本關係
| spring-boot-starter-parent | | spring-cloud-dependencles | |
|:--------------------------:| --------:|:-------------------------:|:--------:|
| **版本號** | **發佈日期** | **版本號** | **發佈日期** |
| 1.5.2.RELEASE | 2017-03 | Dalston.RC1 | 2017-x |
| 1.5.9.RELEASE | 2017-11 | Edgware.RELEASE | 2017-11 |
| 1.5.16.RELEASE | 2018-04 | Edgware.SR5 | 2018-10 |
| 1.5.20.RELEASE | 2018-09 | Edgware.SR5 | 2018-10 |
| 2.0.2.RELEASE | 2018-05 | Fomchiey.BULD-SNAPSHOT | 2018-x |
| 2.0.6.RELEASE | 2018-10 | Fomchiey-SR2 | 2018-10 |
| 2.1.4.RELEASE | 2019-04 | Greenwich.SR1 | 2019-03 |
使用後兩個
SpringCloud Rest學習環境搭建:
1、創建空的maven項目,刪除src目錄
父工程依賴(子項目引入依賴可以不寫版本號)
<?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.ykk</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>springcloud-api</module>
<module>springcloud-provider-dept-8001</module>
</modules>
<!--打包方式 pom-->
<packaging>pom</packaging>
<!-- 依賴版本控制-->
<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>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<springboot.version>2.1.4.RELEASE</springboot.version>
<mysql-connector-java.version>5.1.47</mysql-connector-java.version>
<druid.version>1.1.10</druid.version>
<mybatis.version>1.3.2</mybatis.version>
<logback-core.version>1.2.3</logback-core.version>
</properties>
<!-- 依賴管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springCloud的依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringBoot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--數據庫-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!--日誌測試~-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback-core.version}</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
創建springcloud-api子項目
導入依賴
<?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>springcloud</artifactId>
<groupId>com.ykk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-api</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
創建實體類Dept
@Data
@NoArgsConstructor
// 開啓鏈式寫法
@Accessors(chain = true)
public class Dept implements Serializable {
private Long deptno;
private String dname;
// 這個數據存在那個數據庫的字段,微服務,一個服務對應一個數據庫,同一個信息可能存在不同的數據庫
private String db_source;
public Dept(String dname) {
this.dname = dname;
}
}
服務提供者
創建子項目springcloud-provider-dept-8001
導入依賴
<?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>springcloud</artifactId>
<groupId>com.ykk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider-dept-8001</artifactId>
<dependencies>
<!-- 我們需要拿到dept實體類,所有需要導入springcloud-api模塊-->
<dependency>
<groupId>com.ykk</groupId>
<artifactId>springcloud-api</artifactId>
</dependency>
<!-- 因爲父項目有依賴管理,所以子項目的依賴可以不寫版本號-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<!--數據庫-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--日誌測試~-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<!--SpringBoot 啓動器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--jetty-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!--熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>com.ykk</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
這裏特別留意,需要使用到springcloud-api子項目的dept類,所有需要導入
application.yml配置文件
server:
port: 8001
mybatis:
type-aliases-package: com.ykk.springcloud.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
spring:
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
mybatis-config.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 開啓二級緩存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
開始寫實現代碼,按照Dao-Service-controller開發順序
DeptDao接口
@Mapper
// @Repository註解修飾哪個類,則表明這個類具有對對象進行CRUD(增刪改查)的功能
@Repository
public interface DeptDao {
// 添加dept
boolean addDept(Dept dept);
// 根據id查詢dept
Dept queryById(Long id);
// 查詢所有
List<Dept> queryAll();
}
DeptDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ykk.springcloud.dao.DeptDao">
<insert id="addDept" parameterType="Dept">
INSERT INTO dept (dname,db_source)
VALUES (#{dname},DATABASE());
</insert>
<select id="queryById" resultType="Dept" parameterType="Long">
SELECT * FROM dept
WHERE deptno=#{deptno};
</select>
<select id="queryAll" resultType="Dept">
SELECT * FROM dept;
</select>
</mapper>
DeptService
public interface DeptService {
boolean addDept(Dept dept);
Dept queryById(Long id);
List<Dept> queryAll();
}
DeptServiceImpl (特別注意要加註解,容易漏寫)
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptDao deptDao;
@Override
public boolean addDept(Dept dept) {
return deptDao.addDept(dept);
}
@Override
public Dept queryById(Long id) {
return deptDao.queryById(id);
}
@Override
public List<Dept> queryAll() {
return deptDao.queryAll();
}
}
DeptController
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@PostMapping("dept/add")
public boolean addDept(Dept dept) {
return deptService.addDept(dept);
}
// http://127.0.0.1:8001/dept/get/1
@GetMapping("dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
System.out.println(id);
return deptService.queryById(id);
}
@GetMapping("dept/list")
public List<Dept> getAll() {
return deptService.queryAll();
}
}
DeptProvider_8001動類
@SpringBootApplication
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
消費者
什麼是RestTemplate
RestTemplate 是從 Spring3.0 開始支持的一個 HTTP 請求工具,它提供了常見的REST請求方案的模版,例如 GET 請求、POST 請求、PUT 請求、DELETE 請求以及一些通用的請求執行方法 exchange 以及 execute。RestTemplate 繼承自 InterceptingHttpAccessor 並且實現了 RestOperations 接口,其中 RestOperations 接口定義了基本的 RESTful 操作,這些操作在 RestTemplate 中都得到了實現。接下來我們就來看看這些操作方法的使用。
創建子項目springcloud-consumer-dept-80
導入依賴
<?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>springcloud</artifactId>
<groupId>com.ykk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-dept-80</artifactId>
<dependencies>
<dependency>
<groupId>com.ykk</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
application.yml(80端口好處是URL可以不寫具體端口號)
server:
port: 80
ConfigBean
@Configuration
public class ConfigBean {
@Bean
public RestTemplate getRestTemplate(){
// 將RestTemplate註冊到bean中,使用時通過Autowried自動裝配
return new RestTemplate();
}
}
DeptConsumerController
@RestController
public class DeptConsumerController {
// 理解:消費者不應該有service層
// RestTemplate提供方法調用,並註冊到spring中
@Autowired
private RestTemplate restTemplate;
private static final String REST_URL_PREFIX = "http://localhost:8001";
// 此方法調用後數據庫確實生成了一條數據,但沒有內容,連接問題??
@RequestMapping("consumer/dept/add")
public boolean add(Dept dept) {
System.out.println(dept.toString());
return restTemplate.postForObject(REST_URL_PREFIX + "dept/add", Dept.class, Boolean.class);
}
@RequestMapping("consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return restTemplate.getForObject(REST_URL_PREFIX + "dept/get/" + id, Dept.class);
}
@RequestMapping("consumer/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PREFIX + "dept/list", List.class);
}
}
DeptConsumer_80啓動類
@SpringBootApplication
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
Eureka服務註冊中心
5.1 什麼是Eureka
- Netflix在涉及Eureka時,遵循的就是API原則.
- Eureka是Netflix的有個子模塊,也是核心模塊之一。Eureka是基於REST的服務,用於定位服務,以實現雲端中間件層服務發現和故障轉移,服務註冊與發現對於微服務來說是非常重要的,有了服務註冊與發現,只需要使用服務的標識符,就可以訪問到服務,而不需要修改服務調用的配置文件了,功能類似於Dubbo的註冊中心,比如Zookeeper.
5.2 原理理解
-
Eureka基本的架構
- Springcloud 封裝了Netflix公司開發的Eureka模塊來實現服務註冊與發現 (對比Zookeeper).
- Eureka採用了C-S的架構設計,EurekaServer作爲服務註冊功能的服務器,他是服務註冊中心.
- 而系統中的其他微服務,使用Eureka的客戶端連接到EurekaServer並維持心跳連接。這樣系統的維護人員就可以通過EurekaServer來監控系統中各個微服務是否正常運行,Springcloud 的一些其他模塊 (比如Zuul) 就可以通過EurekaServer來發現系統中的其他微服務,並執行相關的邏輯.
和Dubbo架構對比.
- Eureka 包含兩個組件:Eureka Server 和 Eureka Client.
- Eureka Server 提供服務註冊,各個節點啓動後,回在EurekaServer中進行註冊,這樣Eureka Server中的服務註冊表中將會儲存所有課用服務節點的信息,服務節點的信息可以在界面中直觀的看到.
- Eureka Client 是一個Java客戶端,用於簡化EurekaServer的交互,客戶端同時也具備一個內置的,使用輪詢負載算法的負載均衡器。在應用啓動後,將會向EurekaServer發送心跳 (默認週期爲30秒) 。如果Eureka Server在多個心跳週期內沒有接收到某個節點的心跳,EurekaServer將會從服務註冊表中把這個服務節點移除掉 (默認週期爲90s).
-
三大角色
- Eureka Server:提供服務的註冊與發現
- Service Provider:服務生產方,將自身服務註冊到Eureka中,從而使服務消費方能狗找到
- Service Consumer:服務消費方,從Eureka中獲取註冊服務列表,從而找到消費服務
創建子項目springcloud-eureka-7001
<?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>springcloud</artifactId>
<groupId>com.ykk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-eureka-7001</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<!--Eureka服務依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
applicationyml配置文件
server:
port: 7001
eureka:
instance:
hostname: localhost # Eureka服務端示例名稱
client:
register-with-eureka: false # 表示是否想Eureka註冊中心註冊自己
fetch-registry: false # 表示自己爲註冊中心
service-url: # 監控畫面
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
EurekaServer_7001啓動類
@SpringBootApplication
// 開啓eureka服務
@EnableEurekaServer
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class, args);
}
}
訪問:http://127.0.0.1:7001/ 即可看到監控頁面
將子項目springcloud-provider-dept-8001註冊到eureka服務中
切換到子項目springcloud-provider-dept-8001
pom文件添加eureka依賴
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
application.yml配置文件添加eureka配置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ #註冊到eureka服務地址
啓動類加註解
@SpringBootApplication
// 開啓eureka註冊服務
@EnableEurekaClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
先啓動springcloud-eureka-7001再啓動springcloud-provider-dept-8001,訪問:http://127.0.0.1:7001/
修改狀態描述
yml配置文件添加
eureka:
instance:
instance-id: 888 #修改Eureka上的默認描述信息
狀態描述完善
pom文件添加依賴
<!--actuator完善監控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
yml配置文件添加
# info配置
info:
# 項目的名稱
app.name: springcloud-provider-dept-8001
# 公司的名稱
company.name: ykk
點擊監控頁面狀態描述
如果此時停掉springcloud-provider-dept-8001 等30s後 監控會開啓保護機制:
EureKa自我保護機制:好死不如賴活着
一句話總結就是:某時刻某一個微服務不可用,eureka不會立即清理,依舊會對該微服務的信息進行保存!
- 默認情況下,當eureka server在一定時間內沒有收到實例的心跳,便會把該實例從註冊表中刪除(默認是90秒),但是,如果短時間內丟失大量的實例心跳,便會觸發eureka server的自我保護機制,比如在開發測試時,需要頻繁地重啓微服務實例,但是我們很少會把eureka server一起重啓(因爲在開發過程中不會修改eureka註冊中心),當一分鐘內收到的心跳數大量減少時,會觸發該保護機制。可以在eureka管理界面看到Renews threshold和Renews(last min),當後者(最後一分鐘收到的心跳數)小於前者(心跳閾值)的時候,觸發保護機制,會出現紅色的警告:
EMERGENCY!EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT.RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEGING EXPIRED JUST TO BE SAFE.
從警告中可以看到,eureka認爲雖然收不到實例的心跳,但它認爲實例還是健康的,eureka會保護這些實例,不會把它們從註冊表中刪掉。 - 該保護機制的目的是避免網絡連接故障,在發生網絡故障時,微服務和註冊中心之間無法正常通信,但服務本身是健康的,不應該註銷該服務,如果eureka因網絡故障而把微服務誤刪了,那即使網絡恢復了,該微服務也不會重新註冊到eureka server了,因爲只有在微服務啓動的時候纔會發起註冊請求,後面只會發送心跳和服務列表請求,這樣的話,該實例雖然是運行着,但永遠不會被其它服務所感知。所以,eureka server在短時間內丟失過多的客戶端心跳時,會進入自我保護模式,該模式下,eureka會保護註冊表中的信息,不在註銷任何微服務,當網絡故障恢復後,eureka會自動退出保護模式。自我保護模式可以讓集羣更加健壯。
- 但是我們在開發測試階段,需要頻繁地重啓發布,如果觸發了保護機制,則舊的服務實例沒有被刪除,這時請求有可能跑到舊的實例中,而該實例已經關閉了,這就導致請求錯誤,影響開發測試。所以,在開發測試階段,我們可以把自我保護模式關閉,只需在eureka server配置文件中加上如下配置即可:
eureka.server.enable-self-preservation=false
【不推薦關閉自我保護機制】
詳細內容可以參考下這篇博客內容:https://blog.csdn.net/wudiyong22/article/details/80827594
獲取微服務註冊的信息(團隊開發會用到?)
DeptController增加接口測試
// 導包留意是springcloud的
@Autowired
private DiscoveryClient client;
@GetMapping("/dept/discovery")
public Object discovery() {
// 獲取微服務列表的清單
List<String> services = client.getServices();
System.out.println("discovery=>services:" + services);
// 得到一個具體的微服務信息,通過具體的微服務id,applicaioinName;
List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance instance : instances) {
System.out.println(
instance.getHost() + "\t" + // 主機名稱
instance.getPort() + "\t" + // 端口號
instance.getUri() + "\t" + // uri
instance.getServiceId() // 服務id
);
}
return this.client;
}
啓動類開啓註解
@SpringBootApplication
// 開啓eureka註冊服務
@EnableEurekaClient
// @EnableEurekaClient 開啓服務發現客戶端的註解,可以用來獲取一些配置的信息,得到具體的微服務
// 沒開啓也能拿到
@EnableDiscoveryClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
Eureka:集羣環境配置
在本機C:\Windows\System32\drivers\etc目錄下的hosts文件下自定義3個地址(模擬網絡地址);
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
創建子項目springcloud-eureka-7002,springcloud-eureka-7003,共3個eureka服務
3個項目pom文件、yml配置文件、啓動類都一樣,只是個別配置參數不同
application。yml配置文件
server:
port: 7001
eureka:
instance:
# 3個項目對應3個地址
hostname: eureka7001.com # Eureka服務端示例名稱,這個地址是本地自定義的
client:
register-with-eureka: false # 表示是否想Eureka註冊中心註冊自己
fetch-registry: false # 表示自己爲註冊中心
service-url: # 監控畫面
# 通過,間隔其他地址,3個項目對應綁定其他2個地址
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
3個eureka服務啓動後,訪問其中一個即可看到其他兩個eureka服務
http://127.0.0.1:7001
http://127.0.0.1:7002
http://127.0.0.1:7003
子項目springcloud-provider-dept-8001註冊到3個eureka集羣服務上
eureka:
client:
service-url:
#註冊中心地址7001-7003,每個地址用,隔開
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
運行即可在上圖任何一個頁面上看到這個模塊
對比和Zookeeper區別
1. 回顧CAP原則
RDBMS (MySQL\Oracle\sqlServer) ===> ACID
NoSQL (Redis\MongoDB) ===> CAP
2. ACID是什麼?
- A (Atomicity) 原子性
- C (Consistency) 一致性
- I (Isolation) 隔離性
- D (Durability) 持久性
3. CAP是什麼?
- C (Consistency) 強一致性
- A (Availability) 可用性
- P (Partition tolerance) 分區容錯性
CAP的三進二:CA、AP、CP
4. CAP理論的核心
- 一個分佈式系統不可能同時很好的滿足一致性,可用性和分區容錯性這三個需求
-
根據CAP原理,將NoSQL數據庫分成了滿足CA原則,滿足CP原則和滿足AP原則三大類
- CA:單點集羣,滿足一致性,可用性的系統,通常可擴展性較差
- CP:滿足一致性,分區容錯的系統,通常性能不是特別高
- AP:滿足可用性,分區容錯的系統,通常可能對一致性要求低一些
5. 作爲分佈式服務註冊中心,Eureka比Zookeeper好在哪裏?
著名的CAP理論指出,一個分佈式系統不可能同時滿足C (一致性) 、A (可用性) 、P (容錯性),由於分區容錯性P再分佈式系統中是必須要保證的,因此我們只能再A和C之間進行權衡。
- Zookeeper 保證的是 CP —> 滿足一致性,分區容錯的系統,通常性能不是特別高
- Eureka 保證的是 AP —> 滿足可用性,分區容錯的系統,通常可能對一致性要求低一些
Zookeeper保證的是CP
當向註冊中心查詢服務列表時,我們可以容忍註冊中心返回的是幾分鐘以前的註冊信息,但不能接收服務直接down掉不可用。也就是說,服務註冊功能對可用性的要求要高於一致性。但zookeeper會出現這樣一種情況,當master節點因爲網絡故障與其他節點失去聯繫時,剩餘節點會重新進行leader選舉。問題在於,選舉leader的時間太長,30-120s,且選舉期間整個zookeeper集羣是不可用的,這就導致在選舉期間註冊服務癱瘓。在雲部署的環境下,因爲網絡問題使得zookeeper集羣失去master節點是較大概率發生的事件,雖然服務最終能夠恢復,但是,漫長的選舉時間導致註冊長期不可用,是不可容忍的。
Eureka保證的是AP
Eureka看明白了這一點,因此在設計時就優先保證可用性。Eureka各個節點都是平等的,幾個節點掛掉不會影響正常節點的工作,剩餘的節點依然可以提供註冊和查詢服務。而Eureka的客戶端在向某個Eureka註冊時,如果發現連接失敗,則會自動切換至其他節點,只要有一臺Eureka還在,就能保住註冊服務的可用性,只不過查到的信息可能不是最新的,除此之外,Eureka還有之中自我保護機制,如果在15分鐘內超過85%的節點都沒有正常的心跳,那麼Eureka就認爲客戶端與註冊中心出現了網絡故障,此時會出現以下幾種情況:
- Eureka不在從註冊列表中移除因爲長時間沒收到心跳而應該過期的服務
- Eureka仍然能夠接受新服務的註冊和查詢請求,但是不會被同步到其他節點上 (即保證當前節點依然可用)
- 當網絡穩定時,當前實例新的註冊信息會被同步到其他節點中
因此,Eureka可以很好的應對因網絡故障導致部分節點失去聯繫的情況,而不會像zookeeper那樣使整個註冊服務癱瘓