Skip to main content

Node 初探

Nginx + Node + Vue 部署初试(2019-03-08更新)

Nginx

  1. 定义

    异步框架的 Web服务器,也可以用作反向代理负载平衡器 , HTTP缓存, 媒体流等的开源软件。它最初是一个旨在实现最高性能和稳定性的Web服务器。除了HTTP服务器功能外,NGINX还可以用作电子邮件(IMAPPOP3SMTP)的代理服务器以及HTTPTCPUDP服务器的反向代理和负载平衡器。

  2. 特点

    • 更快
    • 高扩展性, Nginx的模块都是嵌入到二进制文件中执行
    • 高可靠性
    • 低内存消耗
    • 单机支持10万次的并发连接
    • 热部署, master管理进行与work工作进程分离设计,因此具备热部署功能
    • 最自由的BSD许可协议
  3. 功能

    • 静态服务(css , js , html, images, videos
    • SSLTLS SNI 支持
    • HTTP/HTTPS, SMTP, IMAP/POP3 反向代理
    • FastCGI反向代理
    • 负载均衡
    • 页面缓存(CDN
    • 支持gzipexpirse
    • 支持 keep-alive 和管道连接
    • 基于 PCRErewrite 重写模块
    • 带宽限制
    • 基于IP 和名称的虚拟主机服务
    • 支持访问速率、并发限制
    • 反向代理(适用2000WPV、并发连接1W/秒),简单的负载均衡和容错
    • 基于客户端 IP 地址和 HTTP 基本认证的访问控制

Mac 安装Nginx

// 推荐使用`brew`, 安装`homebrew`
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
// Homebrew 安装 Nginx
brew install nginx
// Mac 下 Nginx的目录
cd /usr/local/etc/nginx
ll -al
vim nginx.conf

homebrew详见

cd-nginx

Nginx 参数列表

配置参数属性解释说明参数列表
user设置nginx服务的系统使用用户nobody(注意:此处用户如果比启动Nginx的用户权限低,你需要使用当前用户重启Nginx)
nginx -s stop 关闭nginx-> nginx 启动-> `ps aux
worker_processes开启的线程数一般与服务器核数保持一致
error_log定位全局错误日志文件错误日志定义等级,[ debug | info notice | warn | error | crit ],debug输出最多,crir输出最少
pid指定进程id的存储文件位置
worker_rlimit_nofile指定一个nginx进程打开的最多文件描述符数目,受系统进程的最大打开文件数量限制
events包含Nginx中所有处理连接的设置
httpNginx http处理的所有核心特性

Event

Nginx是以event(事件)处理模型为基础的模块。它为了支持跨平台,抽象出了event模块。它支持的event处理类型有:AIO(异步IO),/dev/pollSolarisUnix特有),epollLinux特有),eventportSolaris 10特有),kqueueBSD特有),pollrtsig(实时信号),select等。 它的作用是监听accept后建立的连接,对读写事件进行添加删除。事件处理模型和Nginx的非阻塞IO模型结合在一起使用。当IO可读可写的时候,相应的读写事件就会被唤醒,此时就会去处理事件的回调函数。

配置参数属性解释说明参数列表
worker_connections定义每个进程的最大连接数,受系统进程的最大打开文件数量限制单个后台worker process进程的最大并发链接数 (最大连接数= worker_processes worker_connections)
在反向代理环境下:
最大连接数 = worker_processes
worker_connections / 4
use选择一个可用的事件的模型(可以在编译时指定),Nginx会自动选择事件的模型[ epoll | /dev/poll | poll | eventport | kqueue | select | rtsig ]
multi_accept一个新连接通知后接受尽可能多的连接on / off
accept_mutex开启或者禁用使用互斥锁来打开socketson / off

Event Use支持的事件模型

events-use

Events详见

HTTP

可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。

配置参数属性解释说明参数列表
include主模块指令,实现对配置文件所包含的文件的设定,可以减少主配置文件的复杂度,DNS主配置文件中的zonerfc1912,acl基本上都是用include语句
default_type核心模块指令默认设置为二进制流,也就是当文件类型未定义时使用这种方式
log_format日志格式的设定日志格式的名称,可自行设置,后面引用
access_log引用日志引用log_format设置的名称
keepalive_timeout设置客户端连接保存活动的超时时间0是无限制
sendfile开启高效文件传输模式on / off
tcp_nopush开启防止网络阻塞on / off
tcp_nodelay开启防止网络阻塞on / off
upstream负载均衡
serverNginx的server虚拟主机配置

Upstream

它的作用是实现在轮询和客户端IP之间的后端服务器负荷平衡。

配置参数属性解释说明
轮询(默认)当weight不指定时,各服务器weight相同,每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
weight指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况
ip_hash每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
fair(第三方)按后端服务器的响应时间来分配请求,响应时间短的优先分配。
url_hash(第三方)按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。

weight 默认为1.weight越大,负载的权重就越大。

Nginx Upstream 状态

例如:

upstream news {
server 127.0.0.1:9527 backup;
server 127.0.0.1:9527 weight=1 max_fails=2 fail_timeout=3;
...
}
配置参数属性解释说明
backup预留的备份服务器
down当前的server暂时不参与负载均衡
fail_timeout经过max_fails 失败后,服务暂停的时间
max_conns限制最大的接收的连接数
max_fails允许请求失败的次数

use location:在server中添加

proxy_pass http://127.0.0.1:9527;
// 因为我的API接口是这个,such as /api/profile
// location 具体匹配规则详见后面
location ~ /api/ {
proxy_pass http://127.0.0.1:9527;
}

Server

配置虚拟主机的相关参数,一个http中可以有多个server

配置参数属性解释说明参数列表
listen监听端口http -> 80 / https -> 443
server_name设置主机域名localhost
charset设置访问的语言编码
access_log设置虚拟主机访问日志的存放路径及日志的格式
location设置虚拟主机的基本信息

Location

配置请求的路由,以及各种页面的处理情况。

配置参数属性解释说明参数列表
root设置虚拟主机的网站根目录Vue项目的根目录/Users/rainy/Desktop/MyWork/Work/cloudwiz-website/dist
index设置虚拟主机默认访问的网页index.html index.htm
proxy通过不同协议将请求从NGINX传递到代理服务器
  • =: 开头表示精确匹配,如 api 中只匹配根目录结尾的请求,后面不能带任何字符串.
  • ^~ :开头表示uri以某个常规字符串开头,不是正则匹配.
  • ~: 开头表示区分大小写的正则匹配.
  • ~*: 开头表示不区分大小写的正则匹配.
  • / : 通用匹配, 如果没有其它匹配,任何请求都会匹配到.

匹配优先级(高到低)

  1. location =
  2. location 完整路径
  3. location ^~ 路径
  4. location ~,~* 正则顺序
  5. location 部分起始路径
  6. /

详见Location配置

Reverse Proxy

NGINX代理请求时,它会将请求发送到指定的代理服务器,获取响应并将其发送回客户端。可以使用指定的协议将请求代理到HTTP服务器(另一个NGINX服务器或任何其他服务器)或非HTTP服务器(可以运行使用特定框架(如PHPPython)开发的应用程序)。

location  / some / path /  {
proxy_pass http://www.example.com:8080;

proxy_set_header Host $ host ;
proxy_set_header X-Real-IP $ remote_addr ;

// 禁用特定位置的缓冲
proxy_buffering off ;
proxy_buffers 16 4k ;
proxy_buffer_size 2k ;

proxy_bind 127.0.0.2 ; // IP地址也可以用变量指定
}

将请求传递给非HTTP代理服务器,**_pass应使用相应的指令:

配置参数属性解释说明参数列
proxy_pass将请求传递给HTTP代理服务器
proxy_set_header传递请求标头默认情况下,NGINX在代理请求中重新定义两个头字段“Host”和“Connection”,并删除其值为空字符串的头字段。“Host”设置为$proxy_host变量,“Connection”设置为close
proxy_buffering负责启用和禁用缓冲on / off
proxy_buffers请求分配的缓冲区的大小和数量
proxy_buffer_size代理服务器的响应的第一部分存储在单独的缓冲区大小通常包含一个相对较小的响应头,并且可以比其余响应的缓冲区小。
proxy_bind接受来自特定IP网络或IP地址范围的连接指定proxy_bind必要网络接口的指令和IP地址

详见Proxy

全局变量Global Variable

变量名变量含义
$args请求中的参数
$content_lengthHTTP请求信息里的Content-Length
$content_type请求信息里的Content-Type
$host请求信息中的Host,如果请求中没有Host行,则等于设置的服务器名
$http_cookiecookie 信息
$http_referer引用地址
$http_user_agent客户端代理信息
$remote_addr客户端地址
$remote_port客户端端口号
$remote_user客户端用户名,认证用
$request_method请求的方法,比如GETPOST
$request_uri完整的原始请求URI(带参数)
$scheme请求方案,httphttps
$server_addr接受请求的服务器的地址,如果没有用listen指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费);
$server_protocol请求的协议版本,HTTP/1.0HTTP/1.1
$uri请求中的当前URI, $uri在请求处理期间 ,值可能会发生变化,例如在执行内部重定向或使用索引文件时

全局变量详见Alphabetical index of variables

修改 http server中的配置

nginx-config

启动Nginx

nginx
ps -ef | grep nginx

新加载修改后的Nginx配置文件

nginx -s reload

reloadresatrt是两个不同的概念。

  1. reload 重新加载conf文件,不中断Nginx服务,conf文件有问题则加载上一次的conf.
  2. restart 会根据配置文件,重启整个Nginx服务,造成服务器中断一段时间,当然会因为conf文件问题报错。

关闭Nginx

nginx -s stop

因为我已经启动了,所以就重启一下Nginx即可

nginx-reload

Linux安装Nginx

Linux安装

常见的Linux命令

使用pstree查看当前服务器启动的进程

pstree

pstree

查找Nginx的位置

ps -aux | grep nginx

server-where-nginx

进入nginx的目录

212-server-nginx

nginx-conf

然后配置nginx.conf文件即可

http {  
upstream add-news {
server 127.0.0.1:9527;
}

server {
listen 8080;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;
root /Users/rainy/Desktop/MyWork/Work/website/dist;
index index.html index.htm;

location / {
# root html;
try_files $uri $uri/ @router;
index index.html index.htm;
}

location @router {
rewrite ^.*$ /index.html last;
}

location ~ /api/ {
proxy_pass http://127.0.0.1:9527;
}
# 我的图片存放在本地服务器上的路径👇
location /news-images/ {
expires 24h;
root /Users/rainy/Desktop/MyWork/Work/website/server/;
autoindex on;
}
# 通过转发某服务器上的图片 -> https://localhost:9527/*/*.png
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 24h;
proxy_pass https://localhost:9527;
access_log /root/nginx/logs/images.log;
proxy_store on;
proxy_store_access user:rw group:rw all:rw;
proxy_redirect off;
proxy_set_header Host 127.0.0.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 1280k;
proxy_connect_timeout 900;
proxy_send_timeout 900;
proxy_read_timeout 900;
proxy_buffer_size 40k;
proxy_buffers 40 320k;
proxy_busy_buffers_size 640k;
proxy_temp_file_write_size 640k;
}
}
}

Docker安装Nginx

  1. 查找 Docker Hub 上的 nginx镜像

    docker search nginx

    docker-search

  2. 拉取官方镜像

    docker pull nginx
  1. 查看当前镜像

    docker images nginx

    docker-images

Server Tree

tree -C -L 3 -I '*node_modules*'

├── server
│   ├── app.js
│   ├── db
│   │   ├── db.js
│   │   └── newsSql.js
│   ├── package-lock.json
│   ├── package.json
│   └── routers
│   ├── news.js
│   └── router.js

file-tree

Node Server

npm init
npm install express mysql body-parser -S
  1. app.js

    const express = require('express')
    const bodyParser = require('body-parser')
    const app = express()
    const router = require('./routers/router')

    const PORT = 9527

    app.use(bodyParser.json())
    app.use(bodyParser.urlencoded({ extended: true }))

    app.use(router)

    app.listen(PORT, () => {
    console.log(`Listen port at ${PORT}`)
    })

  2. db.js -> Mysql配置

    module.exports = {
    mysql: {
    host: 'localhost',
    user: 'root',
    password: 'xxxx',
    database: 'test',
    port: '3306'
    }
    }
  3. router.js

    const express = require('express')
    const router = express.Router()
    const news = require('./news')

    router.get('/api/news/queryAll', (req, res, next) => {
    news.queryAll(req, res, next)
    })

    router.get('/api/news/query', (req, res, next) => {
    news.queryById(req, res, next)
    })

    router.post('/api/news/add', (req, res, next) => {
    news.add(req, res, next)
    })

    router.post('/api/news/update', (req, res, next) => {
    news.update(req, res, next)
    })

    router.delete('/api/news/deleteNews', (req, res, next) => {
    news.deleteNews(req, res, next)
    })

    module.exports = router
  4. newSql.js

    module.exports = {
    createNews: `CREATE TABLE news (
    id int(255) NOT NULL AUTO_INCREMENT,
    type varchar(255) CHARACTER SET utf8 NOT NULL,
    title varchar(255) CHARACTER SET utf8 NOT NULL,
    description varchar(255) CHARACTER SET utf8 NOT NULL,
    occur_time varchar(255) CHARACTER SET utf8 NOT NULL,
    url varchar(255) NOT NULL,
    newsImg varchar(255) NOT NULL,
    PRIMARY KEY (id)
    )`,
    queryAll: 'SELECT * FROM news',
    queryById: 'SELECT * FROM news WHERE id = ?',
    add: 'INSERT INTO news (type, title, description, occur_time, url, newsImg) VALUES (?, ?, ?, ?, ?, ?)',
    update: 'UPDATE news SET type = ?, title = ?, description = ?, occur_time = ?, url = ?, newsImg = ? WHERE id = ?',
    delete: 'DELETE FROM news WHERE id = ?'
    }
  5. news.js

    const mysql = require('mysql')
    const formidable = require('formidable')
    const fs = require('fs')
    const path = require('path')
    const db = require('../db/db')
    const $newsSql = require('../db/newsSql')

    let pool = mysql.createPool(db.mysql)

    let NEWS_IMAGES_PATH = 'news-images'

    let queryAll = (req, res, next) => {
    pool.getConnection((error, connect) => {
    if (error) {
    throw error
    } else {
    connect.query($newsSql.queryAll, (error, data) => {
    if (error) {
    throw error
    }
    res.json({
    code: '200',
    msg: 'success',
    data
    })
    connect.release()
    })
    }
    })
    }

    let queryById = (req, res, next) => {
    let id = +req.query.id
    pool.getConnection((error, connect) => {
    if (error) {
    throw error
    } else {
    connect.query($newsSql.queryById, id, (error, data) => {
    if (error) {
    throw error
    }
    res.json({
    code: '200',
    msg: 'success',
    data
    })
    connect.release()
    })
    }
    })
    }

    let add = (req, res, next) => {
    let rb = req.body
    let params = [rb.type, rb.title, rb.description, rb.occur_time, rb.url, rb.newsImg]
    pool.getConnection((error, connect) => {
    if (error) {
    throw error
    } else {
    connect.query($newsSql.add, params, (error, data) => {
    if (error) {
    throw error
    }
    res.json({
    code: '200',
    msg: 'success'
    })
    connect.release()
    })
    }
    })
    }

    let update = (req, res, next) => {
    let rb = req.body
    let params = [rb.type, rb.title, rb.description, rb.occur_time, rb.url, rb.newsImg, rb.id]
    pool.getConnection((error, connect) => {
    if (error) {
    throw error
    } else {
    connect.query($newsSql.update, [...params], (error, data) => {
    if (error) {
    throw error
    }
    res.json({
    code: '200',
    msg: 'success'
    })
    connect.release()
    })
    }
    })
    }

    let deleteNews = (req, res, next) => {
    let id = +req.query.id
    pool.getConnection((error, connect) => {
    if (error) {
    throw error
    } else {
    connect.query($newsSql.delete, id, (error, data) => {
    if (error) {
    throw error
    }
    res.json({
    code: '200',
    msg: 'success'
    })
    connect.release()
    })
    }
    })
    }

    let mkdirSync = dirname => {
    if (fs.existsSync(dirname)) {
    return true
    } else {
    if (mkdirSync(path.dirname(dirname))) {
    fs.mkdirSync(dirname)
    return true
    }
    }
    return false
    }

    let uploadImg = (req, res, next) => {
    const form = new formidable.IncomingForm()
    form.encoding = 'utf-8'
    form.keepExtensions = true
    form.parse(req, (err, fileds, files) => {
    if (err) {
    console.log(err)
    }
    let imgPath = fs.readFileSync(files.file.path)
    let filename = path.resolve(__dirname, `../${NEWS_IMAGES_PATH}/${files.file.name}`)
    if (mkdirSync(NEWS_IMAGES_PATH)) {
    fs.writeFile(filename, imgPath, (err) => {
    if (err) {
    console.log(err)
    }
    res.json({
    code: '200',
    msg: 'success',
    files: files.file
    })
    })
    }
    })
    }

    module.exports = {
    queryAll,
    queryById,
    add,
    update,
    deleteNews,
    uploadImg
    }

Vue配置代理以及使用api

  1. config/index.js 修改proxyTable

    module.exports = {
    dev: {
    proxyTable: {
    '/api': {
    target: 'http://127.0.0.1:9527',
    changeOrigin: true,
    pathRewrite: {
    '^/api': '/api'
    }
    }
    }
    }
    }
  2. 使用axios调用接口

    import axios from 'axios'
    export default {
    created () {
    this._getAllNews()
    },
    methods: {
    _getAllNews () {
    axios.get('/api/news/queryAll').then(res => {
    if (+res.data.code === SCC_CODE) {
    this.news = res.data.data
    }
    })
    }
    }
    }

Vue build打包

npm run build

vue-build

因为我的Server端是Express写的,启动Server需要使用Node,所以我们需要在服务器上安装Node.

Linux安装Node 8.x

# Using Ubuntu
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs

# Using Debian, as root
curl -sL https://deb.nodesource.com/setup_8.x | bash -
apt-get install -y nodejs

# Using Centos
curl -sL https://rpm.nodesource.com/setup_8.x | bash -
yum install -y nodejs

具体安装各版本的Node详见

启动Node

此处我之前的命令执行错误,所以我需要kill这个进程

nohup node website/server/app.js &

node-nohup

nohup:可以将程序以忽略挂起信号的方式运行起来,被运行的程序的输出信息将不会显示到终端。

无论是否将 nohup 命令的输出重定向到终端,输出都将附加到当前目录的 nohup.out文件中。如果当前目录的 nohup.out 文件不可写,输出重定向到$HOME/nohup.out文件中。如果没有文件能创建或打开以用于追加,那么 command 参数指定的命令不可调用。如果标准错误是一个终端,那么把指定的命令写给标准错误的所有输出作为标准输出重定向到相同的文件描述符。

nohup-out

到这里,我们的WebNode ServerNginx都已经配置并启动好了,我们只需要到浏览器输入你的服务器IP:8080即可.

Nginx众多概念详见官方词汇表

nginx-glossary