學習之前,確保有以下知識基礎:
- Java網絡編程
- Socket傳輸數據
- IO流
rpc簡介及實現
rpc是remote procedure call的簡寫,意思為遠程過程調用。
rpc應用較多的情景是分布式開發,那什么是分布式開發呢?
原本我也是想自己解釋的,奈何網上大佬解釋得很清楚了,這里就不獻丑了,建議閱讀完下面推薦的幾篇再繼續往下
剛開始的時候,服務和調用都是在同一機器,這叫本地過程調用
之后,由於客戶量增長,一個服務器並不能滿足要求,之后便是把調用和服務分開,分別部署在不同的機器,負責調用服務方法的稱之為客戶機,負責提供服務方法的稱為服務機
上圖的原理可能步驟有點多,但是只要記住一點,客戶機是把數據通過Socket或者是其他協議傳遞到了服務機,讓服務機進行處理,從而以相同的協議方式把數據傳遞回來
如何實現一個簡單的RPC一文中,大佬已經實現了一個簡潔的rpc框架,然后對於這個rpc框架,並提出來的一些可以優化的點:
1. 缺乏通用性
我通過給Calculator接口寫了一個CalculatorRemoteImpl,來實現計算器的遠程調用,下一次要是有別的接口需要遠程調用,是不是又得再寫對應的遠程調用實現類?這肯定是很不方便的。
2、集成Spring
在實現了代理對象通用化之后,下一步就可以考慮集成Spring的IOC功能了,通過Spring來創建代理對象,這一點就需要對Spring的bean初始化有一定掌握了。
3、長連接or短連接
總不能每次要調用RPC接口時都去開啟一個Socket建立連接吧?是不是可以保持若干個長連接,然后每次有rpc請求時,把請求放到任務隊列中,然后由線程池去消費執行?只是一個思路,后續可以參考一下Dubbo是如何實現的。
4、 服務端線程池
我們現在的Server端,是單線程的,每次都要等一個請求處理完,才能去accept另一個socket的連接,這樣性能肯定很差,是不是可以通過一個線程池,來實現同時處理多個RPC請求?同樣只是一個思路。
5、服務注冊中心
正如之前提到的,要調用服務,首先你需要一個服務注冊中心,告訴你對方服務都有哪些實例。Dubbo的服務注冊中心是可以配置的,官方推薦使用Zookeeper。如果使用Zookeeper的話,要怎樣往上面注冊實例,又要怎樣獲取實例,這些都是要實現的。
6、負載均衡
如何從多個實例里挑選一個出來,進行調用,這就要用到負載均衡了。負載均衡的策略肯定不只一種,要怎樣把策略做成可配置的?又要如何實現這些策略?同樣可以參考Dubbo,Dubbo - 負載均衡
7、結果緩存
每次調用查詢接口時都要真的去Server端查詢嗎?是不是要考慮一下支持緩存?
8、多版本控制
服務端接口修改了,舊的接口怎么辦?
9、異步調用
客戶端調用完接口之后,不想等待服務端返回,想去干點別的事,可以支持不?
10、優雅停機
服務端要停機了,還沒處理完的請求,怎么辦?
PS:使用rpc的時候,需要考慮到網絡問題,需要采用重試機制
由上述的這些問題,之后便是出現了一些優秀的rpc框架,如dubbo、spring cloud等
dubbo簡介
Dubbo是阿里巴巴開源的基於 Java 的高性能 RPC(遠程過程調用) 分布式服務框架(SOA),致力於提供高性能和透明化的RPC遠程服務調用方案,以及SOA服務治理方案,其內部使用了 Netty、Zookeeper,保證了高性能高可用性。
dubbo結構圖:
節點 | 角色說明 |
---|---|
Provider | 暴露服務的服務提供方 |
Consumer | 調用遠程服務的服務消費方 |
Registry | 服務注冊與發現的注冊中心 |
Monitor | 統計服務的調用次數和調用時間的監控中心 |
Container | 服務運行容器 |
其中,注冊中心Registry
和監控中心Monitor
都是可選的,所以,我們下文先簡單實現dubbo(點對點傳輸數據)
<dependency>
<groupId>com.starsone</groupId>
<artifactId>dubbo-api</artifactId>
<version>0.0.1</version>
</dependency>
dubbo簡單實現
項目說明:
項目基於spring boot,分為三個部分,api
,consumer
和provider
- api主要是用來聲明一些服務的接口(maven項目)
- provider則是對服務接口的具體實現(spring boot項目,通過maven依賴api項目)
- consumer則是遠程調用provider提供的服務接口(spring boot項目,通過maven依賴api),
本質上consumer相當於客戶機,而provider相當於客戶機
1.新建項目
使用IDEA,建立一個空白的項目,之后新建module
之后IDEA會彈出一個新建module的窗口
依次新建api、provider、cousmer三個module
api選擇maven項目,之后填寫相關的包名信息直接新建即可(不需要選擇具體的maven結構),而另外兩個則是spring boot項目,選擇spring initializr新建即可,同樣,不要勾選其他的依賴,填寫好包名相關信息新建即可
2.api項目聲明服務接口
在api項目中,我們新建一個CalculatorService接口,里面定義一個add的方法
public interface CalculatorService {
int add(int a,int b);
}
3.配置api項目的依賴
原本,之后的provider和consumer項目都是需要引用dubbo-spring-boot-starter這個依賴,dubbo-spring-boot-starter依賴中已經包含了dubbo依賴,這樣就可以不需要寫dubbo的依賴了
由於之后我們的provider和consumer項目都是需要引用api這個項目,所以,我們可以把provider和consumer所需要的依賴dubbo-spring-boot-starter
添加到api這個項目中
之后的provider和consumer項目也就是依賴了api項目,也成功依賴了dubbo-spring-boot-starter
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.5</version>
</dependency>
4.provider和consumer項目引用api項目依賴
由於之前我們創建的api項目是maven項目,所以添加依賴就很簡單,在provider和consumer各自的pom.xml添加依賴即可
5.provider實現api中的服務接口
我們在provider項目中新建一個類CalculatorServiceImpl
,去實現CalculatorService
接口
@Service(interfaceName = "calculatorService")
class CalculatorServiceImpl:CalculatorService {
override fun add(a: Int, b: Int): Int {
val result = a+b
println("$a+$b=$result")
return result
}
}
注意,這里的Service注解是dubbo包里面的注解,而不是spring中的Service,定義接口名interfaceName
為calculatorService
,方便之后容器進行查找
6.配置provider項目
我們需要修改spring boot的配置文件,這里我使用yml的形式進行配置,閱讀比較舒適
spring:
application:
name: dubbo-provider-application
dubbo:
scan:
#掃描指定包是否包含有dubbo中Service注解的類
base-packages: com.starsone.provider.service
protocol:
name: dubbo #協議,默認為dubbo(其他協議webserovice、Thrift、Hessain、http)
port: 12345 #端口,默認為20880
registry:
address: N/A #不需要注冊中心
PS:如果不想在配置文件制定掃描包含有Service注解的類,可以在provider項目中的application類中添加開啟dubbo自動掃描的注解@EnableDubbo
provider項目結構圖:
7.運行provider
由於我們沒有引入注冊中心,所以得先運行provider,獲得ip地址
之后consumer項目中,才能讓springr容器去根據ip地址+端口號去找到對應的實例並自動裝載
由輸出日志,我們可以看到ip地址
8.consumer獲得service對象
@Component
class MyRunner:ApplicationRunner {
@Reference(url ="dubbo://192.168.52.1:12345",interfaceName = "calculatorService" )
private lateinit var calculatorService: CalculatorService
override fun run(args: ApplicationArguments?) {
println(calculatorService.add(5,14))
}
}
這里,由於是為了簡單考慮,沒有使用web依賴,所以,使用了ApplicationRunner這個接口進行測試,spring容器在加載完成會自動回調此接口
Reference注解是dubbo中的注解,consumer項目運行之后,consumer中的dubbo就會根據此url和一些其他的信息進行數據的傳遞,遠程調用provider中的服務,之后,provider接收數據並進行處理,返回數據給consumer,是不是有了rpc的感覺?
9.配置consumer及測試
配置的話,只配置了應用的名稱
之后,我們運行consumer的application,可以看到結果
同樣,在provider項目,也是打印出了consumer項目傳遞過來的參數
引入注冊中心
前面的實現,是沒有注冊中心的,屬於一種直連的方式,但是,實際上,分布式開發,具有多台服務機
客戶機應該是向注冊中心請求,由注冊中心查詢當前空閑的服務機,並根據某種策略,選擇其中一台服務機,將其ip地址返回給客戶機,之后客戶機通過ip地址,與該服務機進行連接,進行rpc操作
dubbo框架中,推薦使用ZooKeeper作為注冊中心
ZooKeeper是一個分布式的,開放源碼的分布式應用程序協調服務,是Google的Chubby一個開源的實現,是Hadoop和Hbase的重要組件。它是一個為分布式應用提供一致性服務的軟件,提供的功能包括:配置維護、域名服務、分布式同步、組服務等。
1.下載zookeeper
http://mirror.bit.edu.cn/apache/zookeeper/
注意,這里下載的版本最好與項目中的依賴版本一致
2.導入zookeeper依賴
我們需要修改api項目中的依賴,這樣provider和consumer兩個項目的依賴也是得以修改
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Zookeeper客戶端 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
3.配置provider和consumer的注冊中心
provider:
consumer:
4.取消consumer指定url
由於我們使用的是注冊中心,所以,不需要指定url了,把Reference注解中的url刪掉
5.運行zookeeper
解壓下載的zookeeper壓縮包,進入到conf目錄,把zoo_sample.cfg
文件改為zoo.cfg
進入bin目錄,點擊zkserver.cmd
文件,運行zookeeper
6.運行provider和consumer
先運行provider,之后運行consumer,可以看到結果
本篇文章也是折騰了幾天,參考了十幾篇文章,一步步測試才弄成功,有些知識點並沒有太深入,像dubbo控制台、監控中心等如何搭建,后期學習的時候再進行補充說明吧