当前位置: 首页 > 编程日记 > 正文

从理论到实践 全面理解HTTP/2

前言

为了降低加载时间,相信大多数人都做过如下尝试

  • Keep-alive: TCP持久连接,增加了TCP连接的复用性,但只有当上一个请求/响应完全完成后,client才能发送下一个请求
  • Pipelining: 可同时发送多个请求,但是服务器必须严格按照请求的先后顺序返回响应,若第一个请求的响应迟迟不能返回,那后面的响应都会被阻塞,也就是所谓的队头阻塞
  • 请求合并:雪碧图,css/js内联、css/js合并等,然而请求合并又会带来缓存失效、解析变慢、阻塞渲染、木桶效应等诸多问题
  • 域名散列:绕过了同域名最多6个TCP的限制,但增加了DNS开销和TCP开销,也会大幅降低缓存的利用率
  • ……

不可否认,这些优化在一定程度上降低了网站加载时间,但对于一个web应用庞大的请求量来说,这些只是冰上一角、隔靴搔痒。

以上问题归根结底是HTTP1.1协议本身的问题,若要从根本上解决HTTP1.1的低效,只能从协议本身入手。为此Google开发了SPDY协议,主要是为了降低传输时间;基于SPDY协议,IETF和SPDY组全体成员共同开发了HTTP/2,并在2015年5月以RFC 7504正式发表。SPDY或者HTTP/2并不是一个全新的协议,它只是修改了HTTP的请求与应答在网络上的传输方式,增加了一个spdy传输层,用于处理、标记、简化和压缩HTTP请求,所以它们并不会破坏现有程序的工作,对于支持的场景,使用新特性可以获得更快的速度,对于不支持的场景,也可以实现平稳退化。

HTTP/2继承了spdy的多路复用、优先级排序等诸多优秀特性,也额外做了不少改进。其中较为显著的改进是HTTP/2使用了一份经过定制的压缩算法,以此替代了SPDY的动态流压缩算法,用于避免对协议的Oracle攻击。

多数主流浏览器已在2015年底支持了该标准(划重点)。具体支持度如下:

HTTP/2支持度

数据来源

可以看到国内有58.55%的浏览器已经完全支持HTTP/2,而全球的支持度更是高达85.66%。这么高的支持度,so,你心动了吗

why HTTP/2

二进制格式传输

我们知道HTTP/1.1的头信息肯定是文本(ASCII编码),数据体可以是文本,也可以是二进制(需要做自己做额外的转换,协议本身并不会转换)。而在HTTP/2中,新增了二进制分帧层,将数据转换成二进制,也就是说HTTP/2中所有的内容都是采用二进制传输。

使用二进制有什么好处吗?当然!效率会更高,而且最主要的是可以定义额外的帧,如果用文本实现帧传输,解析起来将会十分麻烦。HTTP/2共定义了十种帧,较为常见的有数据帧、头部帧、PING帧、SETTING帧、优先级帧和PUSH_PROMISE帧等,为将来的高级应用打好了基础。

HTTP/2

如上图,Binary Framing就是新增的二进制分帧层。

多路复用

二进制分帧层把数据转换为二进制的同时,也把数据分成了一个一个的帧。帧是HTTP/2中数据传输的最小单位;每个帧都有stream_ID字段,表示这个帧属于哪个流,接收方把stream_ID相同的所有帧组合到一起就是被传输的内容了。而流是HTTP/2中的一个逻辑上的概念,它代表着HTTP/1.1中的一个请求或者一个响应,协议规定client发给server的流的stream_ID为奇数,server发给client的流ID是偶数。需要注意的是,流只是一个逻辑概念,便于理解和记忆的,实际并不存在。

理解了帧和流的概念,完整的HTTP/2的通信就可以被形象地表示为这样:

HTTP/2帧和流通信示意图

可以发现,在一个TCP链接中,可以同时双向地发送帧,而且不同流中的帧可以交错发送,不需要等某个流发送完,才发送下一个。也就是说在一个TCP连接中,可以同时传输多个流,即可以同时传输多个HTTP请求和响应,这种同时传输不需要遵循先入先出等规定,因此也不会产生阻塞,效率极高。

