服務演變之路
單體應用架構
在剛開始的時候,企業的使用者量、資料量規模都⽐較⼩,項⽬所有的功能模組都放在⼀個⼯程中編碼、編譯、打包並且部署在⼀個Tomcat容器中的架構模式就是單體應用架構,這樣的架構既簡單實用、便於維護,成本⼜低,成為了那個時代的主流架構⽅式。這時候由於業務以及規模都⽐較⼩,所以⽆論服務以及DB都是使⽤單節點(all-in-one)的⽅式進⾏部署,這就是單體架構。
優點:
1、項⽬前期開發節奏快,團隊成員少的時候能夠快速迭代
2、架構簡單:MVC架構,只需要藉助IDE開發、除錯即可
3、易於測試:只需要通過單元測試或者瀏覽器完成
4、易於部署:打包成單⼀可執行的jar或者打成war包放到容器內啟動
缺點:
1、隨著時間推移業務增加,功能不斷迭代,項⽬會不斷變得臃腫,業務耦合嚴重。
2、新增業務困難:在已經亂如麻的系統中增加新業務,維護成本高,新⼈來了以後很難接手任務,學習成本高。
3、核⼼業務與邊緣業務混合在⼀塊,出現問題互相影響
垂直應用架構
為了避免單體架構上出現的那些問題,開始對應⽤按照業務做垂直劃分,把原來的的⼀個單體架構拆成⼀堆單體應⽤,這時候就由原來的單應⽤變成了多應⽤部署,這就是垂直架構
優點:
1、可以針對不同模組進行優化。
2、⽅便⽔平擴充套件,負載均衡,容錯率提高。
3、系統間相互獨⽴,互不影響,新的業務迭代時更加⾼效。
缺點:
1、服務之間相互呼叫,如果某個服務的端⼝或者ip地址發⽣改變,呼叫的系統得手動改變。
2、搭建叢集之後,實現負載均衡⽐較複雜,如:內網負載,在遷移機器時會影響呼叫⽅的路由,導致線上故障。
3、服務之間呼叫⽅式不統⼀,基於 httpclient 、 webservice ,接⼝協議不統⼀。
4、服務監控不到位:除了依靠埠、程式的監控,呼叫的成功率、失敗率、總耗時等等這些監控指標是沒有的。
SOA架構
SOA (Service-Oriented Architecture),即⾯向服務的架構。其思想就是根據實際業務,把系統拆分成合適的、獨⽴部署的模組,模組之間相互獨⽴(通過Webservice/Dubbo等技術進⾏通訊)。因此衍⽣出了⼀系列相應的技術,如對服務提供、服務調⽤、連線處理、通訊協議、序列化方式、服務發現、服務路由、⽇志輸出等⾏為進⾏封裝的服務框架。
SOA主要解決的問題
1> 資訊孤島。
2> 共享業務的重用
微服務
那麼被SOA拆分出來的服務是否也需要以業務功能為維度來進行拆分和獨立部署,以降低業務的耦合及提升容錯性?微服務就是這樣一種解決方案。
我們可以把SOA看成微服務的超集,也就是多個微服務可以組成一個SOA服務。
伴隨著服務顆粒化的細化,會導致原本10個服務可能拆分成了100個微服務,一旦服務規模擴大,就意味著服務的構建、釋出、運維的複雜度也會成倍增加。
優點:
1> 每個服務足夠小,足夠內聚,專注於一個業務功能點提供服務。程式碼更容易理解。
2> 有程式碼修改或部署上線,只會影響對應的微服務,而不會是整個服務。
3> 可針對服務是計算型還是IO型進行鍼對性的硬體升級。
4> 可以針對某些高吞吐服務進行硬體升級或者服務橫向擴容,而不是對所有服務都升級,節約投入成本。
缺點:
1> 極大的增加了運維工作量,以前幾個war包,現在可能需要部署幾百個。
2> 微服務之間的互相呼叫,會增加通訊成本。
3> 分散式事務問題會引出資料一致性的問題。
4> 服務增多,如果管控成百上千的服務。如何準確並快速定位問題。
SOA與微服務的區別
SOA關注的是服務的重用性及解決資訊孤島問題。
微服務關注的是解耦,雖然解耦和可重用性從特定的角度來看是一樣的,但本質上是有區別的;
解耦:降低業務之間的耦合度。
重用性:關注的是服務的複用。
微服務會更多地關注在DevOps的持續交付上,因為服務粒度細化之後使得開發運維變得更加重要,因此微服務與容器化技術的結合更加緊密。
服務發現介紹
服務治理的理念
-
在傳統的系統部署中,服務運⾏在⼀個固定的已知的IP和端 ⼝上,如果⼀個服務需要調⽤另⼀個服務,那麼可以通過地 址直接調⽤。但是,在虛擬化或者容器化的環境中,服務例項的啟動和銷燬是很頻繁的,那麼服務地址也是在動態變化 的。這種情況下,就需要服務發現機制了。
-
服務發現有兩種:
- 基於客戶端的服務發現
客戶端通過查詢服務註冊中心,獲取可用服務的實際網路地址(IP&PORT)。然後通過負載均衡演算法來選擇一個可用的服務例項,並將請求傳送至該服務。
在服務啟動的時候,向服務註冊中心註冊服務;在服務停止的時候,向服務註冊中心登出服務。服務註冊的一個典型實現方式就是通過heartbeat機制(心跳機制)定時重新整理
- 基於服務端的服務發現
客戶端向load balancer上傳送請求。load balancer查詢服務註冊中心,找到可用的服務,然後轉發請求到該服務上。和客戶端發現一樣,服務都要到註冊中心進行服務的註冊和銷燬。
服務發現呼叫流程
客戶端服務發現
優點:客端知道所有的服務提供者ip,可以根據⾃⼰的業務情況⾮常⽅便的實現負載均衡。
缺點:耦合性太強,不同語⾔的客戶端都需要⾃⼰實現⼀套負載均衡。
服務端服務發現
優點:服務的發現邏輯對客戶端透明,客戶端⽆需關注服務負載均衡,直接發起呼叫即可。
缺點:服務端需要關注LB的高可用。
服務發現技術對比
Nacos實戰
Nacos介紹
Nacos是阿里的一個開源產品,它是針對微服務架構中的服務發現、配置管理、服務治理的綜合型解決方案。
官方介紹如下:
致力於幫助您發現、配置和管理微服務。
提供了一組簡單易用的特性集,幫助您快速實現動態服務發現、服務配置、服務後設資料及流量管理。
幫助您更敏捷和容易地構建、交付和管理微服務平臺。
是構建以“服務”為中心的現代應用架構 (例如微服務正規化、雲原生正規化) 的服務基礎設施。
Nacos特性
-
服務發現與服務健康檢查
Nacos使服務更容易註冊,並通過DNS或HTTP介面發現其他服務;
Nacos還提供服務的實時健康檢查,以防止向不健康的主機或服務例項傳送請求。
- 動態配置服務
動態配置服務執行在所有環境中以集中和動態的方式管理所有服務的配置。
Nacos消除了在更新配置時重新部署應用程式,這使配置的更改更加高效和靈活。
-
動態DNS服務
Nacos提供基於DNS協議的服務發現能力,旨在支援異構語言的服務發現;
支援將註冊在Nacos上的服務以域名的方式暴露端點,讓三方應用方便查閱及發現。
-
服務和後設資料管理
Nacos能讓您從微服務平臺建設的視角管理資料中心的所有服務及後設資料(即:服務相關的一些配置和狀態資訊)
包括:管理服務的描述、生命週期、服務的靜態依賴分析、服務的健康狀態、服務的流量管理、路由及安全策略。
Nacos架構圖
【注】
Provider APP :服務提供者
Consumer APP :服務消費者
Name Server:通過VIP(Vritual IP)或者DNS的方式實現Nacos高可用叢集的服務路由。
Nacos Server :Nacos服務提供者,裡面包含:
- Open API:功能訪問入口。
- Config Service:配置服務模組。在服務或者應用執行過程中,提供動態配置或者後設資料以及配置管理的服務提供者。
- Naming Service:名字服務模組。提供分散式系統中所有物件(Object)、實體(Entity)的“名字”到關聯的後設資料之間的對映管理服務,服務發現和DNS就是名字服務的2大場景。
- Consistency Protocol:一致性協議,用來實現Nacos叢集節點的資料同步。使用Raft演算法(使用類似演算法的中介軟體還有Etcd、Redis哨兵選舉等)。
- Nacos Console:Nacos的控制檯。
Nacos快速入門
-
SpringCloud常見的整合方案
【注】
- Ribbon:基於客戶端的負載均衡。
- Feign:可以幫我們更快捷、優雅地呼叫HTTP API。將HTTP報文請求方式偽裝為簡單的java介面呼叫方式。
-
搭建Nacos服務端
請參見【配置中心Nacos】中Nacos的安裝步驟
Nocos快速開始
- 新增父類pom依賴
<properties>
<java.version>1.8</java.version>
<spring.boot>2.1.3.RELEASE</spring.boot>
<spring.cloud>Greenwich.RELEASE</spring.cloud>
<spring.cloud.alibaba>2.1.0.RELEASE</spring.cloud.alibaba>
</properties>
<dependencyManagement>
<dependencies>
<!-- 引入Spring Cloud Alibaba依賴 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 引入Spring Cloud依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 引入Spring Boot依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 專案主體結構
nacos-provider相關程式碼
主pom
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
- nacos-provider-web
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
yaml
server:
port: 7000
spring:
application:
name: nacos-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
NacosProviderApplication
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class NacosProviderApplication {
public static void main(String[] args) {
SpringApplication.run(NacosProviderApplication.class, args);
}
}
ProviderController
@Slf4j
@RestController
@RequestMapping("/provider")
public class ProviderController {
@Resource
private ProviderService providerService;
@RequestMapping("/hello")
public String hello() {
log.info("客戶端hello");
return providerService.hello();
}
}
providerService
@FeignClient(name = "nacos-consumer")
public interface ProviderService {
@GetMapping("/consumer/hello")
String hello();
}
nacos-consumer相關程式碼
主pom
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
- nacos-consumer-web
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
ymal
server:
port: 7002
spring:
application:
name: nacos-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
NacosConsumerApplication
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class NacosConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(NacosConsumerApplication.class, args);
}
}
ConsumerController
@Slf4j
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Resource
private ProviderService providerService;
/**
* 基於Feign呼叫
* http://localhost:7002/consumer/hello
*/
@GetMapping("/hello")
public String hello() {
log.info("Feign invoke!");
return providerService.hello();
}
}