本文主要介紹 SpringCloud 的入門
一、SpringCloud 的五大組件
(需要牢牢記住他們,現在混個眼熟,下面會詳細介紹,)

自學參考檔案:
- SpringCloud 官方檔案(漢化版):https://springcloud.cc/spring-cloud-dalston.html
- SpringCloud中國社區:http://springcloud.cn/
- SpringCloud中文網:https://springcloud.cc
自學參考文章:
- 一文詳解微服務架構:https://www.cnblogs.com/skabyy/p/11396571.html
自學參考知乎各個大神們的回答:
- 微服務架構是什么?:https://www.zhihu.com/question/65502802
自學參考視頻:
- kuangstudy:https://www.kuangstudy.com/course/play/1321005531116863490
二、什么是微服務
Spring官網:https://spring.io/

微服務(Microservice Architecture) 是近幾年流行的一種架構思想,關于它的概念很難一言以蔽之,究竟什么是微服務呢?我們在此參考ThoughtWorks 公司的首席科學家 Martin Fowler 于2014年提出的一段話:
原文:https://martinfowler.com/articles/microservices.html
中文:https://www.cnblogs.com/liuning8023/p/4493156.html
就目前而言,對于微服務,業界沒有一個統一的標準定義,通常而言,微服務架構是一種架構模式,或者說是一種架構風格,它提倡將單一的應用程式劃分為一組小的服務,每個服務運行在其獨特的自己的行程內,服務之間互相協調,互相配置,為用戶提供最終價值,服務之間采用輕量級的通信機制互相溝通,每個服務都圍繞著具體的業務進行構建,并且能夠被獨立的部署到生產環境中,另外,應盡量避免統一的,集中式的服務管理機制,對具體的一個服務而言,應根據業務背景關系,選擇適合的語言,工具對其進行構建,可以有一個非常輕量級的集中管理來協調這些服務,可以用不同的語言來撰寫服務,也可以使用不同的資料庫, (牢牢記住上面這段概念,幾乎每句話都有微服務的關鍵點)
三、傳統開發模式 VS 微服務
- 傳統的web開發方式
一般被稱為Monolithic(單體式開發),所有的功能打包在一個 WAR包里,基本沒有外部依賴(除了容器),部署在一個JEE容器(Tomcat,JBoss,WebLogic)里,包含了 DO/DAO,Service,UI等所有邏輯,

優點:
集中式管理,開發簡單,功能都在本地,沒有分布式的管理和呼叫,
缺點:
1.效率低,開發都在同一個專案改代碼,互相等待,沖突不斷;
2.穩定性差,一個微小的問題,可能導致整個程式掛掉;
3.維護性難,代碼高耦合,內部關系難以摸清楚;
4.擴展性差,無法滿足高并發下的業務需求;
- 隨著業務的發展,移動端興起

這一階段,架構設計存在著很多不合理的地方:
1.網站和移動端存在著很多相同業務邏輯的重復代碼;
2.資料庫被多個應用依賴,無法重構和優化;
3.資料有時候通過資料庫共享,有時候通過介面呼叫傳輸,介面呼叫關系雜亂;
4.單個應用為了給其他應用提供介面,設計得越來越復雜,包含本不屬于它得邏輯;
5.應用之間界限模糊,功能歸屬混亂,出現問題后各部門職責很難劃分,出現分歧或爭端;
6.所有的應用都在一個資料庫上操作,資料庫出現性能瓶頸;
7.管理后臺保障級別比較低,添加新的功能可能影響到其他應用;
8.開發、部署、維護、升級愈發困難;
- 下一階段,除去大量的冗余代碼

在這一階段,服務已經被拆分開了,但是資料庫依然是共用的,會出現一些問題:
1.資料庫性能瓶頸,而且存在一定的風險;
2.資料庫表結構可能被多個服務依賴,維護困難;
- 提高系統的實時性,再次升級架構(微服務)

