FSM 有限状态机

有限状态机

有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机(英语:finite-state automation,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。

特点

  • 状态总数(state)是有限的。

  • 任一时刻,只处在一种状态之中。

  • 某种条件下,会从一种状态转变(transition)到另一种状态。

  • 每个状态都是一个机器,所有机器接受的输入是一致的

  • 状态机的本身是没有状态的,如果用函数来表示的话,应该是纯函数。

  • 每一个状态机都知道自己的下一个状态

    每个机器都有确定的下一个状态 (Moore)

    每个机器根据输入决定下一个状态 (Mealy)

案例

网页上有一个菜单元素。鼠标悬停的时候,菜单显示;鼠标移开的时候,菜单隐藏。如果使用有限状态机描述,就是这个菜单只有两种状态(显示和隐藏),鼠标会引发状态转变。

本案例中的状态机其实就是摩尔状态机,每个状态都有确定的下一个状态,而存在的问题就是状态和行为是耦合的。

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
<body>
<div id = 'box1'>1</div>
<div id = 'box2' style="display:none">2</div>
<script>

var menu = {
// 当前状态
currentState: 'hide',
// 绑定事件
initialize: function (dom) {
dom.addEventListener('mouseover',this.transition.bind(this))
dom.addEventListener('mouseout',this.transition.bind(this))
},
// 状态转换
transition: function (event) {
switch (this.currentState) {
case "hide":
this.currentState = 'show';
document.getElementById('box2').style.display ='block'
break;
case "show":
this.currentState = 'hide';
document.getElementById('box2').style.display ='none'
break;
default:
console.log('Invalid State!');
break;
}
}
};
menu.initialize(document.getElementById('box1'));
</script>
</body>

查找字符串

尝试在一个字符串中找到 abcd

需要注意的是在每次状态迁移之后,一定要把上一个的状态置为false,因为状态转移之后与上一个状态再没有任何关系

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
const machine = (str) => {
let foundA = false;
let foundB = false;
let foundC = false;
for (let s of str) {
if (s === 'a') {
foundA = true;
} else if (s === 'b' && foundA) {
foundA = false;
foundB = true;
} else if (s === 'c' && foundB) {
foundB = false;
foundC = true;
} else if (s === 'd' && foundC) {
foundC = false;
return true;
} else {
foundA = false;
foundB = false;
foundC = false;
}
}
return false;
}
console.log(machine('abbccd'))

查找字符串 函数式状态机

显而易见的好处是,状态机本身没有状态,所以无需在维护状态

需要注意的一点是,每一个函数返回的是start(s),因为遇到ababcd这种情况时,第二个a可以当做字符串的开头,如果直接返回start会跳过这一步的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const start = (s) => {
if (s === 'a') return foundB;
return start;
}
const foundB = (s) => {
if (s === 'b') return foundC;
return start(s);
}
const foundC = (s) => {
if (s === 'c') return foundD;
return start(s);
}
const foundD = (s) => {
if (s === 'd') return end;
return start(s)
}
const end = () => end;
let state = start;
for (let s of 'ababcd') {
state = state(s);
}
console.log(state === end)

有效数字问题

Cookie的domain属性

一些概念

调试第三方模块

存在的问题

有时候需要在react项目中打断点调试,或者调试react源码

如果直接在node_modules中的文件打断点,添加注释或修改,在某些vsCode的版本中会提示nobonud breakPoint,不能进入断点

但最主要的问题,当项目重新初始化,所有的修改会被删除

安装插件

在vsCode市场中安装 Debugger for Chrome 插件

添加配置文件

选择Chrome

修改配置文件端口为项目端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000", //把端口修改为项目端口
"webRoot": "${workspaceFolder}"
}
]
}
  • 将需要调试的第三方包复制到本地,并在node_modules中删除

  • 进入第三方包的文件夹根目录执行,yarn link 创建一个链接

  • 进入项目文件夹根目录执行,yarn link "package name" 将依赖添加到node_modules中

  • 这时查看依赖包的路径为本地第三方包的路径

  • 进入项目文件夹根目录执行,yarn unlink "package name" 将依赖添从node_modules中删除

  • 进入第三方包的文件夹根目录执行,yarn unlink, 不在链接到全局