在这种传输模式下,HTTP请求变得十分廉价,我们不需要再时刻顾虑网站的http请求数是否太多、TCP连接数是否太多、是否会产生阻塞等问题了。

HPACK 首部压缩

为什么需要压缩?

在 HTTP/1 中,HTTP 请求和响应都是由「状态行、请求 / 响应头部、消息主体」三部分组成。一般而言,消息主体都会经过 gzip 压缩,或者本身传输的就是压缩过后的二进制文件(例如图片、音频),但状态行和头部却没有经过任何压缩,直接以纯文本传输。

随着 Web 功能越来越复杂,每个页面产生的请求数也越来越多,根据 HTTP Archive 的统计,当前平均每个页面都会产生上百个请求。越来越多的请求导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。

为了减少冗余的头部信息带来的消耗,HTTP/2采用HPACK 算法压缩请求和响应的header。下面这张图非常直观地表达了HPACK头部压缩的原理:

HAPCK原理

图片来源: Velocity 2015 • SC 会议分享

具体规则可以描述为:

  • 通信双方共同维护了一份静态表,包含了常见的头部名称与值的组合
  • 根据先入先出的原则,维护一份可动态添加内容的动态表
  • 用基于该静态哈夫曼码表的哈夫曼编码数据

当要发送一个请求时,会先将其头部和静态表对照,对于完全匹配的键值对,可以直接使用一个数字表示,如上图中的2:method: GET,对于头部名称匹配的键值对,可以将名称使用一个数字传输,如上图中的19:path: /resource,同时告诉服务端将它添加到动态表中,以后的相同键值对就用一个数字表示了。这样,像cookie这些不经常变动的值,只用发送一次就好了。

server push

在开始HTTP/2 server push 前,我们先来看看一个HTTP/1.1的页面是如何加载的。

<!DOCTYPE html>
<html>
<head><link rel="stylesheet" href="style.css"><script src="user.js"></script>
</head>
<body><h1>hello http2</h1>
</body>
</html>
  1. 浏览器向服务器请求/user.html
  2. 服务器处理请求,把/user.html发给浏览器
  3. 浏览器解析收到的/user.html,发现还需要请求/user.jsstyle.css静态资源
  4. 分别发送两个请求,获取/user.jsstyle.css
  5. 服务器分别响应两个请求,发送资源
  6. 浏览器收到资源,渲染页面

至此,这个页面才加载完毕,可以被用户看到。可以发现在步骤3和4中,服务器一直处于空闲等待状态,而浏览器到第6步才能得到资源渲染页面,这使页面的首次加载变得缓慢。

而HTTP/2的server push允许服务器在未收到请求时就向浏览器推送资源。即服务器发送/user.html时,就可以主动把/user.jsstyle.csspush给浏览器,使资源提前达到浏览器;除了静态文件,还可以推送比较耗时的API,只是需要提前将参数和cookie等信息通过某个方式告知服务端(如和路由关联)。Apache、GO的net/http、node-spdy都实现了server push(但ngnix没有=_=),本文后面的实践部分用node-spdy写了一个极为简陋的例子,有兴趣的小伙伴可以动手尝试一下。

Server push是HTTP/2协议里面唯一一个需要开发者自己配置的功能。其他功能都是服务器和浏览器自动实现,无需开发者介入。

在HTTP1.1时代,也有提前获取资源的方法,如preload和prefetch,前者是在页面解析初期就告诉浏览器,这个资源是浏览器马上要用到的,可以立刻发送对资源的请求,当需要用到该资源时就可以直接用而不用等待请求和响应的返回了;后者是当前页面用不到但下一页面可能会用到的资源,优先级较低,只有当浏览器空闲时才会请求prefetch标记的资源。从应用层面上看,preload和server push并没有什么区别,但是server push减少浏览器请求的时间,略优于preload,在一些场景中,可以将两者结合使用。

实战

纸上谈兵终觉浅,来实践一下吧!亲手搭建自己的 HTTP/2 demo,并抓包验证。

