怎样利用缓存服务器来负载均衡

怎样利用缓存服务器来负载均衡,第1张

  根据一些专家的调查分析,发现企业在使用数据库的时候,90%以上主要用来查询。有些企业这个比例甚至更高。也就说,用户对数据库的操作,其实更新操作占的比例很少。大部分的操走都只是查询操作。

  如一些论坛,大部分用户只会看贴,而不会发帖。这就是一个典型的查询操作比例大大超过更新操作比例的例子。针对这种情况,其查询操作往往是其数据库性能的瓶颈。如何有效提高查询的性能,这就使各个数据库专家在考虑的问题。在SQL Server数据库中,已经有了一个现成的解决方案。数据库管理员可以利用缓存服务器来提高数据库的性能。笔者这里就以SQLServer2008为例,谈谈如何利用缓存服务器来实现负载均衡,来提高数据库的查询效率。

  一、 数据查询与数据更新分开走。

  如上图所示,如果用户要查看某个帖子,其就会打开某个连接。此时WEB应用服务器就会从后台数据库中查询相关的记录。这里需要注意的是,由于其只是查看帖子,而不涉及到更新的操作,为此WEB应用服务器就只从缓存服务器中读取数据。这个缓存服务器中的记录跟数据库服务器的内容是同步的。WEB应用服务器在从数据库缓存服务器读取数据之前,还会先判断一下哪台数据库服务器比较空。会优先连接到比较空闲的数据缓存服务器中,然后从这台服务器中读取数据。所以,当访问这个论坛的用户比较多时,这个数据缓存服务器能够实现负载均衡的需要。

  如果用户看了某个帖子,现在需要发表一个评论,此时后台数据库会怎么操作呢注意,当WEB应用服务器发送了一个 Update更新操作的时候,其应用服务器会自动连接到数据库服务器,而不会再连接到数据库缓存服务器。而是直接向数据库服务器发送更新操走的语句。当数据库服务器更新了相关的内容之后,会与数据库缓存服务器实现数据的同步。从上图中可以看出,整个数据查询与数据更新WEB应用服务器是分两条路走。其实这就好像是公路上分道行驶,机动车走机动车道;非机动车走非机动车道。

    如此的话,就不会因为非机动车比较慢,而影响到机动车的速度。在这个方案中,将数据库的更新操作与查询操作分开来走,也是类似的道理。在查询时,数据流是单向流动的,所以能够在很大程度上提高查询的效率。从而让数据负载均衡的效果更加明显。总之,当某个应用程序查询操作大大超过更新操作时,通过在多个数据库间缓存只读数据,并在数据库间均匀连接客户端以分发负载,则就可以向外扩展工作负荷的读取分区,即实现负载均衡的目的。

  二、 采用这个方案需要注意的地方。

  在部署这个解决方案时,仍然有些数据库管理员需要关注的内容。如以下这些内容,数据库管理员需要根据企业的实际情况来进行调整,以提高这个方案的价值。

  首先需要考虑数据缓存服务器与数据库服务器之间同步的频率问题。这个同步操作是一把双刃剑。若同步的频率太高,会影响数据库服务器与缓存服务器的性能;若同步频率比较低的话,则数据库缓存服务器中的数据得不到及时的更新。

  如此的话,用户查询时可能在短时间内无法获取最新的数据。所以,一般来说,系统滞后的时间应该尽量的短,即数据库服务器的更新内容必须尽快与数据库缓存服务器进行同步。

  理想的状态时,在更新数据库服务器的同时更新数据库缓存服务器。但是,这么做是以牺牲数据库与数据库缓存服务器的性能为代价的。为此数据库管理员在实施这个解决方案时,往往不会这么做。而是设置在一段时间之后同步。如可以设置为10秒、60秒、300秒或者更长的时间后进行同步。

  具体这个同步的时间间隔多少为好,没有一个统一的标准。这需要数据库管理员根据企业对数据同步的要求不同而定。一般来说,数据库管理员在满足用户需要的前期下,可以将这个时间设置的相对长一点。这可以避免因为过多的同步操作而降低了这个方案的价值。其实,对于大部分用户来说,60秒左右的时间差异还是可以接受的。如在论坛中,一个人发帖后,在一分钟之后看到一般不会有什么问题。对于人的感觉来说,这个一分钟时间不长。但是对于数据库服务器来说,这一分钟可以做很多事情。所以,适当延长这个同步时间,却可以在很大程度上提高数据库服务器性能。这个时间的代价,有时候还是值得的。

  其次,在数据库服务器与数据库缓存服务器之间,应该建立比较直接的、快速的网络连接。当用户比较多时,数据库服务器与数据库缓存服务器之间若发生同步操作,则会造成很多的网络流量。有时候同步操作发生时,影响这个工作的效率可能并不是数据库服务器或者数据库缓存服务器本身,而是他们之间的网络连接。

  由于其可用的带宽跟不少数据库服务器系统的吞吐量,从而影响到了同步操作的效率。为此,在数据库服务器与数据库缓存服务器之间的网路连接,应该尽量的直接。如最好不要在中间夹着其他的不必要的网络设备;也最好不要在他们之间配备防火墙等安全策略。这些安全策略与网络设备都会在很大程度上影响到这个同步操作的效率。

  另外,最好也不要有其他的应用服务来争抢带宽。所以简单的说,如果可能的话,在数据库服务器上部署多张网卡,直接与数据库源服务器实现双机互联,而那传输同步操作需要的数据,这是一个很不错的手段。由于其数据传输更直接、而且其他设备或者应用服务也会来争夺其带宽,同时又可以克服他们的非法攻击。为此,只要他们之间多距离比较短的话,采用这种方案可能效果会比较好,可以在最大程度内缩短这个同步操作所需要的时间,从而让其他用户尽早看到更新的数据。

  第三为同步选择合适的复制方案。

  那么该如何实现数据库服务器与缓存服务器之间的同步呢在SQLServer数据库中,有三个方案可供数据库管理员选择。这三个方案分别为快照复制、合并复制与事务复制。这三个复制模型各有各的特点。不过从最终效果来看,其都可以实现数据库服务器与数据库缓存服务器之间的同步。不过由于其内部的实现机制不同,为此其虽然结果相同,但是从性能等方面考虑,还是有差异的。

  各种复制模型的原理与特点属于基本知识的范畴,笔者在这里就不做过多阐述了。笔者认为,在利用这个数据库缓存服务器来实现负载均衡的方案中,最好采用事务复制的同步方案。因为相比其他方案来说,事务日志能够满足事务的一致性、数据库服务器系统比较大的吞吐量、同步时尽量少的开销、以及系统比较短的滞后时间等等需求。

  另外在有些企业中采用这个方案的话,还要考虑到表与记录的过滤需求。而通过事务复制的话,就可以实现对列和行的过滤。而其他复制模型的话,只能够部分满足这些需求。

    所以,笔者认为,在选择数据同步方案时,可能选择事务复制来实现同步,更加的合适。不过最终是否真是如此,还是要求数据库管理员根据企业的实际需要,然后分别采用几个复制模型来进行测试,才能够得出真正合理的结果。

  转载,仅供参考。