调试

如果react项目打断点不能被捕获,可以尝试在入口index.js文件中添加一行 debugger;

这样在调试器中点击下一步,会跳到下一个断点

原理

Nginx进程模型

Nginx默认采用多进程工作方式,Nginx启动后,会运行一个master进程和多个worker进程。其中master充当整个进程组与用户的交互接口,同时对进程进行监护,管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。worker用来处理基本的网络事件,worker之间是平等的,他们共同竞争来处理来自客户端的请求。

在创建master进程时,先建立需要监听的socket(listenfd),然后从master进程中fork()出多个worker进程,如此一来每个worker进程多可以监听用户请求的socket。一般来说,当一个连接进来后,所有在Worker都会收到通知,但是只有一个进程可以接受这个连接请求,其它的都失败,这是所谓的惊群现象。nginx提供了一个accept_mutex(互斥锁),有了这把锁之后,同一时刻,就只会有一个进程在accpet连接,这样就不会有惊群问题了。

先打开accept_mutex选项,只有获得了accept_mutex的进程才会去添加accept事件。nginx使用一个叫ngx_accept_disabled的变量来控制是否去竞争accept_mutex锁。ngx_accept_disabled = nginx单进程的所有连接总数 / 8 -空闲连接数量,当ngx_accept_disabled大于0时,不会去尝试获取accept_mutex锁,ngx_accept_disable越大,于是让出的机会就越多,这样其它进程获取锁的机会也就越大。不去accept,每个worker进程的连接数就控制下来了,其它进程的连接池就会得到利用,这样,nginx就控制了多进程间连接的平衡。

每个worker进程都有一个独立的连接池,连接池的大小是worker_connections。这里的连接池里面保存的其实不是真实的连接,它只是一个worker_connections大小的一个ngx_connection_t结构的数组。并且,nginx会通过一个链表free_connections来保存所有的空闲ngx_connection_t,每次获取一个连接时,就从空闲连接链表中获取一个,用完后,再放回空闲连接链表里面。一个nginx能建立的最大连接数,应该是worker_connections * worker_processes。当然,这里说的是最大连接数,对于HTTP请求本地资源来说,能够支持的最大并发数量是worker_connections * worker_processes,而如果是HTTP作为反向代理来说,最大并发数量应该是worker_connections * worker_processes/2。因为作为反向代理服务器,每个并发会建立与客户端的连接和与后端服务的连接,会占用两个连接。

Nginx处理HTTP请求流程

http请求是典型的请求-响应类型的的网络协议。http是文件协议,所以我们在分析请求行与请求头,以及输出响应行与响应头,往往是一行一行的进行处理。通常在一个连接建立好后,读取一行数据,分析出请求行中包含的method、uri、http_version信息。然后再一行一行处理请求头,并根据请求method与请求头的信息来决定是否有请求体以及请求体的长度,然后再去读取请求体。得到请求后,我们处理请求产生需要输出的数据,然后再生成响应行,响应头以及响应体。在将响应发送给客户端之后,一个完整的请求就处理完了。

高可用

什么是高可用

高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。如果一个系统能够一直提供服务,那么这个可用性则是百分之百,但是天有不测风云。所以我们只能尽可能的去减少服务的故障。

解决的问题

在生产环境上很多时候是以Nginx做反向代理对外提供服务,但是一天Nginx难免遇见故障,如:服务器宕机。当Nginx宕机那么所有对外提供的接口都将导致无法访问。

虽然我们无法保证服务器百分之百可用,但是也得想办法避免这种悲剧,今天我们使用keepalived来实现Nginx的高可用。

双机热备方案

这种方案是国内企业中最为普遍的一种高可用方案,双机热备其实就是指一台服务器在提供服务,另一台为某服务的备用状态,当一台服务器不可用另外一台就会顶替上去。

keepalived是什么?