spdy这个库实现了 HTTP/2,同时也提供了对express的支持,所以这里我选用spdy + express搭建demo。demo源码

路径说明:

- ca/  证书、秘钥等文件
- src/- img/- js/- page1.html
- server.js

HTTPS 秘钥和证书

虽然HTTP/2有加密(h2)和非加密(h2c)两种形式,但大多主流浏览器只支持h2-基于TLS/1.2或以上版本的加密连接,所以在搭建demo前,我们首先要自颁发一个证书,这样就可以在浏览器访问中使用https了,你可以自行搜索证书颁发方法,也可以按照下述步骤去生成

首先要安装open-ssl,然后执行以下命令

$ openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
....
$ openssl rsa -passin pass:x -in server.pass.key -out server.key
writing RSA key
$ rm server.pass.key$ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
....
$ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt

然后你就会得到三个文件server.crt, server.csr, server.key,将它们拷贝到ca文件夹中,稍后会用到。

搭建HTTP/2服务

express是一个Node.js框架,这里我们用它声明了路由/,返回的html文件page1.html中引用了js和图片等静态资源。

// server.js
const http2 = require('spdy')
const express = require('express')
const app = express()
const publicPath = 'src'app.use(express.static(publicPath))app.get('/', function (req, res) {res.setHeader('Content-Type', 'text/html')res.sendFile(__dirname + '/src/page1.html')
})var options = {key: fs.readFileSync('./ca/server.key'),cert: fs.readFileSync('./ca/server.crt')
}
http2.createServer(options, app).listen(8080, () => {console.log(`Server is listening on https://127.0.0.1:8080 .`)
})

用浏览器访问https://127.0.0.1:8080/,打开控制台可以看所有的请求和它们的瀑布图:

没有push的瀑布图

可以清楚地看到,当第一个请求,也就是对document的请求完全返回并解析后,浏览器才开始发起对js和图片等静态资源的的请求。前面说过,server push允许服务器主动向浏览器推送资源,那么是否可以在第一个请求未完成时,就把接下来所需的js和img推送给浏览器呢?这样不仅充分利用了HTTP/2的多路复用,还减少了服务器的空闲等待时间。

对路由的处理函数进行改造:

app.get('/', function (req, res) {
+   push('/img/yunxin1.png', res, 'image/png')
+   push('/img/yunxin2.png', res, 'image/png')
+   push('/js/log3.js', res, 'application/javascript')res.setHeader('Content-Type', 'text/html')res.sendFile(__dirname + '/src/page1.html')
})function push (reqPath, target, type) {let content = fs.readFileSync(path.join(__dirname, publicPath, reqPath))let stream = target.push(reqPath, {status: 200,method: 'GET',request: { accept: '*/*' },response: {'content-type': type}})stream.on('error', function() {})stream.end(content)
}

来看下应用了server push的瀑布图:

server push 瀑布图

很明显,被push的静态资源可以很快地被使用,而没有被push的资源,如log1.jslog2.js则需要经过较长的时间才能被使用。

浏览器控制台看到的东西毕竟很有限,我们来玩点更有意思的~

wireshark 抓包验证

wireshark是一款可以识别HTTP/2的抓包工具,它的原理是直接读取并分析网卡数据,我们用它来验证是否真正实现了HTTP/2以及其底层通信原理。

首先去官网下载安装包并安装wireshark,这一步没啥好说的。

我们知道,http/2里的请求和响应都被拆分成了帧,如果我们直接去抓取HTTP/2通信包,那抓到的只能是一帧一帧地数据,像这样:

未解密的HTTP/2抓包图

可以看到,抓到的都是TCP类型的包(红色方框);观察前三个包的内容(绿色方框),分别是SYN、[SYN, ACK]和ACK,这就我们所熟知的TCP三次握手;右下角的黄色小方框是请求当前页面后抓到的TCP包的总数,其实这个页面只有七八个请求,但抓到的包的数量却有334个,这也验证了HTTP/2的请求和响应的确是被分成了一帧一帧的。

