Chapter 3-01

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

第3章多线程服务器的适用场合与常用编程模型

?“两个进程不在同一台机器上”指的是逻辑上不在同一个操作系统里运行,虽然物理上可能位于同一机器虚拟出来的两台“虚拟机”上。

3.1 进程与线程

?一个进程是“内存中正在运行的程序”。本书的进程指的是Linux操作系统通过fork()系统调用产生的东西。

?每个进程有自己独立的地址空间(address space),“在同一个进程”还是“不在同一个进程”是系统功能划分的决策点。《Erlang程序设计》[ERL]把“进程”比喻为“人”,为我们提供了一个思考的框架。

?每个人有自己的记忆(memory),人与人通过谈话(消息传递)来交流,谈话既可以是面谈(同一台服务器),也可以在电话里谈(不同的服务器,有网络通信)。面谈和电话谈的区别在于,面谈可以立即知道对方是否死了(crash, SIGCHLD),而电话谈只能通过周期性的心跳来判断对方是否还活着。

?设计分布式系统时可以采取“角色扮演”,团队里的几个人各自扮演一个进程,人的角色由进程的代码决定(管登录的、管消息分发的、管买卖的等等)。每个人有自己的记忆,但不知道别人的记忆,要想知道别人的看法,只能通过交谈(暂不考虑共享内存)。然后就可以思考:

1.容错:万一有人突然死了

2.扩容:新人中途加进来

3.负载均衡:把甲的活儿挪给乙做

4.退休:甲要修复bug,先别派新任务,等他做完手上的事情就把他重启

?线程的出现给Unix添了不少乱,很多C库函数不是线程安全的,需要重新定义(§4.2);signal的语意也大为复杂化。

?线程的特点是共享地址空间,从而可以高效地共享数据。一台机器上的多个进程能高效地共享代码段(操作系统可以映射为同样的物理内存),但不能共享数据。

?“多线程”的价值是为了更好地发挥多核处理器(multicores)的效能。在单核时代,多线程没有多大价值。Alan Cox说过:“A computer is a state machine. Threads are for people who can’t program state machines.”(计算机是一台状态机。线程是给那些不能编写状态机程序的人准备的。)如果只有一块CPU、一个执行单元,那么按状态机的思路去写程序是最高效的,这也是下一节展示的编程模型。

3.2 单线程服务器的常用编程模型

?[UNP]对此有很好的总结(第6章的IO模型、第30章的客户端/服务器设计范式)。

?在高性能的网络程序中,使用得最广泛的是“non-blocking IO + IO multiplexing”模型,即Reactor模式1,有:

1.lighttpd,单线程服务器。(Nginx与之类似,每个工作进程有一个event loop。)

2.libevent,libev。

3.ACE,Poco C++libraries。

4.Java NIO,包括Apache Mina和Netty。

5.POE(Perl)。

6.Twisted(Python)。

?相反,Boost.Asio和Windows I/O Completion Ports实现了Proactor模式2,应用面窄一些。此外,ACE也实现了Proactor模式。

?在“non-blocking IO + IO multiplexing”模型中,程序的基本结构是一个事件循环(event loop),以事件驱动(event-driven)和事件回调的方式实现业务逻辑:

// 代码仅为示意,没有完整考虑各种情况
while (!done)
{
    int timeout_ms=max(1000, getNextTimedCallback());
    int retval=::poll(fds, nfds, timeout_ms);
    if (retval < 0)
    {
        处理错误,回调用户的error handler
    }
    else
    {
        处理到期的 timers,回调用户的timer handler
        if (retval > 0)
        {
            处理 IO 事件,回调用户的IO event handler
        }
    }
}

?select(2)/poll(2)有伸缩性方面的不足,Linux下可替换为epoll(4),其他操作系统也有对应的高性能替代品3。

?Reactor模型的优点很明显,编程不难,效率也不错。不仅可以用于读写socket,连接的建立(connect(2)/accept(2))甚至DNS解析都可以用非阻塞方式进行,以提高并发度和吞吐量(throughput),对于IO密集的应用是个不错的选择。lighttpd就是这样,它内部的fdevent结构十分精妙,值得学习。

?基于事件驱动的编程模型有本质的缺点,它要求事件回调函数必须是非阻塞的。对于涉及网络IO的请求响应式协议,它容易割裂业务逻辑,使其散布于多个回调函数之中,相对不容易理解和维护。现代的语言有一些应对方法(例如coroutine),但是本书只关注 C++这种传统语言,因此就不展开讨论了。

3.3 多线程服务器的常用编程模型

?相关文献5。大概有这么几种(见§6.6更详细的讨论):

1.每个请求创建一个线程,使用阻塞式IO操作,可惜伸缩性不佳。

