NodeJS yêu cầu gzip

Thí dụ

const http = require('http')
const fs   = require('fs')
const zlib = require('zlib')

http.createServer((request, response) => {
  const stream          = fs.createReadStream('index.html')
  const acceptsEncoding = request.headers['accept-encoding']

  let encoder = {
    hasEncoder     : false,
    contentEncoding: {},
    createEncoder  : () => throw 'There is no encoder'
  }

  if (!acceptsEncoding) {
    acceptsEncoding = ''
  }

  if (acceptsEncoding.match(/\bdeflate\b/)) {
    encoder = {
      hasEncoder     : true,
      contentEncoding: { 'content-encoding': 'deflate' },
      createEncoder  : zlib.createDeflate
    }
  } else if (acceptsEncoding.match(/\bgzip\b/)) {
    encoder = {
      hasEncoder     : true,
      contentEncoding: { 'content-encoding': 'gzip' },
      createEncoder  : zlib.createGzip
    }
  }

  response.writeHead(200, encoder.contentEncoding)

  if (encoder.hasEncoder) {
    stream = stream.pipe(encoder.createEncoder())
  }

  stream.pipe(response)

}).listen(1337)

Một trong những dự án gần đây của tôi liên quan đến việc cạo một số dữ liệu web để xử lý ngoại tuyến. Tôi bắt đầu sử dụng thư viện

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
1 tuyệt vời của Mikeal Rogers, thư viện này có một số cải tiến hay và tiện lợi so với thư viện Node http mặc định

Khi tôi công bố nguyên mẫu đầu tiên của mình trên web, cơ sở dữ liệu bắt đầu phát triển nhanh hơn nhiều so với kế hoạch của tôi. Tôi đã bắt đầu bằng cách lưu trữ dữ liệu phản hồi thô và không nén, do đó, tối ưu hóa ngay lập tức là sử dụng tiêu đề yêu cầu HTTP

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
2 để tìm nạp dữ liệu nén từ máy chủ

Thật không may, một số máy chủ mục tiêu của tôi đôi khi gửi lại dữ liệu không nén (điều mà họ có quyền thực hiện theo thông số HTTP, điều này hơi khó chịu). Tôi cần một cách để xử lý dữ liệu nén theo điều kiện dựa trên tiêu đề phản hồi

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
3. Tôi đã tìm thấy một giải pháp hoạt động với Nút mặc định. js HTTP, nhưng không rõ ràng ngay lập tức về cách chuyển thư viện đó sang thư viện
app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
1 của Mikeal

Cách tiếp cận 1. không có luồng

Giải pháp đầu tiên của tôi đã thu thập các khối dữ liệu vào Bộ đệm, sau đó chuyển dữ liệu đó vào các hàm zlib có liên quan nếu cần. Đó là nhiều mã hơn tôi muốn, nhưng nó hoạt động tốt

Ghi chú. để đơn giản, tôi đã bỏ qua logic ghi nội dung phản hồi đã nén vào cơ sở dữ liệu

https. //ý chính. github. com/5499763

Cách tiếp cận 2. dòng suối

Nhược điểm của phương pháp đầu tiên là tất cả dữ liệu phản hồi được lưu vào bộ nhớ đệm. Điều này tốt cho trường hợp sử dụng của tôi, nhưng nói chung, điều này có thể gây ra các vấn đề về bộ nhớ nếu bạn đang tìm kiếm các trang web có nội dung phản hồi thực sự lớn

Cách tiếp cận tốt hơn là sử dụng luồng, như Mikeal. Luồng là một sự trừu tượng tuyệt vời có thể giúp bạn quản lý mức tiêu thụ bộ nhớ tốt hơn, trong số những thứ khác. Có hai phần giới thiệu tuyệt vời về các luồng Node tại đây và tại đây. Hãy nhớ rằng các luồng trong Node. js hơi phức tạp và vẫn đang phát triển (ví dụ: Node 0. 10 luồng được giới thiệu2 không hoàn toàn tương thích ngược với các phiên bản Node cũ hơn)

Đây là một giải pháp hiệu quả giúp chuyển dữ liệu phản hồi thành luồng zlib, sau đó chuyển dữ liệu đó tới đích cuối cùng (trong trường hợp này là một tệp). Lưu ý rằng mã sạch hơn và dễ đọc hơn

https. //ý chính. github. com/5515364

Bản tóm tắt

Cả hai cách tiếp cận đó sẽ hoàn thành công việc với thư viện của Mikeal và cách bạn chọn tùy thuộc vào trường hợp sử dụng. Trong dự án của mình, tôi cần lưu dữ liệu phản hồi đã nén dưới dạng một trường của tài liệu Mongoose, sau đó xử lý thêm dữ liệu đã giải nén. Các luồng không phù hợp với trường hợp sử dụng này, vì vậy tôi đã sử dụng phương pháp đầu tiên