此時,拆分后的各個服務可以采用異構的技術,比如,資料分析服務可以使用資料倉庫作為持久化層,以便于高效地做一些統計計算;促銷服務訪問比較頻繁,因此可以加入快取機制,
微服務,它是具體解決某一個問題/提供落地對應服務的一個服務應用,狹義的看,可以看作是IDEA中的一個個微服務工程,或者Moudel,IDEA 工具里面使用Maven開發的一個個獨立的小Moudel,它具體是使用SpringBoot開發的一個小模塊,專業的事情交給專業的模塊來做,一個模塊就做著一件事情,強調的是一個個的個體,每個個體完成一個具體的任務或者功能,
微服務的優點:
1.單一職責原則;
2.每個服務足夠內聚,足夠小,代碼容易理解;
3.開發效率高,一個服務可能就是專一的只干一件事;
4.微服務能夠被小團隊單獨開發,這個團隊只需2-5個開發人員組成;
5.松耦合,無論是在開發階段或部署階段都是獨立的;
6.可以使用不同的語言開發;
7.易于和第三方集成,微服務允許容易且靈活的方式集成自動部署,通過持續集成工具,如jenkins,Hudson,bamboo;
8.每個微服務都有自己的存盤能力,可以有自己的資料庫,也可以有統一的資料庫;
微服務的缺點:
1.開發人員要處理分布式系統的復雜性;
2.多服務運維難度,隨著服務的增加,運維的壓力也在增大;
3.各個服務間的通信成本問題;
4.整個應用分散成多個服務,定位故障相對困難;
5.一個服務故障可能產生雪崩效用,導致整個系統故障;
整體解決思路如下:

四、SpringCloud入門
官網:http://projects.spring.io/spring-cloud/