2.使用线程池,同样使用阻塞式IO操作。与第1种相比,这是提高性能的措施。

3.使用non-blocking IO + IO multiplexing。即Java NIO的方式。

4.Leader/Follower等高级模式。

?在默认情况下,我会使用第3种,即non-blocking IO + one loop per thread模式来编写多线程C++网络服务程序。

3.3.1 one loop per thread

?此种模型下,程序里的每个IO线程有一个event loop(或者叫Reactor),用于处理读写和定时事件(无论周期性的还是单次的),代码框架跟§3.2一样。

?libev的作者说6:One loop per thread is usually a good model. Doing this is almost never wrong, sometimes a better-performance model exists, but it is always a good start.

?这种方式的好处是:

1.线程数目基本固定,可以在程序启动的时候设置,不会频繁创建与销毁。

2.可以很方便地在线程间调配负载。

3.IO事件发生的线程是固定的,同一个TCP连接不必考虑事件并发。

?Event loop代表了线程的主循环,需要让哪个线程干活,就把timer或IO channel(如TCP连接)注册到哪个线程的loop里即可。对实时性有要求的connection可以单独用一个线程;数据量大的connection可以独占一个线程,并把数据处理任务分摊到另几个计算线程中(用线程池);其他次要的辅助性connections可以共享一个线程。

?对于non-trivial的服务端程序,一般会采用non-blocking IO + IO multiplexing,每个connection/acceptor都会注册到某个event loop上,程序里有多个event loop,每个线程至多有一个event loop。

?多线程程序要求event loop线程安全。要允许一个线程往别的线程的loop里塞东西7,这个loop必须得是线程安全的。如何实现一个优质的多线程Reactor?参考第8章。

3.3.2 线程池

?对于没有IO而光有计算任务的线程,使用event loop有点浪费,用一种补充方案,即用blocking queue实现的任务队列(TaskQueue):

typedef boost::function<void()> Functor;
BlockingQueue<Functor> taskQueue;  // 线程安全的阻塞队列

void workerThread()
{
    while (running)  // running是全局变量
    {
        Functor task = taskQueue.take();  // this blocks
        task();  // 在产品代码中需要考虑异常处理
    }
}

?以下是启动容量(并发数)为N的线程池:

int N = num_of_computing_threads;
for (int i = 0; i < N; ++i)
{
    create_thread(&workerThread);  // 伪代码:启动线程
}

?使用起来也很简单:

Foo foo;  // Foo有calc()成员函数
boost::function<void()> task = boost::bind(&Foo::calc, &foo);
taskQueue.post(task);

?上面十几行代码实现了一个简单的固定数目的线程池。在真实的项目中,这些代码都应该封装到一个class中,而不是使用全局对象。注意Foo对象的生命期,(第1章)。

?除了任务队列,还可以用BlockingQueue实现数据的生产者消费者队列,即T是数据类型而非函数对象9,queue的消费者(s)从中拿到数据进行处理。

?BlockingQueue是多线程编程的利器,它的实现可参照Java util.concurrent里的 (Array|Linked)BlockingQueue。muduo里有一个基本实现,包括无界的BlockingQueue和有界的BoundedBlockingQueue两个class。还可以试试Intel Threading Building Blocks里的concurrent_queue,性能更好。

3.3.3 推荐模式

?推荐的C++多线程服务端编程模式为:one(event) loop per thread + thread pool。

1.event loop(也叫IO loop)用作IO multiplexing,配合non-blocking IO和定时器。

2.thread pool用来做计算,具体可以是任务队列或生产者消费者队列。

?以这种方式写服务器程序,需要一个优质的基于Reactor模式的网络库(muduo)来支撑。

?程序里具体用几个loop、线程池的大小等参数需要根据应用来设定,基本的原则是“阻抗匹配”,使得CPU和IO都能高效地运作,例子见§3.6。

?此外,程序里或许还有个别执行特殊任务的线程,比如logging,这对应用程序来说基本是不可见的,但是在分配资源(CPU和 IO)的时候要算进去,以免高估了系统的容量。

3.4 进程间通信只用TCP

?Linux下进程间通信(IPC)的方式,[UNPv2]列出的有:匿名管道(pipe)、具名管道(FIFO)、POSIX消息队列、共享内存、信号(signals)、Sockets。同步原语(synchronization primitives)也很多,如互斥器(mutex)、条件变量(condition variable)、读写锁(reader-writer lock)、文件锁(record locking)、信号量(semaphore)等等。如何选择呢?根据个人经验,贵精不贵多,认真挑选三四样东西,每样用得很熟,不容易犯错。