Bài viết này thảo luận về các phương pháp hay nhất về hiệu suất và độ tin cậy cho các ứng dụng Express được triển khai vào sản xuất

Chủ đề này rõ ràng rơi vào thế giới “devops”, bao trùm cả hoạt động và phát triển truyền thống. Theo đó, thông tin được chia thành hai phần

  • Những việc cần làm trong mã của bạn (phần dev)
  • Những việc cần làm trong môi trường/thiết lập của bạn (phần hoạt động)

Những việc cần làm trong mã của bạn

Dưới đây là một số điều bạn có thể làm trong mã của mình để cải thiện hiệu suất của ứng dụng

Sử dụng nén gzip

Nén Gzip có thể làm giảm đáng kể kích thước của nội dung phản hồi và do đó tăng tốc độ của ứng dụng web. Sử dụng phần mềm trung gian nén để nén gzip trong ứng dụng Express của bạn. Ví dụ

const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())

Đối với một trang web có lưu lượng truy cập cao đang trong quá trình sản xuất, cách tốt nhất để thực hiện nén là triển khai nó ở mức proxy ngược (xem phần ). Trong trường hợp đó, bạn không cần sử dụng phần mềm trung gian nén. Để biết chi tiết về cách bật tính năng nén gzip trong Nginx, hãy xem Mô-đun ngx_http_gzip_module trong tài liệu Nginx

Không sử dụng chức năng đồng bộ

Các hàm và phương thức đồng bộ liên kết quá trình thực thi cho đến khi chúng quay trở lại. Một lệnh gọi đến chức năng đồng bộ có thể quay lại sau vài micro giây hoặc mili giây, tuy nhiên, trong các trang web có lưu lượng truy cập cao, các lệnh gọi này cộng lại và làm giảm hiệu suất của ứng dụng. Tránh sử dụng chúng trong sản xuất

Mặc dù Nút và nhiều mô-đun cung cấp các phiên bản chức năng đồng bộ và không đồng bộ, nhưng luôn sử dụng phiên bản không đồng bộ trong sản xuất. Thời điểm duy nhất khi một chức năng đồng bộ có thể được chứng minh là khi khởi động ban đầu

Nếu bạn đang sử dụng Nút. js 4. 0+ hoặc io. js 2. 1. 0+, bạn có thể sử dụng cờ dòng lệnh

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
5 để in cảnh báo và theo dõi ngăn xếp bất cứ khi nào ứng dụng của bạn sử dụng API đồng bộ. Tất nhiên, bạn sẽ không muốn sử dụng điều này trong quá trình sản xuất mà muốn đảm bảo rằng mã của bạn đã sẵn sàng để sản xuất. Xem để biết thêm thông tin

Ghi nhật ký chính xác

Nói chung, có hai lý do để đăng nhập từ ứng dụng của bạn. Để gỡ lỗi và ghi nhật ký hoạt động của ứng dụng (về cơ bản, mọi thứ khác). Sử dụng

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
6 hoặc
app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
7 để in thông báo tường trình tới thiết bị đầu cuối là cách phổ biến trong quá trình phát triển. Nhưng khi đích đến là một thiết bị đầu cuối hoặc một tệp, thì chúng không phù hợp để sản xuất, trừ khi bạn chuyển đầu ra sang một chương trình khác

Để gỡ lỗi

Nếu bạn đăng nhập vì mục đích gỡ lỗi, thì thay vì sử dụng

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
6, hãy sử dụng mô-đun gỡ lỗi đặc biệt như gỡ lỗi. Mô-đun này cho phép bạn sử dụng biến môi trường GỠ LỖI để kiểm soát thông báo gỡ lỗi nào được gửi tới
app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
7, nếu có. Để giữ cho ứng dụng của bạn hoàn toàn không đồng bộ, bạn vẫn muốn chuyển
app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
7 sang một chương trình khác. Nhưng sau đó, bạn sẽ không thực sự gỡ lỗi trong quá trình sản xuất phải không?

Đối với hoạt động ứng dụng

Nếu bạn đang ghi nhật ký hoạt động của ứng dụng (ví dụ: theo dõi lưu lượng truy cập hoặc lệnh gọi API), thay vì sử dụng

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
6, hãy sử dụng thư viện ghi nhật ký như Winston hoặc Bunyan. Để so sánh chi tiết về hai thư viện này, hãy xem bài đăng trên blog StrongLoop So sánh Nút Winston và Nút Bunyan. nhật ký js