这次给大家带来Webpack如何实现持久化缓存,Webpack实现持久化缓存的注意事项有哪些,下面就是实战案例,一起来看一下。

前言

最近在看 webpack 如何做持久化缓存的内容,发现其中还是有一些坑点的,正好有时间就将它们整理总结一下,读完本文你大致能够明白:

什么是持久化缓存,为什么做持久化缓存

webpack 如何做持久化缓存?

webpack 做缓存的一些注意点。

持久化缓存

首先我们需要去解释一下,什么是持久化缓存,在现在前后端分离的应用大行其道的背景下,前端 html,css,js 往往是以一种静态资源文件的形式存在于服务器,通过接口来获取数据来展示动态内容。这就涉及到公司如何去部署前端代码的问题,所以就涉及到一个更新部署的问题,是先部署页面,还是先部署资源?

先部署页面,再部署资源:在二者部署的时间间隔内,如果有用户访问页面,就会在新的页面结构中加载旧的资源,并且把这个旧版本资源当做新版本缓存起来,其结果就是:用户访问到一个样式错乱的页面,除非手动去刷新,否则在资源缓存过期之前,页面会一直处于错乱的状态。

先部署资源,再部署页面:在部署时间间隔内,有旧版本的资源本地缓存的用户访问网站,由于请求的页面是旧版本,资源引用没有改变,浏览器将直接使用本地缓存,这样属于正常情况,但没有本地缓存或者缓存过期的用户在访问网站的时候,就会出现旧版本页面加载新版本资源的情况,导致页面执行错误。

所以我们需要一种部署策略来保证在更新我们线上的代码的时候,线上用户也能平滑地过渡并且正确打开我们的网站。

推荐先看这个回答:大公司里怎样开发和部署前端代码?

当你读完上面的回答,大致就会明白,现在比较成熟的持久化缓存方案就是在静态资源的名字后面加 hash 值,因为每次修改文件生成的 hash 值不一样,这样做的好处在于增量式发布文件,避免覆盖掉之前文件从而导致线上的用户访问失效。

因为只要做到每次发布的静态资源(css, js, img)的名称都是独一无二的,那么我就可以:

针对 html 文件:不开启缓存,把 html 放到自己的服务器上,关闭服务器的缓存,自己的服务器只提供 html 文件和数据接口

针对静态的 js,css,等文件:开启 cdn 和缓存,将静态资源上传到 cdn 服务商,我们可以对资源开启长期缓存,因为每个资源的路径都是独一无二的,所以不会导致资源被覆盖,保证线上用户访问的稳定性。

每次发布更新的时候,先将静态资源(js, css, img) 传到 cdn 服务上,然后再上传 html 文件,这样既保证了老用户能否正常访问,又能让新用户看到新的页面。

上面大致介绍了下主流的前端持久化缓存方案,那么我们为什么需要做持久化缓存呢?

用户使用浏览器第一次访问我们的站点时,该页面引入了各式各样的静态资源,如果我们能做到持久化缓存的话,可以在 http 响应头加上 Cache-control 或 Expires 字段来设置缓存,浏览器可以将这些资源一一缓存到本地。

用户在后续访问的时候,如果需要再次请求同样的静态资源,且静态资源没有过期,那么浏览器可以直接走本地缓存而不用再通过网络请求资源。

webpack 如何做持久化缓存

上面简单介绍完持久化缓存,下面这个才是重点,那么我们应该如何在 webpack 中进行持久化缓存的呢,我们需要做到以下两点:

保证 hash 值的唯一性,即为每个打包后的资源生成一个独一无二的 hash 值,只要打包内容不一致,那么 hash 值就不一致。

保证 hash 值的稳定性,我们需要做到修改某个模块的时候,只有受影响的打包后文件 hash 值改变,与该模块无关的打包文件 hash 值不变。

hash 文件名是实现持久化缓存的第一步,目前 webpack 有两种计算 hash 的方式([hash] 和 [chunkhash])

hash 代表每次 webpack 在编译的过程中会生成唯一的 hash 值,在项目中任何一个文件改动后就会被重新创建,然后 webpack 计算新的 hash 值。

chunkhash 是根据模块计算出来的 hash 值,所以某个文件的改动只会影响它本身的 hash 值,不会影响其他文件。

所以如果你只是单纯地将所有内容打包成同一个文件,那么 hash 就能够满足你了,如果你的项目涉及到拆包,分模块进行加载等等,那么你需要用 chunkhash,来保证每次更新之后只有相关的文件 hash 值发生改变。

所以我们在一份具有持久化缓存的 webpack 配置应该长这样:

moduleexports = {

entry: dirname + '/src/indexjs',

output: {

path: dirname + '/dist',

filename: '[name][chunkhash:8]js',

}

}上面代码的含义就是:以 indexjs 为入口,将所有的代码全部打包成一个文件取名为 indexxxxxjs 并放到 dist 目录下,现在我们可以在每次更新项目的时候做到生成新命名的文件了。

如果是应付简单的场景,这样做就够了,但是在大型多页面应用中,我们往往需要对页面进行性能优化:

分离业务代码和第三方的代码:之所以将业务代码和第三方代码分离出来,是因为业务代码更新频率高,而第三方代码更新迭代速度慢,所以我们将第三方代码(库,框架)进行抽离,这样可以充分利用浏览器的缓存来加载第三方库。

按需加载:比如在使用 React-Router 的时候,当用户需要访问到某个路由的时候再去加载对应的组件,那么用户没有必要在一开始的时候就将所有的路由组件下载到本地。

在多页面应用中,我们往往可以将公共模块进行抽离,比如 header, footer 等等,这样页面在进行跳转的时候这些公共模块因为存在于缓存里,就可以直接进行加载了,而不是再进行网络请求了。

那么如何进行拆包,分模块进行加载,这就需要 webpack 内置插件:CommonsChunkPlugin,下面我将通过一个例子,来诠释 webpack 该如何进行配置。

