好久不见,我想和你聊聊!

沉寂了好长时间,很多粉丝邮件问我最近在干啥,是不是转行了?倒是没有转行,只是现在退居幕后了,毕竟是年轻人的时代。既然来了,那就回顾下吧! 时间线 1、2021.9 - 2022.12,进入某互联网大厂,专注前端工程化,做出了基于大前端的DevOps基建。也算是填补了前端架构、工程化方向的空白。回想起来,从那会微前端、低代码、BFF、协同编辑,到工程化,再到架构,也算是完整见证了前端的发展。 2、2023.5 - 2025.5,跟随者AI时代的变革,投入了AI项目,从每周AI资讯,到AI会话基建,再到AI Coding应用,最后到出版AI作品,每一步都写满了故事。历史的长河是惊人的相似,又经历了一次从「人潮拥挤」到「回归平淡」的过程,就像AI的发展:短期被高估,长期被低估。这一步可能是我未来要坚定走下去的! 3、2025.5 - 至今,随着对AI的深入了解,开始喜欢创新类的事情。恰巧关注到3D打印,从此一发不可收拾,业余时间,搞出了很多经典好玩的艺术品,这条路我还会持续探索。如果你也有兴趣,不妨携手前行。 感悟:无从选择才是「贫穷」 年轻人最大的财富不是赚了多少钱,而是做了多少探索,有选择的资本。 利用业余时间折腾了两个公众号,一个搞AI,另一个还是搞AI。回顾起来,为什么走上了AI+3D打印这条路,其实也是从写公众号文章中找到的思路。不断的写,不断的思考,不断地交流,这才是我最大的收获! 1、AI公众号:「2AGI之路」 DeepSeek+PS 帮你批量制作表情包,躺着挣点小钱儿~ DeepSeek+剪映:5分钟打造全网爆火儿童绘本故事~ AI “硬核”音箱来袭!用 ESP32-S3 开发板打造下一代智能陪伴玩具(保姆教程) 10W+爆款 AI 流水线:Coze深度写作×DeepSeek算法洞察×HTML极速排版 Manus 二度来袭,三个实用案例一探深浅! 2、AI+3D打印公众号:「涛哥玩AI」 小白也能轻松挣钱!AI+DeepSeek+3D打印,让创意瞬间落地 孩子手绘画也能变现?AI 3D建模让创意价值飙升! 从脑洞大开到惨不忍睹:我用AI把《仙逆》主角整成了“塑料人” AI冰淇淋爆火:小红书博主如何靠创意狂吸几十万点赞?(附完整提示词) 打印前不用建模?拓竹隐藏技能曝光:AI一键生成3D模型,创意直接起飞! 两本代表作 如果你对AI感兴趣,欢迎关注我的实践书籍《AIGC大语言模型轻松学》,京东、当当各大平台热卖中~ 如果你对工程化比较关注,这本《Docker实战派》可能会帮到你。 您的浏览器不支持播放该视频! 本书已入选『自学入门的编程书 TOP 榜』,京东、当当,热卖中! 怎么找到我 如果你对AI+3D感兴趣,欢迎关注我的视频号「涛哥玩3D」。此外,如果你也想加入粉丝群,请加我微信(Jartto),备注:加粉丝群。

2025/8/31
articleCard.readMore

用 Tauri+Rust+Yew 来构建你的跨端桌面应用

Tauri 可以开发跨平台桌面应用。起先了解 Tauri 的时候,就被它「性能+包体积」的优势所吸引。正好之前有 Electron 开发经验,那不妨来尝试一下! 一、Tauri 是什么 Tauri 是一个框架,可以用于构建轻量级、安全的桌面应用程序。它与现代前端框架(如 Vue.js、React.js 或者 Svelte)一起使用,使开发者可以用自己熟悉的 Web 技术创建桌面应用。 Tauri 的主要特点包括: 轻量级:Tauri 应用程序比使用 Electron 构建的应用程序更小,更快。这是因为 Tauri 使用 Rust 构建,Rust 是一种性能优越且内存安全的编程语言。 安全性:Tauri 的设计考虑了安全性。它有一个内置的安全层,可以防止一些常见的攻击。 跨平台:Tauri 支持在 Windows、Mac 和 Linux 上构建应用程序。 总的来说,Tauri 提供了一种高效、安全的方式来使用 Web 技术构建桌面应用程序。 前置安装 说明文档 Rust + Tauri + Yew 1 2 3 cargo install create-tauri-app # 初始化项目 cargo create-tauri-app 如果你还没有安装Tauri CLI 1 cargo install tauri-cli 运行项目 1 cargo tauri dev 日志信息如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Running BeforeDevCommand (`trunk serve`) 2023-03-19T12:51:19.075591Z INFO 📦 starting build 2023-03-19T12:51:19.075822Z INFO spawning asset pipelines 2023-03-19T12:51:19.486709Z INFO building rust-tauri-app-ui 2023-03-19T12:51:19.486759Z INFO copying directory path="public" 2023-03-19T12:51:19.486782Z INFO copying & hashing css path="styles.css" 2023-03-19T12:51:19.487151Z INFO finished copying & hashing css path="styles.css" 2023-03-19T12:51:19.487281Z INFO finished copying directory path="public" Finished dev [unoptimized + debuginfo] target(s) in 0.17s 2023-03-19T12:51:19.701213Z INFO fetching cargo artifacts 2023-03-19T12:51:19.940618Z INFO processing WASM for rust-tauri-app-ui 2023-03-19T12:51:19.961661Z INFO calling wasm-bindgen for rust-tauri-app-ui 2023-03-19T12:51:20.120952Z INFO copying generated wasm-bindgen artifacts 2023-03-19T12:51:20.122868Z INFO applying new distribution 2023-03-19T12:51:20.123718Z INFO ✅ success 2023-03-19T12:51:20.124694Z INFO 📡 serving static assets at -> / 2023-03-19T12:51:20.124755Z INFO 📡 server listening at http://127.0.0.1:1420 Info Watching /Users/jartto/Documents/Project/rust-tauri-app/src-tauri for changes... Compiling rust-tauri-app v0.0.0 (/Users/jartto/Documents/Project/rust-tauri-app/src-tauri) Finished dev [unoptimized + debuginfo] target(s) in 2.29s 效果预览 如果看到如下窗口,意味着应用已经正常启动了。 代码说明 Tauri 为您的前端开发提供了其他系统原生功能。 我们将其称作指令,这使得您可以从 JavaScript 前端调用由 Rust 编写的函数。 由此,您可以使用性能飞快的 Rust 代码处理繁重的任务或系统调用。 以下是一个简单示例: 1 2 3 4 5 // src-tauri/src/main.rs #[tauri::command] fn greet(name: &str) -> String { format!("Hello, {}!", name) } 一个指令等于一个普通的 Rust 函数,只是还加上了 #[tauri::command] 宏来让其与您的 JavaScript 环境交互。 最后,我们需要让 Tauri 知悉您刚创建的指令才能让其调用。 我们需要使用 .invoke_handler() 函数及 Generate_handler![] 宏来注册指令: 1 2 3 4 5 6 7 // src-tauri/src/main.rs fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![greet]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } 现在前端可以调用刚注册的指令了! 我们通常会推荐使用 @tauri-apps/api 包,但由于本指南至此还没有使用打包工具,所以需要在 tauri.conf.json 文件中启用 withGlobalTauri 选项。 1 2 3 4 5 6 7 8 { "build": { "beforeBuildCommand": "", "beforeDevCommand": "", "devPath": "../ui", "distDir": "../ui", "withGlobalTauri": true }, 此选项会将已打包版本的 API 函数注入到前端中。接下来,可以修改 index.html 文件来调用指令: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <h1 id="header">Welcome from Tauri!</h1> <script> // access the pre-bundled global API functions const { invoke } = window.__TAURI__.tauri // now we can call our Command! // You will see "Welcome from Tauri" replaced // by "Hello, World!"! invoke('greet', { name: 'World' }) // `invoke` returns a Promise .then((response) => { window.header.innerHTML = response }) </script> </body> </html> 应用调试 应用程序调试 打包应用 执行命令 1 cargo tauri build 会报如下错误: 1 2 You must change the bundle identifier in `tauri.conf.json > tauri > bundle > identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications. 您必须在 `tauri.conf.json > tauri > bundle > identifier` 中更改包标识符。 默认值 com.tauri.dev 是不允许的,因为它在应用程序中必须是唯一的。 按照提示修改标识符:com.tauri.dev ——> com.tauri.jartto,重新运行打包命令: 1 cargo tauri build 此时,程序已经开始编译: 1 2 3 4 5 6 7 8 9 Compiling serialize-to-javascript v0.1.1 Compiling rust-tauri-app v0.0.0 (/Users/jartto/Documents/Project/rust-tauri-app/src-tauri) Compiling ignore v0.4.18 Compiling tauri-macros v1.2.1 Compiling serde_repr v0.1.12 Compiling encoding_rs v0.8.32 Compiling state v0.5.3 Compiling embed_plist v1.2.2 Building [=======================> ] 376/377: rust-tauri-app(bin) 我们重点关注最后两行: 1 2 3 Finished 2 bundles at: /Users/jartto/Documents/Project/rust-tauri-app/target/release/bundle/macos/rust-tauri-app.app /Users/jartto/Documents/Project/rust-tauri-app/target/release/bundle/dmg/rust-tauri-app_0.0.0_aarch64.dmg 顺着目录可以找到最终打包生成的文件地址,如下: 查看 APP 文件大小: 选择 rust-tauri-app_0.0.0_aarch64.dmg 并双击安装,如下: 运行效果就不用说了,和上文效果预览一致。至此,起手项目大功告成!

2023/5/16
articleCard.readMore

Docker 新书上市一周年

我的新书《Docker 实战派:容器入门七步法》上市已经一周年了,在这个里程碑的时刻,感谢各位朋友的支持。希望今年能够持续「大麦」,拿下同类书籍 Top1! 您的浏览器不支持播放该视频! 本书已入选『自学入门的编程书 TOP 榜』,京东、当当,热卖中! 一、内容回顾 市场上不乏Docker技术相关的书籍,或围绕官方基础文档缺乏新意,或直入源码让新人望而却步。鲜有既满足读者入门需要,又结合企业实际案例的佳作。 《Docker实战派:容器入门七步法》正是看到了这一点,另辟蹊径,从读者角度出发,首次提出了「七步法」的概念。 何谓「七步法」?七是人们最容易记住的数字,也是人类瞬间记忆的极限,本书正是立意于此。 第一步,从具象的故事开始,开门见山、降低认知门槛。 第二步,通过「第一个Docker项目」,帮助读者快速上手。在读者建立起体系概念后; 第三步,直切核心原理,围绕Docker架构展开,由浅入深的讲解了Docker底层隔离机制、容器生命周期、网络与通讯、存储原理以及源码。深入剖析,知其然而知其所以然。 第四步,趁热打铁,围绕前后端项目,从全栈角度进行项目实战。 第五步,从Docker容器运维角度出发,进一步补充读者知识图谱,这也是初学者最容易忽视的内容。 第六步,步入高级教程,该部分重点围绕Docker技术最佳实践展开,提供了容器与进程、文件存储与备份、网络配置、镜像优化以及安全策略等内容,示例丰富,操作性十足。 第七步,全书内容升华。通过云原生持续交付模型、企业容器标准化及两个实际的企业级方案,将本书所有内容进行串联。 至此,七步完成。读者可以清晰的感受每一步带来的技术提升,稳扎稳打,从而完全将Docker技术融会贯通。 二、本书亮点 《Docker 实战派:容器入门七步法》最大的亮点就是:趣味易懂,案例丰富,实操性强。 (1)趣味易懂 书中较多的原理,剥除了Docker官方文档晦涩难懂的外衣,通过趣味的故事展开。如:通过「盖房子」的比方来理解Docker是什么,通过「别墅与胶囊旅馆」的例子来阐述容器与虚拟机的概念,通过「工厂和车间」来说明进程和线程等。读者无需记忆,就可轻松理解,这也正是本书想要传达的观点——技术并非晦涩难懂,而是缺乏技巧。 (2)案例丰富 本书第二、四、五、六、七章都包含大量的示例。不管是“第一个Docker项目”还是项目实战、或者是「企业案例」都包含了大量的代码讲解。读者完全可以按照教程逐步实现,体验Docker编程的乐趣。 (3)实操性强 值得一提的是,本书中案例均来自于实际的研发过程,为了让读者能够轻松掌握,去除了容器中包含的业务逻辑,保留了Docker容器最核心的架构,实操性强。熟练掌握书中的精彩案例,沉淀其所表现出来的方法论,相信读者一定能够在企业应用中灵活运用,事半功倍。 三、精彩书评 「毋庸讳言,现如今还不了解Docker就不是一个合格的开发者。Docker对DevOps的飞速发展具有重要作用。本书结合作者多年一线“大厂”技术实践的经验,既有前端开发者的视角,又有上下游的相关案例,为读者提供了一个完整的DevOps“地图”,可以作为一线开发人员的案头用书。」——高途集团大前端技术通道负责人 黄后锦 「Docker作为一种开源的应用容器引擎正在被广泛使用。本书由浅入深地介绍了相关的知识点,将很多不容易理解的概念用生活中的例子生动、形象地表达了出来,对于各个阶段的学习者来说都非常友好。同时,本书从研发岗位的不同视角,介绍了Docker的实践方案,对相关开发者的日常工作具有一定的指导作用。」——字节跳动商业技术营销工程团队负责人 赵龙 「云计算技术的普及,使企业和组织更聚焦于自身的核心业务。而云原生如同“集装箱改变世界”一样,通过标准化的方式来应对业务在打包、部署和管理等过程中遇到的各种挑战,从而帮助企业达到降本增效的目的。容器技术可以说是云原生技术体系结构的基础。而Docker则是容器技术落地的“先驱”,是非常重要的容器技术实现,在整个云原生技术体系中具有重要作用。本书通过一个故事让读者明白Docker是什么,之后通过一个项目带领读者快速上手实践,并帮助读者补充了解Docker的核心原理,而后从项目实践、持续集成与发布、Docker的高级应用、打造企业级应用等方面展开介绍。本书是帮助读者入门Docker的佳作。乐于见到有更多这样的图书来帮助更多有需求的人,帮助他们早日走上云原生的大舞台。」——阿里云边缘云原生技术负责人 周晶

2023/3/19
articleCard.readMore

30s 就可以掌握的 Nginx 片段

