看tomcat源码的一点理解

12 篇文章 0 订阅
5 篇文章 0 订阅

http连接的表现就是socket对象

客户端连接服务端时,先随机分配一个端口去连接服务器的80端口,然后服务器会另外随机分配一个端口与客户端通信,该端口由一个socket持有。在程序上的表现就是:当这个socket还存在,就意味着客户端和服务端还在建立连接;反之,即断开连接。

通常情况下,连接会被很快的处理完,然后断开。当客户端再次需要请求服务器资源数,再重新经历上面的过程进行连接。这时客户端和服务端的通信端口还是会重新随机分配,每次连接可能都不一样。客户端在服务端是靠session唯一标识的,和端口没有关系。

所以,我们常说的服务器能够有多大的并发量,实际上就是指服务端能够同时处理多少socket对象。

Tomcat bio和nio的区别

  • bio:只有Acceptor和Worker
  • nio:在Acceptor和Worker之间多了一个队列,Acceptor接收到请求之后,看Worker有没有空,有空就直接处理,没空的话,就先放到队列里。

socket对象在服务端的处理过程:

  1. 一开始建立连接,连接信息会存放在ServerSocket连接请求的队列中(队列长度为acceptCount)
  2. 如果现在提交的任务数没有超过maxConnections,那么就ServerSocket.accept()返回socket对象,封装为任务提交到线程池;现在提交的任务数超过了maxConnections,则阻塞
  3. 任务提交到线程池后,分三种情况
    1. 线程数<=minSpareThreads:不管有没有空闲线程,每次来任务都新建线程
    2. minSpareThreads<线程数<maxThreads:每次来任务优先使用空闲线程,如果没有空闲线程才新建线程
    3. 线程数==maxThreads:把任务放入任务队列中排队
  4. 当任务被处理完后,或队列已满则销毁任务以及任务中的socket对象

Tomcat连接数相关参数

  • minProcessors (minSpareThreads):最小空闲连接线程数,线程数小于此数时,每次来任务都新建线程处理,默认值为25
  • maxProcessors (maxThreads):最大连接线程数,线程数小于此数时,每次来任务如果没有空余线程才新建线程处理,默认值为200
  • acceptCount:允许的最大并发连接数(瞬时连接),为ServerSocket连接请求的队列长度,默认值为100
  • maxConnections:允许的最大连接数,计数参数,可以提交给线程池的最大任务数,默认值为10000
  • enableLookups:是否反查域名,取值为:true或false。为了提高处理能力,应设置为false
  • connectionTimeout:网络连接超时,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为30000毫秒

Tomcat连接数相关参数对应程序

  • minProcessors和maxProcessors:连接线程数,对应HttpProcessor线程池中的线程数
  • acceptCount:允许的最大并发连接数,对应ServerSocket(int port, int backlog)中的backlog(连接请求的队列长度)
  • maxConnections:允许的最大连接数,线程池有个计数器connectionLimitLatch = new LimitLatch(getMaxConnections());,任务进入线程池时会调用countUpOrAwaitConnection(),任务完成时会调用countDownConnection()。如果线程池中的任务数超过maxConnections时,countUpOrAwaitConnection()就会阻塞,这样就可以阻止任务继续提交

maxConnections在不同模式下的默认值

当Tomcat接收的连接数达到maxConnections时,Acceptor线程不会读取accept队列中的连接;这时accept队列中的线程会一直阻塞着,直到Tomcat接收的连接数小于maxConnections。如果设置为-1,则连接数不受限制。

默认值与连接器使用的协议有关:NIO的默认值是10000,APR/native的默认值是8192,而BIO的默认值为maxThreads。
在windows下,APR/native的maxConnections值会自动调整为设置值以下最大的1024的整数倍;如设置为2000,则最大值实际是1024。

 

Tomcat中线程池解析

1. 线程池

executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);

 

2. 任务队列

taskqueue = new TaskQueue(maxQueueSize); //maxQueueSize = Integer.MAX_VALUE;

 