本文的代码放在我的 Github 上,有兴趣的可以下载来看看:

git clone https://githubcom/happylindz/bloggit

cd blog/code/multiple-page-webpack-demo

npm install阅读下面的内容之前我强烈建议你看下我之前的文章:深入理解 webpack 文件打包机制,理解 webpack 文件的打包的机制有助于你更好地实现持久化缓存。

例子大概是这样描述的:它由两个页面组成 pageA 和 pageB

// src/pageAjs

import componentA from '/common/componentA';

// 使用到 jquery 第三方库,需要抽离,避免业务打包文件过大

import $ from 'jquery';

// 加载 css 文件,一部分为公共样式,一部分为独有样式,需要抽离

import '/css/commoncss'

import '/css/pageAcss';

consolelog(componentA);

consolelog($trim(' do something '));

// src/pageBjs

// 页面 A 和 B 都用到了公共模块 componentA,需要抽离,避免重复加载

import componentA from '/common/componentA';

import componentB from '/common/componentB';

import '/css/commoncss'

import '/css/pageBcss';

consolelog(componentA);

consolelog(componentB);

// 用到异步加载模块 asyncComponent,需要抽离,加载首屏速度

documentgetElementById('xxxxx')addEventListener('click', () => {

import( / webpackChunkName: "async" /

'/common/asyncComponentjs')then((async) => {

async();

})

})

// 公共模块基本长这样

export default "component X";上面的页面内容基本简单涉及到了我们拆分模块的三种模式:拆分公共库,按需加载和拆分公共模块。那么接下来要来配置 webpack:

const path = require('path');

const webpack = require('webpack');

const ExtractTextPlugin = require('extract-text-webpack-plugin');

moduleexports = {

entry: {

pageA: [pathresolve(dirname, '/src/pageAjs')],

pageB: pathresolve(dirname, '/src/pageBjs'),

},

output: {

path: pathresolve(dirname, '/dist'),

filename: 'js/[name][chunkhash:8]js',

chunkFilename: 'js/[name][chunkhash:8]js'

},

module: {

rules: [

{

// 用正则去匹配要用该 loader 转换的 CSS 文件

test: /css$/,

use: ExtractTextPluginextract({

fallback: "style-loader",

use: ["css-loader"]

})

}

]

},

plugins: [

new webpackoptimizeCommonsChunkPlugin({

name: 'common',

minChunks: 2,

}),

new webpackoptimizeCommonsChunkPlugin({

name: 'vendor',

minChunks: ({ resource }) => (

resource && resourceindexOf('node_modules') >= 0 && resourcematch(/js$/)

)

}),

new ExtractTextPlugin({

filename: `css/[name][chunkhash:8]css`,

}),

]

}第一个 CommonsChunkPlugin 用于抽离公共模块,相当于是说 webpack 大佬,如果你看到某个模块被加载两次即以上,那么请你帮我移到 common chunk 里面,这里 minChunks 为 2,粒度拆解最细,你可以根据自己的实际情况,看选择是用多少次模块才将它们抽离。

第二个 CommonsChunkPlugin 用来提取第三方代码,将它们进行抽离,判断资源是否来自 node_modules,如果是,则说明是第三方模块,那就将它们抽离。相当于是告诉 webpack 大佬,如果你看见某些模块是来自 node_modules 目录的,并且名字是 js 结尾的话,麻烦把他们都移到 vendor chunk 里去,如果 vendor chunk 不存在的话,就创建一个新的。

这样配置有什么好处,随着业务的增长,我们依赖的第三方库代码很可能会越来越多,如果我们专门配置一个入口来存放第三方代码,这时候我们的 webpackconfigjs 就会变成:

// 不利于拓展

moduleexports = {

entry: {

app: '/src/mainjs',

vendor: [

'vue',

'axio',

'vue-router',

'vuex',

// more

],

},

} 第三个 ExtractTextPlugin 插件用于将 css 从打包好的 js 文件中抽离,生成独立的 css 文件,想象一下,当你只是修改了下样式,并没有修改页面的功能逻辑,你肯定不希望你的 js 文件 hash 值变化,你肯定是希望 css 和 js 能够相互分开,且互不影响。

运行 webpack 后可以看到打包之后的效果:

├── css

│ ├── common2beb7387css

│ ├── pageAd178426dcss

│ └── pageB33931188css

└── js

├── async03f28fafjs

├── common2beb7387js

├── pageAd178426djs

├── pageB33931188js

└── vendor22a1d956js可以看出 css 和 js 已经分离,并且我们对模块进行了拆分,保证了模块 chunk 的唯一性,当你每次更新代码的时候,会生成不一样的 hash 值。

唯一性有了,那么我们需要保证 hash 值的稳定性,试想下这样的场景,你肯定不希望你修改某部分的代码(模块,css)导致了文件的 hash 值全变了,那么显然是不明智的,那么我们去做到 hash 值变化最小化呢?

换句话说,我们就要找出 webpack 编译中会导致缓存失效的因素,想办法去解决或优化它?

影响 chunkhash 值变化主要由以下四个部分引起的:

包含模块的源代码

webpack 用于启动运行的 runtime 代码

webpack 生成的模块 moduleid(包括包含模块 id 和被引用的依赖模块 id)

chunkID

这四部分只要有任意部分发生变化,生成的分块文件就不一样了,缓存也就会失效,下面就从四个部分一一介绍:

一、源代码变化:

显然不用多说,缓存必须要刷新,不然就有问题了

二、webpack 启动运行的 runtime 代码:

看过我之前的文章:深入理解 webpack 文件打包机制 就会知道,在 webpack 启动的时候需要执行一些启动代码。

(function(modules) {

window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {

//

};

function webpack_require(moduleId) {

//

}

webpack_requiree = function requireEnsure(chunkId, callback) {

//

scriptsrc = webpack_requirep + "" + chunkId + "" + ({"0":"pageA","1":"pageB","3":"vendor"}[chunkId]||chunkId) + "" + {"0":"e72ce7d4","1":"69f6bbe3","2":"9adbbaa0","3":"53fa02a7"}[chunkId] + "js";

};

})([]);大致内容像上面这样,它们是 webpack 的一些启动代码,它们是一些函数,告诉浏览器如何加载 webpack 定义的模块。

其中有一行代码每次更新都会改变的,因为启动代码需要清楚地知道 chunkid 和 chunkhash 值得对应关系,这样在异步加载的时候才能正确地拼接出异步 js 文件的路径。

