zoukankan      html  css  js  c++  java
  • 【问底】夏俊:深入站点服务端技术(一)——站点并发的问题

    摘要:本文来自拥有十年IT从业经验、擅长站点架构设计、Web前端技术以及Java企业级开发的夏俊,此文也是《关于大型站点技术演进的思考》系列文章的最新出炉内容。首发于CSDN,各位技术人员不容错过。

    注:本文首发于CSDN,转载请标明出处。

    【编者按】 本文来自拥有十年IT从业经验、擅长站点架构设计、Web前端技术以及Java企业级开发的夏俊。此文也是《关于大型站点技术演进的思考》系列文章的最新出炉内容。首发于CSDN。各位技术人员不容错过。

    下面为正文:

    一、 引子

    《关于大型站点技术演进的思考》已经连载完了两个系列,它们各自是《存储的瓶颈》和《站点静态化的处理》。这两个系列相应到站点里的组件就是存储端和浏览器端。站点除了这两端外,另一端那就是服务端了,服务端上接浏览器端。下承存储端,所以当我们想让站点的浏览器端或存储端性能更加优秀的时候,就不得不去考虑服务端的问题,由于服务端和它们永远都是剪不断理还乱的关联性。

    如今我要开启《关于大型站点技术演进的思考》这个主题下最后一个系列,这个系列就是 讨论站点组件里最后一端服务端了,由于服务端和浏览器端以及存储端存在着一种永远都剪不断的关系。所以本系列还会解说和其它两端相关联的技术,只是本系列的讲述的深度会更高些,希望通过这样的深入的研究让我们更加深入的理解那些能作用于浏览器端和存储端的服务端技术。当然服务端除了上接浏览器端,下承存储端作用外。它自身还有自己的技术范畴,那就是怎样使用服务端技术实现站点的业务逻辑了,以上这些就是本系列将要讨论的主题了。

    本系列大概会按下面思路进行讨论:

    • 首先是从和浏览器端相连的服务端技术讲起,这块知识映射到MVC模式那就是控制层的相关技术,这里主要包括:并发性。并发和集群的问题。HTTP协议的转化问题等等,最后我将以MVC里C层即控制层的作用来总结下这块的技术;
    • 接下来就是解说服务端实现业务逻辑层的技术,这个映射到MVC模式就是M层即模型层的问题了。这里面我不会去讨论怎样使用服务端技术写业务逻辑,而是以模型层和控制层关系的角度、模型层和存储层关系的角度以及模型层的服务治理的角度来讨论模型层架构设计的问题。
    • 然后就是服务端和存储端相关的技术了,这里面主要就是我在存储瓶颈系列里提到的数据訪问层。该层的作用是为了迁移存储层的计算功能,从而达到减轻存储层系统压力的目的。上面内容讲完以后我会參照以上的技术总结下大型站点分布式系统和站点SOA应用的特点。

    当然上面的知识都是我自己多年经验和自己所掌握知识的总结,如今还没有完整的知识雏形,所以在写的过程里非常有可能会依据实际情况进行调整。无论最后结果怎样,上面的列举的慷慨向我都会尽力讲到的。

    二、关于站点并发的问题

    以下就我開始讲服务端和浏览器端相关部分的技术了,首先从站点的并发性開始讲起吧。

    1)《关于大型站点技术演进的思考》前两系列的内容

    存储的瓶颈和站点静态化处理參见本人的 博客

    2)站点并发问题概述

    什么是站点的并发?这个问题答案非常easy,站点的并发就是指站点在同一个时间能够同一时候处理多个用户请求。谈到站点的并发,非常多朋友非常自然的会想到多线程技术。多线程能够使得一个应用程序并行处理多个计算任务,这个技术的作用类推到Web应用里那就是多线程技术能够让站点并行处理多个请求,因此多线程技术是能够用来处理站点的并发问题的。

    所以当我们要去理解站点的并发问题时候,首先要解决的问题就是怎样使用多线程技术。

    当我们学好了多线程技术。是不是就能够解决站点的并发问题了?回答当然是能够的,只是一个站点对并发的要求绝对不是只要求我们会使用多线程技术那么简单了,当站点并发的问题解决后。我们立即就要面临一个相同迫切的问题了,那就是怎样提升站点的并发能力。这个问题落到实处就是怎样让站点在有限的系统资源下并发能力变得更强,换句话说就是怎样让有限的系统资源下。站点的并发数更大,当站点并发数变大以后,我们又要考虑怎样让单个请求处理效率更高。这两个内容就是本篇文章的主题了。

    本篇文章主要是谈论如何提升单台server的并发能力问题,下一篇文章谈论的是当站点处理用户请求的服务端使用了集群技术后,针对并发的处理会发生如何的变化呢。

    3)多线程技术

    并发技术相应的是多线程技术,而多线程技术又是建立在线程技术上。那么我们这里首先谈谈线程技术的问题。

    第一步我要做的是明白什么是线程,以下是百度百科里对线程的解释。详细例如以下:

    线程,有时被称为轻量级进程(Lightweight Process。LWP),是程序执行流的最小单元。

    一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体。是被系统独立调度和分派的基本单位,线程自己不拥有系统资源。仅仅拥有一点儿在执行中不可缺少的资源,但它可与同属一个进程的其他线程共享进程所拥有的所有资源。

    从这个定义里我们知道线程有例如以下特点:

    • 特点一:线程是进程的一个实体,也就是说线程的母体是进程,这是线程的范畴问题。
    • 特点二:线程是轻量级的,它自己不拥有系统资源。可是我们知道线程运行时候是能够操作硬盘的文件。也能够操作内存的数据,那么这些被线程操作的资源从哪里来的呢?上面的解释里回答了这个问题。那就是线程能够共享进程的资源。换句话说线程操作的资源是进程的资源。

    • 特点三:线程自己也有寄存器和堆栈。这些东西本身也是能够存储数据的,数据也是要占用系统的部分资源,那么这个解释也就说明,线程本身也是能够占一定系统资源了,仅仅只是这个资源比較少而已,所以这个解释就补充了特点二的场景:线程操作的资源除了它所在进程的资源外还有它自身能控制的资源,这一点比較重要。我会在后面讲线程安全问题里提到线程自有资源的使用问题的。

    单个线程就是一个独立的程序运行流。我们在学习计算机语言,写的练习代码都是在一个单线程的场景下进行的,单个线程放到站点并发处理的范畴里,它有个问题是我们一定要注意的,那就是怎样提升单个线程的运行效率问题,也就是说我们怎样让一个线程运行的更快同一时候还要让一个线程运行时候所消耗的系统资源更低。为了解决问题,我们就要分析下单个线程的运作特点,看看线程运作流程里那些方面会影响到线程的运行效率问题。

    线程运作流程非常easy。我们使用线程时候首先是创建一个线程,线程创建好以后就是使用它了,线程使用完成以后就要销毁它了。把这个流程放到请求处理场景里,我们就会发现线程的创建过程和线程的销毁过程事实上是和处理请求的逻辑无关。可是一个线程又必须经历这三个阶段,因此线程创建的时间和线程销毁的时间也会被统计到请求处理时间里,那么我们就会想有没有办法能够消除线程创建和销毁所花的时间对请求处理的影响呢?

    以前有人在Linux上做过一个測试。这个測试结果就是一个线程创建和消耗至少要消耗2MB的内存。依照这个结论。假设我们在Linux上创建1000个线程那么系统至少要消耗2G以上的内存。这个消耗是非常惊人,假设我们每次使用一个线程就来个创建销毁,那么请求处理时候就会要新增非常多无谓的系统资源消耗。假如碰到server系统资源非常紧张时候,线程的频繁创建和销毁的过程就会侵占很多其它系统资源从而影响到线程的运行,这个问题我们又该怎样来攻克了?

    要解决问题我们首先要回想下线程的运作流程,这个结果例如以下图所看到的:

     

    这张图不是我们希望看到的,我们希望看到以下这种流程图。例如以下图所看到的:

     

    我们希望请求处理流程仅仅是作用在线程运行这块。那么在实际的生产实践里我们又是怎样来解决问题了?解决方式就是使用线程池技术,线程池技术就是基于线程创建和销毁操作影响性能的角度来设计的。只是线程池技术并不是简单的事先创建好一批线程,然后统一销毁一批线程那么简单,这里我以Java的JDK里自带的线程池技术为例来讲讲关于线程池技术的使用,内容详细例如以下:

    JDK里的线程池对线程池大小的设定使用了两个參数,一个是核心线程个数,一个是最大线程个数。核心线程在系统启动时候就会被创建,假设用户请求没有超过核心线程处理能力,那么线程池不会再创建新线程,假设核心线程个数已经处理只是来了,线程池就会开启新线程,新线程第一次创建后,使用完成后也不是马上对其销毁,也是被会收到线程池里,当线程池里的线程总数超过了最大线程个数,线程池将不会再创建新线程,这样的做法让线程数量依据实际请求的情况进行调整,这样既达到了充分利用计算机资源的目的。同一时候也避免了系统资源的浪费,JDK的线程池还有个超时时间。当超出核心线程的线程在一定时间内一直未被使用,那么这些线程将会被销毁,资源就会被释放,这样就让线程池的线程的数量总是处在一个合理的范围里;假设请求实在太多了,线程池里的线程临时处理只是来了。JDK的线程池还提供一个队列机制,让这些请求排队等待,当某个线程处理完成,该线程又会从这个队列里取出一个请求进行处理,这样就避免请求的丢失,jdk的线程池对队列的管理有非常多策略,有兴趣的童鞋能够查查百度,这里我还要说的是jdk线程池的安全策略做的非常好,假设队列的容量超出了计算机的处理能力,队列会抛弃无法处理的请求,这个也叫做线程池的拒绝策略。

    由上面对JDK自带线程池的介绍,我们发现使用线程池我们要考虑例如以下的问题。详细例如以下:

    • 问题一:线程池初始化的时候我们究竟要事先创建多少个线程放在池子里比較合适。

    • 问题二:线程池初始化时候假设创建的线程过多会有什么问题。
    • 问题三:一台server因为受限于自身能力的限制比如CPU的能力。内存的能力等等,那么一台server能够创建多少个有效线程的数量事实上是有个临界值的。那么当业务方要求创建超出临界值的线程时候我们的处理策略是如何的呢。

    问题三的我在介绍JDK里线程池的设计方案时候已经提到了,解决方法就是当业务方要求超出临界值时候。详细就是超出线程池最大线程数的时候,我们用一个队列先缓存这些请求,等线程池里的线程空暇出来后,再去从队列里取出业务请求交付给线程进行处理。

    至于问题一和问题二。这个就和多线程的技术相关了,为了更好的解答它,我这里先来介绍下多线程技术。

    多线程技术的核心就是让多个线程同一时候被运行。用户的感受就是计算机能够并行运行计算任务,那么我们首先要理解下多个线程是怎样进行并发操作的。详细例如以下:

    一个程序包括两个部分,它们就是计算和存储了,好的程序就和一个活生生的人一样,存储相当于人的臭皮囊也就是身体了,而计算就是人的大脑了,而程序的计算是通过CPU来完毕的,所以线程的并行运行效果就是看CPU是怎样并行处理多个线程的机制了。那么CPU怎样做到并行处理呢?事实上CPU并不能做到并行处理,CPU仅仅能一次运行一个计算指令。听到这个回答,我们的头是不是一下子变大了,CPU没法做到并行处理,那我们看到活生生的并行操作究竟是怎么回事呢?

    线程技术里有个概念叫做时间片,要解释时间片我们就要从进程说起了,当一个进程被操作系统运行时候,CPU会给这个进程分配一段运行时间,当进程里面创建了线程以后,操作系统会把进程分配到的运行时间分成片段赋予给线程,假如进程里仅仅有一个线程,那么这个线程的时间片的长度就和进程的时间段基本一致,假设进程里开启的线程有多个。假设这些线程没有设置什么优先级策略。那么每一个线程就会平分到同样的时间片,时间片就是线程被CPU运行的单位时间,当CPU依照时间片规定的时间运行了某个线程后。假设线程的CPU计算没有所有运行完成,那么CPU就会让线程先挂起来等于是让线程处于一个等待状态。CPU的调度机制找到下一个线程。当下一个线程时间片所规定的时间运行完成后,CPU就会让这个线程挂起来。再去找第三个线程,这个过程依次进行下去。等进程里开启的线程都运行完成后。第一个线程才会又一次開始运行。这个机制就是线程的轮询机制了,也是我们常说的线程切换机制背后的原理了。

    该机制能够保障在一个固定时间范围内,所有线程都能得到运行,因为线程的运行速度很快,所以给用户的感觉就是多个线程是能够并行运行的。

    由上面的原理我们来看看这个实例,详细例如以下:

    有一个线程被分配到的时间片长度是10毫秒。假设这个线程没有其它干扰。它运行完成须要花费50毫秒,那就等于要运行5次时间片。假如我们再新增了一个线程,该线程时间片也是10毫秒。也要运行50毫秒才干运行完成,那么当中一个线程运行完一次时间片。依照轮询机制另外一个线程也要被运行。最后我们就会发现第一个线程运行时间就变成了100毫秒,假设我们再加上CPU的调度所花的时间,该线程的运行时间就会远远大于100毫秒。尽管100多毫秒人的感官是非常难觉察到,可是这个做法毕竟让单个线程的运行效率大幅度减少了,假设我们线程开启的很多其它,那么单个线程运行效率也就会变得更低。

    有了这个结论我们再去看看线程池问题一和问题二,假设我们一開始创建了太多线程。并且这些线程大部分都会被闲置,那么这些闲置的线程就会让有效线程的运行效率大大降低。同一时候闲置的线程还会消耗系统资源。而这些被消耗的系统资源都没实用到业务处理上,所以成熟的线程池方案就会设计核心线程和最大线程的概念。它们能够让线程池依据实际业务需求和系统负载能力做到动态调节。这样就能够降低开启很多其它线程影响线程运行效率的问题,也能够让计算机的系统资源得到更加有效的利用。

    4)站点的并发与多线程技术

    多线程技术能够实现并发操作。站点的并发场景很适合使用多线程技术,那么我们就先来谈谈怎样使用多线程技术来实现站点并发,首先我们从单个站点请求处理场景開始说起吧。

    单个网络请求是从浏览器端发送,通过网络传输到服务端,服务端接收到数据后进行处理,处理完毕后服务端再把响应通过网络发送给浏览器端。而这个过程都是使用服务端一个线程全程陪同的完毕,这个做法似乎没什么问题,这里我先给大家看一个表格,这个表格是Node.js 的作者 Ryan Dahl 为 JSConf 大会所作的演讲里提供的,详细例如以下所看到的:

    I/O设备

    CPU 调用周期

    CPU一级缓存

    3

    CPU二级缓存

    14

    内存

    250

    硬盘

    41000000

    网络

    240000000

    从这个表格里我们发现,网络IO的处理时间是CPU一级缓存处理时间的一亿倍。假如我们把CPU一级缓存的处理速度等同于CPU的计算速度。这里我如果CPU运行时间是1毫秒。那么当一个线程里有网络操作时候,CPU要等待将近1亿毫秒的时间才被运行,这1亿毫秒时间里CPU不知道能够做多少事情啊。

    我们再以Java的网络编程为例进一步说明这个问题。站点的网络传输协议是HTTP协议,HTTP协议使用的是TCP协议进行网络通讯的。java里使用socket技术来编写TCP通讯程序,最主要的socket编程里当client有数据传递到服务端后,服务端的ServerSocket就会开启一个线程处理这个请求了。可是服务端的处理须要client把所有传输数据完成后才干做兴许处理,所以当client数据还没传输完成,处理线程就要在哪里等待传输数据完成,我们由上表能够知道线程的等待时间相对于CPU运行时间是何等长了,等待的线程什么都没有做的时候还要參入线程的轮询处理里。因此它还会影响其它线程的运行效率。

    单个server本身所能承载的线程数量是有限的,假如某个线程就这种被闲置起来就会导致线程的利用率十分低下。这些问题我们究竟该怎样来攻克了?

    5)怎样提升线程的使用效率?

    要解答标题的问题,我们首先要分析下站点请求的特点,站点的请求事实上包括两个操作步奏,这两个步奏各自是IO操作和CPU操作,而线程的作用主要是体如今CPU的操作上,假设我们在一个线程里包括IO操作和CPU操作,特别是使用到非常慢的网络IO操作时候,那么IO操作的效率就会影响到CPU操作的运行效率,假设我们能把这两个操作分解开来,让线程仅仅去关心CPU操作,这样单个线程就能被更加充分的利用起来,但是IO操作毕竟是请求操作里不可切割的操作,那么我们究竟该怎样破这个局了?

    破这个局的方法就是使用赫赫有名的reactor设计模式,我们来看看reactor模式的设计图。例如以下图所看到的:

     

    reactor模式里有一个组件就是reactor组件。它事实上是一个单独的线程。client的请求首先是发送到服务端的reactor线程进行处理,reactor线程採取轮询的方式轮询client的请求,当它发现某一个请求的传输数据完成后,reactor就以事件通知的方式从线程池里取出一个线程进行兴许处理。这样线程池里的每一个线程都能被充分的使用。

    我们再细致分析下reactor模式,我们发现啊,reactor模式事实上并没有提升一个请求的总体运行效率,而是把请求里效率最低的IO操作和CPU计算操作分成两步进行运行,这个方式就等于是把同步请求操作成了一个异步运行操作,尽管该方式没有提升请求总体的处理效率,可是它能让服务端的线程利用率更高,这也就变相的让站点的并发处理能力增强了,并且该方式能够避免线程被闲置在那里空转的问题。假如我们能有效的控制好线程的合理数量,那么该方式还是有可能提升单个请求的运行效率的。

    Reactor模式处理请求的模式和上节里讲到请求处理模式。它们的核心问题都是发生在请求的IO处理问题上,所以上节的IO处理场景在java技术领域里有个专有名词表述那就是BIO。中文解释就是堵塞的IO,这个堵塞的含义就是指当一个IO操作运行时候它会独占IO处理的线程。其它IO操作就被此IO操作堵塞起来,而Reactor模式下的IO操作在java技术里也有个专有名词那就是NIO,NIO最早出如今JDK1.4版。当时官方解释是New IO。通过我们上面的论述我们发现NIO事实上是针对BIO的堵塞问题设计的。所以我们习惯把NIO称之为非堵塞IO,非堵塞IO操作不会堵塞线程的操作。

    古老的Apacheserver和java里的tomcat容器新版本号都採取了NIO技术。可是Apache和它同类型的ngnixserver相比,所能承载的并发能力实在差太多了,实际场景下Apache能支持5000个并发就要惊为天人了,而ngnix官方文档里就说它能够支持5万并发,而实际场景里支持3万并发那是一点问题都没有的。为什么ngnix能够达到如此高的性能呢?它有什么独门绝技呢?

    6)C10K的目标

    业界有一个C10K的目标。所谓c10k问题,指的是server同一时候支持成千上万个client的问题,也就是concurrent 10 000 connection(这也是c10k这个名字的由来)。

    我们想让Apache同一时候支持上万并发这个在实际场景下是一件基本无法完毕的事情。听到我的说法不知道会不会有朋友不服气。我要是把server的配置搞得高高的。我就不信Apache不能支持上万并发,假设我们这么做了我们就会发现server硬件的提升并不能导致Apache并发能力的线性提升。假如我们设定Apache在5000并发下我们通过添加硬件性能能够近似的提升Apache的并发能力,当Apache并发达到5000后。我们再把硬件性能提升一倍,终于我们一定会发现Apache的并发数并不会变成1万,它能支持到7000以上就谢天谢地了。并且Apache的并发越高。其单个连接的处理性能下降的也很厉害。而这个场景迁移到ngnix上。结果就大不同样了,ngnix基本能够做到硬件性能提升,其并发性能也能达到线性提升的目的。

    由此可见ngnix实在太优秀了。它究竟用什么独门秘籍了?大家是不是非常急迫的想知道了,以下我就来讲讲ngnix的设计思想了。

    首先我们要分析下多线程做并发的问题,线程本身是要消耗系统资源的,假设我们开启线程非常多,计算机就得抽取很多其它资源维护这些线程,所以线程越多浪费掉的系统资源也就很多其它,这是多线程做并发的第一个问题。

    多线程做并发第二个问题就是多线程的并行处理机制了。线程的并发要通过线程轮询或者说线程切换来保证,而线程的切换会影响单个线程的运行效率。并且CPU管理线程切换也会消耗CPU的计算能力,所以当我们线程开启的越多,单个线程的运行效率也会随之下降。

    这也就是Apache容器在高并发下表现出来的问题。

    明白了问题。解决方法就出来了。我们假设想让线程运行效率更高我们就不要创建太多线程,这样就能够降低维护线程的开销,同一时候也能降低线程切换的开销。最理想的方案是我们仅仅使用一个线程来处理全部并发,假设一个线程能够处理好全部并发,那么线程的开销问题就基本能够忽略不计了。

    依照这个思路我们就要抛弃多线程处理并发的思路了,这种想法咋一看是不是有点毁人三观了。那么一个线程究竟可不能够处理并发了?问题的答案是肯定的。

    这里我先不解说ngnix的设计,我们先讲讲Node.js技术,Node.js作者之所以要创建Node.js,源自于他对怎样设计一个高效的Web容器的思考,他觉得高效的Web容器要採取异步机制,事件驱动机制和非堵塞的IO处理。看到这个描写叙述我们发现这个和我前面讲到reactor模式是何其的相似,可是Node.js在异步机制和事件驱动做的比上面的reactor模式方案更加优秀,在网络IO处理上。Node.js和reactor模式基本一致,在Node.js有一个专门的模块异步处理IO操作,只是Node.js对IO操作已经完毕的请求的兴许处理就和reactor模式大不同样了,Node.js仅仅用一个线程完毕这个请求的兴许处理。这个线程处理请求的方式借鉴了多线程里并行处理的原理,由于请求最耗时的IO操作被抽取出来异步处理,所以处理兴许请求的单线程仅仅须要运行请求环节里效率最高的部分,这就让每一个请求的处理速度变得非常快了,人眼基本感觉不出这个顺序性操作,只是Node.js单线程处理请求的方式和多线程的轮询机制是不太一样的。首先轮询本身的效率是非常低下的,为了轮询,操作系统把线程拆分成若干个步奏运行,这个做法就添加了我们对多线程处理的难度,假设碰到不同线程操作一个共享资源。由于步奏的拆分就非常有可能产生错误的操作共享资源的行为,这也就是线程安全问题的源头了。这个问题我后面会将具体讨论,这里就不展开论述了。

    Node.js吸取了多线程方案的经验教训,它没有採取轮询方式处理请求。而是以队列方式一个个运行请求,这样就能够充分利用CPU的操作时间,同一时候也规避了线程安全的问题。并且採取这个方式,当一个请求到达服务端后服务端添加的系统资源消耗基本和请求本身的系统资源消耗一致,所以和Node.js机制类似的ngnixserver当并发上去后。请求处理性能也不会像Apache那样陡降下去。

    只是Node.js处理机制里另一个细节我们要特别注意下,那就是异步IO操作怎样和主线程处理协同起来的。也就是当IO处理完成后我们怎样把兴许操作插入到主线程下,这个问题看起来非常easy,我们直接把新的请求放到请求队列的最后不即可了吗,那么问题来了。这个问题就和Ajax技术的问题类似。Ajax能够异步请求。可是由于请求要通过网络给服务端处理,所以Ajax从開始运行到最后获取响应处理响应结果之间就存在非常大的时间差,而这个时间差是非常难把控的。或许有时是1秒,有时就变成了3秒,假设我们简单把结果代码放在Ajax处理后面,那么我们仅仅能烧香拜佛希望响应能在代码运行到结果处理位置时候到达。那么Ajax技术是怎样解决问题呢?Ajax使用回调函数机制来破这个题。当服务端响应全然返回到client后,javascript里有相关的事件就会通知回调函数立即运行,这事实上就把请求发送和响应处理做成了一个异步模式,这也就是Node.js解释里提到的异步机制和事件驱动了,异步机制事实上就是指的是回调函数的使用,这个机制放到Node.js对主线程请求队列的操作里,那就是异步IO处理完成后。事件机制就会把请求兴许操作以回调函数的方式放在请求队列的后面,这样就能有效的保证请求处理的时序性了。

    Ngnix的设计思想和Node.js的设计思想类似,只是ngnix使用的不是谷歌的V8引擎完毕这个机制的,而是直接使用操作系统的类似机制完毕,所以ngnix的并发能力比Node.js会强非常多,它和Apache相比那就是质的飞越了。

    好了。本篇内容讲完了,下篇文章我開始再补充下单台server并发处理的内容。之后我就要讨论并发和集群之间的问题了。

    (责编/钱曙光) 

    作者简单介绍:夏俊,拥有十年的IT从业经验,擅长的技术领域有站点的架构设计、Web前端技术以及java企业级开发。热爱技术,喜欢分享自己的技术研究成果和经验


    由“2015 OpenStack技术大会”、“2015 Spark技术峰会”、“2015 Container技术峰会” 所组成的 OpenCloud 2015大会于4月17-18日在北京召开。 日程已经所有公开懂行的人都在这里!(优惠票价期,速来)

    很多其它《问底》内容:

    《问底》是CSDN云计算频道新建栏目,以实践为本,分享个人对于新时代软件架构与研发的深刻见解。在含有“【问底】”字样标题的文章中。你会看到某个国外IT巨头的架构分享。会看到国内资深project师对某个技术的实践总结,更会看到一系列关于某个新技术的探索。

     《问底》邀请对技术具有独特/深刻见解的你一起打造一片仅仅属于技术的天空。详情可邮件至qianshg@csdn.net。 

  • 相关阅读:
    二分练习题4 查找最接近的元素 题解
    二分练习题5 二分法求函数的零点 题解
    二分练习题3 查找小于x的最大元素 题解
    二分练习题2 查找大于等于x的最小元素 题解
    二分练习题1 查找元素 题解
    code forces 1176 D. Recover it!
    code forces 1173 B. Nauuo and Chess
    code forces 1173 C. Nauuo and Cards
    吴恩达深度学习课程笔记-15
    吴恩达深度学习课程笔记-14
  • 原文地址:https://www.cnblogs.com/brucemengbm/p/7103075.html
Copyright © 2011-2022 走看看