3. 部分代码

    public boolean offer(Runnable o) {
        //we can't do any checks
        if (parent==null) return super.offer(o);
        //we are maxed out on threads, simply queue the object
        //如果线程数==maxThreads,把任务放入任务队列中排队
        if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
        //we have idle threads, just add it to the queue
        //如果有空闲线程,就使用空闲线程
        if (parent.getSubmittedCount()<(parent.getPoolSize())) return super.offer(o);
        //if we have less threads than maximum force creation of a new thread
        //如果没有空闲线程才新建线程,并且线程数还没有达到maxThreads就返回false,这会导致#############1
        if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
        //if we reached here, we need to add it to the queue
        return super.offer(o);
    }


    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {//调用上面的函数#############2
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))//此时如果线程数还没有达到最大线程数,就新建线程#############3
            reject(command);//否则抛出异常#############4
    }


    public void execute(Runnable command, long timeout, TimeUnit unit) {
        submittedCount.incrementAndGet();
        try {
            super.execute(command);//调用上面的函数#############5
        //下面的异常并不是真正异常,如果抛出异常,只是说明线程已经开到最大数了#############6
        }catch (RejectedExecutionException rx) {
            if (super.getQueue() instanceof TaskQueue) {
                final TaskQueue queue = (TaskQueue)super.getQueue();
                try {
                    //强制把任务放入队列排队#############7
                    if (!queue.force(command, timeout, unit)) {
                        submittedCount.decrementAndGet();
                        //如果放不进去才抛出“真正”的RejectedExecutionException异常#############8
                        throw new RejectedExecutionException("Queue capacity is full.");
                    }
                } catch (InterruptedException x) {
                    submittedCount.decrementAndGet();
                    throw new RejectedExecutionException(x);
                }
            } else {
                submittedCount.decrementAndGet();
                throw rx;
            }
        }
    }


思考:由此可见,用异常来控制逻辑,尽管很巧妙,但并不是一个好的设计 (←_←#)

在线用户数、连接数、瞬时并发数、线程数的区别

在线用户数=连接数+静态用户数(已登录,但连接已断开,只是在浏览静态数据)(有session对象,没有socket对象)
连接数=已接受连接数+瞬时并发数(acceptCount:在连接队列里等待的socket对象数) //这个状态的客户端显示为浏览器显示“转圈”
已接受连接数=线程数(RUNNABLE状态)(正在处理)+任务队列中的任务数(已接受,待处理)

对网络端口的理解

实际上,电脑在网卡上的硬件网络端口只有一个。我们所说的1-65535号端口,并不是真的有这么多个硬件端口,硬件端口实际上只有一个,访问所有端口的数据包都发往这个硬件端口。硬件端口接收到数据包之后进行解析,然后通知监听对应端口的socket对象来取数据。

参考

[1] 并发量计算

1.业务并发用户数;2.最大并发访问数;3.系统用户数;4.同时在线用户数;

假设一个OA系统有1000用户,这是系统用户数;最高峰同时有500人在线,是“同时在线人数”,也称作“最大业务并发用户数”;500个同时使用系统用户中20%查看系统公告,不构成压力;20%填写表格(只在提交时才会请求,填写对服务器不构成压力);40%在发呆(什么都没做);20%用户不停从一个页面跳转另一个页面(只有这20%对服务器产生了压力)。

说明服务器实际压力,能承受的最大并发访问数,既取决于业务并发用户数,还取决于用户的业务场景,这些可以通过对服务器日志的分析得到。

一般只需要分析出典型业务(用户常用,最关注的业务操作)
给出一个估算业务并发用户数的公式(测试人员一般只关心业务并发用户数)
C=nL/T
C^=C+3×(C的平方根)
C是平均的业务并发用户数、n是login session的数量、L是login session的平均长度、T是指考察的时间段长度、C^是指业务并发用户数的峰值。

该公式的得出是假设用户的login session产生符合泊松分布而估算得到。
假设OA系统有1000用户,每天400个用户发访问,每个登录到退出平均时间2小时,在1天时间内用户只在8小时内使用该系统。
C=400×2/8=100
C^=100+3×(100的平方根)=100+3×10=130
另外,如果知道平均每个用户发出的请求数u,则系统吞吐量可以估算为u×C

请注意:精确估算,还要考虑用户业务操作存在一定的时间集中性(比如上班后1小时内是OA系统高峰期),采用公式计算仍然会存在偏差。针对例子OA系统可以把1小时设定为考察时间的粒度,将一天8小时划分为8个区间,这样可以解决业务操作存在集中性问题,更趋于精准,偏差更小。

[2] 系统吞度量要素

系统吞吐量几个重要参数:QPS(TPS)、并发数、响应时间

QPS(TPS):每秒钟request/事务 数量

并发数: 系统同时处理的request/事务数

响应时间: 一般取平均响应时间

(很多人经常会把并发数和TPS理解混淆)

理解了上面三个要素的意义之后,就能推算出它们之间的关系:

QPS(TPS)= 并发数/平均响应时间

一个系统吞吐量通常由QPS(TPS)、并发数两个因素决定,每套系统这两个值都有一个相对极限值,在应用场景访问压力下,只要某一项达到系统最高值,系统的吞吐量就上不去了,如果压力继续增大,系统的吞吐量反而会下降,原因是系统超负荷工作,上下文切换、内存等等其它消耗导致系统性能下降。 决定系统响应时间要素

我们做项目要排计划,可以多人同时并发做多项任务,也可以一个人或者多个人串行工作,始终会有一条关键路径,这条路径就是项目的工期。

系统一次调用的响应时间跟项目计划一样,也有一条关键路径,这个关键路径是就是系统影响时间;

关键路径是有CPU运算、IO、外部系统响应等等组成。

[3] tomcat高并发配置与优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值