Keepalived软件起初是专为LVS负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了可以实现高可用的VRRP (Virtual Router Redundancy Protocol ,虚拟路由器冗余协议)功能。因此,Keepalived除了能够管理LVS软件外,还可以作为其他服务(例如:Nginx、Haproxy、MySQL等)的高可用解决方案软件

故障转移机制

Keepalived高可用服务之间的故障切换转移,是通过VRRP 来实现的。
在 Keepalived服务正常工作时,主 Master节点会不断地向备节点发送(多播的方式)心跳消息,用以告诉备Backup节点自己还活着,当主 Master节点发生故障时,就无法发送心跳消息,备节点也就因此无法继续检测到来自主 Master节点的心跳了,于是调用自身的接管程序,接管主Master节点的 IP资源及服务。而当主 Master节点恢复时,备Backup节点又会释放主节点故障时自身接管的IP资源及服务,恢复到原来的备用角色。

实现过程

安装keepalived

1
sudo apt install keepalived

修改主机(192.168.16.128)keepalived配置文件

yum方式安装的会生产配置文件在/etc/keepalived下:

1
vi keepalived.conf

keepalived.conf:

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
#检测脚本
vrrp_script chk_http_port {
script "/usr/local/src/check_nginx_pid.sh" #心跳执行的脚本,检测nginx是否启动
interval 2 #(检测脚本执行的间隔,单位是秒)
weight 2 #权重
}
#vrrp 实例定义部分
vrrp_instance VI_1 {
state MASTER # 指定keepalived的角色,MASTER为主,BACKUP为备
interface ens33 # 当前进行vrrp通讯的网络接口卡(当前centos的网卡) 用ifconfig查看你具体的网卡
virtual_router_id 66 # 虚拟路由编号,主从要一直
priority 100 # 优先级,数值越大,获取处理请求的优先级越高
advert_int 1 # 检查间隔,默认为1s(vrrp组播周期秒数)
#授权访问
authentication {
auth_type PASS #设置验证类型和密码,MASTER和BACKUP必须使用相同的密码才能正常通信
auth_pass 1111
}
track_script {
chk_http_port #(调用检测脚本)
}
virtual_ipaddress {
192.168.16.130 # 定义虚拟ip(VIP),可多设,每行一个
}
}

virtual_ipaddress 里面可以配置vip,在线上通过vip来访问服务。

interface需要根据服务器网卡进行设置通常查看方式ip addr

authentication配置授权访问后备机也需要相同配置

修改备机keepalived配置文件

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
#检测脚本
vrrp_script chk_http_port {
script "/usr/local/src/check_nginx_pid.sh" #心跳执行的脚本,检测nginx是否启动
interval 2 #(检测脚本执行的间隔)
weight 2 #权重
}
#vrrp 实例定义部分
vrrp_instance VI_1 {
state BACKUP # 指定keepalived的角色,MASTER为主,BACKUP为备
interface ens33 # 当前进行vrrp通讯的网络接口卡(当前centos的网卡) 用ifconfig查看你具体的网卡
virtual_router_id 66 # 虚拟路由编号,主从要一直
priority 99 # 优先级,数值越大,获取处理请求的优先级越高
advert_int 1 # 检查间隔,默认为1s(vrrp组播周期秒数)
#授权访问
authentication {
auth_type PASS #设置验证类型和密码,MASTER和BACKUP必须使用相同的密码才能正常通信
auth_pass 1111
}
track_script {
chk_http_port #(调用检测脚本)
}
virtual_ipaddress {
192.168.16.130 # 定义虚拟ip(VIP),可多设,每行一个
}
}

检测脚本:

1
2
3
4
5
6
7
8
9
#!/bin/bash
#检测nginx是否启动了
A=`ps -C nginx --no-header |wc -l`
if [ $A -eq 0 ];then #如果nginx没有启动就启动nginx
systemctl start nginx #重启nginx
if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then #nginx重启失败,则停掉keepalived服务,进行VIP转移
killall keepalived
fi
fi

脚本授权:chmod 775 check_nginx_pid.sh

