分佈式網絡通信框架Netty_基於Netty+Zookeeper純手寫RPC遠程調用框架_1 Dubbo服務器-服務註冊實現

                                   分佈式網絡通信框架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服務器端口,實現服務註冊。

  1. .消費者從Zookeeper上獲取註冊地址即可實現服務調用。

2.Zookeeper註冊中心實現了對已經註冊的服務進行服務治理,起到了類似服務容器的作用,既可以註冊和調用服務,也可以進行服務治理。一般情況下Zookeeper和Dubbo服務器都需要搭建集羣的,這樣做的目的是提高系統容災性和彈性,不會因爲某一個或一些服務出現問題導致其他所有服務都無法使用,採用主從機制,從機定時同步主機信息,通過zkid作爲標誌用來標識節點數據同步的優先級,zkid值越大表示當前Zookeeper節點的數據狀態是最新的。

Zookeeper基於節點和事件處理機制,實現了服務治理。

轉載請註明原作者

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章