Fastify 是一个高效的 Node.js Web 框架,它是基于 Express 的 API 设计的,但相比于 Express,Fastify 的性能更优秀。Fastify 在设计之初就考虑到了性能的问题,采用了一些优化策略,但也可以通过一些优化手段进一步提升性能。
本文将介绍一些优化 Fastify 项目性能的方法,并通过示例代码进行演示。
1. 启用 HTTP2
HTTP2 是一种现代协议,让浏览器在同一个 TCP 连接上并行发送多个请求。启用 HTTP2 可以减少连接建立的时间,提高连接的重用率,从而减少网络延迟和带宽占用。需要注意的是,启用 HTTP2 需要 HTTPS 支持。
启用方式如下:
const fastify = require('fastify')({
http2: true,
https: {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
}
})2. 优化路由
路由是 Web 应用程序的核心,优化路由可以大大提升应用程序的性能。Fastify 提供了一个非常简单的方式来定义路由,如下:
fastify.get('/user/:id', async (request, reply) => {
const userId = request.params.id
const user = await getUserById(userId)
return user
})常见的路由优化方法有以下几种:
2.1. 利用参数限制
在路由中,如果参数只能取值为特定的值,则可以使用正则表达式或 ENUM 类型进行限制。
// www.javascriptcn.com code example
// 使用正则表达式限制参数值
fastify.get('/user/:id([0-9]+)', async (request, reply) => {
const userId = request.params.id
const user = await getUserById(userId)
return user
})
// 使用 ENUM 类型限制参数值
const validSearchFields = ['name', 'email', 'phone']
fastify.get('/search', {
schema: {
querystring: {
type: 'object',
additionalProperties: false,
properties: {
field: {
type: 'string',
enum: validSearchFields
},
query: {
type: 'string'
}
},
required: ['field', 'query']
}
}
}, async (request, reply) => {
const { field, query } = request.query
const result = await search(field, query)
return result
})2.2. 减少路由数量
路由数量对应用程序的性能有很大影响,过多的路由会增加 Fastify 应用程序的负担。可以通过以下方法减少路由数量:
- 合并相似的路由
- 使用通配符路由
// www.javascriptcn.com code example
// 合并相似的路由
fastify.get('/user/:id', async (request, reply) => {
const userId = request.params.id
const user = await getUserById(userId)
return user
})
fastify.put('/user/:id', async (request, reply) => {
const userId = request.params.id
const user = request.body
await updateUserById(userId, user)
return { success: true }
})
// 可以合并为一个路由
fastify.route({
method: ['GET', 'PUT'],
url: '/user/:id',
handler: async (request, reply) => {
const userId = request.params.id
if(request.method === 'GET') {
const user = await getUserById(userId)
return user
} else {
const user = request.body
await updateUserById(userId, user)
return { success: true }
}
}
})
// 使用通配符路由
fastify.get('/user*', async (request, reply) => {
const userId = request.params['*']
const user = await getUserById(userId)
return user
})2.3. 缓存路由
使用路由缓存可以减少路由的编译和查找的时间,提高性能。
// www.javascriptcn.com code example
const routerConfig = {
'/api/user/:id': [
{ method: 'GET', handler: async (request, reply) => {
const userId = request.params.id
const user = await getUserById(userId)
return user
}}
]
}
const router = new Router(routerConfig)
fastify.register(router.lookup)3. 优化数据库查询
数据库查询是 Web 应用程序性能影响最大的部分之一,优化数据库查询可以大幅提高应用程序性能。
常见的数据库查询优化方法有以下几种:
- 使用索引
- 压缩查询结果
- 分页查询
// www.javascriptcn.com code example
// 创建索引
db.collection('orders').createIndex({ status: 1, createAt: -1 })
// 在代码中使用索引
async function findOrdersWithStatusAndNotExpired(db, status) {
const orders = await db
.collection('orders')
.find({
status,
createAt: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
}, { projection: { _id: 0 }})
.hint({ status: 1, createAt: -1 })
.toArray()
return orders
}
// 压缩查询结果
fastify.get('/orders', async (request, reply) => {
const orders = await db
.collection('orders')
.find({}, { projection: { _id: 0 }})
.hint({ status: 1, createAt: -1 })
.toArray()
const compressedOrders = lz4.encode(JSON.stringify(orders))
reply.header('Content-Encoding', 'lz4')
return compressedOrders
})
// 分页查询
fastify.get('/orders', async (request, reply) => {
const page = parseInt(request.query.page, 10) || 1
const pageSize = parseInt(request.query.pageSize, 10) || 20
const skip = (page - 1) * pageSize
const [count, orders] = await Promise.all([
db.collection('orders').countDocuments(),
db.collection('orders')
.find({}, { projection: { _id: 0 }})
.hint({ status: 1, createAt: -1 })
.skip(skip)
.limit(pageSize)
.toArray()
])
const totalPages = Math.ceil(count / pageSize)
return {
page,
pageSize,
totalPages,
orders
}
})4. 使用缓存
缓存是一种重要的性能优化策略,可以将结果缓存在内存中,从而减少对数据库的访问。常见的缓存策略有以下几种:
- 前端缓存
- 数据库缓存
- 存储缓存
4.1. 前端缓存
前端缓存是指在浏览器端缓存 Web 页面或资源,以减少对服务器的访问。可以通过设置响应头的 Cache-Control 和 Expires 字段来实现:
fastify.get('/static.js', async (request, reply) => {
const contents = await readFile('/path/to/static.js')
reply
.header('Cache-Control', 'max-age=3600')
.header('Expires', new Date(Date.now() + 3600 * 1000).toUTCString())
.send(contents)
})4.2. 数据库缓存
数据库缓存是指使用缓存存储系统(如 Redis, Memcached 等)来缓存数据库查询结果。
// www.javascriptcn.com code example
const redis = require('redis')
const cacheClient = redis.createClient()
async function cachableFindOrdersWithStatusAndNotExpired(db, status) {
const key = `orders-${status}`
const cached = await promisify(cacheClient.get).call(cacheClient, key)
if (cached) {
return JSON.parse(cached)
}
const result = await findOrdersWithStatusAndNotExpired(db, status)
cacheClient.set(key, JSON.stringify(result), 'EX', 3600)
return result
}4.3. 存储缓存
存储缓存是指将结果存储在本地存储(如内存、硬盘等)中,从而减少对数据库和缓存存储系统的访问。
// www.javascriptcn.com code example
const cache = {}
fastify.get('/users', async (request, reply) => {
const cached = cache['users']
if (cached) {
return cached
}
const result = await db.collection('users').find({}).toArray()
cache['users'] = result
setTimeout(() => delete cache['users'], 3600 * 1000)
return result
})5. 使用流
流是一种高效的数据处理方式,它可以将数据分成小块进行处理,从而减少内存的占用和 CPU 的负载。因此,在处理大量数据的时候,使用流能够大幅提高应用程序的性能。
const fs = require('fs')
fastify.get('/stream', async (request, reply) => {
const stream = fs.createReadStream('/path/to/large/file')
reply.type('video/mp4')
reply.send(stream)
})经过优化后的 Fastify 项目的性能将会更加优秀,用户体验更佳。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67cfcb13e46428fe9ebc99d0