Xử lý ngoại lệ đúng cách

Các ứng dụng nút gặp sự cố khi chúng gặp phải một ngoại lệ chưa được phát hiện. Không xử lý các trường hợp ngoại lệ và thực hiện các hành động thích hợp sẽ khiến ứng dụng Express của bạn gặp sự cố và chuyển sang chế độ ngoại tuyến. Nếu bạn làm theo lời khuyên bên dưới thì ứng dụng của bạn sẽ phục hồi sau sự cố. May mắn thay, các ứng dụng Express thường có thời gian khởi động ngắn. Tuy nhiên, bạn muốn tránh sự cố ngay từ đầu và để làm được điều đó, bạn cần xử lý các ngoại lệ đúng cách

Để đảm bảo bạn xử lý tất cả các ngoại lệ, hãy sử dụng các kỹ thuật sau

Trước khi đi sâu vào các chủ đề này, bạn nên có hiểu biết cơ bản về xử lý lỗi Node/Express. sử dụng các cuộc gọi lại lỗi đầu tiên và truyền lỗi trong phần mềm trung gian. Nút sử dụng quy ước "gọi lại lỗi đầu tiên" để trả về lỗi từ các hàm không đồng bộ, trong đó tham số đầu tiên của hàm gọi lại là đối tượng lỗi, theo sau là dữ liệu kết quả trong các tham số tiếp theo. Để cho biết không có lỗi, hãy chuyển null làm tham số đầu tiên. Hàm gọi lại phải tuân theo quy ước gọi lại lỗi trước để xử lý lỗi một cách có ý nghĩa. Và trong Express, cách tốt nhất là sử dụng hàm next() để truyền lỗi qua chuỗi phần mềm trung gian

Để biết thêm về các nguyên tắc cơ bản của việc xử lý lỗi, hãy xem

Những gì không làm

Một điều bạn không nên làm là lắng nghe sự kiện

app.get('/', (req, res, next) => {
  // do some sync stuff
  queryDb()
    .then((data) => makeCsv(data)) // handle data
    .then((csv) => { /* handle csv */ })
    .catch(next)
})

app.use((err, req, res, next) => {
  // handle error
})
2, được phát ra khi một ngoại lệ bong bóng trong suốt quá trình quay lại vòng lặp sự kiện. Việc thêm một trình lắng nghe sự kiện cho
app.get('/', (req, res, next) => {
  // do some sync stuff
  queryDb()
    .then((data) => makeCsv(data)) // handle data
    .then((csv) => { /* handle csv */ })
    .catch(next)
})

app.use((err, req, res, next) => {
  // handle error
})
2 sẽ thay đổi hành vi mặc định của quy trình đang gặp ngoại lệ; . Điều này nghe có vẻ là một cách hay để ngăn ứng dụng của bạn gặp sự cố, nhưng việc tiếp tục chạy ứng dụng sau một ngoại lệ chưa được phát hiện là một phương pháp nguy hiểm và không được khuyến khích vì trạng thái của quy trình trở nên không đáng tin cậy và không thể đoán trước

Ngoài ra, sử dụng

app.get('/', (req, res, next) => {
  // do some sync stuff
  queryDb()
    .then((data) => makeCsv(data)) // handle data
    .then((csv) => { /* handle csv */ })
    .catch(next)
})

app.use((err, req, res, next) => {
  // handle error
})
2 được chính thức công nhận là. Vì vậy, lắng nghe
app.get('/', (req, res, next) => {
  // do some sync stuff
  queryDb()
    .then((data) => makeCsv(data)) // handle data
    .then((csv) => { /* handle csv */ })
    .catch(next)
})

app.use((err, req, res, next) => {
  // handle error
})
2 chỉ là một ý tưởng tồi. Đây là lý do tại sao chúng tôi đề xuất những thứ như nhiều quy trình và người giám sát. sự cố và khởi động lại thường là cách đáng tin cậy nhất để khôi phục lỗi

Chúng tôi cũng không khuyên bạn nên sử dụng tên miền. Nó thường không giải quyết được vấn đề và là một mô-đun không dùng nữa

Try-catch là một cấu trúc ngôn ngữ JavaScript mà bạn có thể sử dụng để bắt các ngoại lệ trong mã đồng bộ. Ví dụ, sử dụng try-catch để xử lý các lỗi phân tích cú pháp JSON như bên dưới

Sử dụng một công cụ như JSHint hoặc JSLint để giúp bạn tìm các ngoại lệ ngầm như