说明:脚本必须通过授权,不然没权限访问啊,在这里我们两条服务器执行、VIP(virtual_ipaddress:192.168.16.130),我们在生产环境是直接通过vip来访问服务。

模拟nginx故障:

修改两个服务器默认访问的Nginx的html页面作为区别。

首先访问192.168.16.130,通过vip进行访问,页面显示192.168.16.128;说明当前是主服务器提供的服务。

这个时候192.168.16.128主服务器执行命令:

systemctl stop nginx; #停止nginx

再次访问vip(192.168.16.130)发现这个时候页面显示的还是:192.168.16.128,这是脚本里面自动重启。

现在直接将192.168.16.128服务器关闭,在此访问vip(192.168.16.130)现在发现页面显示192.168.16.129这个时候keepalived就自动故障转移了,一套企业级生产环境的高可用方案就搭建好了。

keepalived中还有许多功能比如:邮箱提醒等

动静分离

静态资源部署在nginx服务器

nginx来当做静态资源服务器

来自80端口的请求会去nginx服务器根路径下的data文件夹查找

http://xxx.xxx.xxx.xx/static/a/logo.png 会请求根路径下 /data/static/a/logo.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
http {
include mime.types;
default_type application/octet-stream;


server {
listen 80;
server_name xxx.xxx.xxx.xxx;

#charset koi8-r;

#access_log logs/host.access.log main;

location /static/ {
root /data/;
}

}
}

转发到静态资源服务器

转发时使用负载均衡配置

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
http {
include mime.types;
default_type application/octet-stream;
upstream stack_pools {
server 172.25.254.134:80 weight=5;
}
upstream dynamic_pools {
server 172.25.254.135:80 weight=5;
}
server {
listen 80;
server_name xxx.xxx.xxx.xx;
location / {
root html;
index index.html index.htm;
proxy_pass http://dynamic_pools;
}
location /image/ {
proxy_pass http://stack_pools;
}
location /dynamic/ {
roxy_pass http://dynamic_pools;
}
}
}

负载均衡

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
http {
upstream myServer{
ip_hash;
server localhost:1111 weight=10;
server localhost:2222;
fair;
}
listen port;
server_name xxx.xxx.xxx.xx;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
proxy_pass http://myServer;
}
}

轮询策略

每个请求按时间的顺序逐一分配到不同德尔服务器中,如果服务器挂了,可以自动剔除。

权重策略

权重越高,被分配的请求越多

ip hash

每个请求按访问ip的hash结果分配,这样每个访客固定访问一个服务器后,可以解决session问题。

fair 第三方

根据请求的响应时间来分配,响应时间短的先分配