那么这部分代码最终放在哪个文件呢?因为我们刚才配置的时候最后生成的 common chunk 模块,那么这部分运行时代码会被直接内置在里面,这就导致了,我们每次更新我们业务代码(pageA, pageB, 模块)的时候, common chunkhash 会一直变化,但是这显然不符合我们的设想,因为我们只是要用 common chunk 用来存放公共模块(这里指的是 componentA),那么我 componentA 都没去修改,凭啥 chunkhash 需要变了。

所以我们需要将这部分 runtime 代码抽离成单独文件。

moduleexports = {

//

plugins: [

//

// 放到其他的 CommonsChunkPlugin 后面

new webpackoptimizeCommonsChunkPlugin({

name: 'runtime',

minChunks: Infinity,

}),

]

}这相当于是告诉 webpack 帮我把运行时代码抽离,放到单独的文件中。

├── css

│ ├── common4cc08e4dcss

│ ├── pageAd178426dcss

│ └── pageB33931188css

└── js

├── async03f28fafjs

├── common4cc08e4djs

├── pageAd178426djs

├── pageB33931188js

├── runtime8c79fdcdjs

└── vendorcef44292js多生成了一个 runtimexxxxjs,以后你在改动业务代码的时候,common chunk 的 hash 值就不会变了,取而代之的是 runtime chunk hash 值会变,既然这部分代码是动态的,可以通过 chunk-manifest-webpack-plugin 将他们 inline 到 html 中,减少一次网络请求。

三、webpack 生成的模块 moduleid

在 webpack2 中默认加载 OccurrenceOrderPlugin 这个插件,OccurrenceOrderPlugin 插件会按引入次数最多的模块进行排序,引入次数的模块的 moduleId 越小,但是这仍然是不稳定的,随着你代码量的增加,虽然代码引用次数的模块 moduleId 越小,越不容易变化,但是难免还是不确定的。

默认情况下,模块的 id 是这个模块在模块数组中的索引。OccurenceOrderPlugin 会将引用次数多的模块放在前面,在每次编译时模块的顺序都是一致的,如果你修改代码时新增或删除了一些模块,这将可能会影响到所有模块的 id。

最佳实践方案是通过 HashedModuleIdsPlugin 这个插件,这个插件会根据模块的相对路径生成一个长度只有四位的字符串作为模块的 id,既隐藏了模块的路径信息,又减少了模块 id 的长度。

这样一来,改变 moduleId 的方式就只有文件路径的改变了,只要你的文件路径值不变,生成四位的字符串就不变,hash 值也不变。增加或删除业务代码模块不会对 moduleid 产生任何影响。

moduleexports = {

plugins: [

new webpackHashedModuleIdsPlugin(),

// 放在最前面

//

]

}四、chunkID

实际情况中分块的个数的顺序在多次编译之间大多都是固定的, 不太容易发生变化。

这里涉及的只是比较基础的模块拆分,还有一些其它情况没有考虑到,比如异步加载组件中包含公共模块,可以再次将公共模块进行抽离。形成异步公共 chunk 模块。有想深入学习的可以看这篇文章:Webpack 大法之 Code Splitting

webpack 做缓存的一些注意点

CSS 文件 hash 值失效的问题

不建议线上发布使用 DllPlugin 插件

CSS 文件 hash 值失效的问题:

ExtractTextPlugin 有个比较严重的问题,那就是它生成文件名所用的[chunkhash]是直接取自于引用该 css 代码段的 js chunk ;换句话说,如果我只是修改 css 代码段,而不动 js 代码,那么最后生成出来的 css 文件名依然没有变化。

所以我们需要将 ExtractTextPlugin 中的 chunkhash 改为 contenthash,顾名思义,contenthash 代表的是文本文件内容的 hash 值,也就是只有 style 文件的 hash 值。这样编译出来的 js 和 css 文件就有独立的 hash 值了。