Dưới đây là một ví dụ về việc sử dụng tính năng bắt thử để xử lý một ngoại lệ có khả năng làm hỏng quy trình. Hàm phần mềm trung gian này chấp nhận một tham số trường truy vấn có tên là “params” là một đối tượng JSON

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})

Tuy nhiên, try-catch chỉ hoạt động đối với mã đồng bộ. Vì nền tảng Node chủ yếu là không đồng bộ (đặc biệt là trong môi trường sản xuất), nên try-catch sẽ không bắt được nhiều ngoại lệ

sử dụng lời hứa

Lời hứa sẽ xử lý bất kỳ ngoại lệ nào (cả rõ ràng và ẩn) trong các khối mã không đồng bộ sử dụng

app.get('/', (req, res, next) => {
  // do some sync stuff
  queryDb()
    .then((data) => makeCsv(data)) // handle data
    .then((csv) => { /* handle csv */ })
    .catch(next)
})

app.use((err, req, res, next) => {
  // handle error
})
6. Chỉ cần thêm
app.get('/', (req, res, next) => {
  // do some sync stuff
  queryDb()
    .then((data) => makeCsv(data)) // handle data
    .then((csv) => { /* handle csv */ })
    .catch(next)
})

app.use((err, req, res, next) => {
  // handle error
})
7 vào cuối chuỗi lời hứa. Ví dụ

app.get('/', (req, res, next) => {
  // do some sync stuff
  queryDb()
    .then((data) => makeCsv(data)) // handle data
    .then((csv) => { /* handle csv */ })
    .catch(next)
})

app.use((err, req, res, next) => {
  // handle error
})

Bây giờ tất cả các lỗi không đồng bộ và đồng bộ được truyền đến phần mềm trung gian lỗi

Tuy nhiên, có hai lưu ý

  1. Tất cả mã không đồng bộ của bạn phải trả lại lời hứa (ngoại trừ bộ phát). Nếu một thư viện cụ thể không trả lại lời hứa, hãy chuyển đổi đối tượng cơ sở bằng cách sử dụng hàm trợ giúp như Bluebird. promisifyAll()
  2. Trình phát sự kiện (như luồng) vẫn có thể gây ra các ngoại lệ chưa được phát hiện. Vì vậy, hãy chắc chắn rằng bạn đang xử lý sự kiện lỗi đúng cách;
const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
7

Hàm

app.get('/', (req, res, next) => {
  // do some sync stuff
  queryDb()
    .then((data) => makeCsv(data)) // handle data
    .then((csv) => { /* handle csv */ })
    .catch(next)
})

app.use((err, req, res, next) => {
  // handle error
})
8 là một trình bao bọc bắt các lời hứa bị từ chối và gọi
app.get('/', (req, res, next) => {
  // do some sync stuff
  queryDb()
    .then((data) => makeCsv(data)) // handle data
    .then((csv) => { /* handle csv */ })
    .catch(next)
})

app.use((err, req, res, next) => {
  // handle error
})
9 với lỗi là đối số đầu tiên. Để biết chi tiết, xem

Để biết thêm thông tin về xử lý lỗi bằng cách sử dụng lời hứa, hãy xem Lời hứa trong nút. js với Q – Giải pháp thay thế cho Callback

Những việc cần làm trong môi trường/thiết lập của bạn

Dưới đây là một số điều bạn có thể làm trong môi trường hệ thống của mình để cải thiện hiệu suất của ứng dụng

Đặt NODE_ENV thành “sản xuất”

Biến môi trường NODE_ENV chỉ định môi trường mà ứng dụng đang chạy (thường là phát triển hoặc sản xuất). Một trong những điều đơn giản nhất bạn có thể làm để cải thiện hiệu suất là đặt NODE_ENV thành “sản xuất. ”

Đặt NODE_ENV thành “sản xuất” sẽ tạo Express

  • Mẫu xem bộ đệm
  • Các tệp CSS lưu trữ được tạo từ các tiện ích mở rộng CSS
  • Tạo thông báo lỗi ít dài dòng hơn

Các thử nghiệm chỉ ra rằng chỉ cần làm điều này có thể cải thiện hiệu suất ứng dụng lên gấp ba lần

Nếu bạn cần viết mã dành riêng cho môi trường, bạn có thể kiểm tra giá trị của NODE_ENV bằng

const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
70. Xin lưu ý rằng việc kiểm tra giá trị của bất kỳ biến môi trường nào sẽ bị phạt về hiệu suất và do đó nên được thực hiện một cách tiết kiệm

Trong quá trình phát triển, bạn thường đặt các biến môi trường trong trình bao tương tác của mình, chẳng hạn bằng cách sử dụng tệp

