一次socket长连接运行导致的性能问题

socket长连接篇

客户端维持心跳导致出现性能问题

客户端代码

实现一个定时发送心跳包给服务端的线程,一个接收服务端返回消息的线程。
package practice;

import client.Client;
import client.KeepAlive;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * Created by sheng on 17/12/22.
 */
public class MyClient {
    private boolean running =false;
    interface ObjectAction {
        Object doAction(Object obj);
    }

    class DefaultObjectAction implements ObjectAction {
        @Override
        public Object doAction(Object object) {
            System.out.println(object);
            return object;
        }
    }

    public static void main(String[] args) {
        MyClient client = new MyClient();
        client.doStart();
    }

    public void doStart(){
        try {
            if(running)return;
            Socket socket=new Socket("127.0.0.1",7890);
            running=true;
            Thread t1=new Thread(new KeepAliveWatchDog(socket));
            t1.start();
            Thread t2=new Thread(new ReceiveThread(socket));
            t2.start();
            Scanner input=new Scanner(System.in);
            String command=input.next();
            if(command.equals("cancel")){
                doStop();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public void doStop(){
        if(running) running=false;
    }

    class KeepAliveWatchDog extends Thread{
        Socket socket;
        long lastReceive=System.currentTimeMillis();
        public KeepAliveWatchDog(Socket socket){
            this.socket=socket;
        }
        @Override
        public void run() {
            //线程靠running变量保持持续活跃,一旦主线程要求关闭,所有子线程要主动关闭套接字
            //看门狗每3秒心跳一次
            while(running){
            try {
                if (System.currentTimeMillis() - lastReceive > 2000) {
                    OutputStream outputStream = socket.getOutputStream();
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
                    objectOutputStream.writeObject(new KeepAlive());
                    System.out.println("send to server...");
                    objectOutputStream.flush();
                    lastReceive = System.currentTimeMillis();
                } else {
                    Thread.sleep(10);
                }
            }catch(IOException e){

            }catch(InterruptedException e){

            }
            }
            if(!running){
                close();
            }
        }

        public void close(){
            if(this.socket!=null){
                try {
                    this.socket.close();
                }catch(IOException ex){

                }
            }
            System.out.println("KeepAliveWatchDog socket closed");
        }
    }
    class ReceiveThread extends Thread{
        Socket socket;
        public ReceiveThread(Socket socket){
            this.socket=socket;
        }
        @Override
        public void run() {
                while(running){
                    try {
                        InputStream inputStream = socket.getInputStream();
                        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
                        if (objectInputStream.available() > 0) {
                            Object object = objectInputStream.readObject();
                            ObjectAction oa = new DefaultObjectAction();
                            oa.doAction(object);
                        } else {
                            Thread.sleep(10);
                        }
                    }catch(IOException e){

                    }catch(InterruptedException e){

                    }catch(ClassNotFoundException e){

                    }
                }
                if(!running){
                    close();
                }

        }

        public void close(){
            if(this.socket!=null){
                try {
                    this.socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            System.out.println("ReceiveThread socket closed");
        }
    }
}

服务端代码

实现一个守护线程进行socket监听客户端连接,每个客户端socket都会建立一个新的处理线程处理客户端的请求,并返回心跳包。

package practice;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
 * Created by sheng on 17/12/20.
 * 根据消息的类型,自动匹配处理器
 */
public class MyServer {
    private final int PORT = 7890;
    private boolean running = false;
    private Thread thread1;
    private Map mapping=new HashMap();
    interface ObjectAction {
        Object doAction(Object obj);
    }

    class DefaultObjectAction implements ObjectAction {
        @Override
        public Object doAction(Object object) {
            System.out.println(object);
            return object;
        }
    }

    public static void main(String[] args) {
        MyServer server = new MyServer();
        server.doStart();
    }

    public void doStart() {
        //启动accept线程,进行
        if (running) return;//确保服务器单线程启动
        running = true;
        thread1 = new Thread(new ConnWatchDogThread());
        thread1.start();
        System.out.println("server initial....");
        Scanner input=new Scanner(System.in);
        String next=input.next();
        if(next.equals("cancel")){
            doStop();
        }
        //启动socket action线程接收处理
    }

    public void doStop() {
        if (running) running = false;
    }

    /**
     * 消息-处理器模型,避免做冗余的判断
     * 通过key获取value对象处理器的方式比if..else...或设计模式都要简单便捷.
     * */
    public void addMapping(Class classes, ObjectAction oa){
        this.mapping.put(classes,oa);
    }

    public ObjectAction getAction(Class classes){
        return this.mapping.get(classes);
    }
    /**
     * 接收线程
     */
    class ConnWatchDogThread extends Thread {
        ServerSocket socket;

        @Override
        public void run() {
            try {
                socket = new ServerSocket(PORT, 5);
                while (running) {
                    Socket socket1 = socket.accept();//阻塞方法,但是只会接收一个,需要循环接收
                    System.out.println("accepted client:"+socket1.getRemoteSocketAddress());
                    Thread thread1 = new Thread(new SocketActionThread(socket1));
                    thread1.start();
                }
                if(!running)
                close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void close() {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 消息处理线程
     */
    class SocketActionThread extends Thread {
        Socket socket;
        long lastReceiveTime = System.currentTimeMillis();
        public SocketActionThread(Socket socket) {
            this.socket = socket;
        }
        private int delay = 3000;
        private boolean runnable=true;
        @Override
        public void run() {
            //长连接每隔3秒都需要心跳喂狗一次,
            while(running && runnable) {
                try {
                    if (System.currentTimeMillis() - lastReceiveTime > delay && running) {
                        executeOvertime();
                    } else {
                        //执行读写socket
                        InputStream inputStream = socket.getInputStream();
                        if (inputStream.available() > 0) {
                            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
                            Object obj = objectInputStream.readObject();
                            lastReceiveTime = System.currentTimeMillis();
                            ObjectAction oa = new DefaultObjectAction();
                            Object out = oa.doAction(obj);
                            if (out != null) {
                                //回写给客户端
                                OutputStream outputStream = socket.getOutputStream();
                                ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
                                objectOutputStream.writeObject(out);
                            }

                        } else {
                            Thread.sleep(10);
                        }
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (IOException ex) {
                    ex.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
            if(!running){
                close();
            }
        }

        //超时,主动断开socket
        public void executeOvertime() {
            if(runnable)runnable=false;
            if (this.socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("client over time:"+socket.getRemoteSocketAddress());
        }

        public void close(){
            if(this.socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
实验结果

我开启了一个server,开启了5个client。client每隔2秒发送一次心跳,服务器端收到后会回复。
我并没有手动结束,但是日志提示,客户端主动断开了。
2017-12-23 11:36:19 维持连接包 开始
2017-12-23 16:06:04 维持连接包 结束
内存使用信息:
开启了5个java进程,占用内存分别为598MB,555MB,471MB,447MB,432MB.
手动关闭client端后,只剩下server 进程,占用48MB。

客户端哪个地方写的有bug,导致客户端内存占用率这么大,还是长连接的维持本来就占用内存较高。

———-分割线

解决思路
是什么引起内存增加? 监控到引起内存爆满?
java内存分析工具 jconsole snip

一次socket长连接运行导致的性能问题_第1张图片
1.从上图我们可以看到每隔10分钟,JVM就会将堆内存回收一次.

一次socket长连接运行导致的性能问题_第2张图片
2.从上图可以看到多个客户端请求响应,服务端内存回收的频率加快了.

一次socket长连接运行导致的性能问题_第3张图片
3.上图客户端正常运行的内存占用

一次socket长连接运行导致的性能问题_第4张图片
4.上图是当服务器端意外断开或者出现网络超时,客户端出现了OOM.

一次socket长连接运行导致的性能问题_第5张图片
5.当出现网络超时时,发送线程并没有正常退出.
通过下面的日志输出可以看到,IOException一直在报错,我尝试在异常中增加socket关闭操作,然而并没有用.不是socket没关闭造成的.
一次socket长连接运行导致的性能问题_第6张图片

一次socket长连接运行导致的性能问题_第7张图片
6.真正引起OOM的是线程处于不可控状态,while一直在循环异常.
一次socket长连接运行导致的性能问题_第8张图片
7.在发生exception时,要进行线程控制,这里设置线程的局部变量runnable=false;在异常发生时,让while循环结束,在run方法最后释放socket资源.

到此完整解决 2018-1-16

你可能感兴趣的:(总结随笔)