1969 | 1983 | 1995 | 2002 | 2004 | 2009 |
---|---|---|---|---|---|
结构化 | 面向对象 | 设计模式 | 设计原则 | DDD | DCI |
进程、线程、协程的特点及区别
支持协程的语言
适合使用协程的场景
协程与异步和并发的联系
多个进程/线程同时阻塞等同一个事件时,事件发生后,唤醒所有的进程
但最终只可能有一个进程/线程对该事件进行处理,其他在失败后重新休眠,这种性能浪费就是惊群
accept 惊群
主进程创建socket, bind, listen后,fork出多个子进程都循环处理(accept)这个socket
每个进程都阻塞在accpet上,当一个新的连接到来时,所有的进程都会被唤醒,但其中只有一个进程会accept成功,其余皆失败,重新休眠。这就是accept惊群
fork出多个进程是为了利用多核CPU
内核早解决这问题了
多个进程/线程都阻塞在对同一个 socket 的 accept 调用上时,新连接到来,内核只唤醒一个进程,其他进程保持休眠,压根就不会被唤醒
epoll惊群
accept 已无惊群问题,但 epoll 还有
即,如果多个进程/线程阻塞在监听同一个 listening socket fd 的 epoll_wait 上,当有一个新的连接到来时,多个子进程被唤醒
为什么内核不处理 epoll 惊群
accept 应该只能被一个进程调用成功,内核很清楚这一点
但 epoll 监听的fd,除可能被 accept 调用外,还有可能是其他网络 IO 事件的
其他 IO 事件是否只能由一个进程处理(如一个文件会由多个进程来读写),得用户决定,内核不能强制
所以,对 epoll 的惊群,内核则不予处理
大规模并发服务的技术,归纳起来就是两种方式:
-
thread per client,用blocking I/O
-
多个clients一个thread,用nonblocking I/O或asynchronous I/O
Linux asynchronous I/O还不好,一般都是用nonblocking I/O
多数都是用epoll()的edge triggering(select()性能问题)
宏观与微观角度看epoll+nonblock
宏观角度可以叫做全异步
微观角度还是同步 IO(数据到达后得到系统通知,然后同步执行 recv 取回数据,没有 iowait)
真正的异步 IO(AIO)应像 Windows IOCP 一样,传入文件句柄,缓存区,尺寸等参数和一个函数指针,当os真正完成了 IO 操作,再执行对应的函数
-
实际上 socket ,epoll 已经是最高效的模型了,虽然比 AIO 多一次 recv 系统调用,但总体来看没有任何 IO 等待,效率很高
-
epoll 天然 reactor 模型,程序实现更容易。AIO 如 windows 的 IOCP,异步回调的方式,开发难度很高
为什么还是需要 AIO
文件句柄跟 socket 完全不同,它总是处于可读状态
,不能 epoll+nonblock 实现异步化
在 epoll 的全异步 Server 中,读写文件必须得用 AIO
动态语言提供了在运行时改变程序结构
的能力,有时候会提供巨大的方便
函数调用拦截
是aop的基础
class Target:
def targetFunc(self):
print ("targetFunction")
temp=Target.targetFunc
def foo(self):
print ("before call")
temp(self)
print ("after call")
Target.targetFunc=foo
t=Target()
t.targetFunc()
对调用者来说根本就不知道函数已经给动了手脚
用Java, C++来实现就麻烦些了
tcp 使用场景,一般区分为“长连接”和“短连接”
如何解决tcp短连接的TIME_WAIT问题
短连接最大缺点:占用资源(如:本地端口、socket句柄)
正常的TCP client关闭后,进入TIME_WAIT,持续的时间一般在1~4分钟
每秒建1000个短连接(Web请求访问memcached)
假TIME_WAIT时间是1分钟,1分钟内需要建立6W个短连接,这些短连接1分钟内都处于TIME_WAIT状态,都不会释放
Linux默认本地端口范围:net.ipv4.ip_local_port_range = 32768 61000
不到3W,由于没有本地端口就不能建立了
并发 & 并行
-
并发(concurrency)
关注任务切分
创业公司开始只有一个人,一人分饰多角,一会做产品规划,一会写代码,一会见客户
虽然不能见客户的同时写代码,但通过分配时间片,切分了任务,,表现出来好像是多个任务一起在执行
-
并行(parallelism)
关注同时执行
发现自己太忙了,时间分配不过来,于是请了工程师,产品经理,市场总监,各司其职,这时候多个任务可以同时执行了
继承是面向对象编程的一个重要的方式,通过继承,子类就可以扩展父类的功能
Animal类层次设计
4 种动物:Dog(狗)、Bat(蝙蝠)、Parrot(鹦鹉)、Ostrich(鸵鸟)
按哺乳动物和鸟类归类,类层次:
按 能跑 和 能飞 归类,类层次
要把上面的两种分类都包含进来,就得设计更多的层次
哺乳类:能跑的哺乳类,能飞的哺乳类
鸟类:能跑的鸟类,能飞的鸟类
这么一来,类的层次就复杂了:
如果要再增加 “宠物类” 和 “非宠物类”,这么搞下去,类数量指数增长(类爆炸
)
基本的 IO(包括网络和文件) 编程过程
- 打开文件描述符(windows:handler,java:stream 或 channel)
- 多路捕获 IO 可读写状态(Multiplexe:select/poll/epoll)
- 对可读写的 FD 进行 IO 读写
IO 设备速度比CPU、内存慢,开多线程更好利用 CPU和内存,每个线程读写一个fd
C10K
海量网络连接下,瓶颈不在机器设备和网络速度,在于os和 IO 应用程序的沟通协作的方式
一万个 socket 连接过来,传统阻塞式 IO 编程模型要开一万个线程来应对
一万个线程要不断的关闭线程重建线程,资源都浪费在这上面了
一个线程耗 1M 内存,1 万个线程至少10G ,IA-32 机器架构基本不可能的(要开 PAE),x64 架构才有可能舒服点,这仅仅是粗略算的内存消耗,还有别的资源
高性能网络编程(即 IO 编程)
一,需要解耦 IO 连接和应用程序线程的对应关系
,这就是非阻塞(nonblocking)、
异步(asynchronous)要求的由来
(构造一个线程池,epoll 监控到有数据的 fd,把 fd 传入线程池,由这些 worker thread 来读写 io)
二,需要高性能的 OS 对 IO 设备可读写(数据来了)的通知方式:
从 level-triggerednotification 到 edge-triggered notification