精华内容
下载资源
问答
  • webService和WebApi的区别

    千次阅读 2020-12-10 21:55:54
    3、webService用于内部通信服务好,webApi用于外部服务请求好。 4、Response可以被Web API的MediaTypeFormatter转换成Json、XML 或者任何其他的格式。 5、soap它描述了一种在分散或分布式的环境中如何交换信息...

    1、webservice基于简单对象访问协议Simple Object Access Protocol(soap),XML传输消息,只能部署在IIS上。

    2、webApi为简单的HTTP新框架,合适构建移动客户端服务。

    3、webService用于内部通信服务较好,webApi用于外部服务请求较好。

    4、Response可以被Web API的MediaTypeFormatter转换成Json、XML 或者任何其他的格式。

    5、soap它描述了一种在分散或分布式的环境中如何交换信息的轻量级协议。soap在http协议的基础上,一个基于XML的协议。

    SOAP是个通信协议, SOAP在HTTP协议的基础上,把编写成XML的REQUEST参数, 放在HTTP BODY上提交个WEB SERVICE服务器(SERVLET,ASP什么的) 处理完成后,结果也写成XML作为RESPONSE送回用户端

    webservice优点:

    1、分布式:不同应用程序和不同系统平台上通信,soap通信协议避免复杂协议转换。

    2、跨开发语言调用。

    3、穿越防火墙的能力强

    4、因为基于xml所以跨平台可互操作性强。

    5、从理论上讲,开发人员可通过调用Web应用编程接口(API)(就像调用本地服务一样),将Web服务集成到应用程序中,不同的是Web API调用可通过互联网发送给位于远程系统中的某一服务。

    webService缺点:

    相对较慢:使用soap协议去完成简单的数据传输的效率不高.

    webApi优点:

    1、json数据传递

    2、http请求,与前端交互方便。

    3、移动客户端服务较好。

    webApi缺点:

    Web API是一个用于仅通过HTTP构建非基于SOAP的服务的框架 - 因此使用此框架的传输协议不会更多。基于SOAP可以使用很多传输协议:HTTP,TCP,命名管道,MSMQ等等。

    展开全文
  • Web API应用支持HTTPS的经验总结

    千次阅读 2015-11-03 15:03:49
    在我前面介绍的WebAPI文章里面,介绍了WebAPI的架构设计方面的内容,其中提出了现在流行的WebAPI优先的路线,这种也是我们开发多应用(APP、微信、微网站、商城、以及Winform等方面的整合)的时候值得考虑的线路之一...

    在我前面介绍的WebAPI文章里面,介绍了WebAPI的架构设计方面的内容,其中提出了现在流行的WebAPI优先的路线,这种也是我们开发多应用(APP、微信、微网站、商城、以及Winform等方面的整合)的时候值得考虑的线路之一。一般情况下,由于HTTP协议的安全性,传递的参数容易被拦截,从而可能导致潜在的危险,所以一般WebAPI接口层都采用了HTTPS协议的,也就是采用SSL层来对数据进行安全性的加密的。

    1、HTTPS基础知识介绍

    1) HTTPS

        HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。https:URL表明它使用了HTTPS,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。这个系统的最初研发由网景公司进行,提供了身份验证与加密通讯方法,现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。

    2)HTTPS和HTTP的区别

      一、https协议需要到ca申请证书,一般免费证书很少,需要交费。

      二、http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。

      三、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

          四、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

    3)https的实现原理

    有两种基本的加解密算法类型

    1)对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有DES、AES等;

    2)非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的非对称加密算法有RSA、DSA等。

     

    https的通信过程 

     

    4) https通信的优点

     1)客户端产生的密钥只有客户端和服务器端能得到;

    2)加密的数据只有客户端和服务器端才能得到明文;

    3)客户端到服务端的通信是安全的。 

     

    2、SSL基础知识介绍

    1)SSL安全套接层协议(Secure Socket Layer)

      为Netscape所研发,用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取及窃听。目前一般通用之规格为40 bit之安全标准,美国则已推出128 bit之更高安全标准,但限制出境。只要3.0版本以上之IE或Netscape浏览器即可支持SSL。

      当前版本为3.0。它已被广泛地用于Web浏览器与服务器之间的身份认证和加密数据传输。

    SSL协议位于TCP/IP协议与各种应用层协议之间,是一种国际标准的加密及身份认证通信协议,为TCP提供一个可靠的端到端的安全服务,为两个通讯个体之间提供保密性和完整性(身份鉴别)。SSL协议可分为两层:SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。

    2)SSL协议特点

    1)SSL协议可用于保护正常运行于TCP之上的任何应用协议,如HTTP、FTP、SMTP或Telnet的通信,最常见的是用SSL来保护HTTP的通信。

    2)SSL协议的优点在于它是与应用层协议无关的。高层的应用协议(如HTTP、FTP、Telnet等)能透明地建立于SSL协议之上。

    3)SSL协议在应用层协议之前就已经完成加密算法、通信密钥的协商以及服务器的认证工作。在此之后应用层协议所传送的数据都会被加密,从而保证通信的安全性。

    4)SSL协议使用通信双方的客户证书以及CA根证书,允许客户/服务器应用以一种不能被偷听的方式通信,在通信双方间建立起了一条安全的、可信任的通信通道。

    5)该协议使用密钥对传送数据加密,许多网站都是通过这种协议从客户端接收信用卡编号等保密信息。常用于交易过程中。

     

    3)SSL功能

    1)客户对服务器的身份认证:

    SSL服务器允许客户的浏览器使用标准的公钥加密技术和一些可靠的认证中心(CA)的证书,来确认服务器的合法性。

     

    2)服务器对客户的身份认证:

    也可通过公钥技术和证书进行认证,也可通过用户名,password来认证。

     

    3)建立服务器与客户之间安全的数据通道:

    SSL要求客户与服务器之间的所有发送的数据都被发送端加密、接收端解密,同时还检查数据的完整性。

     

    3、支持SSL的CA证书购买 

    上面介绍一些基础知识,我们可以简单的概括一下,就是引入了HTTPS,可以很好解决接口参数的安全性问题。这些在很多大厂商的接口里面,都是使用HTTPS协议的,如腾讯微信、支付宝等接口,特别对于支付内容,使用HTTPS是必须的。

    对于支持HTTPS,核心的问题就是解决证书的问题,必须是由第三方权威机构颁发的证书,这样才能达到不被伪造的可能性,一般我们采用的是CA证书,这些证书是需要通过付费购买的(天下没有免费的午餐),证书的购买的供应商网站有很多,可以选择自己合适的进行购买。

    1)国外的GoDaddy平台购买证书过程

    这个参考博客园站长dudu的介绍内容,这个goDaddy是支持支付宝用美元购买的,使用平台是英文,可以购买域名、证书等服务,是一个影响力较大的厂商。

    1) 打开godaddy.com网站,通过菜单进入Products -> SSL&Security -> SSL Certificates,选择Protect All Subdomains("Wildcard"), 在Pick your plan type中,选择Standard(Validates domain ownership),然后完成购买。

    2)进入My Account -> SSL CERTIFICATES,创建证书(Certificate),创建时将之前得到的CSR内容复制到“CSR文本框”中。

    3)接入来进入GoDaddy的审批流程,在审批过程中需要验证域名的所有者(dns填加记录或上传html文件至网站目录),验证成功后很快就会生成CA证书。

    4)下载CA证书文件至生成CSR的服务器上。

     

    普通证书费用是63美元左右,可以通过支付宝进行交易。

     

    2)国内沃通平台购买证书过程

    这个是纯中文版本的平台,比较方便使用,费用也比上面的贵一些,分的级别好像也多一些,相对上面那个国外的GoDaddy的三个种类证书,这个产品线分了6个类型。最低的也要接近500块,相对GoDaddy来说,费用要多一些了。由于是初步使用,也就购买了这个使用了。

    申请购买也很简单,一步步按提示操作填写内容即可,大致分为这几步:

    1)输入购买的证书类型,以及年限等资料;

    2)输入域名的信息,以及需要域名对应的邮件进行验证;

    3)提交购买订单后,使用在线支付或者使用公司账号汇款到指定账号;

    4)客服验证后,技术人员提供证书生成操作,我们在列表里面尽快使用证书密码下载证书。

     

    最后成功后,在订单信息里面,有这样的列表,提供了两个SHA1和SHA2两种加密协议的证书,官方建议使用SHA2。

     

    4、Web API应用支持HTTPS

    有了证书,我们在云服务器上(如阿里云)的IIS里面的证书模块里面,可以导入已有的证书。

     

    导入证书成功后,我们可以看到列表里面有具体的证书了,同时双击可以查看证书详细信息。

    创建一个网站,并指定使用这个证书,端口采用443即可。

    一般情况下,我们创建这个步骤后,你可以使用类似这样地址:https://www.iqidi.com/测试下具体的网站是否已经开通了,如果可以正常访问,那说明你的443端口已经启动侦听了,如果没有,可能是没有启动,或者是防火墙禁止了,一般情况下,我们还是需要在防火墙里面增加443端口的入站规则,设置为允许,否则可能被屏蔽了。

    如果都设置了,可以通过DOS命令行来检查端口是否已经开始侦听了。

    查询443端口监听

    netstat -ano | find "443"

     

    如果还是有问题,可以请求CA证书的供应商或者云供应商的技术人员帮你看看,一般都是会配合你解决的,我的开始也是折腾了他们一会,最后也莫名其妙的可以了。

    最后成功后,在Chrome浏览器里面打开地址就有绿色的标志了。

    由于目前我的API平台还在搭建完善中,因此这个域名展示还是没有东西可以展示的。

    最后的构架会如前面Web API框架图所示,做到一个统一的整合方案里面。

    由于Web API层作为一个公共的接口层,我们就很好保证了各个界面应用层的数据一致性,如果考虑到响应式的集成处理,我们甚至可以把微信应用、APP应用、Web应用做层一套Web程序,即使为了利用各自应用的特殊性,也可以把这些应用做的很相似,这样就给用户提供了一个统一的界面表示方式,极大提高客户使用的界面体验效果,用户几乎不需要额外的界面学习,就可以熟悉整个应用体系的各个模块使用。

    从上面的架构分析来看,我们的Web API作为核心层,可以在上面开发我们各种企业业务应用,

    在目前比较热门的会员管理、客户管理等方面,结合微信的应用催化剂,就可以做的更加符合移动的潮流,从而实现我们“互联网+”的应用落地。

     

    展开全文
  • Apache服务器访问分析及解决

    万次阅读 2017-08-04 18:45:49
    起因:线上的一台服务器,最近总是出现 访问的情况发生,点击一个链接要2秒钟以上才能打开,按照我们对于访问人数的估计,服务器应该不至于响应这么,从而需要针对这个问题进行分析,来解决网站访问。...

    起因:线上的一台服务器,最近总是出现 访问 很慢的情况发生,点击一个链接要2秒钟以上才能打开,按照我们对于访问人数的估计,服务器应该不至于响应这么慢,从而需要针对这个问题进行分析,来解决网站访问过慢。

    分析:

    1、首先,在页面访问变慢情况发生时,使用 top 命令查看了服务器的负载情况,发现负载并不高,初步估计不是程序的问题。 
    2、然后,查看了线程中的 httpd 的数量, ps -aux | grep httpd | wc -l 发现,线程数已经达到了 apache 设置的最大值。由此断定是网站访问人数过多造成了访问过慢。 
    3、为了验证,查看了连接数和当前的连接数,分别是 

        netstat -ant | grep $ip:80 | wc -l 
        netstat -ant | grep $ip:80 | grep EST | wc -l 
        发现果然,连接数特别多,远远超过我们的估计值。 
    4、刚开始的时候,对于服务器的 MPM 配置方式不是特别的熟悉,认为修改服务器配置可以解决问题。主要的配置部分包括 prefork 模式 或者 work 模式的配置,下面是一些简单的介绍。

        prefork 模式: 
        以 prefork 模式工作的 apache 的默认配置: 
       

     <IfModule mpm_prefork_module> 
           ServerLimit            2000    
           StartServers              5    #指定服务器启动时建立的子进程数量 
           MinSpareServers           5    #指定空闲子进程的最小数量 
           MaxSpareServers          10    #指定空闲子进程的最大数量 
           MaxClients              150    #指定同一时间客户端最大接入请求的数量(单个进程并发线程数),任何超过该限制的请求都将进入等候队列,一旦一个连接被释放,队列中的请求将得到服务 
           MaxRequestsPerChild       0    #指定每个子进程在其生存周期内允许伺服的最大请求数量,默认为10000,0表示子进程永远不结束 
        </IfModule> 


        prefork 控制进程在最初建立“StartServers”个子进程后,为了满足 MinSpareServers 设置的需要创建一个进程,等待一秒钟,继续创建两个,再等待一秒钟,继续创建四个……如此按指数级增加创建的进程数,最多达到每秒32个,直到满足MinSpareServers设置的值为止。这种模式可以不必在请求到来时再产生新的进程,从而减小了系统开销以增加性能。 
        MaxSpareServers 设置了最大的空闲进程数,如果空闲进程数大于这个值,Apache会自动kill掉一些多余进程。这个值不要设得过大,但如果设的值比 MinSpareServers小,Apache会自动把其调整为 MinSpareServers+1。如果站点负载较大,可考虑同时加大MinSpareServers和MaxSpareServers。 
        MaxClients是这些指令中最为重要的一个,设定的是 Apache可以同时处理的请求,是对Apache性能影响最大的参数。其缺省值150是远远不够的,如果请求总数已达到这个值(可通过ps -ef|grephttpd|wc -l来确认),那么后面的请求就要排队,直到某个已处理请求完毕。这就是系统资源还剩下很多而HTTP访问却很慢的主要原因。虽然理论上这个值越大,可以处理的请求就越多,但Apache默认的限制不能大于256。在 apache2 中通过ServerLimit指令无须重编译Apache就可以加大MaxClients。 
        虽然通过设置ServerLimit,我们可以把MaxClients加得很大,但是往往会适得其反,系统耗光所有内存。以一台服务器为例:内存2G,每个apache进程消耗大约0.5%(可通过ps aux来确认)的内存,也就是10M,这样,理论上这台服务器最多跑200个apache进程就会耗光系统所有内存,所以,设置MaxClients要慎重。 
        worker 模式: 
        以 worker 模式工作的 apache 的默认配置为: 

    <IfModule mpm_worker_module> 
           StartServers              2 
           MaxClients              150 
            MinSpareThreads          25 
           MaxSpareThreads           75 
           ThreadsPerChild           25 
           MaxRequestsPerChild        0 
        </IfModule> 

        Worker 由主控制进程生成“StartServers”个子进程,每个子进程中包含固定的ThreadsPerChild线程数,各个线程独立地处理请求。同样,为了不在请求到来时再生成线程, 
        MinSpareThreads和MaxSpareThreads设置了最少和最多的空闲线程数;而MaxClients 设置了同时连入的clients最大总数。如果现有子进程中的线程总数不能满足负载,控制进程将派生新的子进程。 
        MinSpareThreads和 MaxSpareThreads的最大缺省值分别是75和250。这两个参数对Apache的性能影响并不大,可以按照实际情况相应调节。
        ThreadsPerChild是worker MPM中与性能相关最密切的指令。 
        ThreadsPerChild的最大缺省值是64,如果负载较大,64也是不够的。这时要显式使用 ThreadLimit指令,它的最大缺省值是20000。 
        Worker模式下所能同时处理的请求总数是由子进程总数乘以ThreadsPerChild 值决定的,应该大于等于MaxClients。如果负载很大,现有的子进程数不能满足时,控制进程会派生新的子进程。默认最大的子进程总数是16,加大时也需要显式声明ServerLimit(最大值是20000)。需要注意的是,如果显式声明了ServerLimit,那么它乘以 ThreadsPerChild的值必须大于等于MaxClients,而且MaxClients必须是ThreadsPerChild的整数倍,否则 Apache将会自动调节到一个相应值。

        服务器的apache采用的是 prefork 的工作模式,对 MaxClients 进行了相应的调整,发现服务启动后很短时间,连接数就能够达到最大。 
    5、后来想到需要查看用户都是访问的那些页面,将配置中的 access_log 打开,发现85%以上的访问都是直接访问的资源文件,由此判定,用户可能使用了多线程的下载工具,或者这些资源遭受了盗链。 
    6、找到了问题所在,进行解决也就比较好办了。想到了两个方法: 
        A、对单个IP进行连接的线程限制,不允许多线程连接资源。 
            对于IP限制,我采用了 mod_limitipconn 这个模块。这个模块的好处是比较简单,缺点是不能够针对单独的文件夹或者文件进行设置,而且不支持虚拟主机。 
            在 apache 中安装了这个模块后,在配置文件中添加如下几段就可以生效了:           

     ExtendedStatus On 
                <IfModule mod_limitipconn.c > 
                   < Location / >   # 所有虚拟主机的/目录 
                       MaxConnPerIP 3     # 每IP只允许3个并发连接 
                       NoIPLimit image/*  # 对图片不做IP限制 
                   < /Location > 
                <Location /mp3 >  # 所有主机的/mp3目录 
                MaxConnPerIP1         # 每IP只允许一个连接请求    
                OnlyIPLimitaudio/mpeg video    # 该限制只对视频和音频格式的文件 
                   < /Location > 
                </IfModule > 


        B、添加URL重写,防止盗链。 
            防止盗链,一个重要的方法就是判断请求的 refer,但是如果使用一些浏览器发出请求的时候将 refer 去掉,或者伪装,这个办法就无能为力了。但是貌似还有更高级的方法,还是可以实现这个功能。 
            安装apache的 mod_rewrite 模块后,在apache配置文件中添加 
            

    RewriteEngine On 
            RewriteCond %{HTTP_REFERER} !^http://abc.com/.*$ [NC] 
            RewriteCond %{HTTP_REFERER} !^http://abc.com$ [NC] 
            RewriteCond %{HTTP_REFERER} !^http://www.abc.com/.*$ [NC] 
            RewriteCond %{HTTP_REFERER} !^http://www.abc.com$ [NC] 
            RewriteRule .*\.(gif|jpg|swf)$ http://www.abc.com/about/nolink.png [R,NC] 


            这样盗链的请求会被重定向到一个错误页面,从而减少下载带给服务器的压力。

    参考资料: 
    1、部署Apache 的一些技巧。       

    2、Apache Server 负载能力测试    
    3、Apache AB                        
    4、Apache的参数设置                
    5、Ab的用法                        
    6、Apache限制连接数和并发数        
    7、Apache安装mod_rewrite模块        
    8、Apache防盗链的简单实现           

     

     

     

    Windows系统下如果优化Apache的性能主要是通过专门针对Windows NT优化的MPM(多路处理模块)-mpm_winnt.c来优化的,它使用一个单独的父进程产生一个单独的子进程,在这个子进程中轮流产生多个线程来处理请求。也就是说mpm_winnt只能启动父子两个进程, 不能像Linux下那样同时启动多个进程。 mpm_winnt主要通过ThreadsPerChild和MaxRequestsPerChild两个参数来优化Apache。 ThreadsPerChild 这个参数用于设置每个进程的线程数, 子进程在启动时建立这些线程后就不再建立新的线程了.一方面因为mpm_winnt不能启动多个进程, 所以这个数值要足够大,以便可以处理可能的请求高峰;另一方面该参数以服务器的响应速度为准的, 数目太大的反而会变慢。因此需要综合均衡一个合理的数值。mpm_winnt上的默认值是64, 最大值是1920. 这里建议设置为100-500之间,服务器性能高的话值大一些,反之值小一些。 MaxRequestsPerChild 该参数表示每个子进程能够处理的最大请求数, 即同时间内子进程数目.设置为零表示不限制, mpm_winnt上的默认值就是0. 官方参考手册中不建议设置为0, 主要基于两点考虑: (1) 可以防止(偶然的)内存泄漏无限进行,从而耗尽内存; (2) 给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。 因此这个参数的值更大程度上取决于服务器的内存,如果内存比较大的话可以设置为0或很大的数字,否则设置一个小的数值。需要说明的是,如果这个值设置的太小的话会造成Apache频繁重启,在日志文件中会看到如下的文字:Processexiting because it reached MaxRequestsPerChild. Signaling the parent,这样一来降低了Apache的总体性能。 另外,可以通过查看Apache提供的server-status(状态报告)来验证当前所设置数值是否合理,在httpd.conf文件中做如下设置来打开它: # 首先需要加载mod_status模块 LoadModule status_module modules/mod_status.so  # 然后设置访问的地址 <Location /server-status> SetHandler server-status Orderdeny,allow Deny from all # 如果限制某个IP访问则设置为 Allow from 192.168.1.1 Allow from all </Location> 综合来说,因为Windows NT下Apache只能启动父子两个进程,因此只能通过增大单个进程的线程数以及单个进程能够处理的最大请求数来进行优化。其他优化的参数同Linux系统下是一样的,大家可以加以参考。下面针对上述两个参数给出一个建议的设置: <IfModule mpm_winnt.c> ThreadsPerChild 250MaxRequestsPerChild 5000 </IfModule> 用上面的方法优化了一下windows+apache+php后,发现响应速度快了很多,一般最多只延迟3秒左右,但对于美国的主机来说应该是个正常的状态吧!

     

     

    大型网站HTTPS实践三:基于协议和配置的优化

     

    1 前言

    上文讲到 HTTPS对用户访问速度的影响

    本文就为大家介绍HTTPS在访问速度,计算性能,安全等方面基于协议和配置的优化。

    2 HTTPS 访问速度优化

    2.1 Tcp fast open

    HTTPS HTTP使用 TCP 协议进行传输,也就意味着必须通过三次握手建立 TCP连接,但一个 RTT 的时间内只传输一个 syn 包是不是太浪费?能不能在 syn 包发出的同时捎上应用层的数据?其实是可以的,这也是 tcp fast open的思路,简称TFO。具体原理可以参考rfc7413

    遗憾的是TFO需要高版本内核的支持,linux 3.7以后支持TFO,但是目前的windows系统还不支持 TFO,所以只能在公司内部服务器之间发挥作用。

    2.2 HSTS

    前面提到过将用户HTTP请求 302 跳转到HTTPS,这会有两个影响:

    1、不安全,302跳转不仅暴露了用户的访问站点,也很容易被中间者支持。

    2、降低访问速度,302跳转不仅需要一个 RTT,浏览器执行跳转也需要执行时间。

    由于 302跳转事实上是由浏览器触发的,服务器无法完全控制,这个需求导致了 HSTS的诞生:

    HSTS(HTTP Strict Transport Security)。服务端返回一个HSTS httpheader,浏览器获取到HSTS头部之后,在一段时间内,不管用户输入www.baidu.com还是http://www.baidu.com,都会默认将请求内部跳转成https://www.baidu.com

    Chrome, firefox, ie 都支持了HSTShttp://caniuse.com/#feat=stricttransportsecurity)。

    2.3 Session resume

    Session resume 顾名思义就是复用session,实现简化握手。复用session的好处有两个:

    1、减少了 CPU消耗,因为不需要进行非对称密钥交换的计算。

    2、提升访问速度,不需要进行完全握手阶段二,节省了一个 RTT和计算耗时。

    TLS 协议目前提供两种机制实现 session resume,分别介绍一下。

    2.3.1 Session cache

    Session cache 的原理是使用client hello中的 session id查询服务端的 session cache,如果服务端有对应的缓存,则直接使用已有的 session信息提前完成握手,称为简化握手。

    Session cache 有两个缺点:

    1、需要消耗服务端内存来存储 session内容。

    2、目前的开源软件包括 nginx,apache只支持单机多进程间共享缓存,不支持多机间分布式缓存,对于百度或者其他大型互联网公司而言,单机 session cache几乎没有作用。

    Session cache 也有一个非常大的优点:

    session id TLS 协议的标准字段,市面上的浏览器全部都支持 session cache

    百度通过对TLS握手协议及服务器端实现的优化,已经支持全局的 session cache,能够明显提升用户的访问速度,节省服务器计算资源。

    2.3.2 Session ticket

    上节提到了session cache的两个缺点,session ticket能够弥补这些不足。

    Session ticket 的原理参考RFC4507。简述如下:

    server session 信息加密成 ticket 发送给浏览器,浏览器后续握手请求时会发送 ticketserver端如果能成功解密和处理 ticket,就能完成简化握手。

    显然,sessionticket的优点是不需要服务端消耗大量资源来存储 session内容。

    Session ticket 的缺点:

    1session ticket只是 TLS 协议的一个扩展特性,目前的支持率不是很广泛,只有 60%左右。

    2session ticket需要维护一个全局的key来加解密,需要考虑KEY的安全性和部署效率。

    总体来讲,sessionticket的功能特性明显优于session cache。希望客户端实现优先支持 session ticket

    2.4 Ocsp stapling

    Ocsp 全称在线证书状态检查协议 (rfc6960),用来向 CA站点查询证书状态,比如是否撤销。通常情况下,浏览器使用 OCSP协议发起查询请求,CA返回证书状态内容,然后浏览器接受证书是否可信的状态。

    这个过程非常消耗时间,因为CA站点有可能在国外,网络不稳定,RTT也比较大。那有没有办法不直接向CA站点请求OCSP 内容呢?ocspstapling 就能实现这个功能。

    详细介绍参考RFC6066 8 节。简述原理就是浏览器发起 client hello时会携带一个certificate status request的扩展,服务端看到这个扩展后将 OCSP内容直接返回给浏览器,完成证书状态检查。

    由于浏览器不需要直接向CA站点查询证书状态,这个功能对访问速度的提升非常明显。

    Nginx 目前已经支持这个 ocsp stapling file,只需要配置ocsp stapling file的指令就能开启这个功能:

    ·  ssl_staplingon;ssl_stapling_file ocsp.staple;

    2.5 False start

    通常情况下,应用层数据必须等完全握手全部结束之后才能传输。这个其实比较浪费时间,那能不能类似 TFO一样,在完全握手的第二个阶段将应用数据一起发出来呢?google提出了 false start来实现这个功能。详细介绍参考https://tools.ietf.org/html/draft-bmoeller-tls-falsestart-00

    简单概括False start的原理就是在 client_key_exchange发出时将应用层数据一起发出来,能够节省一个 RTT

    False start 依赖于 PFSperfect forward secrecy完美前向加密),而PFS又依赖于DHE 密钥交换系列算法(DHE_RSA,ECDHE_RSA, DHE_DSS, ECDHE_ECDSA),所以尽量优先支持 ECDHE密钥交换算法实现 false start

    2.6 使用SPDY或者 HTTP2

    SPDY google推出的优化 HTTP 传输效率的协议(https://www.chromium.org/spdy),它基本上沿用了HTTP协议的语义, 但是通过使用帧控制实现了多个特性,显著提升了 HTTP协议的传输效率。

    SPDY 最大的特性就是多路复用,能将多个 HTTP请求在同一个连接上一起发出去,不像目前的 HTTP协议一样,只能串行地逐个发送请求。Pipeline虽然支持多个请求一起发送,但是接收时依然得按照顺序接收,本质上无法解决并发的问题。

    HTTP2 IETF 2015 2 月份通过的 HTTP 下一代协议,它以 SPDY 为原型,经过两年多的讨论和完善最终确定。

    本文就不过多介绍SPDY HTTP2 的收益,需要说明两点:

    1SPDY HTTP2 目前的实现默认使用 HTTPS 协议。

    2SPDY HTTP2 都支持现有的 HTTP 语义和API,对 WEB应用几乎是透明的。

    Google 宣布 chrome浏览器 2016 年将放弃 SPDY 协议,全面支持 HTTP2,但是目前国内部分浏览器厂商进度非常慢,不仅不支持 HTTP2,连 SPDY 都没有支持过。

    百度服务端和百度手机浏览器现在都已经支持 SPDY3.1协议。

     

    3 HTTPS 计算性能优化

    3.1 优先使用ECC

    ECC 椭圆加密算术相比普通的离散对数计算速度性能要强很多。下表是 NIST推荐的密钥长度对照表。

    表格 2 NIST推荐使用的密钥长度

    对于 RSA算法来讲,目前至少使用2048位以上的密钥长度才能保证安全性。ECC只需要使用224位长度的密钥就能实现RSA2048位长度的安全强度。在进行相同的模指数运算时速度显然要快很多。

    3.2 使用最新版的 openssl

    一般来讲,新版的openssl相比老版的计算速度和安全性都会有提升。比如 openssl1.0.2采用了intel 最新的优化成果,椭圆曲线p256 的计算性能提升了 4倍。(https://eprint.iacr.org/2013/816.pdf)

    Openssl 2014 年就升级了 5次,基本都是为了修复实现上的 BUG或者算法上的漏洞而升级的。所以尽量使用最新版本,避免安全上的风险。

    3.3 硬件加速方案

    现在比较常用的TLS硬件加速方案主要有两种:

    1SSL专用加速卡。

    2GPU SSL加速。

    上述两个方案的主流用法都是将硬件插入到服务器的 PCI插槽中,由硬件完成最消耗性能的计算。但这样的方案有如下缺点:

    1、支持算法有限。比如不支持 ECC,不支持 GCM等。

    2、升级成本高。

    a) 出现新的加密算法或者协议时,硬件加速方案无法及时升级。

    b) 出现比较大的安全漏洞时,部分硬件方案在无法在短期内升级解决。比如 2014年暴露的 heartbleed漏洞。

    3、无法充分利用硬件加速性能。硬件加速程序一般都运行在内核态,计算结果传递到应用层需要 IO和内存拷贝开销,即使硬件计算性能非常好,上层的同步等待和 IO开销也会导致整体性能达不到预期,无法充分利用硬件加速卡的计算能力。

    4、维护性差。硬件驱动及应用层 API大部分是由安全厂家提供,出现问题后还需要厂家跟进。用户无法掌握核心代码,比较被动。不像开源的 openssl,不管算法还是协议,用户都能掌握。

    3.4 TLS 远程代理计算

    也正是因为上述原因,百度实现了专用的 SSL硬件加速集群。基本思路是:

    1、优化 TLS协议栈,剥离最消耗 CPU资源的计算,主要有如下部分:

    a)  RSA中的加解密计算。

    b)  ECC算法中的公私钥生成。

    c)  ECC算法中的共享密钥生成。

    2、优化硬件计算部分。硬件计算不涉及协议及状态交互,只需要处理大数运算。

    3Web server TLS 计算集群之间的任务是异步的。即 web server将待计算内容发送给加速集群后,依然可以继续处理其他请求,整个过程是异步非阻塞的。

    4 HTTPS 安全配置

    4.1 协议版本选择

    SSL2.0 早就被证明是不安全的协议了,统计发现目前已经没有客户端支持 SSL2.0,所以可以放心地在服务端禁用 SSL2.0协议。

    2014 年爆发了 POODLE攻击,SSL3.0 因此被证明是不安全的。但是统计发现依然有 0.5%的流量只支持 SSL3.0。所以只能有选择地支持 SSL3.0

    TLS1.1 1.2目前为止没有发现安全漏洞,建议优先支持。

    4.2 加密套件选择

    加密套件包含四个部分:

    1、非对称密钥交换算法。建议优先使用 ECDHE,禁用 DHE,次优先选择 RSA

    2、证书签名算法。由于部分浏览器及操作系统不支持 ECDSA签名,目前默认都是使用 RSA签名,其中 SHA1 签名已经不再安全,chrome 及微软 2016 年开始不再支持 SHA1签名的证书(http://googleonlinesecurity.blogspot.jp/2014/09/gradually-sunsetting-sha-1.html)

    3、对称加解密算法。优先使用 AES-GCM算法,针对 1.0 以上协议禁用 RC4 rfc7465)。

    4、内容一致性校验算法。Md5 sha1 都已经不安全,建议使用 sha2 以上的安全哈希函数。

    4.3 HTTPS 防攻击

    4.3.1 防止协议降级攻击

    降级攻击一般包括两种:加密套件降级攻击 (cipher suite rollback)和协议降级攻击(version roll back)。降级攻击的原理就是攻击者伪造或者修改 client hello消息,使得客户端和服务器之间使用比较弱的加密套件或者协议完成通信。

    为了应对降级攻击,现在server端和浏览器之间都实现了SCSV功能,原理参考https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00

    一句话解释就是如果客户端想要降级,必须发送 TLS_SCSV的信号,服务器如果看到 TLS_SCSV,就不会接受比服务端最高协议版本低的协议。

    4.3.2 防止重新协商攻击

    重新协商(tlsrenegotiation)分为两种:加密套件重协商 (cipher suite renegotiation)和协议重协商(protocolrenegotiation)。

    重新协商会有两个隐患:

    1、重协商后使用弱的安全算法。这样的后果就是传输内容很容易泄露。

    2、重协商过程中不断发起完全握手请求,触发服务端进行高强度计算并引发服务拒绝。

    对于重协商,最直接的保护手段就是禁止客户端主动重协商,当然出于特殊场景的需求,应该允许服务端主动发起重协商。

    5 结束语

    HTTPS 的实践和优化涉及到了非常多的知识点,由于篇幅关系,本文对很多优化策略只是简单介绍了一下.如果想要了解协议背后的原理,还是需要详细阅读 TLS协议及 PKI 知识。对于大型站点来说,如果希望做到极致,HTTPS的部署需要结合产品和基础设施的架构来进行详细的考虑,比起部署支持 HTTPS的接入和对它的优化,在产品和运维层面上花费的功夫会更多。

     

    大型网站的HTTPS实践一:HTTPS协议和原理

    1 前言

    百度已经于近日上线了全站HTTPS的安全搜索,默认会将HTTP请求跳转成HTTPS。本文重点介绍HTTPS协议, 并简单介绍部署全站HTTPS 的意义。

    2 HTTPS 协议概述

    HTTPS 可以认为是 HTTP + TLSHTTP协议大家耳熟能详了,目前大部分 WEB应用和网站都是使用 HTTP协议传输的。

    TLS 是传输层加密协议,它的前身是 SSL协议,最早由 netscape公司于 1995 年发布,1999 年经过 IETF讨论和规范后,改名为 TLS。如果没有特别说明,SSL TLS 说的都是同一个协议。

    HTTP TLS在协议层的位置以及 TLS协议的组成如下图:

    1 TLS协议格式

    TLS 协议主要有五部分:应用数据层协议,握手协议,报警协议,加密消息确认协议,心跳协议。

    TLS 协议本身又是由 record协议传输的,record协议的格式如上图最右所示。

    目前常用的HTTP协议是HTTP1.1,常用的TLS协议版本有如下几个:TLS1.2,TLS1.1, TLS1.0 SSL3.0。其中 SSL3.0由于 POODLE 攻击已经被证明不安全,但统计发现依然有不到 1%的浏览器使用 SSL3.0TLS1.0也存在部分安全漏洞,比如 RC4 BEAST 攻击。

    TLS1.2 TLS1.1暂时没有已知的安全漏洞,比较安全,同时有大量扩展提升速度和性能,推荐大家使用。

    需要关注一点的就是TLS1.3将会是TLS 协议一个非常重大的改革。不管是安全性还是用户访问速度都会有质的提升。不过目前没有明确的发布时间。

    同时 HTTP2也已经正式定稿,这个由SPDY协议演化而来的协议相比HTTP1.1又是一个非常重大的变动,能够明显提升应用层数据的传输效率。

    3 HTTPS 功能介绍

    百度使用HTTPS协议主要是为了保护用户隐私,防止流量劫持。

    HTTP 本身是明文传输的,没有经过任何安全处理。例如用户在百度搜索了一个关键字,比如苹果手机,中间者完全能够查看到这个信息,并且有可能打电话过来骚扰用户。也有一些用户投诉使用百度时,发现首页或者结果页面浮了一个很长很大的广告,这也肯定是中间者往页面插的广告内容。如果劫持技术比较低劣的话,用户甚至无法访问百度。

    这里提到的中间者主要指一些网络节点,是用户数据在浏览器和百度服务器中间传输必须要经过的节点。比如 WIFI热点,路由器,防火墙,反向代理,缓存服务器等。

    HTTP协议下,中间者可以随意嗅探用户搜索内容,窃取隐私甚至篡改网页。不过 HTTPS是这些劫持行为的克星,能够完全有效地防御。

    总体来说,HTTPS协议提供了三个强大的功能来对抗上述的劫持行为:

    1、内容加密。浏览器到百度服务器的内容都是以加密形式传输,中间者无法直接查看原始内容。

    2、身份认证。保证用户访问的是百度服务,即使被 DNS劫持到了第三方站点,也会提醒用户没有访问百度服务,有可能被劫持

    3、数据完整性。防止内容被第三方冒充或者篡改。

    HTTPS是如何做到上述三点的呢?下面从原理角度介绍一下。

    4 HTTPS 原理介绍

    4.1 内容加密

    加密算法一般分为两种,对称加密和非对称加密。所谓对称加密(也叫密钥加密)就是指加密和解密使用的是相同的密钥。而非对称加密(也叫公钥加密)就是指加密和解密使用了不同的密钥。

    2对称加密

    3非对称加密

    对称内容加密强度非常高,一般破解不了。但存在一个很大的问题就是无法安全地生成和保管密钥。假如客户端软件和服务器之间每次会话都使用固定的,相同的密钥加密和解密,肯定存在很大的安全隐患。如果有人从客户端端获取到了对称密钥,整个内容就不存在安全性了,而且管理海量的客户端密钥也是一件很复杂的事情。

    非对称加密主要用于密钥交换(也叫密钥协商),能够很好地解决这个问题。浏览器和服务器每次新建会话时都使用非对称密钥交换算法协商出对称密钥,使用这些对称密钥完成应用数据的加解密和验证,整个会话过程中的密钥只在内存中生成和保存,而且每个会话的对称密钥都不相同(除非会话复用),中间者无法窃取。

    非对称密钥交换很安全,但同时也是HTTPS性能和速度严重降低的罪魁祸首。想要知道HTTPS为什么影响速度,为什么消耗资源,就一定要理解非对称密钥交换的整个过程。

    下面重点介绍一下非对称密钥交换的数学原理及在 TLS握手过程中的应用。

    4.1.1 非对称密钥交换

    在非对称密钥交换算法出现以前,对称加密一个很大的问题就是不知道如何安全生成和保管密钥。非对称密钥交换过程主要就是为了解决这个问题,使得对称密钥的生成和使用更加安全。

    密钥交换算法本身非常复杂,密钥交换过程涉及到随机数生成,模指数运算,空白补齐,加密,签名等操作。

    常见的密钥交换算法有RSAECDHEDHDHE等算法。它们的特性如下:

    ·  RSA:算法实现简单,诞生于1977 年,历史悠久,经过了长时间的破解测试,安全性高。缺点就是需要比较大的素数(目前常用的是 2048 位)来保证安全强度,很消耗 CPU 运算资源。RSA 是目前唯一一个既能用于密钥交换又能用于证书签名的算法。

    ·  DH:diffie-hellman密钥交换算法,诞生时间比较早(1977年),但是1999 年才公开。缺点是比较消耗 CPU性能。

    ·  ECDHE:使用椭圆曲线(ECC)的 DH 算法,优点是能用较小的素数(256 位)实现 RSA相同的安全等级。缺点是算法实现复杂,用于密钥交换的历史不长,没有经过长时间的安全攻击测试。  

    ·  ECDH:不支持 PFS,安全性低,同时无法实现false start。  

    ·  DHE:不支持 ECC。非常消耗 CPU资源。

    建议优先支持RSA ECDH_RSA密钥交换算法。原因是:

    1ECDHE支持 ECC 加速,计算速度更快。支持 PFS,更加安全。支持 false start,用户访问速度更快。

    2、目前还有至少 20%以上的客户端不支持 ECDHE,我们推荐使用 RSA而不是 DH 或者 DHE,因为 DH系列算法非常消耗 CPU(相当于要做两次 RSA计算)。

    需要注意通常所说的ECDHE密钥交换默认都是指ECDHE_RSA,使用 ECDHE生成 DH 算法所需的公私钥,然后使用 RSA 算法进行签名最后再计算得出对称密钥。

    非对称加密相比对称加密更加安全,但也存在两个明显缺点:

    1CPU计算资源消耗非常大。一次完全 TLS握手,密钥交换时的非对称解密计算量占整个握手过程的 90%以上。而对称加密的计算量只相当于非对称加密的 0.1%,如果应用层数据也使用非对称加解密,性能开销太大,无法承受。

    2、非对称加密算法对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是 2048位,意味着待加密内容不能超过 256个字节。

    所以公钥加密目前只能用来作密钥交换或者内容签名,不适合用来做应用层传输内容的加解密。

    非对称密钥交换算法是整个HTTPS得以安全的基石,充分理解非对称密钥交换算法是理解 HTTPS协议和功能的关键。

    下面分别通俗地介绍一下RSA ECDHE 在密钥交换过程中的应用。

    4.1.1.1 RSA 密钥协商

    4.1.1.1.1 RSA 算法介绍

    RSA 算法的安全性是建立在乘法不可逆或者大数因子很难分解的基础上。RSA的推导和实现涉及到了欧拉函数和费马定理及模反元素的概念,有兴趣的读者可以自行百度。

    RSA 算法是统治世界的最重要算法之一,而且从目前来看,RSA也是 HTTPS 体系中最重要的算法,没有之一。

    RSA 的计算步骤如下:

    1、随机挑选两个质数 p, q,假设 p = 13, q = 19 n = p *q = 13 * 19 = 247;

    2(n)表示与整数 n 互质数的个数。如果 n 等于两个质数的积,则(n)=(p-1)(q-1)挑选一个数 e,满足1< e <(n)并且 e 与互质,假设 e= 17;

    3、计算 e关于 n 的模反元素, ed=1 mod (n) , e = 17 ,(n) =216  可得 d = 89;

    4、求出了 e,和 d,假设明文 m = 135,密文用 c表示。

    那么加解密计算如下:

    实际应用中,(n,e)组成了公钥对,(n,d)组成了私钥对,其中 n d 都是一个接近22048的大数。即使现在性能很强的CPU,想要计算m≡c^d mod(n),也需要消耗比较大的计算资源和时间。

    公钥对(n, e)一般都注册到了证书里,任何人都能直接查看,比如百度证书的公钥对如下图,其中最末 6个数字(010001)换算成 10进制就是 65537,也就是公钥对中的 ee取值比较小的好处有两个:

    1、由 c=m^e mod(n)可知,e 较小,客户端CPU 计算消耗的资源较少。

    2、加大 server端的破解难度。e 比较小,私钥对中的 d 必然会非常大。所以 d 的取值空间也就非常大,增加了破解难度。

    那为什么(n,e)能做为公钥公开,甚至大家都能直接从证书中查看到,这样安全吗?分析如下:

    由于 ed≡1 mod(n),知道了 e n,想要求出私钥 d,就必须知道(n)。而(n)=(p-1)*(q-1),必须计算出 p q 才能确定私钥 d。但是当 n大到一定程度时(比如接近2^2048),即使现在最快的CPU也无法进行这个因式分解,即无法知道 n是由哪个数 p q 乘出来的。所以就算知道了公钥,整个加解密过程还是非常安全的。

    5百度 HTTPS 证书公钥

     

    4.1.1.1.2 握手过程中的 RSA密钥协商

    介绍完了RSA的原理,那最终会话所需要的对称密钥是如何生成的呢?跟 RSA有什么关系?

    TLS1.2为例简单描述一下,省略跟密钥交换无关的握手消息。过程如下:

    1、浏览器发送 client_hello,包含一个随机数 random1

    2、服务端回复 server_hello,包含一个随机数 random2,同时回复 certificate,携带了证书公钥 P

    3、浏览器接收到 random2之后就能够生成 premaster_secrect以及master_secrect。其中 premaster_secret长度为 48 个字节,前 2 个字节是协议版本号,剩下的 46个字节填充一个随机数。结构如下:

    ·  Struct{byte Version[2];bute random[46];}

    master secrect 的生成算法简述如下:

    Master_key = PRF(premaster_secret, “mastersecrect”, 随机数1+随机数2)其中 PRF 是一个随机函数,定义如下:PRF(secret, label, seed) = P_MD5(S1, label + seed)  XOR P_SHA-1(S2, label + seed)

    从上式可以看出,把premaster_key赋值给 secret”master key”赋值给 label,浏览器和服务器端的两个随机数做种子就能确定地求出一个 48 位长的随机数。

    mastersecrect包含了六部分内容,分别是用于校验内容一致性的密钥,用于对称内容加解密的密钥,以及初始化向量(用于 CBC模式),客户端和服务端各一份。

    至此,浏览器侧的密钥已经完成协商。

    4、浏览器使用证书公钥 P premaster_secrect加密后发送给服务器。

    5、服务端使用私钥解密得到 premaster_secrect。又由于服务端之前就收到了随机数 1,所以服务端根据相同的生成算法,在相同的输入参数下,求出了相同的 master secrect

    RSA 密钥协商握手过程图示如下:

    6 RSA密钥协商过程

    可以看出,密钥协商过程需要 2 RTT,这也是HTTPS慢的一个重要原因。而RSA发挥的关键作用就是对premaster_secrect进行了加密和解密。中间者不可能破解 RSA算法,也就不可能知道 premaster_secrect,从而保证了密钥协商过程的安全性。

    4.1.1.2 ECDHE 密钥协商

    4.1.1.2.1 DH ECC算法原理

    ECDHE 算法实现要复杂很多,主要分为两部分:diffie-hellman算法(简称为DH)及 ECC(椭圆曲线算术)。他们的安全性都是建立在离散对数计算很困难的基础上。

    简单介绍一下dh算法的实现,先介绍两个基本概念:

    ·  本原根:如果整数 a 是素数 p 的本原根,则 a, a^2, …, a^(p-1) 在 mod p 下都不相同。 

    ·  离散对数:对任意整数 b 和素数 p 的本原根 a,存在唯一的指数 i 满足:

    b ≡ a^i mod p (0≤i≤p-1)

    则称 i b 的以 a 为底的模 p的离散对数。

    理解这两个概念,dh算法就非常简单了,示例如下:

    假设 client server 需要协商密钥,p=2579,则本原根 a= 2

    1Client选择随机数 Kc = 123做为自己的私钥,计算 Yc = a^Kc  mod p = 2^123 mod 2579 =2400,把 Yc作为公钥发送给server

    2Server选择随机数 Ks = 293作为私钥,计算 Ys = a^Ks  mod p = s^293 mod 2579 = 968,把 Ys作为公钥发送给client

    3Client计算共享密钥:secrect =  Ys^Kc mod (p) = 968^123 mod(2579) = 434

    4Server计算共享密钥:secrect = Yc^Ks mod(p) =2400^293 mod(2579)=434

    上述公式中的Ys,Yc,P, a,都是公开信息,可以被中间者查看,只有 Ks,Kc作为私钥没有公开,当私钥较小时,通过穷举攻击能够计算出共享密钥,但是当私钥非常大时,穷举攻击肯定是不可行的。

    DH 算法有一个比较大的缺陷就是需要提供足够大的私钥来保证安全性,所以比较消耗 CPU计算资源。ECC 椭圆曲线算术能够很好的解决这个问题,224位的密钥长度就能达到 RSA2048位的安全强度。

    ECC 的曲线公式描述的其实不是椭圆,只是跟椭圆曲线周长公式形似才叫椭圆曲线加密算术。ECC涉及到了有限域、群等近世代数的多个概念,就不做详细介绍了。

    ECC 安全性依赖于这样一个事实:

    P = kQ,已知 k, Q 求出 P 相对简单,但是已知 P Q 求出 k 却非常困难。

    上式看起来非常简单,但有如下约束条件:

    1Q是一个非常大的质数,p, k, q都是椭圆曲线有限域上的离散点。

    2、有限域定义了自己的加法和乘法法则,即使 kQ的运算也非常复杂。

    ECC 应用于 Diffie-Hellman密钥交换过程如下:

    1、定义一个满足椭圆方程的有限域,即挑选 p, a, b满足如下方程:

    y^2 mod p = (x^3+ax +b) mod p

    2、挑选基点 G = (x, y)G的阶为 nn为满足 nG = 0 的最小正整数。

    3Client选择私钥 Kc (0 <Kc

    4server选择私钥 Ks 并产生公钥 Ys =Ks*G

    5client计算共享密钥 K = Kc*Ys  server 端计算共享密钥Ks*Yc ,这两者的结果是一样的,因为:

     Kc*Ys = Kc*(Ks*G) = Ks*(Kc*G) = Ks*Yc

    由上面描述可知,只要确定p, a, b就能确定一条有限域上的椭圆曲线,由于不是所有的椭圆曲线都能够用于加密,所以 p, a, b的选取非常讲究,直接关系曲线的安全性和计算速度。

    Openssl 实现的,也是 FIPS推荐的 256 位素数域上的椭圆曲线参数定义如下:

    质数 p =115792089210356248762697446949407573530086143415290314195533631308867097853951 n =115792089210356248762697446949407573529996955224135760342422259061068512044369SEED= c49d3608 86e70493 6a6678e1 139d26b7 819f7e90c = 7efba166 2985be94 03cb055c75d4f7e0 ce8d84a9 c5114abcaf317768 0104fa0d 椭圆曲线的系数 a = 0椭圆曲线的系统 b = 5ac635d8 aa3a93e7 b3ebbd55 769886bc651d06b0 cc53b0f63bce3c3e 27d2604b基点 G x = 6b17d1f2 e12c4247 f8bce6e5 63a440f277037d81 2deb33a0f4a13945 d898c296基点 G y = 4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e162bce3357 6b315ececbb64068 37bf51f5

    4.1.1.2.2 握手过程中的 ECDHE密钥协商

    简单介绍了ECC DH 算法的数学原理,我们看下ECDHE TLS 握手过程中的应用。

    相比 RSAECDHE需要多发送一个server_key_exchange的握手消息才能完成密钥协商。

    同样以TLS1.2为例,简单描述一下过程:

    1、浏览器发送 client_hello,包含一个随机数 random1,同时需要有 2 个扩展:

    a)    Elliptic_curves:客户端支持的曲线类型和有限域参数。现在使用最多的是 256位的素数域,参数定义如上节所述。

    b)   Ec_point_formats:支持的曲线点格式,默认都是uncompressed

    2、服务端回复 server_hello,包含一个随机数 random2 ECC 扩展。

    3、服务端回复 certificate,携带了证书公钥。

    4、服务端生成 ECDH临时公钥,同时回复 server_key_exchange,包含三部分重要内容:

    a)    ECC相关的参数。

    b)   ECDH临时公钥。

    c)    ECC参数和公钥生成的签名值,用于客户端校验。

    5、浏览器接收 server_key_exchange之后,使用证书公钥进行签名解密和校验,获取服务器端的 ECDH临时公钥,生成会话所需要的共享密钥。

    至此,浏览器端完成了密钥协商。

    6、浏览器生成 ECDH临时公钥和 client_key_exchange消息,跟RSA 密钥协商不同的是,这个消息不需要加密了。

    7、服务器处理 client_key_exchang消息,获取客户端ECDH临时公钥。

    8、服务器生成会话所需要的共享密钥。

    9Server端密钥协商过程结束。

    图示如下:

    7 ECDHE密钥协商过程

     

    4.1.2 对称内容加密

    非对称密钥交换过程结束之后就得出了本次会话需要使用的对称密钥。对称加密又分为两种模式:流式加密和分组加密。流式加密现在常用的就是 RC4,不过 RC4已经不再安全,微软也建议网站尽量不要使用 RC4流式加密。

    一种新的替代RC4的流式加密算法叫ChaCha20,它是 google推出的速度更快,更安全的加密算法。目前已经被 android chrome 采用,也编译进了 google 的开源 openssl 分支 —boring ssl,并且nginx 1.7.4也支持编译 boringssl

    分组加密以前常用的模式是AES-CBC,但是CBC已经被证明容易遭受BEASTLUCKY13攻击。目前建议使用的分组加密模式是 AES-GCM,不过它的缺点是计算量大,性能和电量消耗都比较高,不适用于移动电话和平板电脑。

    4.2 身份认证

    身份认证主要涉及到PKI和数字证书。通常来讲 PKI(公钥基础设施)包含如下部分:

    ·  End entity:终端实体,可以是一个终端硬件或者网站。

    ·  CA:证书签发机构。

    ·  RA:证书注册及审核机构。比如审查申请网站或者公司的真实性。

    ·  CRL issuer:负责证书撤销列表的发布和维护。

    ·  Repository:负责数字证书及 CRL内容存储和分发。

    申请一个受信任的数字证书通常有如下流程:

    1、终端实体生成公私钥和证书请求。

    2RA检查实体的合法性。如果个人或者小网站,这一步不是必须的。

    3CA签发证书,发送给申请者。

    4、证书更新到 repository,终端后续从 repository更新证书,查询证书状态等。

    目前百度使用的证书是X509v3格式,由如下三个部分组成:

    1tbsCertificate(to be signed certificate待签名证书内容),这部分包含了10个要素,分别是版本号,序列号,签名算法标识,发行者名称,有效期,证书主体名,证书主体公钥信息,发行商唯一标识,主体唯一标识,扩展等。

    2signatureAlgorithm,签名算法标识,指定对tbsCertificate进行签名的算法。

    3signaturValue(签名值),使用 signatureAlgorithmtbsCertificate进行计算得到签名值。

    数字证书有两个作用:

    1、身份授权。确保浏览器访问的网站是经过 CA验证的可信任的网站。

    2、分发公钥。每个数字证书都包含了注册者生成的公钥。在 SSL握手时会通过 certificate消息传输给客户端。比如前文提到的 RSA证书公钥加密及 ECDHE的签名都是使用的这个公钥。

    申请者拿到CA的证书并部署在网站服务器端,那浏览器发起握手接收到证书后,如何确认这个证书就是 CA签发的呢?怎样避免第三方伪造这个证书?

    答案就是数字签名(digitalsignature)。数字签名是证书的防伪标签,目前使用最广泛的 SHA-RSA数字签名的制作和验证过程如下:

    1、数字签名的签发。首先是使用哈希函数对待签名内容进行安全哈希,生成消息摘要,然后使用 CA自己的私钥对消息摘要进行加密。

    2、数字签名的校验。使用 CA的公钥解密签名,然后使用相同的签名函数对待签名证书内容进行签名并和服务端数字签名里的签名内容进行比较,如果相同就认为校验成功。

    8数字签名生成及校验

    这里有几点需要说明:

    数字签名签发和校验使用的密钥对是CA自己的公私密钥,跟证书申请者提交的公钥没有关系。数字签名的签发过程跟公钥加密的过程刚好相反,即是用私钥加密,公钥解密。现在大的 CA都会有证书链,证书链的好处一是安全,保持根 CA的私钥离线使用。第二个好处是方便部署和撤销,即如果证书出现问题,只需要撤销相应级别的证书,根证书依然安全。 CA证书都是自签名,即用自己的公钥和私钥完成了签名的制作和验证。而证书链上的证书签名都是使用上一级证书的密钥对完成签名和验证的。怎样获取根 CA和多级 CA 的密钥对?它们是否可信?当然可信,因为这些厂商跟浏览器和操作系统都有合作,它们的公钥都默认装到了浏览器或者操作系统环境里。比如firefox就自己维护了一个可信任的 CA列表,而chrome IE 使用的是操作系统的 CA列表。 4.3 数据完整性

    这部分内容比较好理解,跟平时的md5签名类似,只不过安全要求要高很多。openssl现在使用的完整性校验算法有两种:MD5或者 SHA。由于 MD5在实际应用中存在冲突的可能性比较大,所以尽量别采用 MD5来验证内容一致性。SHA也不能使用 SHA0 SHA1,中国山东大学的王小云教授在 2005年就宣布破解了 SHA-1完整版算法。

    微软和google都已经宣布16 年及 17 年之后不再支持sha1签名证书。

    5 HTTPS 使用成本

    HTTPS 目前唯一的问题就是它还没有得到大规模应用,受到的关注和研究都比较少。至于使用成本和额外开销,完全不用太过担心。

    一般来讲,使用HTTPS前大家可能会非常关注如下问题:

    1、证书费用以及更新维护。大家觉得申请证书很麻烦,证书也很贵,可是证书其实一点都不贵,便宜的一年几十块钱,最多也就几百。而且现在也有了免费的证书机构,比如著名的 mozilla发起的免费证书项目:let’s encrypthttps://letsencrypt.org/)就支持免费证书安装和自动更新。这个项目将于今年中旬投入正式使用。

    数字证书的费用其实也不高,对于中小网站可以使用便宜甚至免费的数字证书服务(可能存在安全隐患),像著名的 verisign公司的证书一般也就几千到几万块一年不等。当然如果公司对证书的需求比较大,定制性要求高,可以建立自己的 CA站点,比如 google,能够随意签发 google相关证书。

    2HTTPS降低用户访问速度。HTTPS对速度会有一定程度的降低,但是只要经过合理优化和部署,HTTPS对速度的影响完全可以接受。在很多场景下,HTTPS速度完全不逊于 HTTP,如果使用 SPDYHTTPS的速度甚至还要比 HTTP快。

    大家现在使用百度HTTPS安全搜索,有感觉到慢吗?

    3HTTPS消耗 CPU 资源,需要增加大量机器。前面介绍过非对称密钥交换,这是消耗 CPU计算资源的大户,此外,对称加解密,也需要 CPU的计算。

    同样地,只要合理优化,HTTPS的机器成本也不会明显增加。对于中小网站,完全不需要增加机器也能满足性能需求。

    6 后记

    国外的大型互联网公司很多已经启用了全站 HTTPS,这也是未来互联网的趋势。国内的大型互联网并没有全站部署 HTTPS,只是在一些涉及账户或者交易的子页面 / 子请求上启用了 HTTPS。百度搜索首次全站部署 HTTPS,对国内互联网的全站 HTTPS进程必将有着巨大的推动作用。

    目前互联网上关于HTTPS的中文资料比较少,本文就着重介绍了 HTTPS协议涉及到的重要知识点和平时不太容易理解的盲区,希望能对大家理解 HTTPS协议有帮助。百度 HTTPS性能优化涉及到大量内容,从前端页面、后端架构、协议特性、加密算法、流量调度、架构和运维、安全等方面都做了大量工作。本系列的文章将一一进行介绍。

     

    HTTPS对网站性能SEO有哪些影响

    1 前言

    HTTPS在保护用户隐私,防止流量劫持方面发挥着非常关键的作用,但与此同时,HTTPS也会降低用户访问速度,增加网站服务器的计算资源消耗。

    本文主要介绍https对用户体验的影响。

    本文最早发表于百度运维部官方博客

    2 HTTPS对访问速度的影响

    在介绍速度优化策略之前,先来看下HTTPS对速度有什么影响。影响主要来自两方面:

    1. 协议交互所增加的网络RTT(round trip time)

    2. 加解密相关的计算耗时。

    下面分别介绍一下。

    2.1 网络耗时增加

    由于 HTTPHTTPS都需要DNS解析,并且大部分情况下使用了DNS缓存,为了突出对比效果,忽略主域名的DNS解析时间。

    用户使用HTTP协议访问http://www.baidu.com(或者www.baidu.com)时会有如下网络上的交互耗时:

    可见,用户只需要完成TCP三次握手建立TCP连接就能够直接发送HTTP请求获取应用层数据,此外在整个访问过程中也没有需要消耗计算资源的地方。

    接下来看HTTPS的访问过程,相比HTTP要复杂很多,在部分场景下,使用HTTPS访问有可能增加7RTT。如下图:

    HTTPS首次请求需要的网络耗时解释如下:

    1. 三次握手建立TCP连接。耗时一个RTT

    2. 使用HTTP发起GET请求,服务端返回302跳转到https://www.baidu.com。需要一个RTT以及302跳转延时。

    a) 大部分情况下用户不会手动输入https://www.baidu.com来访问HTTPS,服务端只能返回302强制浏览器跳转到https

    b) 浏览器处理302跳转也需要耗时。

    3. 三次握手重新建立TCP连接。耗时一个RTT

    a) 302跳转到HTTPS服务器之后,由于端口和服务器不同,需要重新完成三次握手,建立TCP连接。

    4. TLS完全握手阶段一。耗时至少一个RTT

    a) 这个阶段主要是完成加密套件的协商和证书的身份认证。

    b) 服务端和浏览器会协商出相同的密钥交换算法、对称加密算法、内容一致性校验算法、证书签名算法、椭圆曲线(ECC算法不需要)等。

    c) 浏览器获取到证书后需要校验证书的有效性,比如是否过期,是否撤销。

    5. 解析CA站点的DNS。耗时一个RTT

    a) 浏览器获取到证书后,有可能需要发起OCSP或者CRL请求,查询证书状态。

    b) 浏览器首先获取证书里的CA域名。

    c) 如果没有命中缓存,浏览器需要解析CA域名的DNS

    6. 三次握手建立CA站点的TCP连接。耗时一个RTT

    a) DNS解析到IP后,需要完成三次握手建立TCP连接。

    7. 发起OCSP请求,获取响应。耗时一个RTT

    8. 完全握手阶段二,耗时一个RTT及计算时间。

    a) 完全握手阶段二主要是密钥协商。

    9. 完全握手结束后,浏览器和服务器之间进行应用层(也就是HTTP)数据传输。

    当然不是每个请求都需要增加7RTT才能完成HTTPS首次请求交互。大概只有不到0.01%的请求才有可能需要经历上述步骤,它们需要满足如下条件:

    1. 必须是首次请求。即建立TCP连接后发起的第一个请求,该连接上的后续请求都不需要再发生上述行为。

    2. 必须要发生完全握手,而正常情况下80%的请求能实现简化握手。

    3. 浏览器需要开启OCSP或者CRL功能。Chrome默认关闭了ocsp功能,firefoxIE都默认开启。

    4. 浏览器没有命中OCSP缓存。Ocsp一般的更新周期是7天,firefox的查询周期也是7天,也就说是7天中才会发生一次ocsp的查询。

    5. 浏览器没有命中CA站点的DNS缓存。只有没命中DNS缓存的情况下才会解析CADNS

    2.2 计算耗时增加

    上节还只是简单描述了HTTPS关键路径上必须消耗的纯网络耗时,没有包括非常消耗CPU资源的计算耗时,事实上计算耗时也不小(30ms以上),从浏览器和服务器的角度分别介绍一下:

    1浏览器计算耗时

    a) RSA证书签名校验,浏览器需要解密签名,计算证书哈希值。如果有多个证书链,浏览器需要校验多个证书。

    b) RSA密钥交换时,需要使用证书公钥加密premaster。耗时比较小,但如果手机性能比较差,可能也需要1ms的时间。

    c) ECC密钥交换时,需要计算椭圆曲线的公私钥。

    d) ECC密钥交换时,需要使用证书公钥解密获取服务端发过来的ECC公钥。

    e) ECC密钥交换时,需要根据服务端公钥计算master key

    f) 应用层数据对称加解密。

    g) 应用层数据一致性校验。

    2服务端计算耗时

    a) RSA密钥交换时需要使用证书私钥解密premaster。这个过程非常消耗性能。

    b) ECC密钥交换时,需要计算椭圆曲线的公私钥。

    c) ECC密钥交换时,需要使用证书私钥加密ECC的公钥。

    d) ECC密钥交换时,需要根据浏览器公钥计算共享的master key

    e) 应用层数据对称加解密。

    f) 应用层数据一致性校验。

    由于客户端的CPU和操作系统种类比较多,所以计算耗时不能一概而论。手机端的HTTPS计算会比较消耗性能,单纯计算增加的延迟至少在50ms以上。PC端也会增加至少10ms以上的计算延迟。

    服务器的性能一般比较强,但由于RSA证书私钥长度远大于客户端,所以服务端的计算延迟也会在5ms以上。

     

     

    站点采用HTTPS协议的利弊分析、及SEO建议

    站长之家(Chinaz.com)注:本文作者为Moz网站专栏作家Cyrus Shepard,是一篇关于“HTTPS站点优化建议及技巧的分享型文章。文章写于谷歌宣布将“HTTPS协议作为搜索引擎排名参考因素后。

    谷歌几乎没有明确对外公开过影响谷歌搜索引擎排名的因素具体有哪些,因而当其在去年8月份宣布采用“HTTPS加密协议有利于搜索引擎排名时,我的心情就两字儿:震惊!

    HTTPS与其他的谷歌参考因素不同,实行起来比较复杂,有一定的风险性,而且还需一些额外的费用。但利益也是显而易见的,使用HTTPS协议的站点更安全、且在搜索排名上更具优势。

    Moz网站20149月份的调查数据显示:17.24%的站长表示其网站已采用HTTPS协议;24.9%的站长表示正在搭建中;57.85%的站长表示目前仍无此项计划。

    如下图:

    虽然大部分站长仍无转向HTTPS阵营的打算,但相比之前的情况已有提升。看来,谷歌的算法更新对站长们还是很有震慑力的。

    采用HTTPS协议对SEO有何好处?

    除了安全性更高这一好处外,HTTPSSEO也是有一定益处的。

    1、使用HTTPS协议有利于搜索引擎排名

    去年8月份,谷歌曾发布公告表示将把是否使用安全加密协议(即HTTPS作为搜索引擎排名的一项参考因素。同等情况下,HTTPS站点能比HTTP站点获得更好的搜索排名。

    不过得说明下,影响谷歌搜索引擎排名的因素已有逾200项,因而HTTPS协议的影响到底几何目前尚不清楚。

    因而,与其他谷歌排名影响因素一样的是,HTTPS协议也并非独立存在的。

    建议:

    如果只是为了搜索引擎排名的话,那有很多因素的影响力比HTTPS协议大。

    如下图(14个影响力大于HTTPS协议的影响因素):

    更多影响因素可查看:影响谷歌搜索引擎排名的因素调查(完整版)

    2、安全隐私

    不少站长都认为,只有诸如电子商务、金融、社交网络等存在敏感信息安全问题的站点才有采用HTTPS协议的必要,其实不然。任何类型的站点都可以从中获益。

    1)使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;

    2HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。

    3HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。

    建议:

    在成本费用允许情况下,还是建议站长采用HTTPS加密协议,毕竟网站安全也是用户体验的一个重要环节,而且还有利于搜索引擎排名,何乐而不为呢!

    使用HTTPS协议有何挑战?

    1、容易忽略的问题

    将站点由HTTP转为HTTPS协议涉及到很多问题,有时候会忽略了一些重要的细节问题:

    1robots.txt文件中是否屏蔽了重要的URL链接?

    2Canonical标签指向的URL是否正确?

    3)当用户访问你的网站时,是否会出现浏览器安全警告提示窗口?(出现安全警告提示可能会吓走用户)

    虽然概率很小,但这几个问题还是可能出现的。

    2、网站加载速度问题

    HTTPS协议的握手过程比较费时,对网站的响应速度有负面影响。据ACM CoNEXT数据显示,使用HTTPS协议很可能会使页面的加载时间延长近50%。而网站加载速度也是影响搜索引擎排名的一个很重要的因素。

    不过,还是可以通过一些技巧来减少这个问题的。比如,压缩文本内容可以降低解码耗用的CPU资源。实际上,建立HTTPS连接,要求额外的TCP往返,因此会新增一些发送和接收的字节,但这是第一次打开网页时的情况。

    3、成本

    据数据显示,很多站长每年花在SSL证书上的费用在100美元-200美元之间,这对于个人博客、或是小型站点来说是一笔不小的开支。不过,现在网上也有不少免费SSL证书,可查看:

    WoSign沃通SSL证书免费申请及账户设置教程

    CloudFlare SSLWosign沃通SSL申请开通和安装使用

    2014美国主机网站域名 SSL证书使用的七点总结

    如何购买廉价SSL证书

    详解HTTPSSSL证书申请、站点搭建等

    4HTTPS兼容性问题

    这里所说得兼容性包括很多方面,比如现有的Web应用要尽可能无缝地迁移到HTTPS、浏览器对HTTPS的兼容性问题、HTTPS协议解析以及SSL证书管理等。

    5、更多问题

    如果你的网站依靠AdSense获得收入的话,那么转型HTTPS站点可能会使得收入大幅下降(谷歌对广告源采用SSL协议的站点有所限制)。

    此外,即使是谷歌管理员工具也尚不支持HTTPS站点的迁移工作。要完成SSL加密的全球化,需要的不止是时间,还少不了各方的努力啊。

    使用HTTPS协议的站点数量增长情况

    如今,越来越多的站点采用了HTTPS协议,不过大多用于登陆页面、或是存在交易信息的页面,很少网站选择全站采用HTTPS协议。

    Builtwith调查数据显示,在排名TOP10000的网站中,只有4.2%的站点默认使用HTTPS加密访问模式。再将范围放大到TOP100万个网站,这个百分比则降到了1.9%

    如下图:

    不过,随着谷歌和百度等搜索引擎对HTTPS协议的优待,这个百分比未来应该会有所上升。

    HTTPS站点的SEO自检清单

    1、确保网站的每个元素(包括插件、JSCSS文件、图片、内容分发网站等)都采用HTTPS协议;

    2、使用301重定向将HTTP URL指向HTTPS版地址。记住别误用302跳转;

    3、保证Canonical标签指向HTTPSURL

    ·  再谈Canonical标签:与301有何区别?

    ·  canonical标签介绍

    4、采用HTTPS协议后,应确保网站内链指向的是HTTPSURL,而非旧版URL。这对用户体验以及网站优化而言,都是一个很重要的步骤。

    5、在谷歌、必应等平台上的管理员工具中监控HTTPS版本站点;

    6、使用谷歌管理员工具中Fetch&Render功能(http://googlewebmastercentral.blogspot.com/2014/05/rendering-pages-with-fetch-as-google.html),确保你的HTTPS站点能够正常的被谷歌抓取;

    7、更新网站sitemaps,并在谷歌管理员工具中提交新版sitemaps

    8、更新robots.txt文件,加入新版sitemaps内容,确保重要的HTTPS版页面不会被屏蔽;

    9、如有必要,还应该更新网站的分析跟踪代码。现在已经有很多新的谷歌分析代码段都能够处理HTTPS站点了。

    10、采用HSTS协议(HTTP严格传输安全协议),其作用是强制客户端(如浏览器)使用HTTPS与服务器建立连接。可在保证安全性的前提下,提高网站的响应速度。

     

     

    Apache全局/局部https访问配置方法

    一、全局https访问

    1.找到apache安装目录的httpd.conf配置文件,进行一下操作:

    2.#LoadModule rewrite_module modules/mod_rewrite.so(把代码前面的#号去掉,如果没有这个模块,请加上);

    3.在httpd.conf加入代码:

    RewriteEngine on

    RewriteCond %{SERVER_PORT}!^443$

    RewriteRule ^(.*)?$ https://%{SERVER_NAME}$1[L,R]

    重启apache即可。

    二、指定路径访问使用https访问

    1.在httpd.conf加入代码:

    RewriteEngineon
    RewriteBase /test
    RewriteCond %{SERVER_PORT} !^443$
    RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R]

    2.重启apache即可。

     

     

     

    性能测试简介

    即压力测试,就是根据一定数量的VUVirtualUsers)我称为并发用户操作核心交易后,系统所能达到的最大瓶劲,以便于发现系统的极限、有没有Outofmemory这样的问题存在以及相关的系统设置、配置是否搭挡的合理的一种测试。

    一般商业的比较好的用LoaderRunner,如果没钱的就用OpensourceJmeter来模拟这个VU的操作。

    压力测试,存在几个误区,需要小心。

    1  无限大的拼命增加VU的数量

    系统再完美,硬件配置再高,也经不住没有经过合理运算的VU的压力呀。

    2  偏执的用一定的数据量的VU,跑7*24小时

    不是说这个没必要,很有必要,小日本的电视为什么寿命敢说比中国人生产的电视机寿命长?因为它用一个机械臂就对着电视机的按钮不断的点点点。

    我们说的压力测试要测试多长时间,关键是要看经过科学计算的VU的数量以及核心交易数有多少,不是说我拿250VU24*7如果没有问题我这个系统就没有问题了,这样的说法是不对的,错误的。随便举个例子就能把你推倒。

    假设我有250VU,同时跑上万笔交易,每个VU都有上万笔交易,250VU一次跑下来可能就要数个小时,你又怎么能断定250VU对于这样的系统我跑24*7小时就能真的达到上万笔交易在250VU的并发操作下能够真的跑完7天的全部交易?可能需要一周半或者两周呢?对吧?

    我还看到过有人拿500VU对着一条交易反复跑24*7小时。。。这样的测试有意义吗?你系统就仅仅只有一条交易?你怎么能够判断这条交易涉及到的数据量最大?更不用说交易是彼此间有依赖的,可能a+b+c+d的交易的一个混合组织就能够超出你单笔交易所涉及到的数据量了呢!

    2.2 合理的制定系统最大用户、并发用户

    提供下面这个公式,以供大家在平时或者日常需要进行的性能测试中作为一个参考。

    1        计算平均的并发用户数:C = nL/T

    公式(1)中,C是平均的并发用户数;nlogin session的数量;Llogin session的平均长度;T指考察的时间段长度。

    2        并发用户数峰值:C’ ≈ C+3根号C

    公式(2)则给出了并发用户数峰值的计算方式中,其中,C’指并发用户数的峰值,C就是公式(1)中得到的平均的并发用户数。该公式的得出是假设用户的loginsession产生符合泊松分布而估算得到的。

    实例:

    假设有一个OA系统,该系统有3000个用户,平均每天大约有400个用户要访问该系统,对一个典型用户来说,一天之内用户从登录到退出该系统的平均时间为4小时,在一天的时间内,用户只在8小时内使用该系统。

    则根据公式(1)和公式(2),可以得到:

    C =400*4/8 = 200

    C’≈200+3*根号200 = 242

    F=VU * R / T

    其中F为吞吐量,VU表示虚拟用户个数,R表示每个虚拟用户发出的请求数,T表示性能测试所用的时间

    R = T /TS

    2.3 影响和评估性能的几个关键指标

    从上面的公式一节中我们还得到了一个名词吐吞量。和吞吐量相关的有下面这些概念,记录下来以供参考。

    ²  吞吐量

    指在一次性能测试过程中网络上传输的数据量的总和。

    对于交互式应用来说,吞吐量指标反映的是服务器承受的压力,在容量规划的测试中,吞吐量是一个重点关注的指标,因为它能够说明系统级别的负载能力,另外,在性能调优过程中,吞吐量指标也有重要的价值。

    ²  吞吐率

    单位时间内网络上传输的数据量,也可以指单位时间内处理客户请求数量。它是衡量网络性能的重要指标,通常情况下,吞吐率用字节数/来衡量,当然,你可以用请求数/页面数/来衡量。其实,不管是一个请求还是一个页面,它的本质都是在网络上传输的数据,那么来表示数据的单位就是字节数。

    ²  事务

    就是用户某一步或几步操作的集合。不过,我们要保证它有一个完整意义。比如用户对某一个页面的一次请求,用户对某系统的一次登录,淘宝用户对商品的一次确认支付过程。这些我们都可以看作一个事务。那么如何衡量服务器对事务的处理能力。又引出一个概念----TPS

    ²  TPS (Transaction Per second)

    每秒钟系统能够处理事务或交易的数量,它是衡量系统处理能力的重要指标。

    ²  点击率(Hit PerSecond

    点击率可以看做是TPS的一种特定情况。点击率更能体现用户端对服务器的压力。TPS更能体现服务器对客户请求的处理能力。

    每秒钟用户向web服务器提交的HTTP请求数。这个指标是web应用特有的一个指标;web应用是请求-响应模式,用户发一个申请,服务器就要处理一次,所以点击是web应用能够处理的交易的最小单位。如果把每次点击定义为一个交易,点击率和TPS就是一个概念。容易看出,点击率越大。对服务器的压力也越大,点击率只是一个性能参考指标,重要的是分析点击时产生的影响。

    需要注意的是,这里的点击不是指鼠标的一次单击操作,因为一次单击操作中,客户端可能向服务器发现多个HTTP请求。

    ²  吞吐量指标的作用:

    ü  用户协助设计性能测试场景,以及衡量性能测试场景是否达到了预期的设计目标:在设计性能测试场景时,吞吐量可被用户协助设计性能测试场景,根据估算的吞吐量数据,可以对应到测试场景的事务发生频率,事务发生次数等;另外,在测试完成后,根据实际的吞吐量可以衡量测试是否达到了预期的目标。

    ü  用于协助分析性能瓶颈:吞吐量的限制是性能瓶颈的一种重要表现形式,因此,有针对性地对吞吐量设计测试,可以协助尽快定位到性能冰晶所在位置。

    ²  平均相应时间

    也称为系统响应时间,它一般指在指定数量的VU情况下,每笔交易从mouseclickIE的数据刷新与展示之间的间隔,比如说:250VU下每笔交易的响应时间不超过2秒。

    当然,响应时间也不能一概而论,对于实时交易如果银行柜台操作、超市收银员(邪恶的笑。。。)的操作、证交所交易员的操作来说这些操作的响应时间当然是越快越好,而对于一些企业级的如:

    与银行T+1交易间的数据跑批、延时交易、T+1报表等,你要求它在2秒内响应,它也做不到啊。就好比你有个1MB的带宽,你传的东西是超过4MB,你要它在2秒内跑完理论速度也做不到啊,对吧,所以有些报表或者数据,光前面传输时间就不止两秒了。。。一口咬死说我所有的交易平均相应时间要2秒,真的是不科学的!

    2.4 合理的性能测试

    ²  VU数量的增加

    一个合理的性能测试除了需要合理的计算VU的数量、合理的设置系统平均响应时间外还需要合理的在测试时去规划发起VU的时间,比如说,我看到有人喜欢这样做压力测试。

    第一秒时间,500个并发用户全部发起了。。。结果导致系统没多久就崩了,然后说系统没有满足设计要求。

    为什么说上述这样的做法是不对的?我们说不是完全不对,只能说这样的测试已经超过了500VU的并发的设计指标了。

    合理的并发应该是如下这样的:

    25-50VU开始起交易了,然后过一段时间又有25-50个用户,过一段时间又增加一些VU,当所有的设计VU都发起交易了,此时,再让压力测试跑一段时间比如说:24*7是比较合理的。所以VU数量不是一上手就500个在一秒内发起的,VU数量的增加应该如下面这张趋势图:



    这是一个阶梯状的梯型图,可以看到VU的发起是逐渐逐渐增多的,以下两种情况如果发生需要检查你的系统是否在原有设置上存在问题:

    ü  VU数量上升阶段时崩溃

    有时仅仅在VU数量上升阶段,系统就会了现各种各样的错误,甚至有崩溃者,这时就有重新考虑你的系统是否有设置不合理的地方了。

    ü  VU全部发起后没多久系统崩溃

    VU在达到最高值时即所有的VU都已经发起了,此时它是以一条直的水平线随着系统运行而向前延伸着的,但过不了多久,比如说:运行24*7小时,运行了没一、两天,系统崩溃了,也需要做检查。

    所以,理想的性能测试应该是VU数量上升到最终VU从发起开始到最后所有VU把交易做完后,VU数量落回零为止。

    ²  吐吞量的变化

    2.3节我们可以知道,吞吐量是随着压力/性能测试的时间而逐渐增大的,因此你的吞吐量指示应该如下图所示:



    肯定是这样,你的吞吐量因该是积累的,如果你的吞吐量在上升了一段时间后突然下落,而此时你的性能测试还在跑着,如下图所示:



    那么,此时代表什么事情发生了?你可以查一下你的loaderrunner或者jmeter里对于这段吞吐量回落期间的交易的response的状态进行查看,你将会发现大量的error已经产生,因为产生了error,所以你的交易其实已经出错了,因此每次运行的数据量越来越小,这也就意味着你的压力测试没有过关,系统被你压崩了!

    ²  平均响应时间

    平均响应时间如VU的数量增加趋势图一样,一定是一开始响应时间最短,然后一点点增高,当增高到一定的程度后,即所有的VU都发起交易时,你的响应时间应该维持在一个水平值,然后随着VU将交易都一笔笔做完后,这个响应时间就会落下来,这段时间内的平均值就是你的系统平均响应时间。看看它,有没有符合设计标准?

    ²  内存监控

    我们就来说AppServer,我们这边用的是TomcatSUNJVM的内存变化,我们就用两张图例来讲解吧:

    理想状态情况下的JVM内存使用趋势:



    这是一个波浪型的(或者也可以说是锯齿型的)趋势图,随着VU数量的一点点增加,我们的内存使用数会不断的增加,但是JVM的垃圾回收是自动回收机制的,因此如果你的JVM如上述样的趋势,内存上涨一段时间,随即会一点点下落,然后再上涨一点,涨到快到头了又开始下落,直到最后你的VU数量全部下降下来时,你的JVM的内存使用也会一点点的下降。

    非理想状态情况下的JVM内存使用趋势:



    嘿嘿嘿,看到了吗?你的JVM随着VU数量的上升,而直线上升,然后到了一定的点后,即到了Java –Xmx后的那个值后,突然直线回落,而此时你的交易还在进行,压力测试也还在进行,可是内存突然回落了。。。因为你的JVM已经crash了,即OUT OF MEMORY鸟。

    ²  CPU Load

    我们来看一份测试人员提交上来CPU得用率的报告:

    Web Server

    App Server

    DB Server

    60%

    98%

    =_=!(oh my god)

    6%

    同时平均响应时间好慢啊。

    拿过来看了一下代码与设计。。。Struts+spring+JDBC的一个框架,没啥花头的,再仔细一看Service层。

    大量的复杂业务逻辑甚至报表的产生全部用的是javaobject:List, Hashmap等操作,甚至还有在Service层进行排序、复杂查询等操作。

    一看DB层的CPU利用率才6%,将一部分最复杂的业务拿出去做成StoreProcedure(存储过程后),再重新运行压力测试。

    Web Server

    App Server

    DB Server

    60%

    57%

    =_=!(oh my god)

    26%

    同时平均响应时间比原来快了15-16倍。

    为什么??

    看看第一份报告,我们当时还查看了数据库服务器的配置,和APPServer的配置是一个级别的,而利用率才6%。。。

    数据库,至所以是大型的商用的关系型数据库,你只拿它做一个存储介质,你这不是浪费吗?人家里面设置的这个StoreProcedure的处理能力,索引效率,数据分块等功能都没有去利用,而用你的代码去实现那么多复杂业务比如说多表关联、嵌套等操作,用必要吗?那要数据库干什么用呢?

    是啊,我承认,原有这样的代码,跨平台能力强一点,可付出的代价是什么呢?

    用户在乎你所谓的跨平台的理论还是在乎的是你系统的效率?一个系统定好了用DB2或者是SQL SERVER,你觉得过一年它会换成ORACLE或者MySQL吗?如果1年一换,那你做的系统也只能让用户勉强使用一年,我劝你还是不要去做了。在中国,有人统计过5年左右会有一次系统的更换,而一些银行、保险、金融行业的系统一旦采用了哪个数据库,除非这个系统彻底出了问题,负责是不会轻意换数据库的,因此不要拿所谓的纯JAVA代码或者说我用的是hibernate,ejb实现可以跨数据库这套来说事,效率低下的系统可以否定你所做的一切,一切!

    三、Apache服务器的优化

    上面两节,讲了大量的理论与实际工作中碰到的相关案例,现在就来讲一下在我们第一天和第二天中的ApacheHttpServer + Tomcat这样的架构,怎么来做优化吧。

    3.1 Linux/UnixLinux系统下Apache并发数的优化

    ApacheHttp Server在刚安装完后是没有并发数的控制的,它采用一个默认的值,那么我们的Web Server硬件很好,允许我们撑到1000个并发即VU而因为我们没有去配置导致我们的WebServer300个并发都撑不到,你们认为,这是谁的责任?

    ApacheHttp服务器采用prefork或者是worker两种并发控制模式。

    ² preforkMPM

    使用多个子进程,每个子进程只有一个线程。每个进程在某个确定的时间只能维持一个连接。在大多数平台上,PreforkMPM在效率上要比Worker MPM要高,但是内存使用大得多。prefork的无线程设计在某些情况下将比worker更有优势:它可以使用那些没有处理好线程安全的第三方模块,并且对于那些线程调试困难的平台而言,它也更容易调试一些。

    ² workerMPM 使用多个子进程,每个子进程有多个线程。每个线程在某个确定的时间只能维持一个连接。通常来说,在一个高流量的HTTP服务器上,Worker MPM是个比较好的选择,因为Worker MPM的内存使用比PreforkMPM要低得多。但worker MPM也由不完善的地方,如果一个线程崩溃,整个进程就会连同其所有线程一起"死掉".由于线程共享内存空间,所以一个程序在运行时必须被系统识别为"每个线程都是安全的"

    一般来说我们的ApacheHttp Server都是装在Unix/Linux下的,而且是采用源码编译的方式来安装的,我们能够指定在编译时Apache就采用哪种模式,为了明确我们目前的Apache采用的是哪种模式在工作,我们还可以使用httpd–l命令即在Apachebin目录下执行httpd –l,来确认我们使用的是哪种模式。

    这边,我们使用Apache配置语言中的” IfModule”来自动选择模式的配置。

    我们的ApacheHttp Server在配完后一般是没有这样的配置的,是需要你手动的添加如下这样的一块内容的,我们来看,在httpd.conf文件中定位到最后一行LoadModule,敲入回车,加入如下内容:

    <IfModule prefork.c>

    ServerLimit  20000

    StartServers  5

    MinSpareServers  5

    MaxSpareServers  10

    MaxClients  1000

    MaxRequestsPerChild 0

    </IfModule>

    上述参数解释:

    ü   ServerLimit    20000

    默认的MaxClient最大是256个线程,如果想设置更大的值,就的加上ServerLimit这个参数。20000ServerLimit这个参数的最大值。如果需要更大,则必须编译apache,此前都是不需要重新编译Apache

    生效前提:必须放在其他指令的前面

    ü  StartServers  5

    指定服务器启动时建立的子进程数量,prefork默认为5

     

    ü  MinSpareServers  5

    指定空闲子进程的最小数量,默认为5。如果当前空闲子进程数少于MinSpareServers,那么Apache将以最大每秒一个的速度产生新的子进程。此参数不要设的太大。

    ü  MaxSpareServers  10

    设置空闲子进程的最大数量,默认为10。如果当前有超过MaxSpareServers数量的空闲子进程,那么父进程将杀死多余的子进程。此参数不要设的太大。如果你将该指令的值设置为比MinSpareServers小,Apache将会自动将其修改成"MinSpareServers+1"

    ü  MaxClients  256

    限定同一时间客户端最大接入请求的数量(单个进程并发线程数),默认为256。任何超过MaxClients限制的请求都将进入等候队列,一旦一个链接被释放,队列中的请求将得到服务。要增大这个值,你必须同时增大ServerLimit

    ü  MaxRequestsPerChild10000

    每个子进程在其生存期内允许伺服的最大请求数量,默认为10000.到达MaxRequestsPerChild的限制后,子进程将会结束。如果MaxRequestsPerChild"0",子进程将永远不会结束。

    MaxRequestsPerChild设置成非零值有两个好处:

    1.可以防止(偶然的)内存泄漏无限进行,从而耗尽内存。

    2.给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。

    Prefork.c的工作方式:

    一个单独的控制进程(父进程)负责产生子进程,这些子进程用于监听请求并作出应答。Apache总是试图保持一些备用的(spare)或者是空闲的子进程用于迎接即将到来的请求。这样客户端就不需要在得到服务前等候子进程的产生。在Unix系统中,父进程通常以root身份运行以便邦定80端口,而Apache产生的子进程通常以一个低特权的用户运行。UserGroup指令用于设置子进程的低特权用户。运行子进程的用户必须要对它所服务的内容有读取的权限,但是对服务内容之外的其他资源必须拥有尽可能少的权限。

    在上述的</IfModule>后再加入一个”<IfModule>”如下红色加粗(大又粗)内容:

    <IfModule prefork.c>

    ServerLimit  20000

    StartServers  5

    MinSpareServers  5

    MaxSpareServers  10

    MaxClients  1000

    MaxRequestsPerChild 0

    </IfModule>

    <IfModule worker.c>

    ServerLimit  50

    ThreadLimit  200

    StartServers  5

    MaxClients  5000

    MinSpareThreads  25

    MaxSpareThreads  500

    ThreadsPerChild  100

    MaxRequestsPerChild 0

    </IfModule>

    上述参数解释:

    ü  ServerLimit16

    服务器允许配置的进程数上限。这个指令和ThreadLimit结合使用设置了MaxClients最大允许配置的数值。任何在重启期间对这个指令的改变都将被忽略,但对MaxClients的修改却会生效。

    ü  ThreadLimit64

    每个子进程可配置的线程数上限。这个指令设置了每个子进程可配置的线程数ThreadsPerChild上限。任何在重启期间对这个指令的改变都将被忽略,但对ThreadsPerChild的修改却会生效。默认值是"64".

    ü  StartServers3

    服务器启动时建立的子进程数,默认值是"3"

    ü  MinSpareThreads75

    最小空闲线程数,默认值是"75"。这个MPM将基于整个服务器监视空闲线程数。如果服务器中总的空闲线程数太少,子进程将产生新的空闲线程。

    ü  MaxSpareThreads250

    设置最大空闲线程数。默认值是"250"。这个MPM将基于整个服务器监视空闲线程数。如果服务器中总的空闲线程数太多,子进程将杀死多余的空闲线程。MaxSpareThreads的取值范围是有限制的。Apache将按照如下限制自动修正你设置的值:worker要求其大于等于MinSpareThreads加上ThreadsPerChild的和

    ü  MaxClients400

    允许同时伺服的最大接入请求数量(最大线程数量)。任何超过MaxClients限制的请求都将进入等候队列。默认值是"400",16(ServerLimit)乘以25(ThreadsPerChild)的结果。因此要增加MaxClients的时候,你必须同时增加ServerLimit的值。

    ü  ThreadsPerChild25

    每个子进程建立的常驻的执行线程数。默认值是25。子进程在启动时建立这些线程后就不再建立新的线程了。

    ü  MaxRequestsPerChild  0

    设置每个子进程在其生存期内允许伺服的最大请求数量。到达MaxRequestsPerChild的限制后,子进程将会结束。如果MaxRequestsPerChild"0",子进程将永远不会结束。

    MaxRequestsPerChild设置成非零值有两个好处:

    1.可以防止(偶然的)内存泄漏无限进行,从而耗尽内存。

    2.给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。

    注意

    对于KeepAlive链接,只有第一个请求会被计数。事实上,它改变了每个子进程限制最大链接数量的行为。

    Worker.c的工作方式:

    每个进程可以拥有的线程数量是固定的。服务器会根据负载情况增加或减少进程数量。一个单独的控制进程(父进程)负责子进程的建立。每个子进程可以建立ThreadsPerChild数量的服务线程和一个监听线程,该监听线程监听接入请求并将其传递给服务线程处理和应答。Apache总是试图维持一个备用(spare)或是空闲的服务线程池。这样,客户端无须等待新线程或新进程的建立即可得到处理。在Unix中,为了能够绑定80端口,父进程一般都是以root身份启动,随后,Apache以较低权限的用户建立子进程和线程。UserGroup指令用于设置Apache子进程的权限。虽然子进程必须对其提供的内容拥有读权限,但应该尽可能给予它较少的特权。另外,除非使用了suexec,否则,这些指令设置的权限将被CGI脚本所继承。

    公式:

    ThreadLimit>=ThreadsPerChild

    MaxClients <= ServerLimit * ThreadsPerChild  必须是ThreadsPerChild的倍数

    MaxSpareThreads>=MinSpareThreads+ThreadsPerChild

    硬限制:

    ServerLimiThreadLimit这两个指令决定了活动子进程数量和每个子进程中线程数量的硬限制。要想改变这个硬限制必须完全停止服务器然后再启动服务器(直接重启是不行的)

    Apache在编译ServerLimit时内部有一个硬性的限制,你不能超越这个限制。

    preforkMPM最大为"ServerLimit200000"

    其它MPM(包括work MPM)最大为"ServerLimit 20000

    Apache在编译ThreadLimit时内部有一个硬性的限制,你不能超越这个限制。

    mpm_winnt"ThreadLimit 15000"

    其它MPM(包括work prefork)"ThreadLimit 20000

    注意

    使用ServerLimitThreadLimit时要特别当心。如果将ServerLimitThreadLimit设置成一个高出实际需要许多的值,将会有过多的共享内存被分配。当设置成超过系统的处理能力,Apache可能无法启动,或者系统将变得不稳定。

    3.2 WindowsWindows系统下Apache并发数的优化

    以上是Linux/Unix下的Apache的并发数优化配置,如果我们打入了httpd–l如下显示:

    怎么办?

    ü  步骤一

    先修改/path/apache/conf/httpd.conf文件。

    httpd.conf

    “#Includeconf/extra/httpd-mpm.conf”前面的 “#”去掉,保存。

    ü  步骤二

    再修改/apache安装目录/conf/extra/httpd-mpm.conf文件。

    mpm_winnt模式下,Apache不使用prefork也不使用work工作模式,切记!

    因此,我们只要找到原文件中:

    <IfModule mpm_winnt_module>

        ThreadsPerChild      150

        MaxRequestsPerChild    0

    </IfModule>

    修改后

    <IfModule mpm_winnt_module>

        ThreadsPerChild      500

        MaxRequestsPerChild    5000

    </IfModule>

    上述参数解释:

    ü  ThreadsPerChild

    是指一个进程最多拥有的线程数(Windows版本,貌似不可以开启多个进程),一般100-500就可以,根据服务器的具体性能来决定。

    ü  MaxRequestsPerChild

    是指一个线程最多可以接受的连接数,默认是0,就是不限制的意思,

    0极有可能会导致内存泄露。所以,可以根据实际情况,配置一个比较大的值。Apache会在几个线程之间进行轮询,找到负载最轻的一个线程来接受新的连接。

    注意:

    修改后,一定不要apacherestart,而是先 apache stop然后再 apache start才可以。

    3.3 启用服务端图片压缩

    对于静态的html 文件,在apache 可加载mod_deflate.so 模块,把内容压缩后输出,可节约大量的传输带宽。

    打开httpd.conf文件,找到:

    #LoadModule deflate_module modules/mod_deflate.so

    将前面的“#”去掉,变成:

    LoadModule deflate_module modules/mod_deflate.so

    然后在最后一行的LoadModule处,加入如下的几行:

    <IfModule mod_deflate.c>

     DeflateCompressionLevel 7

     AddOutputFilterByType DEFLATE text/html text/plain text/xml application/x-httpd-PHP

     AddOutputFilter DEFLATE css js

    </IfModule>

    注意:

    默认等级是6,而且9级需要更多的CPU时间,用默认的6级就可以了。

    要注意的是,apache 2.2.15,我用httpd -l,居然发现mod_deflat已经内置了,所以其实就不用再在httpd.conf中增加loadmodule,否则会说出错的

    3.4 Apache中将MS办公文档自动关联客户端的MS-Office

    我们经常会在web页的一个超链接上点一个指向物理文件的文档,我们一般会得到保存,另存为,打开3个选项,当我们打开的如果是一个MS文档,在选打开选项时IE会自动启用客户端上装有的word或者是excel等相关MS办公工具去打开,这个怎么做呢?很简单。

    打开httpd.conf,找到:

        AddType application/x-compress .Z

        AddType application/x-gzip .gz .tgz

    在其后敲入一个回车,加入:

    AddType application/vnd.openxmlformats  docx pptx xlsx doc xls ppt txt

    重启Apache服务即可。

    3.5 防止DDOS攻击

    DDOS攻击即采用自动点击机器人或者连续点击工具不断的刷新某一个网址或者网页上的按钮,造成网站在一时间收到大量的HTTP请求,进而阻塞网站正常的HTTP通道甚至造成网站瘫痪。



    为了防止这一形式的攻击,我们一般把在一个按钮或者是一个请求在一秒内连续执行如:100次,可以认为是一种攻击(比如说你打开一个网页,点一下提交按钮,然后按住F5键不松开)。

    Linux下的Apache HttpServer安装后会提供一个mod_evasive20的模块,用于防止这一形式的攻击,它的做法是:

    如果认为是一个DDOS攻击,它的防范手段采用如下两种形势:

    ü  把这个请求相关联的IP,封锁30分钟

    ü  直接把相关的IP踢入黑名单,让其永不翻身

    设置:

    在你的Apachehttpd.conf文件中的最后一行“LoadModule”加入如下这句:

    LoadModule evasive20_module   /usr/lib/httpd/modules/mod_evasive20.so

    然后加入下面这几行

    <IfModule mod_evasive20.c>

    DOSHashTableSize 3097

    DOSPageCount 15

    DOSSiteCount 100

    DOSPageInterval 1

    DOSSiteInterval 1

    DOSBlockingPeriod 36000

    DOSEmailNotify 网站超级管理员@xxx.com

    DOSLogDir "logs/mod_evasive"

    </IfModule>

    核心参数解释:

    ü  DOSHashTableSize3097 记录黑名单的尺寸

    ü  DOSPageCount 每个页面被判断为dos攻击的读取次数

    ü  DOSSiteCount 每个站点被判断为dos攻击的读取部件(object)的个数

    ü  DOSPageInterval 读取页面间隔秒

    ü  DOSSiteInterval 读取站点间隔秒

    ü  DOSBlockingPeriod 被封时间间隔秒

    注意:

    上述设置是针对Linux/Unix下的Apache Server,相关的Windows下的Apache见如下设置:

    Windows下的Apache加载mod_evasive模块

    1. 下载附件中的压缩包,解压并拷贝mod_dosevasive22.dllApache安装目录下的modules目录(当然也可以是其他目录,需要自己修改路径)。

    2. 修改Apache的配置文件http.conf

    添加以下内容

    LoadModule dosevasive22_module modules/mod_dosevasive22.dll

    DOSHashTableSize 3097

    DOSPageCount 3

    DOSSiteCount 50

    DOSPageInterval 1

    DOSSiteInterval 1

    DOSBlockingPeriod 10

    3.6 Apache中设置URL含中文附件的下载/打开的方法(仅限Linux系统下)

    这个话题很有趣,起因是我们在工程中碰到了客户这样的一个需求:

    <ahref=”xxx.xxx.xx/xx/xxx/轮胎损坏情况2007-05-05.jpg”>损坏部件</a>

    看看好像没啥问题,一点这个超链接,因该是在IE中打开一个叫轮胎损坏情况2007-05-05.jpg”,嘿嘿,大家自己动手放一个带有中文名的这样的一个图片,看看能否被解析,解析不了。

    所以我们就说,真奇怪,我们上传图片都是上传时的图片名经上传组件解析过以后变成一个UUID或者是GUID一类的文件名如:gb19070122abcxd.jpg这样一种英文加数字组合的文件名,这样的文件名,Apache当然是可以解析的,客户坚持一定我上传的图片是中文名(连中文描述都不行),因为,客户说:我们是中国人,当然用中文图片名。。。

    没办法,找了半天,找到一篇日文的教程,还好还好,N年前学过一点点日语,照着教程把它啃下来了。

    这是一个日本人写的关于在Apache中支持以亚州文字命名文件名的一个补丁,叫“mod_encoding”

    相关配置:

    1.       下载完后是一个这样的压缩包:mod_encoding-20021209.tar.gz

    2.       解压后使用:

    configure

    make

    make install

    make这一行时,编译出错,报“make: *** [mod_encoding.so]Error 1”这样的错

    原因很明显,是regex.h未包含进来,解决办法也很简单:

    ü  vi打开mod_encoding.c

    ü  #include<httpd.h>那一段的前面加上如下一行:

    #include<regex.h>然后:

              重新makemake install搞定,CALL!!!

    3.       编译后得到一个:mod_encoding.so的文件,然后在httpd.conf文件中加入下面这几行:

    LoadModule encoding_module modules/mod_encoding.so

    Header add MS-Author-Via "DAV"

    <IfModule mod_encoding.c>

      EncodingEngine    on

      NormalizeUsername on

      SetServerEncoding GBK

      DefaultClientEncoding UTF-8 GBK GB2312

      AddClientEncoding "(Microsoft .* DAV $)" UTF-8 GBK GB2312

      AddClientEncoding "Microsoft .* DAV" UTF-8 GBK GB2312

      AddClientEncoding "Microsoft-WebDAV*" UTF-8 GBK GB2312

    </IfModule>

    4.       重启Apache,搞定,在apache中我们的url可以是中文名的附件了。

    3.7 不可忽视的keepalive选项

    Apache 服务器中,KeepAlive是一个布尔值,On代表打开,Off 代表关闭,这个指令在其他众多的 HTTPD服务器中都是存在的。

    KeepAlive 配置指令决定当处理完用户发起的 HTTP请求后是否立即关闭 TCP连接,如果 KeepAlive设置为On,那么用户完成一次访问后,不会立即断开连接,如果还有请求,那么会继续在这一次 TCP连接中完成,而不用重复建立新的 TCP连接和关闭TCP 连接,可以提高用户访问速度。

    那么我们考虑3种情况:

    1.用户浏览一个网页时,除了网页本身外,还引用了多个JavaScript 文件,多个css文件,多个图片文件,并且这些文件都在同一个HTTP服务器上。

    2.用户浏览一个网页时,除了网页本身外,还引用一个javascript文件,一个图片文件。

    3.用户浏览的是一个动态网页,由程序即时生成内容,并且不引用其他内容。

    对于上面3中情况,我认为:1最适合打开 KeepAlive2 随意,3最适合关闭 KeepAlive

     下面我来分析一下原因。

     在Apache中,打开和关闭 KeepAlive功能,服务器端会有什么异同呢?

     先看看理论分析。

    打开KeepAlive 后,意味着每次用户完成全部访问后,都要保持一定时间后才关闭会关闭TCP连接,那么在关闭连接之前,必然会有一个Apache进程对应于该用户而不能处理其他用户,假设KeepAlive的超时时间为10 秒种,服务器每秒处理 50个独立用户访问,那么系统中 Apache的总进程数就是 10 * 50 500 个,如果一个进程占用 4M 内存,那么总共会消耗 2G内存,所以可以看出,在这种配置中,相当消耗内存,但好处是系统只处理了 50 TCP的握手和关闭操作。

     

    如果关闭KeepAlive,如果还是每秒50个用户访问,如果用户每次连续的请求数为3个,那么 Apache 的总进程数就是 50 * 3= 150个,如果还是每个进程占用 4M内存,那么总的内存消耗为 600M,这种配置能节省大量内存,但是,系统处理了 150 TCP的握手和关闭的操作,因此又会多消耗一些 CPU资源。

    再看看实践的观察。

    我在一组大量处理动态网页内容的服务器中,起初打开KeepAlive功能,经常观察到用户访问量大时Apache进程数也非常多,系统频繁使用交换内存,系统不稳定,有时负载会出现较大波动。关闭了KeepAlive功能后,看到明显的变化是:Apache的进程数减少了,空闲内存增加了,用于文件系统Cache的内存也增加了,CPU的开销增加了,但是服务更稳定了,系统负载也比较稳定,很少有负载大范围波动的情况,负载有一定程度的降低;变化不明显的是:访问量较少的时候,系统平均负载没有明显变化。

    总结一下:

    在内存非常充足的服务器上,不管是否关闭KeepAlive功能,服务器性能不会有明显变化;

    如果服务器内存较少,或者服务器有非常大量的文件系统访问时,或者主要处理动态网页服务,关闭KeepAlive后可以节省很多内存,而节省出来的内存用于文件系统Cache,可以提高文件系统访问的性能,并且系统会更加稳定。

    ü   补充1

    关于是否应该关闭 KeepAlive选项,我觉得可以基于下面的一个公式来判断。

      在理想的网络连接状况下,系统的Apache进程数和内存使用可以用如下公式表达:

    HttpdProcessNumber= KeepAliveTimeout *TotalRequestPerSecond / Average(KeepAliveRequests)

    HttpdUsedMemory= HttpdProcessNumber *MemoryPerHttpdProcess

      换成中文意思:

    Apache进程数 = KeepAliveTimeout *每秒种HTTP请求数 /平均KeepAlive请求

    Apache占用内存 =Apache进程数 *平均每进程占用内存数

      需要特别说明的是:

    [平均KeepAlive请求]数,是指每个用户连接上服务器后,持续发出的 HTTP请求数。当 KeepAliveTimeout 0或者 KeepAlive关闭时,KeepAliveTimeout不参与乘的运算从上面的公式看,如果 [每秒用户请求]多,[KeepAliveTimeout]的值大,[平均KeepAlive请求]的值小,都会造成 [Apache进程数]多和 [内存]多,但是当 [平均KeepAlive请求]的值越大时,[Apache进程数] [内存]都是趋向于减少的。

    基于上面的公式,我们就可以推算出当平均KeepAlive请求 <= KeepAliveTimeout时,关闭 KeepAlive选项是划算的,否则就可以考虑打开。

    ü   补充2

    KeepAlive 该参数控制Apache是否允许在一个连接中有多个请求,默认打开。但对于大多数论坛类型站点来说,通常设置为off以关闭该支持。

    ü   补充3

    如果服务器前跑有应用squid服务,或者其它七层设备,KeepAlive On设定要开启持续长连接

    实际在前端有squid的情况下,KeepAlive很关键。记得On

    Keeyalive不能随心所欲设置,而是需要根据实际情况,我们来看一个真实的在我工作中发生的搞笑一次事件:

    当时我已经离开该项目了,该项目的TeamLeader看到了keepalive的概念,他只看到了关闭keeyalive可以节省web服务器的内存,当时我们的web服务器只有4gb内存,而并发请求的量很大,因此他就把这个keepalive设成了off

    然后直接导致脱机客户端(脱机客户端用的是.net然后webservice连接)的“login”每次都显示出错

    一查代码才知道,由于这个脱机客户端使用的是webservice访问,.net开发团队在login功能中设了一个超时,30秒,30timeout后就认为服务器没有开启,结果呢由于原来的apache设的是keeyalivetimeout 15秒,现在被改成了off,好家伙,根本就没有了这个timeout概念,因此每次.net登录直接被apache弹回来,因为没有了这个timeout的接口了。

    由此可见,学东西。。。不能一知半解,务必求全面了解哈。

    3.8HostnameLookups设置为off

    尽量较少DNS查询的次数。如果你使用了任何”Allowfromdomain””Denyfromdomain”指令(也就是domain使用的是主机名而不是IP地址),则代价是要进行两次DNS查询(一次正向和一次反向,以确认没有作假)。所以,为了得到最高的性能,应该避免使用这些指令(不用域名而用IP地址也是可以的)

    展开全文
  • 处于项目需要,我研究了...最开始我实现了一个web端录好音然后上传服务端进行语音识别的简单demo,但是这种结构太过简单,对浏览器的负担太重,而且响应,交互差;后来经过调研,发现微软的语音服务接口是支持流...

    处于项目需要,我研究了一下web端的语音识别实现。目前市场上语音服务已经非常成熟了,国内的科大讯飞或是国外的微软在这块都可以提供足够优质的服务,对于我们工程应用来说只需要花钱调用接口就行了,难点在于整体web应用的开发。最开始我实现了一个web端录好音然后上传服务端进行语音识别的简单demo,但是这种结构太过简单,对浏览器的负担太重,而且响应慢,交互差;后来经过调研,发现微软的语音服务接口是支持流输入的连续识别的,因此开发重点就在于实现前后端的流式传输。
    参考这位国外大牛写的博文Continuous speech to text on the server with Cognitive Services,开发的新demo可以达到理想的效果,在网页上点击“开始录音”开启一次录音,对着麦克风随意说话,网页会把实时的音频数据传递给后端,后端实时识别并返回转换结果,点击“结束录音”停止。
    在这里插入图片描述

    1 整体结构

    整体结构图如下所示,在web端需要使用HTML5的Web Audio API接收麦克风输入的音频流,进行适量的处理后实时传递给服务端;web与服务端之间的音频流交互通过SignalR来实现;具体的语音识别通过调用微软语音服务实现。
    在这里插入图片描述该web实时语音识别demo可以实现下面的功能:

    • 可以通过网页传入麦克风音频
    • 网页可以实时显示语音识别结果,包括中间结果和最终结果
    • 可以保存每一次的录音,并且录音时长可以非常长
    • 支持多个web同时访问,服务端管理多个连接

    2 技术栈

    • ASP.NET Core开发
      服务端使用较新的 ASP.NET Core技术开发,不同于传统的 ASP.NETASP.NET Core更加适合前后台分离的web应用,我们会用 ASP.NET Core框架开发REST API为前端服务。如果不去纠结 ASP.NET Core的框架结构,实际的开发和之前的.NET应用开发没什么不同,毕竟只是底层结构不同。
    • JavaScript或TypeScript开发
      前端的逻辑用JavaScript开发,具体什么框架无所谓。我是用angular开发的,因此严格的说开发语言是TypeScript了。至于网页的具体内容,熟悉html、CSS就行了。
    • Web Audio API的使用和基本的音频处理的知识
      采集和上传麦克风音频都在浏览器进行,对此需要使用HTML5标准下的Web API进行音频流的获取,使用音频上下文(AudioContext)实时处理音频流。具体处理音频流时,需要了解一点基本的音频知识,例如采样率、声道等参数,WAV文件格式等等。
      相关资料:
      HTML5网页录音和压缩
      HTML5 getUserMedia/AudioContext 打造音谱图形化
      Capturing Audio & Video in HTML5
    • 微软语音认知服务
      微软的语音识别技术是微软云服务中的成员之一,相比于国内比较熟知的科大讯飞,微软的优势在于契合 .NET Core技术栈,开发起来非常方便,支持连续识别,支持自定义训练,并且支持容器部署,这对于那些对上传云服务有安全顾虑的用户更是好消息。当然价格考虑就得看具体情况了,不过如果你有Azure账号的话,可以开通标准版本的语音识别服务,这是免费的,只有时间限制;没有账号的话可以使用微软提供的体验账号体验一个月。
      官方文档: Speech Services Documentation
    • SignalR的使用
      要实现web和服务端的流通信,就必须使用web socket一类的技术来进行长连接通信,微软的SignalR是基于web socket的实时通信技术,如果我们的web需要和服务端保持长连接或者需要接收服务端的消息推送,使用该技术可以方便的实现。需要注意的是 ASP.NET SignalR and ASP.NET Core SignalR是有区别的,在 .NET Core环境下需要导入的是SignalR Core
      官方文档: Introduction to ASP.NET Core SignalR
      Angular下调用SignalR: How to Use SignalR with .NET Core and Angular
    • 字节流和异步编程的概念
      参考这篇博文Continuous speech to text on the server with Cognitive Services,在服务端需要自己实现一个特别的字节流来作为语音服务的数据源,因为语音服务在默认的字节流上一旦读取不到数据就会自动结束。在具体的实现中,将会用到一些信号量来进行读取控制。

    3 后端细节

    3.1 获取微软语音识别服务

    如果没有Azure账号,可以用微软提供的试用账号:
    在这里插入图片描述
    有Azure账号的话,在Azure门户里开通语音识别服务
    在这里插入图片描述
    在这里插入图片描述
    创建的时候可以选择F0类型的收费标准,这种是免费的:
    在这里插入图片描述
    在这里插入图片描述
    开通成功后,得到API Key的值,我们调用服务的时候传入这个参数;另一个参数是region,这个要看你创建服务的时候选择的区域,如果你选择的是East Asia,这个参数就是“eastasia”,如果用的是测试账号,统一用“westus”。

    3.2 创建并配置ASP.NET Core API项目

    新建一个 ASP.NET Core API项目
    在这里插入图片描述
    通过Nuget管理器添加语音服务和SignalR相关的包。Microsoft.CognitiveServices.Speech是微软语音服务包,Microsoft.AspNetCore.SignalR.Protocols.MessagePack用于SignalR中的MessagePack协议通信。
    在这里插入图片描述
    在Startup.cs中为 ASP.NET Core项目注入并配置SignalR服务:

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddCors(options =>
                {
                    options.AddPolicy("CorsPolicy",
                        builder => builder.WithOrigins("http://localhost:4200")
                        .AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowCredentials());
                }); // 跨域请求设置
                services.AddSignalR().AddMessagePackProtocol(options =>
                {
                    options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
            {
                MessagePack.Resolvers.StandardResolver.Instance
            };
                }); // 允许signalR以MessagePack消息进行通信
                services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                    app.UseHsts();
                }
    
                app.UseHttpsRedirection();
                app.UseCors("CorsPolicy"); //添加跨域请求服务
                app.UseSignalR(routes =>
                {
                    routes.MapHub<ContinuousS2TAPI.S2THub.S2THub>("/s2thub");
                }); //添加SignalR服务并配置路由,访问'/s2thub'将被映射到S2THub对象上
                app.UseMvc();
            }
    

    3.3 SignalR接口

    新建一个S2THub文件夹,将SignalR接口放着里面。先创建一个Connection类,它代表一个客户端连接:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.CognitiveServices.Speech;
    using ContinuousS2TAPI.Speech;
    
    namespace ContinuousS2TAPI.S2THub
    {
        public class Connection
        {
            public string SessionId; // 会话ID
            public SpeechRecognizer SpeechClient; // 一个语音服务对象
            public VoiceAudioStream AudioStream; //代表一个音频流
            public List<byte> VoiceData; //存储该次会话的音频数据
        }
    }
    

    然后创建继承自Hub的S2THub类,这个实例化的S2THub对象将会管理客户端的连接

        public class S2THub : Hub
        {
            private static IConfiguration _config;
            private static IHubContext<S2THub> _hubContext; //S2THub实例的上下文
            private static Dictionary<string, Connection> _connections; //维护客户端连接
    
            public S2THub(IConfiguration configuration, IHubContext<S2THub> ctx)
            {
                if (_config == null)
                    _config = configuration;
    
                if (_connections == null)
                    _connections = new Dictionary<string, Connection>();
    
                if (_hubContext == null)
                    _hubContext = ctx;
            }
            ...
            ...
         }
    

    在S2THub类中,需要定义两个供客户端调用的接口AudioStart和ReceiveAudio,客户端首先需要通过调用AudioStart来通知服务端开始一次语音识别会话,接着在接收到实时的音频二进制数据后调用ReceiveAudio接口来将数据发送给服务端。这里会用到一个名为VoiceAudioStream的流对象,这是我们自定义的流对象,具体实现后文给出。

            public async void AudioStart()
            {
                Console.WriteLine($"Connection {Context.ConnectionId} starting audio.");
                var audioStream = new VoiceAudioStream(); // 创建一个供语音识别对象读取的数据流
                var audioFormat = AudioStreamFormat.GetWaveFormatPCM(16000, 16, 1);
                var audioConfig = AudioConfig.FromStreamInput(audioStream, audioFormat);
                var speechConfig = SpeechConfig.FromSubscription("your key", "you region"); //使用你自己的key和region参数
                speechConfig.SpeechRecognitionLanguage = "zh-CN"; //中文
    
                var speechClient = new SpeechRecognizer(speechConfig, audioConfig);
    
                speechClient.Recognized += _speechClient_Recognized; // 连续识别存在Recognized和Recognizing事件
                speechClient.Recognizing += _speechClient_Recognizing;
                speechClient.Canceled += _speechClient_Canceled;
    
                string sessionId = speechClient.Properties.GetProperty(PropertyId.Speech_SessionId);
    
                var conn = new Connection()
                {
                    SessionId = sessionId,
                    AudioStream = audioStream,
                    SpeechClient = speechClient,
                    VoiceData = new List<byte>()
                };
    
                _connections.Add(Context.ConnectionId, conn); //将这个新的连接记录
    
                await speechClient.StartContinuousRecognitionAsync(); //开始连续识别
    
                Console.WriteLine("Audio start message.");
    
            }
    
            public void ReceiveAudio(byte[] audioChunk)
            {
                //Console.WriteLine("Got chunk: " + audioChunk.Length);
                _connections[Context.ConnectionId].VoiceData.AddRange(audioChunk); //记录接收到的音频数据
                _connections[Context.ConnectionId].AudioStream.Write(audioChunk, 0, audioChunk.Length);//并将实时的音频数据写入流
            }
    

    要将识别的结果返回给客户端,就需要调用客户端的接口实现推送,因此定义一个SendTranscript方法,内部会调用客户端名为IncomingTranscript的接口来推送消息:

            public async Task SendTranscript(string text, string sessionId)
            {
                var connection = _connections.Where(c => c.Value.SessionId == sessionId).FirstOrDefault(); 
                await _hubContext.Clients.Client(connection.Key).SendAsync("IncomingTranscript", text); //调用指定客户端的IncomingTranscript接口
            }
    

    在语音服务的识别事件中我们调用SendTranscript方法返回结果:

            private async void _speechClient_Canceled(object sender, SpeechRecognitionCanceledEventArgs e)
            {
                Console.WriteLine("Recognition was cancelled.");
                if (e.Reason == CancellationReason.Error)
                {
                    Console.WriteLine($"CANCELED: ErrorCode={e.ErrorCode}");
                    Console.WriteLine($"CANCELED: ErrorDetails={e.ErrorDetails}");
                    Console.WriteLine($"CANCELED: Did you update the subscription info?");
                    await SendTranscript("识别失败!", e.SessionId);
                }
            }
    
            private async void _speechClient_Recognizing(object sender, SpeechRecognitionEventArgs e)
            {
                Console.WriteLine($"{e.SessionId} > Intermediate result: {e.Result.Text}");
                await SendTranscript(e.Result.Text, e.SessionId);
            }
    
            private async void _speechClient_Recognized(object sender, SpeechRecognitionEventArgs e)
            {
                Console.WriteLine($"{e.SessionId} > Final result: {e.Result.Text}");
                await SendTranscript(e.Result.Text, e.SessionId);
            }
    

    最后我们重写Hub的OnDisconnectedAsync方法,这个方法会在hub连接断开时调用,可以在该方法内结束识别:

            public async override Task OnDisconnectedAsync(Exception exception)
            {
                var connection = _connections[Context.ConnectionId];
                Console.WriteLine($"Voice list length : {connection.VoiceData.Count}");
                byte[] actualLength = System.BitConverter.GetBytes(connection.VoiceData.Count);
                string rootDir = AppContext.BaseDirectory;
                System.IO.DirectoryInfo directoryInfo = System.IO.Directory.GetParent(rootDir);
                string root = directoryInfo.Parent.Parent.FullName;
                var savePath = $"{root}\\voice{connection.SessionId}.wav";
                using (var stream = new System.IO.FileStream(savePath, System.IO.FileMode.Create)) // 保存音频文件
                {
                    byte[] bytes = connection.VoiceData.ToArray();
                    bytes[4] = actualLength[0];
                    bytes[5] = actualLength[1];
                    bytes[6] = actualLength[2];
                    bytes[7] = actualLength[3];
                    bytes[40] = actualLength[0];
                    bytes[41] = actualLength[1];
                    bytes[42] = actualLength[2];
                    bytes[43] = actualLength[3]; // 计算并填入音频数据长度
                    stream.Write(bytes, 0, bytes.Length);
                }
    
                await connection.SpeechClient.StopContinuousRecognitionAsync(); //结束识别
                connection.SpeechClient.Dispose();
                connection.AudioStream.Dispose();
                _connections.Remove(Context.ConnectionId); //移除连接记录
                Console.WriteLine($"connection : {Context.ConnectionId} closed");
                await base.OnDisconnectedAsync(exception);
            }
    

    3.4 自定义音频流

    SpeechRecognizer对象可以接收一个特殊的流对象PullAudioStreamCallback作为数据源,如果传入了这个对象,SpeechRecognizer会主动从该流对象里读取数据。这个一个虚类,如果你只是给一段音频文件做识别,通过MemoryStream和BinaryStreamReader的简单组合就可以了(可以看微软的官方demo),但是SpeechRecognizer会在流中读取到0个字节后停止识别,在我们的场景中默认的流类型无法满足需求,当没有数据读取到时它们无法block住,PullAudioStreamCallback期望的效果是只有当明确流结束时读取流的Read()方法才返回0。因此需要定义我们自己的音频流对象
    首先定义一个继承自MemoryStream的EchoStream, 该流对象会在没有数据进入时进行等待而不是直接返回0

        public class EchoStream:MemoryStream
        {
            private readonly ManualResetEvent _DataReady = new ManualResetEvent(false);
            private readonly ConcurrentQueue<byte[]> _Buffers = new ConcurrentQueue<byte[]>();
    
            public bool DataAvailable { get { return !_Buffers.IsEmpty; } }
    
            public override void Write(byte[] buffer, int offset, int count)
            {
                _Buffers.Enqueue(buffer.Take(count).ToArray());
                _DataReady.Set();
            }
    
            public override int Read(byte[] buffer, int offset, int count)
            {
                //Debug.WriteLine("Data available: " + DataAvailable);
    
                _DataReady.WaitOne();
    
                byte[] lBuffer;
    
                if (!_Buffers.TryDequeue(out lBuffer))
                {
                    _DataReady.Reset();
                    return -1;
                }
    
                if (!DataAvailable)
                    _DataReady.Reset();
    
                Array.Copy(lBuffer, buffer, lBuffer.Length);
                return lBuffer.Length;
            }
        }
    

    然后定义PullAudioStreamCallback对象,作为语音服务的输入源。服务端会把客户端上传的byte[]数据通过Write()方法写入流,而语音服务会调用Read()方法读取数据,可以看到,通过一个ManualResetEvent信号量,使得流对象必须在调用Close()方法之后才会在Read()方法中返回0

        public class VoiceAudioStream : PullAudioInputStreamCallback
        {
            private readonly EchoStream _dataStream = new EchoStream();
            private ManualResetEvent _waitForEmptyDataStream = null;
    
            public override int Read(byte[] dataBuffer, uint size) //S2T服务从PullAudioInputStream中读取数据, 读到0个字节并不会关闭流
            {
                if (_waitForEmptyDataStream != null && !_dataStream.DataAvailable)
                {
                    _waitForEmptyDataStream.Set();
                    return 0;
                }
    
                return _dataStream.Read(dataBuffer, 0, dataBuffer.Length);
            }
    
            public void Write(byte[] buffer, int offset, int count) //Client向PullAudioInputStream写入数据
            {
                _dataStream.Write(buffer, offset, count);
            }
    
            public override void Close()
            {
                if (_dataStream.DataAvailable)
                {
                    _waitForEmptyDataStream = new ManualResetEvent(false); //通过ManualResetEvent强制流的使用者必须调用close来手动关闭流
                    _waitForEmptyDataStream.WaitOne();
                }
    
                _waitForEmptyDataStream.Close();
                _dataStream.Dispose();
                base.Close();
            }
        }
    

    4 前端细节

    前端是用Angular框架写的,这个Demo我们需要用到SignalR相关的库。使用npm install @aspnet/signalrnpm install @aspnet/signalr-protocol-msgpack安装这两个包,并导入到组件中:
    在这里插入图片描述
    此外在polyfills.ts文件中添加如下代码,否则可能会有浏览器兼容问题
    在这里插入图片描述
    在组件的初始化代码中,初始化Hub对象,并检查当前浏览器是否支持实时音频,目前主要是firefox和chrome支持浏览器流媒体获取。通过this.s2tHub.on('IncomingTranscript', (message) => { this.addMessage(message); });这句代码,给客户端注册了一个IncomingTranscript方法以供服务端调用

      constructor(private http: HttpClient) {
        this.s2tHub = new signalR.HubConnectionBuilder() 
            .withUrl(this.apiurl) //apiurl是SignalR Hub的地址,我这里是'https://localhost:5001/s2thub'
            .withHubProtocol(new signalRmsgpack.MessagePackHubProtocol()) 
            .configureLogging(signalR.LogLevel.Information)
            .build(); // 创建Hub对象
        this.s2tHub.on('IncomingTranscript', (message) => {
          this.addMessage(message);
        });  // 在客户端注册IncomingTranscript接口
       }
    
      ngOnInit() {
        if (navigator.mediaDevices.getUserMedia) {  // 检测当前浏览器是否支持流媒体
          this.addMessage('This browser support getUserMedia');
          this.supportS2T = true;
        } else {
          this.addMessage('This browser does\'nt support getUserMedia');
          this.supportS2T = false;
        }
      }
    

    点击"Start"后,启动Hub连接,开始向服务端发送数据。先是调用服务端的AudioStart接口通知服务端开始识别;然后调用ReceiveAudio接口先向服务端发送44字节的wav头数据,语音识别服务目前支持的音频类型是PCM和WAV,16000采样率,单声道,16位宽;最后在startStreaming中开始处理实时音频流。

      async startRecord() {
        if (!this.supportS2T) {
          return;
        }
        let connectSuccess = true;
        if (this.s2tHub.state !== signalR.HubConnectionState.Connected) {
          await this.s2tHub.start().catch(err => { this.addMessage(err); connectSuccess = false; } );
        }
        if (!connectSuccess)
        {
          return;
        }
    
        this.s2tHub.send('AudioStart');
        this.s2tHub.send('ReceiveAudio', new Uint8Array(this.createStreamRiffHeader()));
        this.startStreaming();
      }
      
      private createStreamRiffHeader() {
        // create data buffer
        const buffer = new ArrayBuffer(44);
        const view = new DataView(buffer);
    
        /* RIFF identifier */
        view.setUint8(0, 'R'.charCodeAt(0));
        view.setUint8(1, 'I'.charCodeAt(0));
        view.setUint8(2, 'F'.charCodeAt(0));
        view.setUint8(3, 'F'.charCodeAt(0));
    
        /* file length */
        view.setUint32(4, 0, true); // 因为不知道数据会有多长,先将其设为0
        /* RIFF type & Format */
        view.setUint8(8, 'W'.charCodeAt(0));
        view.setUint8(9, 'A'.charCodeAt(0));
        view.setUint8(10, 'V'.charCodeAt(0));
        view.setUint8(11, 'E'.charCodeAt(0));
        view.setUint8(12, 'f'.charCodeAt(0));
        view.setUint8(13, 'm'.charCodeAt(0));
        view.setUint8(14, 't'.charCodeAt(0));
        view.setUint8(15, ' '.charCodeAt(0));
    
        /* format chunk length */
        view.setUint32(16, 16, true); // 16位宽
        /* sample format (raw) */
        view.setUint16(20, 1, true);
        /* channel count */
        view.setUint16(22, 1, true); // 单通道
        /* sample rate */
        view.setUint32(24, 16000, true); // 16000采样率
        /* byte rate (sample rate * block align) */
        view.setUint32(28, 32000, true);
        /* block align (channel count * bytes per sample) */
        view.setUint16(32, 2, true);
        /* bits per sample */
        view.setUint16(34, 16, true);
        /* data chunk identifier */
        view.setUint8(36, 'd'.charCodeAt(0));
        view.setUint8(37, 'a'.charCodeAt(0));
        view.setUint8(38, 't'.charCodeAt(0));
        view.setUint8(39, 'a'.charCodeAt(0));
    
        /* data chunk length */
        view.setUint32(40, 0, true); // 因为不知道数据会有多长,先将其设为0
    
        return buffer;
    }
    

    在startStreaming方法中,首先调用window.navigator.mediaDevices.getUserMedia获取到麦克风输入流,然后创建AudioContext对象,用它创建多个音频处理节点,这些节点依次连接:
    在这里插入图片描述
    audioInput节点是音频输入节点,以音频流为输入;lowpassFilter节点作为一个滤波节点,对输入音频做低通滤波进行简单的降噪;jsScriptNode节点是主要的处理节点,可以为该节点添加事件处理,每当有数据进入该节点就进行处理和上传;最后是destination节点,它是Web Audio Context终结点,默认情况下会连接到本地的扬声器。

      private startStreaming() {
        window.navigator.mediaDevices.getUserMedia({
          audio: true
        }).then(mediaStream => {
          this.addMessage('get media stream successfully');
          this.audioStream = mediaStream;
          this.context = new AudioContext();
    
          this.audioInput = this.context.createMediaStreamSource(this.audioStream); // 源节点
    
          this.lowpassFilter = this.context.createBiquadFilter();
          this.lowpassFilter.type = 'lowpass';
          this.lowpassFilter.frequency.setValueAtTime(8000, this.context.currentTime); //滤波节点
    
          this.jsScriptNode = this.context.createScriptProcessor(4096, 1, 1); 
          this.jsScriptNode.addEventListener('audioprocess', event => {
            this.processAudio(event);
          });  // 处理事件
    
          this.audioInput.connect(this.lowpassFilter);
          this.lowpassFilter.connect(this.jsScriptNode);
          this.jsScriptNode.connect(this.context.destination);
    
        }).catch(err => {
          this.addMessage('get media stream failed');
        });
      }
    

    在jsScriptNode节点的audioprocess事件中,我们通过降采样、取单通道音频等处理获得正确格式的音频数据,再调用服务端的ReceiveAudio方法上传数据块

      private processAudio(audioProcessingEvent: any) {
        var inputBuffer = audioProcessingEvent.inputBuffer;
        // The output buffer contains the samples that will be modified and played
        var outputBuffer = audioProcessingEvent.outputBuffer;
        var isampleRate = inputBuffer.sampleRate;
        var osampleRate = 16000;
        var inputData = inputBuffer.getChannelData(0);
        var outputData = outputBuffer.getChannelData(0);
        var output = this.downsampleArray(isampleRate, osampleRate, inputData);
    
        for (var i = 0; i < outputBuffer.length; i++) {
            outputData[i] = inputData[i];
        }
    
        this.s2tHub.send('ReceiveAudio', new Uint8Array(output.buffer)).catch(err => {this.addMessage(err); });
      }
    
      private downsampleArray(irate: any, orate: any, input: any): Int16Array { // 降采样
        const ratio = irate / orate;
        const olength = Math.round(input.length / ratio);
        const output = new Int16Array(olength);
    
        var iidx = 0;
        var oidx = 0;
    
        for (var oidx = 0; oidx < output.length; oidx++) {
            const nextiidx = Math.round((oidx + 1) * ratio);
    
            var sum = 0;
            var cnt = 0;
    
            for (; iidx < nextiidx && iidx < input.length; iidx++) {
                sum += input[iidx];
                cnt++;
            }
    
            // saturate output between -1 and 1
            var newfval = Math.max(-1, Math.min(sum / cnt, 1));
    
            // multiply negative values by 2^15 and positive by 2^15 -1 (range of short)
            var newsval = newfval < 0 ? newfval * 0x8000 : newfval * 0x7FFF;
    
            output[oidx] = Math.round(newsval);
        }
    
        return output;
      }
    

    最后,定义stopRecord方法,断开AudioContext连接和Hub连接:

      async stopRecord() {
        this.jsScriptNode.disconnect(this.context.destination);
        this.lowpassFilter.disconnect(this.jsScriptNode);
        this.audioInput.disconnect(this.lowpassFilter);
        this.s2tHub.stop();
      }
    

    5 扩展

    • 自定义语音模型
      可以上传训练数据进行自定义模型的训练,得到的自定义语音服务可以适应更加特定的业务场景。 不过自定义模型是需要开通标准收费服务的
    • 容器部署
      微软目前也支持服务的容器部署,通过服务的本地部署,可以提高信息传输速度,并且减小私有信息安全性的顾虑
    展开全文
  • Vue部署提高页面访问速度,nginx代理

    千次阅读 2019-12-24 20:30:33
    在没有压缩本地js,css的文件下,部署线上环境是,访问页面加载极,网上搜了一下,果然有相应的解决办法,特此记录一下。还可以用cdn的方式,后面再看 2、步骤 在项目下打开命令窗口 -D : 写入devDependencies,...
  • 用产品思维设计API(五)—— 安全,就只能用HTTPS?前言 最近公司内部在重构项目代码,包括API方向的重构,期间遇到了很多的问题,不由得让我重新思考...这里所说的API仅为Web API,提供APP\WEB开发使用。 年前,我司
  • Java高并发秒杀API(四)之高并发优化

    万次阅读 多人点赞 2017-10-06 17:07:54
    Java高并发秒杀API(四)之高并发优化1. 高并发优化分析 关于并发 并发性上不去是因为当多个线程同时访问一行数据时,产生了事务,因此产生写锁,每当一个获取了事务的线程把锁释放,另一个排队线程才能拿到写锁,QPS...
  • WebService与RestAPI 、SoapAPI

    千次阅读 2018-11-10 13:10:52
    SOAP(简单对象访问协议)和REST(Representational State Transfer)都是Web服务的通信协议。SOAP长期以来一直是Web服务接口的标准方法,近年来它开始由REST主导,根据Stormpath统计,有超过70%的公共API使用 REST...
  • “基于搭建微服务架构的实践,作者总结出一套适用于现代化Web和云技术的实战经验,并从微服务领域的先行者(如Netflix、Soundcloud、谷歌、亚马逊、Spotify等)身上学到了很多经验。全文很长,建议收藏转发后阅读。 ...
  • 注意:由于这个项目小我把js写在html文件内,没有放在js文件夹内,有的同学打开api.bing.com/bing_search可能会报错未找到js文件,像上面一样添加规则就行. 此时在右边右击,选择openURL,即可以看到效果了 ...
  • 关于如何承载现有快速发展的API生态链,本文接下来介绍API网关在其中扮演的角色。
  • 网站访问缓慢排查思路

    千次阅读 2019-09-02 15:11:18
    一、网络问题 1、临时性 检查:ping, mtr,dig,dig+trace 等命令,检查网络状况,DNS等 解决:联系机房或视具体情况而定 ...2、网络不同或距离太远 ...3、资源加载 检查:chrome控制台 解决:CDN,合并请求,压缩页面...
  • 构建单页Web应用

    千次阅读 2016-02-21 16:21:49
    让我们先来看几个网站:coding(https://coding.net/)teambition(https://www.teambition.com/)cloud9(https://c9.io/) 注意这几个网站的相同点,那就是在...这就是单页Web应用。所谓单页应用,指的是在一个
  • 亚马逊SP-API对接实践解析(amazon selling partner api

    千次阅读 热门讨论 2021-08-31 19:02:06
    亚马逊(amazon)在2020年10月推出了新的替代MWS的api方案,称为Selling Partner API(SP-API). SP-API在授权方式、权限管理、覆盖站点、支持的卖家模式、接口交互数据格式上都做了升级优化,当然目前这个SP-API还不够...
  • Android访问中央气象台的天气预报API得到天气数据

    万次阅读 热门讨论 2011-12-08 17:55:52
    在用Android获取天气预报数据时,大家一定会首先想到Google的天气预报API,其实除了Google的天气预报API,免费的天气预报接口还有http://www.webservicex.net/globalweather.asmx?op=GetWeather、...
  • WSO2 API Manager调研学习总结

    千次阅读 2018-03-05 17:01:49
    近期要做一个SOA服务治理相关的项目,详细调研了下WSO2的API Manager(简称,APIM)。因为在调研过程中发现,国内研究WSO2的开发者少,社区中也不是很活跃,可供直接上手的中文文档很少,好在官网提供的用户文档...
  • 而电信的公网IP并不是固定的,要想持续稳定的访问到搭建的服务,我们可以使用固定的域名访问,这就需要DDNS,每次IP变化后,将域名动态的解析到新的公网IP上。 一开始准备使用路由自带的DDNS功能,家里用的小米...
  • 在使用HttpWebRequest建立http请求时,第一次连接的响应速度会很,而且还会出现请求超时的错误,这里大概有十几秒钟的等待时间,但是一旦第一次运行成功后,下面的请求页面速度就会很快了。 我发现的两种解决...
  • Zend API:深入 PHP 内核

    千次阅读 2012-03-22 10:45:13
    http://roygu.com/doc/blog/dive-to-php-core.htmlZend API:深入 PHP 内核译序及目录译序网上关于 PHP 的资料多如牛毛,关于其核心 Zend Engine 的却少之又少。PHP 中文手册出现已 N 年,但 Zend API 的翻译却仍然...
  • 众所周知,Rust近年发展迅猛,同时也带来一些新的概念,比如生命周期等,另外rust没有全局状态,或者说实现比较困难,以及编译检查比较严格,相对速度也比较,这样对实现web框架带来一些困难,下来我们看下这些...
  • 小白入坑 Web 渗透测试必备指南

    万次阅读 多人点赞 2017-11-13 00:00:00
    本文来自作者 肖志华 在 GitChat 上分享「Web 渗透测试入坑必备指南」,「阅读原文」查看交流实录 「文末高能」 编辑 | 黑石 小白如何快速入门 由于本人技术性受限制,可能部分内容显得不那么清晰...
  • SpringCloud微服务 之API网关设计(一)

    万次阅读 2018-12-11 11:22:10
    由于我们使用的服务系统架构,所以没办法像传统单体应用一样依靠数据库的 join 查询来得到最终结果,那么如何才能访问各个服务呢? 按照微服务设计的指导原则,我们的微服务可能存在下面的问题: 服务使用了多种...
  • Android访问中央气象台的天气预报API得到天气数据 .   http://flash.weather.com.cn/wmaps/xml/china.xml  能够获取国内各省及省会城市的天气,可以通过pyName载入各省内城市的天气  如:河北省 pyName...
  • 大型 Web 应用插件化架构探索

    千次阅读 2021-01-08 16:20:00
    随着 Web 技术的逐渐成熟,越来越多的应用架构趋向于复杂,例如阿里云、腾讯云等巨型控制台项目,每个产品下都有各自的团队来负责维护和迭代。不论是维护还是发布以及管控成本都随着业务体量的增...
  • web安全防范

    千次阅读 2018-06-28 21:28:31
    web安全 安全永远是产品的最基础需求 这里总结几种常见攻击与防范 一.XSS XSS,跨站脚本攻击,原理是恶意攻击者往 Web 页面里插入恶意可执行网页脚本代码,当用户浏览该页之时,嵌入其中 Web 里面的...
  • 企业级API网关的设计

    千次阅读 2017-07-06 14:12:49
    原创 2017-05-25 郑治国 EAWorld 转载本文需注明出处:微信公众号...三、企业级API网关需要具备的条件 四、业界常用的API网关方案 五、如何设计一个好的企业级API网关产品 六、小结 一、网关简介

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 46,109
精华内容 18,443
关键字:

webapi访问较慢