const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
71 hoặc tệp
const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
72 của bạn. Nhưng nói chung, bạn không nên làm điều đó trên máy chủ sản xuất; . Phần tiếp theo cung cấp thêm chi tiết về cách sử dụng hệ thống init của bạn nói chung, nhưng cài đặt NODE_ENV rất quan trọng đối với hiệu suất (và dễ thực hiện), nên nó được đánh dấu ở đây

Với Upstart, hãy sử dụng từ khóa

const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
73 trong hồ sơ công việc của bạn. Ví dụ

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
4

Để biết thêm thông tin, hãy xem

Với systemd, hãy sử dụng lệnh

const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
74 trong tệp đơn vị của bạn. Ví dụ

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
6

Để biết thêm thông tin, hãy xem Sử dụng biến môi trường trong đơn vị systemd

Đảm bảo ứng dụng của bạn tự động khởi động lại

Trong quá trình sản xuất, bạn không bao giờ muốn ứng dụng của mình ngoại tuyến. Điều này có nghĩa là bạn cần đảm bảo rằng nó sẽ khởi động lại cả nếu ứng dụng gặp sự cố và nếu chính máy chủ gặp sự cố. Mặc dù bạn hy vọng rằng cả hai sự kiện đó đều không xảy ra, nhưng trên thực tế, bạn phải tính đến cả hai tình huống có thể xảy ra bằng cách

  • Sử dụng trình quản lý quy trình để khởi động lại ứng dụng (và Nút) khi ứng dụng gặp sự cố
  • Sử dụng hệ thống init do HĐH của bạn cung cấp để khởi động lại trình quản lý quy trình khi HĐH gặp sự cố. Cũng có thể sử dụng hệ thống init mà không cần trình quản lý quy trình

Các ứng dụng nút gặp sự cố nếu chúng gặp phải một ngoại lệ chưa được phát hiện. Điều quan trọng nhất bạn cần làm là đảm bảo ứng dụng của bạn được kiểm tra tốt và xử lý tất cả các trường hợp ngoại lệ (xem để biết chi tiết). Nhưng để đảm bảo an toàn, hãy đặt một cơ chế để đảm bảo rằng nếu và khi ứng dụng của bạn gặp sự cố, ứng dụng sẽ tự động khởi động lại

Sử dụng trình quản lý quy trình

Trong quá trình phát triển, bạn đã bắt đầu ứng dụng của mình đơn giản từ dòng lệnh với

const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
75 hoặc thứ gì đó tương tự. Nhưng làm điều này trong sản xuất là một công thức cho thảm họa. Nếu ứng dụng gặp sự cố, ứng dụng sẽ ngoại tuyến cho đến khi bạn khởi động lại. Để đảm bảo ứng dụng của bạn khởi động lại nếu gặp sự cố, hãy sử dụng trình quản lý quy trình. Trình quản lý quy trình là một "bộ chứa" cho các ứng dụng hỗ trợ triển khai, cung cấp tính khả dụng cao và cho phép bạn quản lý ứng dụng trong thời gian chạy

Ngoài việc khởi động lại ứng dụng của bạn khi ứng dụng gặp sự cố, trình quản lý quy trình có thể cho phép bạn

  • Có được thông tin chuyên sâu về hiệu suất thời gian chạy và mức tiêu thụ tài nguyên
  • Tự động sửa đổi cài đặt để cải thiện hiệu suất
  • Kiểm soát phân cụm (StrongLoop PM và pm2)

Các trình quản lý quy trình phổ biến nhất cho Node như sau

Để so sánh từng tính năng của ba trình quản lý quy trình, hãy xem http. //mạnh-chiều. io/so sánh/. Để có phần giới thiệu chi tiết hơn về cả ba, hãy xem Trình quản lý quy trình cho ứng dụng Express

Sử dụng bất kỳ trình quản lý quy trình nào trong số này sẽ đủ để duy trì ứng dụng của bạn, ngay cả khi nó thỉnh thoảng bị lỗi

Tuy nhiên, StrongLoop PM có rất nhiều tính năng dành riêng cho việc triển khai sản xuất. Bạn có thể sử dụng nó và các công cụ StrongLoop liên quan để

  • Xây dựng và đóng gói ứng dụng của bạn cục bộ, sau đó triển khai ứng dụng đó một cách an toàn vào hệ thống sản xuất của bạn
  • Tự động khởi động lại ứng dụng của bạn nếu ứng dụng gặp sự cố vì bất kỳ lý do gì
  • Quản lý cụm của bạn từ xa
  • Xem cấu hình CPU và ảnh chụp nhanh heap để tối ưu hóa hiệu suất và chẩn đoán rò rỉ bộ nhớ
  • Xem số liệu hiệu suất cho ứng dụng của bạn
  • Dễ dàng mở rộng quy mô cho nhiều máy chủ với điều khiển tích hợp cho bộ cân bằng tải Nginx