?进程间通信首选Sockets(指TCP,不考虑UDP、Unix domain),好处在于:可以跨主机,具有伸缩性。如果一台机器的处理能力不够,那么能用多台机器来处理。把进程分散到同一局域网的多台机器上,程序修改host:port配置就能用。相反,前面列出的其他IPC都不能跨机器10,这限制了scalability。

?在编程上,TCP sockets和pipe都是操作文件描述符,用来收发字节流,都可以read/write/fcntl/select/poll等。不同的是,TCP是双向,Linux的pipe是单向,进程间双向通信还得开两个文件描述符,不方便11;而且进程要有父子关系才能用pipe,这都限制了pipe的使用。

?在收发字节流这一通信模型下,Sockets/TCP是最自然的IPC。pipe也有一个经典应用场景,那就是写Reactor/event loop时用来异步唤醒select(或等价的poll/epoll_wait)调用12,Sun HotSpot JVM在Linux就是这么做的13。

?TCP port由一个进程独占,且操作系统会自动回收(listening port和已建立连接的TCP socket都是文件描述符,在进程结束时操作系统会关闭所有文件描述符)。这说明,即使程序意外退出,也不会给系统留下垃圾,程序重启之后容易恢复,而不需要重启操作系统(用跨进程的mutex就有这个风险)。还有一个好处,因为port是独占的,所以可以防止程序重复启动,后面那个进程抢不到port,自然就没法初始化了,避免造成意料之外的结果。

?两个进程通过TCP通信,如果一个崩溃了,操作系统会关闭连接,另一个进程几乎立刻就能感知,可以快速failover。当然应用层的心跳也是必不可少的(§9.3)。

?与其他IPC相比,TCP协议的天生的好处是“可记录、可重现”。tcpdump和Wireshark是解决两个进程间协议和状态争端的好帮手,也是性能(吞吐量、延迟)分析的利器。我们可以借此编写分布式程序的自动化回归测试。也可以用tcpcopy之类的工具进行压力测试。TCP还能跨语言,服务端和客户端不必使用同一种语言。

?另外,如果网络库带“连接重试”功能,我们可以不要求系统里的进程以特定的顺序启动,任何一个进程都能单独重启。即TCP连接是可再生的,连接的任何一方都可以退出再启动,重建连接之后就能继续工作,这对开发牢靠的分布式系统意义重大。

?使用TCP这种字节流(byte stream)方式通信,会有marshal/unmarshal的开销,这要求选用合适的消息格式,准确说是wire format,推荐Google Protocol Buffers。见§9.6关于分布式系统消息格式的讨论。

?有人说如果两个进程在同一台机器,就用共享内存,否则就用TCP。试问,是否值得为那么一点性能提升而让代码的复杂度大大增加呢?何况TCP的local吞吐量一点都不低,见§6.5.1的测试结果。

?TCP是字节流协议,只能顺序读取,有写缓冲;共享内存是消息协议,a进程填好一块内存让b进程来读,基本是“停等(stop wait)”方式。要把这两种方式揉到一个程序里,需要建一个抽象层,封装两种IPC。这会带来不透明性,并且增加测试的复杂度。而且万一通信的某一方崩溃,状态reconcile也会比sockets麻烦。(数据刚写到一半,怎么办?)

?TCP本身是个数据流协议,除了直接使用它来通信外,还可以在此之上构建RPC/ HTTP之类的上层通信协议。另外,除了点对点的通信之外,应用级的广播协议也是非常有用的,可以方便地构建可观可控的分布式系统,见§7.11。

分布式系统中使用TCP长连接通信

?§3.1提到,分布式系统的软件设计和功能划分一般应该以“进程”为单位。从宏观上看,一个分布式系统是由运行在多台机器上的多个进程组成的,进程之间采用TCP长连接通信。本章讨论分布式系统中单个服务进程的设计方法,第9章将谈一谈整个系统的设计。

?提倡用多线程,并不是说把整个系统放到一个进程里实现,而是指功能划分之后,在实现每一类服务进程时,在必要时可以借助多线程来提高性能。对于整个分布式系统,要做到能scale out,即享受增加机器带来的好处。

?使用TCP长连接的好处有两点:

1.容易定位分布式系统中的服务之间的依赖关系。只要在机器上运行netstat -tpna |grep :port就能立刻列出用到某服务的客户端地址(Foreign列),然后在客户端的机器上用netstat或lsof命令找出是哪个进程发起的连接。这样在迁移服务的时候能有效地防止出现outage。TCP短连接和UDP则不具备这一特性。

2.通过接收和发送队列的长度容易定位网络或程序故障。在正常运行的时候,netstat打印的Recv-Q和Send-Q都应该接近0,或者在0附近摆动。如果Recv-Q保持不变或持续增加,则通常意味着服务进程的处理速度变慢,可能发生了死锁或阻塞。如果Send-Q保持不变或持续增加,有可能是对方服务器太忙、来不及处理,也有可能是网络中间某个路由器或交换机故障造成丢包,甚至对方服务器掉线,这些因素都可能表现为数据发送不出去。通过持续监控 Recv-Q和Send-Q就能及早预警性能或可用性故障。以下是服务端线程阻塞造成Recv-Q和客户端Send-Q激增的例子。

