分佈式網絡通信框架Netty
基於Netty+Zookeeper純手寫RPC遠程調用框架
1 Dubbo服務器-服務註冊實現
田超凡
2019年11月2日
轉載請註明原作者
1 Dubbo實現原理
什麼是Dubbo
是一款高性能、輕量級的開源Java RPC框架,它提供了三大核心能力:面向接口的遠程方法調用,智能容錯和負載均衡,以及服務自動註冊和發現,基於Zookeeper作爲註冊中心實現服務治理等。
Dubbo官網:http://dubbo.apache.org/zh-cn/
Dubbo底層實現原理
服務提供者(Provider):暴露服務的服務提供方,服務提供者在啓動時,向註冊中心註冊自己提供的服務。
服務消費者(Consumer): 調用遠程服務的服務消費方,服務消費者在啓動時,向註冊中心訂閱自己所需的服務,服務消費者,從提供者地址列表中,基於軟負載均衡算法,選一臺提供者進行調用,如果調用失敗,再選另一臺調用。
註冊中心(Registry):註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者
監控中心(Monitor):服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心
完全手寫Dubbo需要用到的相關技術知識
RPC遠程調用的概念和實現原理,主流RPC遠程調用框架:
dubbo、dubbox、HttpClient、grpc、Feign和REST、AlibabaCloud(Naxio)
Zookeeper註冊中心實現原理
Netty實現dubbo生產者和消費者(服務器和客戶端),基於TCP長連接實現RPC遠程調用
多線程、網絡編程基礎、Socket網絡通信、IO處理模型(BIO/NIO/AIO/IOM/ROM)
觀察者模式、策略模式優化代碼實現細節,提高複用性和擴展性
TCP粘包拆包、網絡傳輸對象數據序列化和反序列化(JSON/MessagePack/Netty編碼解碼器/Google ProtoBuf)
Java反射和類加載器
快速搭建Dubbo+Zookeeper基本環境的步驟
創建dubbo生產者Provider
創建dubbo消費者Consumber
啓動zookeeper註冊中心,連接dubbo服務
實現服務註冊和發現,實現RPC遠程調用
Dubbo整體架構實現原理分析
Provider生產者,發佈註冊服務到zookeeper註冊中心
Consumer消費者,通過觀察者模式訂閱zookeeper註冊中心,獲取zookeeper註冊中心的服務,實現服務調用
Registry註冊中心,主要使用zookeeper註冊中心,實現服務註冊與服務治理
Monitor監控中心,監控服務調用次數和失敗錯誤信息
基於Netty+Zookeeper手寫RPC遠程調用框架,實現服務註冊
搭建Dubbo Provider - Netty服務器端
搭建Dubbo Consumer - Netty客戶端
搭建Registration - 基於反射實現服務註冊和訪問地址拼接,註冊到zookeeper
Dubbo支持那些協議
Dubbo支持dubbo、rmi、hessian、http、webservice、thrift、redis等多種協議,但是Dubbo官網是推薦我們使用Dubbo協議的。
2 Dubbo環境搭建
模塊劃分:
member_provider_api --- 會員服務提供公共api
member_provider_impl--- 會員服務提供公共實現
member_impl_consumer---訂單服務消費者
API接口
public interface UserService { String getUserName(Long userId); }
|
生產者
public class UserServiceImpl implements UserService { public String getUserName(Long userId) { return "mayikt"; } }
|
生產者配置文件 provider.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--定義了提供方應用信息,用於計算依賴關係;在 dubbo-admin 或 dubbo-monitor 會顯示這個名字,方便辨識 --> <dubbo:application name="mayiktp"/> <!--使用 zookeeper 註冊中心暴露服務,注意要先開啓 zookeeper --> <dubbo:registry address="zookeeper://localhost:2181"/> <!-- 用dubbo協議在20880端口暴露服務 --> <dubbo:protocol name="dubbo" port="20880"/> <!--使用 dubbo 協議實現定義好的 api.PermissionService 接口 --> <dubbo:service interface="com.mayikt.api.service.UserService" ref="userService" protocol="dubbo"/> <!--具體實現該接口的 bean --> <bean id="userService" class="com.mayikt.api.service.impl.UserServiceImpl"/> </beans>
|
啓動生產者
public class Provider {
public static void main(String[] args) throws Exception { System.setProperty("java.net.preferIPv4Stack", "true"); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"provider.xml"}); context.start(); System.out.println("Provider started."); System.in.read(); // press any key to exit } }
|
消費者
public class Consumer { public static void main(String[] args) throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"consumer.xml"}); context.start(); //獲取遠程服務代理 UserService userService = (UserService) context.getBean("userService"); // 執行遠程方法 String userName = userService.getUserName(1L); // 現實調用結果 System.out.println(userName); } }
|
消費者配置文件:consumer.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <dubbo:application name="order-consumer"/> <!--向 zookeeper 訂閱 provider 的地址,由 zookeeper 定時推送 --> <dubbo:registry address="zookeeper://localhost:2181"/> <!--使用 dubbo 協議調用定義好的 api.PermissionService 接口 --> <dubbo:reference id="userService" interface="com.mayikt.api.service.UserService"/> </beans>
|
3 基於Netty+Zookeeper手寫Dubbo服務器
3.1 手寫Dubbo整體步驟流程
將dubbo-admin放入到tomcat webapps目錄下 運行 修改dubbo.properties zk連接地址即可。
3.2 手寫Dubbo整體步驟流程
創建Netty工程
netty-dubbo-common--- Netty服務器端
----自定義@RpcAnnotation
--- 服務註冊與發現ServiceRegistration
--- rpc MayiktRpcServer
netty-dubbo-client ---Netty客戶端
member-service-provider-api ----生產者
member-provider-impl ----消費者
netty-dubbo-server
RpcAnnotation
@Documented @Inherited @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RpcAnnotation { Class value(); }
|
ServiceRegistration
public interface ServiceRegistration {
/** * 服務註冊 * * @param serverName * @param serviceClassAddres */ void regist(String serverName, String serviceClassAddres); }
public class ServiceRegistrationImpl implements ServiceRegistration {
/** * zk連接地址 */ private final String zkServers = "127.0.0.1";
/** * 會話時間 */ private final int connectionTimeout = 5000;
/*** * zkClient */ private ZkClient zkClient;
/** * dubbo服務註冊根路徑名稱 */ private String serviceParentName = "/mayikt_dubbo";
/** * 註冊我們的providers */ private String providers = serviceParentName + "/providers";
public ServiceRegistrationImpl() {
// 初始化zk連接 zkClient = new ZkClient(zkServers, connectionTimeout); }
public void regist(String serverName, String serviceClassAddres) {
//在zk上創建dubbo節點 /dubbo if (!zkClient.exists(serviceParentName)) {
// 創建持久根節點 zkClient.createPersistent(serviceParentName); }
// 註冊我們的服務接口 /dubbo/com.mayikt.service.api.UserService String zkserviceClassAddresPath = serviceParentName + "/" + serverName;
if (!zkClient.exists(zkserviceClassAddresPath)) {
zkClient.createPersistent(zkserviceClassAddresPath); }
// 註冊我們的服務地址 /dubbo/com.mayikt.service.api.UserService/providers String providersNodePath = zkserviceClassAddresPath + "/providers";
if (!zkClient.exists(providersNodePath)) {
zkClient.createPersistent(providersNodePath); }
// 在providersNodePath下創建我們的服務發佈的地址 需要將url實現轉移 String serviceNodePath = providersNodePath + "/" + URLEncoder.encode(serviceClassAddres);
if (zkClient.exists(serviceNodePath)) {
// 將該節點刪除 zkClient.delete(serviceNodePath); }
// 創建我們的臨時節點 zkClient.createEphemeral(serviceNodePath);
} }
|
RpcServer
public class RpcServer{
/** * 註冊服務 */ private ServiceRegistration serviceRegistration;
/** * 服務註冊端口號 */ private int port;
/** * 服務地址 */ private String host;
public MayiktRpcServer(String host, int port) {
serviceRegistration = new ServiceRegistrationImpl();
this.host = host;
this.port = port; }
public void start(Object services) { System.out.println("services:" + services);
// 綁定服務 bindService(services);
//發佈服務 releaseService(); }
/** * 發佈服務 */ private void releaseService() {
// 用於接受客戶端連接的請求 (並沒有處理請求) NioEventLoopGroup bossGroup = new NioEventLoopGroup();
// 用於處理客戶端連接的讀寫操作 NioEventLoopGroup workGroup = new NioEventLoopGroup();
// 用於創建我們的ServerBootstrap ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
} });
// 綁定我們的端口號碼 try {
// 綁定端口號,同步等待成功 ChannelFuture future = serverBootstrap.bind(port).sync(); System.out.println("服務註冊成功" + port);
// 等待服務器監聽端口 future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace();
} finally {
// 優雅的關閉連接 bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } }
/** * 綁定服務 * * @param service */ private void bindService(Object service) { UserService userService = new UserService();
RpcAnnotation declaredAnnotation = userService.getClass().getDeclaredAnnotation(RpcAnnotation.class);
if (declaredAnnotation != null) { String value = declaredAnnotation.value().toString().replace("class ", ""); String serviceClassAddres = "mayikt://" + host + ":" + port + "//" + value;
serviceRegistration.regist(value, serviceClassAddres); } } }
|
member-service-provider
public interface UserService { String getUserName(); }
@RpcAnnotation(UserService.class) public class UserServiceImpl implements UserService {
public String getUserName() {
return "memberInfo"; } }
|
補充說明:
1.Dubbo整體架構實現原理步驟可以分爲兩步:
(1).生產者啓動的時候能夠實現被其他服務調用,Dubbo服務器使用Netty作爲服務器端,當前的地址註冊到Zookeeper上,底層實際上還是dubbo協議:
Dubbo:// ip地址+端口號+com.api.service.UserService.getUser(Long)
Netty服務器的端口號設置爲20880,這是因爲此處Dubbo服務器就是使用Netty進行網絡通信的,所以Dubbo服務器就是自定義的Netty服務器,Netty服務器端口即爲手寫實現的Dubbo服務器端口,實現服務註冊。
- .消費者從Zookeeper上獲取註冊地址即可實現服務調用。
2.Zookeeper註冊中心實現了對已經註冊的服務進行服務治理,起到了類似服務容器的作用,既可以註冊和調用服務,也可以進行服務治理。一般情況下Zookeeper和Dubbo服務器都需要搭建集羣的,這樣做的目的是提高系統容災性和彈性,不會因爲某一個或一些服務出現問題導致其他所有服務都無法使用,採用主從機制,從機定時同步主機信息,通過zkid作爲標誌用來標識節點數據同步的優先級,zkid值越大表示當前Zookeeper節點的數據狀態是最新的。
Zookeeper基於節點和事件處理機制,實現了服務治理。
轉載請註明原作者