Như được giải thích bên dưới, khi bạn cài đặt StrongLoop PM làm dịch vụ hệ điều hành bằng hệ thống init của mình, nó sẽ tự động khởi động lại khi hệ thống khởi động lại. Do đó, nó sẽ giữ cho các quy trình và cụm ứng dụng của bạn tồn tại mãi mãi

Sử dụng một hệ thống init

Lớp độ tin cậy tiếp theo là đảm bảo rằng ứng dụng của bạn khởi động lại khi máy chủ khởi động lại. Hệ thống vẫn có thể ngừng hoạt động vì nhiều lý do. Để đảm bảo rằng ứng dụng của bạn khởi động lại nếu máy chủ gặp sự cố, hãy sử dụng hệ thống init được tích hợp trong hệ điều hành của bạn. Hai hệ thống init chính được sử dụng ngày nay là systemd và Upstart

Có hai cách để sử dụng hệ thống init với ứng dụng Express của bạn

  • Chạy ứng dụng của bạn trong trình quản lý quy trình và cài đặt trình quản lý quy trình dưới dạng dịch vụ với hệ thống init. Trình quản lý quy trình sẽ khởi động lại ứng dụng của bạn khi ứng dụng gặp sự cố và hệ thống init sẽ khởi động lại trình quản lý quy trình khi hệ điều hành khởi động lại. Đây là cách tiếp cận được khuyến nghị
  • Chạy ứng dụng của bạn (và Nút) trực tiếp với hệ thống init. Điều này đơn giản hơn một chút, nhưng bạn không nhận được những lợi ích bổ sung khi sử dụng trình quản lý quy trình
hệ thống

Systemd là một trình quản lý dịch vụ và hệ thống Linux. Hầu hết các bản phân phối Linux chính đã sử dụng systemd làm hệ thống init mặc định của chúng

Tệp cấu hình dịch vụ systemd được gọi là tệp đơn vị, với tên tệp kết thúc bằng

const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
76. Đây là một tệp đơn vị ví dụ để quản lý trực tiếp ứng dụng Node. Thay thế các giá trị kèm theo trong
const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
77 cho hệ thống và ứng dụng của bạn

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
0

Để biết thêm thông tin về systemd, hãy xem tài liệu tham khảo systemd (trang hướng dẫn)

StrongLoop PM dưới dạng dịch vụ systemd

Bạn có thể dễ dàng cài đặt StrongLoop Process Manager dưới dạng dịch vụ systemd. Sau khi bạn thực hiện, khi máy chủ khởi động lại, nó sẽ tự động khởi động lại StrongLoop PM, sau đó sẽ khởi động lại tất cả các ứng dụng mà nó đang quản lý

Để cài đặt StrongLoop PM làm dịch vụ systemd

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
1

Sau đó bắt đầu dịch vụ với

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
2

Để biết thêm thông tin, xem

mới bắt đầu

Upstart là một công cụ hệ thống có sẵn trên nhiều bản phân phối Linux để bắt đầu các tác vụ và dịch vụ trong khi khởi động hệ thống, dừng chúng trong khi tắt máy và giám sát chúng. Bạn có thể định cấu hình ứng dụng Express hoặc trình quản lý quy trình của mình dưới dạng dịch vụ và sau đó Upstart sẽ tự động khởi động lại khi gặp sự cố

Dịch vụ mới bắt đầu được xác định trong tệp cấu hình công việc (còn được gọi là “công việc”) với tên tệp kết thúc bằng

const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
78. Ví dụ sau đây cho thấy cách tạo một công việc có tên là “myapp” cho một ứng dụng có tên là “myapp” với tệp chính nằm ở
const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
79

Tạo một tệp có tên

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
40 tại
app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
41 với nội dung sau (thay thế văn bản in đậm bằng các giá trị cho hệ thống và ứng dụng của bạn)

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
7

GHI CHÚ. Tập lệnh này yêu cầu Upstart 1. 4 trở lên, được hỗ trợ trên Ubuntu 12. 04-14. 10

Vì công việc được định cấu hình để chạy khi hệ thống khởi động, nên ứng dụng của bạn sẽ được khởi động cùng với hệ điều hành và tự động khởi động lại nếu ứng dụng gặp sự cố hoặc hệ thống gặp sự cố

