Netty入門三之最佳實踐

關鍵字 最佳實踐: 數據通信,心跳檢測

代碼在 https://github.com/zhaikaishun/NettyTutorial 代碼在SocketIO_03下

Netty最佳實踐

實際場景一:數據通信

我們需要考慮兩臺或者多臺機器使用Netty如何進行通信,作者個人大體上把他分爲3種

  1. 第一種,使用長連接通道不斷開的形式進行通信,也就是服務器和客戶端的通道一直處於開啓狀態,如果服務器性能足夠好,並且我們的客戶端數量比較少的情況下,還是比較推薦這種方式的

  2. 第二種,一次性批量提交數據,採用短連接方式。也就是說數據先保存到臨時表中,當到達臨界值時進行一次性批量提交,這種情況是做不到實時傳輸,在對實時性不高的應用程序可以推薦使用。

  3. 第三種,我們可以使用一種特殊的長連接,在制定某一時間之內,服務器與某臺客戶端沒有任何通信,則斷開連接,下次連接則是客戶端向服務器發送請求的時候,再次建立連接,但是這種模式我們需要考慮2個因素:
    第一點: 如何在超時後關閉通道?關閉通道後我們又如何再次建立連接?
    第二點: 客戶端宕機時,我們無需考慮,下次客戶端重啓之後我們就可以與服務器建立連接,但是當服務器宕機時,我們的客戶端如何與服務器進行連接呢?

第一點解決辦法:netty有自帶的工具類在超時後關閉通道。關閉後,client再次發送連接請求即可再次建立連接
第二點解決辦法: 用一個腳本定時的監聽Netty是否有宕機,若有宕機,則運行腳本再次啓動Server端即可

舉個例子: 代碼在SocketIO_03 bhz.netty.runtime, 具體的去github上看吧
Server端,沒什麼改變,就是加了一個sc.pipeline().addLast(new ReadTimeoutHandler(5)); 說明如果5秒鐘某個Client沒有和我通信,就斷開和這個Client的連接。
Server端

sc.pipeline().addLast(new ReadTimeoutHandler(5)); 

同理Client端也是需要加這個

sc.pipeline().addLast(new ReadTimeoutHandler(5)); 

如何讓Client在斷開後重連呢?
主要是New一個線程,這個線程在斷開後開啓,然後通過判斷是否連接關閉,如果連接關閉,那麼就發起一個連接。

    public ChannelFuture getChannelFuture(){

        if(this.cf == null){
            this.connect();
        }
        if(!this.cf.channel().isActive()){
            this.connect();
        }

        return this.cf;
    }

實際場景二:心跳檢測

我們使用Socket通信一般經常會處理多個服務器之間的心跳檢測,我們去維護服務器集羣,肯定要有一臺或多臺服務器主機(Master),然後還應該有N臺(Slave),那麼我們的主機肯定要時時刻刻知道自己下面的從服務器的各方面情況,然後進行實時監控的功能,這個在分佈式架構裏面叫做心跳檢測或者說心跳監控,最佳處理方案我還是覺得使用一些通信框架進行實現,我們的Netty就可以去做這樣一件事。
這裏先介紹一個工具jar包sigar,這個可以用來查看一些服務器的信息
使用方法: 網上下載sigar-bin-lib包,我github上有, 打開hyperic-sigar-1.6.4\sigar-bin\lib 將對應的配置文件複製到服務器的jdk的bin目錄下, 例如我這裏是64位的jdk程序,那麼我複製sigar-amd64-winnt.dll 到我的java的bin目錄下
linux的我一般複製libsigar-amd64-linux.so到jdk bin目錄下, 具體的百度搜索有很多。這裏簡要介紹如何使用
API例子也在 SocketIO_03的bhz.utils包中

心跳例子: 代碼在SocketIO_03 bhz.netty.heartBeat
步驟一
1. Server端需要檢測Client端的認證是否通過(爲了確保安全)
2. 如果通過,Client端定時發送自己所在電腦的信息,Server端接收信息

RequestInfo類: 用來存放機器的信息

public class RequestInfo implements Serializable {

    private String ip ;
    private HashMap<String, Object> cpuPercMap ;
    private HashMap<String, Object> memoryMap;
    //.. other field