反向代理

  • proxy_pass 包含 URI 路径(如 /api):

    无论末尾是否有斜杠,均删除 location 匹配部分,拼接剩余路径。

    例:location /webapp + proxy_pass http://xxx/api → /api/剩余路径。

  • proxy_pass 不包含 URI 路径(如 http://xxx):

    末尾无斜杠 → 直接拼接完整请求 URI(http://xxx/完整路径)。

    末尾有斜杠 → 替换 location 匹配部分,拼接剩余路径(http://xxx/剩余路径)。

简单反向代理

把本机 80 端口的请求转发到其他的服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
http {
include mime.types;
default_type application/octet-stream;

server {
listen 80;
server_name xxx.xxx.xxx.xxx;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
root xxx.xxx.xxx.xxx:port;
index index.html index.htm;
}

}
}

通过正则匹配路径转发

根据路径中不同字段转,把 80 端口的请求,转发到不同的服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
http {
include mime.types;
default_type application/octet-stream;

server {
listen 80;
server_name xxx.xxx.xxx.xxx;

location ~ /api/ {
proxy_pass xxx.xxx.xxx.xxx:port;
}
location ~ /server/ {
proxy_pass xxx.xxx.xxx.xxx:port;
}

}
}

配置文件

位置

1
/etc/nginx/nginx.conf

配置结构

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
...              #全局块

events { #events块
...
}

http #http块
{
... #http全局块
server #server块
{
... #server全局块
location [PATTERN] #location块
{
...
}
location [PATTERN]
{
...
}
}
server
{
...
}
... #http全局块
}

全局块

1
2
# 值越大,可支持的并发数量越多
worker_processes 1

events块

主要涉及Nginx服务器和用户的网络连接

http块

最常用的配置,代理,缓存,日志定义等绝大多数功能和第三方模块的配置都在这里

http全局块

http全局配置包括文件引入,MIME-TYPE定义,日志自定义,链接超时时间,单链接请求上限等

server块

这块和虚拟主机有密切的关系,虚拟主机从用户的角度看,一台独立的硬件主机是完全一样的,该技术的产生是为了节约互联网服务器硬件成本

每个http块又可以包括多个server块,每个server块就相当于一个虚拟主机

每个server块也分为全局server块,以及可以同时包含多个location块

1
2
3
4
5
6
7
8
server {

# 监听80端口
listen 80;

# 主机名称
server_name localhost;
}
全局server块

最常见的配置是本虚拟主机的监听配置和本虚拟主机的名称或IP配置。

location块

主要作用是基于Nginx服务器接受到的请求字符串(例如,server_name/uri-string),对虚拟主机名称(也可以是ip别名)之外的字符串(例如 前面的uri-string)进行匹配,对待定的请求进行处理,地址定向,数据缓存和应答控制等功能,还有许多第三方模块的配置也在这里进行

详细配置

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
########### 每个指令必须有分号结束。#################
#user administrator administrators; #配置用户或者组,默认为nobody nobody。
#worker_processes 2; #允许生成的进程数,默认为1
#pid /nginx/pid/nginx.pid; #指定nginx进程运行文件存放地址
error_log log/error.log debug; #制定日志路径,级别。这个设置可以放入全局块,http块,server块,级别以此为:debug|info|notice|warn|error|crit|alert|emerg
events {
accept_mutex on; #设置网路连接序列化,防止惊群现象发生,默认为on
multi_accept on; #设置一个进程是否同时接受多个网络连接,默认为off
#use epoll; #事件驱动模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport
worker_connections 1024; #最大连接数,默认为512
}
http {
include mime.types; #文件扩展名与文件类型映射表
default_type application/octet-stream; #默认文件类型,默认为text/plain
#access_log off; #取消服务日志
log_format myFormat '$remote_addr–$remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for'; #自定义格式
access_log log/access.log myFormat; #combined为日志格式的默认值
sendfile on; #允许sendfile方式传输文件,默认为off,可以在http块,server块,location块。
sendfile_max_chunk 100k; #每个进程每次调用传输数量不能大于设定的值,默认为0,即不设上限。
keepalive_timeout 65; #连接超时时间,默认为75s,可以在http,server,location块。

upstream mysvr {
server 127.0.0.1:7878;
server 192.168.10.121:3333 backup; #热备
}
error_page 404 https://www.baidu.com; #错误页
server {
keepalive_requests 120; #单连接请求上限次数。
listen 4545; #监听端口
server_name 127.0.0.1; #监听地址
location ~*^.+$ { #请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。
#root path; #根目录
#index vv.txt; #设置默认页
proxy_pass http://mysvr; #请求转向mysvr 定义的服务器列表
deny 127.0.0.1; #拒绝的ip
allow 172.18.5.54; #允许的ip
}
}
}

location匹配优先级

匹配优先级从上之下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
location = / {
#规则A
}
location = /login {
#规则B
}
location ^~ /static/ {
#规则C
}
location ~ .(gif|jpg|png|js|css)$ {
#规则D
}
location ~* .png$ {
#规则E
}
location / {
#规则F
}

常用命令

使用nginx命令需要进入到nginx可执行文件目录 /usr/sbin/

nginx document

查看版本

1
nginx -v

启动nginx

1
./nginx

快速关闭

1
nginx -s stop

安全关闭

1
nginx -s quit

重新加载配置文件

1
nginx -s reload
  • Copyrights © 2015-2026 SunZhiqi

此时无声胜有声!

支付宝
微信