Ngoài việc tự động khởi động lại ứng dụng, Upstart cho phép bạn sử dụng các lệnh này

  • app.get('/search', (req, res) => {
      // Simulating async operation
      setImmediate(() => {
        const jsonStr = req.query.params
        try {
          const jsonObj = JSON.parse(jsonStr)
          res.send('Success')
        } catch (e) {
          res.status(400).send('Invalid JSON string')
        }
      })
    })
    
    42 – Bắt đầu ứng dụng
  • app.get('/search', (req, res) => {
      // Simulating async operation
      setImmediate(() => {
        const jsonStr = req.query.params
        try {
          const jsonObj = JSON.parse(jsonStr)
          res.send('Success')
        } catch (e) {
          res.status(400).send('Invalid JSON string')
        }
      })
    })
    
    43 – Khởi động lại ứng dụng
  • app.get('/search', (req, res) => {
      // Simulating async operation
      setImmediate(() => {
        const jsonStr = req.query.params
        try {
          const jsonObj = JSON.parse(jsonStr)
          res.send('Success')
        } catch (e) {
          res.status(400).send('Invalid JSON string')
        }
      })
    })
    
    44 – Dừng ứng dụng

Để biết thêm thông tin về Upstart, hãy xem Upstart Intro, Cookbook và Best Practices

StrongLoop PM như một dịch vụ mới bắt đầu

Bạn có thể dễ dàng cài đặt StrongLoop Process Manager như một dịch vụ mới bắt đầu. Sau khi bạn thực hiện, khi máy chủ khởi động lại, nó sẽ tự động khởi động lại StrongLoop PM, sau đó sẽ khởi động lại tất cả các ứng dụng mà nó đang quản lý

Để cài đặt StrongLoop PM làm Upstart 1. 4 dịch vụ

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
0

Sau đó chạy dịch vụ với

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
1

GHI CHÚ. Trên các hệ thống không hỗ trợ Upstart 1. 4, các lệnh hơi khác một chút. Xem để biết thêm thông tin

Chạy ứng dụng của bạn trong một cụm

Trong một hệ thống đa lõi, bạn có thể tăng hiệu suất của ứng dụng Node lên nhiều lần bằng cách khởi chạy một cụm quy trình. Một cụm chạy nhiều phiên bản của ứng dụng, lý tưởng nhất là một phiên bản trên mỗi lõi CPU, do đó phân phối tải và tác vụ giữa các phiên bản

NodeJS yêu cầu gzip

QUAN TRỌNG. Vì các phiên bản ứng dụng chạy dưới dạng các quy trình riêng biệt nên chúng không chia sẻ cùng một không gian bộ nhớ. Nghĩa là, các đối tượng là cục bộ cho từng phiên bản của ứng dụng. Do đó, bạn không thể duy trì trạng thái trong mã ứng dụng. Tuy nhiên, bạn có thể sử dụng kho dữ liệu trong bộ nhớ như Redis để lưu trữ dữ liệu và trạng thái liên quan đến phiên. Thông báo trước này về cơ bản áp dụng cho tất cả các hình thức chia tỷ lệ theo chiều ngang, cho dù là phân cụm với nhiều quy trình hay nhiều máy chủ vật lý

Trong các ứng dụng được nhóm, các quy trình công nhân có thể gặp sự cố riêng lẻ mà không ảnh hưởng đến phần còn lại của quy trình. Ngoài lợi thế về hiệu suất, cách ly lỗi là một lý do khác để chạy một cụm quy trình ứng dụng. Bất cứ khi nào một quy trình công nhân gặp sự cố, hãy luôn đảm bảo ghi nhật ký sự kiện và tạo ra một quy trình mới bằng cách sử dụng cụm. cái nĩa()

Sử dụng mô-đun cụm của Node

Phân cụm được thực hiện với mô-đun cụm của Node. Điều này cho phép một quy trình chính sinh ra các quy trình công nhân và phân phối các kết nối đến giữa các công nhân. Tuy nhiên, thay vì sử dụng mô-đun này trực tiếp, tốt hơn hết là sử dụng một trong nhiều công cụ hiện có tự động thực hiện điều đó cho bạn;

Sử dụng StrongLoop PM

Nếu bạn triển khai ứng dụng của mình lên StrongLoop Process Manager (PM), thì bạn có thể tận dụng khả năng phân cụm mà không cần sửa đổi mã ứng dụng của mình

Khi StrongLoop Process Manager (PM) chạy một ứng dụng, nó sẽ tự động chạy ứng dụng đó trong một cụm có số worker bằng với số lõi CPU trên hệ thống. Bạn có thể thay đổi thủ công số lượng worker process trong cụm bằng công cụ dòng lệnh slc mà không cần dừng ứng dụng