抓HTTP1.1的包,我们可以清楚地看到都有哪些请求和响应,它们的协议、大小等,而HTTP/2的数据包却是一帧一帧地,那么怎么看HTTP/2都有哪些请求和响应呢?其实wireshark会自动帮我们重组拥有相同stream_ID的帧,重组后就可看到实际有哪些请求和响应了,但是因为我们用的是https,所有的数据都被加密了,wireshark就不知道该怎么去重组了。

有两个办法可以在wireshark中解密 HTTPS 流量:第一如果你拥有 HTTPS 网站的加密私钥,可以用加密私钥来解密这个网站的加密流量;2)某些浏览器支持将 TLS 会话中使用的对称密钥保存在外部文件中,可供 Wireshark 解密使用。

但是HTTP/2为了前向安全性,不允许使用RAS秘钥交换,所有我们无法使用第一个方法来解密HTTP/2流量。介绍第二种方法:当系统环境变量中存在SSLKEYFILELOG时,Chrome和firefox会将对称秘钥保存在该环境变量指向的文件中,然后把这个文件导入wireshark,就可以解密HTTP/2流量了,具体做法如下:

  1. 新建ssl.log文件
  2. 添加系统环境变量SSLKEYFILELOG,指向第一步创建的文件
  3. 在wireshark中打开 preferences->Protocols,找到SSL,将配置面板的 「(Pre)-Master-Secret log filename」选中第一步创建的文件

SSL配置面板图

这时用Chrome或Firefox访问任何一个https页面,ssl.log中应该就有写入的秘钥数据了。

解密完成后,我们就可以看到HTTP/2的包了

下图是在demo的主页面抓取的包,可以清楚地看到有哪些HTTP/2请求。

demo https图

HTTP/2协议中的流和可以在一个TCP连接中交错传输,只需建立一个TCP连接就可以完成和服务器的所有通信,我们来看下在demo中的HTTP/2是不是这样的:

wireshark下方还有一个面板,里面有当前包的具体信息,如大小、源IP、目的IP、端口、数据、协议等,在Transmission Control Protocol下有一个[Stream index],如下图,它是TCP连接的编号,代表当前包是从哪个TCP连接中传输的。观察demo页面请求产生的包,可以发现它们的stream index 都相同,说明这些HTTP/2请求和响应是在一个TCP连接中被传输的,这么多流的确复用了一个TCP连接。

TCP id图

除了多路复用外,我们还可以通过抓包来观察HTTP/2的头部压缩。下图是当前路由下的第一个请求,实际被传输的头部数据有253bytes,解压后的头部信息有482bytes。压缩后的大小减少了几乎一半

HTTP/2 HPACK

但这只是第一个请求,我们看看后来的请求,如第三个,实际传输的头部大小只有30bytes,而解压后的大小有441byte,压缩后的体积仅为原来的1/14!如今web应用单是一个页面就动辄几百的请求数,HPACK能节约的流量可想而知。
HTTP/2 HPACK

结语

在文章开篇,我们列举了HTTP1.x时代的困境,引入并简要说明了HTTP/2的起源;然后对比着HTTP1.x,介绍了HTTP/2的诸多优秀特性,来说明为什么选择HTTP/2;在文章的最后一部分,介绍了如何一步一步搭建一个HTTP/2实例,并抓包观察,验证了HTTP/2的多路复用,头部压缩等特性。最后,您是否也被这些高效特性吸引了呢?动手试试吧~

参考:

  • HTTP/2 维基百科
  • w3c-preload
  • HTTP/2 Server Push with Node.js
  • 使用 Wireshark 调试 HTTP/2 流量
  • Optimize Your App with HTTP/2 Server Push Using Node and Express
  • web性能优化与HTTP/2

相关文章:

开机不进去桌面执行gui_电脑系统崩溃进不去,简单几步设置U盘,快速找回桌面重要文件...

hello~我可爱的粉丝们&#xff01;今天粮小白分享一个电脑系统崩溃无法开机&#xff0c;怎么把电脑数据保存备份的小技能&#xff0c;就来就点个关注吧。每天更新生活必备技能哦。电脑系统崩溃进不去&#xff0c;简单几步设置U盘&#xff0c;快速找回桌面重要文件近些年来&…

