SegmentFault 全栈之路最新的文章
2019-04-29T21:17:15+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
Google搜索灭霸彩蛋的实现效果
https://segmentfault.com/a/1190000019033549
2019-04-29T21:17:15+08:00
2019-04-29T21:17:15+08:00
LichKing24
https://segmentfault.com/u/lichking24
2
<p>详见Github<br><a href="https://link.segmentfault.com/?enc=oZRr5hUHEIopucfgvI5BoQ%3D%3D.8G8%2BAXqyWHtExwJX6UDfLsergUZ9aq6JuXOjaghfOfb60GQ6IylX8l8MqeyuWzpV" rel="nofollow">https://github.com/lichking24...</a></p>
<h2>Thanos Dust Effect</h2>
<h3>Effects Demo</h3>
<p>As Thanos making a snap with the Infinite Gauntlet, half the heros vanish to dust. As using the time stone, people come back.</p>
<h3>Details</h3>
<p>The procedures:</p>
<ol>
<li>Click the Gauntlet button, and display animation to make a snap, and play audio file;</li>
<li>
<p>Select half heros by random, the random method is to resort members array;</p>
<pre><code>arr.sort(function() {
return 0.5 - Math.random();
});</code></pre>
</li>
<li>
<p>Make selected item to dust</p>
<ul>
<li>3.1 use html2canvas library to convert dom to a canvas image</li>
<li>3.2 split the canvas images into many pieces by pixel, <code>function generateFrames()</code>
</li>
<li>3.3 create a container which has the same size and position as the converted dom</li>
<li>3.4 appendChild to the container with the pieces</li>
<li>3.5 rotate random degrees and translate random pixel for each piece, which shows the dust animation</li>
<li>3.6 set converted dom item to be invisible and finish the SNAP action</li>
</ul>
</li>
<li>Reverse time to bring heros back by adjusting the visibility of converted dom items</li>
</ol>
<h3>FAQ</h3>
<p><strong>Why use a nodejs express server?</strong></p>
<p>A static html file will show errors with "Unable to get image data from canvas because the canvas has been tainted by cross-origin data", even if I set allowTaint to be true.</p>
Vue项目构建持续集成阿里云CDN
https://segmentfault.com/a/1190000018915895
2019-04-18T22:46:34+08:00
2019-04-18T22:46:34+08:00
LichKing24
https://segmentfault.com/u/lichking24
10
<p>CDN加速是Web应用性能优化和用户体验提升的至关重要的一环,当一个项目构建部署时,就需要考虑到如何高效的去完成相关资源的CDN部署。</p>
<p>本文以一个基于 <code>vue-cli3</code> 构建的项目实例,来简单讲解如何配合Teamcity,自动进行阿里云CDN资源部署和持续集成。</p>
<h2>项目构建</h2>
<p>vue-cli3 默认支持将项目以 <code>test</code>、<code>development</code>、<code>production</code> 三种模式构建,其中 <code>production</code> 模式将在 build 后生成 <code>dist</code>目录。我们在项目路径下插入 <code>.env.[mode]</code> 格式的文件就可以实现自定义模式。</p>
<p>通常,默认的构建模式无法满足项目研发需求。一个项目至少需要包含</p>
<ol>
<li>本地调试 - 即开发过程中的 <code>development</code> 模式,不生成 dist 静态目录,使用 vue-dev-server运行项目;</li>
<li>测试环境 - 即基本的集成测试,需要文件静态化,部署到测试环境;</li>
<li>线上环境 - 即用户环境,也需要文件静态化,并做CDN加速等性能优化措施;</li>
</ol>
<p>按照这个模型,我们需要自定义一个 <code>deploy</code> 模式,来实现和普通 <code>production</code>打包后,资源引入路径的区别。</p>
<p><strong>首先,环境创建</strong></p>
<p>在项目根目录下创建 <code>.env.deploy</code> 文件,添加内容如下:</p>
<pre><code>NODE_ENV=production
DEPLOY=online</code></pre>
<p><code>NODE_ENV</code>的设置代表webpack构建时使用<code>production</code>模式,即会生成 <code>dist</code>静态目录。<br><code>DEPLOY</code>的设置,是一个我们定义的变量,用于在配置中区分<code>deploy</code>和<code>production</code>模式。</p>
<p><strong>其次,配置文件</strong></p>
<p>在 <code>vue.config.js</code> 中,配置 <code>BASE_URL</code></p>
<pre><code>// 根据自定义的变量来进行内容设置
let BASE_URL = '/'
switch(process.env.DEPLOY) {
case 'online':
BASE_URL = 'http://web-cdn.xxx.com/'
break
default:
BASE_URL = '/'
}
module.exports = {
publicPath: BASE_URL,
....
}</code></pre>
<p>该配置会使得当程序使用 <code>deploy</code> 模式运行时,打包的资源根路径为我们的CDN地址。</p>
<p><strong>最后,构建命令</strong></p>
<p>在 <code>package.json</code> 中,配置使用 <code>deploy</code> 模式的打包命令</p>
<pre><code>"scripts": {
"build": "vue-cli-service build",
"deploy": "vue-cli-service build --mode deploy",
...
}</code></pre>
<p>当用户执行 <code>npm run build</code> 时,会生成以 <code>/</code> 为资源路径的文件;<br>当用户执行 <code>npm run deploy</code> 时,生成 <code>index.html</code> 中的资源路径就变成了我们配置的CDN路径。</p>
<pre><code><!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<meta http-equiv=X-UA-Compatible content="IE=edge">
<meta name=viewport content="width=device-width,initial-scale=1">
<link rel=icon href=http://web-cdn.xxx.com/favicon.ico>
<title>Demo</title>
<link href=http://web-cdn.xxx.com/css/chunk-0fabbc4c.08fa0fd2.css rel=prefetch>
<link href=http://web-cdn.xxx.com/css/chunk-1025f268.0dc416de.css rel=prefetch>
<link href=http://web-cdn.xxx.com/js/app.84dcc9e6.js rel=preload as=script>
</head>
<body>
<div id=app></div>
<script src=http://web-cdn.xxx.com/js/chunk-vendors.614ecc0c.js></script>
<script src=http://web-cdn.xxx.com/js/app.84dcc9e6.js></script>
</body>
</html></code></pre>
<h2>阿里云CDN配置和上传</h2>
<p>接下来,我们要做的就是配置一个CDN,并能够把这些资源传上去。</p>
<p>首先,在阿里云上配置CDN,做好域名CNAME解析,并获取到阿里云的 <code>accessKeyId</code>、<code>accessKeySecret</code>、<code>Region</code>、<code>BucketName</code>等信息,然后选择一种语言,写好上传脚本。</p>
<p>这里我们以Node脚本为例:</p>
<pre><code>// oss-deploy.js
let OSS = require('ali-oss')
let fs = require('fs')
let client = new OSS({
region: 'oss-cn-hangzhou',
accessKeyId: 'xxx',
accessKeySecret: 'xxx',
bucket: 'xxx'
})
// 使用async+await方法,实现同步化,方便在失败后重试处理
async function put(fileName) {
try {
let result = await client.put(fileName, '../dist/' + fileName)
console.log('File Upload Success: ', fileName)
} catch (e) {
console.log('File Upload Failed: ', fileName)
// 这里省略异常/失败的重试
}
}
// 读取打包后的 dist 路径,按照原文件夹结构,进行上传
let readFileList = (path, filesList) => {
let files = fs.readdirSync(path)
files.forEach(itm => {
if (itm) {
let stat = fs.statSync(path + itm)
if (stat.isDirectory()) {
readFileList(path + itm + '/', filesList)
} else {
filesList.push(path + itm)
}
}
})
return filesList
}
let dist = readFileList('../dist/', [])
// 递归执行文件上传操作
let i = 0, l = dist.length
let uploadAsset = () => {
if (i < l) {
let name = dist[i].split('../dist/')[1]
put(name)
i++
uploadAsset()
}
}
uploadAsset()</code></pre>
<p>执行</p>
<pre><code>npm install --save-dev ali-oss
node oss-deploy.js</code></pre>
<p>即可看到文件已经被上传到了CDN路径下。</p>
<h2>持续集成</h2>
<p>上面的两个模块,已经实现了基本的CDN部署。但我们在项目开发的时候,肯定不希望每次 build完,都去自己执行上传CDN,再去服务器上部署。</p>
<p>这里我们再把 <code>TeamCity</code>上实现自动build、一键上线的流程简单阐述。</p>
<p>TeamCity上的执行脚本如下:</p>
<pre><code>cd /apps/kaleido-cms/
git pull -f origin master
npm install
npm run deploy
git add dist/*
git commit -m "Deploy"
git push origin master
cd /apps/kaleido-cms/deploy
node oss-deploy.js
ssh root@10.0.0.1 "./deploy_cms.sh"
ssh root@10.0.0.2 "./deploy_cms.sh"</code></pre>
<p>因为线上服务通常是集群模式,而 webpack在不同服务器执行build,会产生不同的哈希值版本号,会导致远程资源无法获取到。所以我们需要在持续集成部署的服务器上做build操作,生成dist路径,上传到git和cdn。最后再到集群的每个服务器上拉取静态文件即可。</p>
<hr>
<p><strong>补充:</strong></p>
<ol>
<li>在同一台服务器上,只要文件完全不变,我们使用vue-cli3构建生成的最终文件的哈希值版本号就不会产生改变。因此,对于用户来说当我们更新版本时,并不会对用户造成所有缓存文件失效的性能和体验影响。</li>
<li>在阿里云的CDN上,是使用协商缓存的ETag来进行文件资源缓存,因此重名新文件覆盖旧文件时,如文件内容完全一致,Etag也会保持一致,对用户来讲也不必担心缓存问题;如文件发生变更,用户协商缓存也将无法命中,就会取新的资源文件。</li>
<li>有些方法是把静态资源的请求发到Nginx,然后再转发到CDN地址。笔者认为,这样会造成所有资源需要重定向、并且在Nginx上无法设置缓存信息,性能上不如本文介绍的直接构建生成CDN地址的HTML文件的方法。</li>
</ol>
<p>通过这套操作,最终我们实现了在TeamCity上,一键执行打包、上传CDN、部署的整个流程。</p>
Web应用性能优化随笔
https://segmentfault.com/a/1190000018902483
2019-04-17T20:53:56+08:00
2019-04-17T20:53:56+08:00
LichKing24
https://segmentfault.com/u/lichking24
4
<h2>优化思路是什么?</h2>
<p><strong>BIG QUESTION: 当我们谈到一个web应用的性能优化,应该从哪些方面去考虑?</strong></p>
<p>思路就是,当我们去访问一个web应用的时候,都做了哪些操作?对应这些操作的,就是我们所能进行的优化的模块!</p>
<ol>
<li>浏览器请求DNS服务器,获取IP地址;</li>
<li>建立TCP连接;</li>
<li>浏览器发出详细请求,通常为HTTP(s)、WebSocket之类;</li>
<li>服务器响应请求,并返回数据;</li>
<li>浏览器渲染返回的数据到页面;</li>
<li>释放TCP连接;</li>
</ol>
<p>那么,我们接下来就按照这个流程,一步一步来看如何进行优化。</p>
<hr>
<h2>不好控制和不重要的优化内容</h2>
<h3>1. DNS</h3>
<p>首先,DNS查询的流程如下图所示</p>
<p><img src="/img/bVbrs8C?w=1800&h=121" alt="clipboard.png" title="clipboard.png"></p>
<p>当有浏览器缓存时,就不会去查询之后的步骤,有无缓存的对比如下图</p>
<p><img src="/img/bVbrs9e?w=1845&h=962" alt="clipboard.png" title="clipboard.png"></p>
<p>可以看到,<code>DNS Lookup</code>这一步,在有缓存之后就不会出现。</p>
<h3>2. TCP连接建立</h3>
<p>同样如上图所示,<code>Initial Connection</code> 所占用的时间即为TCP建立连接的耗时。由于TCP建立需要3次握手的操作,使其成为HTTP(s)协议通信耗时最长的过程。</p>
<p>针对这个过程,我们可以使用<code>HTTP Keep-Alive</code>机制,来保持TCP连接状态,这样可以使客户端不需要在每次http请求的时候都去与服务器建立TCP连接,可以节省掉很大的时间开支、提高服务器吞吐率。</p>
<p>之所以说这个也是不好控制和不重要的优化点,是因为现代浏览器已经默认支持 Keep-Alive,而常用的Web服务器也都可以对应进行支持。</p>
<p>如需手动设置,要注意 <code>timeout</code> 和 <code>max</code> 参数,保持时间太长的TCP连接,也会对服务器造成无用资源消耗。</p>
<h2>重要的优化内容</h2>
<h3>3. 浏览器请求和服务器响应</h3>
<p>针对请求和响应的优化,简而言之,就是:</p>
<ol>
<li>减少请求次数</li>
<li>降低请求耗时</li>
</ol>
<p>在这里,可做的优化点包括:</p>
<ol>
<li>CDN</li>
<li>压缩传输,即GZIP/Deflate</li>
<li>前端模块化</li>
<li>使用缓存</li>
<li>使用本地存储</li>
<li>负载均衡:Nginx/SLB</li>
</ol>
<h4>3.1 CDN</h4>
<p>CDN是Web应用优化的基础,也是最重要最影响用户体验的一个环节!<br>在项目中,需要注意将打包后的资源文件上传到CDN地址,并且在构建工具中配置相关CDN信息。</p>
<h4>3.2 数据压缩</h4>
<p>服务端与客户端的数据交互可通过压缩来进行优化,常用的压缩方式是 <code>GZip</code>和<code>Deflate</code>,本文以 <code>GZip</code>为例。<br><code>GZip</code>支持 HTML/CSS/JS/XML/PlainText等多种格式的压缩。</p>
<p>下图可看出是否使用压缩,对于传输文件大小的影响。</p>
<p><img src="/img/bVbrtly?w=2820&h=828" alt="clipboard.png" title="clipboard.png"></p>
<h4>3.3 前端模块化</h4>
<p>前端模块化便于文件管理、也更利于资源归类压缩,模块化大致经历了如下历程。</p>
<pre><code>script -> 闭包函数 -> AMD -> CMD -> CommonJS -> ES6</code></pre>
<p>而对应的构建工具,也有以下发展历程。</p>
<pre><code>Grunt/Gulp -> Browserify -> Webpack</code></pre>
<p>前端模块化详情可参见 <a href="https://segmentfault.com/a/1190000018511382?share_user=1030000018418598">《Web前端模块化方法》</a></p>
<h4>3.4 使用缓存</h4>
<p>当客户端使用服务端返回的内容时,可以通过缓存机制,减少请求传输次数。<br>缓存分为 <code>强制缓存</code>和<code>协商缓存</code> 两种。</p>
<p>强制缓存使用 <code>Expires</code> 和 <code>Cache-Control:max-age</code> 控制缓存有效时间。<br>协商缓存使用 <code>Last-Modified</code> 配合 <code>ETag</code> 控制缓存有效时间。</p>
<p>通常来讲,系统优先匹配强制缓存。<br>对于静态资源,系统需要做好相关缓存,避免重复请求。</p>
<h4>3.5 使用本地存储</h4>
<p>使用本地存储,可以在一定程度上减少服务器请求,也可以加快内容展示速度。</p>
<p>本地存储的使用有以下历程:</p>
<pre><code>Cookie+变量 -> WebStorage -> 单页面内存式存储 -> IndexDB</code></pre>
<p><strong>变量 + Cookie</strong>
</p>
<ul>
<li>Cookie 被设计来管理状态,但在简单应用中可存储数据
</li>
<li>4KB限制 + 域名绑定</li>
</ul>
<p><strong>单页面内存式存储</strong></p>
<ul>
<li>针对单页面应用设计的变量存储</li>
<li>如VueX</li>
</ul>
<p><strong>Web Storage</strong></p>
<ul>
<li>LocalStorage & SessionStorage</li>
<li>区别:生命周期、作用域</li>
</ul>
<p><strong>IndexDB</strong>
</p>
<ul><li>终极方案、NoSQL</li></ul>
<h3>3.6 负载均衡</h3>
<p>通常负载均衡包含4中模式:</p>
<p><img src="/img/bVbrttR?w=2550&h=978" alt="clipboard.png" title="clipboard.png"></p>
<h3>4. 浏览器渲染</h3>
<h4>4.1 Async和Defer</h4>
<p>对于资源文件的引入,使用 <code>async</code>和<code>defer</code>。<br><code>async</code> 没有固定顺序,即加载到文件就会引入;<br><code>defer</code>会按照dom顺序进行插入。</p>
<h4>4.2 懒加载</h4>
<p>内容的懒加载,如配合 vue-router的按需加载;<br>图片的懒加载,即需要展示时才去加载。</p>
<h4>4.3 图片优化</h4>
<p>图片优化是渲染层面能够最大程度提升性能的优化模块,也是操作起来最麻烦、需要投入精力最多的模块。</p>
<p>笔者认为大致分为三个方向:</p>
<ol>
<li>源:确保所有的图片都从CDN源获取</li>
<li>裁剪:使用正确大小的图片</li>
<li>格式:按需使用格式</li>
</ol>
<p>源和裁剪都比较明确,格式方面我们细说一下。</p>
<p>常用格式包括:</p>
<ol>
<li>JPG:<br> 体积小、有损压缩、不支持透明</li>
<li>PNG:<br> 体积大、无损压缩、支持透明</li>
<li>SVG:<br> 文本文件、体积小、不失真、兼容性好</li>
<li>BASE64:<br> 文本文件、依赖编码、小图标解决方案;<br> 需要少量小图标时可以使用此方案,需要大量小图标时建议使用CssSprites;</li>
<li>
<p>WebP<br> 新格式,支持动图、体积小;浏览器支持的兼容性差。</p>
<pre><code># 阿里在1688网站上的使用方法可以借鉴:
https://alicdn.com/xxx.jpg_xxx.webp</code></pre>
</li>
</ol>
<h4>4.4 Throttle & Debounce</h4>
<p><strong>节流(Throttle)</strong><br>
在某段时间内,不论触发了多少次回调,都只做第一次,并在结束时给出响应。</p>
<p><strong>防抖(Debounce)</strong><br>在某段时间内,不论触发了多少次回调,都只做最后一次,并在完成后给出响应。</p>
<p>节流和防抖的目标,都是减少执行,降低损耗,减少卡顿。</p>
<p>示例如下:</p>
<p>Throttle示例</p>
<pre><code>let flag = false
window.onscroll = () => {
if (flag) {
return
}
flag = true
setTimeout(()=>{
doSomething()
flag = false
})
}</code></pre>
<p>Debounce示例</p>
<pre><code><input onKeyUp="kp()">
var timer
var kp = ()=> {
clearTimeout(timer)
timer = setTimeout(()=> {
search()
}, 500)
}</code></pre>
AB Test 压力测试工具使用整理
https://segmentfault.com/a/1190000018710573
2019-03-30T10:50:53+08:00
2019-03-30T10:50:53+08:00
LichKing24
https://segmentfault.com/u/lichking24
1
<p>Apache Bench,是 Apache 自带的压力测试工具。a可以对服务器进行访问压力测试。<br>系统安装 Apache Server,自带 ab 命令。</p>
<h2>1. 概念</h2>
<ol>
<li>吞吐率:服务器并发处理能力的量化描述,单位是reqs/s,指的是某个并发用户数下单位时间内处理的请求数。某个并发用户数下单位时间内能处理的最大请求数,称之为最大吞吐率。</li>
<li>并发连接数:某一时刻服务器所接受的请求数(会话数)。</li>
<li>并发用户数:某一时刻服务器所接受的连接数,一个用户可能同时产生多个连接。</li>
<li>用户平均请求等待时间:总请求数 / 并发用户数。</li>
<li>服务器平均请求等待时间:处理完成所有请求数所花费的时间 / 总请求数。</li>
</ol>
<h2>2. 命令</h2>
<pre><code>➜ ~ ab
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
# 本次测试所发的总请求数
-n requests Number of requests to perform
#
-c concurrency Number of multiple requests to make at a time
-t timelimit Seconds to max. to spend on benchmarking
This implies -n 50000
-s timeout Seconds to max. wait for each response
Default is 30 seconds
-b windowsize Size of TCP send/receive buffer, in bytes
-B address Address to bind to when making outgoing connections
-p postfile File containing data to POST. Remember also to set -T
-u putfile File containing data to PUT. Remember also to set -T
-T content-type Content-type header to use for POST/PUT data, eg.
'application/x-www-form-urlencoded'
Default is 'text/plain'
-v verbosity How much troubleshooting info to print
-w Print out results in HTML tables
-i Use HEAD instead of GET
-x attributes String to insert as table attributes
-y attributes String to insert as tr attributes
-z attributes String to insert as td or th attributes
-C attribute Add cookie, eg. 'Apache=1234'. (repeatable)
-H attribute Add Arbitrary header line, eg. 'Accept-Encoding: gzip'
Inserted after all normal header lines. (repeatable)
-A attribute Add Basic WWW Authentication, the attributes
are a colon separated username and password.
-P attribute Add Basic Proxy Authentication, the attributes
are a colon separated username and password.
-X proxy:port Proxyserver and port number to use
-V Print version number and exit
-k Use HTTP KeepAlive feature
-d Do not show percentiles served table.
-S Do not show confidence estimators and warnings.
-q Do not show progress when doing more than 150 requests
-l Accept variable document length (use this for dynamic pages)
-g filename Output collected data to gnuplot format file.
-e filename Output CSV file with percentages served
-r Don't exit on socket receive errors.
-m method Method name
-h Display usage information (this message)
-I Disable TLS Server Name Indication (SNI) extension
-Z ciphersuite Specify SSL/TLS cipher suite (See openssl ciphers)
-f protocol Specify SSL/TLS protocol
(TLS1, TLS1.1, TLS1.2 or ALL)</code></pre>
<h2>3. 测试示例</h2>
<h2>3.1 基础访问测试</h2>
<pre><code>ab -n 1000 -c 10 http://cms.test.com/</code></pre>
<p>测试结果如下</p>
<pre><code>Server Software:
Server Hostname: cms.kaleidoapp.cn
Server Port: 80
Document Path: /
Document Length: 2746 bytes
Concurrency Level: 10
Time taken for tests: 8.352 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 2981000 bytes
HTML transferred: 2746000 bytes
Requests per second: 119.74 [#/sec] (mean)
Time per request: 83.517 [ms] (mean)
Time per request: 8.352 [ms] (mean, across all concurrent requests)
Transfer rate: 348.57 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 29 39 11.2 37 173
Processing: 33 43 12.0 42 190
Waiting: 32 43 12.0 41 189
Total: 62 82 17.8 79 226
Percentage of the requests served within a certain time (ms)
50% 79
66% 84
75% 89
80% 90
90% 93
95% 96
98% 135
99% 197
100% 226 (longest request)</code></pre>
<p><strong>结果可以看出:</strong></p>
<ol>
<li>吞吐率:119.74</li>
<li>整个测试持续的时间:8.352s</li>
<li>完成的请求数:1000</li>
<li>失败的请求数:0</li>
<li>总的网络传输量:2981000 bytes</li>
<li>HTML内容传输量:2746000 bytes</li>
<li>用户平均请求等待时间:83.517 ms</li>
<li>服务器平均请求处理时间:8.352 ms</li>
<li>平均每秒网络上的流量:348.57 kb</li>
</ol>
<p><strong>网络消耗时间分解</strong></p>
<p>展示最小、平均、方差、中位值、最大值。</p>
<pre><code>Connection Times (ms)
min mean[+/-sd] median max
Connect: 29 39 11.2 37 173
Processing: 33 43 12.0 42 190
Waiting: 32 43 12.0 41 189
Total: 62 82 17.8 79 226</code></pre>
<p><strong>请求处理时间分布</strong></p>
<p>可以看出,95% 的请求用时在 95ms 以内,最长请求时间为 226ms。</p>
<pre><code>Percentage of the requests served within a certain time (ms)
50% 79
66% 84
75% 89
80% 90
90% 93
95% 96
98% 135
99% 197
100% 226 (longest request)</code></pre>
<h3>3.2 接口请求</h3>
<pre><code>ab -n 1000 -c 10 -p 'list.json' -T 'application/json' -H 'Authorization: token' -s 10 http://api.test.com/app/v1/list</code></pre>
<p>说明:</p>
<pre><code>-p 代表的是入参的 json 文件,或是 text 的键值对
-T 代表 contentType
-H 代表的是 http header 信息
-s 代表的是超时时间,单位是秒,默认是 30s</code></pre>
Web前端模块化方法
https://segmentfault.com/a/1190000018511382
2019-03-14T23:17:53+08:00
2019-03-14T23:17:53+08:00
LichKing24
https://segmentfault.com/u/lichking24
1
<h2>前端模块化</h2>
<h3>1. 模块化优点</h3>
<p>目前由于MVVM模式的流行,各种语言都更注重模块化。模块化设计的好处:</p>
<ol>
<li>作用域:避免全局变量污染;</li>
<li>复用:可重复利用封装的模块为不同地方实现相同功能;</li>
<li>解耦:减少不同功能代码相互关联性,让代码分工明确,也方便Debug;</li>
<li>按需加载:加快加载速度、异步加载不必要的部分;</li>
</ol>
<h3>2. 模块化方法整理</h3>
<p>从最早的script标签开始,前端模块化经过了多种编程方案的演化,逐步完善。</p>
<h4>2.1 script标签</h4>
<pre><code><script src="module_a.js"></script>
<script src="module_b.js"></script>
<script src="main.js"></script></code></pre>
<p>所有的js文件共享全局作用域,容易引起作用域污染。</p>
<h4>2.2 闭包函数(立即执行函数)</h4>
<pre><code>(function(){
// xxx
})()</code></pre>
<p>这是笔者早年使用最多的js编程方式 0_0</p>
<p>虽然避免了作用域污染的问题,但多个文件内的函数互相调用时,处理较为麻烦。常用方法是:</p>
<ol>
<li>在闭包内定义一个 <code>window.myFunc = function(){}</code> 的方法,在闭包外可以调用;</li>
<li>使用事件监听,给需要外部调用的方法设置事件,从外部触发事件;介绍一种自定义事件来控制闭包函数间传值的方法:</li>
</ol>
<pre><code>// 利用闭包函数自定义一个事件监听触发机制
// 自定义机制,不会受到默认的事件影响
var EventManager = (function() {
var events = {}
return {
add: function(name, fn) {
if(!events[name]){
events[name] = [];
}
events[name].push(fn);
},
fire: function(name, args) {
var fnList = events[name];
if(fnList){
for (var i = 0, l = fnList.length; i < l; i++) {
var fn = fnList[i];
if (fn && typeof fn == 'function') {
fn(args);
}
}
}
}
}
})()
// 在闭包内监听,可调用闭包内方法
(function() {
EventManager.add('user.login', function(data) {
console.log('user.login', data)
})
})()
// 在任意位置触发
EventManager.fire('user.login', {name: 'lc'})</code></pre>
<p>自定义事件监听,相对于window下的函数,更加灵活,也更符合模块化的思想。举个例子:<br>通信系统中,用户登录后,需要获取聊天记录、通信录、个人信息,这些分别在不同的模块(闭包)中。<br>如果用函数的思想,需要在登录的地方进行不同的方法调用,这样就使得登录模块与多个业务模块产生了耦合;如果用自定义事件的方法,登录后只需要广播一个事件,同时在多个业务模块分别监听事件,各模块间就完全没有耦合,就算任意删掉一个模块也可以保证其他模块正常运行。</p>
<p><strong>存在问题:</strong></p>
<p>不论是全局函数、全局事件、自定义事件,在调用每个闭包中的方法时,斗需要确保该闭包先执行后调用。在复杂项目中,需要先执行大量闭包函数,会导致启动慢、逻辑复杂等各种问题。</p>
<h4>2.3 AMD - 异步模块定义</h4>
<pre><code>define('myfunc', ['math'], function(math) {
math.sum(1, 2)
});</code></pre>
<p>通过 <code>define</code>函数引入需要的依赖包,每个模块所依赖的包/模块一目了然。</p>
<h4>2.4 CMD - 通用模块定义</h4>
<pre><code>define(function(require, exports, module) {
const math = require('math')
math.sum(1, 2)
})</code></pre>
<p>CMD的原则是将引入模块尽量后置,在使用的时候才去引入。<br>这样使得js执行时效率更高,更符合lazy load的思维方式,但对代码管理确不是很方便。</p>
<h4>2.5. CommonJS</h4>
<pre><code>const math = require('math')
module.exports = function() {
math.sum(1, 2)
}</code></pre>
<p>目前NodeJS的模块管理常用的就是这种方式,很多NPM的包也是这样处理的模块引入。</p>
<h4>2.6 ES6的模块化</h4>
<pre><code>import { myFunc1, myFunc2 } from 'myFuncs';
import Vue from 'vue';
export function hello() {};
export default {
// ...
};</code></pre>
<p>Vue、React等常用框架目前都在使用这种模块化方法。</p>
<p>比如在vue中,配合vue-router,在组件中按需import模块或模块中的函数,可以通过webpack实现按需加载。同时,这种模块化方式使得模块间的方法调用更加方便,不需要考虑模块声明前后顺序,因为webpack会自动生成依赖树。</p>
<h4>2.7 样式文件模块化</h4>
<pre><code>// util.less
.common {
color: pink;
}
// main.less
@import 'util.less'
.red {
color: red;
}</code></pre>
<p>样式文件目前也支持模块化。</p>
<hr>
<p>参考:《深入浅出Webpack》</p>
Redis Sentinel 哨兵模式
https://segmentfault.com/a/1190000018478725
2019-03-12T18:46:44+08:00
2019-03-12T18:46:44+08:00
LichKing24
https://segmentfault.com/u/lichking24
2
<h2>Redis Sentinel 哨兵模式</h2>
<h3>1.网络架构</h3>
<p>网络结构如下,通过Sentinel监控Master和Slave服务器:</p>
<p><img src="/img/bVbpHjS?w=792&h=944" alt="clipboard.png" title="clipboard.png"></p>
<h3>2.配置启动</h3>
<p>配置文件如下:</p>
<pre><code>vi /etc/redis-sentinel.conf</code></pre>
<pre><code>port 26379
dir "/tmp"
logfile "/var/log/redis/sentinel_20086.log"
# 进程守护
daemonize yes
# 格式:sentinel <option_name> <master_name> <option_value>
# 最后的一个2代表在sentinel集群中,多少个节点认为master死了,才能真正认为该master不可用
sentinel monitor T1 127.0.0.1 6379 2
# sentinel会向master发送心跳确认存活
# 如果master在“一定时间范围”内不回应PONG 或者是回复了一个错误消息
# 那么这个sentinel会主观地认为这个master已经不可用了
# (subjectively down, 也简称为SDOWN)。
# down-after-milliseconds 用来指定这个“一定时间范围”,单位是毫秒,默认30秒。
sentinel down-after-milliseconds T1 15000
# failover过期时间。
# 当failover开始后,在此时间内仍然没有触发任何failover操作,当前sentinel将会认为此次failoer失败。
# 默认180秒,即3分钟。
sentinel failover-timeout T1 120000
# 在发生failover时,这个选项指定了最多可以有多少个slave同时对新的master进行同步。
# 这个数字越小,完成failover所需的时间就越长;
# 这个数字越大,就意味着越多的slave因为replication而不可用。
# 可以通过将这个值设为 1 来保证每次只有一个slave处于不能处理命令请求的状态。
sentinel parallel-syncs T1 1
# sentinel 连接密码验证
# sentinel auth-pass <master_name> xxxxx
# 发生切换之后执行的一个自定义脚本
# sentinel notification-script <master-name> <script-path>
# sentinel client-reconfig-script <master-name> <script-path></code></pre>
<p>Sentinel 也需要集群化,以确保:</p>
<ol>
<li>某个/些 Sentinel节点挂了,仍然可以实现Redis主从切换;</li>
<li>Redis客户端可以通过任意一个Sentinel读取/写入信息。</li>
</ol>
<p><strong>启动Sentinel:</strong></p>
<pre><code>redis-sentinel /path/to/sentinel.conf</code></pre>
<p>启动后,通过Sentinel的自动识别,配置文件中会自动加上已识别的Redis集群节点和Sentinel集群节点。</p>
<h3>3.实现流程</h3>
<ol>
<li>Sentinel集群通过配置文件发现master,启动时会监控master;</li>
<li>向master发送info命令,获取其所有slave节点;</li>
<li>Sentinel集群向Redis主从服务器发送hello信息(心跳),包括Sentinel本身的ip、端口、id等内容,以此来向其他Sentinel宣告自己的存在;</li>
<li>Sentinel集群通过订阅接收其他Sentinel发送的hello信息,以此来发现监视同一个主服务器的其他Sentinel;集群之间会互相创建命令连接用于通信,因为已经有主从服务器作为发送和接收hello信息的中介,Sentinel之间不会创建订阅连接;</li>
<li>Sentinel集群使用ping命令来检测实例的状态,如果在指定的时间内(down-after-milliseconds)没有回复或则返回错误的回复,那么该实例被判为下线;</li>
<li>当failover主备切换被触发后,并不会马上进行,还需要Sentinel中的大多数sentinel授权后才可以进行failover,即进行failover的Sentinel会去获得指定quorum个的Sentinel的授权,成功后进入ODOWN状态。如在5个Sentinel中配置了2个quorum,等到2个Sentinel认为master死了就执行failover。</li>
<li>Sentinel向选为master的slave发送 SLAVEOF NO ONE 命令,选择slave的条件是Sentinel首先会根据slaves的优先级来进行排序,优先级越小排名越靠前。如果优先级相同,则查看复制的下标,哪个从master接收的复制数据多,哪个就靠前。如果优先级和下标都相同,就选择进程ID较小的。</li>
<li>Sentinel被授权后,它将会获得宕掉的master的一份最新配置版本号(config-epoch),当failover执行结束以后,这个版本号将会被用于最新的配置,通过广播形式通知其它sentinel,其它的sentinel则更新对应master的配置。</li>
</ol>
<blockquote>注意:<br>因为redis采用的是异步复制,没有办法避免数据的丢失。<br>可以在redis.conf通过以下配置来使得数据不会丢失:</blockquote>
<pre><code>// master最少得有多少个健康的slave存活才能执行写命令
min-slaves-to-write 1
// 延迟小于min-slaves-max-lag秒的slave才认为是健康的slave
min-slaves-max-lag 10</code></pre>
<blockquote>当所有Slave都不符合条件时,master将停止写入。</blockquote>
<h3>4.故障转移</h3>
<p>发生故障转移时,需要进行领导者选举。</p>
<ul>
<li>sentinel首先会根据slaves的优先级来进行排序,优先级越小排名越靠前;</li>
<li>如果优先级相同,则查看复制的下标,哪个从master接收的复制数据多,哪个就靠前;</li>
<li>如果优先级和下标都相同,就选择RunID较小的那个;</li>
</ul>
<p>如果一个redis的slave优先级配置为0,那么它将永远不会被选为master。</p>
<p><strong>故障转移过程</strong></p>
<p>领导者Sentinel需要将一个salve提升为master,此slave必须为状态良好,不能处于SDOWN/ODOWN状态。</p>
<ol>
<li>“+failover-triggered”: Leader开始进行failover,此后紧跟着“+failover-state-wait-start”,wait数秒。</li>
<li>“+failover-state-select-slave”: Leader开始查找合适的slave</li>
<li>“+selected-slave”: 已经找到合适的slave</li>
<li>“+failover-state-sen-slaveof-noone”: Leader向slave发送“slaveof no one”指令,此时slave已经完成角色转换,此slave即为master</li>
<li>“+failover-state-wait-promotition”: 等待其他sentinel确认slave</li>
<li>“+promoted-slave”:确认成功</li>
<li>“+failover-state-reconf-slaves”: 开始对slaves进行reconfig操作</li>
<li>“+slave-reconf-sent”:向指定的slave发送“slaveof”指令,告知此slave跟随新的master</li>
<li>“+slave-reconf-inprog”: 此slave正在执行slaveof + SYNC过程,如过slave收到“+slave-reconf-sent”之后将会执行slaveof操作。</li>
<li>“+slave-reconf-done”: 此slave同步完成,此后leader可以继续下一个slave的reconfig操作。循环 step 7</li>
<li>“+failover-end”: 故障转移结束</li>
<li>“+switch-master”:故障转移成功后,各个sentinel实例开始监控新的master。</li>
</ol>
<h3>5.增加和删除节点</h3>
<p>Sentinel会通过PUB/SUB自动识别新增节点。</p>
<p>删除流程:</p>
<ol>
<li>停止所要删除的sentinel</li>
<li>发送一个SENTINEL RESET <em> 命令给所有其它的sentinel实例,如果你想要重置指定master上面的sentinel,只需要把</em>号改为特定的名字,注意,需要一个接一个发,每次发送的间隔不低于30秒(down-after-milliseconds);</li>
<li>检查一下所有的sentinels是否都有一致的当前sentinel数;</li>
</ol>
<hr>
<p>参考资料:<br><a href="https://link.segmentfault.com/?enc=BR0bEC9FF%2BAQZ1cOKxVDxA%3D%3D.wn6DVtqxZXV3cTjO2Xk6OpjQdyets1BOqtgpYFs2l2kH7vFAEMnzeHPil5IXDKlyXeKeGsK1lBU687a%2FV%2FfAgg%3D%3D" rel="nofollow">https://www.cnblogs.com/zhouj...</a><br><a href="https://link.segmentfault.com/?enc=FqNmYhowLCdiJGStWTg4aQ%3D%3D.CfmTxIw3%2FwgKx8EaRvrRReivks%2B4ScnxeyfBVZCys94n%2FNAPggMYXcZxB5q96U8Y" rel="nofollow">http://www.cnblogs.com/zhouji...</a><br><a href="https://segmentfault.com/u/beanlam">https://segmentfault.com/u/be...</a></p>
Redis的主从复制
https://segmentfault.com/a/1190000018438424
2019-03-08T17:57:45+08:00
2019-03-08T17:57:45+08:00
LichKing24
https://segmentfault.com/u/lichking24
1
<h2>Redis的主从复制</h2>
<h3>1.主从复制</h3>
<p>Redis配置成主从模式,主库(Master)只负责写数据,从库(Slave)只负责读数据。</p>
<blockquote>注意:<br>一个主库可以拥有多个从库,但一个从库只能隶属于一个主库。</blockquote>
<p>将选为Slave的机器上配置:</p>
<pre><code># REPLICATION
slave of <master ip> <port>
masterauth <password> </code></pre>
<p>开启主从关系后,数据会自动进行复制。</p>
<h3>2.全量复制和部分复制</h3>
<p>Redis的复制方式包括全量复制和部分复制。</p>
<h4>2.1 全量复制</h4>
<p>流程如下:</p>
<ol>
<li>Slave发出一个同步命令,要求Master同步数据;</li>
<li>Master向Salve发送runid和offset;当Slave上没有offset记录时,执行全量复制;</li>
<li>Slave执行save masterinfo,保存Master的基本信息;</li>
<li>Master执行bgsave生成快照;</li>
<li>Master执行send RDB将快照发送到Slave的缓冲区;</li>
<li>Slave更新旧的RDB文件,并加载新的RDB;</li>
</ol>
<p>由上述流程可以看到,全量复制的开销主要在:</p>
<ol>
<li>生成RDB文件,即bgsave;</li>
<li>RDB服务器间传输;</li>
<li>如果有AOF设置,达到重写阈值,会进行AOF重写;</li>
</ol>
<h4>2.2 部分复制</h4>
<p>部分复制在Redis 2.8之后开始支持,可以减少全量复制的开销。</p>
<p>部分复制的原理:</p>
<ol>
<li>每台机器启动后都会有一个与当前进程相关的 <code>runid</code>;</li>
<li>每个机器在写入数据后会有一个偏移量 <code>master_repl_offset</code>;</li>
<li>通过偏移量来判断Slave与Master直接的数据差多少;</li>
</ol>
<p>部分复制常用在主从连接断开、Master抖动时。</p>
<p>流程如下:</p>
<ol>
<li>主从连接断开;</li>
<li>Slave尝试连接主机;Master写入复制缓冲区 <code>repl_back_buffer</code>;</li>
<li>恢复连接后,Slave将自己当前runid和偏移量传输给Master,并请求同步数据;</li>
<li>Master检查偏移量是否在缓冲区范围内,如果是,则进行部分复制;如果不是,则进行全量复制;</li>
</ol>
<blockquote>优点:<br>部分复制直接使用缓冲区的数据进行RDB同步,相比全量复制,减少了RDB的生成和传输开销;同时,也减少了AOF重写阈值达到的几率。</blockquote>
<h3>3.主从复制中的问题</h3>
<h4>3.1 读写分离的问题</h4>
<ul>
<li>
<p>复制数据延迟:Slave 延迟导致读写不一致。</p>
<ul><li>监控偏移量 offset,如果超出范围就将读节点切换到 Master 上,并重新全量复制Slave节点。</li></ul>
</li>
<li>
<p>读到过期数据:Redis 采用懒惰性策略和采样式策略。</p>
<ul>
<li>懒惰性策略指 Redis 只有当操作 key时才去看数据是否过期;采样式策略指定期会去采样,如果是过期的,就自动删除。当过期数量非常多的时候,采样速度比不上逻辑数据变化的速度,Slave 没有写权限,只有 Master 可以删除,就会出现过期数据。</li>
<li>Redis 3.2 以上版本修复此问题。</li>
</ul>
</li>
<li>
<p>Slave 节点故障</p>
<ul><li>Slave 节点通过持久化数据与主节点进行部分复制同步;Redis2.8 实现 Slave 恢复后部分复制同步;</li></ul>
</li>
<li>
<p>Master 节点故障</p>
<ul>
<li>需要手动切换主从关系;</li>
<li>使用 Redis哨兵模式自动完成主从切换;</li>
</ul>
</li>
</ul>
<h4>3.2 复制风暴</h4>
<p>Master 重启,多个 Slave 会需要复制。这个时候需要更换复制拓扑,通过在 Slave 下再分从机,减少主机 Master 的压力。</p>
<p><img src="/img/bVbpwN5?w=520&h=284" alt="clipboard.png" title="clipboard.png"></p>
<h3>4. 配置文件说明</h3>
<pre><code>################################# REPLICATION #################################
# 复制选项,slave复制对应的master。
slaveof <masterip> <masterport>
# 如果master设置了requirepass,那么slave要连上master,需要有master的密码才行。
# masterauth就是用来配置master的密码,这样可以在连上master后进行认证。
# masterauth <master-password>
# 当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:
# 1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。
# 2) 如果slave-serve-stale-data设置为no,除去INFO和SLAVOF命令之外的任何请求都会返回一个错误”SYNC with master in progress”。
slave-serve-stale-data yes
# 作为从服务器,默认情况下是只读的(yes)
slave-read-only yes
# 是否使用socket方式复制数据。
# 目前redis复制提供两种方式,disk和socket。
# 如果新的slave连上来或者重连的slave无法部分同步,就会执行全量同步,master会生成rdb文件。
# 有2种方式:
# 1.disk方式是master创建一个新的进程把rdb文件保存到磁盘,再把磁盘上的rdb文件传递给slave。
# 2.socket是master创建一个新的进程,直接把rdb文件以socket的方式发给slave。
# disk方式的时候,当一个rdb保存的过程中,多个slave都能共享这个rdb文件。
# socket的方式就的一个个slave顺序复制。
# 在磁盘速度缓慢,网速快的情况下推荐用socket方式。
repl-diskless-sync no
# diskless复制的延迟时间,防止设置为0。
# 一旦复制开始,节点不会再接收新slave的复制请求直到下一个rdb传输。
# 所以最好等待一段时间,等更多的slave连上来。
repl-diskless-sync-delay 5
# slave根据指定的时间间隔向服务器发送ping请求。
# 时间间隔可以通过 repl_ping_slave_period 来设置,默认10秒。
# repl-ping-slave-period 10
# 复制连接超时时间。
# master和slave都有超时时间的设置。
# master检测到slave上次发送的时间超过repl-timeout,即认为slave离线,清除该slave信息。
# slave检测到上次和master交互的时间超过repl-timeout,则认为master离线。
# 需要注意的是repl-timeout需要设置一个比repl-ping-slave-period更大的值,不然会经常检测到超时。
# repl-timeout 60
# 是否禁止复制tcp链接的tcp nodelay参数,可传递yes或者no。
# 默认是no,即使用tcp nodelay。
# 如果master设置了yes来禁止tcp nodelay设置,在把数据复制给slave的时候,会减少包的数量和更小的网络带宽。
# 但是这也可能带来数据的延迟。
# 默认我们推荐更小的延迟,但是在数据量传输很大的场景下,建议选择yes。
repl-disable-tcp-nodelay no
# 复制缓冲区大小,这是一个环形复制缓冲区,用来保存最新复制的命令。
# 这样在slave离线的时候,不需要完全复制master的数据,如果可以执行部分同步,只需要把缓冲区的部分数据复制给slave,就能恢复正常复制状态。
# 缓冲区的大小越大,slave离线的时间可以更长,复制缓冲区只有在有slave连接的时候才分配内存。
# 没有slave的一段时间,内存会被释放出来,默认1m。
# repl-backlog-size 5mb
# master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。
# 单位为秒。
# repl-backlog-ttl 3600
# 当master不可用,Sentinel会根据slave的优先级选举一个master。
# 最低的优先级的slave,当选master。
# 而配置成0,永远不会被选举。
# 注意:要实现Sentinel自动选举,至少需要2台slave。
slave-priority 100
# redis提供了可以让master停止写入的方式,如果配置了min-slaves-to-write,健康的slave的个数小于N,mater就禁止写入。
# master最少得有多少个健康的slave存活才能执行写命令。
# 这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。
# 设置为0是关闭该功能,默认也是0。
# min-slaves-to-write 3
# 延迟小于min-slaves-max-lag秒的slave才认为是健康的slave。
# min-slaves-max-lag 10</code></pre>
Redis常用操作整理
https://segmentfault.com/a/1190000018426268
2019-03-07T19:11:33+08:00
2019-03-07T19:11:33+08:00
LichKing24
https://segmentfault.com/u/lichking24
1
<blockquote>本文主要整理一些在以往开发中用到过及可能用到的功能,没有涉及的功能暂不做整理。</blockquote>
<h2>1.基本数据类型</h2>
<h3>1.1 String</h3>
<ul>
<li>GET、SET、DEL</li>
<li>SETNX、SET xx</li>
<li>MSET、MGET</li>
<li>GETSET、APPEND</li>
<li>STRLEN</li>
<li>INCR、DECR、INCRBY、DECRBY、INCRBYFLOAT</li>
<li>SETRANGE、GETRANGE</li>
</ul>
<blockquote>
<p>注意:</p>
<ol>
<li>尽量使用MGET而不是GET,减少网络请求时间。</li>
<li>存在与数据库同步时,避免在直接在Redis计算。</li>
</ol>
</blockquote>
<h3>1.2 Hash</h3>
<ul><li>HSET、HGET、HGETALL</li></ul>
<h3>1.3 List</h3>
<ul>
<li>LPUSH、RPUSH</li>
<li>LPOP、RPOP</li>
<li>LRANGE</li>
</ul>
<blockquote>
<p>注意:</p>
<ol>
<li>List是有序字符串集合,实际开发可用于排行榜之类功能,也可用于消息队列。</li>
<li>List中可以出现重复数据。</li>
</ol>
</blockquote>
<h3>1.4 Set</h3>
<ul>
<li>SADD、SPOP</li>
<li>SCARD、SMEMBERS</li>
</ul>
<blockquote>
<p>注意:</p>
<ol>
<li>Set是无需集合</li>
<li>集合成员是唯一的,不会出现重复数据</li>
</ol>
</blockquote>
<h3>1.5 Sorted Set</h3>
<ul>
<li>ZADD</li>
<li>ZRANGE</li>
<li>ZREM</li>
<li>ZCARD</li>
</ul>
<blockquote>注意:<br>用于构造一个有序、但数据不重复的集合</blockquote>
<h2>2. PUB/SUB(发布/订阅)</h2>
<ul>
<li>SUBSCRIBE、UNSUBSCRIBE</li>
<li>PSUBSCRIBE、PUNSUBSCRIBE</li>
<li>PUBLISH</li>
</ul>
<blockquote>
<p>注意:</p>
<ol>
<li>Redis客户端可以订阅任意数量的频道。</li>
<li>客户端只能消费订阅之后发布的消息,一个消息可以被多个订阅者消费</li>
</ol>
</blockquote>
<h2>3. 事务(Transactions)</h2>
<ul>
<li>MULTI</li>
<li>EXEC</li>
<li>DISCARD</li>
<li>WATCH key:监视 key,如果在事务执行之前 key 被其他命令所改动,那么事务将被打断。</li>
<li>UNWATCH:取消 WATCH 命令对所有 key 的监视;</li>
</ul>
<blockquote>注意:<br>Redis的事务不具备一致性,EXEC执行后,若事务中断,已经执行的部分不会回滚。</blockquote>
<h2>4. 慢查询</h2>
<ul>
<li>SLOWLOG GET</li>
<li>SLOWLOG LEN</li>
<li>SLOWLOG RESET</li>
</ul>
<blockquote>
<p>注意:</p>
<ol>
<li>慢查询时间不包括命令队列时间</li>
<li>常用在请求超时,可通过慢查询日志看是否有级联阻塞</li>
</ol>
</blockquote>
<h2>5. 管道(Pipeline)</h2>
<blockquote>注意:<br>Pipeline只能在单节点上执行</blockquote>
<h2>6. 地理位置(GEO)</h2>
<ul>
<li>GEO ADD key 经度 维度 标识 ...</li>
<li>GEOPOS key 标识</li>
<li>GEODIST key 标识1 标识2 距离单位</li>
<li>GEORADIUS</li>
</ul>
<blockquote>
<p>注意:</p>
<ol>
<li>可用于摇一摇、同城约会之类的,可计算范围内的人</li>
<li>可用于范围内固定目标,比如饭店、滑雪场</li>
<li>使用GEORADIUS的排序,做实时距离相关的比赛功能</li>
</ol>
</blockquote>
<h2>7. 数据持久化(RDB & AOF)</h2>
<p>持久化方式有两种:RDB快照 和 AOF日志。</p>
<h3>7.1 RDB</h3>
<p>RDB快照有三种触发机制:</p>
<ol>
<li>save命令,执行同步快照</li>
<li>bgsave命令,执行新线程,生成快照</li>
<li>自动触发,执行新线程,生成快照</li>
</ol>
<p>一般使用bgsave或自动快照。自动快照配置如下:</p>
<pre><code># /etc/redis.conf
// RDB
// 每500秒有超过100次key被修改就执行快照
save 500 100 </code></pre>
<ul>
<li>Redis 调用 fork() 进程,同时拥有父进程和子进程;</li>
<li>子进程将数据都写到一个临时 RDB 文件之中;</li>
<li>当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换旧的 RDB 文件;</li>
</ul>
<p>生产环境中,RDB适合冷备份,其他适合建议使用AOF。</p>
<h3>7.2 AOF</h3>
<p>AOF 三种策略分别是 always 、everysec 和 no。</p>
<ol>
<li>always:每条命令都写入 AOF 文件中,保证不丢失;</li>
<li>everysec:每秒将缓冲区数据写入一次,可能丢失1秒数据;</li>
<li>no:操作系统决定什么时候写入;</li>
</ol>
<blockquote>注意:<br>在写入的时候,AOF会压缩命令。</blockquote>
<p><strong>AOF重写</strong></p>
<p><code>auto-aof-rewrite-min-size</code>:配置最小尺寸,超过就进行重写。<br><code>auto-aof-rewrite-percentage</code>:指当前AOF文件比上次重写的增长比例大小。</p>
<p>AOF 重写即 AOF 文件在一定大小之后,重新将整个内存写到 AOF 文件当中,以反映最新的状态(相当于 bgsave)。这样就避免了 AOF 文件过大而实际内存数据小的问题(频繁修改数据问题)。</p>
<pre><code># AOF 配置如下
# 默认是 no
appendonly yes
# 设置 AOF 名字
appendfilename "aof-${ip}-${port}.aof"
# 每秒同步
appendfsync everysec
# AOF文件目录
dir /diskpath
# 重写AOF时是否持续记录新的AOF日志
# 设置为 no,不会丢数据,但可能造成线程阻塞
# 设置为 yes,有可能丢数据,但不会造成阻塞和系统延迟
no-appendfsync-on-rewrite yes </code></pre>
<h3>7.3 数据持久化常见问题</h3>
<p><strong>环境开销</strong></p>
<ul>
<li>fork进程消耗大量内存;</li>
<li>AOF和RDB生成,属于CPU密集型,不与CPU密集型业务一起部署;</li>
<li>AOF和RDB写入会占用硬盘,使用iotop分析硬盘状态;不和数据库、消息队列等一起部署;使用SSD硬盘。</li>
</ul>
<p><strong>AOF追加阻塞</strong></p>
<pre><code># 查看阻塞次数
# 如阻塞较多,要调整业务实现或者调整AOF策略
redis-cli info resistence</code></pre>