Ví dụ: giả sử bạn đã triển khai ứng dụng của mình để sản xuất. foo. com và StrongLoop PM đang lắng nghe trên cổng 8701 (mặc định), sau đó đặt kích thước cụm thành tám bằng cách sử dụng slc

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
2

Để biết thêm thông tin về phân cụm với StrongLoop PM, hãy xem Phân cụm trong tài liệu StrongLoop

Sử dụng PM2

Nếu bạn triển khai ứng dụng của mình với PM2, thì bạn có thể tận dụng khả năng phân cụm mà không cần sửa đổi mã ứng dụng của mình. Bạn nên đảm bảo rằng bạn là người đầu tiên, nghĩa là không có dữ liệu cục bộ nào được lưu trữ trong quy trình (chẳng hạn như phiên, kết nối websocket và những thứ tương tự)

Khi chạy ứng dụng với PM2, bạn có thể kích hoạt chế độ cụm để chạy ứng dụng đó theo cụm với số phiên bản do bạn chọn, chẳng hạn như khớp với số lượng CPU hiện có trên máy. Bạn có thể thay đổi số lượng quy trình trong cụm theo cách thủ công bằng công cụ dòng lệnh

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
45 mà không cần dừng ứng dụng

Để bật chế độ cụm, hãy khởi động ứng dụng của bạn như vậy

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
3

Điều này cũng có thể được định cấu hình trong tệp quy trình PM2 (

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
46 hoặc tương tự) bằng cách đặt
app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
47 thành
app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
48 và
app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
49 thành số lượng công nhân bắt đầu

Sau khi chạy, ứng dụng có thể được thu nhỏ như vậy

app.get('/search', (req, res) => {
  // Simulating async operation
  setImmediate(() => {
    const jsonStr = req.query.params
    try {
      const jsonObj = JSON.parse(jsonStr)
      res.send('Success')
    } catch (e) {
      res.status(400).send('Invalid JSON string')
    }
  })
})
4

Để biết thêm thông tin về phân cụm với PM2, hãy xem Chế độ cụm trong tài liệu PM2

Kết quả yêu cầu bộ đệm

Một chiến lược khác để cải thiện hiệu suất trong sản xuất là lưu trữ kết quả của các yêu cầu để ứng dụng của bạn không lặp lại thao tác phục vụ cùng một yêu cầu nhiều lần

Sử dụng máy chủ bộ nhớ đệm như Varnish hoặc Nginx (xem thêm Bộ nhớ đệm Nginx) để cải thiện đáng kể tốc độ và hiệu suất của ứng dụng của bạn

Sử dụng bộ cân bằng tải

Bất kể ứng dụng được tối ưu hóa như thế nào, một phiên bản duy nhất chỉ có thể xử lý một lượng tải và lưu lượng truy cập hạn chế. Một cách để mở rộng quy mô ứng dụng là chạy nhiều phiên bản của ứng dụng đó và phân phối lưu lượng thông qua bộ cân bằng tải. Việc thiết lập bộ cân bằng tải có thể cải thiện hiệu suất và tốc độ của ứng dụng, đồng thời cho phép ứng dụng mở rộng quy mô nhiều hơn mức có thể với một phiên bản duy nhất

Bộ cân bằng tải thường là một proxy ngược điều phối lưu lượng đến và đi từ nhiều phiên bản ứng dụng và máy chủ. Bạn có thể dễ dàng thiết lập bộ cân bằng tải cho ứng dụng của mình bằng cách sử dụng Nginx hoặc HAProxy

Với cân bằng tải, bạn có thể phải đảm bảo rằng các yêu cầu được liên kết với một ID phiên cụ thể sẽ kết nối với quy trình tạo ra chúng. Điều này được gọi là mối quan hệ phiên hoặc phiên cố định và có thể được giải quyết bằng đề xuất ở trên để sử dụng kho lưu trữ dữ liệu như Redis cho dữ liệu phiên (tùy thuộc vào ứng dụng của bạn). Để thảo luận, hãy xem Sử dụng nhiều nút

Sử dụng proxy ngược

Một proxy ngược nằm trước một ứng dụng web và thực hiện các hoạt động hỗ trợ đối với các yêu cầu, ngoài việc hướng các yêu cầu đến ứng dụng. Nó có thể xử lý các trang lỗi, nén, lưu vào bộ đệm, cung cấp tệp và cân bằng tải trong số những thứ khác

Bàn giao các tác vụ không yêu cầu kiến ​​thức về trạng thái ứng dụng cho proxy ngược giải phóng Express để thực hiện các tác vụ ứng dụng chuyên biệt. Vì lý do này, nên chạy Express đằng sau proxy ngược như Nginx hoặc HAProxy trong sản xuất