锁定老帖子 主题:讨论--关于开发邮箱系统
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2006-01-27
T1 写道 "一台机器只可1w封/s, 如果要求10w/s, 加上十台机器不就行了" 这真是信口开河. 做企业级的人对性能真的一点概念都没有 其他的不用管,果以linux的线程在active状况下切换以10ms来计算,从thread1切换到thread 1000,就要耗时10秒,更不用说唤醒线程要将近200ms的时间。这种消耗你真能忍受? 而且如果机器真能开到1000 thread,支持10w路也够了,socket的select的方式依赖于FD_SETSIZE的文件描述符容量,一般的操作系统的容量比如linux都是单线程select 64个端口.1000个线程,并发6w4怎么也够了那里还要堆10台机器,除非你运行的算法太烂。这种thread从几十跳到700多的做法,一看就是不是懂电信应用的外行写出来得Toy。 至于说C程序容易Core Dump,我倒是经常见到那些厂商拿来的基于Java的电信系统,老是出 outofmemory error. |
|
返回顶楼 | |
发表时间:2006-01-27
T1 写道 "一台机器只可1w封/s, 如果要求10w/s, 加上十台机器不就行了" 这真是信口开河. 做企业级的人对性能真的一点概念都没有 其他的不用管,果以linux的线程在active状况下切换以10ms来计算,从thread1切换到thread 1000,就要耗时10秒,更不用说唤醒线程要将近200ms的时间。这种消耗你真能忍受? 而且如果机器真能开到1000 thread,支持10w路也够了,socket的select的方式依赖于FD_SETSIZE的文件描述符容量,一般的操作系统的容量比如linux都是单线程select 64个端口.1000个线程,并发6w4怎么也够了那里还要堆10台机器,除非你运行的算法太烂。这种thread从几十跳到700多的做法,一看就是不是懂电信应用的外行写出来得Toy。 至于说C程序容易Core Dump,我倒是经常见到那些厂商拿来的基于Java的电信系统,老是出 outofmemory error. 啊啊,T1 大人出手了呀,不如您啥时候来一篇 J10K 以正视听吧,嘿嘿。 |
|
返回顶楼 | |
发表时间:2006-01-28
是微秒吧。要是毫秒,现在的web服务器扔了差不多都要
|
|
返回顶楼 | |
发表时间:2006-01-28
nihongye 写道 是微秒吧。要是毫秒,现在的web服务器扔了差不多都要
所以我说,做企业应用的朋友对系统级的性能问题缺乏了解。 自己查Linux的系统API文档把,schedule函数的参数都是ms, us级别的切换只有uOS这样的RTOS才做的到。现在一般单核心芯片都是10ms切换,每个时间片250ms,wakeup一次200ms.http://www.ahcit.com/lanmuyd.asp?id=1506 这里有具体的Benchmark,双核心的Xeon是5ms,而且只有150 thread.其实这种问题都应该是大学系统结构里面的基本概念.频率和周期,倒数相关,主频3g的X86 cpu,一个cycle 10ns,22条流水,如果是us的话,10us,也只不过执行22000条流水,这还不算有互锁的情况。linux下面一个mutex lock,大概800条指令走40个cycle,400ns差不多0.5us,你自己算算22000条流水够干什么.何况操作系统上还不单跑你一个java,还有诸多的核心服务真正分配到Java的流水指令1/10都不到。 Java之所以不能做到电信级别的规模,最大的障碍是GC.一次GC要pending所有的working thread,唤醒一个就要200ms,上百的thread的wakeup开销已经难以忍受了。所以我看过很多号称电信级别Java应用,要么是响应速度太慢,要么是GC来不及清扫最后out of memory.网上的volano benchmark是专门针对服务器java权威性能测试。http://www.volano.com/report/index.html 2003 march的数据里面。最好的BEA Jrocket,能达到10000+的connection,但是这个时候,并发只有3000+.这还是Jrocket用native thread专门进行优化过的,像使用普遍的Sun JVM ,跑到3000+的connection已经开始out of memory了。除此之外,Java在其他方面还有相当多的问题,比如说磁盘吞吐量很大的Java程序在大并发量下很容易损毁硬盘。因为RadomAccess没有buffer,有buffer的Stream又不是随机读取,使用nio的MapFile,如果文件超大内存就死的挺挺的. 说实话你要是在电信行业里面说10k的并发,你都不好意思和人打招呼。我说几个数据你自己体会吧。电信行业的业务分几大类,第一类到第三类都是电信的基础业务比如中国中等城市的局交换是5000kBHCA为门槛,这种地方都是电路交换TDM的天下一般的纯软件是进不了这一类的业务。剩下的是第四第五类数据交换类业务,俗称class4/5,一般是小型交换局机站IP数据业务,这类业务的入门门槛是500kBHCA,这一段业务现在是TDM和IP均分天下,IP由逐步取代的趋势。比方说一个区的区段交换局像上海的徐汇区87xxx段是500kBHCA的NokiaDX200,占地不过就是33平方。如果如果换成并发10k的SIP或者h.323的softswitch,你拿着普通IBM的商用刀片去堆叠50U好了,我不算你冗余,Qos,网络路由, 在寸土寸金的电信机房里面,一套softswitch占地与庞大的TDM不相上下就能让别人笑掉大牙. 所以一般的电信级的程序,不要说用Java,如果不对操作系统作改造直接用C代码从理论上也是不可可能的。通常操作系统的socket的定义类型是word,连接理论峰值是65535,也就是说,你的单个服务器程序,在不修改操作系统的前提下最多可以承受6万多的用户同时连接。但是,在实际应用中,利用操作系统进行调度能达到一万人的同时连接并能保证正常的数据交换已经是很不容易了,通常这个值都在2000到5000之间。所以现在很多作softswitch的二线厂商,都是自己修改linux内核,对底层的socket甚至网卡驱动程序做很多的特殊化处理,比如加大tcp的滑动窗口,从内核态网卡驱动中直接获取Etheral Frame,然后送入自己的优化过的tcp stack中进行处理,以降低内核态用户态之间的切换,更有甚者,用汇编做us级别时钟替代,然后用Round Robin Scheduling代替原来的LRU来调度代码片,有的时候一个函数要分成10几个代码片执行。我前一个公司做的基于嵌入式linux的softswitch峰值是9U达到1500k的BHCA,即便是这样相对于Nokia的TDM交换机7000kBHCA的纪录还是小菜。 当然也不是说Java真没用武之地,现在电信业里面用Java做一些外围的Net Management System里面用的还挺多,不过也就是做一些EMS,NMS,Billing这样的接入层,真的CTG-MBOSS中间件还是C++/CORBA扛着。比如国内电信用的最多的PowerIBBS,内核是BEA的TUXEDO/C++,只有在接入端才用Java.这些领域里面的Java服务程序也不是随便用企业级的思路设计就能work的。Java要做到10k以下获得高并发也不是做不到,但是需要在内存,thread,concurrency,IO上面走很多tricky的方法。这些细节讲起来就比较琐碎复杂了,以后大家有这方面的需求了我再说吧。除了细节上的讲究以外,设计理念上企业级和电信级应用的设计也是非常不同的。 比方说,做企业级系统的朋友通常相信,过早的优化是愚蠢的,只有到了真正出现性能问题的时候再去refactor代码做优化。这对企业级系统是非常正确的,但是在电信级的程序就无能为力了.一个原因是,我们的编码通常是在一个固定平台上进行的,不过早的做优化是建立在对系统级API性能的信赖,出现性能问题只是局部采用了不良的算法。但是如果性能问题是,出在了系统级API上了,那么那些对系统级API的调用已经弥散于整个程序的各个角落.你真要到除了性能问题才去优化,等于就是要把整个程序写一边.比如说,如果你发现内存分配策略出现问题,你要从用你自己的内存分配策略像mempool之类的替换new,这个时候你就没有方便的办法把程序各个地方的new都refactor到你自己的allocator.第二个原因是,很多性能问题不是你经过一次两次的压力测试就能看出来的。比如内存碎片,很多程序的内存分配算法写的不好,运行1-2个月以后你就会发现系统运行效率成倍的下降.企业级的系统,大不了从新reboot系统,但是在电信级就是一种灾难了。这种性能问题,靠你在几十个子系统里面跟踪代码,然后refactor程序是根本解决不了问题的。解决这种问题目前只能靠有经验的设计人员,预先设计好优化策略。 |
|
返回顶楼 | |
发表时间:2006-01-29
写了一个测试,不知道写得是否有问题
public class TestX { public Integer mutex = new Integer(0);; boolean running = true; int count = 0; public void runTest(); { for (int i = 0; i < 100; i++); { Runnable r = new X();; Thread t = new Thread(r);; t.setDaemon(true);; t.start();; } } class X implements java.lang.Runnable { public void run();{ while (running); synchronized (mutex); { count++; mutex.notify();; try { mutex.wait();; } catch (InterruptedException e); { e.printStackTrace();; } } } } public static void main(String[] args); throws InterruptedException { TestX x = new TestX();; long begin = System.currentTimeMillis();; x.runTest();; Thread.sleep(1000 * 10);; long end = System.currentTimeMillis();; x.running = false; System.out.println("time = " + (end - begin);+" ms");; System.out.println("count = " + x.count);; System.out.println("count/time = " + x.count / (end - begin); + "/ms");; } } 在p3 600,win2000上结果: time = 10125 ms count = 234706 count/time = 23/ms 引用 主频3g的X86 cpu,一个cycle 10ns
?? 1000^3 / 3 * 1000^3 = 0.33ns |
|
返回顶楼 | |
发表时间:2006-01-29
nihongye 写道 写了一个测试,不知道写得是否有问题
public class TestX { public Integer mutex = new Integer(0);; boolean running = true; int count = 0; public void runTest(); { for (int i = 0; i < 100; i++); { Runnable r = new X();; Thread t = new Thread(r);; t.setDaemon(true);; t.start();; } } class X implements java.lang.Runnable { public void run();{ while (running); synchronized (mutex); { count++; mutex.notify();; try { mutex.wait();; } catch (InterruptedException e); { e.printStackTrace();; } } } } public static void main(String[] args); throws InterruptedException { TestX x = new TestX();; long begin = System.currentTimeMillis();; x.runTest();; Thread.sleep(1000 * 10);; long end = System.currentTimeMillis();; x.running = false; System.out.println("time = " + (end - begin);+" ms");; System.out.println("count = " + x.count);; System.out.println("count/time = " + x.count / (end - begin); + "/ms");; } } 在p3 600,win2000上结果: time = 10125 ms count = 234706 count/time = 23/ms 引用 主频3g的X86 cpu,一个cycle 10ns
?? 1000^3 / 3 * 1000^3 = 0.33ns 你这测试,我就不说什么了很低级的错误........ 如果你硬件玩的很转的话应该分得清什么叫做外频什么叫做倍频.3gHZ是主频,主频=外频*倍频,外频是CPU的真实频率,倍频只是为了同步系统总线用的,你倍频低了不等于你的CPU慢了,只是CPU从内存里面读数据的速度慢了,所以主频的快慢和CPU的处理能力是完全无关的,AMD和Intel为了这个东西吵了N年了. 打开你的电脑进入BIOS,看看你的CPU的外频数和倍频数一般是100,133,166,200.200的是5ns,100的是10ns.为了系统稳定和降低散热,Compact PCI架构的高密度工控服务器一般都是在133,166,两个地方乘倍频,加上从L1,L2的潜伏大致就在10ns左右。 艾这里都快成mydrivers的新手区了 |
|
返回顶楼 | |
发表时间:2006-01-29
Trustno1 写道 说实话你要是在电信行业里面说10k的并发,你都不好意思和人打招呼。我说几个数据你自己体会吧。电信行业的业务分几大类,第一类到第三类都是电信的基础业务比如中国中等城市的局交换是5000kBHCA为门槛,这种地方都是电路交换TDM的天下一般的纯软件是进不了这一类的业务。剩下的是第四第五类数据交换类业务,俗称class4/5,一般是小型交换局机站IP数据业务,这类业务的入门门槛是500kBHCA,这一段业务现在是TDM和IP均分天下,IP由逐步取代的趋势。比方说一个区的区段交换局像上海的徐汇区87xxx段是500kBHCA的NokiaDX200................... T1说的都是交换机层面上的事情了,跟应用开发本来就没什么关系吧?我感觉那些都是硬件厂商做的事情了 Trustno1 写道 比如国内电信用的最多的PowerIBBS,内核是BEA的TUXEDO/C++
what's PowerIBBS? Trustno1 写道 Java要做到10k以下获得高并发也不是做不到,但是需要在内存,thread,concurrency,IO上面走很多tricky的方法。这些细节讲起来就比较琐碎复杂了,以后大家有这方面的需求了我再说吧。除了细节上的讲究以外,设计理念上企业级和电信级应用的设计也是非常不同的。 请T1给详细说一下吧 |
|
返回顶楼 | |
发表时间:2006-01-29
引用 外频是CPU的真实频率,倍频只是为了同步系统总线用的,你倍频低了不等于你的CPU慢了,只是CPU从内存里面读数据的速度慢了,所以主频的快慢和CPU的处理能力是完全无关的,AMD和Intel为了这个东西吵了N年了.
既然名字是外频,即是cpu与外部的通讯频率,它对处理速度的制约只是体现在与外部通讯上。至于倍频,你的说法我是第一次听到,主频快慢和处理能力无关,有趣, 新年快乐。 |
|
返回顶楼 | |
发表时间:2006-01-29
引用 what's PowerIBBS?
不好意思打错一个字PowerIBSS ,国内电信用的最多的CTG-MBOSS系统 引用 T1说的都是交换机层面上的事情了,跟应用开发本来就没什么关系吧?我感觉那些都是硬件厂商做的事情了
谁说没有关系,一来现在电信最火的是NGI,NGN,目标就是VOBB,把所有的语音服务构架在Broad Band上面.他最大的卖点就是用软件替代电路交换降低成本。在这里就是软件人员的天下,现在所有的原来的大的硬件厂商像Nortel,Avaya,Nokia都把他们的交换机往IP迁移。二来,即便是TMD交换机他也只负责交换而已,但是如何处理所有的话路的信息,统计,如何监控交换机的工作状态,等等都是应用层面的事情,虽然这些地方的实时性要求比交换路由低一些,但也是需要进行实时的端到端的管理,这同样不是企业级的设计能应付的. 引用 请T1给详细说一下吧
不是不想讲,而是这方面的知识不成系统非常的零碎。有些是基本常识,有些则是非常特殊的tricky。基本常识,虽然很多大家都是知道的,但是一来往往忽视这个问题,二来很多常识大家也只是知道一个大概但是具体的细节往往是一片空白。我举两个常识吧,第一是TCP/IP里面UDP总是要比TCP快因此能选用UDP情况下尽量使用UDP,第二内存句柄的复制(Java的reference,C的point)要比new快100倍,因此对于内存平凡使用的模块采用mempool重复使用内存是一种非常普遍的策略。这两个都是人近皆知的常识,但是很少有人知道这两者其实有着非常微妙的关系。我们知道即使使用mempool,虽然new可以做到o(1)但是,如果分配的内存大小不一样,那么内存deallocate的时候pool的中会出现碎片,需要做归并,这种归并通常都是o(n)的。但是如果每次分配的内存大小一致,那么deallocation的复杂度就能做到o(1)。因此我们设计网络通信协议的时候,UDP的datagram就满足这种情况。udp的一个datagram总是固定长度的,因此这两种策略的互相配合就会使得性能得到很大的提升.当然tcp的recv函数同样可以让他设定收取buffer的长度,但是我们知道tcp是有滑动窗口,而udp是没有的。如果你的buffer长度与滑动窗口不成比例,那么太大tcp的传输效率就会降低太小就会出现丢包的现象,在大并发量的情况下你可能花很大的精力去调整这个值,但是用UDP就能省去这样的大麻烦。很多大并发量的情况下,虽然很多网络应用都是需要维持连接的但是我们通常都用udp来模拟tcp的握手效果,即能达到tcp的效,又能达到udp的速度。当然这只是最基本的一些小技巧,类似的还有很多比如说,mempool里面内存不够分配了怎么办?是new一个新的chunck呢?还是利用两个mempool来做循环池?循环池有循环池的好处,new 内存块有new内存块的好处,不同的高并发应用都是有不同的答案,不能一概而论所以我说只能等大家有具体的应用了我才好对症下药的给大家讲讲这里面的开发经验。 说完了常识,再说一个不常见的tricky。通常作并发离不开produce/custome模式,教科书和我们平时的经验里面,生产者/消费者模式之间的共享的缓冲,一定要用lock锁住,否则就会出现意想不到的情况.但是我前面说了,一个mutex大概800条指令,走40个cycal,耗时0.5us这点时间在10k以上的高并发量的情况下已经足以达到丢包的程度.也就是说,在这种情况下你既要做并发,又不能使用锁。下面肯定有人说,靠你这不是既要马儿跑得快,又要马儿不吃草么。是的,天下的确就是有这样便宜的事情。怎么做呢? 一般来说producer和customer的速度都是不匹配的 ,一个快一个慢,这样的情况我们的教科书里面通常是说用循环缓冲来解决。我们可以把循环缓冲想象为操场的上画满等距小格的跑道,两个人在上面以不同的速度匀速的经过小格,一个快跑者一个慢跑者。为了两个人跑的格数一样,在使用lock的情况下,一个叫做mutex的裁判,会把快跑者栏下让他原地踏步要等待慢跑者赶上来才能继续走。然而mutex裁判的行为,快跑者原地踏步然后再加速到原先速度的行为都会拖慢整个比赛的过程,那么我们可以把这个裁判撤掉,让两个人按正常速度前行,如果快跑者通过慢跑者尚未跑道的空格,那么这些空格就不作数,只有通过慢跑者的路过的空格才作数,因此我们可以让慢跑者在格子上放上一个flag,而快跑者路过这些flag的地方就把flag收掉。那么按照flag来计算,两者跑过的格数就肯定同步了。好了这里有人就要说了,你这不是给猫挂铃铛么?设置flag,去掉flag,难道不需要mutex同步?这个问题我们首先要回到操作系统和体系结构的课程的基本知识。我们操作细分16位比如dos,32位比如xp,64位比如xp64。我们操作系统教科书上说16位,32位是指cpu的寻址能力,这只是其中一个含义,而体系结构书里面说,所谓的16位,32位 是指一个cpu时钟周期里面,能产生多少个电信号。比如8位机cpu一个cycle里面同时产生8个电位信号,组成1个字节,16位一个cycle面处理2字节,而32位一个cycle生成32个电信号处理处理4字节正好是一个int.而对于cpu来说,一个cycle内部的操作是原子操作,所谓的原子操作就是不能再分那当然是同步的。因此在大于32位的操作系统上,读写一个32位整形数肯定是同步的。那么问题也就顺利的解决了,只要我们在共享内存的每一个chunck上留出一个32bit的区段作校验位,producer置1,customer置0,customer先copy所有的内存块,然后看到是1的则保留,是0则丢掉,producer,检查到0往先往里面写数据最后置1,检查到1直接跳到下一格.那么整个过程就同步了。我估计现在很多朋友肯定已经在eclipse上写代码来验证这个方法了,嘿嘿别高兴的太早,这个方法在java上是行不通的,因为java上的一个int其实要转换成vm的bytecode,他是不是会转换成操作系统的一个原子操作,这是基于vm决定的。因此java上你设置一个4字节的int,就不一定是一个原子操作了。那怎么办呢?俗话说大路朝天各走半边,阳关道走不通,俺们就走独木桥。在Java里面,要做无锁 的同步就不能使用内存,而是需要文件。很多人就要说了,文件肯定比内存慢这不是找死么,是的不过只是文件写到磁盘中才是如此。而当文件被改的时候其实并不是实际去读写硬盘,而是利用操作系统的磁盘缓存特性存在缓存里,只要你不掉用fflush,这种函数他还在内存里。因此我们就可以利用文件句柄结构里面的一个32位整型的AT(access time)来做flag.你可以用jni写一个c的外部DLL,让java调用,这样你就能在Java里面保证得到原子的int读写. |
|
返回顶楼 | |
发表时间:2006-01-29
引用 public static void main(String[] args) throws InterruptedException {
TestX x = new TestX(); long begin = System.currentTimeMillis(); x.runTest(); Thread.sleep(1000 * 10); long end = System.currentTimeMillis(); x.running = false; System.out.println("time = " + (end - begin)+" ms"); System.out.println("count = " + x.count); System.out.println("count/time = " + x.count / (end - begin) + "/ms"); } 这段代码你想证明什么呢?runtest()+sleep(1000*10)的速度?runtest不过是开启100个thread的速度罢了,这个时间除count的有什么意义呢?另外x.running=false,你能保证所有的thread瞬时退出,所有count计数完毕? 至于一个cycle到底有多少时间这是别人在Intel平台上用RTOS的测试结果 http://www.koretide.com.cn/faq.php 用户进程无阻塞加锁Mutex时间开销:205.6纳秒 如果在普通的操作系统上,是这个数值的2到3倍。 你要算0.33ns的cycle也没有问题,但是你这样算就要加上Latency。光比主频的cycle有什么意义,5个cycle的Latency,CPU根本不干事情。实际拿来运算的 还是和用主频算得一样。 hehe,也祝你新年快乐。 |
|
返回顶楼 | |