$ netstat -tn

Proto Recv-Q Send-Q Local Address Foreign

tcp 78393 0 10.0.0.10:2000 10.0.0.10:39748 # 服务端连接

tcp 0 132608 10.0.0.10:39748 10.0.0.10:2000 # 客户端连接

tcp 0 52 10.0.0.10:22 10.0.0.4:55572

剩余内容实在是没有任何的实践经验,用到/需要时再阅读。

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

时间: 2024-08-01 22:44:53

Chapter 3-01的相关文章

我喜欢减肥我们来减肥吧

http://www.ebay.com/cln/honus.jyw4mvptb/cars/158313278016/2015.01.28.html http://www.ebay.com/cln/honus.jyw4mvptb/cars/158313282016/2015.01.28.html http://www.ebay.com/cln/honus.jyw4mvptb/cars/158313289016/2015.01.28.html http://www.ebay.com/cln/usli

百度回家看沙发沙发是减肥了卡斯加积分卡拉是减肥

http://www.ebay.com/cln/hpryu-caw8ke/cars/158056866019/2015.01.31 http://www.ebay.com/cln/xub.50x2l7cj/cars/158445650015/2015.01.31 http://www.ebay.com/cln/xub.50x2l7cj/cars/158445674015/2015.01.31 http://www.ebay.com/cln/xub.50x2l7cj/cars/1584456790

巢哑偕倥乇椭煞谙暗逞帕俸

IEEE Spectrum 杂志发布了一年一度的编程语言排行榜,这也是他们发布的第四届编程语言 Top 榜. 据介绍,IEEE Spectrum 的排序是来自 10 个重要线上数据源的综合,例如 Stack Overflow.Twitter.Reddit.IEEE Xplore.GitHub.CareerBuilder 等,对 48 种语言进行排行. 与其他排行榜不同的是,IEEE Spectrum 可以让读者自己选择参数组合时的权重,得到不同的排序结果.考虑到典型的 Spectrum 读者需求

我国第三代移动通信研究开发进展-尤肖虎200106

众所周知,数据科学是这几年才火起来的概念,而应运而生的数据科学家(data scientist)明显缺乏清晰的录取标准和工作内容.此次课程以<星际争霸II>回放文件分析为例,集中在IBM Cloud相关数据分析服务的应用.面对星际游戏爱好者希望提升技能的要求,我们使用IBM Data Science Experience中的jJupyter Notebooks来实现数据的可视化以及对数据进行深度分析,并最终存储到IBM Cloudant中.这是个介绍+动手实践的教程,参会者不仅将和讲师一起在线

pl/sql学习1——标量变量psahnh6S

为类型.不能用于表列的数据类型.范围为的子类型.自然数.为的子类型.具有约束为单精度浮点数.为变量赋值时.后面要加为双精度浮点数.为变量赋值时.后面要加.为数字总位数.为小数位数是的子类型.最大精度位是的子类型.最大精度位单精度浮点型是的子类型.最大精度位双精度浮点型定义精度为位的实数..定义为位的整数.变长字符串.最长测试变量数据!.定长字符串.最长测试变长二进制字符串物理存储的为类型...固定长度.个字节使用定义数据类型那个最小值:最大值:最小值:最大值:最小值:最大值:最小值:最大值:最小

chapter 01

hibernate.cfg.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.

[Learning You a Haskell for Great Goods!] chapter 01 starting out

Installation under CentOS/Fedora # yum install ghc Version [[email protected] haskell]# ghc -v Glasgow Haskell Compiler, Version 7.0.4, for Haskell 98, stage 2 booted by GHC version 7.0.4 Change prompt echo :set prompt "ghci> " > ~/.ghci C

Chapter 01:创建和销毁对象

<一>考虑用静态工厂方法代替构造器 下面是Boolean类的一个简单示例: public final class Boolean implements java.io.Serializable, Comparable<Boolean> { public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); public static

Notes : &lt;Hands-on ML with Sklearn &amp; TF&gt; Chapter 7

.caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px solid #000; } .table { border-collapse: collapse !important; } .table td, .table th { background-color: #fff !important; } .table-bordered th, .table-bordere

[CSS Mastery]Chapter 1: Setting the Foundations

Chapter 1: Setting the Foundations The human race is a naturally inquisitive species. We just love tinkering with things. When I recently bought a new iMac, I had it to bits within seconds, before I’d even read the instruction manual. We enjoy workin