    public String getIp() {
        return ip;
    }
    public void setIp(String ip) {
        this.ip = ip;
    }
    public HashMap<String, Object> getCpuPercMap() {
        return cpuPercMap;
    }
    public void setCpuPercMap(HashMap<String, Object> cpuPercMap) {
        this.cpuPercMap = cpuPercMap;
    }
    public HashMap<String, Object> getMemoryMap() {
        return memoryMap;
    }
    public void setMemoryMap(HashMap<String, Object> memoryMap) {
        this.memoryMap = memoryMap;
    }


}

Server端代碼

public class Server {

    public static void main(String[] args) throws Exception{

        EventLoopGroup pGroup = new NioEventLoopGroup();
        EventLoopGroup cGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        b.group(pGroup, cGroup)
         .channel(NioServerSocketChannel.class)
         .option(ChannelOption.SO_BACKLOG, 1024)
         //設置日誌
         .handler(new LoggingHandler(LogLevel.INFO))
         .childHandler(new ChannelInitializer<SocketChannel>() {
            protected void initChannel(SocketChannel sc) throws Exception {
                sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
                sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
                sc.pipeline().addLast(new ServerHeartBeatHandler());
            }
        });

        ChannelFuture cf = b.bind(8765).sync();

        cf.channel().closeFuture().sync();
        pGroup.shutdownGracefully();
        cGroup.shutdownGracefully();

    }
}

ServerHeartBeatHandler處理類

public class ServerHeartBeatHandler extends ChannelHandlerAdapter {

    /** key:ip value:auth */
    private static HashMap<String, String> AUTH_IP_MAP = new HashMap<String, String>();
    private static final String SUCCESS_KEY = "auth_success_key";

    static {
        AUTH_IP_MAP.put("192.168.1.101", "1234");
    }

    private boolean auth(ChannelHandlerContext ctx, Object msg){
            //System.out.println(msg);
            String [] ret = ((String) msg).split(",");
            String auth = AUTH_IP_MAP.get(ret[0]);
            if(auth != null && auth.equals(ret[1])){
                ctx.writeAndFlush(SUCCESS_KEY);
                return true;
            } else {
                ctx.writeAndFlush("auth failure !").addListener(ChannelFutureListener.CLOSE);
                return false;
            }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if(msg instanceof String){
            auth(ctx, msg);
        } else if (msg instanceof RequestInfo) {

            RequestInfo info = (RequestInfo) msg;
            System.out.println("--------------------------------------------");
            System.out.println("當前主機ip爲: " + info.getIp());
            System.out.println("當前主機cpu情況: ");
            HashMap<String, Object> cpu = info.getCpuPercMap();
            System.out.println("總使用率: " + cpu.get("combined"));
            System.out.println("用戶使用率: " + cpu.get("user"));
            System.out.println("系統使用率: " + cpu.get("sys"));
            System.out.println("等待率: " + cpu.get("wait"));
            System.out.println("空閒率: " + cpu.get("idle"));

            System.out.println("當前主機memory情況: ");
            HashMap<String, Object> memory = info.getMemoryMap();
            System.out.println("內存總量: " + memory.get("total"));
            System.out.println("當前內存使用量: " + memory.get("used"));
            System.out.println("當前內存剩餘量: " + memory.get("free"));
            System.out.println("--------------------------------------------");

            ctx.writeAndFlush("info received!");
        } else {
            ctx.writeAndFlush("connect failure!").addListener(ChannelFutureListener.CLOSE);
        }
    }


}

Client端代碼

public class Client {
    public static void main(String[] args) throws Exception{

        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group)
         .channel(NioSocketChannel.class)
         .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel sc) throws Exception {
                sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
                sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
                sc.pipeline().addLast(new ClienHeartBeattHandler());
            }
        });

        ChannelFuture cf = b.connect("127.0.0.1", 8765).sync();

        cf.channel().closeFuture().sync();
        group.shutdownGracefully();
    }
}

ClienHeartBeattHandler處理類

public class ClienHeartBeattHandler extends ChannelHandlerAdapter {

    private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    private ScheduledFuture<?> heartBeat;
    //主動向服務器發送認證信息
    private InetAddress addr ;