SpringCloud沒有采用數字編號的方式命名版本號,而是采用了倫敦地鐵站的名稱,同時根據字母表的順序來對應版本時間順序,比如最早的Realse版本:Angel,第二個Realse版本:Brixton,然后是Camden、Dalston、Edgware,目前最新的是Hoxton SR4 CURRENT GA通用穩定版,
- 微服務技術堆疊有那些?
| 微服務技術條目 | 落地技術 |
|---|---|
| 服務開發 | 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等 |
- 各微服務框架對比
| 功能點/服務框架 | Netflix/SpringCloud | Motan | gRPC | Thrit | 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 | |||
| 社區活躍程度 | 高 | 一般 | 高 | 一般 | 2017年后重新開始維護,之前中斷了5年 |
| 學習難度 | 中等 | 低 | 高 | 高 | 低 |
| 檔案豐富程度 | 高 | 一般 | 一般 | 一般 | 高 |
| 其他 | Spring Cloud Bus為我們的應用程式帶來了更多管理端點 | 支持降級 | Netflix內部在開發集成gRPC | IDL定義 | 實踐的公司比較多 |
- Rest 搭建學習環境
1.創建父工程 springcloud (使用 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>com.zhou</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>springcloud-consumer-dept-80</module>
<module>springcloud-provider-dept-8001</module>
<module>springcloud-api</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>
</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的依賴-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>runtime</scope>
</dependency>
<!--SpringBoot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--資料庫-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
<!--SpringBoot 啟動器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--日志測驗-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<!--Maven 資源過濾問題-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
注意:父工程為 springcloud,其下有多個子 module
2.創建子模塊 springcloud-api,它只負責接管 pojo
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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.zhou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-api</artifactId>
<dependencies>
<!--當前的module自己需要的依賴,如果父類中已經配置了版本,這里就不需要寫了-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
3.在SQLyog中創建資料庫 db01

idea連接此資料庫并創建一些欄位和資料

insert into dept (dname, db_source) VALUES ('開發部',DATABASE());
insert into dept (dname, db_source) VALUES ('設計部',DATABASE());
insert into dept (dname, db_source) VALUES ('人事部',DATABASE());
insert into dept (dname, db_source) VALUES ('運營部',DATABASE());
insert into dept (dname, db_source) VALUES ('企劃部',DATABASE());
insert into dept (dname, db_source) VALUES ('編輯部',DATABASE());
4.創建物體類
package com.zhou.springcloud.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@NoArgsConstructor
@Accessors(chain = true) //鏈式編程,默認 boolean chain() default false;
public class Dept implements Serializable { //Dept 物體類
private Long deptno;//主鍵
private String dname;
//這個資料存在那個資料庫的欄位,微服務,一個服務對應一個資料庫,同一個資訊可能存在不同的資料庫
private String db_source;
public Dept(String dname) {
this.dname = dname;
}
}
5.創建子模塊 springcloud-provider-dept-8001 (服務的提供者)
子模塊 springcloud-provider-dept-8001 的整體專案結構如下:

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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.zhou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider-dept-8001</artifactId>
<dependencies>
<!--我們需要拿到物體類,所以要配置 api module-->
<dependency>
<groupId>com.zhou</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--junit-->
<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>
<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>
<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>
</dependencies>
</project>
application.yaml
server:
port: 8001
#mybatis 配置
mybatis:
type-aliases-package: com.zhou.springcloud.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
#speing 配置
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
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="https://www.cnblogs.com/1693977889zz/p/true"/>
</settings>
</configuration>
DeptMapper 介面
package com.zhou.springcloud.mapper;
import com.zhou.springcloud.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface DeptMapper {
boolean addDept(Dept dept);
Dept queryById(@Param("id") Long id);
List<Dept> queryAll();
}
DeptMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhou.springcloud.mapper.DeptMapper">
<insert id="addDept" parameterType="Dept">
insert into dept (dname, db_source)
values (#{dname},DATABASE());
</insert>
<select id="queryById" parameterType="Long" resultType="Dept">
select * from dept where deptno=#{id};
</select>
<select id="queryAll" resultType="Dept">
select * from dept
</select>
</mapper>
DeptService 介面
package com.zhou.springcloud.service;
import com.zhou.springcloud.pojo.Dept;
import java.util.List;
public interface DeptService {
boolean addDept(Dept dept);
Dept queryById(Long id);
List<Dept> queryAll();
}
DeptServiceImpl.java
package com.zhou.springcloud.service;
import com.zhou.springcloud.mapper.DeptMapper;
import com.zhou.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService{
@Autowired
private DeptMapper deptMapper;
@Override
public boolean addDept(Dept dept) {
return deptMapper.addDept(dept);
}
@Override
public Dept queryById(Long id) {
return deptMapper.queryById(id);
}
@Override
public List<Dept> queryAll() {
return deptMapper.queryAll();
}
}
DeptController.java
package com.zhou.springcloud.controller;
import com.zhou.springcloud.pojo.Dept;
import com.zhou.springcloud.service.DeptService;
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.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
//提供Restful服務
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/dept/add")//方便此時的測驗,這里用了Get,沒有用Post
public boolean addDept(Dept dept) {
return deptService.addDept(dept);
}
@GetMapping("/dept/get/{id}")
public Dept getDept(@PathVariable("id") Long id){
return deptService.queryById(id);
}
@GetMapping("/dept/list")
public List<Dept> queryAll(){
return deptService.queryAll();
}
}
6.啟動類
package com.zhou.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//啟動類
@SpringBootApplication
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
7.Run 測驗
訪問:http://localhost:8001/dept/list (測驗成功!)
訪問:http://localhost:8001/dept/get/1 (測驗成功!)
訪問:http://localhost:8001/dept/add?dname=地獄部 (測驗成功!)
查詢資料庫

1.創建子模塊 springcloud-consumer-dept-80 (服務的消費者)
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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.zhou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-dept-80</artifactId>
<dependencies>
<!--不需要連接資料庫,需要物體類+web-->
<dependency>
<groupId>com.zhou</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
server:
port: 80
ConfigBean.java
package com.zhou.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration //@Configuration 相當于 spring中 applicationContext.xml
public class ConfigBean {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
RestTemplate 部分原始碼:

DeptConsumerController.java
package com.zhou.springcloud.controller;
import com.zhou.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Controller
public class DeptConsumerController {
//理解:消費者,不應該有 service 層
//RestTemplate.... 供我們直接呼叫就可以了,注冊到Spring中
@Autowired
private RestTemplate restTemplate;
//http://localhost:8081/dept/add?dname=地獄部
private static final String REST_URL_PREFIX="http://localhost:8081";
@RequestMapping("/consumer/dept/add")
@ResponseBody
public boolean add(Dept dept){
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept,Boolean.class);
}
@RequestMapping("/consumer/dept/get/{id}")
@ResponseBody
public Dept get(@PathVariable("id") Long id){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}
@RequestMapping("/consumer/dept/list")
@ResponseBody
public List<Dept> list(){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
}
}
DeptConsumer_80.java 啟動類
package com.zhou.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class,args);
}
}
2.修改:springcloud-provider-dept-8001 它的 DeptController.java類的addDept()方法
(否則,在下面測驗中,使用瀏覽器url傳參的方式來插入資料時,資料庫中顯示dname的值為null)

@PostMapping("dept/add")
public boolean addDept(@RequestBody Dept dept){
return deptService.addDept(dept);
}
3.啟動
首先,啟動服務的提供者 springcloud-provider-dept-8001 的啟動類 DeptProvider_8001
其次,啟動服務的消費者 springcloud-consumer-dept-80 的啟動類 DeptConsumer_80