java遍历给定目录,树形结构输出所有文件,包括子目录中的文件

http://www.cnblogs.com/jenson138/p/4691418.html import java.io.File;public class ReadDirectory {// 文件所在的层数private int fileLevel;/*** 生成输出格式* param name 输出的文件名或目录名* param level 输出的文件名或者目录名所在的层次* return 输出的字符串*/pu…

数据千万条,备份第一条,数据找不回,老板两行泪

2019独角兽企业重金招聘Python工程师标准>>> 开工第一天&#xff0c;请带着你的回忆看下文&#xff0c;想想你这些年删过的库&#xff0c;被删过的库。。。 数据库备份是个老生常谈的话题&#xff0c;看似很简单&#xff0c;但在实际操作过程中&#xff0c;运维人员…

【转】Visual Studio 2010 架构图之用例图(UML Use Case Diagram)

这篇文章是来自园子的&#xff0c;我只是把自己觉得重要的地方&#xff0c;做了更鲜明的标记和解释。 使用Visual Studio 2010可以在项目中添加架构图&#xff0c;项目可以是ASP.NET、Windows Forms、...&#xff0c;也可以创建单独的Modeling Projects。 可以通过下图方式在项…

spi通讯不需要地线吗_抖音真的需要养号吗?不需要!千万别被割了韭菜

“抖音新账号一定要先养号&#xff0c;再发布作品。”这句话&#xff0c;你是不是听很多人跟你讲过&#xff1f;包括我也曾在之前的推文里建议大家先养号&#xff0c;并且说的有理有据。&#xff08;要打脸了&#xff09;但是&#xff0c;这是一句谣传&#xff01;暂且不说谣言…

Linux学习之setjmp和longjmp函数

nsetjmp和longjmp函数实现函数之间的跳转(需包含头文件"setjmp.h")&#xff1a;函数原型&#xff1a;int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val); setjmp函数用于设置跳转的目的位置&#xff0c;longjmp函数进行跳转。env&#xff1a;保留了需要返…

Ubuntu 和 Redhat / Fedora 服务管理命令对比表(附Fedora16新的服务管理工具systemctl )...

以 apache/httpd 服务作为例子 任务 Red Hat / Fedora Ubuntu Ubuntu (with sysv-rc-conf or sysvconfig) 立即启动/停止某服务 service httpd start invoke-rc.d apache start service apache start 启动时自动加载 chkconfig httpd on update-rc.d apache defaults…

酷派、华为不能打印log解决办法

我发现我的手机无法再eclipse里面输出自己打的log.i等log&#xff0c;一开始还以为是我手机root或刷过机的问题。 最终非常偶然找到了解决的方法&#xff1a; 酷派大神F1&#xff0c;F2在出厂时将log的级别做了限制。 解除限制的方法是&#xff1a;拨号盘输入*20121220# ->…

80070583类不存在_Java自学-接口与继承 内部类

&#xfeff;Java 内部类内部类分为四种&#xff1a; 非静态内部类 静态内部类 匿名类 本地类步骤 1 : 非静态内部类非静态内部类 BattleScore “战斗成绩” 非静态内部类可以直接在一个类里面定义比如&#xff1a; 战斗成绩只有在一个英雄对象存在的时候才有意义 所以实例化Ba…

java 初识对象和对象引用的关系

在接触java之前就知道了java的对象引用&#xff0c;但概念一直很模糊&#xff0c;看了《Head First in Java》后觉得书中的例子很有趣&#xff0c;便分享给大家。 首先要搞清对象引用是什么&#xff1f; 我们都知道变量可以存放数据&#xff0c;就像杯子一样&#xff0c;不同大…

IOS UI 代码创建UIButton,UITextField,UILabel

//add a Label UILabel *label[[UILabelalloc]initWithFrame:CGRectMake(100,20, 150, 40)]; [label setText:"i am a label "]; label.font[UIFontsystemFontOfSize:20]; label.textAlignmentNSTextAlignmentLeft; //文字内容自适应标签宽度 label.adjustsFontSizeT…