    private static final String SUCCESS_KEY = "auth_success_key";

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        addr = InetAddress.getLocalHost();
        String ip = addr.getHostAddress();
        String key = "1234";
        //證書
        String auth = ip + "," + key;
        ctx.writeAndFlush(auth);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            if(msg instanceof String){
                String ret = (String)msg;
                if(SUCCESS_KEY.equals(ret)){
                    // 握手成功,主動發送心跳消息
                    this.heartBeat = this.scheduler.scheduleWithFixedDelay(new HeartBeatTask(ctx), 0, 2, TimeUnit.SECONDS);
                    System.out.println(msg);                
                }
                else {
                    System.out.println(msg);
                }
            }
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    private class HeartBeatTask implements Runnable {
        private final ChannelHandlerContext ctx;

        public HeartBeatTask(final ChannelHandlerContext ctx) {
            this.ctx = ctx;
        }

        @Override
        public void run() {
            try {
                System.out.println("ff");
                RequestInfo info = new RequestInfo();
                //ip
                info.setIp(addr.getHostAddress());
                Sigar sigar = new Sigar();
                //cpu prec
                CpuPerc cpuPerc = sigar.getCpuPerc();
                HashMap<String, Object> cpuPercMap = new HashMap<String, Object>();
                cpuPercMap.put("combined", cpuPerc.getCombined());
                cpuPercMap.put("user", cpuPerc.getUser());
                cpuPercMap.put("sys", cpuPerc.getSys());
                cpuPercMap.put("wait", cpuPerc.getWait());
                cpuPercMap.put("idle", cpuPerc.getIdle());
                // memory
                Mem mem = sigar.getMem();
                HashMap<String, Object> memoryMap = new HashMap<String, Object>();
                memoryMap.put("total", mem.getTotal() / 1024L);
                memoryMap.put("used", mem.getUsed() / 1024L);
                memoryMap.put("free", mem.getFree() / 1024L);
                info.setCpuPercMap(cpuPercMap);
                info.setMemoryMap(memoryMap);
                ctx.writeAndFlush(info);
                System.out.println("gg");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            if (heartBeat != null) {
                heartBeat.cancel(true);
                heartBeat = null;
            }
            ctx.fireExceptionCaught(cause);
        }

    }
}

MarshallingCodeCFactory工具類
用來解編碼的,不需要看懂,能用就行了

/**
 * Marshalling工廠
 * @author(alienware)
 * @since 2014-12-16
 */
public final class MarshallingCodeCFactory {

    /**
     * 創建Jboss Marshalling解碼器MarshallingDecoder
     * @return MarshallingDecoder
     */
    public static MarshallingDecoder buildMarshallingDecoder() {
        //首先通過Marshalling工具類的精通方法獲取Marshalling實例對象 參數serial標識創建的是java序列化工廠對象。
        final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
        //創建了MarshallingConfiguration對象,配置了版本號爲5 
        final MarshallingConfiguration configuration = new MarshallingConfiguration();
        configuration.setVersion(5);
        //根據marshallerFactory和configuration創建provider
        UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
        //構建Netty的MarshallingDecoder對象,倆個參數分別爲provider和單個消息序列化後的最大長度
        MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024 * 1024 * 1);
        return decoder;
    }

    /**
     * 創建Jboss Marshalling編碼器MarshallingEncoder
     * @return MarshallingEncoder
     */
    public static MarshallingEncoder buildMarshallingEncoder() {
        final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
        final MarshallingConfiguration configuration = new MarshallingConfiguration();
        configuration.setVersion(5);
        MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
        //構建Netty的MarshallingEncoder對象,MarshallingEncoder用於實現序列化接口的POJO對象序列化爲二進制數組
        MarshallingEncoder encoder = new MarshallingEncoder(provider);
        return encoder;
    }
}

注意Sigar的配置文件放在jdk bin目錄下,當運行Server端,再啓動Client端的時候,如果認證失敗,則服務器主動斷開與client的連接,如果認證通過,那麼Client就每2秒鐘發送一次心跳檢測給Server端.

特別感謝互聯網架構師白鶴翔老師,本文大多出自他的視頻講解。
筆者主要是記錄筆記,以便之後翻閱,正所謂好記性不如爛筆頭,爛筆頭不如雲筆記

發佈了137 篇原創文章 · 獲贊 211 · 訪問量 59萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章