4.Run 測驗
訪問:http://localhost/consumer/dept/list

訪問:http://localhost/consumer/dept/get/5

訪問:http://localhost/consumer/dept/add?dname=架構師3

查看資料庫

五、Eureka 服務注冊中心
- Eureka 定義
Eureka 是Netflix的一個子模塊,也是核心模塊之一,Eureka是基于REST的服務,用于定位服務,以實作云端中間件層服務發現和故障轉移,服務注冊與發現對于微服務來說是非常重要的,有了服務注冊與發現,只需要使用服務的識別符號,就可以訪問到服務,而不需要修改服務呼叫的組態檔了,功能類似于Dubbo的注冊中心,比如Zookeeper,

在云中,應用程式不能總是知道其他服務的確切位置,一個服務注冊中心,比如Netflix Eureka,或者一個sidecar解決方案,比如HashiCorp Consul,都會有所幫助,springcloud為流行的注冊中心提供DiscoveryClient實作,比如Eureka、Consul、Zookeeper,甚至Kubernetes的內置系統,還有一個springcloud負載均衡器可以幫助您在服務實體之間小心地分配負載,
官方介紹:https://spring.io/projects/spring-cloud-netflix

springcloudnetflix通過自動配置并系結到Spring環境和其他Spring編程模型習慣用法,為Spring啟動應用程式提供Netflix作業系統集成,通過一些簡單的注釋,您可以快速啟用和配置應用程式中的常見模式,并使用經過測驗的Netflix組件構建大型分布式系統,提供的模式包括服務發現(Eureka)、斷路器(Hystrix)、智能路由(Zuul)和客戶端負載平衡(Ribbon)
- Dubbo 和 SpringCloud對比
最大區別:Spring Cloud 拋棄了Dubbo的RPC通信,采用的是基于HTTP的REST方式,
二者解決的問題域不一樣:Dubbo的定位是一款RPC框架,而SpringCloud的目標是微服務架構下的一站式解決方案,
| 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 |
嚴格來說,這兩種方式各有優劣,雖然從一定程度來說,SpringCloud犧牲了服務呼叫的性能,但也避免了上面提到的原生RPC帶來的問題,而且REST相比RPC更為靈活,服務提供方和呼叫方的依賴只依靠一紙契約,不存在代碼級別的強依賴,這個優點在當下強調快速演化的微服務環境下,顯得更加合適,
- Eureka基本的架構
1.Springcloud 封裝了Netflix公司開發的Eureka模塊來實作服務注冊與發現 (對比Zookeeper).
2.Eureka采用了C-S的架構設計,EurekaServer作為服務注冊功能的服務器,他是服務注冊中心.
3.系統中的其他微服務,使用Eureka的客戶端連接到EurekaServer并維持心跳連接,(方便監控系統中各個微服務是否正常運行)

- 與Dubbo架構對比

- 構建 Eureka 代碼示例
1.創建子模塊 springcloud-eureka-7001
pom 依賴
<artifactId>springcloud-eureka-7001</artifactId>
<!--匯入依賴-->
<dependencies>
<!--spring-cloud-starter-netflix-eureka-server 依賴-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<!--熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
application.yaml
server:
port: 7001
# Eureka 部署
eureka:
instance:
# Eureka服務端的實體名字
hostname: localhost
client:
# 表示是否向 Eureka 注冊中心注冊自己(這個模塊本事是服務器,所以不需要)
register-with-eureka: false
# fetch-registry 如果為 false,則表示自己為注冊中心,客戶端的為 true
fetch-registry: false
# Eureka 監控頁面
service-url:
#public static final String DEFAULT_URL = "http://localhost:8761/eureka/";
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
啟動類 EurekaServer_7001.java
package com.zhou.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @Auther: zhouzhou
* @Description: 啟動之后,訪問 http://127.0.0.1:7001/
*/
@SpringBootApplication
@EnableEurekaServer //服務端的啟動類,可以接受別人注冊進來
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
Run 測驗
報錯資訊:Caused by: java.lang.ClassNotFoundException: org.springframework.boot.context.properties.ConfigurationPropertiesBean