作为一枚程序员,日常研发少不了上线部署工作。一旦走上“基建”的道路,你就会发现 Nginx 是你绕不开的一个坎。毫不夸张的说:Nginx 能顶半边天! 也许你会反驳,我们有专业的运维( OP )团队,不用操心。然而实际情况却是 OP 每天被繁重的工单占据着,你无时无刻不在排队。大公司如此,小公司更甚。因此,储备一些 Nginx 知识,一定会让你事半功倍。 本文总结了日常开发中高频出现的 15 个 Nginx 配置片段,因为短小,所以你只需 30 秒就可以掌握。 一、跨域配置 由于浏览器的安全策略,前端处理跨域请求的概率极高,如下是开启跨域请求常规手段。 1 2 3 4 5 6 7 if ($request_method = OPTIONS ) { add_header "Access-Control-Allow-Origin" *; add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD"; add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept"; add_header 'Access-Control-Max-Age' 600; return 200; } 二、开启 GZip 压缩 如果你希望压缩常规的文件类型,可以参考如下代码: 1 2 3 4 5 6 7 8 9 http { include conf/mime.types; gzip on; gzip_min_length 1000; gzip_buffers 4 8k; gzip_http_version 1.1; gzip_types text/plain application/x-javascript text/css application/xml application/javascript application/json; } GZip 涉及参数较多,还有 gzip_comp_level、gzip_proxied等,详情可以参考:Nginx配置 - Gzip压缩 三、跨域传递 Cookie Chrome 80以后的版本,Cookie默认不可跨域,除非服务器在响应头里再设置 same-site 属性(strict,lax,none)。 Strict最为严格,完全禁止第三方Cookie,跨站点时,任何情况下都不会发送Cookie。换言之,只有当前网页的URL与请求目标一致,才会带上Cookie。这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个GitHub链接,用户点击跳转就不会带有GitHub的Cookie,跳转过去总是未登陆状态。 None,Cookie只能通过HTTPS协议发送。必须同时设置Secure属性(Cookie只能通过HTTPS协议发送),否则无效。 1 Set-Cookie: widget_session=abc123; SameSite=None; Secure Lax规则稍稍放宽,大多数情况也是不发送第三方Cookie,但是导航到目标网址的Get请求除外。 还有一种方式是使用proxy_pass反向代理。如果只是Host、端口转换,则Cookie不会丢失。再次访问时,浏览器会发送当前的Cookie。当然,路径变化了,则需要设置Cookie的路径转换。 1 2 3 4 location /foo { proxy_pass http://localhost:4000; proxy_cookie_path /foo "/; SameSite=None; HTTPOnly; Secure"; } 四、健康检查 Nginx 服务端会按照设定的间隔时间主动向后端的 upstream_server 发出检查请求来验证后端的各个 upstream_server 的状态。 如果得到某个服务器失败的返回超过一定次数,比如 3 次就会标记该服务器为异常,就不会将请求转发至该服务器。一般情况下后端服务器需要为这种健康检查专门提供一个低消耗的接口。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 http { # 指定一个 upstream 负载均衡组,名称为 evalue upstream evalue { # 定义组内的节点服务,如果不加 weight 参数,默认就是 Round Robin ,加上了 weight 参数就是加权轮询 server 192.168.90.100:9999 weight=100; server 192.168.90.101:9999 weight=100; # interval=3000 检查间隔 3 秒 , rise=3 连续成功3次认为服务健康 , fall=5 连续失败 5 次认为服务不健康 , timeout=3000 健康检查的超时时间为 3 秒 , type=http 检查类型 http check interval=3000 rise=3 fall=5 timeout=3000 type=http; # check_http_send 设定检查的行为:请求类型 url 请求协议 -> HEAD /api/v1/health HTTP/1.0 check_http_send "HEAD /api/v1/health HTTP/1.0\r\n\r\n"; # 设定认为返回正常的响应状态 check_http_expect_alive http_2xx http_3xx; } } 五、泛域名解析 要在 Nginx 中配置域名泛解析,可以使用通配符 * 来实现次级域名指向同一 IP 地址。如下是一个简单的 Nginx 配置示例: 1 2 3 4 5 server { listen 80; server_name *.jartto.com; root /var/www/jartto.com; } 上面的配置将所有以 .jartto.com 结尾的域名都指向 /var/www/jartto.com 目录下的网站。如果要配置二级域名,可以使用以下的 Nginx 配置: 1 2 3 4 5 server { listen 80; server_name *.sub.jartto.com; root /var/www/sub.jartto.com; } 上面的配置将所有以 .sub.jartto.com 结尾的二级域名都指向 /var/www/sub.jartto.com 目录下的网站。需要注意的是,在使用泛解析时,可能会导致一些安全问题。因此,建议仅在必要时使用泛解析,并且要对网站进行充分保护。 六、使用 $request_id 实现链路追踪 Nginx在1.11.0版本中就提供了内置变量$request_id,其原理就是生成 32 位的随机字符串,虽不能比拟 UUID 的概率,但 32 位的随机字符串的重复概率也是微不足道了,所以一般可视为 UUID 来使用。 1 2 3 4 5 6 7 8 9 10 location ^~ /habo/gid { add_header Cache-Control no-store; default_type application/javascript; set $unionId $cookie_GID; if ($unionId = "") { set $unionId $request_id; add_header Set-Cookie "GID=${unionId};path=/habo/;max-age=${GID_MAX_AGE}"; } return 200 "document.cookie='GID=${unionId};path=/;max-age=${GID_MAX_AGE}'"; } 七、限流配置 Nginx提供了两种限流方式:控制速率和控制并发连接数。 其中,控制速率是指限制单位时间内的请求次数,而控制并发连接数是指限制同时处理的请求数量。 下面是一个简单的Nginx限流配置示例,使用leaky bucket算法进行限流: 1 2 3 4 5 6 7 8 9 10 http { limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; server { location / { limit_req zone=one burst=5; proxy_pass http://backend; } } } 在上述配置中,limit_req_zone用于定义一个名为one的共享内存区域,并将其与客户端IP地址相关联。这个共享内存区域最大占用10MB空间,并且允许每秒钟通过 1 个请求。然后,在location块中使用了limit_req指令来启用限流功能。这里设置burst为 5,表示当客户端在短时间内发送超过 1 个请求时,可以暂时容忍一定数量的请求超出限制。 需要注意的是,在实际应用中需要根据具体情况调整参数值以达到最佳效果。如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 map $http_baggage_flow $plimit { "ptest" $server_name; default ""; } limit_req_zone $plimit zone=prelimit:10m rate=600r/s; server { listen 443 ssl; server_name www.jartto.com; limit_req zone=prelimit nodelay; limit_req_status 530; location = /530.html { default_type application/json; return 200 '{"status" : 530}'; } ... } 八、History 二级路由刷新问题 vue-router+webpack项目线上部署时单页项目路由,刷新页面出现 404 问题,一般需要配置如下: 1 2 3 4 5 6 7 8 9 location / {   root html;   try_files $uri $uri/ @router;   index index.html index.htm; } location @router {   rewrite ^.*$ /index.html last; } 九、Cookie 路由识别 最常见的场景就是灰度发布,Nginx 来识别来自前端的流量,从而进行转发。 1 2 3 4 5 6 7 8 map $http_cookie $m_upstream { ~*baggage-version=isolute-feat-.*$ al-bj-sre-k8s-test-istio-gateway; default test.jartto.com; } upstream al-bj-sre-k8s-test-istio-gateway { server 47.95.128.11:80; } 十、服务端开启图片转换 这里主要是设置 WebP 格式图片,如果你还不了解,请查看:WebP 方案分析与实践。 1 2 3 4 map $http_accept $webp_suffix { default ""; "~*webp" ".webp"; } 1 2 3 4 5 6 7 location ~* ^/_nuxt/img/(.+\.png|jpe?g)$ { rewrite ^/_nuxt/img/(.+\.png|jpe?g)$ /$1 break; root /apps/srv/instance/test-webp.jartto.com/.nuxt/dist/client/img/; add_header Vary Accept; try_files $uri$webp_suffix $uri =404; expires 30d; } 十一、负载均衡 负载均衡通常有四种算法: 轮询,默认方式,每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务挂了,能自动剔除; weight,权重分配,指定轮询几率,权重越高,在被访问的概率越大,用于后端服务器性能不均的情况; ip_hash,每个请求按访问 IP 的 hash 结果分配,这样每个访客固定访问一个后端服务器,可以解决动态网页 session 共享问题。负载均衡每次请求都会重新定位到服务器集群中的某一个,那么已经登录某一个服务器的用户再重新定位到另一个服务器,其登录信息将会丢失,这样显然是不妥的; fair(第三方),按后端服务器的响应时间分配,响应时间短的优先分配,依赖第三方插件 nginx-upstream-fair,使用前请先安装; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 http { upstream jartto-server { # ip_hash; # ip_hash 方式 # fair; # fair 方式 server 127.0.0.1:8081; # 负载均衡目的服务地址 server 127.0.0.1:8080; server 127.0.0.1:8082 weight=10; # weight 方式,不写默认为 1 } server { location / { proxy_pass http://jartto-server; proxy_connect_timeout 10; } } } 十二、配置 HTTPS 要在 Nginx 上配置 HTTPS,可以按照以下步骤操作: 为 Nginx 安装 SSL 模块。 这可以通过使用 --with-http_ssl_module 选项编译 Nginx 或安装包含 SSL 模块的预构建包来完成。 从受信任的证书颁发机构CA为您的域获取SSL证书。 这可以通过购买证书或从Let's Encrypt获得免费证书来完成。 配置Nginx以使用SSL证书和密钥文件。这涉及将以下行添加到您的Nginx配置文件中: 1 2 ssl_certificate /path/to/jartto.crt; ssl_certificate_key /path/to/jartto.key; 如果需要,配置Nginx将HTTP请求重定向到HTTPS。 这可以使用服务器块来完成,该服务器块监听端口80并将所有请求重定向到端口443(默认 HTTPS 端口)。 重新启动Nginx以应用更改。 下面是启用HTTPS并将HTTP请求重定向到HTTPS的Nginx配置文件示例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 server { listen 80; server_name jartto.com www.jartto.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl; server_name jartto.com www.jartto.com; ssl_certificate /path/to/jartto.crt; ssl_certificate_key /path/to/jartto.key; # Other SSL-related settings go here # Other server block settings go here } 如上配置文件同时监听端口80和443,但仅在端口443上通过HTTPS提供内容。所有HTTP请求都使用第一个服务器块中的返回语句重定向到它们等效的HTTPS URL。 十三、图片防盗链 如果你不想图片被外网随便引用,那么可以配置图片防盗链能力,配置如下: 1 2 3 4 5 6 7 8 9 10 11 12 server { listen 80; server_name *.jartto.com; # 图片防盗链 location ~* \.(gif|jpg|jpeg|png|bmp|swf)$ { valid_referers none blocked server_names ~\.google\. ~\.baidu\. *.qq.com; # 只允许本机 IP 外链引用 if ($invalid_referer){ return 403; } } } 十四、配置多 Server 要在Nginx中配置多个服务器,可以在Nginx配置文件中定义多个服务器块。每个服务器块代表一个单独的虚拟服务器,可以监听不同的端口或IP地址,并提供不同的内容。下面是如何在Nginx中配置两个虚拟服务器的示例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 http { server { listen 80; server_name www.jartto.com; root /var/www/jartto.com; # other server configuration directives } server { listen 80; server_name www.another-jartto.com; root /var/www/another-jartto.com; # other server configuration directives } } 上述示例中,我们定义了两个监听端口80的虚拟服务器。第一个虚拟服务器配置为从目录/var/www/jartto.com为域www.jartto.com提供内容。第二个虚拟服务器配置为从目录/var/www/another-jartto.com为域www.another-jartto.com提供内容。每个服务器块都可以有自己的一组配置指令,例如SSL证书、访问日志、错误页面等等。 通过在Nginx配置文件中定义多个服务器块,我们就可以在单个Nginx实例上托管多个网站或应用程序。 十五、动态修改配置模块 ngx_dynamic_upstream 是用于使用 HTTP API 动态操作上游的模块,例如 ngx_http_upstream_conf。如果你想动态修改Nginx配置信息,那么不妨试试如下代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 upstream backends { zone zone_for_backends 1m; server 127.0.0.1:6001; server 127.0.0.1:6002; server 127.0.0.1:6003; } server { listen 6000; location /dynamic { allow 127.0.0.1; deny all; dynamic_upstream; } location / { proxy_pass http://backends; } } 使用方式如下: 1 2 3 4 $ curl "http://127.0.0.1:6000/dynamic?upstream=zone_for_backends&verbose=" server 127.0.0.1:6001 weight=1 max_fails=1 fail_timeout=10; server 127.0.0.1:6002 weight=1 max_fails=1 fail_timeout=10; server 127.0.0.1:6003 weight=1 max_fails=1 fail_timeout=10;

2023/3/12
articleCard.readMore

了不起的 Istio

很多企业都会面临从单体应用向微服务架构的转型,也会衍生出更多的分布式场景需求。随着规模和复杂度的不断增长,如何才能更好的理解、高效的管理服务网格呢? 本节篇幅较长,我们主要围绕以下几点来展开: 1.什么是服务网格? 2.初识 Istio 3.核心特性 4.流程架构 5.核心模块 6.Envoy 进阶 7.方案畅想 对许多公司来说,Docker 和 Kubernetes 这样的工具已经解决了部署问题,或者说几乎解决了。但他们还没有解决运行时的问题,这就是服务网格(Service Mesh)的由来。 一、什么是服务网格? 服务网格(Service Mesh)用来描述组成这些应用程序的微服务网络以及它们之间的交互。它是一个用于保证服务间安全、快速、可靠通信的网络代理组件,是随着微服务和云原生应用兴起而诞生的基础设施层。 它通常以轻量级网络代理的方式同应用部署在一起。比如 Sidecar 方式,如下图所示: 我们对上图做个解释: Service Mesh 设计一般划分为两个模块,控制面和数据面。对于应用来说,所有流量都会经过数据面进行转发。顺利转发的前提:数据面需要知道转发的目标地址,目标地址本身是由一些业务逻辑来决定的(例如服务发现)。 所以自然而然地,我们可以推断控制面需要负责管理数据面能正常运行所需要的一些配置: 需要知道某次请求转发去哪里:服务发现配置; 外部流量进入需要判断是否已经达到服务流量上限:限流配置; 依赖服务返回错误时,需要能够执行相应的熔断逻辑:熔断配置; Serivce Mesh 可以看作是一个位于 TCP/IP 之上的网络模型,抽象了服务间可靠通信的机制。但与 TCP 不同,它是面向应用的,为应用提供了统一的可视化和控制。 1.Service Mesh 具有如下优点: 屏蔽分布式系统通信的复杂性(负载均衡、服务发现、认证授权、监控追踪、流量控制等等),服务只用关注业务逻辑; 真正的语言无关,服务可以用任何语言编写,只需和 Service Mesh 通信即可; 对应用透明,Service Mesh 组件可以单独升级; 2.Service Mesh 目前也面临一些挑战: Service Mesh 组件以代理模式计算并转发请求,一定程度上会降低通信系统性能,并增加系统资源开销; Service Mesh 组件接管了网络流量,因此服务的整体稳定性依赖于 Service Mesh,同时额外引入的大量 Service Mesh 服务实例的运维和管理也是一个挑战; 随着服务网格的规模和复杂性不断的增长,它将会变得越来越难以理解和管理。 Service Mesh 的需求包括服务发现、负载均衡、故障恢复、度量和监控等。Service Mesh 通常还有更复杂的运维需求,比如 A/B 测试、金丝雀发布、速率限制、访问控制和端到端认证。 Service Mesh的出现,弥补了 Kubernetes 在微服务的连接、管理和监控方面的短板,为 Kubernetes 提供更好的应用和服务管理。因此,Service Mesh 的代表 Istio 一经推出,就被认为是可以和 Kubernetes 形成双剑合璧效果的微服务管理的利器,受到了业界的推崇。 Istio 提供了对整个服务网格的行为洞察和操作控制的能力,以及一个完整的满足微服务应用各种需求的解决方案。Istio 主要采用一种一致的方式来保护、连接和监控微服务,降低了管理微服务部署的复杂性。 二、初识 Istio Istio 发音「意丝帝欧」,重音在意上。官方给出的 Istio 的总结,简单明了: 1 Istio lets you connect, secure, control, and observe services. 连接、安全、控制和观测服务。 简单来说,Istio 针对现有的服务网格,提供一种简单的方式将连接、安全、控制和观测的模块,与应用程序或服务隔离开来,从而开发人员可以将更多的精力放在核心的业务逻辑上,以下是 Istio 的核心功能: 1.HTTP、gRPC、WebSocket 和 TCP 流量的自动负载均衡; 2.通过丰富的路由规则、重试、故障转移和故障注入,可以对流量行为进行细粒度控制; 3.可插入的策略层和配置 API,支持访问控制、速率限制和配额; 4.对出入集群入口和出口中所有流量的自动度量指标、日志记录和追踪; 5.通过强大的基于身份的验证和授权,在集群中实现安全的服务间通信; 从较高的层面来说,Istio 有助于降低这些部署的复杂性,并减轻开发团队的压力。它是一个完全开源的服务网格,作为透明的一层接入到现有的分布式应用程序里。它也是一个平台,拥有可以集成任何日志、遥测和策略系统的 API 接口。 Istio 多样化的特性使我们能够成功且高效地运行分布式微服务架构,并提供保护、连接和监控微服务的统一方法。 三、核心特性 Istio 以统一的方式提供了许多跨服务网格的关键功能: 1.流量管理 Istio 简单的规则配置和流量路由允许我们控制服务之间的流量和 API 调用过程。Istio 简化了服务级属性(如熔断器、超时和重试)的配置,并且让它轻而易举的执行重要的任务(如 A/B 测试、金丝雀发布和按流量百分比划分的分阶段发布)。 有了更好的对流量的可视性和开箱即用的故障恢复特性,我们就可以在问题产生之前捕获它们,无论面对什么情况都可以使调用更可靠,网络更健壮。 2.安全 Istio 的安全特性解放了开发人员,使其只需要专注于应用程序级别的安全。 Istio 提供了底层的安全通信通道,并为大规模的服务通信管理认证、授权和加密。有了 Istio,服务通信在默认情况下就是受保护的,可以在跨不同协议和运行时的情况下实施一致的策略,而所有这些都只需要很少甚至不需要修改应用程序。 Istio 是独立于平台的,可以与 Kubernetes(或基础设施)的网络策略一起使用。但它更强大,能够在网络和应用层面保护 Pod 到 Pod 或者服务到服务之间的通信。 3.可观察性 Istio 健壮的追踪、监控和日志特性让我们能够深入的了解服务网格部署。通过 Istio 的监控能力,可以真正的了解到服务的性能是如何影响上游和下游的。而它的定制 Dashboard 提供了对所有服务性能的可视化能力,并让我们看到它如何影响其他进程。 Istio 的 Mixer 组件负责策略控制和遥测数据收集。它提供了后端抽象和中介,将一部分 Istio与后端的基础设施实现细节隔离开来,并为运维人员提供了对网格与后端基础实施之间交互的细粒度控制。 所有这些特性都使我们能够更有效地设置、监控和加强服务的 SLO。当然,底线是我们可以快速有效地检测到并修复出现的问题。 4.平台支持 Istio 独立于平台,被设计为可以在各种环境中运行,包括跨云、内部环境、Kubernetes、Mesos 等等。我们可以在 Kubernetes 或是装有 Consul 的 Nomad 环境上部署 Istio。 Istio 目前支持: Kubernetes 上的服务部署 基于 Consul 的服务注册 服务运行在独立的虚拟机上 5.整合和定制 Istio 的策略实施组件可以扩展和定制,与现有的 ACL、日志、监控、配额、审查等解决方案集成。 四、流程架构 Istio 服务网格逻辑上分为数据平面(Control Plane)和控制平面(Data Plane),架构图如下所示: 1.数据平面Data Plane由一组以 Sidecar 方式部署的智能代理 Envoy 组成。 Envoy 被部署为 Sidecar,和对应服务在同一个 Kubernetes pod 中。这允许 Istio 将大量关于流量行为的信号作为属性提取出来,而这些属性又可以在 Mixer 中用于执行策略决策,并发送给监控系统,以提供整个网格行为的信息。 这些代理可以调节和控制微服务及 Mixer 之间所有的网络通信。 2.控制平面Control Plane负责管理和配置代理来路由流量,此外配置 Mixer 以实施策略和收集遥测数据。主要包含如下几部分内容: Mixer:策略和请求追踪; Pilot:提供服务发现功能,为智能路由(例如 A/B 测试、金丝雀部署等)和弹性(超时、重试、熔断器等)提供流量管理功能; Citadel:分发 TLS 证书到智能代理; Sidecar injector:可以允许向应用中无侵入的添加功能,避免为了满足第三方需求而添加额外的代码; 五、核心模块 上文提到了很多技术名词,我们需要重点解释一下: 1.什么是 Sidecar 模式? Sidecar 是一种将应用功能从应用本身剥离出来作为单独进程的设计模式,可以允许向应用中无侵入的添加功能,避免为了满足第三方需求而添加额外的代码。 在软件架构中,Sidecar 附加到主应用,或者叫父应用上,以扩展、增强功能特性,同时 Sidecar 与主应用是松耦合的。 Sidecar 是一种单节点多容器的应用设计形式,主张以额外的容器来扩展或增强主容器。 2.Envoy 的作用是什么? Envoy 是一个独立的进程,旨在与每个应用程序服务器一起运行。所有 Envoy 组成了一个透明的通信网格,其中每个应用程序发送和接收来自本地主机的消息,并且不需要知道网络拓扑。 与传统的服务通信服务的库方法相比,进程外架构有两个实质性好处: Envoy 支持任何编程语言写的服务。只用部署一个 Envoy 就可以在 Java、C++、Go、PHP、Python 等服务间形成网格。 任何使用过大型面向服务的体系结构的人都知道,部署库升级可能会非常痛苦。Envoy 可以在整个基础设施中迅速部署和升级。 Envoy 以透明的方式弥合了面向服务的体系结构使用多个应用程序框架和语言的情况。 3.Mixer Mixer 是一个独立于平台的组件,负责在服务网格上执行访问控制和使用策略,并从 Envoy 代理和其他服务收集遥测数据,代理提取请求级属性,发送到 Mixer 进行评估。有关属性提取和策略评估的更多信息,请参见 Mixer 配置。 Mixer 中包括一个灵活的插件模型,使其能够接入到各种主机环境和基础设施后端,从这些细节中抽象出 Envoy 代理和 Istio 管理的服务。 4.Pilot 控制面中负责流量管理的组件为 Pilot,它为 Envoy Sidecar 提供服务发现功能,为智能路由(例如 A/B 测试、金丝雀部署等)和弹性(超时、重试、熔断器等)提供流量管理功能。它将控制流量行为的高级路由规则转换为特定于 Envoy 的配置,并在运行时将它们传播到 Sidecar。 5.Istio 如何保证服务通信的安全? Istio 以可扩缩的方式管理微服务间通信的身份验证、授权和加密。Istio 提供基础的安全通信渠道,使开发者可以专注于应用层级的安全。 Istio 可以增强微服务及其通信(包括服务到服务和最终用户到服务的通信)的安全性,且不需要更改服务代码。 它为每个服务提供基于角色的强大身份机制,以实现跨集群、跨云端的互操作性。 如果我们结合使用 Istio 与 Kubernetes(或基础架构)网络政策,Pod 到 Pod 或服务到服务的通信在网络层和应用层都将安全无虞。Istio 以 Google 的深度防御策略为基础构建而成,以确保微服务通信的安全。 当我们在 Google Cloud 中使用 Istio 时,Google 的基础架构可让我们构建真正安全的应用部署。 Istio 可确保服务通信在默认情况下是安全的,并且我们可以跨不同协议和运行时一致地实施安全政策,而只需对应用稍作调整,甚至无需调整。 六、Envoy 进阶 Istio 使用 Envoy 代理的扩展版本,Envoy 是以 C++ 开发的高性能代理,用于调解服务网格中所有服务的所有入站和出站流量。 Envoy 的许多内置功能被 Istio 发扬光大,例如: 动态服务发现 负载均衡 TLS 终止 HTTP2 & gRPC 代理 熔断器 健康检查、基于百分比流量拆分的灰度发布 故障注入 丰富的度量指标 Envoy 分为主线程、工作线程、文件刷新线程,其中主线程就是负责工作线程和文件刷新线程的管理和调度。而工作线程主要负责监听、过滤和转发,工作线程里面会包含一个监听器,如果收到一个请求之后会通过过滤链来进行数据过滤。前面两个都是非阻塞的,唯一一个阻塞的是这种 IO 操作的,会不断地把内存里面一些缓存进行落盘。 总结来说,我们可以围绕如下 5 方面: 1.服务的动态注册和发现 Envoy 可以选择使用一组分层的动态配置 API 来进行集中管理。 这些层为 Envoy 提供了动态更新,后端群集的主机、后端群集本身、HTTP 路由、侦听套接字和通信加密。为了实现更简单的部署,后端主机发现可以通过 DNS 解析 (甚至完全跳过) 完成,层也可以替换为静态配置文件。 2.健康检查 构建 Envoy 网格的建议方法是将服务发现视为最终一致的过程。 Envoy 包括一个运行状况检查子系统,该子系统可以选择对上游服务集群执行主动运行状况检查。 然后,Envoy 使用服务发现和运行状况检查信息的联合来确定健康的负载均衡服务器。Envoy 还支持通过异常检测子系统进行被动运行状况检查。 3.高级负载均衡 分布式系统中不同组件之间的负载平衡是一个复杂的问题。 由于 Envoy 是一个独立的代理而不是库,因此它能够在一个位置实现高级负载平衡技术,并使任何应用程序都可以访问。 目前 Envoy 包括支持自动重试、断路、通过外部速率限制服务限制全局速率、请求隐藏和异常值检测。未来计划为 Request Racing 提供支持。 4.前端/边缘系统代理支持 虽然 Envoy 主要是为服务通信系统而设计的,但对前端/边缘系统也是很有用的,如:可观测性、管理、相同的服务发现和负载平衡算法等。 Envoy 包含足够的功能,使其可用作大多数 Web 应用服务用例的边缘代理。这包括作为 TLS 的终点、HTTP/1.1 和 HTTP/2 支持, 以及 HTTP L7 路由。 5.最好的观察统计能力 Envoy 的首要目标是使网络透明。但是在网络级别和应用程序级都无法避免的容易出现问题。Envoy 包含了对所有子系统的强有力的统计支持。 statsd 和其他兼容的数据提供程序是当前支持的统计接收器,插入不同的统计接收器也并不困难。 Envoy 可以通过管理端口查看统计信息,还支持通过第三方供应商进行分布式追踪。 更多详情请参考:什么是 Envoy ? 七、方案畅想 应用上面的原理,我们可以有很多具体的方案应用于日常开发。 1.方案一:应用 Istio 改造微服务 模仿在线书店的一个分类,显示一本书的信息。 页面上会显示一本书的描述,书籍的细节(ISBN、页数等),以及关于这本书的一些评论。 应用的端到端架构:Bookinfo 应用中的几个微服务是由不同的语言编写的。 这些服务对 Istio 并无依赖,但是构成了一个有代表性的服务网格的例子:它由多个服务、多个语言构成,并且 reviews 服务具有多个版本。 用 Istio 改造后架构如下:要在 Istio 中运行这一应用,无需对应用自身做出任何改变。我们只需要把 Envoy Sidecar 注入到每个服务之中。最终的部署结果将如下图所示: 所有的微服务都和 Envoy Sidecar 集成在一起,被集成服务所有的出入流量都被 Sidecar 所劫持,这样就为外部控制准备了所需的 Hook,然后就可以利用 Istio 控制平面为应用提供服务路由、遥测数据收集以及策略实施等功能。 更多细节,请移步 官网示例。 2.方案二:用 Istio 改造 CI/CD 流程 对上述流程图简单解释一下: 通过 Docker 对代码进行容器化处理; 通过 Gitlab 托管代码; Jenkins 监听 Gitlab 下的代码,触发自动构建,并执行 Kustomize 文件; Kustomize 通过配置文件,设置了 Istio 的配置(染色识别、流量分发),并启动 K8s 部署应用; 最终我们通过 Rancher 来对多容器进行界面化管理; 打开浏览器进行访问; 看到这里,相信你也了解了,我们实现了一个前端多容器化部署的案例。它有什么意义呢? 首先,当然是环境隔离了,研发每人一个容器开发,互不干扰; 其次,我们可以做很多小流量、灰度发布等事情; 自动化部署,一站式的流程体验; 如果你对容器化还不太了解,请先看看前面两篇文章: Docker 边学边用 一文了解 Kubernetes Istio 还是有很多可圈可点的地方,相信看到这里你也有了更全面的认识。如果你想深入了解,不妨仔细研究官方示例,并且在实际项目中不断打磨。 八、参考资料 1.Istio 官网 2.什么是 Envoy 3.微服务之 Service Mesh 4.什么是 Service Mesh 5.Istio 如何连接、管理和保护微服务 2.0? 6.在 MOSN 中玩转 dubbo-go

2020/7/29
articleCard.readMore

一文了解 Kubernetes

上一节我们着重讲解了 Docker,其实遗留了一个大问题。Docker 虽好用,但面对强大的集群,成千上万的容器,突然感觉不香了。 这时候就需要我们的主角 Kubernetes 上场了,先来了解一下 K8s 的基本概念,后面再介绍实践,由浅入深步步为营。 关于 K8s 的基本概念,我们将会围绕如下七点展开: 1.Docker 的管理痛点 2.什么是 K8s? 3.云架构 & 云原生 4.K8s 架构原理 5.K8s 核心组件 6.K8s 的服务注册与发现 7.关键问题 一、Docker 的管理痛点 如果想要将 Docker 应用于庞大的业务实现,是存在困难的编排、管理和调度问题。于是,我们迫切需要一套管理系统,对 Docker 及容器进行更高级更灵活的管理。 Kubernetes 应运而生!Kubernetes,名词源于希腊语,意为「舵手」或「飞行员」。Google 在 2014 年开源了 Kubernetes 项目,建立在 Google 在大规模运行生产工作负载方面拥有十几年的经验的基础上,结合了社区中最好的想法和实践。 K8s 是 Kubernetes 的缩写,用 8 替代了 「ubernete」,下文我们将使用简称。 二、什么是 K8s ? K8s 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。K8s 拥有一个庞大且快速增长的生态系统。K8s 的服务、支持和工具广泛可用。 通过 K8s 我们可以: 1.快速部署应用 2.快速扩展应用 3.无缝对接新的应用功能 4.节省资源,优化硬件资源的使用 K8s 有如下特点: 1.可移植: 支持公有云,私有云,混合云,多重云 multi-cloud 2.可扩展: 模块化,插件化,可挂载,可组合 3.自动化: 自动部署,自动重启,自动复制,自动伸缩/扩展 三、云架构 & 云原生 1.云和 K8s 是什么关系 云就是使用容器构建的一套服务集群网络,云由很多的大量容器构成。K8s 就是用来管理云中的容器。 2.常见几类云架构 On-Premises (本地部署) iaas(基础设施即服务) 用户:租用(购买|分配权限)云主机,用户不需要考虑网络,DNS,硬件环境方面的问题。 运营商:提供网络,存储,DNS,这样服务就叫做基础设施服务 paas(平台即服务) mysql/es/mq/... saas(软件即服务) 钉钉 财务管理 serverless 无服务,不需要服务器。站在用户的角度考虑问题,用户只需要使用云服务器即可,在云服务器所在的基础环境,软件环境都不需要用户关心。 如果觉得不好理解,推荐阅读这篇文章:如何通俗解释 IaaS、PaaS、SaaS 的区别 可以预见:未来服务开发都是 Serverless,企业都构建了自己的私有云环境,或者是使用公有云环境。 3.云原生 为了让应用程序(项目,服务软件)都运行在云上的解决方案,这样的方案叫做云原生。 云原生有如下特点: 容器化,所有服务都必须部署在容器中 微服务,Web 服务架构式服务架构 CI/CD DevOps 四、K8s 架构原理 1.K8s 架构 概括来说 K8s 架构就是一个 Master 对应一群 Node 节点。 下面我们来逐一介绍 K8s 架构图中的 Master 和 Node。 2.Master 节点结构如下: apiserver 即 K8s 网关,所有的指令请求都必须要经过 apiserver; scheduler 调度器,使用调度算法,把请求资源调度到某一个 node 节点; controller 控制器,维护 K8s 资源对象; etcd 存储资源对象; 3.Node节点 kubelet 在每一个 node 节点都存在一份,在 node 节点上的资源操作指令由 kubelet 来执行; kube-proxy 代理服务,处理服务间负载均衡; pod 是 k8s 管理的基本单元(最小单元),pod 内部是容器,k8s 不直接管理容器,而是管理pod; docker 运行容器的基础环境,容器引擎; fluentd 日志收集服务; 在介绍完 K8s 架构后,我们又引入了很多技术名词。不要着急,先有整体概念,再各个击破。请耐心阅读下文,相信你一定会有不一样的收获。 五、K8s 核心组件 1.K8s 组件 K8s 是用来管理容器,但是不直接操作容器,最小操作单元是 Pod (间接管理容器) 一个 Master 有一群 Node 节点与之对应 Master 节点不存储容器,只负责调度、网管、控制器、资源对象存储 容器的存储在 Node 节点,容器是存储在 Pod 内部的) Pod 内部可以有一个容器,或者多个容器 Kubelet 负责本地 Pod 的维护 Kube-proxy 负责负载均衡,在多个 Pod 之间来做负载均衡 2.Pod 是什么? pod 也是一个容器,这个容器中装的是 Docker 创建的容器,Pod 用来封装容器的一个容器,Pod 是一个虚拟化分组; Pod 相当于独立主机,可以封装一个或者多个容器; Pod 有自己的 IP 地址、主机名,相当于一台独立沙箱环境。 3.Pod 到底用来干什么? 通常情况下,在服务部署时候,使用 Pod 来管理一组相关的服务。一个 Pod 中要么部署一个服务,要么部署一组有关系的服务。 一组相关的服务是指:在链式调用的调用连路上的服务。 4.Web 服务集群如何实现? 实现服务集群:只需要复制多方 Pod 的副本即可,这也是 K8s 管理的先进之处,K8s 如果继续扩容,只需要控制 Pod 的数量即可,缩容道理类似。 5.Pod 底层网络,数据存储是如何进行的? Pod 内部容器创建之前,必须先创建 Pause 容器; 服务容器之间访问 localhost ,相当于访问本地服务一样,性能非常高; 6.ReplicaSet 副本控制器 控制 Pod 副本「服务集群」的数量,永远与预期设定的数量保持一致即可。当有 Pod 服务宕机时候,副本控制器将会立马重新创建一个新的 Pod,永远保证副本为设置数量。 副本控制器:标签选择器-选择维护一组相关的服务(它自己的服务) 1 2 3 selector: app = web Release = stable ReplicationController 副本控制器:单选 ReplicaSet 副本控制器:单选,复合选择 在新版的 K8s 中,建议使用 ReplicaSet 作为副本控制器,ReplicationController 不再使用了。 7.Deployment 部署对象 服务部署结构模型 滚动更新 ReplicaSet 副本控制器控制 Pod 副本的数量。但是,项目的需求在不断迭代、不断的更新,项目版本将会不停的的发版。版本的变化,如何做到服务更新? 部署模型: ReplicaSet 不支持滚动更新,Deployment 对象支持滚动更新,通常和 ReplicaSet 一起使用; Deployment 管理 ReplicaSet,RS 重新建立新的 RS,创建新的 Pod; 8.MySQL 使用容器化部署,存在什么样的问题? 容器是生命周期的,一旦宕机,数据丢失 Pod 部署,Pod 有生命周期,数据丢失 对于 K8s 来说,不能使用 Deployment 部署有状态服务。 通常情况下,Deployment 被用来部署无状态服务,那么对于有状态服务的部署,使用 StatefulSet 进行有状态服务的部署。 什么是有状态服务? 有实时的数据需要存储 有状态服务集群中,把某一个服务抽离出去,一段时间后再加入机器网络,如果集群网络无法使用 什么是无状态服务? 没有实时的数据需要存储 无状态服务集群中,把某一个服务抽离出去,一段时间后再加入机器网络,对集群服务没有任何影响 9.StatefulSet 为了解决有状态服务使用容器化部署的一个问题。 部署模型 有状态服务 StatefulSet 保证 Pod 重新建立后,Hostname 不会发生变化,Pod 就可以通过 Hostname 来关联数据。 六、K8s 的服务注册与发现 1.Pod 的结构是怎样的? Pod 相当于一个容器,Pod 有独立 IP 地址,也有自己的 Hostname,利用 Namespace 进行资源隔离,独立沙箱环境。 Pod 内部封装的是容器,可以封装一个,或者多个容器(通常是一组相关的容器) 2.Pod 网络 Pod 有自己独立的 IP 地址 Pod 内部容器之间访问采用 Localhost 访问 Pod 内部容器访问是 Localhost,Pod 之间的通信属于远程访问。 3.Pod 是如何对外提供服务访问的? Pod 是虚拟的资源对象(进程),没有对应实体(物理机,物理网卡)与之对应,无法直接对外提供服务访问。 那么该如何解决这个问题呢? Pod 如果想要对外提供服务,必须绑定物理机端口。也就是说在物理机上开启端口,让这个端口和 Pod 的端口进行映射,这样就可以通过物理机进行数据包的转发。 概括来说:先通过物理机 IP + Port 进行访问,再进行数据包转发。 4.一组相关的 Pod 副本,如何实现访问负载均衡? 我们先明确一个概念,Pod 是一个进程,是有生命周期的。宕机、版本更新,都会创建新的 Pod。这时候 IP 地址会发生变化,Hostname 会发生变化,使用 Nginx 做负载均衡就不太合适了。 所以我们需要依赖 Service 的能力。 5.Service 如何实现负载均衡? 简单来说,Service 资源对象包括如下三部分: Pod IP:Pod 的 IP 地址 Node IP:物理机 IP 地址 Cluster IP:虚拟 IP ,是由 K8s 抽象出的 Service 对象,这个 Service 对象就是一个 VIP 的资源对象 6.Service VIP 更深入原理探讨 Service 和 Pod 都是一个进程,Service 也不能对外网提供服务; Service 和 Pod 之间可以直接进行通信,它们的通信属于局域网通信; 把请求交给 Service 后,Service 使用 iptable,ipvs 做数据包的分发; 7.Service 对象是如何和 Pod 进行关联的? 不同的业务有不同的 Service; Service 和 Pod 通过标签选择器进行关联; 1 2 3 selector: app=x 选择一组订单的服务 pod ,创建一个 service; 通过 endpoints 存放一组 pod ip; Service 通过标签选择器选择一组相关的副本,然后创建一个 Service。 8.Pod 宕机、发布新的版本的时候,Service 如何发现 Pod 已经发生了变化? 每个 Pod 中都有 Kube-Proxy,监听所有 Pod。如果发现 Pod 有变化,就动态更新(etcd 中存储)对应的 IP 映射关系。 七、关键问题 1.企业使用 K8s 主要用来做什么? 自动化运维平台 创业型公司,中小型企业,使用 K8s 构建一套自动化运维平台,自动维护服务数量,保持服务永远和预期的数据保持一致性,让服务可以永远提供服务。这样最直接的好处就是降本增效。 充分利用服务器资源 互联网企业,有很多服务器资源「物理机」,为了充分利用服务器资源,使用 K8s 构建私有云环境,项目运行在云。这在大型互联网公司尤为重要。 服务的无缝迁移 项目开发中,产品需求不停的迭代,更新产品。这就意味着项目不停的发布新的版本,而 K8s 可以实现项目从开发到生产无缝迁移。 2.K8s 服务的负载均衡是如何实现的? Pod 中的容器很可能因为各种原因发生故障而死掉。Deployment 等 Controller 会通过动态创建和销毁 Pod 来保证应用整体的健壮性。换句话说,Pod 是脆弱的,但应用是健壮的。每个 Pod 都有自己的 IP 地址。当 controller 用新 Pod 替代发生故障的 Pod 时,新 Pod 会分配到新的 IP 地址。 这样就产生了一个问题:如果一组 Pod 对外提供服务(比如 HTTP),它们的 IP 很有可能发生变化,那么客户端如何找到并访问这个服务呢? K8s 给出的解决方案是 Service。 Kubernetes Service 从逻辑上代表了一组 Pod,具体是哪些 Pod 则是由 Label 来挑选。 Service 有自己 IP,而且这个 IP 是不变的。客户端只需要访问 Service 的 IP,K8s 则负责建立和维护 Service 与 Pod 的映射关系。无论后端 Pod 如何变化,对客户端不会有任何影响,因为 Service 没有变。 3.无状态服务一般使用什么方式进行部署? Deployment 为 Pod 和 ReplicaSet 提供了一个 声明式定义方法,通常被用来部署无状态服务。 Deployment 的主要作用: 定义 Deployment 来创建 Pod 和 ReplicaSet 滚动升级和回滚应用扩容和索容暂停和继续。Deployment不仅仅可以滚动更新,而且可以进行回滚,如果发现升级到 V2 版本后,服务不可用,可以迅速回滚到 V1 版本。