moduleexports = {

plugins: [

//

new ExtractTextPlugin({

filename: `css

一、浏览器缓存

浏览器缓存即http缓存;浏览器缓存根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是 强制缓存 和 协商缓存  。

浏览器第一次请求资源的时候服务器会告诉客户端是否应该缓存资源,根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中。如下图:

1强制缓存 :浏览器会对缓存进行查找,并根据一定的规则确定是否使用缓存。

强制缓存的缓存规则?

HTTP/10 Expires 这个字段是绝对时间,比如2018年6月30日12:30,然后在这个时间点之前的请求都会使用浏览器缓存,除非清除了缓存。

这个字段的缺点就是只会同步客户端的时间,这就有可能修改客户端时间导致缓存失效。

HTTP/11 cache-Control       这个是11的时候替换Expires的,它会有几种取值:

public :所有内容都将被缓存(客户端和代理服务器都可缓存)

private :所有内容只有客户端可以缓存, Cache-Control的默认取值

no-cache :客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定

no-store :所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存

max-age=xxx (xxx is numeric) :缓存内容将在xxx秒后失效

比如max-age=500,则在500秒内再次请求会直接只用缓存。

优先性:cache-Control > Expires

如果同时存在,cache-Control会覆盖Expires。

这个字段的缺点就是:

如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。

如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。

上图中浏览器缓存中存在该资源的缓存结果,并且没有失效,就会直接使用缓存的内容。

上图中浏览器缓存中没有该资源的缓存结果和标识,就会直接向服务器发起HTTP请求。

2协商缓存: 浏览器的强制缓存失效后(时间过期),浏览器携带缓存标识请求服务器,由服务器决定是否使用缓存。

服务器决定的规则?

控制协商缓存的字段有 Last-Modified / If-Modified-Since 和 Etag / If-None-Match。

①Last-Modified 是服务器返回给浏览器的本资源的最后修改时间。

当下次再次请求的时候,浏览器会在请求头中带 If-Modified-Since ,即上次请求下来的 Last-Modified 的值,

然后服务器会用这个值和该资源最后修改的时间比较,如果最后修改时间大于这个值,则会重新请求该资源,返回状态码200。

如果这个值和最后修改时间相等,则会返回304,告诉浏览器继续使用缓存。

② Etag 是服务器返回的一个hash值。

当下次再次请求的时候,浏览器会在请求头中带 If-None-Match ,即上次请求下来的 Etag 值,

然后服务器会用这个值和该资源在服务器的 Etag 值比较,如果一致则会返回304,继续使用缓存;如果不一致,则会重新请求,返回200。

二、服务器缓存

上面是一个简单的流程图:

用户1访问A页面,服务器解析A页面返回给用户1,同时在服务器内存上做一定映射,把A页面缓存在硬盘上面

用户2访问A页面,服务器直接根据内存上的映射找到对应的页面缓存,直接返回给用户2,这样就减少了服务器对同一页面的重复解析

服务器缓存和浏览器缓存的区别:

服务器缓存是把页面缓存到服务器上的硬盘里,而浏览器缓存是把页面缓存到用户自己的电脑里

Nginx服务器 

Nginx是一个高性能的HTTP和反向代理服务器。具有非常多的优越性:

在连接高并发的情况下,Nginx是Apache服务器不错的替代品,Nginx在美国是做虚拟主机生意的老板们经常选择的软件平台之一。

Nginx提供了expires、etag、if-modified-since指令来实现浏览器缓存控制。

nginx -s reload#重新加载配置文件 

nginx -s reopen#重新打开log文件 

nginx -s stop#快速关闭nginx服务 

nginx -s quit #优雅的关闭nginx服务,等待工作进程处理完所有的请求

Nginx设置静态文件的缓存过期时间 

location ~\(js|css|html|png|jpg)$ {

  expires 3d;

}

 expires    3d;  //表示缓存3天

expires    3h;  //表示缓存3小时

expires    max;  //表示缓存10年

expires    -1;  //表示永远过期。

如果设置为-1在js、css等静态文件在没有修改的情况下返回的是http 304,如果修改返回http 200

对于静态资源会自动添加ETag,可以通过添加etag off指令禁止生成ETag。如果是静态文件,那么Last-Modified值为文件的最后修改时间。

在开发调试web的时候,经常会碰到因浏览器缓存(cache)而经常要去清空缓存或者强制刷新来测试的烦恼,提供下apache不缓存配置和nginx不缓存配置的设置。在常用的缓存设置里面有两种方式,都是使用add_header来设置:分别为Cache-Control和Pragma。

location ~ \(css|js|swf|php|htm|html )$ {

  add_header Cache-Control no-store;

  add_header Pragma no-cache;

  }

nginx gzip压缩

使用 gzip 压缩可以降低网站带宽消耗,同时提升访问速度。

主要在nginx服务端将页面进行压缩,然后在浏览器端进行解压和解析,

目前大多数流行的浏览器都迟滞gzip格式的压缩,所以不用担心。

默认情况下,Nginx的gzip压缩是关闭的,同时,Nginx默认只对text/html进行压缩

gzip on;

ersio #开启gzip压缩输出

gzip_http_vn 10 ;#默认11

#其中的gzip_http_version的设置,它的默认值是11,就是说对HTTP/11协议的请求才会进行gzip压缩

#如果我们使用了proxy_pass进行反向代理,那么nginx和后端的upstream server之间是用HTTP/10协议通信的。

gzip_vary on ;

#和http头有关系,加个vary头,给代理服务器用的,有的浏览器支持压缩,有的不支持,

#所以避免浪费不支持的也压缩,所以根据客户端的HTTP头来判断,是否需要压缩

gzip_comp_level 6;

#设置gzip压缩等级,等级越底压缩速度越快文件压缩比越小,反之速度越慢文件压缩比越大 1-9

gzip_proxied any;

#Ngnix作为反向代理的时候启用

#expample:gzip_proxied no-cache;

# off – 关闭所有的代理结果数据压缩

# expired – 启用压缩,如果header中包含”Expires”头信息

# no-cache – 启用压缩,如果header中包含”Cache-Control:no-cache”头信息

# no-store – 启用压缩,如果header中包含”Cache-Control:no-store”头信息

# private – 启用压缩,如果header中包含”Cache-Control:private”头信息

# no_last_modified – 启用压缩,如果header中包含”Last_Modified”头信息

# no_etag – 启用压缩,如果header中包含“ETag”头信息

# auth – 启用压缩,如果header中包含“Authorization”头信息

# any – 无条件压缩所有结果数据

gzip_types text/html ;#压缩的文件类型

#设置需要压缩的MIME类型,非设置值不进行压缩

#param:text/html|application/x-javascript|text/css|application/xml

gzip_buffers 16 8k; #设置gzip申请内存的大小,其作用是按块大小的倍数申请内存空间设置gzip申请内存的大小,其作用是按块大小的倍数申请内存空间

#设置gzip申请内存的大小,其作用是按块大小的倍数申请内存空间

# param1:int 增加的倍数

# param2:int(k) 后面单位是k

# example: gzip_buffers 4 8k;

# Disable gzip for certain browsers

gzip_disable “MSIE [1-6](!SV1)”; #ie6不支持gzip,需要禁用掉ie6

Redis复制流程概述

Redis的复制功能是完全建立在之前我们讨论过的基于内存快照的持久化策略基础上的,也就是说无论你的持久化策略选择的是什么,只要用到了Redis的复制功能,就一定会有内存快照发生,那么首先要注意你的系统内存容量规划,原因可以参考我上一篇文章中提到的Redis磁盘IO问题。

Redis复制流程在Slave和Master端各自是一套状态机流转,涉及的状态信息是:

Slave 端:

REDIS_REPL_NONEREDIS_REPL_CONNECTREDIS_REPL_CONNECTED

Master端:

REDIS_REPL_WAIT_BGSAVE_STARTREDIS_REPL_WAIT_BGSAVE_ENDREDIS_REPL_SEND_BULKREDIS_REPL_ONLINE

整个状态机流程过程如下:

Slave端在配置文件中添加了slave of指令,于是Slave启动时读取配置文件,初始状态为REDIS_REPL_CONNECT。

Slave端在定时任务serverCron(Redis内部的定时器触发事件)中连接Master,发送sync命令,然后阻塞等待master发送回其内存快照文件(最新版的Redis已经不需要让Slave阻塞)。

Master端收到sync命令简单判断是否有正在进行的内存快照子进程,没有则立即开始内存快照,有则等待其结束,当快照完成后会将该文件发送给Slave端。

Slave端接收Master发来的内存快照文件,保存到本地,待接收完成后,清空内存表,重新读取Master发来的内存快照文件,重建整个内存表数据结构,并最终状态置位为 REDIS_REPL_CONNECTED状态,Slave状态机流转完成。

Master端在发送快照文件过程中,接收的任何会改变数据集的命令都会暂时先保存在Slave网络连接的发送缓存队列里(list数据结构),待快照完成后,依次发给Slave,之后收到的命令相同处理,并将状态置位为 REDIS_REPL_ONLINE。

整个复制过程完成,流程如下图所示:

Redis复制机制的缺陷

从上面的流程可以看出,Slave从库在连接Master主库时,Master会进行内存快照,然后把整个快照文件发给Slave,也就是没有象MySQL那样有复制位置的概念,即无增量复制,这会给整个集群搭建带来非常多的问题。

比如一台线上正在运行的Master主库配置了一台从库进行简单读写分离,这时Slave由于网络或者其它原因与Master断开了连接,那么当Slave进行重新连接时,需要重新获取整个Master的内存快照,Slave所有数据跟着全部清除,然后重新建立整个内存表,一方面Slave恢复的时间会非常慢,另一方面也会给主库带来压力。

所以基于上述原因,如果你的Redis集群需要主从复制,那么最好事先配置好所有的从库,避免中途再去增加从库。

Cache还是Storage

在我们分析过了Redis的复制与持久化功能后,我们不难得出一个结论,实际上Redis目前发布的版本还都是一个单机版的思路,主要的问题集中在,持久化方式不够成熟,复制机制存在比较大的缺陷,这时我们又开始重新思考Redis的定位:Cache还是Storage?

如果作为Cache的话,似乎除了有些非常特殊的业务场景,必须要使用Redis的某种数据结构之外,我们使用Memcached可能更合适,毕竟Memcached无论客户端包和服务器本身更久经考验。

如果是作为存储Storage的话,我们面临的最大的问题是无论是持久化还是复制都没有办法解决Redis单点问题,即一台Redis挂掉了,没有太好的办法能够快速的恢复,通常几十G的持久化数据,Redis重启加载需要几个小时的时间,而复制又有缺陷,如何解决呢?

Redis可扩展集群搭建1 主动复制避开Redis复制缺陷。

既然Redis的复制功能有缺陷,那么我们不妨放弃Redis本身提供的复制功能,我们可以采用主动复制的方式来搭建我们的集群环境。

所谓主动复制是指由业务端或者通过代理中间件对Redis存储的数据进行双写或多写,通过数据的多份存储来达到与复制相同的目的,主动复制不仅限于用在Redis集群上,目前很多公司采用主动复制的技术来解决MySQL主从之间复制的延迟问题,比如Twitter还专门开发了用于复制和分区的中间件gizzard(https://githubcom/twitter/gizzard) 。

主动复制虽然解决了被动复制的延迟问题,但也带来了新的问题,就是数据的一致性问题,数据写2次或多次,如何保证多份数据的一致性呢?如果你的应用对数据一致性要求不高,允许最终一致性的话,那么通常简单的解决方案是可以通过时间戳或者vector clock等方式,让客户端同时取到多份数据并进行校验,如果你的应用对数据一致性要求非常高,那么就需要引入一些复杂的一致性算法比如Paxos来保证数据的一致性,但是写入性能也会相应下降很多。

通过主动复制,数据多份存储我们也就不再担心Redis单点故障的问题了,如果一组Redis集群挂掉,我们可以让业务快速切换到另一组Redis上,降低业务风险。

2 通过presharding进行Redis在线扩容。

通过主动复制我们解决了Redis单点故障问题,那么还有一个重要的问题需要解决:容量规划与在线扩容问题。

我们前面分析过Redis的适用场景是全部数据存储在内存中,而内存容量有限,那么首先需要根据业务数据量进行初步的容量规划,比如你的业务数据需要100G存储空间,假设服务器内存是48G,那么根据上一篇我们讨论的Redis磁盘IO的问题,我们大约需要3~4台服务器来存储。这个实际是对现有业务情况所做的一个容量规划,假如业务增长很快,很快就会发现当前的容量已经不够了,Redis里面存储的数据很快就会超过物理内存大小,那么如何进行Redis的在线扩容呢?

Redis的作者提出了一种叫做presharding的方案来解决动态扩容和数据分区的问题,实际就是在同一台机器上部署多个Redis实例的方式,当容量不够时将多个实例拆分到不同的机器上,这样实际就达到了扩容的效果。

拆分过程如下:

在新机器上启动好对应端口的Redis实例。

配置新端口为待迁移端口的从库。

待复制完成,与主库完成同步后,切换所有客户端配置到新的从库的端口。

配置从库为新的主库。

移除老的端口实例。

重复上述过程迁移好所有的端口到指定服务器上。

以上拆分流程是Redis作者提出的一个平滑迁移的过程,不过该拆分方法还是很依赖Redis本身的复制功能的,如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。所以做这个拆分的过程最好选择为业务访问低峰时段进行。

Redis复制的改进思路

我们线上的系统使用了我们自己改进版的Redis,主要解决了Redis没有增量复制的缺陷,能够完成类似Mysql Binlog那样可以通过从库请求日志位置进行增量复制。

我们的持久化方案是首先写Redis的AOF文件,并对这个AOF文件按文件大小进行自动分割滚动,同时关闭Redis的Rewrite命令,然后会在业务低峰时间进行内存快照存储,并把当前的AOF文件位置一起写入到快照文件中,这样我们可以使快照文件与AOF文件的位置保持一致性,这样我们得到了系统某一时刻的内存快照,并且同时也能知道这一时刻对应的AOF文件的位置,那么当从库发送同步命令时,我们首先会把快照文件发送给从库,然后从库会取出该快照文件中存储的AOF文件位置,并将该位置发给主库,主库会随后发送该位置之后的所有命令,以后的复制就都是这个位置之后的增量信息了。

Redis与MySQL的结合

目前大部分互联网公司使用MySQL作为数据的主要持久化存储,那么如何让Redis与MySQL很好的结合在一起呢?我们主要使用了一种基于MySQL作为主库,Redis作为高速数据查询从库的异构读写分离的方案。

为此我们专门开发了自己的MySQL复制工具,可以方便的实时同步MySQL中的数据到Redis上。

(MySQL-Redis 异构读写分离)

总结:

Redis的复制功能没有增量复制,每次重连都会把主库整个内存快照发给从库,所以需要避免向在线服务的压力较大的主库上增加从库。

Redis的复制由于会使用快照持久化方式,所以如果你的Redis持久化方式选择的是日志追加方式(aof),那么系统有可能在同一时刻既做aof日志文件的同步刷写磁盘,又做快照写磁盘操作,这个时候Redis的响应能力会受到影响。所以如果选用aof持久化,则加从库需要更加谨慎。

可以使用主动复制和presharding方法进行Redis集群搭建与在线扩容。

实现

要实现页面输出缓存,只要将一条 OutputCache 指令添加到页面即可。

<%@ OutputCache Duration="60" VaryByParam="" %>

如同其他页面指令一样,该指令应该出现在 ASPX 页面的顶部,即在任何输出之前。它支持五个属性(或参数),其中两个是必需的。

Duration

必需属性。页面应该被缓存的时间,以秒为单位。必须是正整数。

Location

指定应该对输出进行缓存的位置。如果要指定该参数,则必须是下列选项之一:Any、Client、Downstream、None、Server 或 ServerAndClient。

VaryByParam

必需属性。Request 中变量的名称,这些变量名应该产生单独的缓存条目。"none" 表示没有变动。"" 可用于为每个不同的变量数组创建新的缓存条目。变量之间用 ";" 进行分隔。

VaryByHeader

基于指定的标头中的变动改变缓存条目。

VaryByCustom

允许在 globalasax 中指定自定义变动(例如,"Browser")。

利用必需的 Duration 和 VaryByParam 选项的组合可以处理大多数情况。例如,如果您的产品目录允许用户基于 categoryID 和页变量查看目录页,您可以用参数值为 "categoryID;page" 的 VaryByParam 将产品目录缓存一段时间(如果产品不是随时都在改变,一小时还是可以接受的,因此,持续时间是 3600 秒)。这将为每个种类的每个目录页创建单独的缓存条目。每个条目从其第一个请求算起将维持一个小时。

VaryByHeader 和 VaryByCustom 主要用于根据访问页面的客户端对页面的外观或内容进行自定义。同一个 URL 可能需要同时为浏览器和移动电话客户端呈现输出,因此,需要针对不同的客户端缓存不同的内容版本。或者,页面有可能已经针对 IE 进行了优化,但需要能针对 Netscape 或 Opera 完全降低优化(而不仅仅是破坏页面)。后一个例子非常普遍,我们将提供一个说明如何实现此目标的示例:

示例:VaryByCustom 用于支持浏览器自定义

为了使每个浏览器都具有单独的缓存条目,VaryByCustom 的值可以设置为 "browser"。此功能已经内置在缓存模块中,并且将针对每个浏览器名称和主要版本插入单独的页面缓存版本。

<%@ OutputCache Duration="60" VaryByParam="None" VaryByCustom="browser" %>

片段缓存,用户控件输出缓存

缓存整个页面通常并不可行,因为页面的某些部分是针对用户定制的。不过,页面的其他部分是整个应用程序共有的。这些部分最适合使用片段缓存和用户控件进行缓存。菜单和其他布局元素,尤其是那些从数据源动态生成的元素,也应该用这种方法进行缓存。如果需要,可以将缓存的控件配置为基于对其控件(或其他属性)的更改或由页面级输出缓存支持的任何其他变动进行改变。使用同一组控件的几百个页面还可以共享那些控件的缓存条目,而不是为每个页面保留单独的缓存版本。

实现

片段缓存使用的语法与页面级输出缓存一样,但其应用于用户控件(ascx 文件)而不是 Web 窗体(aspx 文件)。除了 Location 属性,对于 OutputCache 在 Web 窗体上支持的所有属性,用户控件也同样支持。用户控件还支持名为 VaryByControl 的 OutputCache 属性,该属性将根据用户控件(通常是页面上的控件,例如,DropDownList)的成员的值改变该控件的缓存。如果指定了 VaryByControl,可以省略 VaryByParam。最后,在默认情况下,对每个页面上的每个用户控件都单独进行缓存。不过,如果一个用户控件不随应用程序中的页面改变,并且在所有页面都使用相同的名称,则可以应用 Shared="true" 参数,该参数将使用户控件的缓存版本供所有引用该控件的页面使用。

示例

<%@ OutputCache Duration="60" VaryByParam="" %>

该示例将缓存用户控件 60 秒,并且将针对查询字符串的每个变动、针对此控件所在的每个页面创建单独的缓存条目。

<%@ OutputCache Duration="60" VaryByParam="none"

VaryByControl="CategoryDropDownList" %>

该示例将缓存用户控件 60 秒,并且将针对 CategoryDropDownList 控件的每个不同的值、针对此控件所在的每个页面创建单独的缓存条目。

<%@ OutputCache Duration="60" VaryByParam="none" VaryByCustom="browser"

Shared="true %>

最后,该示例将缓存用户控件 60 秒,并且将针对每个浏览器名称和主要版本创建一个缓存条目。然后,每个浏览器的缓存条目将由引用此用户控件的所有页面共享(只要所有页面都用相同的 ID 引用该控件即可)。

页面级和用户控件级输出缓存的确是一种可以迅速而简便地提高站点性能的方法,但是在 ASPNET 中,缓存的真正灵活性和强大功能是通过 Cache 对象提供的。使用 Cache 对象,您可以存储任何可序列化的数据对象,基于一个或多个依赖项的组合来控制缓存条目到期的方式。这些依赖项可以包括自从项被缓存后经过的时间、自从项上次被访问后经过的时间、对文件和/或文件夹的更改以及对其他缓存项的更改,在略作处理后还可以包括对数据库中特定表的更改。

在 Cache 中存储数据

在 Cache 中存储数据的最简单的方法就是使用一个键为其赋值,就像 HashTable 或 Dictionary 对象一样:

Cache["key"] = "value";

这种做法将在缓存中存储项,同时不带任何依赖项,因此它不会到期,除非缓存引擎为了给其他缓存数据提供空间而将其删除。要包括特定的缓存依赖项,可使用 Add() 或 Insert() 方法。其中每个方法都有几个重载。Add() 和 Insert() 之间的唯一区别是,Add() 返回对已缓存对象的引用,而 Insert() 没有返回值(在 C# 中为空,在 VB 中为 Sub)。

示例

CacheInsert("key", myXMLFileData, new

SystemWebCachingCacheDependency(ServerMapPath("usersxml")));

该示例可将文件中的 xml 数据插入缓存,无需在以后请求时从文件读取。 CacheDependency 的作用是确保缓存在文件更改后立即到期,以便可以从文件中提取最新数据,重新进行缓存。如果缓存的数据来自若干个文件,还可以指定一个文件名的数组。

CacheInsert("dependentkey", myDependentData, new

SystemWebCachingCacheDependency(new string[] {}, new string[]

{"key"}));

该示例可插入键值为 "key" 的第二个数据块(取决于是否存在第一个数据块)。如果缓存中不存在名为 "key" 的键,或者如果与该键相关联的项已到期或被更新,则 "dependentkey" 的缓存条目将到期。

CacheInsert("key", myTimeSensitiveData, null,

DateTimeNowAddMinutes(1), TimeSpanZero);

绝对到期:此示例将对受时间影响的数据缓存一分钟,一分钟过后,缓存将到期。注意,绝对到期和滑动到期(见下文)不能一起使用。

CacheInsert("key", myFrequentlyAccessedData, null,

SystemWebCachingCacheNoAbsoluteExpiration,

TimeSpanFromMinutes(1));

滑动到期:此示例将缓存一些频繁使用的数据。数据将在缓存中一直保留下去,除非数据未被引用的时间达到了一分钟。注意,滑动到期和绝对到期不能一起使用。

更多选项

除了上面提到的依赖项,我们还可以指定项的优先级(依次为 low、high、NotRemovable,它们是在 SystemWebCachingCacheItemPriority 枚举中定义的)以及当缓存中的项到期时调用的 CacheItemRemovedCallback 函数。大多数时候,默认的优先级已经足够了 — 缓存引擎可以正常完成任务并处理缓存的内存管理。CacheItemRemovedCallback 选项考虑到一些很有趣的可能性,但实际上它很少使用。不过,为了说明该方法,我将提供它的一个使用示例:

CacheItemRemovedCallback 示例

SystemWebCachingCacheItemRemovedCallback callback = new SystemWebCachingCacheItemRemovedCallback (OnRemove);

CacheInsert("key",myFile,null,

SystemWebCachingCacheNoAbsoluteExpiration,

TimeSpanZero,

SystemWebCachingCacheItemPriorityDefault, callback);

public static void OnRemove(string key,

object cacheItem,

SystemWebCachingCacheItemRemovedReason reason)

{

AppendLog("The cached value with key " + key +

" was removed from the cache Reason: " +

reasonToString());

}

该示例将使用 AppendLog() 方法(这里不讨论该方法,请参阅 Writing Entries to Event Logs)中定义的任何逻辑来记录缓存中的数据到期的原因。通过在从缓存中删除项时记录这些项并记录删除的原因,您可以确定是否在有效地使用缓存或者您是否可能需要增加服务器上的内存。注意,callback 是一个静态(在 VB 中为 Shared)方法,建议使用该方法的原因是,如果不使用它,保存回调函数的类的实例将保留在内存中,以支持回调(对 static/Shared 方法则没有必要)。

该特性有一个潜在的用处 — 在后台刷新缓存的数据,这样用户永远都不必等待数据被填充,但数据始终保持相对较新的状态。但实际上,此特性并不适用于当前版本的缓存 API,因为在从缓存中删除缓存的项之前,不触发或不完成回调。因此,用户将频繁地发出尝试访问缓存值的请求,然后发现缓存值为空,不得不等待缓存值的重新填充。我希望在未来的 ASPNET 版本中看到一个附加的回调,可以称为 CachedItemExpiredButNotRemovedCallback,如果定义了该回调,则必须在删除缓存项之前完成执行。

缓存数据引用模式

每当我们尝试访问缓存中的数据时,都应该考虑到一种情况,那就是数据可能已经不在缓存中了。因此,下面的模式应该普遍适用于您对缓存的数据的访问。在这种情况下,我们假定已缓存的数据是一个数据表。

public DataTable GetCustomers(bool BypassCache)

{

string cacheKey = "CustomersDataTable";

object cacheItem = Cache[cacheKey] as DataTable;

if((BypassCache) (cacheItem == null))

{

cacheItem = GetCustomersFromDataSource();

CacheInsert(cacheKey, cacheItem, null,

DateTimeNowAddSeconds(GetCacheSecondsFromConfig(cacheKey),

TimeSpanZero);

}

return (DataTable)cacheItem;

}

关于此模式,有以下几点需要注意:

某些值(例如,cacheKey、cacheItem 和缓存持续时间)是一次定义的,并且只定义一次。

可以根据需要跳过缓存 — 例如,当注册一个新客户并重定向到客户列表后,最好的做法可能就是跳过缓存,用最新数据重新填充缓存,该数据包括新插入的客户。

缓存只能访问一次。这种做法可以提高性能,并确保不会发生 NullReferenceExceptions,因为该项在第一次被检查时是存在的,但第二次检查之前就已经到期了。

该模式使用强类型检查。C# 中的 "as" 运算符尝试将对象转换为类型,如果失败或该对象为空,则只返回 null(空)。

持续时间存储在配置文件中。在理想的情况下,所有的缓存依赖项(无论是基于文件的,或是基于时间的,还是其他类型的依赖项)都应该存储在配置文件中,这样就可以进行更改并轻松地测量性能。我还建议您指定默认缓存持续时间,而且,如果没有为所使用的 cacheKey 指定持续时间,就让 GetCacheSecondsFromConfig() 方法使用该默认持续时间。

相关的代码示例是一个 helper 类,它将处理上述所有情况,但允许通过一行或两行代码访问缓存的数据。请下载 CacheDemosmsi。

小结

缓存可以使应用程序的性能得到很大的提高,因此在设计应用程序以及对应用程序进行性能测试时应该予以考虑。应用程序总会或多或少地受益于缓存,当然有些应用程序比其他应用程序更适合使用缓存。对 ASPNET 提供的缓存选项的深刻理解是任何 ASPNET 开发人员应该掌握的重要技巧。

尽早缓存;经常缓存

您应该在应用程序的每一层都实现缓存。向数据层、业务逻辑层、UI 或输出层添加缓存支持。内存现在非常便宜 — 因此,通过以智能的方式在整个应用程序中实现缓存,可以获得很大的性能提高。

缓存可以掩盖许多过失

缓存是一种无需大量时间和分析就可以获得"足够良好的"性能的方法。这里再次强调,内存现在非常便宜,因此,如果您能通过将输出缓存 30 秒,而不是花上一整天甚至一周的时间尝试优化代码或数据库就可以获得所需的性能,您肯定会选择缓存解决方案(假设可以接受 30 秒的旧数据)。缓存正是那些利用 20% 付出获得 80% 回报的特性之一,因此,要提高性能,应该首先想到缓存。不过,如果设计很糟糕,最终却有可能带来不良的后果,因此,您当然也应该尽量正确地设计应用程序。但如果您只是需要立即获得足够高的性能,缓存就是您的最佳选择,您可以在以后有时间的时候再尽快重新设计应用程序。

页面级输出缓存

作为最简单的缓存形式,输出缓存只是在内存中保留为响应请求而发送的 HTML 的副本。其后再有请求时将提供缓存的输出,直到缓存到期,这样,性能有可能得到很大的提高(取决于需要多少开销来创建原始页面输出 - 发送缓存的输出总是很快,并且比较稳定)。

你详细看看

DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
网站模板库 » 怎样利用缓存服务器来负载均衡

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情