Hash join 和nested loop

Hash join 和nested loop 总所周知&#xff0c;Oracle数据库常用的两种优化器&#xff1a;RBO&#xff08;rule-based-optimizer&#xff09;和CBO(cost-based-optimizer)。目前更多地采用CBO(cost-based-optimizer)基于开销的优化器。在CBO方式下&#xff0c;Oracle会根据表及…

minus oracle 顺序_oracle minus的用法来一波

Oracle Minus关键字SQL中有一个MINUS关键字&#xff0c;它运用在两个SQL语句上&#xff0c;它先找出第一条SQL语句所产生的结果&#xff0c;然后看这些结果有没有在第二个SQL语句的结果中。如果有的话&#xff0c;那这一笔记录就被去除&#xff0c;而不会在最后的结果中出现。如…

我现在的vimrc配置文件

我现在的vimrc配置文件 runtime! debian.vim "设置编码 set encodingutf-8 set fencsutf-8,ucs-bom,shift-jis,gb18030,gbk,gb2312,cp936 set fileencodingsutf-8,ucs-bom,chinese"语言设置 set langmenuzh_CN.UTF-8" command WQ wq command Wq wq command W w…

UVA 10269 Adventure of Super Mario

UVA_10269 由于马里奥的飞行距离有限&#xff0c;因此为了方便处理&#xff0c;我们首先用floyd预处理出马里奥可以飞行的两点间的最短路&#xff0c;然后再将图分成K1层用SPFA求最短路即可。 #include<stdio.h>#include<string.h>#define MAXD 130#define MAXN 20…

“5G杀手级应用”Cloud VR 华为如何打响5G第一枪

雷锋网消息&#xff0c;近日华为在上海召开华为云 5G Cloud VR服务发布会暨5G Cloud VR开发者沙龙&#xff0c;Cloud VR有何潜力成为5G第一批杀手级应用&#xff0c;华为又在其中扮演怎样的角色。Cloud VR和5G更配生产决定消费&#xff0c;消费反作用于生产&#xff0c;对于5G也…

昆仑通态通用版找不到驱动_2021深圳新安西门子伺服驱动电机回收合作共赢

2021深圳新安西门子伺服驱动电机回收合作共赢 一个企业,应尽量做到PLC的机型统主要考虑到以下三方面问题&#xff1a;机型统其模块可互为备用,便于备品备件的采购和管理。机型统其功能和使用方法类似,有利于技术力量的培训和技术水平的提高。机型统其外部设备通用,资源可共享,易…

熟人Dubbo 系列1-Dubbo什么

Dubbo阿里巴巴内部SOA治理方案和服务的核心框架。每天2000 个服务提供3,000,000,000 次訪问量支持&#xff0c;并被广泛应用于阿里巴巴集团的各成员网站。Dubbo自2011年开源后&#xff0c;已被很多非阿里系公司使用。Dubbo[]是一个分布式服务框架&#xff0c;致力于提供高性能和…

CentOS源码安装GitLab汉化版第3版

软件版本&#xff1a; 软件版本CentOS7.5GraphicsMagick1.3.31Git2.21.0Ruby2.5.3Go1.12Node.js10.15.2PostgreSQL11.2Redis5.0.3GitLab11.8.0 汉化版Nginx1.14.21. 安装依赖 yum -y install libicu-devel patch gcc-c readline-devel zlib-devel libffi-devel openssl-devel m…

用JSP+JDBC开发Web程序

以前一直想找个纯粹的JSPJDBC开发Web程序的架构&#xff0c;一直没有找到合适的&#xff0c;后来自己写了一个简单实现&#xff0c;并实施了几个项目。 此开发架构的特点是&#xff1a; 1.架构技术简单&#xff0c;只包含JSP和JDBC&#xff0c;不需要学习即可快速开发Web应用&a…

catia怎么创建约束快捷键_答疑 | CATIA结构树无法显示怎么办?

