对服务端而言,如果客户端高并发的所有请求任务进入到执行的阶段,必然会给服务端带来系统级的灾难,这是因为服务端的底层服务以及系统的集群计算能力它终究是存在可承载的能力是有限的,毕竟不管是服务的承载能力还是计算引擎的数据分析都是,都是存在它的边界能力的,而不可能无限制的来接受客户端的所有请求并对这些请求进行立刻马上的处理,如果真如此,这样带来的灾难是系统级的瘫痪。
当雪崩的时候,没有一片雪花是漂亮的。在性能测试的过程中需要找到底层服务承载的边界处理能力,以及找到计算引擎可承载的最大计算能力,但是仅仅这些是不够的,既然任何的服务以及计算引擎可承载的能力是存在边界的,那么针对边界这部分的任务排队以及任务优先级的思路又是什么了?这就是调度来思考的。不管是操作系统还是常规的其他计算引擎以及服务,都存在内部的调度算法,针对操作系统而言,调度可以简单的理解为CPU时间划分给活跃的进程和线程,而且维护一套优先级的机制,这样更重要的工作可以获取优先执行的机会,同时调度器会跟踪所有的ready-to-run状态的进程。系统的最小粒度是线程,那么也就是说系统调度中粒度最细的是针对线程的调度。下面详细地阐述下抢占式调度和非抢占式调度。
假设在一个单核的CPU下运行20个线程,同一个时间只能运行一个线程,调度算法负责筛选线程执行的优先级,指定了哪些线程优先在CPU上执行,哪些线程需要排队等待。事实上,很多的时候线程执行的速度是非常快的,具体点来就是一个线程执行结束后会切换到另外一个线程来执行,在这个过程中,每个程序执行的时间多少都是由调度算法来进行决策,通过这样的方式来保障调度的公平性。但是往往给我们的错觉是许多任务都是在同时执行中,以为所有的任务都是并发执行中。还是回顾到前面说的,资源是存在边界的,但是要执行的任务超过了资源的边界,这中间就需要调度策略来决定哪些执行哪些排队。下面首先来看调度中抢占式调度的设计思想。
所谓抢占式调度可以理解为在多线程的情况下,各个线程都需要遵守操作系统级的调度策略来抢占系统资源的方式来获取系统资源的使用优先权,也就是线程通过竞争的方式来获取CPU的时间分片,时间划分一般都是非常小的,所以在这样的情况下,更多感觉到的是线程是在并行的方式在执行中。在抢占式调度中,它的调度策略是优先级高的线程更加容易获取资源并且优先执行,所以优先级高的线程在执行效率方面有可能会更高,但是不是绝对。如果执行的任务在服务层资源是完全能够处理的情况下,那么优先级反而不是决定的因素,这个过程中那个任务跑的速度快,就更加具备优先执行的机会,但是如果执行的任务在服务层是无法处理的,比如执行任务数是10个,但是服务层只能处理3个任务,那么这个时候起决定因素的是任务的优先级,优先级高的就拥有优先执行的权限,而优先级低的只能排队等待优先级高的执行结束后释放资源。下面结合Java语言详细的演示下线程优先级的部分,具体案例代码如下:
package com.example.concurrent;
public class ThreadPriorityTest implements Runnable
{
@Override
public void run() {
int calc=0;
for(int i=0;i<999999999;i++)
{
calc+=i;
}
System.out.println(Thread.currentThread().getName()+"执行任务,执行结果:"+calc);
}
public static void main(String[] args) {
Thread objThread1=new Thread(new ThreadPriorityTest());
Thread objThread2=new Thread(new ThreadPriorityTest());
Thread objThread3=new Thread(new ThreadPriorityTest());
Thread objThread4=new Thread(new ThreadPriorityTest());
objThread1.setPriority(8);
objThread1.setName("线程优先级是8");
objThread1.start();
objThread1.setPriority(3);
objThread2.setName("线程优先级是3");
objThread2.start();
objThread3.setPriority(1);
objThread3.setName("线程优先级是1");
objThread3.start();
objThread4.setPriority(10);
objThread4.setName("线程优先级是10");
objThread4.start();
}
}
执行后结果信息如下图所示:
特别备注:不是每次执行结果都是如此,主要看抢占式调度中的调度策略。
上面详细的阐述了抢占式调度,下面详细的阐述下非抢占式调度的策略机制。在非抢占式的调度中,系统对各个线程会按照一定的排序方式分配系统的资源,如果一个线程被分配到系统资源后,就允许该线程一直占用这个资源直到整个线程任务执行结束为止,存在的风险的问题是如果一个线程出现运算大或者是逻辑上存在严重的问题,就会导致其他线程一直处于等待中,可能大概率的会出现死锁。那么可以使用异步的方式来解决这个问题,这样使用协程的方式,遇到IO堵塞会切换,这样可以让系统的资源始终得到最大化的利用。\
其实不管是使用调度的策略,事实上是在性能测试中需要找到计算引擎以及服务可承载的边界能力,也就是边界值,同时也需要验证在大于这个边界值的时候任务的排队机制以及资源释放后任务的入队机制。这样一方面可以保障服务的高可用,另外一方面也可以保障客户端的请求任务都能够及时有效的进行处理,而不至于出现用户体验的问题。