2020/7/15
articleCard.readMore

Docker 边学边用

富 Web 时代,应用变得越来越强大,与此同时也越来越复杂。集群部署、隔离环境、灰度发布以及动态扩容缺一不可,而容器化则成为中间的必要桥梁。 本节我们就来探索一下 Docker 的神秘世界,从零到一掌握 Docker 的基本原理与实践操作。别再守着前端那一亩三分地,是时候该开疆扩土了。 我们将会围绕下面几点展开: 1.讲个故事 2.虚拟机与容器 3.认识 Docker 4.核心概念 5.安装 Docker 6.快速开始 7.常规操作 8.最佳实践 一、讲个故事 为了更好的理解 Docker 是什么,我们先来讲个故事: 我需要盖一个房子,于是我搬石头、砍木头、画图纸、盖房子。一顿操作,终于把这个房子盖好了。 结果,住了一段时间,心血来潮想搬到海边去。这时候按以往的办法,我只能去海边,再次搬石头、砍木头、画图纸、盖房子。 烦恼之际,跑来一个魔法师教会我一种魔法。这种魔法可以把我盖好的房子复制一份,做成「镜像」,放在我的背包里。 等我到了海边,就用这个「镜像」,复制一套房子,拎包入住。 是不是很神奇?对应到我们的项目中来,房子就是项目本身,镜像就是项目的复制,背包就是镜像仓库。如果要动态扩容,从仓库中取出项目镜像,随便复制就可以了。Build once,Run anywhere! 不用再关注版本、兼容、部署等问题,彻底解决了「上线即崩,无休止构建」的尴尬。 二、虚拟机与容器 开始之前,我们来做一些基础知识的储备: 1.虚拟机:虚拟化硬件 虚拟机 Virtual Machine 指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。在实体计算机中能够完成的工作在虚拟机中都能够实现。 在计算机中创建虚拟机时,需要将实体机的部分硬盘和内存容量作为虚拟机的硬盘和内存容量。每个虚拟机都有独立的 CMOS、硬盘和操作系统,可以像使用实体机一样对虚拟机进行操作。在容器技术之前,业界的网红是虚拟机。 虚拟机技术的代表,是 VMWare 和 OpenStack。更多请参看百科虚拟机。 2.容器:将操作系统层虚拟化,是一个标准的软件单元 随处运行:容器可以将代码与配置文件和相关依赖库进行打包,从而确保在任何环境下的运行都是一致的。 高资源利用率:容器提供进程级的隔离,因此可以更加精细地设置 CPU 和内存的使用率,进而更好地利用服务器的计算资源。 快速扩展:每个容器都可作为单独的进程予以运行,并且可以共享底层操作系统的系统资源,这样一来可以加快容器的启动和停止效率。 3.区别与联系 虚拟机虽然可以隔离出很多「子电脑」,但占用空间更大,启动更慢。虚拟机软件可能还要花钱,例如VMWare; 容器技术不需要虚拟出整个操作系统,只需要虚拟一个小规模的环境,类似「沙箱」; 运行空间,虚拟机一般要几 GB 到 几十 GB 的空间,而容器只需要 MB 级甚至 KB 级; 我们来看一下对比数据: 特性虚拟机容器 隔离级别操作系统级进程 隔离策略Hypervisor(虚拟机监控器)Cgroups(控制组群) 系统资源5 ~ 15%0 ~ 5% 启动时间分钟级秒级 镜像存储GB - TBKB - MB 集群规模上百上万 高可用策略备份、容灾、迁移弹性、负载、动态 与虚拟机相比,容器更轻量且速度更快,因为它利用了 Linux 底层操作系统在隔离的环境中运行。虚拟机的 Hypervisor 创建了一个非常牢固的边界,以防止应用程序突破它,而容器的边界不那么强大。 物理机部署不能充分利用资源,造成资源浪费。虚拟机方式部署,虚拟机本身会占用大量资源,导致资源浪费,另外虚拟机性能也很差。而容器化部署比较灵活,且轻量级,性能较好。 虚拟机属于虚拟化技术,而 Docker 这样的容器技术,属于轻量级的虚拟化。 三、认识 Docker 1.概念 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。 Docker 技术的三大核心概念,分别是:镜像 Image、容器 Container、仓库 Repository。 2.Docker 轻量级的原因? 相信你也会有这样的疑惑:为什么 Docker 启动快?如何做到和宿主机共享内核? 当我们请求 Docker 运行容器时,Docker 会在计算机上设置一个资源隔离的环境。然后将打包的应用程序和关联的文件复制到 Namespace 内的文件系统中,此时环境的配置就完成了。之后 Docker 会执行我们预先指定的命令,运行应用程序。 镜像不包含任何动态数据,其内容在构建之后也不会被改变。 四、核心概念 1.Build, Ship and Run(搭建、运输、运行); 2.Build once, Run anywhere(一次搭建,处处运行); 3.Docker 本身并不是容器,它是创建容器的工具,是应用容器引擎; 4.Docker 三大核心概念,分别是:镜像 Image,容器 Container、仓库 Repository; 5.Docker 技术使用 Linux 内核和内核功能(例如 Cgroups 和 namespaces)来分隔进程,以便各进程相互独立运行。 6.由于 Namespace 和 Cgroups 功能仅在 Linux 上可用,因此容器无法在其他操作系统上运行。那么 Docker 如何在 macOS 或 Windows 上运行? Docker 实际上使用了一个技巧,并在非 Linux 操作系统上安装 Linux 虚拟机,然后在虚拟机内运行容器。 7.镜像是一个可执行包,其包含运行应用程序所需的代码、运行时、库、环境变量和配置文件,容器是镜像的运行时实例。 更多关于 Docker 的原理,可以查看 Docker 工作原理及容器化简易指南,这里不再赘述。 五、安装 Docker 1.命令行安装 Homebrew 的 Cask 已经支持 Docker for Mac,因此可以很方便的使用 Homebrew Cask 来进行安装,执行如下命令: 1 brew cask install docker 更多安装方式,请查看官方文档:安装 Docker 2.查看版本 1 docker -v 3.配置镜像加速 设置 Docker Engine 写入配置: 1 2 3 4 5 6 7 8 9 { "registry-mirrors": [ "http://hub-mirror.c.163.com/", "https://registry.docker-cn.com" ], "insecure-registries":[], "experimental": false, "debug": true } 4.安装桌面端 桌面端操作非常简单,先去官网下载。通过 Docker 桌面端,我们可以方便的操作: 1.clone:克隆一个项目 2.build:打包镜像 3.run:运行实例 4.share:共享镜像 好了,准备工作就绪,下面可以大展身手了! 六、快速开始 安装完 Docker 之后,我们先打个实际项目的镜像,边学边用。 1.首先需要大致了解一下我们将会用到的 11 个命令 命令描述 FROM基于哪个镜像来实现 MAINTAINER镜像创建者 ENV声明环境变量 RUN执行命令 ADD添加宿主机文件到容器里,有需要解压的文件会自动解压 COPY添加宿住机文件到容器里 WORKDIR工作目录 EXPOSE容器内应用可使用的端口 CMD容器启动后所执行的程序,如果执行 docker run 后面跟启动命令会被覆盖掉 ENTRYPOINT与 CMD 功能相同,但需 docker run 不会覆盖,如果需要覆盖可增加参数 -entrypoint 来覆盖 VOLUME数据卷,将宿主机的目录映射到容器中的目录 2.新建项目 为了快捷,我们直接使用Vue 脚手架构建项目: 1 vue create docker-demo 尝试启动一下: 1 yarn serve 访问地址:http://localhost:8080/。项目就绪,我们接着为项目打包: 1 yarn build 这时候,项目目录下的 Dist 就是我们要部署的静态资源了,我们继续下一步。 需要注意:前端项目一般分两类,一类直接 Nginx 静态部署,一类需要启动 Node 服务。本节我们只考虑第一种。关于 Node 服务,下文我会详细说明。 3.新建 Dockerfile 1 cd docker-demo && touch Dockerfile 此时的项目目录如下: 1 2 3 4 5 6 7 8 9 10 . ├── Dockerfile ├── README.md ├── babel.config.js ├── dist ├── node_modules ├── package.json ├── public ├── src └── yarn.lock 可以看到我们已经在 docker-demo 目录下成功创建了 Dockerfile 文件。 4.准备 Nginx 镜像 运行你的 Docker 桌面端,就会默认启动实例,我们在控制台拉取 Nginx 镜像: 1 docker pull nginx 控制台会出现如下信息: 1 2 3 4 5 6 7 8 9 10 Using default tag: latest latest: Pulling from library/nginx 8559a31e96f4: Pull complete 8d69e59170f7: Pull complete 3f9f1ec1d262: Pull complete d1f5ff4f210d: Pull complete 1e22bfa8652e: Pull complete Digest: sha256:21f32f6c08406306d822a0e6e8b7dc81f53f336570e852e25fbe1e3e3d0d0133 Status: Downloaded newer image for nginx:latest docker.io/library/nginx:latest 如果你出现这样的异常,请确认 Docker 实例是否正常运行。 1 Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? 镜像准备 OK,我们在根目录创建 Nginx 配置文件: 1 touch default.conf 写入: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 server { listen 80; server_name localhost; #charset koi8-r; access_log /var/log/nginx/host.access.log main; error_log /var/log/nginx/error.log error; location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } 5.配置镜像 打开 Dockerfile ,写入如下内容: 1 2 3 FROM nginx COPY dist/ /usr/share/nginx/html/ COPY default.conf /etc/nginx/conf.d/default.conf 我们逐行解释一下代码: FROM nginx 指定该镜像是基于 nginx:latest 镜像而构建的; COPY dist/ /usr/share/nginx/html/ 命令的意思是将项目根目录下 dist 文件夹中的所有文件复制到镜像中 /usr/share/nginx/html/ 目录下; COPY default.conf /etc/nginx/conf.d/default.conf 将 default.conf 复制到 etc/nginx/conf.d/default.conf,用本地的 default.conf 配置来替换 Nginx 镜像里的默认配置。 6.构建镜像 Docker 通过 build 命令来构建镜像: 1 docker build -t jartto-docker-demo . 按照惯例,我们解释一下上述代码: -t 参数给镜像命名 jartto-docker-demo . 是基于当前目录的 Dockerfile 来构建镜像 执行成功后,将会输出: 1 2 3 4 5 6 7 8 9 10 Sending build context to Docker daemon 115.4MB Step 1/3 : FROM nginx ---> 2622e6cca7eb Step 2/3 : COPY dist/ /usr/share/nginx/html/ ---> Using cache ---> 82b31f98dce6 Step 3/3 : COPY default.conf /etc/nginx/conf.d/default.conf ---> 7df6efaf9592 Successfully built 7df6efaf9592 Successfully tagged jartto-docker-demo:latest 镜像制作成功!我们来查看一下容器: 1 docker image ls | grep jartto-docker-demo 可以看到,我们打出了一个 133MB 的项目镜像: 1 jartto-docker-demo latest 7df6efaf9592 About a minute ago 133MB 镜像也有好坏之分,后续我们将介绍如何优化,这里可以先暂时忽略。 7.运行容器 1 docker run -d -p 3000:80 --name docker-vue jartto-docker-demo 这里解释一下参数: -d 设置容器在后台运行 -p 表示端口映射,把本机的 3000 端口映射到 container 的 80 端口(这样外网就能通过本机的 3000 端口访问了 --name 设置容器名 docker-vue jartto-docker-demo 是我们上面构建的镜像名字 补充一点: 在控制台,我们可以通过 docker ps 查看刚运行的 Container 的 ID: 1 docker ps -a 控制台会输出: 1 2 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ab1375befb0b jartto-docker-demo "/docker-entrypoint.…" 8 minutes ago Up 7 minutes 0.0.0.0:3000->80/tcp docker-vue 如果你使用桌面端,那么打开 Docker Dashboard 就可以看到容器列表了,如下图: 8.访问项目 因为我们映射了本机 3000 端口,所以执行: 1 curl -v -i localhost:3000 或者打开浏览器,访问:localhost:3000 9.发布镜像 如果你想为社区贡献力量,那么需要将镜像发布,方便其他开发者使用。 发布镜像需要如下步骤: 登陆 [dockerhub](https://hub.docker.com),注册账号; 命令行执行 docker login,之后输入我们的账号密码,进行登录; 推送镜像之前,需要打一个 Tag,执行 docker tag <image> <username>/<repository>:<tag> 全流程结束,以后我们要使用,再也不需要「搬石头、砍木头、画图纸、盖房子」了,拎包入住。这也是 docker 独特魅力所在。 七、常规操作 到这里,恭喜你已经完成了 Docker 的入门项目!如果还想继续深入,不妨接着往下看看。 1.参数使用 FROM 指定基础镜像,所有构建的镜像都必须有一个基础镜像,且 FROM 命令必须是 Dockerfile 的第一个命令 FROM <image> [AS <name>] 指定从一个镜像构建起一个新的镜像名字 FROM <image>[:<tag>] [AS <name>] 指定镜像的版本 Tag 示例:FROM mysql:5.0 AS database MAINTAINER 镜像维护人的信息 MAINTAINER <name> 示例:MAINTAINER Jartto Jartto@qq.com RUN 构建镜像时要执行的命令 RUN <command> 示例:RUN ["executable", "param1", "param2"] ADD 将本地的文件添加复制到容器中去,压缩包会解压,可以访问网络上的文件,会自动下载 ADD <src> <dest> 示例:ADD *.js /app 添加 js 文件到容器中的 app 目录下 COPY 功能和 ADD 一样,只是复制,不会解压或者下载文件 CMD 启动容器后执行的命令,和 RUN 不一样,RUN 是在构建镜像是要运行的命令 当使用 docker run 运行容器的时候,这个可以在命令行被覆盖 示例:CMD ["executable", "param1", "param2"] ENTRYPOINT 也是执行命令,和 CMD 一样,只是这个命令不会被命令行覆盖 ENTRYPOINT ["executable", "param1", "param2"] 示例:ENTRYPOINT ["donnet", "myapp.dll"] LABEL:为镜像添加元数据,key-value 形式 LABEL <key>=<value> <key>=<value> ... 示例:LABEL version="1.0" description="这是一个web应用" ENV:设置环境变量,有些容器运行时会需要某些环境变量 ENV <key> <value> 一次设置一个环境变量 ENV <key>=<value> <key>=<value> <key>=<value> 设置多个环境变量 示例:ENV JAVA_HOME /usr/java1.8/ EXPOSE:暴露对外的端口(容器内部程序的端口,虽然会和宿主机的一样,但是其实是两个端口) EXPOSE <port> 示例:EXPOSE 80 容器运行时,需要用 -p 映射外部端口才能访问到容器内的端口 VOLUME:指定数据持久化的目录,官方语言叫做挂载 VOLUME /var/log 指定容器中需要被挂载的目录,会把这个目录映射到宿主机的一个随机目录上,实现数据的持久化和同步。 VOLUME ["/var/log","/var/test".....] 指定容器中多个需要被挂载的目录,会把这些目录映射到宿主机的多个随机目录上,实现数据的持久化和同步 VOLUME /var/data var/log 指定容器中的 var/log 目录挂载到宿主机上的 /var/data 目录,这种形式可以手动指定宿主机上的目录 WORKDIR:设置工作目录,设置之后 ,RUN、CMD、COPY、ADD 的工作目录都会同步变更 WORKDIR <path> 示例:WORKDIR /app/test USER:指定运行命令时所使用的用户,为了安全和权限起见,根据要执行的命令选择不同用户 USER <user>:[<group>] 示例:USER test ARG:设置构建镜像是要传递的参数 ARG <name>[=<value>] ARG name=sss 更多操作,请移步官方使用文档。 八、最佳实践 在掌握 Docker 常规操作之后,我们很容易就可以打出自己想要的项目镜像。然而不同的操作打出的镜像也是千差万别。 究竟是什么原因导致镜像差异,我们不妨继续探索。 以下是在应用 Docker 过程中整理的最佳实践,请尽量遵循如下准则: 1.Require 明确:需要什么镜像 2.步骤精简:变化较少的 Step 优先 3.版本明确:镜像命名明确 4.说明文档:整个镜像打包步骤可以重现 推荐如下两篇文章: Intro Guide to Dockerfile Best Practices Best practices for writing Dockerfiles 九、总结 容器化技术必将是云时代不可或缺的技能之一,而 Docker 只是沧海一粟。随之而来的还有集群容器管理 K8s、Service Mesh 、Istio 等技术。打开 Docker 的大门,不断抽丝剥茧,逐层深入,你将感受到容器化的无穷魅力。 赶快打开技能边界,为你的前端技术赋能吧!