觀察報錯資訊,得出有可能是版本沖突問題,解決辦法是將 spring-cloud-starter-netflix-eureka-server 依賴的版本降為2.1.4.RELEASE
【注意】:報錯原因是版本問題,可以選擇到官網查看版本是否一致,比如 SPRINGCLOUD的版本,我的父依賴用的是GREENWICH.SR1
你如果用了HOXTON.SR10 甚至更新的,請自行查找對應的版本依賴,
繼續測驗,Run 成功!

訪問:http://localhost:7001/

- Eureka 服務注冊 資訊配置以及自我保護機制
配置 Eureka-client
1.在上面創建的子模塊 springlouc-provider-dept-8001 的 pom 中添加依賴
<!--Eureka:spring-cloud-starter-netflix-eureka-client 依賴-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
2.application.yaml 中添加設定
# Eureka配置:配置注冊中心地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
3.啟動類中使用 @EnableEurekaClient注解
package com.zhou.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
//啟動類
@SpringBootApplication
@EnableEurekaClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
4.先啟動7001服務端,再啟動8001客戶端進行測驗,訪問監控頁:http://localhost:7001/ 產看結果如圖,成功

5.修改 Eureka 上的默認描述資訊
# Eureka配置:配置注冊中心地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: springcloud-provider-dept8001 # 修改eureka上的默認描述資訊
prefer-ip-address: true
查看:

6.配置關于服務加載的監控資訊(springcloud-provider-dept-8001)
沒配置之前,訪問:springcloud-provider-dept8001

跳出的頁面如下:

配置服務加載的監控資訊步驟如下:
pom.xml中添加依賴
<!--actuator完善監控資訊-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yaml 中添加配置
# info配置
info:
# 專案名稱
app.name: zhouzhou-springcloud
# 公司名稱
company.name: blog.zhouzhou.com
7.Run 再重繪頁面,繼續訪問:springcloud-provider-dept8001,跳出頁面如下

- EureKa自我保護機制
默認情況下,如果Eureka Server在90秒內沒有接收到某個微服務實體的心跳,會注銷該實體,但是在微服務架構下服務之間通常都是跨行程呼叫,網路通信往往面臨很多問題,比如微服務狀態正常,網路磁區故障,導致此實體被注銷,
固定時間內大量實體被注銷,可能會嚴重威脅某個微服務架構的可用性,為了解決這個問題,Eureka開發了自我保護機制,
Eureka Server在運行期間會去統計心跳失敗比例在15分鐘之內是否低于85%,如果低于85%,Eureka Server即進入自我保護機制,
Eureka Server觸發自我保護機制后,頁面會出現提示:

Eureka Server進入自我保護機制,會出現以下幾種情況:
(1)Eureka不再從注冊串列中移除因為長時間沒收到心跳而應該過期的服務;
(2)Eureka仍然能夠接受新服務的注冊和查詢,但是不會被同步到其它節點上(即保證當前節點依然可用);
(3)當網路穩定后,當前實體新的注冊資訊會被同步到其它節點上;
Eureka自我保護機制是為了防止誤殺服務而提供的一種機制,當個別客戶端出現心跳失聯時,則認為是客戶端的問題,剔除客戶端;當Eureka 捕獲到大量的心跳失敗時,則認為可能是網路問題,進入自我保護機制;當客戶端心跳恢復時,Eureka會自動退出自我保護機制,
如果在保護期內剛好這個服務提供者非正常下線了,此時服務消費者就會拿到一無效的服務實體,則會呼叫失敗,對于這個問題需要服務消費者要有一些容錯機制,比如重試,斷路器等,
- 注冊進來的微服務,獲取其中的一些資訊(團隊開發)
1.查看 EurekaDiscoveryClient 原始碼:

2.觀察 DiscoveryClient 原始碼:

3.在 springcloud-provider-dept-8001 的 DeptController.java中添加 discovery() 方法
//DiscoveryClient 可以用來獲取一些配置的資訊,得到具體的微服務
@Autowired
private DiscoveryClient discoveryClient;
/**
* 獲取一些注冊進來的微服務的資訊
* @return
*/
@GetMapping("dept/discovery")
public Object discovery(){
//獲取微服務串列清單
System.out.println("getServices()=>"+discoveryClient.getServices());
System.out.println("description()=>"+discoveryClient.description());
List<ServiceInstance> instances = discoveryClient.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance instance : instances) {
System.out.println(
instance.getHost()+"\t"+ //主機名稱
instance.getPort()+"\t"+ //埠號
instance.getUri()+"\t"+ //uri
instance.getInstanceId() //服務id
);
}
return this.discoveryClient;
}
4.上面 discoveryClient.getInstances()的引數 ---> SPRINGCLOUD-PROVIDER-DEPT

