什么是 Docker?
Docker 是一种开源项目,用于将应用程序自动部署为可在云或本地运行的便携式独立容器。 Docker 也是一家公司,它与云、Linux 和 Windows 供应商(包括 Microsoft)协作,致力于推广和发展这项技术。 图 2-2。 Docker 在混合云的所有层部署容器 Docker 映像容器可以在 Linux 和 Windows 上本机运行。 但是,Windows 映像仅能在 Windows 主机上运行,Linux 映像可以在 Linux 主机和 Windows 主机上运行(到目前为止,使用 Hyper-V Linux VM),其中主机是指服务器或 VM。 开发人员可以在 Windows、Linux 或 macOS 上使用开发环境。 在开发计算机上,开发人员运行部署了 Docker 映像(包括应用及其依赖项)的 Docker 主机。 在 Linux 或 Mac 上进行开发的开发人员使用基于 Linux 的 Docker 主机,并且他们可以仅为 Linux 容器创建映像。 (在 Mac 上进行开发的开发人员可以从 macOS 中编辑代码或运行 Docker CLI,但截至撰写本文时,容器不在 macOS 上直接运行。)在 Windows 上进行开发的开发人员可以为 Linux 或 Windows 容器创建映像。 为了在开发环境中承载容器,并提供其他开发人员工具,Docker 为 Windows 或 macOS 提供了 Docker 社区版 (CE)。 这些产品安装了承载容器所需的 VM(Docker 主机)。 Docker 还提供 Docker 企业版 (EE),该版本专为企业开发而设计,供生成、交付和在生产中运行大型业务关键型应用程序的 IT 团队使用。 若要运行 Windows 容器,有两种类型的运行时可供使用: Windows Server 容器通过进程和命名空间隔离技术提供应用程序隔离。 Windows Server 容器与容器主机和主机上运行的所有容器共享内核。 Hyper-V 容器通过在高度优化的虚拟机中运行各容器来扩展 Windows Server 容器提供的隔离。 在此配置中,容器主机的内核不与 Hyper-V […]
View Details容器和 Docker 简介
容器化是软件开发的一种方法,通过该方法可将应用程序或服务、其依赖项及其配置(抽象化为部署清单文件)一起打包为容器映像。 容器化应用程序可以作为一个单元进行测试,并可以作为容器映像实例部署到主机操作系统 (OS)。 就像船只、火车或卡车运输集装箱而不论其内部的货物一样,软件容器充当软件部署的标准单元,其中可以包含不同的代码和依赖项。 按照这种方式容器化软件,开发人员和 IT 专业人员只需进行极少修改或不修改,即可将其部署到不同的环境。 容器还会在共享 OS 上将应用程序彼此隔离开。 容器化应用程序在容器主机上运行,而容器主机在 OS(Linux 或 Windows)上运行。因此,容器的占用比虚拟机 (VM) 映像小得多。 每个容器可以运行整个 Web 应用或服务,如图 2-1 所示。 在此示例中,Docker 主机是容器主机,而 App1、App2、Svc 1 和 Svc 2 是容器化应用程序或服务。 图 2-1. 在一个容器主机上运行多个容器 容器化的另一个优势在于可伸缩性。 通过为短期任务创建新容器,可以快速扩大。 从应用程序的角度来看,实例化映像(创建容器)类似于实例化 服务或 Web 应用等进程。 但出于可靠性考虑,在多个主机服务器上运行同一映像的多个实例时,通常要使每个容器(映像实例)在不同容错域中的不同主机服务器或 VM 中运行。 总而言之,容器在整个应用程序生命周期工作流中提供以下优点:隔离性、可移植性、灵活性、可伸缩性和可控性。 最重要的优点是可在开发和运营之间提供隔离。 from:https://docs.microsoft.com/zh-cn/dotnet/standard/microservices-architecture/container-docker-introduction/index
View Details使用nginx-http-concat优化网站响应
前言: 我们在访问淘宝的时候,会看到代码中的js和css文件是通过一次请求或得的,我们知道浏览器一次请求只能并发访问数个资源,这样的处理错输在网络传输层面可以大大节省时间,这里使用的技术就是把css、js等静态资源合并为一个资源。淘宝使用的tengine是基于nginx的web服务器,从11年底开源。所使用的是mod_concat模块,合并多个文件在一个响应报文中。 http1.1下浏览器的并发访问资源数 IE6 2 IE7 2 IE8 6 Firefox2 2 Firefox3 6 Safari 3,4 […]
View Details静态资源缓存控制编译工具
前不久在 知乎 上回答了一个问题:大公司里怎样开发和部署前端代码?。其中讲到了大公司在前端静态资源部署上的一些要求: 配置超长时间的本地缓存 —— 节省带宽,提高性能 采用内容摘要作为缓存更新依据 —— 精确的缓存控制 静态资源CDN部署 —— 优化网络请求 更资源发布路径实现非覆盖式发布 —— 平滑升级 其中比较复杂的部分就是 以文件的摘要信息为依据,控制缓存更新与非覆盖式发布 这个细节。因此基于 fis 包装了一个简单的命令行工具,并设立此项目,用于演示这部分功能。 这个工具基于 fis 的小工具是完全可以用作工程中的,有任何问题可以在 这里 留言。 请跟着下面的步骤来使用这个命令行小工具: 第一步:安装工具 这个命令行小工具依赖 nodejs 环境,因此,请先确保本地安装了它。 使用 nodejs 随带的 npm 包管理工具进行安装:
1 |
npm install -g rsd |
第二步:创建项目 在 命令行 下clone本仓库,或者自己创建一个新的项目,并进入:
1 2 |
mkdir rsd-project <span class="pl-c"><span class="pl-c">#</span> 项目名任意</span> <span class="pl-c1">cd</span> rsd-project |
在项目根目录下创建一个空的 fis-conf.js 文件,这是工具配置,什么都不用写,空着就行。 然后开始在项目目录下,随意创建或添加 页面、脚本、样式、图片、字体、音频、视频等等前端资源文件,正常写前端代码吧! 第三步:发布代码 在项目根目录下执行:
1 |
rsd release --md5 --dest ../output |
然后去到 ../output 目录下去查看一下产出结果吧,所有静态资源都以md5摘要形式发布了出来,所有资源链接,我说 所有链接,包括html中的图片、样式、脚本以及js中的资源地址、css中的资源地址全部都加上了md5戳。 上述命令中,--md5 就是表示要给所有资源定位标记加上摘要信息的意思,不加这个参数就没有摘要信息处理。--dest ../output 表示把代码发布到当前目录上一级的output目录中。整个这条命令还可以简写成:
1 |
rsd release -m -d ../output |
或者进一步连写成:
1 |
rsd release -md ../output |
在本地服务器中浏览发布代码 你本地安装了诸如 Apache、Nginx、Lighttpd、IIS等服务器么?如果安装了,假设你的服务器 根目录 在 d:\wwwroot,你可以利用rsd工具的release命令,把代码发布过去,比如:
1 |
rsd release -md d:<span class="pl-cce">\w</span>wwroot |
这样就把代码发布到了本地服务器根目录下,然后就可以在浏览器中查看效果了! 如果你本地没有安装任何服务器,你可以使用rsd内置的调试服务器,执行命令:
1 |
rsd server start |
接下来我们同样要把代码发布到这个内置服务器中,release命令如果省略 --dest <path>参数,就表示把代码发布到内置服务器的根目录下:
1 |
rsd release -m |
在浏览器中访问: http://localhost:8080 即可 一些小技巧 rsd集成了对很多前端编程语言的支持,包括: 类CSS语言:less, sass, scss, stylus 类JS语言:coffee-script 类HTML语言:markdown, jade 前端模板:handlebars-v1.3.0, EJS 内置了压缩器,在release的时候追加 -o 或者 --optimize 参数即可开启,压缩器包括: clean-css: 压缩所有类CSS语言代码 uglify-js: 压缩所有类JS语言代码 html-minifier: 压缩所有类HTML语言代码 还可以给资源加CDN域名,在release的时候追加 -D 或者 --domains 参数即可,域名配置写在fis-conf.js里:
1 2 |
<span class="pl-c"><span class="pl-c">//</span> fis-conf.js</span> <span class="pl-smi">fis</span>.<span class="pl-smi">config</span>.<span class="pl-c1">set</span>(<span class="pl-s"><span class="pl-pds">'</span>roadmap.domain<span class="pl-pds">'</span></span>, [ <span class="pl-s"><span class="pl-pds">'</span>http://localhost:8080<span class="pl-pds">'</span></span> ]); |
所有常规代码中的资源定位接口都会经过工具处理,包括: 类CSS文件中: 背景图url font-face字体url ie特有的滤镜属性中的src 类JS文件中: 提供了一个叫 __uri('path/to/file') 的编译函数用于定位资源 类HTML文件中: link标签的href属性 script, img, video, audio, object 等标签的src属性 script标签中js代码里的资源定位标记 style标签中css代码里的资源定位标记 所有资源文件可以任意相互引用,工具会处理资源定位标记,使之服从知乎回答中提到的优化策略。 还提供了资源内嵌的编译接口,用于把一个资源的内容以文本、字符串或者base64的形式嵌入到 任意 一个文本文件中。 为了不用每次保存代码就执行一下release命令,工具中提供了文件监听和浏览器自动刷新功能,只要在release的时候在追加上 -w 和 -L 两个参数即可(注意L的大小写),比如:
1 |
rsd release -omwL <span class="pl-c"><span class="pl-c">#</span>压缩、加md5戳、文件监听、浏览器自动刷新</span> |
关于这个小工具 它的原码在 这里。是的,就这么一点点代码,花了大概半小时写完的,因为一切都在 fis 中集成好了,我只是追加几个语言编译插件而已。 from:https://github.com/fouber/static-resource-digest-project
View Details变态的静态资源缓存与更新
这是一个非常有趣的 非主流前端领域,这个领域要探索的是如何用工程手段解决前端开发和部署优化的综合问题,入行到现在一直在学习和实践中。 在我的印象中,facebook是这个领域的鼻祖,有兴趣、有梯子的同学可以去看看facebook的页面源代码,体会一下什么叫工程化。 接下来,我想从原理展开讲述,多图,较长,希望能有耐心看完。 让我们返璞归真,从原始的前端开发讲起。上图是一个“可爱”的index.html页面和它的样式文件a.css,用文本编辑器写代码,无需编译,本地预览,确认OK,丢到服务器,等待用户访问。前端就是这么简单,好好玩啊,门槛好低啊,分分钟学会有木有! 然后我们访问页面,看到效果,再查看一下网络请求,200!不错,太™完美了!那么,研发完成。。。。了么? 等等,这还没完呢!对于大公司来说,那些变态的访问量和性能指标,将会让前端一点也不“好玩”。 看看那个a.css的请求吧,如果每次用户访问页面都要加载,是不是很影响性能,很浪费带宽啊,我们希望最好这样: 利用304,让浏览器使用本地缓存。但,这样也就够了吗?不成!304叫协商缓存,这玩意还是要和服务器通信一次,我们的优化级别是变态级,所以必须彻底灭掉这个请求,变成这样: 强制浏览器使用本地缓存(cache-control/expires),不要和服务器通信。好了,请求方面的优化已经达到变态级别,那问题来了:你都不让浏览器发资源请求了,这缓存咋更新? 很好,相信有人想到了办法:通过更新页面中引用的资源路径,让浏览器主动放弃缓存,加载新资源。好像这样: 下次上线,把链接地址改成新的版本,就更新资源了不是。OK,问题解决了么?!当然没有!大公司的变态又来了,思考这种情况: 页面引用了3个css,而某次上线只改了其中的a.css,如果所有链接都更新版本,就会导致b.css,c.css的缓存也失效,那岂不是又有浪费了?! 重新开启变态模式,我们不难发现,要解决这种问题,必须让url的修改与文件内容关联,也就是说,只有文件内容变化,才会导致相应url的变更,从而实现文件级别的精确缓存控制。 什么东西与文件内容相关呢?我们会很自然的联想到利用 数据摘要要算法 对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种可以精确到单个文件粒度的缓存控制依据了。好了,我们把url改成带摘要信息的: 这回再有文件修改,就只更新那个文件对应的url了,想到这里貌似很完美了。你觉得这就够了么?大公司告诉你:图样图森破! 唉~~~~,让我喘口气 现代互联网企业,为了进一步提升网站性能,会把静态资源和动态网页分集群部署,静态资源会被部署到CDN节点上,网页中引用的资源也会变成对应的部署路径: 好了,当我要更新静态资源的时候,同时也会更新html中的引用吧,就好像这样: 这次发布,同时改了页面结构和样式,也更新了静态资源对应的url地址,现在要发布代码上线,亲爱的前端研发同学,你来告诉我,咱们是先上线页面,还是先上线静态资源? 1.先部署页面,再部署资源:在二者部署的时间间隔内,如果有用户访问页面,就会在新的页面结构中加载旧的资源,并且把这个旧版本的资源当做新版本缓存起来,其结果就是:用户访问到了一个样式错乱的页面,除非手动刷新,否则在资源缓存过期之前,页面会一直执行错误。 2.先部署资源,再部署页面:在部署时间间隔之内,有旧版本资源本地缓存的用户访问网站,由于请求的页面是旧版本的,资源引用没有改变,浏览器将直接使用本地缓存,这种情况下页面展现正常;但没有本地缓存或者缓存过期的用户访问网站,就会出现旧版本页面加载新版本资源的情况,导致页面执行错误,但当页面完成部署,这部分用户再次访问页面又会恢复正常了。 好的,上面一坨分析想说的就是:先部署谁都不成!都会导致部署过程中发生页面错乱的问题。所以,访问量不大的项目,可以让研发同学苦逼一把,等到半夜偷偷上线,先上静态资源,再部署页面,看起来问题少一些。 但是,大公司超变态,没有这样的“绝对低峰期”,只有“相对低峰期”。So,为了稳定的服务,还得继续追求极致啊! 这个奇葩问题,起源于资源的覆盖式发布,用 待发布资源覆盖 已发布资源,就有这种问题。解决它也好办,就是实现非覆盖式发布。 看上图,用文件的摘要信息来对资源文件进行重命名,把摘要信息放到资源文件发布路径中,这样,内容有修改的资源就变成了一个新的文件发布到线上,不会覆盖已有的资源文件。上线过程中,先全量部署静态资源,再灰度部署页面,整个问题就比较完美的解决了。 所以,大公司的静态资源优化方案,基本上要实现这么几个东西: 1.配置超长时间的本地缓存 —— 节省带宽,提高性能 2.采用内容摘要作为缓存更新依据 —— 精确的缓存控制 3.静态资源CDN部署 —— 优化网络请求 4.更资源发布路径实现非覆盖式发布 —— 平滑升级 全套做下来,就是相对比较完整的静态资源缓存控制方案了,而且,还要注意的是,静态资源的缓存控制要求在 前端所有静态资源加载的位置都要做这样的处理 。是的,所有!什么js、css自不必说,还要包括js、css文件中引用的资源路径,由于涉及到摘要信息,引用资源的摘要信息也会引起引用文件本身的内容改变,从而形成级联的摘要变化,大概示意图就是: 好了,目前我们快速的学习了一下前端工程中关于静态资源缓存要面临的优化和部署问题,新的问题又来了:这™让工程师怎么写码啊!!! 要解释优化与工程的结合处理思路,又会扯出一堆有关模块化开发、资源加载、请求合并、前端框架等等的工程问题,以上只是开了个头,解决方案才是精髓,但要说的太多太多,有空再慢慢展开吧。 总之,前端性能优化绝逼是一个工程问题! 以上不是我YY的,可以观察 百度 或者 facebook 的页面以及静态资源源代码,查看它们的资源引用路径处理,以及网络请中静态资源的缓存控制部分。再次赞叹facebook的前端工程建设水平,跪舔了。 建议前端工程师多多关注前端工程领域,也许有人会觉得自己的产品很小,不用这么变态,但很有可能说不定某天你就需要做出这样的改变了。而且,如果我们能把事情做得更极致,为什么不去做呢? 另外,也不要觉得这些是运维或者后端工程师要解决的问题。如果由其他角色来解决,大家总是把自己不关心的问题丢给别人,那么前端工程师的开发过程将受到极大的限制,这种情况甚至在某些大公司都不少见! 妈妈,我再也不玩前端了。。。。5555 业界实践 Assets Pipeline Rails中的Assets Pipeline完成了以上所说的优化细节,对整个静态资源的管理上的设计思考也是如此,了解rails的人也可以把此答案当做是对rails中assets pipeline设计原理的分析。 rails通过把静态资源变成erb模板文件,然后加入<%= asset_path 'image.png' %>,上线前预编译完成处理,fis的实现思路跟这个几乎完全一样,但我们当初确实不知道有rails的这套方案存在。 相关资料: 英文版:http://guides.rubyonrails.org/asset_pipeline.html 中文版:http://guides.ruby-china.org/asset_pipeline.html FIS的解决方案 用 F.I.S 包装了一个小工具,完整实现整个回答所说的最佳部署方案,并提供了源码对照,可以感受一下项目源码和部署代码的对照。 源码项目:https://github.com/fouber/static-resource-digest-project 部署项目:https://github.com/fouber/static-resource-digest-project-release 部署项目可以理解为线上发布后的结果,可以在部署项目里查看所有资源引用的md5化处理。 这个示例也可以用于和assets pipeline做比较。fis没有assets的目录规范约束,而且可以以独立工具的方式组合各种前端开发语言(coffee、less、sass/scss、stylus、markdown、jade、ejs、handlebars等等你能想到的),并与其他后端开发语言结合。 assets pipeline的设计思想值得独立成工具用于前端工程,fis就当做这样的一个选择吧。 from:https://www.cnblogs.com/minigrasshopper/p/7694053.html
View Details前端工程精粹(一):静态资源版本更新与缓存
每个参与过开发企业级web应用的前端工程师或许都曾思考过前端性能优化方面的问题。我们有雅虎14条性能优化原则,还有两本很经典的性能优化指导书:《高性能网站建设指南》、《高性能网站建设进阶指南》。经验丰富的工程师对于前端性能优化方法耳濡目染,基本都能一一列举出来。这些性能优化原则大概是在7年前提出的,对于web性能优化至今都有非常重要的指导意义。 然而,对于构建大型web应用的团队来说,要坚持贯彻这些优化原则并不是一件十分容易的事。因为优化原则中很多要求是与工程管理相违背的,比如“把css放在头部”和“把js放在尾部”这两条原则,我们不能让团队的工程师在写样式和脚本引用的时候都去修改一个相同的页面文件。这样做会严重影响团队成员间并行开发的效率,尤其是在团队有版本管理的情况下,每天要花大量的时间进行代码修改合并,这项成本是难以接受的。因此在前端工程界,总会看到周期性的性能优化工作,辛勤的前端工程师们每到月圆之夜就会倾巢出动根据优化原则做一次性能优化。 本文从一个全新的视角来思考web性能优化与前端工程之间的关系,通过解读百度前端集成解决方案小组(F.I.S)在打造高性能前端架构并统一百度40多条前端产品线的过程中所经历的技术尝试,揭示前端性能优化在前端架构及开发工具设计层面的实现思路。 性能优化原则及分类 笔者先假设本文的读者是有前端开发经验的工程师,并对企业级web应用开发及性能优化有一定的思考,因此我不会重复介绍雅虎14条性能优化原则。如果您没有这些前续知识,请移步这里来学习。 首先,我们把雅虎14条优化原则,《高性能网站建设指南》以及《高性能网站建设进阶指南》中提到的优化点做一次梳理,按照优化方向分类,可以得到这样一张表格: 优化方向 优化手段 请求数量 合并脚本和样式表,CSS Sprites,拆分初始化负载,划分主域 请求带宽 开启GZip,精简JavaScript,移除重复脚本,图像优化 缓存利用 使用CDN,使用外部JavaScript和CSS,添加Expires头,减少DNS查找,配置ETag,使AjaX可缓存 页面结构 将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出 代码校验 避免CSS表达式,避免重定向 表格1 性能优化原则分类 目前大多数前端团队可以利用yui compressor或者google closure compiler等压缩工具很容易做到“精简Javascript”这条原则;同样的,也可以使用图片压缩工具对图像进行压缩,实现“图像优化”原则。这两条原则是对单个资源的处理,因此不会引起任何工程方面的问题。很多团队也通过引入代码校验流程来确保实现“避免css表达式”和“避免重定向”原则。目前绝大多数互联网公司也已经开启了服务端的Gzip压缩,并使用CDN实现静态资源的缓存和快速访问;一些技术实力雄厚的前端团队甚至研发出了自动CSS Sprites工具,解决了CSS Sprites在工程维护方面的难题。使用“查找-替换”思路,我们似乎也可以很好的实现“划分主域”原则。 我们把以上这些已经成熟应用到实际生产中的优化手段去除掉,留下那些还没有很好实现的优化原则。再来回顾一下之前的性能优化分类: 优化方向 优化手段 请求数量 合并脚本和样式表,拆分初始化负载 请求带宽 移除重复脚本 缓存利用 添加Expires头,配置ETag,使Ajax可缓存 页面结构 将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出 表格2 较难实现的优化原则 现在有很多顶尖的前端团队可以将上述还剩下的优化原则也都一一解决,但业界大多数团队都还没能很好的解决这些问题。因此,本文将就这些原则的解决方案做进一步的分析与讲解,从而为那些还没有进入前端工业化开发的团队提供一些基础技术建设意见,也借此机会与业界顶尖的前端团队在工业化工程化方向上交流一下彼此的心得。 静态资源版本更新与缓存 如表格2所示,“缓存利用”分类中保留了“添加Expires头”和“配置ETag”两项。或许有些人会质疑,明明这两项只要配置了服务器的相关选项就可以实现,为什么说它们难以解决呢?确实,开启这两项很容易,但开启了缓存后,我们的项目就开始面临另一个挑战:如何更新这些缓存。 相信大多数团队也找到了类似的答案,它和《高性能网站建设指南》关于“添加Expires头”所说的原则一样——修订文件名。即: 最有效的解决方案是修改其所有链接,这样,全新的请求将从原始服务器下载最新的内容。 思路没错,但要怎么改变链接呢?变成什么样的链接才能有效更新缓存,又能最大限度避免那些没有修改过的文件缓存不失效呢? 先来看看现在一般前端团队的做法: 或者 大家会采用添加query的形式修改链接。这样做是比较直观的解决方案,但在访问量较大的网站,这么做可能将面临一些新的问题。 通常一个大型的web应用几乎每天都会有迭代和更新,发布新版本也就是发布新的静态资源和页面的过程。以上述代码为例,假设现在线上运行着index.html文件,并且使用了线上的a.js资源。index.html的内容为: 这次我们更新了页面中的一些内容,得到一个index.html文件,并开发了新的与之匹配的a.js资源来完成页面交互,新的index.html文件的内容因此而变成了: 好了,现在要开始将两份新的文件发布到线上去。可以看到,index.html和a.js的资源实际上是要覆盖线上的同名文件的。不管怎样,在发布的过程中,index.html和a.js总有一个先后的顺序,从而中间出现一段或大或小的时间间隔。对于一个大型互联网应用来说即使在一个很小的时间间隔内,都有可能出现新用户访问。在这个时间间隔中,访问了网站的用户会发生什么情况呢? 如果先覆盖index.html,后覆盖a.js,用户在这个时间间隙访问,会得到新的index.html配合旧的a.js的情况,从而出现错误的页面。 如果先覆盖a.js,后覆盖index.html,用户在这个间隙访问,会得到旧的index.html配合新的a.js的情况,从而也出现了错误的页面。 这就是为什么大型web应用在版本上线的过程中经常会较集中的出现前端报错日志的原因,也是一些互联网公司选择加班到半夜等待访问低峰期再上线的原因之一。此外,由于静态资源文件版本更新是“覆盖式”的,而页面需要通过修改query来更新,对于使用CDN缓存的web产品来说,还可能面临CDN缓存攻击的问题。我们再来观察一下前面说的版本更新手段: 我们不难预测,a.js的下一个版本是“1.0.1”,那么就可以刻意构造一串这样的请求“a.js?v=1.0.1”、“a.js?v=1.0.2”、……让CDN将当前的资源缓存为“未来的版本”。这样当这个页面所用的资源有更新时,即使更改了链接地址,也会因为CDN的原因返回给用户旧版本的静态资源,从而造成页面错误。即便不是刻意制造的攻击,在上线间隙出现访问也可能导致区域性的CDN缓存错误。 此外,当版本有更新时,修改所有引用链接也是一件与工程管理相悖的事,至少我们需要一个可以“查找-替换”的工具来自动化的解决版本号修改的问题。 对付这个问题,目前来说最优方案就是基于文件内容的hash版本冗余机制了。也就是说,我们希望工程师源码是这么写的: 但是线上代码是这样的: 其中”_82244e91”这串字符是根据a.js的文件内容进行hash运算得到的,只有文件内容发生变化了才会有更改。由于版本序列是与文件名写在一起的,而不是同名文件覆盖,因此不会出现上述说的那些问题。同时,这么做还有其他的好处: 线上的a.js不是同名文件覆盖,而是文件名+hash的冗余,所以可以先上线静态资源,再上线html页面,不存在间隙问题; 遇到问题回滚版本的时候,无需回滚a.js,只须回滚页面即可; 由于静态资源版本号是文件内容的hash,因此所有静态资源可以开启永久强缓存,只有更新了内容的文件才会缓存失效,缓存利用率大增; 修改静态资源后会在线上产生新的文件,一个文件对应一个版本,因此不会受到构造CDN缓存形式的攻击 虽然这种方案是相比之下最完美的解决方案,但它无法通过手工的形式来维护,因为要依靠手工的形式来计算和替换hash值,并生成相应的文件。这将是一项非常繁琐且容易出错的工作,因此我们需要借助工具。我们下面来了解一下fis是如何完成这项工作的。 首先,之所以有这种工具需求,完全是由web应用运行的根本机制决定的:web应用所需的资源是以字面的形式通知浏览器下载而聚合在一起运行的。这种资源加载策略使得web应用从本质上区别于传统桌面应用的版本更新方式。为了实现资源定位的字面量替换操作,前端构建工具理论上需要识别所有资源定位的标记,其中包括: css中的@import url(path)、background:url(path)、backgournd-image:url(path)、filter中的src js中的自定义资源定位函数,在fis中我们将其规定为__uri(path)。 html中的<script src=”path”>、<link href=”path”>、<imgsrc=”path”>、已经embed、audio、video、object等具有资源加载功能的标签。 为了工程上的维护方便,我们希望工程师在源码中写的是相对路径,而工具可以将其替换为线上的绝对路径,从而避免相对路径定位错误的问题(比如js中需要定位图片路径时不能使用相对路径的情况)。 fis的资源定位设计思想 fis有一个非常棒的资源定位系统,它是根据用户自己的配置来指定资源发布后的地址,然后由fis的资源定位系统识别文件中的定位标记,计算内容hash,并根据配置替换为上线后的绝对url路径。 要想实现具备hash版本生成功能的构建工具不是“查找-替换”这么简单的。我们考虑这样一种情况: 资源引用关系 由于我们的资源版本号是通过对文件内容进行hash运算得到,如上图所示,index.html中引用的a.css文件的内容其实也包含了a.png的hash运算结果,因此我们在修改index.html中a.css的引用时,不能直接计算a.css的内容hash,而是要先计算出a.png的内容hash,替换a.css中的引用,得到了a.css的最终内容,再做hash运算,最后替换index.html中的引用。 这意味着构建工具需要具备“递归编译”的能力,这也是为什么fis团队不得不放弃gruntjs等task-based系统的根本原因。针对前端项目的构建工具必须是具备递归处理能力的。此外,由于文件之间的交叉引用等原因,fis构建工具还实现了构建缓存等机制,以提升构建速度。 在解决了基于内容hash的版本更新问题之后,我们可以将所有前端静态资源开启永久强缓存,每次版本发布都可以首先让静态资源全量上线,再进一步上线模板或者页面文件,再也不用担心各种缓存和时间间隙的问题了! 在本系列的下一部分,我们将介绍静态资源管理与模板框架的思路和用法。 作者简介:张云龙,百度公司Web前端研发部前端集成解决方案小组技术负责人,目前负责F.I.S项目,读者可以关注他的微博:http://weibo.com/fouber/。 from:http://www.infoq.com/cn/articles/front-end-engineering-and-performance-optimization-part1
View Details前端静态资源的缓存和更新问题解析
浏览器缓存主要有两类 缓存协商:Last-midified ,Etag 彻底缓存:cache-control,Expires 缓存协商的意思是需要去服务器端询问页面有没有修改过,没有修改过则返回304直接使用缓存内容,否则返回新内容 协商步骤: 1、服务器发送带Last-midified:GMTtime 头的http response 2、浏览器下次请求时带上if-modified-since:GMTtime http 请求头 3、服务端用本地Last-midified时间与if-modified-since比较,计算浏览器数据是否过期并发送响应 Etag的工作原理与Last-midified类似,不同点在于Etag的值是用户可自定义的 彻底缓存的意思是在缓存失效之前不再需要跟服务器交互 常用的是Expires,Expires的值是一个绝对时间,由服务器产生 这儿存在一个问题,就是服务器的时间可能给客户端的时间不一致导致缓存时间的偏差 要解决这个问题就要使用cache-control,它保存的是一个相对浏览器的时间 如果同时存在cache-control和Expires怎么办呢? 浏览器总是优先使用cache-control,如果没有cache-control才考虑Expires expire: 如果apache开启了expire模块, 当浏览器发送该资源请求的时候, apache返回资源的同时,会返回一个名为expire的http头,expire头的内容是一个时间值, 这一个值就是资源在本地的过期时间, 这个值会存在本地. 也就是说,在本地缓存阶段,在本地找到了一个对应的资源值,而且当前时间还没超过资源的过期时间, 那么就直接使用这一个资源,不会发送http请求. cache-control: cache-control是http协议中常用的头部之一,顾名思义, 他是负责控制页面的缓存机制,如果该头部指示缓存, 缓存的内容也会存在本地, 操作流程和expire相似,但也有不同的地方, cache-control有更多的选项, 而且也有更多的处理方式. if-modified-since 和 last-modified: 当apache接收到一个资源请求(假设是用户是第一次访问,没有任何缓存), 服务器返回资源的同时,还会发送一个last-modified的http响应头, last-modified响应头的内容值是该资源在服务器上最后修改的时间.浏览器接受到这个http头后,会 把其内容值和资源同时保存起来. 当用户第二发送资源请求(假设这里expire没有生效或者已经过期), 浏览器在本地找到了一个相同的资源,但是不能确定该资源是否和服务器上的一样(有可能在两次访问期间,服务器上的资源已经被修改过),此时浏览器发送请求的时候,请求头内会 附带一个if-modified-since的请求头, 这个头部的内容就是上一次last-modified返回的值, 服务器把这个头的值和请求资源的最后修改时间对比,如果两个值相同,则认为资源没有修改,将会返回304,让浏览器使用本地资源.否则服务器将返回资源,而且 返回200状态 if-none-match 和 etag: 其实这两个头部和if-modified-since, last-modified的工作原理是一样的, if-none-match作为请求头, etag作为响应头.既然工作原理一样, 为什么etag这对头部会出现呢? 原因在于, last-modified请求头的内容是以文件最后修改的时间作为对比的,但是unix系统里面, 文件修改的时间只保存到了秒. 如果某些应用内存在1秒内对文件做了多次修改,这样last-modified是不能完成比较功能的.所以要引入一个新的机制(原因可能不止这一个); etag的值一般由3个数值组成,资源的inode值, 最后修改时间, 资源大小,以16进制组成一个字符串, 例如:1a-182b-10f; 但这个格式不是固定的, 只要保证该值的唯一性,但不限格式. 静态资源的更新:张云龙老师的blog写的很好移步这里 html5离线存储 步骤: 1、配置apache让apache支持manifest文件 2、创建manifest文件test.manifest
1 2 3 4 5 6 |
CACHE MANIFEST # wanz app v1 # 指明缓存入口(指明需要缓存的文件) CACHE: index.html style.css images/logo.png scripts/main.js # 以下资源必须在线访问 NETWORK: login.php # 如果index.php无法访问则用404.html代替 FALLBACK: /index.php /404.html |
3、关联manifest文件到html文档
1 |
<html manifest="test.manifest"> ... </html> |
注意:#是用来注释一行的,但它还有一个小作用,web应用的缓存只有在manifest文件被修改的情况下才会被更新,所以如果你只是修改了被缓存的文件,那么用户本地的缓存还是不会被更新的,但是你可以通过修改manifest文件来告诉浏览器需要更新缓存了。利用这点,你可以像上面的例子中那样,写一句这样的注释一个文件版本: # wanz app v1 优点:你可以很明确的了解离线web应用的版本 通过简单的修改这个版本号就可以轻易的通知浏览器更新 你可以配合JavaScript程序来完成缓存更新 CACHE: 这个是manifest文件的默认入口,在此入口之后罗列的文件 (或直接写在CACHE MANIFEST后的文件)在它们下载到本地后会被缓存起来 NETWORK: […]
View DetailsC#中HttpClient使用注意:预热与长连接
原文地址:C#中HttpClient使用注意:预热与长连接 最近在测试一个第三方API,准备集成在我们的网站应用中。API的调用使用的是.NET中的HttpClient,由于这个API会在关键业务中用到,对调用API的整体响应速度有严格要求,所以对HttpClient有了格外的关注。 开始测试的时候,只在客户端通过HttpClient用PostAsync发了一个http post请求。测试时发现,从创建HttpClient实例,到发出请求,到读取到服务器的响应数据总耗时在2s左右,而且多次测试都是这样。2s的响应速度当然是无法让人接受的,我们希望至少控制在100ms以内。于是开始追查这个问题的原因。 在API的返回数据中包含了该请求在服务端执行的耗时,这个耗时都在20ms以内,问题与服务端API无关。于是把怀疑点放到了网络延迟上,但ping服务器的响应时间都在10ms左右,网络延迟的可能性也不大。 当我们正准备换一个网络环境进行测试时,突然想到,我们的测试方式有些问题。我们只通过HttpClient发了一个PostAsync请求,假如HttpClient在第一次调用时存在某种预热机制(比如在EF中就有这样的机制),现在2s的总耗时可能大多消耗在HttpClient的预热上。 于是修改测试代码,将调用由1次改为100次,然后恍然大悟地发现——只有第1次是2s,接下来的99次都在100ms以内。果然是HttpClient的某种预热机制在搞鬼! 既然知道了是HttpClient预热机制的原因,那我们可以帮HttpClient进行热身,减少第一次请求的耗时。我们尝试了一种预热方式,在正式发http post请求之前,先发一个http head请求,代码如下:
1 2 3 4 |
_httpClient.SendAsync(new HttpRequestMessage { Method = new HttpMethod("HEAD"), RequestUri = new Uri(BASE_ADDRESS + "/") }) .Result.EnsureSuccessStatusCode(); |
经测试,通过这种热身方法,可以将第一次请求的耗时由2s左右降到1s以内(测试结果是700多ms)。 在知道第1次HttpClient请求耗时2s的真相之后,我们将目光转向了剩下的99次耗时100ms以内的请求,发现绝大部分请求都在50ms以上。有没有可能将之降至50ms以下?而且,之前一直有这样的纠结:每次调用是不是一定要对HttpClient进行Dispose()?是不是要将HttpClient单例或者静态化(声明为静态变量)?借此机会一起研究一下。 在HttpClient的背后,有一个对请求响应速度有着不容忽视影响的东东——TCP连接。一个HttpClient实例会关联一个TCP连接,在对HttpClient进行Dispose时,会关闭TCP连接(我们用Wireshark进行网络抓包也验证了这一点)。 在之前的测试中,我们每次用HttpClient发请求时,都是新建一个HttpClient实例,用完就对它进行Dispose,代码如下:
1 2 3 4 |
using (var httpClient = new HttpClient() { BaseAddress = new Uri(BASE_ADDRESS) }) { httpClient.PostAsync("/", new FormUrlEncodedContent(parameters)); } |
所以每次请求时都要经历新建TCP连接->传数据->关闭连接(也就是通常所说的短连接),而且雪上加霜的是请求用的是https,建立TCP连接时还需要一个基于公私钥加解密的key exchange过程:Client Hello -> Server Hello -> Certificate -> Client Key Exchange -> New Session Ticket。 如果我们想将请求响应时间降至50ms以下,就必须从这个地方下手——重用TCP连接(也就是通常所说的长连接)。要实现长连接,首先需要的就是在HttpClient第1次请求后不关闭TCP连接(不调用Dispose方法);而要让后续的请求继续使用这个未关闭的TCP连接,我们必须要使用同一个HttpClient实例;而要使用同一个HttpClient实例,就得实现HttpClient的单例或者静态化。之前的3 个问题,由于要解决第1个问题,后2个问题变成了别无选择。 为了实现长连接,我们将HttpClient的调用代码改为如下的样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class HttpClientTest { private static readonly HttpClient _httpClient; static HttpClientTest() { _httpClient = new HttpClient() { BaseAddress = new Uri(BASE_ADDRESS) }; //帮HttpClient热身 _httpClient.SendAsync(new HttpRequestMessage { Method = new HttpMethod("HEAD"), RequestUri = new Uri(BASE_ADDRESS + "/") }) .Result.EnsureSuccessStatusCode(); } public async Task<string> PostAsync() { var response = await _httpClient.PostAsync("/", new FormUrlEncodedContent(parameters)); return await response.Content.ReadAsStringAsync(); } } |
然后测试一下请求响应时间:
1 2 3 4 5 6 7 8 9 10 11 |
Elapsed:750ms Elapsed:31ms Elapsed:30ms Elapsed:43ms Elapsed:27ms Elapsed:29ms Elapsed:28ms Elapsed:35ms Elapsed:36ms Elapsed:31ms .... |
除了第1次请求,接下来的99次请求绝大多数都在50ms以内。TCP长连接的效果必须的! 通过Wireshak抓包也验证了长连接的效果: 这时,你也许会产生这样的疑问:将HttpClient声明为静态变量,会不会存在线程安全问题?我们当时也有这样的疑问,后来在stackoverflow上找到了答案:
1 2 3 4 5 6 7 8 9 10 |
As per the comments below (thanks @ischell), the following instance methods are thread safe (all async): CancelPendingRequests DeleteAsync GetAsync GetByteArrayAsync GetStreamAsync GetStringAsync PostAsync PutAsync SendAsync |
HttpClient的所有异步方法都是线程安全的,放心使用。 到这里,HttpClient的问题是不是可以完美收官了?。。。稍等,还有一个问题。 客户端虽然保持着TCP连接,但TCP连接是两口子的事,服务器端呢?你不告诉服务器,服务器怎么知道你要一直保持TCP连接呢?对于客户端,保持TCP连接的开销不大;但是对于服务器,则完全不一样的,如果默认都保持TCP连接,那可是要保持成千上万客户端的连接啊。所以,一般的Web服务器都会根据客户端的诉求来决定是否保持TCP连接,这就是keep-alive存在的理由。 所以,我们还要给HttpClient增加一个Connection:keep-alive的请求头,代码如下:
1 |
_httpClient.DefaultRequestHeaders.Connection.Add("keep-alive"); |
现在终于可以收官了。但是肯定不完美,分享的只是解决问题的过程。 from:https://www.cnblogs.com/JustYong/p/5872296.html
View DetailsGithub 修正上传时“this exceeds GitHub’s file size limit of 100 MB”错误
Github只允许上传最大100MB的文件,如果超过,则会被server reject 则需: git filter-branch --force --index-filter "git rm --cached --ignore-unmatch FILEPATH" --prune-empty --tag-name-filter VERSION — --all git commit --amend -CHEAD git push origin master 注意要在.git文件夹目录下执行以上命令 http://www.walbrix.com/jp/blog/2013-10-github-large-files.html from:https://blog.csdn.net/fightforyourdream/article/details/25357121
View DetailsC# HttpClient请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
using Newtonsoft.Json; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; namespace w3cnet.Utils { /// <summary> /// HttpClient工具类 /// </summary> public class HttpClientUtil { /// <summary> /// GET /// </summary> /// <param name="url"></param> /// <param name="statusCode"></param> /// <returns></returns> public static string Get(string url, out string statusCode) { if (url.StartsWith("https")) ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var response = httpClient.GetAsync(url).Result; statusCode = response.StatusCode.ToString(); if (response.IsSuccessStatusCode) { string result = response.Content.ReadAsStringAsync().Result; return result; } return string.Empty; } /// <summary> /// GET /// </summary> /// <typeparam name="T"></typeparam> /// <param name="url"></param> /// <returns>指定对象</returns> public static T Get<T>(string url) where T : class, new() { if (url.StartsWith("https")) ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var response = httpClient.GetAsync(url).Result; T result = default(T); if (response.IsSuccessStatusCode) { var t = response.Content.ReadAsStringAsync(); var s = t.Result; result = JsonConvert.DeserializeObject<T>(s); } return result; } /// <summary> /// POST /// </summary> /// <param name="url"></param> /// <param name="postData"></param> /// <param name="statusCode"></param> /// <returns></returns> public static string Post(string url, string postData, out string statusCode) { if (url.StartsWith("https")) ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; var httpContent = new StringContent(postData); httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json") { CharSet = "utf-8" }; var httpClient = new HttpClient(); var response = httpClient.PostAsync(url, httpContent).Result; statusCode = response.StatusCode.ToString(); if (response.IsSuccessStatusCode) { string result = response.Content.ReadAsStringAsync().Result; return result; } return null; } /// <summary> /// POST /// </summary> /// <typeparam name="T"></typeparam> /// <param name="url"></param> /// <param name="postData"></param> /// <returns>指定对象</returns> public static T Post<T>(string url, string postData) where T : class, new() { if (url.StartsWith("https")) ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; var httpContent = new StringContent(postData); httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json") { CharSet = "utf-8" }; var httpClient = new HttpClient(); var response = httpClient.PostAsync(url, httpContent).Result; T result = default(T); if (response.IsSuccessStatusCode) { Task<string> t = response.Content.ReadAsStringAsync(); string s = t.Result; result = JsonConvert.DeserializeObject<T>(s); } return result; } } } |
from:https://www.cnblogs.com/louby/p/8021527.html
View Details