问题有小伙伴反馈&#xff0c;设计过程中&#xff0c;CATIA的结构树不见了……怎么办&#xff1f;问题听起来很简单&#xff0c;但总能难倒一些新手。原因与解决方案&#xff1a;下面针对产生该问题的不同的原因&#xff0c;提出不同的解决方案。第一种情况原因&#xff1a;结构…

【UVA】11992 - Fast Matrix Operations(段树模板)

主体段树&#xff0c;要注意&#xff0c;因为有set和add操作&#xff0c;当慵懒的标志下推。递归优先set&#xff0c;后复发add&#xff0c;每次运行set行动add马克清0 WA了好几次是由于计算那一段的时候出问题了&#xff0c;可笑的是我对着模板找了一个多小时的错。 #include&…

记录一次MySQL两千万数据的大表优化解决过程,提供三种解决方案

问题概述使用阿里云rds for MySQL数据库&#xff08;就是MySQL5.6版本&#xff09;&#xff0c;有个用户上网记录表6个月的数据量近2000万&#xff0c;保留最近一年的数据量达到4000万&#xff0c;查询速度极慢&#xff0c;日常卡死。严重影响业务。 问题前提&#xff1a;老系统…

SQL Server 2008 下载地址(微软官方网站)

哪里有sqlserver2008下载&#xff1f;2011-9-24 23:58提问者&#xff1a;ooseestars | 浏览次数&#xff1a;3252次2011-9-26 11:38最佳答案SQL Server 2008 下载地址(微软官方网站) 中文版(3.28GB)&#xff1a;http://sqlserver.dlservice.microsoft.com/dl/download/B/8/0/B8…

java实现最长连续子序列_最长公共子序列 ||

问题&#xff1a;在 前一篇文章 最长公共子序列 | 的基础上要求将所有的最长公共子序列打印出来&#xff0c;因为最长公共子序列可能不只一种。难点&#xff1a;输出一个最长公共子序列并不难&#xff0c;难点在于输出所有的最长公共子序列&#xff0c;我们需要在动态规划表上进…

替换元素和非替换元素的学习

替换元素和非替换元素的学习 (元素)[妙瞳] 引言 元素是文档结构的基础&#xff0c;在CSS中&#xff0c;每个元素生成了一个包含了元素内容的框&#xff08;box,也翻译为“盒子”&#xff09;。但是不同的元素显示的方式会有所不同&#xff0c;例如div和span不同&#xff0c;而s…

第十六天-企业应用架构模式-离线并发模式

1.乐观离线锁 &#xff08;Optimistic Offline Lock&#xff09; 运行机制 使用时机 例&#xff1a;领域层与数据层数据映射器 2.悲观离线锁 &#xff08;Pessimistic Offline Lock&#xff09; 运行机制 使用时机 例&#xff1a;简单锁管理对象 3.粗粒度锁 &#xff08;Coarse…

hdu1518 bjfuoj1042 zoj1909 poj2362 经典的搜索加剪枝

bjfuoj的测试数据最水&#xff0c;用很简单的方法一下就过了&#xff0c;又调了好长时间&#xff0c;才过掉其它OJ上的这道题目~ /* * hdu1518/win.cpp * Created on: 2011-11-8 * Author : ben*/#include <cstdio>#include <cstdlib>#include <cstring>#…

投影参数_智能投影仪参数如何去看,其实很简单

我又来给大家安利投影仪了&#xff0c;毕竟用过的都知道有多刺激&#xff0c;但是估计很多人看到参数就头疼了吧&#xff1f;所以话不多说&#xff0c;直接上科普啦流明亮度流明怎么算的&#xff0c;家人们就不用详细了解了&#xff0c;只用记住&#xff0c;流明越高画面就越亮…

zoj 3554 A Miser Boss

题意&#xff1a;有a、b、c三个人同时工作&#xff0c;三个人做不同的任务需要不同的时间&#xff0c;但最后要求三个人做事情的总时间相同&#xff0c;输出做完所有任务需要的最少时间&#xff0c;如果无法达到三个人总工作时间相同&#xff0c;则输出“No” 当时一股脑筋觉着…