zoukankan      html  css  js  c++  java
  • 2020.04.10 线上性能优化以及Linux下NIO/Epoll模型全解--实战

    1.支付宝模拟线上优化实战

    2.手写JUC工具与提升tomcat吞吐量

    3.网络通信BIO设计与缺陷   -- accept()  和 read()阻塞     

    4.单线程解决高并发NIO精髓解读

    5.OS内核下Epoll与Selete源码解读

    第一部分: 性能优化

    问题:如何在高并发场景下实现支付宝用户登录页面的信息获取?如用户信息,金额,积分等

    浏览器  ---- Spring MVC ---- controller ----- service ---- 用户模块、余额模块、积分模块等

    -- 调用多个系统提供的远程接口,返回数据结果(json格式)

    第一种做法 传统做法:

    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        RemoteService remoteService;
    
        //做一个调多系统提供的远程接口
        @Override
        public String getUserInfo(String userId) {
    
            String v1 = remoteService.getUserInfo(userId);
            JSONObject userInfo  = JSONObject.parseObject(v1); // {1, 2, 3}
    
            String v2 = remoteService.getUserMoney(userId);
            JSONObject moneyInfo  = JSONObject.parseObject(v2); // {4, 5, 6}
    
            //合并
            JSONObject result = new JSONObject();
            result.putAll(userInfo);
            result.putAll(moneyInfo);  // result {1, 2, 3, 4, 5, 6}
    
            return result.toString();
        }
    }
    View Code

     4秒时间,不可以接受,时间太长

    第二种:是不是考虑线程呢?

    thread实现runnable接口,但是没有返回结果,也行不通。

    Callable接口有返回值,与 FutureTask连用
    FutureTask -----extends------runnable----run(todo??)--->Callable.call()

    注意:

    FutureTask.get()  获取线程返回结果,阻塞的。

     具体代码实现如下:

    Callable<JSONObject> userCall = new Callable<JSONObject>() {
                @Override
                public JSONObject call() throws Exception {
                    String v1 = remoteService.getUserInfo(userId);  //httpClient URL http
                    JSONObject userInfo  = JSONObject.parseObject(v1); // {1, 2, 3}
                    return userInfo;
                }
            };
    
            Callable<JSONObject> moneyCall = new Callable<JSONObject>() {
                @Override
                public JSONObject call() throws Exception {
                    String v1 = remoteService.getUserInfo(userId);  //httpClient URL http
                    JSONObject userInfo  = JSONObject.parseObject(v1); // {1, 2, 3}
                    return userInfo;
                }
            };
    
            FutureTask<JSONObject> userTask = new FutureTask(userCall);
            FutureTask<JSONObject> moneyTask = new FutureTask(moneyCall);
            new Thread(userTask).start();
            new Thread(moneyTask).start();
    
    
            //合并
            JSONObject result = new JSONObject();
            try {
                result.putAll(userTask.get());   //阻塞  --没有拿到值,就想当与暂时,拿到值后就放行,如 F6 F8等
                result.putAll(moneyTask.get());  // result {1, 2, 3, 4, 5, 6}
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println("执行消耗时间:" + (System.currentTimeMillis() - start));
    View Code

       时间花费从4秒瞬间将至2秒,也是不可以接受的。

    第二部分:手写JUC工具与提升tomcat吞吐量

    自己手写一个FutureTask实现类:QianBingFutureTask.java

    package com.suning.aaa.impl.service.publicclass;
    
    import java.util.concurrent.*;
    
    public class QianBingFutureTask<V> implements Runnable, Future<V> {
    
        Callable<V> callable;
    
        V result = null;
    
        public QianBingFutureTask(Callable<V> userCall) {
            this.callable = callable;
        }
    
        @Override
        public void run() {
            try {
                result = callable.call(); //获取返回结果
                System.out.println(result);  // -----此值一定会打印
                if(result != null){
                    synchronized (this){
                        this.notifyAll(); //唤醒
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            return false;
        }
    
        @Override
        public boolean isCancelled() {
            return false;
        }
    
        @Override
        public boolean isDone() {
            return false;
        }
    
        @Override
        public V get() throws InterruptedException, ExecutionException {
            if (result != null){
                return result;
            }
            System.out.println(result); // 此值不会打印
            //没有取到result值,此时会阻塞
            synchronized (this){
                this.wait(); //阻塞
            }
            return null;
        }
    
        @Override
        public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
            return null;
        }
    }
    View Code

    1. 第一步: 实现同FutureTask接口类  runnable、Future<V>
    public class QianBingFutureTask<V> implements Runnable, Future<V>

    第二步:构建构造函数,参数Callable

    Callable<V> callable;
    public QianBingFutureTask(Callable<V> userCall) {
    this.callable = callable;
    }

    第三步:实现run()方法,得到Callable.call()返回结果值并唤醒其他线程
    定义一个全局变量 V result= null;

    第四步:实现get()方法,取到结果集则返回;拿不到值则阻塞

    tomcat吞吐量提升

    tomcat7以后    BIO   NIO

    一个tomcat并发量 500---800个请求。------------ 是谁在执行? tomcat线程

              tomcat --- server.xml----port:8080----maxThread=150  

    即tomcat在启动时候, 初始化150个线程. 

    controller返回值定义为Callable接口

    package com.suning.fiaas.controller.publicClass;
    import com.suning.fiaas.intf.service.publicClass.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    import java.util.concurrent.Callable;
    @Controller
    @RequestMapping("/user")
    public class UserController {
        @Autowired
        private UserService userService;
        /**
         * 获取用户信息(用户接口+余额接口)
         * @param userId
         * @return
         */
        @RequestMapping(value = "/getUserInfo", method = RequestMethod.GET )
        @ResponseBody
        public Callable<String> getUserInfo(String userId) {
            System.out.println("tomcat主线程开始-----" + Thread.currentThread() + "-----" + System.currentTimeMillis());
            Callable<String> callable = new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("子线程开始-----" + Thread.currentThread() + "-----" + System.currentTimeMillis());
                    String result =  userService.getUserInfo(userId);
                    System.out.println("子线程开始-----" + Thread.currentThread() + "-----" + System.currentTimeMillis());
                    return result;
                }
            };
            System.out.println("tomcat主线程结束-----" + Thread.currentThread() + "-----" + System.currentTimeMillis());
            return callable;
        }
    }
    View Code

     

    tomcat优化与第一部分优化后2s相比,吞吐量提升2000倍。150个tomcat呢,提升1500*2000倍。

    第三部分:BIO和NIO

    服务器端代码:

    package com.suning.fiaas.controller.publicClass.socket;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class SocketServerTest {
        static byte[] bs = new byte[1024];
    
        public static void main(String[] args) throws IOException {
            ServerSocket socketServer = new ServerSocket(6363);
            while (true){
                System.out.println("==========wait connection===========");
                Socket socketClinet = socketServer.accept();  //阻塞
                System.out.println("==========success connection===========");
    
                System.out.println("==========wait read===========");
                socketClinet.getInputStream().read(bs);       //阻塞
                System.out.println("==========success read===========");
    
                System.out.println(new String(bs));
            }
        }
    }

    客户端代码:

    package com.suning.fiaas.controller.publicClass.socket;
    
    import java.io.IOException;
    import java.net.Socket;
    
    public class SocketClient {
        public static void main(String[] args) throws IOException {
            Socket socket = new Socket("127.0.0.1", 6363);
            socket.getOutputStream().write("abc".getBytes());
            socket.close();
        }
    }

    -- 验证serverSocket.accept()是阻塞的场景

    -- 验证socketClinet.getInputStream().read(bs);是阻塞的场景  --- 键盘输入

     客户端:

    问题: 服务器端 socketServer.accept() 和 socketClinet.getInputStream().read(bs);这两方法是阻塞的  ---> BIO

          10000个连接,如果只有1000个活跃,剩下的9000不活跃,也是浪费资源。

     BIO: 对每一个请求都开启一个线程链接。线程越多,线程上下文切换越多,资源越浪费。

    package com.suning.fiaas.controller.publicClass.socket;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.List;
    
    public class BIOSocketServer {
        static byte[] bs = new byte[1024];
        static List<Socket> socketList = null;
    
        public static void main(String[] args) throws IOException {
            ServerSocket socketServer = new ServerSocket(6363);
            while (true){
                //遍历老的链接
                for (Socket socket : socketList){
                    socket.setBlocking(false);
                    int read = socket.getInputStream().read(bs);   // 非阻塞
                    if (read > 0){
                        System.out.println("read ......" + read );
                    }
                }
    
                //添加新的链接
                socketServer.setBlocking(false);
                Socket socketClinet = socketServer.accept();    // 非阻塞
                if (socketClinet != null){
                    socketList.add(socketClinet);
                }
                System.out.println("socketList length当前链接数目:" + socketList.size());
            }
        }
    }
    View Code

    对于上面大量的for循环,java交给jvm处理-----内核。

    socketList委托给内核处理,只需要1ms,性能提到大大的提升。for循环不应该出现在java中。

     Selete():     java --- native c----select(socketList)----java----循环

    第四部分: NIO精髓解读与OS内核下Epoll与Selete源码解读

    NIO ---- 利用单线程来处理并发问题 --- epoll模型

    NIOSocketServer.java 

    package com.suning.fiaas.controller.publicClass.socket;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.net.SocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.ArrayList;
    import java.util.List;
    
    public class NIOSocketServer {
        static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        static List<SocketChannel> socketList = new ArrayList<>();
    
        //NIO设计就是利用单线程来解决并发的问题
        public static void main(String[] args) throws IOException {
            ServerSocketChannel serverSocket = ServerSocketChannel.open();
    
            SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 6363)
            serverSocket.bind(socketAddress);
            serverSocket.configureBlocking(false);  //去除阻塞
    
            while (true){
                //遍历老的链接
                for (SocketChannel socketChannel : socketList){
                    int read = socketChannel.read(byteBuffer);
                    if (read > 0){
                        System.out.println("read ......" + read );
                        byteBuffer.flip();
    
                        byte[] bs = new byte[read];
                        byteBuffer.get(bs);
                        String content = new String(bs);
                        System.out.println("content ......" + content );
                        byteBuffer.flip();
                    }
                }
    
                //添加新的链接
                SocketChannel accept = serverSocket.accept();
                if (accept != null){
                    System.out.println("conn succ");
                    accept.configureBlocking(false);  //去除阻塞
                    socketList.add(accept);
                    System.out.println("socketList length:" + socketList.size());
                }
            }
        }
    }

    操作系统 

    window     提供  Selete函数(socketList)   ----> 非开源

    linux        epoll  >   Selete()                      ----> epoll效率要比Selete效率更高

     -- man 2 select

    -- man epoll

    -- nc -l localhost 8080 

    比如 redis、nginx只提供了linux版本的。

    linux ---> linux ---->epoll()    c语言{

      prinf()

    }

    小工具: 网络调试助手

  • 相关阅读:
    自底向上的归并排序 .[转]
    分治法寻找数组最大的两个数和最小的两个数
    分治法求最大最小值
    数字移动【转】
    NRF24L01无线模块的使用
    对钙铀云母放射强度的测量
    自制用于放置钙铀云母的铅盒
    Arduino从DHT11读取温湿度数据并显示在1602LCD
    β particle, α particle, γ ray, ionization chamber
    Arduino通过I2C(PCF8574T)驱动1602LCD
  • 原文地址:https://www.cnblogs.com/qianbing/p/12677462.html
Copyright © 2011-2022 走看看