2020/7/4
articleCard.readMore

将博客搬至 CSDN

为了更好的服务小伙伴,本博客将同步至 CSDN,欢迎大家关注账号:Jartto。本站点内容均属原创,如有需求,请联系本人。 本站点依旧同步更新,欢迎持续关注,一起探索前端领域精彩内容。

2020/5/25
articleCard.readMore

WebP 方案分析与实践

对于网站来说,图片始终扮演着重要角色。图片大小直接影响网站速度、流量、运营成本以及用户体验。因此,减少图片大小成为网站优化最重要的一个环节。 如果你对优化还不甚了解,推荐阅读如下文章: 1.网站优化实战 2.网站优化工具 3.CSS 渲染原理以及优化策略 4.网站性能指标 - FMP 5.聚焦 Web 性能指标 TTI 6.Web「性能测试」知多少? 一、背景 当我们在做网站性能优化的时候,减少图片大小,意味着减少了网络传输,提升了网站加载速度,而这部分也是性价比最高的。 我们可以通过压缩图片来减少体积,但压缩比例一直是前端开发和设计师争执的焦点。压缩比例大的话,可以有效减少图片体积,对页面加载有利,但是却损失了像素,是设计师无法容忍的。 在这种矛盾的场景下,我们既要最大程度的压缩图片又要保持足够的清晰,WebP 便应运而生。 值得注意的是:WebP 并不是新技术,而是受限于兼容性而未全面普及。 二、目标与意义 1.时间成本 我们先来看一组数据对比图,如果你做过 Gif 动图,你肯定知道下面这样的处理意义有多大: 在肉眼无法识别差异的前提下,图片大小减少了 88%。 2.带宽成本 YouTube 的视频略缩图采用 WebP 格式后,网页加载速度提升了 10%; 谷歌的 Chrome 网上应用商店采用 WebP 格式图片后,每天可以节省几 TB 的带宽,页面平均加载时间大约减少 1/3; Google+ 移动应用采用 WebP 图片格式后,每天节省了 50TB 数据存储空间。 目前 Google、Facebook、阿里、京东的等国内外互联网公司广泛应用了 WebP,超过 70% 的浏览器已经支持 WebP。 三、什么是 WebP ? WebP 格式,谷歌开发的一种旨在加快图片加载速度的图片格式。 优势在于它具有更优的图像数据压缩算法,在拥有肉眼无法识别差异的图像质量前提下,带来更小的图片体积,同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都非常优秀、稳定和统一。 在尝试一些技术前,我们必须要充分了解他的兼容性,如下图: 可以看出来,绝大多数浏览器已经有了较好的支持。当然,除了 Safari 和 IE!我们来看看浏览器市场占有率吧: Safari 和 Foxmail 也在进行支持 WebP 的测试。 四、应用场景 WebP 看起来不错,那么它究竟适合什么样的场景呢?不着急,我们先来看下面的图例: 从上面我们可以看出,适合 WebP 场景的站点不外乎如下几种场景: 1.网站图片比重大 如果你的网站 80% 甚至更多依赖图片资源,那么请使用 WebP,资源成本可以较少 50% 以上。 2.细节要求不高 图片起到占位目的,不需要毛孔级别,那么请大胆使用! 3.流量运营推广 运营推广都是抢占先机,我们不但要和竞品比拼用户,更要快速响应,提高转化率。 4.视频首图 视频内容形式的网站,资源存储必是一大难题。如何做到稳准狠,速度必不可少,大量的视频占位图也便成为了重中之重。 五、实践方案 相信到这里,你已经对 WebP 有了足够的了解,快来看看实际项目中我们是如何使用的吧~ 方式一:HTML5 Picture Picture 元素允许我们在不同的设备上显示不同的图片,一般用于响应式。 1 2 3 4 <picture> <source type="image/webp" srcset="images/jartto.webp"> <img src="images/jartto.jpg" alt=“jartto’s demo"> </picture> Picture 算是最简单易行的方案了,但是需要注意以下两点: 1.兼容性,IE 不支持,可以查看Picutre Element; 2.老项目迁移成本较大,需要改动每一个 IMG 资源,请尽可能封装成组件; 方式二:Webpack + Nginx 此方案在 Webpack 打包过程生成了 .webp 格式的图片,通过 Nginx 检测浏览器 Accept 是否包含 image/webp 而进行动态转发。 完整格式:accept: image/webp,image/apng,image/,/*;q=0.8 1.项目引入 Webpack 插件 imagemin-webp-webpack-plugin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const ImageminWebpWebpackPlugin = require('imagemin-webp-webpack-plugin’); plugins: [ new ImageminWebpWebpackPlugin({ config: [ { test: /\.(jpe?g|png)$/, options: { quality: 60, } } ], overrideExtension: false, detailedLogs: true, strict: false }) ], 2.Nginx 配置 检测 Accept 头中是否含有 WebP 字段: 1 2 3 4 map $http_accept $webp_suffix { default ""; "~*webp" ".webp"; } 如果浏览器支持 WebP 格式,那么我们就将 .png 或者 .jpg 格式图片转发到 .webp 格式下。 1 2 3 4 5 6 7 location ~* ^/_nuxt/img/(.+\.png|jpe?g)$ { rewrite ^/_nuxt/img/(.+\.png|jpe?g)$ /$1 break; root /apps/srv/instance/test-webp.jartto.wang/.nuxt/dist/client/img/; add_header Vary Accept; try_files $uri$webp_suffix $uri =404; expires 30d; } 设置完 Nginx 转发规则后,记得 Reload Nginx。刷新浏览器,这时候浏览器中的图片 Type 类型已经变成了 WebP 格式。 方式三:服务端 Nginx PageSpeed 模块 Google 开发的 PageSpeed 模块有一个功能,会自动将图像转换成 WebP 格式或者是浏览器所支持的其它格式。 安装 Nginx 模块请查看文档:Build ngx_pagespeed From Source 安装成功之后,需要在 Nginx Config 中添加如下内容: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 pagespeed on; pagespeed FileCachePath "/var/cache/ngx_pagespeed/"; # pagespeed RewriteLevel OptimizeForBandwidth; pagespeed XHeaderValue "Powered By Jartto"; pagespeed EnableFilters convert_gif_to_png; pagespeed EnableFilters convert_png_to_jpeg; pagespeed EnableFilters convert_jpeg_to_webp; pagespeed ImageRecompressionQuality 10; # pagespeed EnableFilters convert_jpeg_to_progressive; # pagespeed EnableFilters inline_images; location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" { add_header "" ""; } location ~ "^/pagespeed_static/" { } location ~ "^/ngx_pagespeed_beacon$" { } 看起来此方案是最佳选择,既没有前端代码侵入,也不需要各种嗅探,服务端一个模块就搞定了。需要注意的是,当我们享受便利的同时,一定要明确具体的原理,多维度思考。 既然在服务端完成 WebP 格式的转化,那么一定要注意此操作对服务器的性能损耗。我们不妨试一下,通过 Wrk 对服务器施压,看一下服务器的并发性能。 具体的压测过程就不细说了,感兴趣的童鞋可以查看:Web 性能测试。我们直接上结论: 相比转码,相同 QPS 下 Nginx 对 CPU 的使用上升了 70%,页面提升时间在毫秒量级,性价比不高。 方式四:Nginx + Lua(OpenResty) 先来科普一下:OpenResty 是一个强大的 Web 应用服务器,Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,更主要的是在性能方面,OpenResty 可以快速构造出足以胜任 10K 以上并发连接响应的超高性能 Web 应用系统。 Lua 是一种轻量小巧的脚本语言,用标准 C 语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。 1.编写 Lua 脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function file_exists(name) local f=io.open(name,"r") if f~=nil then io.close(f) return true else return false end end local newFile = ngx.var.request_filename; local originalFile = newFile:sub(1, #newFile - 5); if not file_exists(originalFile) then ngx.exit(404); return; end os.execute("cwebp -q 75 " .. originalFile .. " -o " .. newFile); if file_exists(newFile) then ngx.exec(ngx.var.uri) else ngx.exit(404) end 不考虑学习成本的话,Nginx + Lua 将会是最好的选择。 2.配置 Nginx 文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # http 中加入,lua 脚本的搜索路径 lua_package_path "/usr/local/openresty/nginx/conf/jartto/?.lua;"; # server 中配置 location location /images { expires 365d; # 如果不存在,则通过 @webp 进行内部重定向 try_files $uri $uri/ @webp; } location @webp{ # 图片访问地址 if ($uri ~ "/([a-zA-Z0-9-_]+)\.(png|jpg|gif)\.webp") { # 查找执行的 lua 脚本 content_by_lua_file "/usr/local/nginx/conf/jartto/webp.lua"; } } 补充:浏览器嗅探(阿里云 OSS 方式) 1.本地嗅探: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // check_webp_feature: // 'feature' can be one of 'lossy', 'lossless', 'alpha' or 'animation'. // 'callback(feature, result)' will be passed back the detection result (in an asynchronous way!) function check_webp_feature(feature, callback) { const kTestImages = { lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA", lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==", alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==", animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA" }; let img = new Image(); img.onload = function () { const result = (img.width > 0) && (img.height > 0); callback(feature, result); }; img.onerror = function () { callback(feature, false); }; img.src = `data:image/webp;base64${kTestImages[feature]}`; } 2.调用方式 1 2 3 4 5 6 // Jartto's Demo check_webp_feature('lossy', function (feature, isSupported) { if (isSupported) { // webp is supported, you can cache the result here if you want } }); 请注意,图像加载是非阻塞且异步的。 这意味着依赖于 WebP 支持的任何代码最好都应放在回调函数中。 六、部署模型 服务端部署模型需要简单了解一下,这样我们就可以根据不同的实际情况从上述四种方案中进行选择了。 注意,如果你需要对 Nginx 进行配置,请不要操作 Load Balancer 层,尽量在应用服务器层操作。 七、技术揭秘 听起来很酷,那么 WebP 究竟使用了什么很魔法,我们来揭秘一下: 1.分块 MacroBlocking 将图片划分成多个宏块 Macro Blocks,典型的宏块由一个 16×16 的亮度像素 luma pixel 块和两个 8×8 的色度像素 chroma pixel 块组成。 分块越小,预测越准,需要记录的信息也越多。 一般来说,细节越丰富的地方,分块越细,即使用 4×4 分块预测。细节相对不丰富的地方使用 16×16 分块。 2.帧内预测 WebP 有损压缩使用了帧内预测编码,这一技术也被用于 VP8 视频编码中的关键帧压缩。 VP8 有四种常见的帧内预测模型: H_PRED(horizontal prediction):像素块中每一行使用其左边一列 Col L 的数据填充; V_PRED(vertical prediction):像素块中每一列使用其上边一行 Row A 的数据填充; DC_PRED(DC prediction):像素块中每个单元使用 Row A 和 Col L 的所有像素的平均值填充; TM_PRED(TrueMotion prediction):混合式,接近真实数据; 使用哪种分块预测模式是动态决定的。 编码器会将所有可能的预测模式都计算出来,然后选出错误程度最小的模式。 3.算法编码 WebP 使用 Arithmetic entropy encoding,该算法相比 JPEG 上使用的 Huffman encoding,在压缩表现上更出色。 深入研究,请移步: 1.Compression Techniques 2.WebP 有损压缩的编码过程 八、4种常见压缩格式 Lossy 有损压缩基于 VP8 关键帧编码。 VP8 是 一种视频压缩格式,是 VP6 和 VP7 格式的后继格式。 Lossless 无损压缩格式由 WebP 团队开发。 Alpha 8 位 Alpha 通道对于图形图像很有用。 Alpha 通道可与有损 RGB 一起使用,该功能目前无法在任何其他格式下使用。 Animation 它支持真彩色动画图像。 24 位色被称为真彩色,它可以达到人眼分辨的极限,发色数是 1677 万多色,也就是 2 的 24 次方。 九、WebP 方案 ROI 一般在做技术选型的过程中,我们都要评估引入技术的收益,这也就是我们常说的 ROI。那么如果你决定要使用 WebP,以下的数据可能会对你产生帮助: 1.优化后,网站资源大小从 2.6MB 减少到 1.5MB; 2.图片大小减少到原来的 1/8; 3.同一时间服务器请求数增加 335%,请求时长减少 75%; 4.Lighthouse Performance 评分 97,FCP,FMP大概在 0.5-0.7s(和其他优化有关); 网站数据可能不尽相同,以上数据仅供参考。 十、总结 上文 我们从 WebP 背景展开,介绍了它的兼容性、应用场景,以及 4 种实践方案,同时也提到了浏览器如何嗅探 WebP 格式。后半部分主要从原理出发,了解 WebP 格式的算法细节以及压缩格式,由浅入深,逐层剖析。 文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~

2020/5/2
articleCard.readMore

Web「性能测试」知多少?

身为前端的你,是否会有这样的烦恼:随着访问用户的成倍增加,站点变得越来越脆弱。任何的访问过慢或崩溃都将是一场灾难。 这就对我们工程师提出了更高的要求,要保障网站的「可访问性」和「稳定性」都维持在一个较高水平。那么,是时候了解了解 Web 性能测试了! 一、情景再现 有一个大型推广活动来了,类似与抢火车票、淘宝双十一,你能否回答 Boss 的如下问题? 1.我们的网站是否能扛住如此的高并发? 2.服务器单机 QPS 是多少? 3.如果站点扛不住,扩容的话,需要几台? … 一连串的问题,如果你招架不住,不妨仔细阅读本文。 二、什么是性能测试? 要回答上面的问题,需要我们有一些知识储备。不着急,循序渐进,各个击破。 一般来说「性能测试」包括压力测试、负载测试、容量测试三种主要测试类型。 1.压力测试 StressTest 压力测试可以测试网站在某个特定的持续的压力下运行的稳定性。 2.负载测试 LoadTest 负载测试是为了检验系统在给定负载下是否能达到预期性能指标。 3.容量测试 CapabilityTest 容量测试针对数据库而言,是在数据库中有较大数量的数据记录情况下对系统进行的测试。 内容比较多,为了专注聚焦,我们本节主要来看一下压力测试。 三、压力测试 压力测试是通过不断向被测系统施加压力,测试系统在压力情况下的性能表现。主要考察当前软硬件环境下系统所能承受的最大负荷并帮助开发人员找出系统瓶颈所在。我们可以模拟巨大的流量请求以查看应用程序在峰值使用情况以及服务器状况。 有效的压力测试将应用以下这些关键条件:重复,并发,量级,随机变化。 需要注意的是:压力测试并不会报告是什么导致了问题。它只会报告这有了问题,例如:查询页面在并发 1000 个用户使用时变慢下来,但它不会显示什么导致了变慢。 捕获到的性能统计数据例如 CPU 和内存使用量只是强调了潜在的问题区域,但并不会指出实际的根源在应用程序的什么地方。 更多的概念可以查看:为什么要进行压力测试?。 四、核心指标 了解了上述压力测试之后,我们先不着急进行网站压测,补充几个可以让你事半功倍的核心指标: 1.什么是 TPS ? 即 Transactions Per Second 的缩写,每秒处理的事务数目。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数,最终利用这些信息作出的评估分。 一个事务可能对应多个请求,这与数据库的事务操作极其相似。 2.什么是 QPS ? Queries Per Second 的缩写,每秒能处理查询数目,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。 需要注意的是:虽然名义上是查询的意思,但实际上,现在习惯于对单一接口服务的处理能力用 QPS 进行表述(即使它并不是查询操作)。 3.什么是 RT ? 响应时间,处理一次请求所需要的平均处理时间。我们一般会关注 90th 请求的的处理时间,因为可能因网络情况出现极端情况,长尾数据会对我们产生干扰。 4.系统 CPU 利用率 如果系统的 CPU 使用率已经很高,说明我们的系统是个计算度很复杂的系统,这时候如果 QPS 已经上不去了,就需要赶紧扩容,通过增加机器分担计算的方式来提高系统的吞吐量。 5.系统内存 如果 CPU 使用率一般,但是系统的 QPS 上不去,说明我们的机器并没有忙于计算,而是受到其他资源的限制,如内存、I/O。这时候首先看下内存是不是已经不够了,如果内存不够了,那就赶紧扩容了。 五、QPS 如何计算? QPS 并没有准确的计算公式,但是实际压测中我们完全可以按照如下模型进行估算: 原理:每天 80% 的访问集中在 20% 的时间里,这 20% 时间叫做「峰值时间」。 公式:( 总 PV 数 80% ) / ( 每天秒数 20% ) = 峰值时间每秒请求数(QPS) 机器:峰值时间每秒 QPS / 单台机器的 QPS = 需要的机器 问:每天 300w PV 的在单台机器上,这台机器需要多少 QPS? 答:( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS) 问:如果一台机器的 QPS 是 58 ,需要几台机器来支持? 答:139 / 58 = 3 具体的计算公式可以参考这篇文章:峰值 QPS 和机器计算公式。 六、推荐工具 压测工具有很多,JMeter,LoadRunner,WebLoad,NeoLoad,Loadster,TcpCopy,AB,WebBench 等等,恐怕一时间无法说完。但是论起上手能力,就要说说我们的主角 wrk 了。 wrk 是一款针对 Http 协议的基准测试工具,它能够在单机多核 CPU 的条件下,使用系统自带的高性能 I/O 机制,如 Epoll,Kqueue 等,通过多线程和事件模式,对目标机器产生大量的负载。 有多容易,我们不妨试试看? 1.安装 1 2 3 git clone https://github.com/wg/wrk.git cd wrk make 注意使用 ./wrk 命令启动。 2.基本使用 1 wrk -t12 -c400 -d30s http://127.0.0.1:8080/jartto 参数说明: -c:HTTP 连接数,每一个线程处理 N = 连接数/线程数 -d:持续时间,2s,2m,2h -t:总的线程数 -s:脚本,可以是 Lua 脚本 -H:增加 HTTP header,例如:User-Agent: jartto --latency:输出时间统计的细节 --timeout:超时时间 3.输出 上面我们使用 12 线程,保持打开 400 个 Http 连接,执行 30s。脚本运行完毕会输出: 1 2 3 4 5 6 7 8 Running 30s test @ http://127.0.0.1:8080/jartto 12 threads and 400 connections Thread Stats Avg Stdev Max +/- Stdev Latency 635.91us 0.89ms 12.92ms 93.69% Req/Sec 56.20k 8.07k 62.00k 86.54% 22464657 requests in 30.00s, 17.76GB read Requests/sec: 748868.53 Transfer/sec: 606.33MB 输出说明: Latency:响应时间 Req/Sec:每个线程每秒钟的完成的请求数 Avg:平均 Max:最大 Stdev:标准差 +/- Stdev: 正负一个标准差占比 标准差大说明样本离散程度高,系统性能波动大。 在压测过程中,一般线程数不宜过多,CPU 核数的 2-4 倍就可以了。 太多反而因为线程切换过多造成效率降低, 因为 wrk 不是使用每个连接一个线程的模型, 而是通过异步网络 I/O 提升并发量。 所以网络通信不会阻塞线程执行,这也是 wrk 可以用很少的线程模拟大量网路连接的原因。 七、高级定制 到这里,相信文章开头提出的问题,你已经可以很好的回答了。我们不妨继续升级,来一些高级定制。可能很多童鞋注意到了上面文档中提到的 -s 参数了,我们先看看官方文档: 1 2 An optional LuaJIT script can perform HTTP request generation, response processing, and custom reporting. LuaJIT 脚本可以执行 HTTP 请求生成,响应处理和自定义报告。这里是 Lua 的一些案例。鉴于篇幅过长,本节我们先了解到这里,精彩部分我们就留到下一篇继续探讨吧! 文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~

2020/4/5
articleCard.readMore

网站性能指标 - FMP

对于前端童鞋来说,优化是一个永恒的话题。上线并不是网站开发的终点,如何吸引用户、提升用户体验,应该成为我们 F2E 的追求目标。 如果你对优化还不了解,建议先阅读如下两篇文章: 网站优化实战 网站优化,这些工具你一定用得着 当然,衡量网站性能的指标有很多,今天我们就来看一个「令人费解而又头疼」的指标 - FMP。 First Meaningful Paint 是指页面的首要内容出现在屏幕上的时间。 一、FMP 之怪现象 目前尚无标准化的 FMP 定义,因此也没有性能条目类型。 部分原因在于很难以通用的方式确定「有效」对于所有页面意味着什么。但是,一般来说,在单个页面或单个应用中,最好是将 FMP 视为主角元素呈现在屏幕上的时刻。 所以,我们经常会面临这样的问题: FCP 在可接受范围,但是 FMP 却完全失控。 也可能是这样的问题: 或许还有这样的问题: 为什么结构类似的站点,FMP 加载却千差万别。要了解 FMP 我们需要知道它的计算规则,下面让我们一层层抽丝剥茧。 二、刨根问底 究竟是什么导致 FMP 的时机差距如此之大?或许我们可以从 FMP 定义来说起。 When FMP and FCP are the same time in seconds, they share the same identical score. If FMP is slower than FCP, say when there's iframe content loading, then the FMP score will be lower than the FCP score. 什么意思呢,我们先来看一张官方图片: 如果 FCP 是 1.5s,FMP 是 3s,那么 FCP 分数将会是 99,但是 FMP 分数将是 75。 除了上述影响外,我们还需要关注 Lighthouse V3 的记分规则: 虽然 FMP 权重仅为 1 ,很遗憾,因为如上规则的存在,我们站点无法到达满分💯。 确定页面上最关键的主角元素之后,我们应确保初始脚本加载仅包含渲染这些元素并使其可交互所需的代码。 相关源码: Lighthouse FMP 源码 Score a perfect 100 in Lighthouse audits 三、重要结论 了解了相关计算规则之后,我们继续来剖析 FMP。对于不同的站点,首要内容是不同的,例如: 对于博客文章:「大标题」 + 「首屏文字」是首要内容; 对于百度或者 Google 的搜索结果页:「首屏的结果」就是首要内容; 对于淘宝等购物网站来说,图片会极为重要,因此它是首要内容; 需要注意的是,通常首要内容是不包括 Headers 和导航条的。 为了方便理解,我们用一个简单的公式表示:「首次有效绘制 = 具有最大布局变化的绘制」。 很好,此刻我们不用纠结「首次有效渲染」了,转而去了解「最大布局变化的绘制」。基于 Chromium 的实现,这个绘制是使用 LayoutAnalyzer 进行计算的,它会收集所有的布局变化,当布局发生最大变化时得出时间。 具有最大布局变化的绘制如何计算呢? 1.侦听页面元素的变化; 2.遍历每次新增的元素,并计算这些元素的得分总; 3.如果元素可见,得分为 1 * weight,如果元素不可见,得分为 0; 还是很抽象,我们继续探索 LayoutAnalyzer,在源码中得到如下公式: 布局显著性 = 添加的对象数目 / max(1, 页面高度 / 屏幕高度) 这下清晰了,既然可以算出来,那么优化 FMP 指日可待。从上面可以看出来「布局显著性」是通过添加对象数目与页面高度来计算的。似乎对象数目成了解决问题的关键节点。 我们继续探索,如何去找到对象数目呢?很简单,打开 Chrome DevTool,切到 Layout 面板。如果你还不会使用 Layout 面板,可以先看看网站优化工具。 这里就不展开了,直接上图,看看红框部分: 惊不惊喜,我们精简 DOM 似乎可以将公式中分子变小,或者让页面高度大于屏幕高度。到这里,所有谜团都解开了,优化 FMP 也就变得毫无挑战了。 相关源码: LayoutAnalyzer 源码 Tracing Results Understanding about:tracing results 五、总结 我们从扑簌迷离的 FMP 表象一层层找到了 Lighthouse 的记分规则,又从 Tracing Results 中得知最大布局变化的计算规则,因此转向 LayoutAnalyzer 源码研究,最终找到 Layout Objects,从而解决了问题。虽然波折,但结局令人舒适。 最后,再啰嗦一下,以下结论对理解 FMP 很重要: 对于博客文章:「大标题」 + 「首屏文字」是首要内容; 对于百度或者 Google 的搜索结果页:「首屏的结果」就是首要内容; 对于淘宝等购物网站来说,图片会极为重要,因此它是首要内容; 如果你觉得还是过于复杂,不妨去试试 SSR 吧,FMP 将不再是烦恼。 文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~

2020/3/30
articleCard.readMore

聚焦 Web 性能指标 TTI

如果你经常做网站优化,可能会陷入一个性能指标的泥潭即「面向指标优化」。真正的用户体验从来不是指标决定,相反它应该最真实的反映用户行为。 所以本节我们就来研究 TTI(Time to Interactive),话题展开之前,我们先来了解一些背景知识。 一、RAIL 模型 RAIL 是一种以用户为中心的性能模型。每个网络应用均具有与其生命周期有关的四个不同方面,且这些方面以不同的方式影响着性能: 1.响应:输入延迟时间(从点按到绘制)小于 100 毫秒。 用户点按按钮(例如打开导航)。 2.动画:每个帧的工作(从 JS 到绘制)完成时间小于 16 毫秒。 用户滚动页面,拖动手指(例如,打开菜单)或看到动画。 拖动时,应用的响应与手指位置有关(例如,拉动刷新、滑动轮播)。 此指标仅适用于拖动的持续阶段,不适用于开始阶段。 3.空闲:主线程 JS 工作分成不大于 50 毫秒的块。 用户没有与页面交互,但主线程应足够用于处理下一个用户输入。 4.加载:页面可以在 1000 毫秒内就绪。 用户加载页面并看到关键路径内容。 如果要提升网站用户体验,RAIL 是个不错的评估模型。 二、解读 TTI(页面可交互时间) TTI 指的是应用既在视觉上都已渲染出了,可以响应用户的输入了。要了解 TTI,我们需要知道它的计算规则,先来看下面这张图: 在官方文档中找到了如下描述: First Idle is the first early sign of time where the main thread has come at rest and the browser has completed a First Meaningful Paint. Time to Interactive is after First Meaningful Paint. The browser’s main thread has been at rest for at least 5 seconds and there are no long tasks that will prevent immediate response to user input. 我们可以简单的理解一下: 1.First Idle 是主线程处于静止状态且浏览器已完成 First Meanfulful Paint 的第一个早期迹象; 2.TTI 在 FMP 之后,浏览器主线程静止至少 5s,并且没有可以阻断用户交互响应的「长任务」。 如果你对 FMP 还不了解,不妨先看看这篇文章:网站性能指标 - FMP。除此之外,第二条中提到的「长任务」又是什么呢? 三、Long Task(长任务) 对于「长任务」,我们通过如下图示说明: 对于用户而言,任务耗时较长表现为滞后或卡顿,而这也是目前网页不良体验的主要根源。 如何测量 Long Task? 1 2 3 4 5 6 7 8 9 // Jartto's Demo const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { // TODO... console.log(entry); } }); observer.observe({entryTypes: ['longtask']}); 控制台输出结果如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { "name": "self", "entryType": "longtask", "startTime": 315009.59500001045, "duration": 99.9899999878835, "attribution": [ { "name": "unknown", "entryType": "taskattribution", "startTime": 0, "duration": 0, "containerType": "window", "containerSrc": "", "containerId": "", "containerName": "" } ] } Long Tasks API 可以将任何耗时超过 50 毫秒的任务标示为可能存在问题,并向应用开发者显示这些任务。 选择 50 毫秒的时间是为了让应用满足在 100 毫秒内响应用户输入的 RAIL 指导原则。 实际开发过程中,我们可以通过一个 hack 来检查页面中「长任务」的代码: 1 2 3 4 5 6 7 8 9 10 11 // detect long tasks hack (function detectLongFrame() { let lastFrameTime = Date.now(); requestAnimationFrame(function() { let currentFrameTime = Date.now(); if (currentFrameTime - lastFrameTime > 50) { // Report long frame here... } detectLongFrame(currentFrameTime); }); }()); 四、如何计算 TTI? 在计算之前,我们先来看一下 Timing API: Google 官方文档中有一段描述: Note: Lower Bounding FirstInteractive at DOMContentLoadedEndDOMContentLoadedEnd is the point where all the DOMContentLoaded listeners finish executing. It is very rare for critical event listeners of a webpage to be installed before this point. Some of the firstInteractive definitions we experimented with fired too early for a small number of sites, because the definitions only looked at long tasks and network activity (and not at, say, how many event listeners are installed), and sometimes when there are no long tasks in the first 5-10 seconds of loading we fire FirstInteractive at FMP, when the sites are often not ready yet to handle user inputs. We found that if we take max(DOMContentLoadedEnd, firstInteractive) as the final firstInteractive value, the values returned to reasonable region. Waiting for DOMContentLoadedEnd to declare FirstInteractive is sensible, so all the definitions introduced below lower bound firstInteractive at DOMContentLoadedEnd. 所以,我们可以通过 domContentLoadedEventEnd 来粗略的进行估算: 1 2 // 页面可交互时间 TTI: domContentLoadedEventEnd - navigationStart, domContentLoadedEventEnd:文档的 DOMContentLoaded 事件的结束时间。 The domContentLoadedEventEnd attribute MUST return a DOMHighResTimeStamp with a time value equal to the time immediately after the current document's DOMContentLoaded event completes. 如果你觉得上述计算过于复杂,可以通过 Google 实验室提供的 Polyfill 来获取。 五、TTI 指标监控 我们可以通过 Google TTI Polyfill来对 TTI 进行监测。 1.安装 1 npm install tti-polyfill 2.使用 1 2 3 4 import ttiPolyfill from './path/to/tti-polyfill.js'; ttiPolyfill.getFirstConsistentlyInteractive(opts).then((tti) => { // Use `tti` value in some way. }); 很简单,就不细说了。推荐几篇 TTI 相关文章: First Interactive and Consistently Interactive User-centric performance metrics Focusing on the Human-Centric Metrics 文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~

2020/3/29
articleCard.readMore

破局:技术视野与规划

有幸参加了 51CTO 的技术峰会,一天满满的干货,感觉收益颇多。于是将重点内容整理总结,分享给大家。 分享标题为《破局:技术视野与规划》,主要围绕峰会内容展开,中间夹杂一些个人见解与思考。 下文多图预警,建议小伙伴们 Wi-Fi 阅读。 一、目录 我们将从以下四方面来展开说明: 1.2019 CTO 发展报告 2.技术团队模型 3.技术视野 4.技能发展与规划 二、2019 CTO 发展报告 - 2019 CTO Development Report 峰会开场就拿出了一份调查报告,主要围绕四方面: 1.宏观环境 2017-2019 年,下行经济环境市场缺口在收缩。向「公司管理驱动」和「科技创新驱动」转变。 从图中我们可以看出来,下行经济环境下市场缺口正在收缩。难道 CTO 要面临失业吗?不着急,我们接着往下看。 2.机遇与挑战 以「业务为中心」的延伸,向上是对商业战略的思考,向下是对技术管理、技术交付的落实。 3.胜任力 既懂得战略、又懂得组织管理、又懂得企业内部运营机制的技术负责人。 我们可以看出:对 CTO 的胜任力要求,越来越趋向于一个既懂得战略、又懂得组织管理、又懂得企业内部运营机制的技术负责人。 所以,并不是需求变少,而是对高精尖人才的要求越来越高。 4.投资人视角 产业革命造就了帝国的崛起,前三次工业革命大家耳熟能详,那么第四次工业革命到底是什么?希望我们中有人可以来定义第四次工业革命~ 下图列举了一些未来 10 年投资的一些方向,抓住机会,理财从现在开始。 三、技术团队模型 - Technical team model 本节,我们将从技术团队模型来说: 内容来自 4 位 CTO 的总结,我们集百家之长。 1.技术团队 ROI:领导的角度 不管问题如何回答,我们只有一个核心观点:对于公司来说,所有的事情都是赚钱相关的。 2.技术团队 ROI:下属的角度 下属想法很淳朴,甚至很无辜。 3.技术团队 ROI 4.BSC 模型 总结来说就是:以前瞻性和战略性为基础的平衡计分卡。 我们简单补充一下: 平衡计分卡(The Balanced ScoreCard,简称 BSC),就是根据企业组织的战略要求而精心设计的指标体系。平衡计分卡是一种绩效管理的工具。 它将企业战略目标逐层分解转化为各种具体的相互平衡的绩效考核指标体系,并对这些指标的实现状况进行不同时段的考核,从而为企业战略目标的完成建立起可靠的执行基础。 平衡计分卡中有一些条目是很难解释清楚或者是衡量出来的。财务指标当然不是问题,而非财务指标往往很难去建立起来。确定绩效的衡量指标往往比想象的更难。 我们来概括一下重点: 全局战略思维(像 CEO 一样思考) 持续提升技术驱动业务创新与增长,赋能企业可持续发展 善用管理、效能与技术工具 5.大型科技团队的管理 大型科技团队中最重要的五部分:使命和定位,绩效管理,团队文化,技术决策,执行力。 这里收集了大型科技公司的组织架构,很有意思: 大概解释一下: 亚马逊等级森严且有序; 谷歌结构清晰,产品和部门之间却相互交错且混乱; Facebook 架构分散,就像一张散开的网络; 微软内部各自占山为王,军阀作风深入骨髓; 苹果一个人说了算,而那个人路人皆知; 庞大的甲骨文,臃肿的法务部显然要比工程部门更加重要; 这里推荐一本书,《信任五层波浪》:自我信任,关系信任,组织信任,市场信任,社会信任。 6.ToB 形态下的技术挑战 双活架构体系: 两个数据中心是对等的、不分主从、并可同时部署业务,可极大的提高资源的利用率和系统的工作效率、性能,双活是觉得备用数据中心只做备份太浪费了,所以让主备两个数据中心都同时承担用户的业务,此时,主备两个数据中心互为备份,并且进行实时备份。 一般来说,主数据中心的负载可能会多一些,比如分担 60~70% 的业务,备数据中心只分担 40%~30% 的业务。 四、技术视野 - Technical perspective 关于技术视野,峰会上提到了几个概念,下面我们来逐一解释。 1.5G 时代 5G 时代,核心网采用微服务架构,也是和容器完美搭配——单体式架构 Monolithic 变成微服务架构Microservices,相当于一个全能型变成N个专能型。 每个专能型,分配给一个隔离的容器,赋予了最大程度的灵活。 5G 的特点概括来说:高数据速率、减少延迟、节省能源、降低成本、提高系统容量和大规模设备连接。 2.工程效能 很多大厂都专门设立了这个部门,主要职责包括:需求治理、质量分析,量化管理,代码构建、代码搜索,开发测试、自动化、发布、舆情监控等。 3.红蓝军对抗 类似于军事领域的红蓝军对抗,网络安全中,红蓝军对抗则是一方扮演黑客「蓝军」,一方扮演防御者「红军」。红蓝军对抗的目的就是用来评估企业安全性,有助于找出企业安全中最脆弱的环节,提升企业安全能力的建设。 4.数字化转型 这里不得不说说什么是「数字领导力」,在我们正在经历的这场数字革命中,透明化、网络化、公开化和分享成为领导力文化的核心。 数字经济时代,数字化领导力是企业战略地使用数字资产达成商业目的的能力。对企业而言,清晰的数字化战略和强有力的数字化领导者将对该目标的实现起到关键作用。 五、技能发展与规划 - Skill development and planning 1.进化路径 2.具备能力 3.提升途径 六、总结 好了,以上就是我分享的主要内容,感兴趣的童鞋欢迎深入交流。如果你需要 PPT ,可以去这里下载(提取码: i6ud)。 文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~

2020/3/8
articleCard.readMore

极简弹幕方案

重大的活动现场一般离不开 PPT 演示,可是如何有效和现场互动呢?这时候弹幕必不可少,静态的 PPT 就略显乏力。有没有一种好的方案可以二者兼得呢? 如何才能使 PPT 具有交互性,这是一个值得思考的问题! 可能很多童鞋想到了,如果使用「网页 PPT」 ,岂不是完美解决了这个问题。本节我们就来提供一种思路,用「PPT + 发射器 + Socket」 实现「极简弹幕方案」。 关于「网页 PPT」,可以查看我之前的文章「酷炫的 HTML5 网页 PPT」一探究竟。 一、效果演示 我们先通过一个简单的视屏演示一下效果: 您的浏览器不支持播放该视频! 相关代码:Demo 地址 二、方案概括 看完上面的演示,是不是迫不及待想知道答案,下面我们来逐步拆分。 先来看看代码结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 . ├── README.md ├── mobile │ ├── README.md │ ├── node_modules │ ├── package.json │ ├── public │ ├── src │ └── yarn.lock ├── package.json ├── ppt │ ├── css │ ├── extras │ ├── images │ ├── index.html │ ├── js │ └── temp ├── server │ ├── app.js │ ├── data │ ├── node_modules │ ├── package-lock.json │ └── package.json └── yarn.lock 我们主要关注以下三个目录: 1.ppt 使用 impressjs 构建的项目,PPT 演讲「主屏」,主要演示内容区域,同时接收「服务端」推送弹幕信息。 2.mobile 移动端,下文称作「发射器」,主要用作现场用户互动向主屏发送弹幕消息。通过 Create React App 生成,技术栈是:React + Antd。 3.server 服务端,主要接受用户弹幕,同时广报到主屏,使用 Socket 实现。 启动方式: 1.进入 server 目录,启动服务: 1 node app.js 此时会启动一个本机 IP 地址的服务。 2.进入 ppt 目录,使用 http-server 启动站点: 1 http-server 注意:接口地址需要替换成本机 IP 地址。 3.进入 mobile 目录,启动发射器: 1 yarn start 注意:请求接口需要使用本机 IP 地址。 Demo 比较简单,主要展示主流程,如果细节过程有问题,欢迎一起探讨。 三、主屏细节(核心代码) 主屏是主要演示版面,我们需要像下面这样作出 PPT,这里我们做了三个页面: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <div id="impress" class="jartto" data-transition-duration="1000"> <div id="cover" class="step slide title" data-x="1000" data-y="1000"> <img src="temp/img/qrcode.png" /> </div> <div id="award" class="step slide" data-x="2000" data-y="3000"> <h1>请开始你的表演~</h1> </div> <div id="change" class="step slide" data-x="2000" data-y="3000" data-scale="5"> <h1>切换 PPT</h1> </div> <div id="thank" class="step slide" data-rel-x="0" data-rel-y="3000" data-rotate="90" data-scale="2"> <img src="images/thanks.png" /> </div> </div> 每个 div 就是一页 ppt,里面可以随意排版,data-x 控制位置,data-scale 控制缩放,data-rotate 控制旋转。 更多 API 文档,请参考如下文档: 1.酷炫的 HTML5 网页 PPT 2.文档地址 四、实现弹幕 为了更好的理解弹幕,我们来实现一个简版: 1.定义弹幕结构 1 <div class="jartto_demo">我是弹幕</div> 2.定义移动动画 1 2 3 4 5 6 7 8 9 10 11 @keyframes barrager{ from{ left:100%; transform:translateX(0); transform:translate3d(0, 0, 0); } to{ left:0; transform:translate3d(-100%, 0, 0); } } 注意,使用 translate3d 可以开启 GPU 硬件加速,会比 translateX 更流畅一些。 关于硬件加速,可以关注我之前写的一篇文章:详谈层合成(composite) 3.使用动画 1 2 3 4 .jartto_demo{ position:absolute; animation: barrager 5s linear 0s; } OK,我们通过三步实现了一个简单的弹幕动画。那么问题来了,弹幕都是随机位置,随机速度,随机颜色出现在屏幕上的,这个该如何实现呢? 4.随机弹幕出现位置 1 2 3 let window_height = $(window).height() - 150; bottom = Math.floor(Math.random() * window_height + 40); code = code.replace(" bottom:{bottom}, //距离底部高度,单位px,默认随机 \n", ''); 5.随机弹幕颜色 1 2 let color = `#${Math.floor(Math.random() * (2 << 23)).toString(16)}`; console.log(color); // #6e8360 好了,大功告成,我们顺手加上 Socket 事件监听。 6.事件监听 为了拿到用户发送过来的弹幕,我们需要做一个事件监听(接收服务端数据): 首先,引入 socket.io.js 文件: 1 <script type="text/javascript" src="http://{jartto.ip}/socket.io/socket.io.js"></script> 1 2 3 4 5 const socket = io('http://{jartto.ip}'); socket.on('server-push', function (data) { console.log('news message >>>>>>>>', data.message); run(data.message); }); 当我们监听到 server-push 事件的时候,run 函数就会初始化弹幕方法,随机生成一条弹幕,在屏幕滑过。 五、发射器细节(核心代码) 发射器就非常简单了,我们使用 Create React App 初始化项目,在 src/app.js 中写入一个表单(这里以 React 为例,Vue 也是大同小异): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <div className="app-box"> <div className="form-box"> <Form.Item {...formItemLayout} label=""> {getFieldDecorator('msg', { rules: [ { required: true, message: '请输入内容', }, ], })(<Input size="large" placeholder="发送消息,嗨起来~" />)} </Form.Item> <Form.Item {...formTailLayout} > <Button className="btns" shape="round" icon="close" size="large" onClick={this.cancle}>取消</Button> <Button type="primary" shape="round" icon="check" size="large" onClick={this.check}>发送</Button> </Form.Item> </div> </div> 用户在输入框输入消息,向我们的服务器发送请求,很简单,就不赘述了。效果图可以参考下面: 请注意,此处为了演示效果,我将三端同框了。 六、服务端细节(核心代码) 服务端比较简单,使用 Express 初始化一个 Node 项目,向 app.js 写入如下内容: 1.启动 Socket 服务: 1 2 3 4 5 6 7 8 const express = require('express'), bodyParser = require('body-parser'), socket = require('socket.io'), fs = require('fs'); const app = express(); const PORT = 4000; const io = socket(app.listen(PORT, () => console.log(`start on port ${PORT}`))); 2.监听 Socket 连接,接收用户发送数据,将数据写入本地 JSON 文件,并广播到 server-push 事件: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 io.on('connection', sockets => { console.log('连接成功!'); app.post('/api/send', (req, res, next) => { // console.log(req.body); let info = JSON.stringify(req.body.msg); fs.writeFile('./data/jartto.json', `${info},\n`, {flag:'a',encoding:'utf-8',mode:'0666'},function(err){ if(err) { console.log('文件写入失败'); res.status(500).send('Error'); } else { sockets.broadcast.emit('server-push', { message: req.body.msg }); res.status(200).send('Done'); } }) }) sockets.on('disconnect', () => { console.log('User Disconnected'); }) }); 3.当然,我们也可以存入数据库做持久化,以下演示存入 MySQL 核心代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 io.on('connection', sockets => { console.log('连接成功!'); app.post('/api/send', (req, res, next) => { let {ua, msg} = req.body.msg; req.getConnection(function(err, cnt) { let query = cnt.add('INSERT INTO (ua, msg)', {ua, msg}, function(err, rows) { if (err) { console.log("Error inserting : %s ",err ); return next(err); } sockets.broadcast.emit('server-push', { message: req.body.msg }); res.status(200).send('Done'); }) }) }) sockets.on('disconnect', () => { console.log('User Disconnected'); }) }); 4.启动服务 1 node app.js 我们的服务端就启动起来了,访问地址是你的主机 IP 和 4000 端口。 七、总结 本文我们从零到一搭建了一个完整的弹幕方案,涉及到三部分:主屏,发射器和服务端,旨在为小伙伴们提供一套极简的设计思路。通过 Demo 我们可以简单的串联一个全栈项目,做更多有趣的事情。 文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~

2020/2/16
articleCard.readMore

GitBook 和它有趣的插件

相信很多童鞋在日常开发中都会有「说明文档」、「学习笔记」和「个人博客」的需求,那么最经济简洁的方式莫过于 GitBook 方案了,10 分钟学习,永久使用。 本文将简单介绍 GitBook 的使用方式以及最佳插件搭配方案,快来运行一个与众不同而且免费托管的个人站点吧! 一、安装与使用 1.安装 GitBook 插件 1 npm install gitbook-cli -g 2.查看安装版本 1 gitbook -V 控制台会输出如下信息: 1 2 3 # Jartto CLI version: 2.3.2 GitBook version: 3.2.3 3.初始化 1 2 3 4 5 6 # 建立项目: mkdir jartto-gitbook-demo # 进入目录: cd jartto-gitbook-demo # 初始化: gitbook init 此时,jartto-gitbook-demo 目录下会自动生成如下文件: 1 2 3 4 5 . ├── README.md └── SUMMARY.md 0 directories, 2 files 4.启动 1 gitbook serve 5.访问站点:http://localhost:4000 恭喜你,到这一步我们已经完成了基本版本。 更详细的操作,请查看如下文档: 官网地址 Github 地址 帮助文档 二、重点说明 1.目录结构 当我们运行 gitbook serve 后,jartto-gitbook-demo 目录下会生成一个 _book 文件夹: 1 2 3 4 5 6 7 8 9 . ├── README.md ├── SUMMARY.md └── _book ├── gitbook ├── index.html └── search_index.json 2 directories, 4 files 2.关于 README.md 1 # Introduction 说明文档,大家应该都不陌生,就不赘述了。 3.关于 SUMMARY.md 1 2 # Summary * [Introduction](README.md) SUMMARY.md 其实就是书的章节目录,我们不妨稍作修改: 1 2 3 4 5 6 7 8 9 # Jartto-GitBook-Demo * [一、概要](README.md) * [1.示例](README.md) * [2.说明](README.md) * [3.文档](README.md) * [二、高级](README.md) * [1.配置](README.md) * [2.插件](README.md) 效果如下: 三、补充文档 当然,GitBook 的远比我们想象的强大,我们还可以通过 gitbook help 来查看: 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 build [book] [output] build a book --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) --format Format to build to (Default is website; Values are website, json, ebook) --[no-]timing Print timing debug information (Default is false) serve [book] [output] serve the book as a website for testing --port Port for server to listen on (Default is 4000) --lrport Port for livereload server to listen on (Default is 35729) --[no-]watch Enable file watcher and live reloading (Default is true) --[no-]live Enable live reloading (Default is true) --[no-]open Enable opening book in browser (Default is false) --browser Specify browser for opening book (Default is ) --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) --format Format to build to (Default is website; Values are website, json, ebook) install [book] install all plugins dependencies --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) parse [book] parse and print debug information about a book --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) init [book] setup and create files for chapters --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) pdf [book] [output] build a book into an ebook file --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) epub [book] [output] build a book into an ebook file --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) mobi [book] [output] build a book into an ebook file --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) 四、有趣插件 了解上面的操作,使用 GitBook 已经没有任何障碍了。 如果你还想做一些个性化的操作,不妨继续深入。 要安装插架,需要我们有配置文件 book.json,我们可以在根目录下创建: 1 touch book.json 写入基本配置: 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 { "title": "Jartto-GitBook-Demo", "description": "Jartto-GitBook-Demo", "author": "sphard", "language": "zh-hans", "root": ".", "plugins": [ "donate", "github-buttons@2.1.0", "edit-link" ], "pluginsConfig": { "donate": { "wechat": "http://jartto.wang/images/wechatpay.jpg", "alipay": "http://jartto.wang/images/alipay.jpg", "title": "", "button": "打赏", "alipayText": "支付宝打赏", "wechatText": "微信打赏" }, "github-buttons": { "repo": "jartto/gitbook", "types": [ "star" ], "size": "small" }, "edit-link": { "base": "https://github.com/jartto/gitbook/master", "label": "Edit This Page" } } } 插件安装通用命令: 1 npm install gitbook-plugin-[插件名] 例如:我们要安装 flexible-alerts 信息框插件: 1 npm install gitbook-plugin-flexible-alerts 效果如下: 还有很多可用插件,具体如下: 信息框(flexible-alerts) 阅读统计(pageview-count) 侧边栏宽度可调节(splitter) 页脚版权(page-copyright) 打赏功能(donate) 分享当前页面(sharing-plus) 修改标题栏图标(custom-favicon) 复选框(todo) 显示图片名称(image-captions) 目录折叠(toggle-chapters) 分章节展示(multipart) 插入 Logo(insert-logo) Google 分析(ga) 返回顶部(back-to-top-button) 代码添加行号和复制按钮(code) 高级搜索,支持中文(search-pro) 添加 Github 图标(github) … 需要注意的是: GitBook 默认带有 5 个插件: highlight search sharing fontsettings livereload 如果要去除自带的插件,可以在插件名称前面加 -,例如: 1 2 3 "plugins":[ "-search" ] 小技巧:NPM 中搜索关键字 GitBook-Plugin,发现更多插件。 五、效果展示 1.GitBook 扩展: 2.示例一: 3.示例二: 4.示例三: 六、总结 上文介绍了 GitBook 的基本使用和一些实用插件,构建在线文档变得轻而易举。加上 Github 免费的托管平台,我们就可以干更多有趣的事情了。快输出你的 HTML、PDF、eBook 技术文档吧~

2020/2/2
articleCard.readMore

系统负载看不懂?

Web 开发会经历「开发-上线-部署」三个过程,部署之后站点的运行状态如何却鲜有关注。如果我们要估算网站能承受多大的并发,你可能需要真正了解系统负载。 一、先看场景 1.服务器状态(Linux 服务器通过 Top 命令查看) 2.数据监控 上面是两个典型应用场景,我们可以直接在服务器查看系统负载。当然,也可以获取数据本地可视化显示。听起来不错,可是问题来了: Q1:load average: 0.03, 0.12, 0.07 是什么? Q2:为什么同时监控 1 分钟,5 分钟,15 分钟? 如果你对此有疑问,不妨继续阅读。 二、参数说明 为了便于理解,我们从「一个比喻」,「两个概念」和「三个边界」来说明。 1.一个比喻 我们可以把 CPU 比喻成一条马路,进程任务就是马路上飞驰的汽车,Load 则表示马路的拥挤程度。 2.两个概念 系统负载(System Load): 系统 CPU 繁忙程度的度量,即有多少进程在等待被 CPU 调度(进程等待队列的长度)。 平均负载(Load Average): 一段时间内系统的平均负载,这个一段时间一般取 1 分钟、5 分钟、15 分钟。 3.三个边界 Load = 0,路上一辆车也没有; Load = 0.7,一大半路上有车; Load = 1,所有路段都有车,基本饱和状态,但是道路仍然能够通行; 阮一峰老师理解 Linux 系统负荷中举的这个例子很形象: 系统负荷为 1.7,意味着车辆太多了,桥已经被占满了(100%),后面等着上桥的车辆为路面车辆的 70%。 系统负荷 2.0,意味着等待上桥的车辆与桥面的车辆一样多; 系统负荷 3.0,意味着等待上桥的车辆是桥面车辆的 2 倍。 总之,当系统负荷大于1,后面的车辆就必须等待了;系统负荷越大,过桥就必须等得越久。 道路的通行能力,就是 CPU 的最大工作量;道路上的车辆,就是一个个等待 CPU 处理的进程(Process)。 三、多核负载如何计算? 上文我们不管是路还是桥的例子,都是默认电脑只有一个 CPU,那如果多 CPU,情况又是如何呢? 很简单,2 个 CPU,意味着电脑的处理能力翻了一倍,能够同时处理的进程数量也翻了一倍。 2 个 CPU 表明系统负载可以达到 2.0,此时每个 CPU 都达到 100% 的工作量。如果你的服务器是 4 核 CPU,那么系统负载极限就是 4.0。 四、答题时间 到这里,相信我们的 Q1 问题已经解决了。我们重点来看 Q2:为什么同时监控 1 分钟,5 分钟,15 分钟? 一分钟理解负载 LoadAverage 中有很好的解释: Load < 0.7时:系统很闲,马路上没什么车,要考虑多部署一些服务 0.7 < Load < 1时:系统状态不错,马路可以轻松应对 Load == 1时:系统马上要处理不多来了,赶紧找一下原因 Load > 5时:马路已经非常繁忙了,进入马路的每辆汽车都要无法很快的运行 那么如果按照 1 分钟来评估系统负载,会被系统短暂的抖动所影响。 所以 1 分钟更多是作为一个参考度量,综合 5 分钟和 10 分钟使监控指标更加准确。 1 分钟 Load > 5,5 分钟 Load < 1,15 分钟 Load < 1 短期内繁忙,中长期空闲,初步判断是一个「抖动」或者是「拥塞前兆」 1 分钟 Load > 5,5 分钟 Load > 1,15 分钟 Load < 1 短期内繁忙,中期内紧张,很可能是一个「拥塞的开始」 1 分钟 Load > 5,5 分钟 Load > 5,15 分钟 Load > 5 短中长期都繁忙,系统「正在拥塞」 1 分钟 Load < 1,5 分钟 Load > 1,15 分钟 Load > 5: 短期内空闲,中长期繁忙,不用紧张,系统「拥塞正在好转」

2020/1/20
articleCard.readMore

人工智能时代,Web 前端能做什么?

最近做了一个项目,通过爬虫去抓取页面快照,然后对页面兼容性进行全面测试。但是遇到一个问题,抓取到海量页面之后,难道还要人工去分析吗? 类似的场景并不会少,是否可以让机器去帮我们实现,最终输出一个可靠报告?答案是肯定的,快照生成后,我们可以对大量快照进行分析,结合 OpenCV 跨平台计算机视觉库,实现图像处理和计算机视觉方面的数据分析,最终输出结果。 我们总会找到一些合适的场景用机器来代替人,而 AI 正是这个支点。 AI 如果是这个时代的契机,那么作为 Web 前端,在这人工智能时代,我们能做什么? 一、什么是人工智能? 人工智能(Artificial Intelligence),英文缩写为 AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 1.计算机科学 人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器,该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。 2.智慧「容器」 人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大,可以设想,未来人工智能带来的科技产品,将会是人类智慧的「容器」。 3.信息加工 人工智能可以对人的意识、思维的信息过程的模拟。人工智能不是人的智能,但能像人那样思考、也可能超过人的智能。 4.研究目标 人工智能是包括十分广泛的科学,它由不同的领域组成,如机器学习,计算机视觉等等,总的说来,人工智能研究的一个主要目标是使机器能够胜任一些通常需要人类智能才能完成的复杂工作。 二、有哪些场景会涉及到 AI? 如果要列举一下有哪些场景会用到 AI,我想可能不仅仅是如下这些: 机器视觉,指纹识别,人脸识别,人脸对比,手势检测,视网膜识别,虹膜识别,掌纹识别,专家系统,自动规划,智能搜索,定理证明,博弈,自动程序设计,智能控制,机器人学,语言和图像理解,遗传编程,物体检测,视频跟踪等。 人工智能就其本质而言,是对人的思维的信息过程的模拟。 对于人的思维模拟可以从两条道路进行, 1.结构模拟,仿照人脑的结构机制,制造出「类人脑」的机器; 2.是功能模拟,暂时撇开人脑的内部结构,而从其功能过程进行模拟。 现代电子计算机的产生便是对人脑思维功能的模拟,是对人脑思维的信息过程的模拟。 三、弱人工智能,强人工智能 弱人工智能如今不断地迅猛发展,尤其是 2008 年经济危机后,美日欧希望借机器人等实现再工业化,工业机器人以比以往任何时候更快的速度发展,更加带动了弱人工智能和相关领域产业的不断突破,很多必须用人来做的工作如今已经能用机器人实现。 强人工智能则暂时处于瓶颈,还需要科学家们和人类的努力。 人工智能是依赖机器学习的,数据和算法是机器学习的核心,而数据更为重要。按照解决问题的能力,我们可以把人工智能,分成两类: 强人工智能:拥有自我意识,具备解决通用问题的能力 弱人工智能:没有自我意识,具备解决特定问题的能力 目前,我们能看到的人工智能,几乎都是弱人工智能,在解决特定问题的能力上,超越了人类。 四、AI 如何影响前端 1.数据可视化,依赖 D3.js,ECharts,WebGL 2.模型可视化 用可视化的手段去解释模型,辅助算法同学调参。最简单的一个应用前端同学肯定非常熟悉,我们来看下图: 是的,曲线函数和曲率我们很难记住,但是有相应的工具,会让一些数据和计算变得简单易懂。 3.相关技术 提到人工智能,和前端密切相关的几个 JS 类库有: tensorflow.js 基于 tensorflow.js Node 的 tvnet 算法,可以提取视频中的稠密光流。 deeplearning.js kera.js 高性能计算: asm.js WebAssembly GPU Opencv,前端做 CV 算法,物体跟踪、图像处理、特征检测等等 大家可能发现一个问题,一般的 tensorflow 模型动辄几百兆,在前端怎么跑呢?这就不得不提到 MobileNet,这是针对于移动端模型提出的神经网络架构,能极大地减少模型参数量,同理也能用到浏览器端上。 更多细节可以查看该文章:前端与人工智能,介绍非常到位。 五、如何做? 既然前端和人工智能有如此多的交集,那么我们该从何做起呢?不要着急,我们先来看一个完整的人工智能项目包含哪些内容。 上图中,可以看到一个完整的人工智能项目是由:算法,数据,工程三部分构成。 工程部分我们可以理解为「大前端」,主要包含 5 部分: 人机交互 数据可视化 产品 Web 算法执行 模型训练 六、简单应用 1.Tranck.js 就是纯浏览器的图像算法库,通过 JS 计算来执行算法逻辑 2.regl-cnn 浏览器端的数字识别类库,与 track.js 不同的是,它利用浏览器的 WebGL 才操作 GPU,实现了 CNN。 3.ConvNetJS 浏览器端做深度学习算法训练的工具,官网地址 4.Amazon Rekognition 基于同样由 Amazon 计算机视觉科学家开发的成熟且高度可扩展的深度学习技术,每天能够分析数十亿张 Prime Photos 图像。 5.对比学习:Keras 搭建 CNN,RNN 等常用神经网络 6.机器学习:MachineLearning 更多内容可以查看: 1.浏览器里运行的人工智能 2.前端在人工智能时代能做些什么 七、深度学习 深度学习,是英文 Deep Learning 的直译。它是实现机器学习的其中一种方式。机器学习还包含其它实现方案。 深度学习里,用到了人工神经网络,这是一个用计算机模拟大脑神经元运作模式的算法。同时,这个人工神经网络的隐藏层数量还必须足够多,才能构成深度神经网络。然后喂之以大量的训练数据,就是深度学习了。 换一个角度,如果隐藏层数量不多,而是每个隐藏层里包含的神经元数量很多,在形态上,它就是一个往宽度发展的神经网络结构。这时,可能就叫广度学习了。 目前,深度学习还是主流,它的训练效率,优于广度学习。 我们可以体验腾讯的一个深度学习案例: 更多有趣应用: 1.TensorFlowJS 学习 2.如何利用 TensorFlow.js 部署简单的 AI 版「你画我猜」图像识别应用 八、明确几个概念 机器学习对我们来说确实陌生,所以一定要从明确一些常用的概念,这样才能提升学习的兴趣。我们来说一些可能会涉及到的内容(我也是正在摸索,目前就知道这些,逃~) 1.精确率 是针对我们预测结果而言的,它表示的是预测为正的样本中有多少是真正的正样本。 2.召回率 是针对我们原来的样本而言的,它表示的是样本中的正例有多少被预测正确了 3.监督学习 监督学习涉及到标注数据,计算机可以使用所提供的数据来识别新的样本。 监督学习的两种主要类型是分类和回归。在分类中,训练的机器将把一组数据分成特定的类。 4.无监督学习 在无监督学习中,数据是未标注的。由于现实中,大多数的数据都是未标注的,因此这些算法特别有用。 无监督学习分为聚类和降维。 5.强化学习 强化学习使用机器的历史和经验来做出决策。强化学习的经典应用是游戏。与监督和无监督学习相反,强化学习不注重提供「正确」的答案或输出。 九、机器学习算法有哪些? 提到机器学习,大家肯定都会自然联想到需要很强的算法功底。没错,确实如此,所以我们需要对算法有一些了解。 那么机器学习主要涉及到哪几类算法呢,我们来看看: 模式识别 计算机视觉 数据挖掘 统计学习 语音识别 自然语言处理 九、机器学习涉及学科 主要围绕在这几方面:线性代数、微积分、概率和统计。 线性代数概念Top 3: 矩阵运算 特征值/特征向量 向量空间和范数 微积分概念Top 3: 偏导数 向量值函数 方向梯度 统计概念Top 3: 贝叶斯定理 组合学 抽样方法 十、计算机视觉 OpenCV 是一个基于 BSD 许可(开源)发行的跨平台计算机视觉库,可以运行在 Linux、Windows、Android 和 Mac OS 操作系统上。 它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了 Python、Ruby、MATLAB 等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。 应用领域: 1、人机互动 2、物体识别 3、图像分割 4、人脸识别 5、动作识别 6、运动跟踪 7、机器人 8、运动分析 9、机器视觉 10、结构分析 11、汽车安全驾驶 OpenCV 的应用领域非常广泛,包括图像拼接、图像降噪、产品质检、人机交互、人脸识别、动作识别、动作跟踪、无人驾驶等。 OpenCV 还提供了机器学习模块,你可以使用正态贝叶斯、K最近邻、支持向量机、决策树、随机森林、人工神经网络等机器学习算法。 这里推荐几个相关学习网站: 1.官网 2.OpenCV教程 3.图像对比 十一、总结 AI 涉及到很多的领域,并不是我们三言两语就能够说的明白。要真正的应用起来,还有很多的路要走。我相信,随着技术的发展,更多的场景将接入 AI,而 Web 则是其中的一个重要环节。加上 Web 跨平台特性,以及「算法-数据-工程」的驱动,未来在该领域一定会大放异彩。 很喜欢这句话:AI makes life better. FE makes AI better.

2020/1/1
articleCard.readMore

CSS 渲染原理以及优化策略

提起 CSS 很多童鞋都很不屑,尤其是看到 RedMonk 2019 Programming Language Rankings 的时候,CSS 竟然排到了第七位。 我们先来看看这张排行榜: 既然 CSS 这么重要,那么我们花点时间来研究相关原理也就物有所值了。 本节我们就来说说 CSS 渲染以及优化相关的内容,主要围绕以下几点,由浅入深,了解来龙去脉: 1.浏览器构成 2.渲染引擎 3.CSS 特性 4.CSS 语法解析过程 5.CSS 选择器执行顺序 6.高效的 ComputedStyle 7.CSS 书写顺序对性能有影响吗 8.优化策略 一、浏览器构成 User Interface: 用户界面,包括浏览器中可见的地址输入框、浏览器前进返回按钮、书签,历史记录等用户可操作的功能选项。 Browser engine: 浏览器引擎,可以在用户界面和渲染引擎之间传送指令或在客户端本地缓存中读写数据,是浏览器各个部分之间相互通信的核心。 Rendering engine: 渲染引擎,解析 DOM 文档和 CSS 规则并将内容排版到浏览器中显示有样式的界面,也就是排版引擎,我们常说的浏览器内核主要指的就是渲染引擎。 Networking: 网络功能模块,是浏览器开启网络线程发送请求以及下载资源的模块。 JavaScript Interpreter: JS 引擎,解释和执行 JS 脚本部分,例如 V8 引擎。 UI Backend: UI 后端则是用于绘制基本的浏览器窗口内控件,比如组合选择框、按钮、输入框等。 Data Persistence: 数据持久化存储,涉及 Cookie、LocalStorage 等一些客户端存储技术,可以通过浏览器引擎提供的 API 进行调用。 二、渲染引擎 渲染引擎,解析 DOM 文档和 CSS 规则并将内容排版到浏览器中显示有样式的界面,也就是排版引擎,我们常说的浏览器内核主要指的就是渲染引擎。 上图中,我们需要关注两条主线: 其一,HTML Parser 生成的 DOM 树; 其二,CSS Parser 生成的 Style Rules ; 在这之后,DOM 树与 Style Rules 会生成一个新的对象,也就是我们常说的 Render Tree 渲染树,结合 Layout 绘制在屏幕上,从而展现出来。 三、CSS 特性 1.优先级: !important > 行内样式(权重1000) > ID 选择器(权重 100) > 类选择器(权重 10) > 标签(权重1) > 通配符 > 继承 > 浏览器默认属性 示例代码一:https://jsfiddle.net/a5xtdoq7/1/ 1 2 3 <div > <p id="box" class="text">Jartto's blog</p> </div> 样式规则: 1 2 #box{color: red;} .text{color: yellow;} 猜一猜,文本会显示什么颜色?当你知道 「ID 选择器 > 类选择器 」的时候,答案不言自明。 升级一下:https://jsfiddle.net/a5xtdoq7/3/ 1 2 3 <div id="box"> <p class="text">Jartto's blog</p> </div> 样式规则: 1 2 #box{color: red;} .text{color: blue;} 这里就考查到了规则「类选择器 > 继承」,ID 对文本来说是继承过来的属性,所以优先级不如直接作用在元素上面的类选择器。 2.继承性 继承得到的样式的优先级是最低的,在任何时候,只要元素本身有同属性的样式定义,就可以覆盖掉继承值; 在存在多个继承样式时,层级关系距离当前元素最近的父级元素的继承样式,具有相对最高的优先级; 有哪些属性是可以继承的呢,我们简单分一下类: 1 2 3 1.font-family,font-size,font-weight 等 f 开头的 CSS 样式; 2.text-align,text-indent 等 t 开头的样式; 3.color; 详细的规则,请看下图: 示例代码二:https://jsfiddle.net/a5xtdoq7/ 1 2 3 4 5 <div> <ol> <li> Jartto's blog </li> </ol> </div> 样式规则定义如下: 1 2 div { color : red!important; } ol { color : green; } 增加了 !important ,猜一猜,文本显示什么颜色? 3.层叠性 层叠就是浏览器对多个样式来源进行叠加,最终确定结果的过程。CSS 之所以有「层叠」的概念,是因为有多个样式来源。 CSS 层叠性是指 CSS 样式在针对同一元素配置同一属性时,依据层叠规则(权重)来处理冲突,选择应用权重高的 CSS 选择器所指定的属性,一般也被描述为权重高的覆盖权重低的,因此也称作层叠。 示例代码三:https://jsfiddle.net/a5xtdoq7/2/ 1 2 3 <div > <p class="two one">Jartto's blog</p> </div> 样式规则如下: 1 2 .one{color: red;} .two{color: blue;} 如果两个类选择器同时作用呢,究竟以谁为准?这里我们要考虑样式表中两个类选择器的先后顺序,后面的会覆盖前面的,所以文本当然显示蓝色了。 升级代码:https://jsfiddle.net/a5xtdoq7/6/ 1 2 3 4 5 <div> <div> <div>Jartto's blog</div> </div> </div> 样式规则: 1 2 3 div div div { color: green; } div div { color: red; } div { color: yellow; } 这个比较直接,算一下权重,谁大听谁的。 继续升级:https://jsfiddle.net/a5xtdoq7/7/ 1 2 3 4 5 <div id="box1" class="one"> <div id="box2" class="two"> <div id="box3" class="three"> Jartto's blog </div> </div> </div> 样式: 1 2 3 .one .two div { color : red; } div #box3 { color : yellow; } #box1 div { color : blue; } 权重: 1 2 3 0 0 2 1 0 1 0 1 0 1 0 1 验证一下:https://jsfiddle.net/a5xtdoq7/9/ 1 2 3 4 5 <div id="box1" class="one"> <div id="box2" class="two"> <div id="box3" class="three"> Jartto's blog </div> </div> </div> 样式如下: 1 2 3 .one .two div { color : red; } #box1 div { color : blue; } div .three { color : green; } 权重: 1 2 3 0 0 2 1 0 1 0 1 0 0 1 1 如果你对上面这些问题都了如指掌,那么恭喜你,基础部分顺利过关,可以继续升级了! 四、CSS 语法解析过程 1.我们来把 CSS 拎出来看一下,HTML Parser 会生成 DOM 树,而 CSS Parser 会将解析结果附加到 DOM 树上,如下图: 2.CSS 有自己的规则,一般如下: WebKit 使用 Flex 和 Bison 解析器生成器,通过 CSS 语法文件自动创建解析器。Bison 会创建自下而上的移位归约解析器。Firefox 使用的是人工编写的自上而下的解析器。 这两种解析器都会将 CSS 文件解析成 StyleSheet 对象,且每个对象都包含 CSS 规则。CSS 规则对象则包含选择器和声明对象,以及其他与 CSS 语法对应的对象。 3.CSS 解析过程会按照 Rule,Declaration 来操作: 4.那么他是如何解析的呢,我们不妨打印一下 CSS Rules: 控制台输入: 1 document.styleSheets[0].cssRules 打印出来的结果大致分为几类: cssText:存储当前节点规则字符串 parentRule:父节点的规则 parentStyleSheet:包含 cssRules,ownerNode,rules 规则 … 规则貌似有点看不懂,不用着急,我们接着往下看。 5.CSS 解析和 Webkit 有什么关系? CSS 依赖 WebCore 来解析,而 WebCore 又是 Webkit 非常重要的一个模块。 要了解 WebCore 是如何解析的,我们需要查看相关源码: 1 2 3 4 5 6 7 8 9 10 11 12 CSSRule* CSSParser::createStyleRule(CSSSelector* selector) { CSSStyleRule* rule = 0; if (selector) { rule = new CSSStyleRule(styleElement); m_parsedStyleObjects.append(rule); rule->setSelector(sinkFloatingSelector(selector)); rule->setDeclaration(new CSSMutableStyleDeclaration(rule, parsedProperties, numParsedProperties)); } clearProperties(); return rule; } 从该函数的实现可以很清楚的看到,解析器达到某条件需要创建一个 CSSStyleRule 的时候将调用该函数,该函数的功能是创建一个 CSSStyleRule ,并将其添加已解析的样式对象列表 m_parsedStyleObjects 中去,这里的对象就是指的 Rule 。 注意:源码是为了参考理解,不需要逐行阅读! Webkit 使用了自动代码生成工具生成了相应的代码,也就是说词法分析和语法分析这部分代码是自动生成的,而 Webkit 中实现的 CallBack 函数就是在 CSSParser 中。 这时候就不得不提到 AST 了,我们继续剖析。 补充阅读:Webkit 对 CSS 支持 6.关于 AST 如果对 AST 还不了解,请移步AST 抽象语法树。这里我们不做过多解释,主要围绕如何解析这一过程展开,先来看一张 Babel 转换过程图: 我们来举一个简单的例子,声明一个箭头函数,如下: 1 2 3 let jarttoTest = () => { // Todo } 通过在线编译,生成如下结果: 从上图我们可以看出:我们的箭头函数被解析成了一段标准代码,包含了类型,起始位置,结束位置,变量声明的类型,变量名,函数名,箭头函数表达式等等。 标准的解析代码,我们可以对其进行一些加工和处理,之后通过相应 API 输出。很多场景都会用到这个过程,如: JS 反编译,语法解析 Babel 编译 ES6 语法 代码高亮 关键字匹配 作用域判断 代码压缩 … 场景千千万,但是都离不开一个过程,那就是: AST 转换过程:解析 - 转换 - 生成 到这里,CSS 如何解析的来龙去脉我们已经非常清楚了,可以回到文章开头的那个流程图了,相信你一定会有另一翻感悟。 五、CSS 选择器执行顺序 渲染引擎解析 CSS 选择器时是从右往左解析,这是为什么呢?举个例子: 1 2 3 4 5 6 7 8 <div> <div class="jartto"> <p><span> 111 </span></p> <p><span> 222 </span></p> <p><span> 333 </span></p> <p><span class='yellow'> 444 </span></p> </div> </div> 样式规则如下: 1 2 3 div > div.jartto p span.yellow{ color:yellow; } 我们按照「从左到右」的方式进行分析: 1.先找到所有 div 节点; 2.在 div 节点内找到所有的子 div ,并且是 class = “jartto”; 3.然后再依次匹配 p span.yellow 等情况; 4.遇到不匹配的情况,就必须回溯到一开始搜索的 div 或者 p 节点,然后去搜索下个节点,重复这样的过程。 有没有觉得很低效,那么问题出在哪里? 这样的搜索过程对于一个只是匹配很少节点的选择器来说,效率是极低的,因为我们花费了大量的时间在回溯匹配不符合规则的节点。 我们不妨换个思路,还以上面的示例为准: 1 2 3 4 5 6 7 8 <div> <div class="jartto"> <p><span> 111 </span></p> <p><span> 222 </span></p> <p><span> 333 </span></p> <p><span class='yellow'> 444 </span></p> </div> </div> 样式规则如下: 1 2 3 div > div.jartto p span.yellow{ color:yellow; } 我们按照「从右向左」的方式进行分析: 1.首先就查找到 class=“yellow” 的 span 元素; 2.接着检测父节点是否为 p 元素,如果不是则进入同级其他节点的遍历,如果是则继续匹配父节点满足 class=“jartto” 的 div 容器; 3.这样就又减少了集合的元素,只有符合当前的子规则才会匹配再上一条子规则。 综上所述,我们可以得出结论: 浏览器 CSS 匹配核心算法的规则是以从右向左方式匹配节点的。 样做是为了减少无效匹配次数,从而匹配快、性能更优。所以,我们在书写 CSS Selector 时,从右向左的 Selector Term 匹配节点越少越好。 不同 CSS 解析器对 CSS Rules 解析速度差异也很大,感兴趣的童鞋可以看看:CSS 解析引擎,这里不再赘述。 六、高效的 ComputedStyle 浏览器还有一个非常棒的策略,在特定情况下,浏览器会共享 Computed Style,网页中能共享的标签非常多,所以能极大的提升执行效率! 如果能共享,那就不需要执行匹配算法了,执行效率自然非常高。 如果两个或多个 Element 的 ComputedStyle 不通过计算可以确认他们相等,那么这些ComputedStyle 相等的 Elements 只会计算一次样式,其余的仅仅共享该 ComputedStyle。 1 2 3 4 5 6 7 <section class="one"> <p class="desc">One</p> </section> <section class="one"> <p class="desc">two</p> </section> 如何高效共享 Computed Style ? 1.TagName 和 Class 属性必须一样; 2.不能有 Style 属性。哪怕 Style 属性相等,他们也不共享; 3.不能使用 Sibling selector,譬如: first-child, :last-selector, + selector 4.mappedAttribute 必须相等; 为了更好的说明,我们再举两个例子: 不能共享,上述规则2: 1 2 <p style="color:red">jartto's</p> <p style="color:red">blog</p> 可以共享,上述规则4: 1 2 <p align="middle">jartto's</p> <p align="middle">blog</p> 到这里,相信你对 ComputedStyle 有了更多的认识,代码也就更加精炼和高效了。 七、CSS 书写顺序对性能有影响吗? 书写顺序会对 CSS 性能有影响吗,这是个值得思考的问题。 需要注意的是:浏览器并不是一获取到 CSS 样式就立马开始解析,而是根据 CSS 样式的书写顺序将之按照 DOM 树的结构分布渲染样式,然后开始遍历每个树结点的 CSS 样式进行解析,此时的 CSS 样式的遍历顺序完全是按照之前的书写顺序。 在解析过程中,一旦浏览器发现某个元素的定位变化影响布局,则需要倒回去重新渲染。 我们来看看下面这个代码片段: 1 2 3 4 width: 150px; height: 150px; font-size: 24px; position: absolute; 当浏览器解析到 position 的时候突然发现该元素是绝对定位元素需要脱离文档流,而之前却是按照普通元素进行解析的,所以不得不重新渲染。 渲染引擎首先解除该元素在文档中所占位置,这就导致了该元素的占位情况发生了变化,其他元素可能会受到它回流的影响而重新排位。 我们对代码进行调整: 1 2 3 4 position: absolute; width: 150px; height: 150px; font-size: 24px; 这样就能让渲染引擎更高效的工作,可是问题来了: 在实际开发过程中,我们如何能保证自己的书写顺序是最优呢? 这里有一个规范,建议顺序大致如下: 1.定位属性 1 position  display  float  left  top  right  bottom   overflow  clear   z-index 2.自身属性 1 width  height  padding  border  margin   background 3.文字样式 1 font-family   font-size   font-style   font-weight   font-varient   color 4.文本属性 1 text-align   vertical-align   text-wrap   text-transform   text-indent    text-decoration   letter-spacing    word-spacing    white-space   text-overflow 5.CSS3中新增属性 1 content   box-shadow   border-radius  transform 当然,我们需要知道这个规则就够了,剩下的可以交给一些插件去做,譬如:CSSLint。能用代码实现的,千万不要去浪费人力。 八、优化策略 我们从浏览器构成,聊到了渲染引擎,再到 CSS 的解析原理,最后到执行顺序,做了一系列的探索。期望大家能从 CSS 的渲染原理中了解整个过程,从而写出更高效的代码。 1.使用 id selector 非常的高效。在使用 id selector 的时候需要注意一点:因为 id 是唯一的,所以不需要既指定 id 又指定 tagName: 1 2 3 4 5 /* Bad */ p#id1 {color:red;} /* Good */ #id1 {color:red;} 2.避免深层次的 node ,譬如: 1 2 3 4 /* Bad */ div > div > div > p {color:red;} /* Good */ p-class{color:red;} 3.不要使用 attribute selector,如:p[att1=”val1”]。这样的匹配非常慢。更不要这样写:p[id="id1"]。这样将 id selector 退化成 attribute selector。 1 2 3 4 5 6 /* Bad */ p[id="jartto"]{color:red;} p[class="blog"]{color:red;} /* Good */ #jartto{color:red;} .blog{color:red;} 4.通常将浏览器前缀置于前面,将标准样式属性置于最后,类似: 1 2 3 4 .foo { -moz-border-radius: 5px; border-radius: 5px; } 可以参考如下 Css 规范。 5.遵守 CSSLint 规则 1 2 3 4 5 6 7 8 9 font-faces         不能使用超过5个web字体 import            禁止使用@import regex-selectors      禁止使用属性选择器中的正则表达式选择器 universal-selector       禁止使用通用选择器* unqualified-attributes    禁止使用不规范的属性选择器 zero-units       0后面不要加单位 overqualified-elements    使用相邻选择器时,不要使用不必要的选择器 shorthand          简写样式属性 duplicate-background-images 相同的url在样式表中不超过一次 6.减少 CSS 文档体积 移除空的 CSS 规则(Remove empty rules) 值为 0 不需要单位 使用缩写 属性值为浮动小数 0.**,可以省略小数点之前的0; 不给 h1-h6 元素定义过多的样式 7.CSS Will Change WillChange 属性,允许作者提前告知浏览器的默认样式,使用一个专用的属性来通知浏览器留意接下来的变化,从而优化和分配内存。 8.不要使用 @import 使用 @import 引入 CSS 会影响浏览器的并行下载。使用 @import 引用的 CSS 文件只有在引用它的那个 CSS 文件被下载、解析之后,浏览器才会知道还有另外一个 CSS 需要下载,这时才去下载,然后下载后开始解析、构建 Render Tree 等一系列操作。 多个 @import 会导致下载顺序紊乱。在 IE 中,@import 会引发资源文件的下载顺序被打乱,即排列在 @import 后面的 JS 文件先于 @import 下载,并且打乱甚至破坏 @import 自身的并行下载。 9.避免过分重排(Reflow) 浏览器重新计算布局位置与大小。 常见的重排元素; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 width height padding margin display border-width border top position font-size float text-align overflow-y font-weight overflow left font-family line-height vertical-align right clear white-space bottom min-height 10.高效利用 computedStyle 公共类 慎用 ChildSelector 尽可能共享 更多请查看上文:高效的 ComputedStyle 11.减少昂贵属性: 当页面发生重绘时,它们会降低浏览器的渲染性能。所以在编写 CSS 时,我们应该尽量减少使用昂贵属性,如: box-shadow border-radius filter :nth-child 12.依赖继承。如果某些属性可以继承,那么自然没有必要在写一遍。 13.遵守 CSS 顺序规则 上面就是对本文的一个总结,你了解 CSS 具体的实现原理,晓得规避错误书写方式,知道为什么这么优化,这就够了。 性能优化,进无止境。

2019/10/23
articleCard.readMore

网站优化,这些工具你一定用得着

工欲善其事,必先利其器。在「网站优化实战」中我们提到了一些优化的相关经验,并没有对优化工具展开讨论,这节就让我们一起上手实践吧! 一、概要 关于优化工具,我们主要从两方面说起:「性能评估工具」和「优化工具」。 1.性能评估工具 Lighthouse PageSpeed YSlow 2.优化工具我们主要依赖「Chrome DevTools」,大致如下: Network Performance Show Third Party Badges Block Request URL Coverage DOM Rendering Layer 二、Lighthouse 1.Lighthouse 安装 Chrome Setting - 更多工具 - 扩展程序 - 打开 Chrome 网上应用店 - Lighthouse 2.插件 - 生成报告 报告是我们的一个重要参考指标,这是网站评估的通用方法。 当然,网站也会有不同的类别,关注指标也不尽相同,后续我们会继续探讨「如何制定合理的网站优化性能指标」。 3.优化建议 Lighthouse 比较人性化的点在于他既提出了问题,同时也提出了解决建议。 三、PageSpeed 1.使用 PageSpeed 我们可以在「Chrome DevTools」菜单栏中找到并打开: 2.分析报告 四、Chrome DevTools - Network 1.关于 Network 我们重点关注标注的 3 处 2.Timing 也是优化不可缺少的工具: 补充说明一下: TTFB:等待初始响应所用的时间,也称为第一字节的时间,这是我们判断服务器以及网络状况的重要指标。 此时间将捕捉到服务器往返的延迟时间,以及等待服务器传送响应所用的时间。 五、Chrome DevTools - Performance 1.概览 2.版面主要由 4 部分构成 控制面板:录制,清除,配置记录期间需要捕获的信息 Overview:页面性能的高级汇总,以及页面加载情况 火焰图:CPU 堆叠追踪的可视化 总览:饼图记录各部分耗时情况 3.Overview 详解 FPS 每秒帧数。绿色竖线越高,FPS 越高。 FPS 图表上的红色块表示长时间帧,很可能会出现卡顿。 CPU CPU 资源。此面积图指示消耗 CPU 资源的事件类型。 NET 每条彩色横杠表示一种资源。横杠越长,检索资源所需的时间越长。 每个横杠的浅色部分表示等待时间(从请求资源到第一个字节下载完成的时间)。 深色部分表示传输时间(下载第一个和最后一个字节之间的时间)。 需要特别注意,Performance 工具中的每一种颜色其实都有自己的含义。 HTML 文件为蓝色。 脚本为黄色。 样式表为紫色。 媒体文件为绿色。 其他资源为灰色。 小技巧: 使用无痕模式,减少 Chrome 扩展程序会给应用的干扰。 4.火焰图 Network Network 这里我们可以看出来,我们资源加载的一个顺序情况。什么时间加载了什么资源,通过这些,我们更直观的知道资源是否并行加载。 Frames 上文提及到的页面帧情况。 Interactions Timings 中如下 5 个指标是我们优化的方向 First Paint DOMContentLoaded Event Onload Event First Contentful Paint First Meaningful Paint Main:展示了主线程运行状况。 X 轴代表着时间,每个长条代表着一个 event。长条越长就代表这个 event 花费的时间越长。 Y 轴代表了调用栈 call stack 。 在栈里,上面的 event 调用了下面的 event。 注意红色警告: JS Heap JavaScript 运行过程中的大部分数据都保存在堆 Heap 中,所以 JavaScript 性能分析另一个比较重要的方面是内存,也就是堆的分析。 打开 Performance 监视器 六、Chrome DevTools - Show Third Party Badges 很多情况下,并不是我们网站本身的问题,有可能你使用的三方资源拖累了站点性能。所以,我们需要使用 Show Third Party Badges 来进行排查。 1.测试站点:https://techcrunch.com/ 2.打开控制面板:Command + Shift + P 3.打开 Network,注意资源前面的彩色标志 三方资源都被标记出来了,移除或者替换那些影响性能的东西。 七、Chrome DevTools - Block Request URL 对于项目中不确定是否有用的资源,我们可以使用 Block Request URL 来排除。 1.选中资源 - 右键 - Block Request URL 阻止某些资源加载,控制变量法来排查页面性能问题。 八、Chrome DevTools - Coverage 1.打开控制面板:Command + Shift + P 2.输入:Show Coverage 3.找到相应的文件,可以看到文件左侧已经标记出了部分代码的使用情况 解决思路也很简单: 尽可能去通过 Webpack 来拆包,控制大小在 40KB 以下,移除那些未使用代码。 九、Chrome DevTools - DOM 我们经常提到要优化 Dom,那么节点控制在什么范围才合理呢? 总共少于 1500 个节点 最大深度为 32 个节点 不要存在子节点超过 60 个节点的父节点 查看所有 DOM 节点数: 1 document.querySelectorAll('*').length 查看子元素个数: 1 document.querySelectorAll('body > *').length 通常,只在需要时查找创建 DOM 节点的方法,并在不再需要时销毁它们。 十、Chrome DevTools - Rendering 关于重渲对页面的影响,我们就不多说了。那么如何知道页面的渲染过程呢?我们可以通过 Rendering 来可视化查看。 1.打开 Rendering 选项 2.刷新页面 绿色区域越重,说明重复渲染的次数越多,通过优化 DOM 来减少无效渲染。 十一、Chrome DevTools - Layer 你可能会很好奇,为什么要查看图层? 这是因为,我们经常会在不知不觉的情况下搞乱了图层关系,或者增加了不合适的图层。 1.打开控制面板:Command + Shift + P 2.选择 Layer 选项 是不是图层问题就清清楚楚的摆在眼前了~ 十二、总结 通过优化工具,我们可以轻而易举的对网站进行定位分析。之后就可以快速展开优化,让网站高性能的运转起来。优化,也不过如此。 后续我们会深入了解一些优化相关的原理细节,如果你有优化相关的问题,欢迎一起探讨,一起进步。

2019/9/8
articleCard.readMore

程序员如何减少开发中的 Bug?

周会上大家抛出了一个问题,程序员如何减少开发中的 Bug?很有意思的一个话题,本篇文章我们来进行探讨与总结。 一、概述 爱因斯坦曾经说过:「如果给我一个小时解答一道决定我生死的问题,我会花55分钟来弄清楚这道题到底是在问什么。一旦清楚了它在问什么,剩下的5分钟足够解答这个问题。」 虽然我们软件开发过程不会面临生死的抉择,但是却直接影响着用户的使用感受,决定着产品的走向。所以程序员如何减少开发中的 Bug,既反映了代码质量,也反映了个人综合能力。 那么我们该如何有效的减少开发中的 Bug 呢? 我觉得应该从两方面说起:业务层和代码层。 二、业务层 软件开发过程我们就不细说了,直接来看最重要的几个节点: 1.需求讨论阶段 一定要明确需求,测试,开发,产品三方务必达成一致。前期如果存在没有明确的问题,那么后期就会造成无效返工和不必要的争执,这在日常开发尤为常见。 所以,软件开发前期,我们都会进行「评审,反讲,评估」三个阶段。 2.开发完成阶段 开发完成后,程序员首先要完成「自测」,也就是软件开发中的「冒烟测试」,确保主流程无误。否则,在开发工程师提交代码后,测试工程师步履维艰,无法有效开展测试,会造成极大的资源浪费。 更规范的流程需要测试工程师在需求明确之后写出「测试用例」,开发工程师在完成开发后,自行对照「测试用例」完成初步验证,之后就可以代码提测了。 这么做的好处就是既保证了「高质量的代码交付」,同时减少了测试工程师的工作量,我们何乐而不为呢? 3.提测 自测和提测有什么区别呢,从软件开发过程来看,其实开发工程师和测试工程师其实完成了不同阶段的测试: 开发工程师「白盒测试」: 是指实际运行被测程序,通过程序的源代码进行测试而不使用用户界面。这种类型的测试需要从代码句法发现内部代码在算法、溢出、路径和条件等方面的缺点或者错误,进而加以修正。 白盒测试需要从代码句法发现内部代码在算法,溢出,路径,条件等等中的缺点或者错误,进而加以修正。 测试工程师实际进行的是「黑盒测试」。那么什么是「黑盒测试」呢? 黑盒测试也称功能测试,它是通过测试来检测每个功能是否都能正常使用。在测试中,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序接口进行测试。 它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息。黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和软件功能进行测试。 黑盒测试是以用户的角度,从输入数据与输出数据的对应关系出发进行测试的。 很明显,如果外部特性本身设计有问题或规格说明的规定有误,用黑盒测试方法是发现不了的。黑盒测试法注重于测试软件的功能需求,主要试图发现下列几类错误。 功能不正确或遗漏; 界面错误; 输入和输出错误; 数据库访问错误; 性能错误; 初始化和终止错误等; 更多细节请查看文章:黑盒测试 三、代码层 代码层面,我们需要从以下几方面来说起: 1.Eslint 规避低级语法问题 这个显而易见,编写代码过程发现问题,避免因为简单语法,如:漏写了逗号,变量名写错,大小写问题等 2.边界处理 做好容错,必要的判空,还有就是代码边界问题。多想一想如果数组不存在,我们如何处理?如果数组越界,我们如何修复?如果数据缺失,我们如何使页面不崩溃? 3.单元测试 如果时间允许,我们可以做好单元测试,每次编译代码,或者提测前启动脚本,确定测试脚本都覆盖到了核心代码,尽可能减少代码出错率。 4.积累 为什么说要积累,其实道理很简单。随着开发经验的增长,你可能会碰到很多问题,那么如果细心积累,其实很多错误在不知不觉中就被处理了。反之,你会不断的掉入同一个坑里,在进坑与出坑中迷失自我。那么我们如何积累呢? 首先,碰到自己不会的问题,如果第一时间没有解决,通过查找或者请教别人解决了,那么一定要用小本本记下来,最好使用云笔记。好处不言自明。 其次,要积累自己的函数库,我们经常用到的一些方法,不妨自己做一个封装,不断沉淀。也许有一天,你会发现,自己不知不知觉中写出了一个 Lodash 函数库。 最后,你可以积累优秀的代码片段,嗯,「我们不生产代码,只是优秀代码的搬运工」。 5.学习 一句话,没有什么比学习优秀开源代码更有趣的事情了。阅读优秀源码,学习作者思想,站在巨人肩膀上,你才能走的更远! 做好上面这些,相信你一定会是一位出色的工程师。 四、总结 对于这类开放问题仁者见仁,智者见智,我相信每个人都会有自己的看法,也会有自己一套独特的方法。不管黑猫白猫,能抓住老鼠的就是好猫。对于程序员来说,能减少 Bug 的方法就是好方法。 程序员群体流传一句话:不写代码就有没有 Bug。 我们不能因为怕犯错误而减少写代码,更应该知难而上,越挫越勇。要知道日常开发中 「Bug 是不可避免的,只能减少」。 当然,这不应该成为我们写出 Bug 推脱的理由。不断超越,方是永恒。

2019/8/24
articleCard.readMore