5.主啟動類中加入 @EnableDiscoveryClient 注解
package com.zhou.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
//啟動類
@SpringBootApplication
//@EnableEurekaClient 開啟Eureka客戶端注解,在服務啟動后自動向注冊中心注冊服務
@EnableEurekaClient
//@EnableDiscoveryClient 開啟服務發現客戶端的注解,可以用來獲取一些配置的資訊,得到具體的微服務
@EnableDiscoveryClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
6.Run 測驗
先啟動 springcloud-eureka-7001 中的住啟動類 EurekaServer_7001,
再啟動 springcloud-provider-dept-8001 中的主啟動類 DeptProvider_8001,
訪問:http://localhost:7001/ 一切正常

繼續訪問:http://localhost:8001/dept/discovery

springcloud-provider-dept-8001 控制臺輸出:

- Eureka:集群環境配置
整體結構如下:

1.新建 子模塊springcloud-eureka-7002 和 springcloud-eureka-7003
2.添加 pom 依賴 (與springcloud-eureka-7001相同)
3.application.yml配置 (與springcloud-eureka-7001相同)
(埠號用各自的 7001、7002和7003)
4.主啟動類 (與springcloud-eureka-7001相同)
5.集群成員相互關聯
配置一些自定義本機名字,在C:\Windows\System32\drivers\etc找到本機hosts檔案,在hosts檔案最后加上,要訪問的本機名稱(默認是localhost)

127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
【注意】:修改hosts檔案后一定要保存,如果遇到 修改hosts檔案無權限的問題,參考下圖配置:

6.修改 各自的 application.yml 的配置
(1)設定各自的 服務端的實體名字(hostname)
(2)設定各自的 集群(關聯)
springcloud-eureka-7001 的 application.yaml
server:
port: 7001
# Eureka 部署
eureka:
instance:
# Eureka服務端的實體名字
hostname: eureka7001.com
client:
# 表示是否向 Eureka 注冊中心注冊自己(這個模塊本事是服務器,所以不需要)
register-with-eureka: false
# fetch-registry 如果為 false,則表示自己為注冊中心,客戶端的為 true
fetch-registry: false
# Eureka 監控頁面
service-url:
# public static final String DEFAULT_URL = "http://localhost:8761/eureka/";
# 單機:defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群(關聯):7001關聯 7002、7003
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
springcloud-eureka-7002 的 application.yaml
server:
port: 7002
eureka:
instance:
# Eureka服務端的實體名字
hostname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
# 集群(關聯):7002關聯 7001、7003
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
springcloud-eureka-7003 的 application.yaml
server:
port: 7003
eureka:
instance:
# Eureka服務端的實體名字
hostname: eureka7003.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
# 集群(關聯):7003關聯 7001、7002
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
7.通過 springcloud-provider-dept-8001 的yaml組態檔,修改 Eureka 的配置:配置服務注冊中心地址

# Eureka配置:配置注冊中心地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: springcloud-provider-dept8001 # 修改eureka上的默認描述資訊
prefer-ip-address: true
8.模擬集群搭建完畢,(可以把一個專案掛載到三個服務器上了)
測驗,訪問:http://localhost:7001/

測驗,訪問:http://localhost:7002/

測驗,訪問:http://localhost: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就認為客戶端與注冊中心出現了網路故障,此時會出現以下幾種情況:
(1)Eureka不在從注冊串列中移除因為長時間沒收到心跳而應該過期的服務
(2)Eureka仍然能夠接受新服務的注冊和查詢請求,但是不會被同步到其他節點上 (即保證當前節點依然可用)
(3)當網路穩定時,當前實體新的注冊資訊會被同步到其他節點中
因此,Eureka可以很好的應對因網路故障導致部分節點失去聯系的情況,而不會像zookeeper那樣使整個注冊服務癱瘓,
六、Ribbon:負載均衡(基于客戶端)
七、Feign:負載均衡(基于服務端)
八、Hystrix:服務熔斷
九、Zull路由網關
十、Spring Cloud Config 分布式配置
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/275309.html
標籤:Java
上一篇:SpringCloudAlibaba—微服務概念及SpringCloudAlibaba介紹
下一篇:訪問“ for”回圈中的索引?
