-10 +

context switch 研究

不知道是不是大家都有用过 ajax 来获取数据的经验,一般的使用场景就是比如定时刷新看是否有新的数据。那不知道大家是否有运维过这样的应用。我的经验是当用户量上去以后,系统的 cpu load 会居高不下。我有一次这样的排查经验,系统的 cpu load 非常高,达到 3000%,一直找不到问题所在,经过排查,找出来问题所在是前端 ajax 请求太频繁,设置了 5 秒轮训,导致 linux 服务器大量的 context switch,消耗了大量的 cpu。

我们来看下面这段代码:

  1. import java.util.concurrent.atomic.AtomicReference;
  2. import java.util.concurrent.locks.LockSupport;
  3. public final class ContextSwitchTest {
  4. static final int RUNS = 3;
  5. static final int ITERATES = 1000000;
  6. static AtomicReference<Thread> turn = new AtomicReference<Thread>();
  7. static final class WorkerThread extends Thread {
  8. volatile Thread other;
  9. volatile int nparks;
  10. public void run() {
  11. final AtomicReference<Thread> t = turn;
  12. final Thread other = this.other;
  13. if (turn == null || other == null)
  14. throw new NullPointerException();
  15. int p = 0;
  16. for (int i = 0; i < ITERATES; ++i) {
  17. while (!t.compareAndSet(other, this)) {
  18. LockSupport.park();
  19. ++p;
  20. }
  21. LockSupport.unpark(other);
  22. }
  23. LockSupport.unpark(other);
  24. nparks = p;
  25. System.out.println("parks: " + p);
  26. }
  27. }
  28. static void test() throws Exception {
  29. WorkerThread a = new WorkerThread();
  30. WorkerThread b = new WorkerThread();
  31. a.other = b;
  32. b.other = a;
  33. turn.set(a);
  34. long startTime = System.nanoTime();
  35. a.start();
  36. b.start();
  37. a.join();
  38. b.join();
  39. long endTime = System.nanoTime();
  40. int parkNum = a.nparks + b.nparks;
  41. System.out.println("Average time: " + ((endTime - startTime) / parkNum)
  42. + "ns");
  43. }
  44. public static void main(String[] args) throws Exception {
  45. for (int i = 0; i < RUNS; i++) {
  46. test();
  47. }
  48. }
  49. }

我们先来看看系统的负载

  1. [root@centos101 ~]# vmstat -w 1
  2. procs -------------------memory------------------ ---swap-- -----io---- --system-- -----cpu-------
  3. r b swpd free buff cache si so bi bo in cs us sy id wa st
  4. 0 0 646344 1293172 2660988 44481292 0 0 1 15 0 0 2 0 98 0 0
  5. 0 0 646344 1292920 2660988 44481292 0 0 0 40 1550 1789 0 0 100 0 0
  6. 1 0 646344 1293044 2660988 44481292 0 0 0 44 1456 2060 0 0 100 0 0
  7. 0 0 646344 1293040 2660988 44481292 0 0 0 0 1802 2028 0 0 100 0 0
  8. 3 0 646344 1293224 2660988 44481292 0 0 0 4 1930 2332 1 0 99 0 0
  9. 1 0 646344 1292596 2660988 44481308 0 0 0 76 1766 2681 1 0 99 0 0
  10. 2 0 646344 1292844 2660988 44481308 0 0 0 4 1324 1774 0 0 100 0 0
  11. 1 0 646344 1293620 2660988 44481308 0 0 0 76 1560 2171 0 0 99 0 0

现在我们运行上面的程序:

  1. [root@centos101 hushi]# java ContextSwitchTest

现在的系统负载为:

  1. [root@centos101 ~]# vmstat -w 1
  2. procs -------------------memory------------------ ---swap-- -----io---- --system-- -----cpu-------
  3. r b swpd free buff cache si so bi bo in cs us sy id wa st
  4. 1 0 646316 1432056 2663212 44338940 0 0 1 15 0 0 2 0 98 0 0
  5. 1 0 646316 1431660 2663212 44338940 0 0 0 0 2959 213549 1 2 97 0 0
  6. 2 0 646316 1431668 2663212 44338940 0 0 0 4 2862 211135 1 2 97 0 0
  7. 1 0 646316 1431760 2663212 44338940 0 0 0 16 2931 211029 1 2 97 0 0
  8. 3 0 646316 1431908 2663212 44338940 0 0 0 172 2835 209765 1 2 97 0 0
  9. 2 0 646316 1431908 2663212 44338940 0 0 0 0 2577 241335 1 2 97 0 0
  10. 3 0 646316 1431624 2663212 44338944 0 0 0 4 3114 279214 2 1 97 0 0
  11. 4 0 646316 1431436 2663212 44338944 0 0 0 0 2550 278861 2 1 97 0 0

请注意 vmstat 结果中的 cs 列,它的意思就是系统的 context switch 的次数。从两次数据可以看出 context switch 从 2000 左右激增到 200000 左右,增加了 100 倍。 运行后的结果为:

  1. [root@centos101 hushi]# java ContextSwitchTest
  2. parks: 960929
  3. parks: 960085
  4. Average time: 8177ns
  5. parks: 937288
  6. parks: 937248
  7. Average time: 8265ns
  8. parks: 875644
  9. parks: 876710
  10. Average time: 7738ns
  11. [root@centos101 hushi]#

现在我们绑定这个程序到某一个 cpu 上,看看执行结果会是怎么样的。

  1. [root@centos101 hushi]# taskset -c 2 java ContextSwitchTest
  2. parks: 988759
  3. parks: 1000000
  4. Average time: 2634ns
  5. parks: 999242
  6. parks: 998704
  7. Average time: 2593ns
  8. parks: 1000001
  9. parks: 986989
  10. Average time: 2634ns
  11. [root@centos101 hushi]#

vmstat 监控到的数据为:

  1. [root@centos101 ~]# vmstat -w 1
  2. procs -------------------memory------------------ ---swap-- -----io---- --system-- -----cpu-------
  3. r b swpd free buff cache si so bi bo in cs us sy id wa st
  4. 1 0 646408 1340460 2663304 44347108 0 0 1 15 0 0 2 0 98 0 0
  5. 1 0 646408 1340728 2663304 44347108 0 0 0 0 2658 393339 1 2 97 0 0
  6. 1 0 646408 1341392 2663304 44347108 0 0 4 124 3042 391449 1 2 97 0 0
  7. 4 0 646408 1339848 2663304 44347108 0 0 0 0 2844 395311 1 2 97 0 0
  8. 2 0 646408 1337472 2663304 44347108 0 0 0 4 2988 392845 1 3 97 0 0
  9. 3 0 646408 1337212 2663304 44347108 0 0 0 4 3096 396890 1 2 97 0 0
  10. 2 0 646408 1337384 2663304 44347108 0 0 0 4 2963 396925 1 3 97 0 0

context 就是我们常说的上线文,switch 必然设计到 2 个 context。过程如下图:

那 context 具体是什么什么,其实就是 process control block,如下图:

具体包括:

上下文切换会带来直接和间接两种因素影响程序性能的消耗

关于我

85 后程序员, 比较熟悉 Java,JVM,Golang 相关技术栈, 关注 Liunx kernel,目前痴迷于分布式系统的设计和实践。 研究包括但不限于 Docker Kubernetes eBPF 等相关技术。

Blog

Code

Life

Archive

0 comments
Anonymous
Markdown is supported

Be the first person to leave a comment!