Docker
实战派:容器入门七步法》上市已经一周年了,在这个里程碑的时刻,感谢各位朋友的支持。希望今年能够持续「大麦」,拿下同类书籍 Top1
!市场上不乏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
的佳作。乐于见到有更多这样的图书来帮助更多有需求的人,帮助他们早日走上云原生的大舞台。」——阿里云边缘云原生技术负责人 周晶
Nginx
是你绕不开的一个坎。毫不夸张的说:Nginx
能顶半边天!OP
)团队,不用操心。然而实际情况却是 OP
每天被繁重的工单占据着,你无时无刻不在排队。大公司如此,小公司更甚。因此,储备一些 Nginx
知识,一定会让你事半功倍。本文总结了日常开发中高频出现的 15 个 Nginx 配置片段,因为短小,所以你只需 30 秒就可以掌握。
由于浏览器的安全策略,前端处理跨域请求的概率极高,如下是开启跨域请求常规手段。1
2
3
4
5
6
7if ($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;
}
如果你希望压缩常规的文件类型,可以参考如下代码:1
2
3
4
5
6
7
8
9http
{
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压缩
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
4location /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
17http {
# 指定一个 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
5server {
listen 80;
server_name *.jartto.com;
root /var/www/jartto.com;
}
上面的配置将所有以 .jartto.com
结尾的域名都指向 /var/www/jartto.com
目录下的网站。如果要配置二级域名,可以使用以下的 Nginx
配置:1
2
3
4
5server {
listen 80;
server_name *.sub.jartto.com;
root /var/www/sub.jartto.com;
}
上面的配置将所有以 .sub.jartto.com
结尾的二级域名都指向 /var/www/sub.jartto.com
目录下的网站。需要注意的是,在使用泛解析时,可能会导致一些安全问题。因此,建议仅在必要时使用泛解析,并且要对网站进行充分保护。
Nginx
在1.11.0
版本中就提供了内置变量$request_id
,其原理就是生成 32 位的随机字符串,虽不能比拟 UUID
的概率,但 32 位的随机字符串的重复概率也是微不足道了,所以一般可视为 UUID
来使用。1
2
3
4
5
6
7
8
9
10location ^~ /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
10http {
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
19map $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}';
}
...
}
vue-router+webpack
项目线上部署时单页项目路由,刷新页面出现 404
问题,一般需要配置如下:1
2
3
4
5
6
7
8
9location / {
root html;
try_files $uri $uri/ @router;
index index.html index.htm;
}
location @router {
rewrite ^.*$ /index.html last;
}
最常见的场景就是灰度发布,Nginx
来识别来自前端的流量,从而进行转发。1
2
3
4
5
6
7
8map $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
4map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
1 | location ~* ^/_nuxt/img/(.+\.png|jpe?g)$ { |
负载均衡通常有四种算法:
weight
,权重分配,指定轮询几率,权重越高,在被访问的概率越大,用于后端服务器性能不均的情况;ip_hash
,每个请求按访问 IP
的 hash
结果分配,这样每个访客固定访问一个后端服务器,可以解决动态网页 session
共享问题。负载均衡每次请求都会重新定位到服务器集群中的某一个,那么已经登录某一个服务器的用户再重新定位到另一个服务器,其登录信息将会丢失,这样显然是不妥的;fair
(第三方),按后端服务器的响应时间分配,响应时间短的优先分配,依赖第三方插件 nginx-upstream-fair
,使用前请先安装;1 | http { |
要在 Nginx
上配置 HTTPS
,可以按照以下步骤操作:
Nginx
安装 SSL
模块。 这可以通过使用 --with-http_ssl_module
选项编译 Nginx
或安装包含 SSL
模块的预构建包来完成。CA
为您的域获取SSL
证书。 这可以通过购买证书或从Let's Encrypt
获得免费证书来完成。配置Nginx
以使用SSL
证书和密钥文件。这涉及将以下行添加到您的Nginx
配置文件中:
1 | ssl_certificate /path/to/jartto.crt; |
如果需要,配置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
15server {
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
12server {
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;
}
}
}
要在Nginx
中配置多个服务器,可以在Nginx
配置文件中定义多个服务器块。每个服务器块代表一个单独的虚拟服务器,可以监听不同的端口或IP
地址,并提供不同的内容。下面是如何在Nginx
中配置两个虚拟服务器的示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17http {
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
22upstream 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;
本节篇幅较长,我们主要围绕以下几点来展开:
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
的总结,简单明了: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
可确保服务通信在默认情况下是安全的,并且我们可以跨不同协议和运行时一致地实施安全政策,而只需对应用稍作调整,甚至无需调整。
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
Docker
,其实遗留了一个大问题。Docker
虽好用,但面对强大的集群,成千上万的容器,突然感觉不香了。Kubernetes
上场了,先来了解一下 K8s
的基本概念,后面再介绍实践,由浅入深步步为营。关于 K8s
的基本概念,我们将会围绕如下七点展开:
1.Docker
的管理痛点
2.什么是 K8s
?
3.云架构 & 云原生
4.K8s
架构原理
5.K8s
核心组件
6.K8s
的服务注册与发现
7.关键问题
如果想要将 Docker
应用于庞大的业务实现,是存在困难的编排、管理和调度问题。于是,我们迫切需要一套管理系统,对 Docker
及容器进行更高级更灵活的管理。
Kubernetes
应运而生!Kubernetes
,名词源于希腊语,意为「舵手」或「飞行员」。Google
在 2014
年开源了 Kubernetes
项目,建立在 Google
在大规模运行生产工作负载方面拥有十几年的经验的基础上,结合了社区中最好的想法和实践。
K8s 是 Kubernetes 的缩写,用 8 替代了 「ubernete」,下文我们将使用简称。
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
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
架构后,我们又引入了很多技术名词。不要着急,先有整体概念,再各个击破。请耐心阅读下文,相信你一定会有不一样的收获。
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
3selector:
app = web
Release = stable
在新版的 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
来关联数据。
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 | selector: |
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
版本。
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 - TB | KB - MB |
集群规模 | 上百 | 上万 |
高可用策略 | 备份、容灾、迁移 | 弹性、负载、动态 |
与虚拟机相比,容器更轻量且速度更快,因为它利用了 Linux
底层操作系统在隔离的环境中运行。虚拟机的 Hypervisor
创建了一个非常牢固的边界,以防止应用程序突破它,而容器的边界不那么强大。
物理机部署不能充分利用资源,造成资源浪费。虚拟机方式部署,虚拟机本身会占用大量资源,导致资源浪费,另外虚拟机性能也很差。而容器化部署比较灵活,且轻量级,性能较好。
虚拟机属于虚拟化技术,而 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
工作原理及容器化简易指南,这里不再赘述。
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
10Using 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
18server {
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
3FROM 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
10Sending 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
2CONTAINER 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
的大门,不断抽丝剥茧,逐层深入,你将感受到容器化的无穷魅力。
赶快打开技能边界,为你的前端技术赋能吧!
]]>CSDN
,欢迎大家关注账号:Jartto
。本站点内容均属原创,如有需求,请联系本人。当我们在做网站性能优化的时候,减少图片大小,意味着减少了网络传输,提升了网站加载速度,而这部分也是性价比最高的。
我们可以通过压缩图片来减少体积,但压缩比例一直是前端开发和设计师争执的焦点。压缩比例大的话,可以有效减少图片体积,对页面加载有利,但是却损失了像素,是设计师无法容忍的。
在这种矛盾的场景下,我们既要最大程度的压缩图片又要保持足够的清晰,WebP
便应运而生。
值得注意的是:WebP 并不是新技术,而是受限于兼容性而未全面普及。
1.时间成本
我们先来看一组数据对比图,如果你做过 Gif
动图,你肯定知道下面这样的处理意义有多大:
在肉眼无法识别差异的前提下,图片大小减少了 88%。
2.带宽成本
YouTube
的视频略缩图采用 WebP
格式后,网页加载速度提升了 10%
;Chrome
网上应用商店采用 WebP
格式图片后,每天可以节省几 TB
的带宽,页面平均加载时间大约减少 1/3
;Google+
移动应用采用 WebP
图片格式后,每天节省了 50TB
数据存储空间。目前 Google
、Facebook
、阿里、京东的等国内外互联网公司广泛应用了 WebP
,超过 70%
的浏览器已经支持 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
17const 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
4map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
如果浏览器支持 WebP
格式,那么我们就将 .png
或者 .jpg
格式图片转发到 .webp
格式下。1
2
3
4
5
6
7location ~* ^/_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
16pagespeed 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
16function 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 有损压缩的编码过程
Lossy
有损压缩基于 VP8
关键帧编码。 VP8
是 一种视频压缩格式,是 VP6
和 VP7
格式的后继格式。
Lossless
无损压缩格式由 WebP
团队开发。
Alpha8
位 Alpha
通道对于图形图像很有用。 Alpha
通道可与有损 RGB
一起使用,该功能目前无法在任何其他格式下使用。
Animation
它支持真彩色动画图像。
24 位色被称为真彩色,它可以达到人眼分辨的极限,发色数是 1677 万多色,也就是 2 的 24 次方。
一般在做技术选型的过程中,我们都要评估引入技术的收益,这也就是我们常说的 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
格式的算法细节以及压缩格式,由浅入深,逐层剖析。
文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~
]]>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
并没有准确的计算公式,但是实际压测中我们完全可以按照如下模型进行估算:
原理:每天 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
3git 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
8Running 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
2An optional LuaJIT script can perform HTTP request generation, response processing,
and custom reporting.
LuaJIT
脚本可以执行 HTTP
请求生成,响应处理和自定义报告。这里是 Lua
的一些案例。鉴于篇幅过长,本节我们先了解到这里,精彩部分我们就留到下一篇继续探讨吧!
文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~
]]>F2E
的追求目标。当然,衡量网站性能的指标有很多,今天我们就来看一个「令人费解而又头疼」的指标 - FMP
。
First Meaningful Paint
是指页面的首要内容出现在屏幕上的时间。
目前尚无标准化的 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
将不再是烦恼。
文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~
]]>TTI(Time to Interactive)
,话题展开之前,我们先来了解一些背景知识。RAIL
是一种以用户为中心的性能模型。每个网络应用均具有与其生命周期有关的四个不同方面,且这些方面以不同的方式影响着性能:
1.响应:输入延迟时间(从点按到绘制)小于 100 毫秒。
用户点按按钮(例如打开导航)。
2.动画:每个帧的工作(从 JS 到绘制)完成时间小于 16 毫秒。
用户滚动页面,拖动手指(例如,打开菜单)或看到动画。 拖动时,应用的响应与手指位置有关(例如,拉动刷新、滑动轮播)。 此指标仅适用于拖动的持续阶段,不适用于开始阶段。
3.空闲:主线程 JS 工作分成不大于 50 毫秒的块。
用户没有与页面交互,但主线程应足够用于处理下一个用户输入。
4.加载:页面可以在 1000 毫秒内就绪。
用户加载页面并看到关键路径内容。
如果要提升网站用户体验,RAIL 是个不错的评估模型。
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
?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);
});
}());
在计算之前,我们先来看一下 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
来获取。
我们可以通过 Google TTI Polyfill
来对 TTI
进行监测。
1.安装1
npm install tti-polyfill
2.使用1
2
3
4import 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
文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~
]]>51CTO
的技术峰会,一天满满的干货,感觉收益颇多。于是将重点内容整理总结,分享给大家。下文多图预警,建议小伙伴们 Wi-Fi 阅读。
我们将从以下四方面来展开说明:
1.2019 CTO
发展报告
2.技术团队模型
3.技术视野
4.技能发展与规划
峰会开场就拿出了一份调查报告,主要围绕四方面:
1.宏观环境
2017-2019 年,下行经济环境市场缺口在收缩。向「公司管理驱动」和「科技创新驱动」转变。
从图中我们可以看出来,下行经济环境下市场缺口正在收缩。难道 CTO
要面临失业吗?不着急,我们接着往下看。
2.机遇与挑战
以「业务为中心」的延伸,向上是对商业战略的思考,向下是对技术管理、技术交付的落实。
3.胜任力
既懂得战略、又懂得组织管理、又懂得企业内部运营机制的技术负责人。
我们可以看出:对 CTO
的胜任力要求,越来越趋向于一个既懂得战略、又懂得组织管理、又懂得企业内部运营机制的技术负责人。
所以,并不是需求变少,而是对高精尖人才的要求越来越高。
4.投资人视角
产业革命造就了帝国的崛起,前三次工业革命大家耳熟能详,那么第四次工业革命到底是什么?希望我们中有人可以来定义第四次工业革命~
下图列举了一些未来 10 年投资的一些方向,抓住机会,理财从现在开始。
本节,我们将从技术团队模型来说:
内容来自 4 位 CTO 的总结,我们集百家之长。
1.技术团队 ROI
:领导的角度
不管问题如何回答,我们只有一个核心观点:对于公司来说,所有的事情都是赚钱相关的。
2.技术团队 ROI
:下属的角度
下属想法很淳朴,甚至很无辜。
3.技术团队 ROI
4.BSC 模型
总结来说就是:以前瞻性和战略性为基础的平衡计分卡。
我们简单补充一下:
平衡计分卡(The Balanced ScoreCard
,简称 BSC
),就是根据企业组织的战略要求而精心设计的指标体系。平衡计分卡是一种绩效管理的工具。
它将企业战略目标逐层分解转化为各种具体的相互平衡的绩效考核指标体系,并对这些指标的实现状况进行不同时段的考核,从而为企业战略目标的完成建立起可靠的执行基础。
平衡计分卡中有一些条目是很难解释清楚或者是衡量出来的。财务指标当然不是问题,而非财务指标往往很难去建立起来。确定绩效的衡量指标往往比想象的更难。
我们来概括一下重点:
5.大型科技团队的管理
大型科技团队中最重要的五部分:使命和定位,绩效管理,团队文化,技术决策,执行力。
这里收集了大型科技公司的组织架构,很有意思:
大概解释一下:
Facebook
架构分散,就像一张散开的网络;这里推荐一本书,《信任五层波浪》:自我信任,关系信任,组织信任,市场信任,社会信任。
6.ToB
形态下的技术挑战
双活架构体系:
两个数据中心是对等的、不分主从、并可同时部署业务,可极大的提高资源的利用率和系统的工作效率、性能,双活是觉得备用数据中心只做备份太浪费了,所以让主备两个数据中心都同时承担用户的业务,此时,主备两个数据中心互为备份,并且进行实时备份。
一般来说,主数据中心的负载可能会多一些,比如分担 60~70% 的业务,备数据中心只分担 40%~30% 的业务。
关于技术视野,峰会上提到了几个概念,下面我们来逐一解释。
1.5G
时代5G
时代,核心网采用微服务架构,也是和容器完美搭配——单体式架构 Monolithic
变成微服务架构Microservices
,相当于一个全能型变成N个专能型。
每个专能型,分配给一个隔离的容器,赋予了最大程度的灵活。
5G
的特点概括来说:高数据速率、减少延迟、节省能源、降低成本、提高系统容量和大规模设备连接。
2.工程效能
很多大厂都专门设立了这个部门,主要职责包括:需求治理、质量分析,量化管理,代码构建、代码搜索,开发测试、自动化、发布、舆情监控等。
3.红蓝军对抗
类似于军事领域的红蓝军对抗,网络安全中,红蓝军对抗则是一方扮演黑客「蓝军」,一方扮演防御者「红军」。红蓝军对抗的目的就是用来评估企业安全性,有助于找出企业安全中最脆弱的环节,提升企业安全能力的建设。
4.数字化转型
这里不得不说说什么是「数字领导力」,在我们正在经历的这场数字革命中,透明化、网络化、公开化和分享成为领导力文化的核心。
数字经济时代,数字化领导力是企业战略地使用数字资产达成商业目的的能力。对企业而言,清晰的数字化战略和强有力的数字化领导者将对该目标的实现起到关键作用。
1.进化路径
2.具备能力
3.提升途径
好了,以上就是我分享的主要内容,感兴趣的童鞋欢迎深入交流。如果你需要 PPT
,可以去这里下载(提取码: i6ud
)。
文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~
]]>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
3let window_height = $(window).height() - 150;
bottom = Math.floor(Math.random() * window_height + 40);
code = code.replace(" bottom:{bottom}, //距离底部高度,单位px,默认随机 \n", '');
5.随机弹幕颜色1
2let 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 | const socket = io('http://{jartto.ip}'); |
当我们监听到 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
8const 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
23io.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
23io.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
我们可以简单的串联一个全栈项目,做更多有趣的事情。
文章最后,打个小广告吧。如果你想搭上在线教育的快车,快速成长,不妨加入我们。一起成长,一起学习,一起挑战更多有趣的事情,「跟谁学-高途课堂」欢迎你,请将简历私我~
]]>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
恭喜你,到这一步我们已经完成了基本版本。
更详细的操作,请查看如下文档:
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
32build [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
技术文档吧~
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,后面的车辆就必须等待了;系统负荷越大,过桥就必须等得越久。
道路的通行能力,就是 CPU
的最大工作量;道路上的车辆,就是一个个等待 CPU
处理的进程(Process
)。
上文我们不管是路还是桥的例子,都是默认电脑只有一个 CPU
,那如果多 CPU
,情况又是如何呢?
很简单,2
个 CPU
,意味着电脑的处理能力翻了一倍,能够同时处理的进程数量也翻了一倍。
2
个 CPU
表明系统负载可以达到 2.0
,此时每个 CPU
都达到 100%
的工作量。如果你的服务器是 4
核 CPU
,那么系统负载极限就是 4.0
。
到这里,相信我们的 Q1
问题已经解决了。我们重点来看 Q2
:为什么同时监控 1
分钟,5
分钟,15
分钟?
一分钟理解负载 LoadAverage 中有很好的解释:
那么如果按照 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:
短期内空闲,中长期繁忙,不用紧张,系统「拥塞正在好转」
我们总会找到一些合适的场景用机器来代替人,而 AI 正是这个支点。
AI 如果是这个时代的契机,那么作为 Web 前端,在这人工智能时代,我们能做什么?
人工智能(Artificial Intelligence),英文缩写为 AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。
1.计算机科学
人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器,该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。
2.智慧「容器」
人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大,可以设想,未来人工智能带来的科技产品,将会是人类智慧的「容器」。
3.信息加工
人工智能可以对人的意识、思维的信息过程的模拟。人工智能不是人的智能,但能像人那样思考、也可能超过人的智能。
4.研究目标
人工智能是包括十分广泛的科学,它由不同的领域组成,如机器学习,计算机视觉等等,总的说来,人工智能研究的一个主要目标是使机器能够胜任一些通常需要人类智能才能完成的复杂工作。
如果要列举一下有哪些场景会用到 AI,我想可能不仅仅是如下这些:
机器视觉,指纹识别,人脸识别,人脸对比,手势检测,视网膜识别,虹膜识别,掌纹识别,专家系统,自动规划,智能搜索,定理证明,博弈,自动程序设计,智能控制,机器人学,语言和图像理解,遗传编程,物体检测,视频跟踪等。
人工智能就其本质而言,是对人的思维的信息过程的模拟。
对于人的思维模拟可以从两条道路进行,
1.结构模拟,仿照人脑的结构机制,制造出「类人脑」的机器;
2.是功能模拟,暂时撇开人脑的内部结构,而从其功能过程进行模拟。
现代电子计算机的产生便是对人脑思维功能的模拟,是对人脑思维的信息过程的模拟。
弱人工智能如今不断地迅猛发展,尤其是 2008 年经济危机后,美日欧希望借机器人等实现再工业化,工业机器人以比以往任何时候更快的速度发展,更加带动了弱人工智能和相关领域产业的不断突破,很多必须用人来做的工作如今已经能用机器人实现。
强人工智能则暂时处于瓶颈,还需要科学家们和人类的努力。
人工智能是依赖机器学习的,数据和算法是机器学习的核心,而数据更为重要。按照解决问题的能力,我们可以把人工智能,分成两类:
目前,我们能看到的人工智能,几乎都是弱人工智能,在解决特定问题的能力上,超越了人类。
1.数据可视化,依赖 D3.js
,ECharts
,WebGL
2.模型可视化
用可视化的手段去解释模型,辅助算法同学调参。最简单的一个应用前端同学肯定非常熟悉,我们来看下图:
是的,曲线函数和曲率我们很难记住,但是有相应的工具,会让一些数据和计算变得简单易懂。
3.相关技术
提到人工智能,和前端密切相关的几个 JS
类库有:
tensorflow.js Node
的 tvnet
算法,可以提取视频中的稠密光流。高性能计算:
大家可能发现一个问题,一般的 tensorflow
模型动辄几百兆,在前端怎么跑呢?这就不得不提到 MobileNet
,这是针对于移动端模型提出的神经网络架构,能极大地减少模型参数量,同理也能用到浏览器端上。
更多细节可以查看该文章:前端与人工智能,介绍非常到位。
既然前端和人工智能有如此多的交集,那么我们该从何做起呢?不要着急,我们先来看一个完整的人工智能项目包含哪些内容。
上图中,可以看到一个完整的人工智能项目是由:算法,数据,工程三部分构成。
工程部分我们可以理解为「大前端」,主要包含 5 部分:
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.
]]>既然 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
绘制在屏幕上,从而展现出来。
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
31.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
2div { color : red ; }
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
3div 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
30 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
30 0 2 1
0 1 0 1
0 0 1 1
如果你对上面这些问题都了如指掌,那么恭喜你,基础部分顺利过关,可以继续升级了!
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
打印出来的结果大致分为几类:
规则貌似有点看不懂,不用着急,我们接着往下看。
5.CSS 解析和 Webkit 有什么关系?
CSS 依赖 WebCore 来解析,而 WebCore 又是 Webkit 非常重要的一个模块。
要了解 WebCore
是如何解析的,我们需要查看相关源码:1
2
3
4
5
6
7
8
9
10
11
12CSSRule* 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
3let jarttoTest = () => {
// Todo
}
通过在线编译,生成如下结果:
从上图我们可以看出:我们的箭头函数被解析成了一段标准代码,包含了类型,起始位置,结束位置,变量声明的类型,变量名,函数名,箭头函数表达式等等。
标准的解析代码,我们可以对其进行一些加工和处理,之后通过相应 API
输出。很多场景都会用到这个过程,如:
场景千千万,但是都离不开一个过程,那就是:
AST 转换过程:解析 - 转换 - 生成
到这里,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
3div > 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
3div > 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 解析引擎,这里不再赘述。
浏览器还有一个非常棒的策略,在特定情况下,浏览器会共享 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
样式的书写顺序将之按照 DOM
树的结构分布渲染样式,然后开始遍历每个树结点的 CSS
样式进行解析,此时的 CSS
样式的遍历顺序完全是按照之前的书写顺序。
在解析过程中,一旦浏览器发现某个元素的定位变化影响布局,则需要倒回去重新渲染。
我们来看看下面这个代码片段:1
2
3
4width: 150px;
height: 150px;
font-size: 24px;
position: absolute;
当浏览器解析到 position
的时候突然发现该元素是绝对定位元素需要脱离文档流,而之前却是按照普通元素进行解析的,所以不得不重新渲染。
渲染引擎首先解除该元素在文档中所占位置,这就导致了该元素的占位情况发生了变化,其他元素可能会受到它回流的影响而重新排位。
我们对代码进行调整:1
2
3
4position: 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
9font-faces 不能使用超过5个web字体
import 禁止使用@import
regex-selectors 禁止使用属性选择器中的正则表达式选择器
universal-selector 禁止使用通用选择器*
unqualified-attributes 禁止使用不规范的属性选择器
zero-units 0后面不要加单位
overqualified-elements 使用相邻选择器时,不要使用不必要的选择器
shorthand 简写样式属性
duplicate-background-images 相同的url在样式表中不超过一次
6.减少 CSS
文档体积
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
24width
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
更多请查看上文:高效的 ComputedStyle
11.减少昂贵属性:
当页面发生重绘时,它们会降低浏览器的渲染性能。所以在编写 CSS
时,我们应该尽量减少使用昂贵属性,如:
12.依赖继承。如果某些属性可以继承,那么自然没有必要在写一遍。
13.遵守 CSS
顺序规则
上面就是对本文的一个总结,你了解 CSS
具体的实现原理,晓得规避错误书写方式,知道为什么这么优化,这就够了。
性能优化,进无止境。
]]>关于优化工具,我们主要从两方面说起:「性能评估工具」和「优化工具」。
1.性能评估工具
2.优化工具我们主要依赖「Chrome DevTools」,大致如下:
1.Lighthouse
安装Chrome Setting
- 更多工具 - 扩展程序 - 打开 Chrome
网上应用店 - Lighthouse
2.插件 - 生成报告
报告是我们的一个重要参考指标,这是网站评估的通用方法。
当然,网站也会有不同的类别,关注指标也不尽相同,后续我们会继续探讨「如何制定合理的网站优化性能指标」。
3.优化建议Lighthouse
比较人性化的点在于他既提出了问题,同时也提出了解决建议。
1.使用 PageSpeed
我们可以在「Chrome DevTools」菜单栏中找到并打开:
2.分析报告
1.关于 Network
我们重点关注标注的 3 处
2.Timing
也是优化不可缺少的工具:
补充说明一下:TTFB
:等待初始响应所用的时间,也称为第一字节的时间,这是我们判断服务器以及网络状况的重要指标。
此时间将捕捉到服务器往返的延迟时间,以及等待服务器传送响应所用的时间。
1.概览
2.版面主要由 4 部分构成
Overview
:页面性能的高级汇总,以及页面加载情况CPU
堆叠追踪的可视化3.Overview 详解
FPS
每秒帧数。绿色竖线越高,FPS
越高。 FPS
图表上的红色块表示长时间帧,很可能会出现卡顿。
CPUCPU
资源。此面积图指示消耗 CPU
资源的事件类型。
NET
每条彩色横杠表示一种资源。横杠越长,检索资源所需的时间越长。
每个横杠的浅色部分表示等待时间(从请求资源到第一个字节下载完成的时间)。
深色部分表示传输时间(下载第一个和最后一个字节之间的时间)。
需要特别注意,Performance
工具中的每一种颜色其实都有自己的含义。
小技巧:
使用无痕模式,减少 Chrome 扩展程序会给应用的干扰。
4.火焰图
NetworkNetwork
这里我们可以看出来,我们资源加载的一个顺序情况。什么时间加载了什么资源,通过这些,我们更直观的知道资源是否并行加载。
Frames
上文提及到的页面帧情况。
Interactions
Timings 中如下 5 个指标是我们优化的方向
Main:展示了主线程运行状况。X
轴代表着时间,每个长条代表着一个 event
。长条越长就代表这个 event
花费的时间越长。Y
轴代表了调用栈 call stack
。
在栈里,上面的 event
调用了下面的 event
。
注意红色警告:
JavaScript
运行过程中的大部分数据都保存在堆 Heap
中,所以 JavaScript
性能分析另一个比较重要的方面是内存,也就是堆的分析。很多情况下,并不是我们网站本身的问题,有可能你使用的三方资源拖累了站点性能。所以,我们需要使用 Show Third Party Badges
来进行排查。
1.测试站点:https://techcrunch.com/
2.打开控制面板:Command + Shift + P
3.打开 Network
,注意资源前面的彩色标志
三方资源都被标记出来了,移除或者替换那些影响性能的东西。
对于项目中不确定是否有用的资源,我们可以使用 Block Request URL
来排除。
1.选中资源 - 右键 - Block Request URL
阻止某些资源加载,控制变量法来排查页面性能问题。
1.打开控制面板:Command + Shift + P
2.输入:Show Coverage
3.找到相应的文件,可以看到文件左侧已经标记出了部分代码的使用情况
解决思路也很简单:
尽可能去通过 Webpack
来拆包,控制大小在 40KB
以下,移除那些未使用代码。
我们经常提到要优化 Dom
,那么节点控制在什么范围才合理呢?
查看所有 DOM
节点数:1
document.querySelectorAll('*').length
查看子元素个数:1
document.querySelectorAll('body > *').length
通常,只在需要时查找创建 DOM 节点的方法,并在不再需要时销毁它们。
关于重渲对页面的影响,我们就不多说了。那么如何知道页面的渲染过程呢?我们可以通过 Rendering
来可视化查看。
1.打开 Rendering 选项
2.刷新页面
绿色区域越重,说明重复渲染的次数越多,通过优化 DOM 来减少无效渲染。
你可能会很好奇,为什么要查看图层?
这是因为,我们经常会在不知不觉的情况下搞乱了图层关系,或者增加了不合适的图层。
1.打开控制面板:Command + Shift + P
2.选择 Layer
选项
是不是图层问题就清清楚楚的摆在眼前了~
通过优化工具,我们可以轻而易举的对网站进行定位分析。之后就可以快速展开优化,让网站高性能的运转起来。优化,也不过如此。
后续我们会深入了解一些优化相关的原理细节,如果你有优化相关的问题,欢迎一起探讨,一起进步。
]]>爱因斯坦曾经说过:「如果给我一个小时解答一道决定我生死的问题,我会花55分钟来弄清楚这道题到底是在问什么。一旦清楚了它在问什么,剩下的5分钟足够解答这个问题。」
虽然我们软件开发过程不会面临生死的抉择,但是却直接影响着用户的使用感受,决定着产品的走向。所以程序员如何减少开发中的 Bug,既反映了代码质量,也反映了个人综合能力。
那么我们该如何有效的减少开发中的 Bug 呢?
我觉得应该从两方面说起:业务层和代码层。
软件开发过程我们就不细说了,直接来看最重要的几个节点:
1.需求讨论阶段
一定要明确需求,测试,开发,产品三方务必达成一致。前期如果存在没有明确的问题,那么后期就会造成无效返工和不必要的争执,这在日常开发尤为常见。
所以,软件开发前期,我们都会进行「评审,反讲,评估」三个阶段。
2.开发完成阶段
开发完成后,程序员首先要完成「自测」,也就是软件开发中的「冒烟测试」,确保主流程无误。否则,在开发工程师提交代码后,测试工程师步履维艰,无法有效开展测试,会造成极大的资源浪费。
更规范的流程需要测试工程师在需求明确之后写出「测试用例」,开发工程师在完成开发后,自行对照「测试用例」完成初步验证,之后就可以代码提测了。
这么做的好处就是既保证了「高质量的代码交付」,同时减少了测试工程师的工作量,我们何乐而不为呢?
3.提测
自测和提测有什么区别呢,从软件开发过程来看,其实开发工程师和测试工程师其实完成了不同阶段的测试:
开发工程师「白盒测试」:
是指实际运行被测程序,通过程序的源代码进行测试而不使用用户界面。这种类型的测试需要从代码句法发现内部代码在算法、溢出、路径和条件等方面的缺点或者错误,进而加以修正。
白盒测试需要从代码句法发现内部代码在算法,溢出,路径,条件等等中的缺点或者错误,进而加以修正。
测试工程师实际进行的是「黑盒测试」。那么什么是「黑盒测试」呢?
黑盒测试也称功能测试,它是通过测试来检测每个功能是否都能正常使用。在测试中,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序接口进行测试。
它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息。黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和软件功能进行测试。
黑盒测试是以用户的角度,从输入数据与输出数据的对应关系出发进行测试的。
很明显,如果外部特性本身设计有问题或规格说明的规定有误,用黑盒测试方法是发现不了的。黑盒测试法注重于测试软件的功能需求,主要试图发现下列几类错误。
更多细节请查看文章:黑盒测试
代码层面,我们需要从以下几方面来说起:
1.Eslint 规避低级语法问题
这个显而易见,编写代码过程发现问题,避免因为简单语法,如:漏写了逗号,变量名写错,大小写问题等
2.边界处理
做好容错,必要的判空,还有就是代码边界问题。多想一想如果数组不存在,我们如何处理?如果数组越界,我们如何修复?如果数据缺失,我们如何使页面不崩溃?
3.单元测试
如果时间允许,我们可以做好单元测试,每次编译代码,或者提测前启动脚本,确定测试脚本都覆盖到了核心代码,尽可能减少代码出错率。
4.积累
为什么说要积累,其实道理很简单。随着开发经验的增长,你可能会碰到很多问题,那么如果细心积累,其实很多错误在不知不觉中就被处理了。反之,你会不断的掉入同一个坑里,在进坑与出坑中迷失自我。那么我们如何积累呢?
首先,碰到自己不会的问题,如果第一时间没有解决,通过查找或者请教别人解决了,那么一定要用小本本记下来,最好使用云笔记。好处不言自明。
其次,要积累自己的函数库,我们经常用到的一些方法,不妨自己做一个封装,不断沉淀。也许有一天,你会发现,自己不知不知觉中写出了一个 Lodash 函数库。
最后,你可以积累优秀的代码片段,嗯,「我们不生产代码,只是优秀代码的搬运工」。
5.学习
一句话,没有什么比学习优秀开源代码更有趣的事情了。阅读优秀源码,学习作者思想,站在巨人肩膀上,你才能走的更远!
做好上面这些,相信你一定会是一位出色的工程师。
对于这类开放问题仁者见仁,智者见智,我相信每个人都会有自己的看法,也会有自己一套独特的方法。不管黑猫白猫,能抓住老鼠的就是好猫。对于程序员来说,能减少 Bug 的方法就是好方法。
程序员群体流传一句话:不写代码就有没有 Bug。
我们不能因为怕犯错误而减少写代码,更应该知难而上,越挫越勇。要知道日常开发中 「Bug 是不可避免的,只能减少」。
当然,这不应该成为我们写出 Bug 推脱的理由。不断超越,方是永恒。
]]>Electron
,可以看我之前的文章:初探 Electron - 理论篇,理论知识没有变。如果你想了解 Electron5.0
版本如何构建 React
项目,不妨继续看本文。需要注意:网上流传的 1.0 版本的项目应该已经启动不了了。
之前研究的时候,Electron
还是 1.0
版本,因此写了一系列的文章。
经过本次尝试,发现 5.0
版本有了更多的变化,所以不想误人子弟,索性更新此系列文章。
这次起手,我们就来构建一个 React 项目。
1.首先,全局安装 cli
:1
npm install --global create-react-app
2.创建项目1
create-react-app electron5-react-demo
3.启动1
cd electron5-react-demo && yarn start
4.访问 http://localhost:3000/
如果看到如下页面,说明你的 React
项目已经成功启动了。
1.首先需要安装 Electron
和 Electron-builder
1
yarn add electron electron-builder --dev
2.增加相应开发工具1
2yarn add wait-on concurrently --dev
yarn add cross-env electron-is-dev
3.项目根目录下新建文件:electron.js
1
touch public/electron.js
此时项目目录如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18.
├── README.md
├── main.js
├── node_modules
├── public
│ ├── electron.js
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ └── serviceWorker.js
└── yarn.lock
4.修改代码,可以去官网上拷贝一份内容,写入 electron.js
,具体如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')
+ const isDev = require('electron-is-dev');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
+ mainWindow.loadURL(
+ isDev
+ ? 'http://localhost:3000'
+ : `file://${path.join(__dirname, "../build/index.html")}`
+ );
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) createWindow()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
注意我们做了两处修改(加号位置):1
2
3
4
5
6
7
8
9
10// 引入环境变量:
const isDev = require('electron-is-dev');
...
// 设置启动文件
mainWindow.loadURL(
isDev
? 'http://localhost:3000'
: `file://${path.join(__dirname, "../build/index.html")}`
);
5.修改 package.json
文件,总共两处:
其一:1
2
3
4
5
6
7{
"name": "electron5-react-demo",
"version": "0.1.0",
"private": true,
+ "main": "public/electron.js",
+ "homepage": "./",
}
其二,修改启动项:1
2
3
4
5
6
7
8
9"react-start": "react-scripts start",
"react-build": "react-scripts build",
"electron-start": "electron .",
"electron-build": "electron-builder",
"release": "yarn react-build && electron-builder --publish=always",
"build": "yarn react-build && yarn electron-build",
"start": "concurrently \"cross-env BROWSER=none yarn react-start\" \"wait-on http://localhost:3000 && electron .\""
"test": "react-scripts test",
"eject": "react-scripts eject",
1 | yarn start |
看到如下界面,恭喜你,已经成功启动了:
试着修改一下吧,热修改也已经生效了。
如果你想构建,可以使用1
yarn build
需要注意:构建会同时构建 React 和 Electron 两个项目。
构建完成后,项目目录中会出现一个 dist
目录:1
2
3
4
5
6
7
8.
├── builder-effective-config.yaml
├── electron5-react-demo-0.1.0-mac.zip
├── electron5-react-demo-0.1.0.dmg
├── electron5-react-demo-0.1.0.dmg.blockmap
├── latest-mac.yml
└── mac
└── electron5-react-demo.app
目录中的 dmg
就是 Mac
上面的安装程序,双击安装:
安装之后去运行吧,到这里,我们已经完成了整个项目。
目前大部分的项目可能都会用到 Git
来做代码管理,那么我们在不断的修改项目的过程中,可能会关注如下几个问题:
1.每个参与者贡献代码量,按劳分配某些资源🙈;
2.参与者的代码增删量,提交次数等;
3.统计活跃度;
那么,如何来对代码量做统计呢?
一般情况,我们可以直接通过 Git log
来统计,如:
1.统计个人代码量:1
git log --author="jartto" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -
2.贡献值统计:1
git log --pretty='%aN' | sort -u | wc -l
3.查看排名前 5 的贡献者:1
git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5
更多 log
操作可以请移步:Git 代码统计。
这时候,你可能在想:有没有省时省力的方式呢,顺便帮我生成报告。答案是肯定的,是时候请出我们的 git_stats
了。
1.首先,我们需要全局安装 git_stats
1
sudo gem install git_stats
2.接下来,运行1
git_stats generate
3.打开 git_stats
目录1
cd git_stats && open index.html
1.概览
2.Dashboard
可视化
如果你对 git_stats
生成的一大堆文件不满意,我们还有一种方式可以「无侵入」,同时显得更加「高冷」。
cloc
cloc 最优秀的地方就是「简洁粗暴」,我们来尝试一下。
1.尝试一下 cloc
,首先,全局安装:1
npm install -g cloc
2.简单用例1
2
3
4
5
6
7
8
9
10
11cloc [options] <file(s)/dir(s)/git hash(es)>
Count physical lines of source code and comments in the given files
(may be archives such as compressed tarballs or zip files) and/or
recursively below the given directories or git commit hashes.
Example: cloc src/ include/ main.c
cloc [options] --diff <set1> <set2>
Compute differences of physical lines of source code and comments
between any pairwise combination of directory names, archive
files or git commit hashes.
Example: cloc --diff Python-3.5.tar.xz python-3.6/
3.使用1
Usage: cloc [options] <file(s)/dir(s)/git hash(es)> | <set 1> <set 2> | <report files>
进入项目,执行:1
cloc .
稍等片刻,就会有一个输出结果:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39-----------------------------------------------------------------------------------
Language files blank comment code
-----------------------------------------------------------------------------------
JavaScript 10319 172724 254924 951843
HTML 679 120179 3665 224595
JSON 1714 256 0 182127
Markdown 1400 63461 2 171768
C++ 69 3538 3197 20331
Python 51 4292 7801 19137
C/C++ Header 117 3628 2033 18942
CSS 113 2011 823 16594
XML 32 4427 1300 11277
Sass 65 282 414 4255
Stylus 60 539 593 3215
YAML 189 324 413 3039
D 57 0 0 3003
EJS 113 43 8 2160
reStructuredText 18 681 51 2122
Bourne Shell 20 394 398 1875
SVG 5 0 1 1646
LESS 13 26 33 1343
make 42 378 245 1310
TypeScript 17 276 584 1161
Perl 1 87 170 582
DTD 1 179 177 514
m4 2 40 2 266
Lisp 3 42 38 264
Bourne Again Shell 8 43 24 161
C 4 40 37 149
Ruby 6 24 5 140
JSON5 2 0 0 123
CoffeeScript 3 18 28 99
Handlebars 4 18 0 96
Smarty 6 17 30 91
Windows Resource File 1 1 1 33
DOS Batch 5 2 0 16
IDL 1 1 0 11
zsh 1 4 13 7
-----------------------------------------------------------------------------------
4.更多的使用命令,可以查看帮助1
cloc --help
上文介绍了三种 Git
代码统计方式:
1.通过 Git log
统计,稍微会麻烦一些,需要有一些 awk
知识的储备;
2.使用插件 git_stats
来生成可视化报告,对用户友好。美中不足就是会在当前项目增加很多 html
统计可视化文件;
3.命令行工具 cloc
,简单易用,无侵入,使用门槛低;
综上所述,我们可以按照自己的使用场景来灵活的选用不同方式。