乐闻世界logo
搜索文章和话题

面试题手册

GraphQL 安全有哪些最佳实践

GraphQL 安全最佳实践GraphQL 的灵活性虽然强大,但也带来了独特的安全挑战。以下是保护 GraphQL API 的关键安全措施。1. 认证与授权认证机制const { ApolloServer } = require('apollo-server');const jwt = require('jsonwebtoken');const server = new ApolloServer({ typeDefs, resolvers, context: ({ req }) => { // 从请求头获取 token const token = req.headers.authorization || ''; try { // 验证 token const decoded = jwt.verify(token, process.env.JWT_SECRET); return { user: decoded }; } catch (error) { return { user: null }; } }});授权中间件const { AuthenticationError, ForbiddenError } = require('apollo-server-express');const resolvers = { Query: { me: (parent, args, context) => { if (!context.user) { throw new AuthenticationError('未认证'); } return context.user; }, users: (parent, args, context) => { if (!context.user || context.user.role !== 'ADMIN') { throw new ForbiddenError('需要管理员权限'); } return User.findAll(); } }};2. 查询深度限制防止深度嵌套攻击const depthLimit = require('graphql-depth-limit');const server = new ApolloServer({ typeDefs, resolvers, validationRules: [ depthLimit(7, { ignore: ['ignoredField'] // 忽略特定字段 }) ]});为什么需要:防止恶意客户端发送深度嵌套查询避免服务器资源耗尽防止 DoS 攻击3. 查询复杂度限制限制查询复杂度const { createComplexityLimitRule } = require('graphql-validation-complexity');const complexityLimitRule = createComplexityLimitRule(1000, { onCost: (cost) => console.log(`查询复杂度: ${cost}`), createError: (max, actual) => { return new Error(`查询复杂度 ${actual} 超过最大限制 ${max}`); }});const server = new ApolloServer({ typeDefs, resolvers, validationRules: [complexityLimitRule]});自定义复杂度计算const typeDefs = ` type Query { user(id: ID!): User @cost(complexity: 1) users(limit: Int!): [User!]! @cost(complexity: 5, multipliers: ["limit"]) posts(first: Int!): [Post!]! @cost(complexity: 10, multipliers: ["first"]) }`;4. 速率限制使用 express-rate-limitconst rateLimit = require('express-rate-limit');const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 分钟 max: 100, // 限制每个 IP 100 个请求 message: '请求过于频繁,请稍后再试'});app.use('/graphql', limiter);GraphQL 特定的速率限制const { GraphQLComplexityLimit } = require('graphql-complexity-limit');const server = new ApolloServer({ typeDefs, resolvers, validationRules: [ new GraphQLComplexityLimit({ maxComplexity: 1000, onExceed: (complexity) => { throw new Error(`查询复杂度 ${complexity} 超过限制`); } }) ]});5. 输入验证使用 Yup 验证const yup = require('yup');const createUserSchema = yup.object().shape({ name: yup.string().required().min(2).max(100), email: yup.string().email().required(), age: yup.number().min(0).max(150).optional()});const resolvers = { Mutation: { createUser: async (_, { input }) => { // 验证输入 await createUserSchema.validate(input); // 创建用户 return User.create(input); } }};GraphQL Schema 验证input CreateUserInput { name: String! @constraint(minLength: 2, maxLength: 100) email: String! @constraint(format: "email") age: Int @constraint(min: 0, max: 150)}6. 字段级权限控制使用指令directive @auth(requires: Role) on FIELD_DEFINITIONenum Role { USER ADMIN}type User { id: ID! name: String! email: String! @auth(requires: ADMIN) salary: Float @auth(requires: ADMIN)}const resolvers = { User: { email: (user, args, context) => { if (context.user?.role !== 'ADMIN') { throw new ForbiddenError('无权访问该字段'); } return user.email; }, salary: (user, args, context) => { if (context.user?.role !== 'ADMIN') { throw new ForbiddenError('无权访问该字段'); } return user.salary; } }};7. 防止查询注入参数化查询// 不好的做法 - 字符串拼接const query = `SELECT * FROM users WHERE id = '${userId}'`;// 好的做法 - 参数化查询const query = 'SELECT * FROM users WHERE id = ?';const result = await db.query(query, [userId]);使用 ORM// 使用 Sequelize ORMconst user = await User.findOne({ where: { id: userId }});8. CORS 配置const server = new ApolloServer({ typeDefs, resolvers, cors: { origin: ['https://yourdomain.com'], // 只允许特定域名 credentials: true, methods: ['POST'] }});9. 错误处理不暴露敏感信息const server = new ApolloServer({ typeDefs, resolvers, formatError: (error) => { // 生产环境不暴露详细错误信息 if (process.env.NODE_ENV === 'production') { return new Error('服务器内部错误'); } // 开发环境返回完整错误信息 return error; }});自定义错误类型type Error { code: String! message: String! field: String}type UserResult { user: User errors: [Error!]!}type Mutation { createUser(input: CreateUserInput!): UserResult!}10. 日志与监控记录查询日志const server = new ApolloServer({ typeDefs, resolvers, plugins: [ { requestDidStart: () => ({ didResolveOperation: (context) => { console.log('查询:', context.request.operationName); console.log('变量:', JSON.stringify(context.request.variables)); } }) } ]});监控异常查询const server = new ApolloServer({ typeDefs, resolvers, plugins: [ { requestDidStart: () => ({ didEncounterErrors: (context) => { context.errors.forEach(error => { console.error('查询错误:', error.message); // 发送到监控系统 monitoringService.logError(error); }); } }) } ]});11. 查询白名单使用 persisted queriesconst { createPersistedQueryLink } = require('@apollo/client/link/persisted-queries');const { sha256 } = require('crypto-hash');const link = createPersistedQueryLink({ sha256, useGETForHashedQueries: true});// 只允许预定义的查询const allowedQueries = new Set([ 'hash1', 'hash2'});const server = new ApolloServer({ typeDefs, resolvers, persistedQueries: { cache: new Map(), ttl: 3600 }});12. 安全检查清单[ ] 实施认证机制(JWT、OAuth)[ ] 实施授权机制(基于角色的访问控制)[ ] 限制查询深度[ ] 限制查询复杂度[ ] 实施速率限制[ ] 验证所有输入[ ] 实施字段级权限控制[ ] 防止查询注入[ ] 配置 CORS[ ] 正确处理错误[ ] 记录查询日志[ ] 监控异常查询[ ] 使用查询白名单[ ] 定期安全审计[ ] 保持依赖更新13. 常见安全威胁及防护| 威胁 | 描述 | 防护措施 ||------|------|----------|| 深度嵌套攻击 | 恶意客户端发送深度嵌套查询 | 限制查询深度 || 复杂度攻击 | 发送高复杂度查询消耗资源 | 限制查询复杂度 || DoS 攻击 | 大量请求导致服务不可用 | 速率限制、查询白名单 || 注入攻击 | 恶意输入导致 SQL 注入 | 参数化查询、输入验证 || 未授权访问 | 访问未授权的数据 | 认证、授权、字段级权限控制 || 信息泄露 | 错误信息暴露敏感数据 | 正确的错误处理 |
阅读 0·2月21日 17:01

GraphQL 与 REST API 的核心区别是什么

GraphQL 与 REST API 的核心区别GraphQL 是一种用于 API 的查询语言和运行时环境,它与 REST API 有以下核心区别:1. 数据获取方式REST: 客户端需要访问多个端点来获取相关数据,可能导致过度获取或获取不足GraphQL: 客户端可以在单个请求中精确指定所需的数据字段,避免过度获取和获取不足2. 端点设计REST: 基于资源的端点设计,每个资源有独立的 URLGraphQL: 单一端点,所有请求都发送到同一个 URL3. 版本控制REST: 通常需要版本控制(如 /api/v1/users)GraphQL: 通过 Schema 演进,无需版本控制,可以废弃字段而不破坏现有客户端4. 请求方法REST: 使用标准 HTTP 方法(GET、POST、PUT、DELETE)GraphQL: 使用 POST 方法发送查询、变更和订阅5. 响应格式REST: 响应格式由服务器决定GraphQL: 响应格式由客户端查询决定6. 缓存策略REST: 可以利用 HTTP 缓存机制GraphQL: 需要实现自定义缓存策略(如 Apollo Client 缓存)7. 实时数据REST: 通常需要轮询或使用 WebSocketGraphQL: 原生支持订阅(Subscriptions)实现实时数据更新8. 错误处理REST: 使用 HTTP 状态码表示错误GraphQL: 在响应体中包含 errors 字段,HTTP 状态码通常为 2009. 类型系统REST: 通常使用 OpenAPI/Swagger 进行文档化,但不是强类型GraphQL: 内置强类型系统,Schema 定义了所有可用的类型和操作10. 文档REST: 需要手动维护 API 文档GraphQL: Schema 本身就是文档,可以通过工具自动生成适用场景GraphQL 适合:需要从多个数据源获取复杂嵌套数据的应用移动应用,需要减少网络请求和数据传输需要灵活数据需求的多个客户端快速迭代的产品,需要频繁调整 APIREST 适合:简单的 CRUD 操作需要利用 HTTP 缓存的场景公共 API,需要简单易用团队对 GraphQL 不熟悉的场景
阅读 0·2月21日 17:01

CDN 的缓存策略有哪些?如何优化 CDN 缓存命中率?

CDN 缓存策略的核心概念CDN 缓存策略决定了内容在边缘节点的存储时间和更新方式,直接影响用户体验和源站负载。合理的缓存策略可以最大化 CDN 的性能优势。主要缓存策略1. 基于 TTL(Time To Live)的缓存TTL 是最基本的缓存控制机制,指定内容在 CDN 节点的缓存时间:短 TTL(秒级):适用于频繁更新的内容,如新闻、股票数据中等 TTL(分钟级):适用于偶尔更新的内容,如商品信息、用户资料长 TTL(小时/天级):适用于很少变化的内容,如静态资源、图片、视频设置方式:Cache-Control: max-age=3600 // 缓存 1 小时Cache-Control: max-age=86400 // 缓存 1 天2. 缓存键(Cache Key)配置缓存键决定哪些请求被视为相同内容:默认缓存键:基于完整 URL自定义缓存键:可以包含或排除特定的请求头、查询参数忽略查询参数:对于不影响内容的参数(如 ?timestamp=xxx)可以忽略示例:完整 URL:https://example.com/image.jpg?width=800&quality=90忽略 quality:https://example.com/image.jpg?width=8003. 分级缓存策略CDN 通常采用多级缓存架构:边缘节点缓存:最接近用户,容量较小,响应最快区域节点缓存:覆盖特定区域,容量中等源站缓存:容量最大,作为最后保障缓存查找顺序:边缘节点 → 区域节点 → 源站4. 缓存预热(Cache Warming)在内容正式发布前,提前将内容推送到 CDN 节点:主动预热:通过 API 或管理控制台主动推送被动预热:通过模拟用户请求触发缓存适用场景:重大活动、新版本发布、热门内容5. 缓存刷新(Cache Purging)主动清除 CDN 节点的缓存内容:URL 刷新:清除特定 URL 的缓存目录刷新:清除整个目录下的缓存全站刷新:清除所有缓存(谨慎使用)刷新方式:立即刷新:立即清除缓存软刷新:等待 TTL 过期后不再续期高级缓存策略1. 动态内容缓存即使是动态内容也可以通过以下方式缓存:协商缓存:使用 ETag 或 Last-Modified 头边缘计算:在 CDN 边缘节点执行简单的动态逻辑API 响应缓存:缓存 API 的响应结果2. 智能缓存基于内容的特性自动调整缓存策略:静态文件自动长缓存:根据文件扩展名自动设置长 TTL动态内容自动短缓存:检测到频繁更新自动缩短 TTL热门内容优先缓存:根据访问频率优化缓存3. 条件缓存根据特定条件决定是否缓存:基于用户类型:普通用户缓存,付费用户实时基于地理位置:某些地区缓存,某些地区实时基于时间:高峰期缓存,低峰期实时缓存策略优化建议1. 合理设置 TTL静态资源:设置较长的 TTL(1 天到 1 年)动态内容:设置较短的 TTL(几秒到几分钟)版本化资源:使用文件名版本控制,可以设置无限缓存2. 使用缓存控制头// 强制缓存Cache-Control: public, max-age=31536000, immutable// 禁止缓存Cache-Control: no-store, no-cache// 协商缓存Cache-Control: no-cacheETag: "abc123"Last-Modified: Wed, 21 Oct 2026 07:28:00 GMT3. 监控缓存命中率高命中率(>90%):缓存策略有效中等命中率(70-90%):需要优化低命中率(:需要重新评估策略4. A/B 测试缓存策略对不同用户群体使用不同的缓存策略,比较效果:测试不同的 TTL 值测试不同的缓存键配置测试是否启用边缘计算常见问题及解决方案问题 1:内容更新后用户仍看到旧内容解决方案:使用 URL 版本控制(如 style.v2.css)主动刷新 CDN 缓存设置合理的 TTL问题 2:缓存命中率低解决方案:检查缓存键配置是否合理分析未缓存的原因(如 Cookie、Authorization 头)调整 TTL 设置问题 3:源站压力仍然很大解决方案:增加 TTL 延长缓存时间启用缓存预热检查是否有大量回源请求面试要点回答这个问题时应该强调:理解不同缓存策略的适用场景能够根据业务需求选择合适的策略了解缓存策略对性能的影响有实际优化经验或案例能够分析缓存命中率并提出改进建议
阅读 0·2月21日 17:01

GraphQL Schema 设计有哪些最佳实践

GraphQL Schema 设计最佳实践GraphQL Schema 是 API 的核心,良好的 Schema 设计能够提高开发效率、减少维护成本,并提供更好的开发者体验。1. Schema 基础结构类型定义type User { id: ID! name: String! email: String! age: Int posts: [Post!]! createdAt: DateTime!}type Post { id: ID! title: String! content: String! author: User! comments: [Comment!]! createdAt: DateTime!}type Comment { id: ID! text: String! author: User! post: Post! createdAt: DateTime!}输入类型input CreateUserInput { name: String! email: String! age: Int}input CreatePostInput { title: String! content: String! authorId: ID!}枚举类型enum PostStatus { DRAFT PUBLISHED ARCHIVED}联合类型union SearchResult = User | Post | Comment接口类型interface Node { id: ID!}type User implements Node { id: ID! name: String! email: String!}type Post implements Node { id: ID! title: String! content: String!}2. 命名规范类型命名使用 PascalCase(首字母大写)使用描述性名称避免缩写使用单数形式示例:# 好的命名type UserProfile { }type Article { }type ShoppingCart { }# 不好的命名type user { }type Art { }type ShopCart { }字段命名使用 camelCase(首字母小写)使用动词或名词避免使用保留字示例:# 好的命名type User { firstName: String! lastName: String! fullName: String! isActive: Boolean!}# 不好的命名type User { first_name: String! LastName: String! Full_Name: String! active: Boolean!}参数命名使用 camelCase描述性名称包含单位或类型信息示例:# 好的命名query GetUsers($limit: Int, $offset: Int) { }query GetPosts($after: String, $first: Int) { }# 不好的命名query GetUsers($l: Int, $o: Int) { }query GetPosts($a: String, $f: Int) { }3. 类型设计原则避免过度嵌套# 不好的设计 - 过度嵌套type User { posts { comments { author { posts { comments { # 无限嵌套 } } } } }}# 好的设计 - 合理嵌套type User { posts(limit: Int): [Post!]!}type Post { comments(limit: Int): [Comment!]!}使用输入类型封装参数# 不好的设计 - 参数过多mutation CreateUser($name: String!, $email: String!, $age: Int, $address: String, $phone: String) { }# 好的设计 - 使用输入类型input CreateUserInput { name: String! email: String! age: Int address: String phone: String}mutation CreateUser($input: CreateUserInput!) { }实现分页# 基于游标的分页(推荐)type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String}type PostConnection { edges: [PostEdge!]! pageInfo: PageInfo! totalCount: Int!}type PostEdge { node: Post! cursor: String!}type Query { posts(after: String, first: Int): PostConnection!}# 基于偏移的分页type PostResult { posts: [Post!]! total: Int! page: Int! pageSize: Int!}type Query { posts(offset: Int, limit: Int): PostResult!}4. 错误处理自定义错误类型type Error { code: String! message: String! field: String}type UserResult { user: User errors: [Error!]!}type Mutation { createUser(input: CreateUserInput!): UserResult!}使用可空字段type Mutation { # 返回可空类型表示可能失败 createUser(input: CreateUserInput!): User}5. 版本控制与演进废弃字段type User { id: ID! name: String! # 废弃字段,提供替代方案 fullName: String @deprecated(reason: "Use 'name' instead") email: String!}添加新字段type User { id: ID! name: String! email: String! # 新增字段,不影响现有客户端 phoneNumber: String}6. 性能优化使用 DataLoaderconst userLoader = new DataLoader(async (userIds) => { const users = await User.findAll({ where: { id: userIds } }); return userIds.map(id => users.find(user => user.id === id));});字段级权限控制type User { id: ID! name: String! # 敏感字段需要权限 email: String! @auth(requires: ADMIN) salary: Float @auth(requires: ADMIN)}7. 文档与描述添加描述"""用户类型,包含用户的基本信息"""type User { """ 用户唯一标识符 """ id: ID! """ 用户姓名,最大长度 100 字符 """ name: String! """ 用户邮箱地址,必须是有效的邮箱格式 """ email: String!}8. 最佳实践总结保持简洁: 避免过度设计,保持 Schema 简洁明了一致性: 在整个项目中保持命名和结构的一致性可扩展性: 设计时考虑未来的扩展需求向后兼容: 添加新功能时保持向后兼容文档化: 为所有类型、字段和参数添加描述性能考虑: 避免过度嵌套,合理使用分页安全性: 实现适当的权限控制和验证测试: 为 Resolver 编写单元测试和集成测试
阅读 0·2月21日 17:01

什么是 CDN 回源?如何减少 CDN 回源?

CDN 回源的概念回源(Origin Pull)是指当 CDN 边缘节点没有缓存用户请求的内容时,边缘节点会向源站(Origin Server)请求内容的过程。回源是 CDN 工作机制中的重要环节,直接影响 CDN 的性能和源站负载。回源触发条件1. 缓存未命中(Cache Miss)这是最常见的回源原因:首次访问:内容从未被缓存过缓存过期:内容已超过 TTL(Time To Live)时间缓存被清除:主动刷新或被动清除缓存缓存键不匹配:请求参数变化导致缓存键不同2. 特殊请求类型某些请求类型会强制回源:POST 请求:通常不缓存,直接回源带特定头的请求:如 Authorization、Cookie 等动态内容:根据业务规则不缓存的内容3. 缓存策略配置根据配置决定是否回源:不缓存的路径:配置为不缓存的 URL 路径特定用户:如登录用户、VIP 用户等特定时间段:如活动期间需要实时数据回源对性能的影响1. 延迟增加回源请求需要经过完整的网络路径:用户 → 边缘节点:通常 <50ms边缘节点 → 源站:可能 100-500ms源站 → 边缘节点 → 用户:往返时间累积总延迟:缓存命中时 <50ms,回源时可能 200-1000ms2. 源站负载增加回源请求会直接打到源站:带宽消耗:所有回源请求都占用源站带宽服务器压力:增加源站 CPU、内存、数据库压力并发限制:可能触发源站的并发限制3. 成本增加带宽成本:CDN 回源带宽通常需要付费源站成本:可能需要升级源站配置流量成本:超出配额的额外费用减少回源的策略1. 优化缓存策略合理设置 TTL// 静态资源:长 TTLCache-Control: public, max-age=31536000, immutable// 动态内容:短 TTLCache-Control: public, max-age=60// 不缓存的内容Cache-Control: no-store使用版本控制通过 URL 版本化避免回源:// 不推荐:更新后需要清除缓存style.css// 推荐:更新时改变 URLstyle.v1.cssstyle.v2.css2. 缓存预热在内容发布前主动推送到 CDN:预热时机:内容发布前 1-2 小时预热内容:预计会频繁访问的内容预热方式:通过 CDN API 或管理控制台示例:# 预热特定 URLcurl -X POST "https://api.cdn.com/prefetch" \ -H "Content-Type: application/json" \ -d '{"urls": ["https://example.com/image.jpg"]}'3. 配置忽略参数忽略不影响内容的查询参数:// 配置忽略 timestamp 参数https://example.com/data?timestamp=123456https://example.com/data?timestamp=789012// 这两个请求会命中同一个缓存4. 使用边缘计算在 CDN 边缘节点处理简单逻辑:请求路由:根据用户类型返回不同内容简单计算:如时间戳转换、格式化等A/B 测试:在边缘节点分配测试组5. 分级缓存利用 CDN 的多级缓存架构:边缘缓存:第一级,容量小但响应快区域缓存:第二级,容量中等源站缓存:最后一级,容量最大优势:即使边缘缓存未命中,也可能命中区域缓存回源优化技巧1. 压缩传输减少回源时的数据传输量:// 启用压缩Accept-Encoding: gzip, deflate, br// 源站响应压缩内容Content-Encoding: gzip效果:文本内容可减少 60-80% 的传输量2. 使用 HTTP/2 或 HTTP/3利用新协议的优势:HTTP/2:多路复用,减少连接数HTTP/3:基于 UDP,减少连接建立时间3. 优化源站性能确保源站能够快速响应:数据库优化:添加索引、优化查询缓存层:使用 Redis、Memcached负载均衡:多台源站分担压力4. 监控回源情况实时监控回源指标:回源率:回源请求占总请求的比例回源延迟:回源请求的平均响应时间回源带宽:回源流量占用的带宽目标:回源率 <10%,回源延迟 <500ms回源常见问题问题 1:回源率过高原因分析:TTL 设置过短缓存键配置不当大量动态请求解决方案:延长静态资源的 TTL优化缓存键配置对动态内容实施边缘计算问题 2:回源延迟高原因分析:源站性能差网络距离远源站负载过高解决方案:优化源站性能使用就近的源站节点实施源站负载均衡问题 3:回源带宽成本高原因分析:大量大文件回源未启用压缩回源率过高解决方案:对大文件实施缓存预热启用压缩传输降低回源率面试要点回答这个问题时应该强调:清楚理解回源的概念和触发条件了解回源对性能和成本的影响掌握减少回源的多种策略有实际的优化经验和案例能够分析回源指标并提出改进方案
阅读 0·2月21日 17:01

CDN 的负载均衡策略有哪些?如何实现 CDN 的高可用?

CDN 负载均衡的概念CDN 负载均衡是指将用户请求智能地分发到多个 CDN 边缘节点,以优化性能、提高可用性和确保稳定性的机制。它是 CDN 系统的核心组件之一,直接影响用户体验和系统可靠性。负载均衡策略1. 地理位置路由(Geo-based Routing)根据用户的地理位置选择最近的节点:工作原理:通过 DNS 或 IP 定位确定用户位置选择距离用户最近的可用节点考虑网络延迟和路径质量优点:减少网络延迟提升用户体验降低跨区域带宽成本实现方式:# Nginx GeoIP 模块示例geo $geo { default default; 1.0.0.0/8 us-east; 2.0.0.0/8 us-west;}upstream cdn_us_east { server cdn-us-east-1.example.com;}upstream cdn_us_west { server cdn-us-west-1.example.com;}2. 就近性路由(Proximity-based Routing)基于网络延迟选择最优节点:评估指标:RTT(Round Trip Time):往返时间丢包率:网络质量带宽利用率:节点负载算法:主动探测:定期测量各节点延迟被动测量:基于实际请求响应时间混合测量:结合主动和被动数据3. 轮询(Round Robin)按顺序依次分发请求到各个节点:特点:简单易实现请求均匀分布不考虑节点负载适用场景:节点性能相近请求量稳定对延迟不敏感配置示例:upstream cdn_nodes { server cdn-1.example.com; server cdn-2.example.com; server cdn-3.example.com;}4. 加权轮询(Weighted Round Robin)根据节点性能分配不同权重:权重因素:服务器性能:CPU、内存、带宽地理位置:优先级区域成本考虑:成本较低的节点权重高配置示例:upstream cdn_nodes { server cdn-1.example.com weight=3; # 高性能节点 server cdn-2.example.com weight=2; # 中等性能节点 server cdn-3.example.com weight=1; # 低性能节点}5. 最少连接(Least Connections)将请求分发到当前连接数最少的节点:优点:动态适应节点负载避免单节点过载提高资源利用率适用场景:请求处理时间差异大节点性能不均衡需要实时负载调整6. 哈希路由(Hash-based Routing)根据请求特征(如 IP、URL)进行哈希分配:哈希方式:源 IP 哈希:同一用户访问同一节点URL 哈希:同一内容访问同一节点一致性哈希:节点变化时影响最小优点:提高缓存命中率保持会话一致性减少缓存失效配置示例:upstream cdn_nodes { ip_hash; # 基于 IP 的哈希 server cdn-1.example.com; server cdn-2.example.com;}健康检查机制1. 主动健康检查定期主动探测节点状态:检查方式:TCP 检查:检查端口是否开放HTTP 检查:发送 HTTP 请求检查响应自定义检查:执行特定的健康检查脚本检查频率:正常节点:每 10-30 秒异常节点:每 1-5 秒恢复节点:增加检查频率配置示例:upstream cdn_nodes { server cdn-1.example.com max_fails=3 fail_timeout=30s; server cdn-2.example.com max_fails=3 fail_timeout=30s;}2. 被动健康检查基于实际请求响应判断节点状态:判断指标:响应时间:超过阈值视为异常错误率:错误率超过阈值视为异常超时率:超时率超过阈值视为异常优势:反映真实用户体验无需额外探测流量实时性强3. 健康检查响应健康状态:健康:正常接收请求不健康:暂时不接收请求恢复中:逐步恢复流量故障转移:自动剔除不健康节点流量重新分配到健康节点节点恢复后逐步加入流量调度优化1. 动态权重调整根据实时情况动态调整节点权重:调整因素:当前负载:CPU、内存、网络使用率响应时间:平均响应时间错误率:请求错误比例调整策略:负载高时降低权重响应慢时降低权重错误多时降低权重2. 熔断机制当节点持续异常时触发熔断:熔断状态:关闭:正常状态打开:熔断状态,不转发请求半开:尝试恢复状态熔断条件:错误率超过阈值(如 50%)响应时间超过阈值(如 5 秒)连续失败次数超过阈值恢复策略:熔断后等待一段时间尝试发送少量请求成功则逐步恢复流量3. 限流和降级保护系统免受过载影响:限流策略:全局限流:限制总请求数节点限流:限制单个节点请求数用户限流:限制单个用户请求数降级策略:静态降级:返回缓存内容动态降级:返回简化内容拒绝降级:直接拒绝请求负载均衡监控1. 关键指标性能指标:响应时间:P50、P95、P99吞吐量:每秒请求数错误率:请求失败比例负载指标:节点负载:CPU、内存、网络使用率连接数:当前连接数队列长度:等待处理的请求数可用性指标:节点可用性:节点在线时间比例故障转移次数:故障转移频率恢复时间:节点恢复所需时间2. 告警机制告警级别:P1(紧急):节点完全不可用P2(重要):节点性能严重下降P3(一般):节点性能轻微下降告警方式:邮件通知短信通知即时通讯工具监控大屏3. 自动化响应自动扩容:负载高时自动增加节点预测流量高峰提前扩容自动缩容:负载低时自动减少节点节省成本自动修复:节点异常时自动重启配置错误时自动回滚常见问题及解决方案问题 1:负载不均衡原因:权重配置不合理健康检查不准确流量突发解决方案:调整节点权重优化健康检查策略增加自动扩容机制问题 2:频繁故障转移原因:健康检查过于敏感网络抖动节点性能不稳定解决方案:调整健康检查阈值增加故障转移延迟优化节点性能问题 3:缓存命中率低原因:负载均衡策略不当节点切换频繁缓存键配置错误解决方案:使用哈希路由增加节点粘性优化缓存键配置面试要点回答这个问题时应该强调:了解不同的负载均衡策略及其适用场景理解健康检查机制的重要性掌握流量调度优化的方法有实际的负载均衡配置经验能够分析负载均衡指标并提出优化建议
阅读 0·2月21日 17:01

什么是 CDN 边缘计算?有哪些应用场景?

CDN 边缘计算的概念CDN 边缘计算(Edge Computing)是指将计算能力从中心化的源站或云端下沉到 CDN 边缘节点,在靠近用户的网络边缘执行计算任务。这种架构可以显著降低延迟、减少回源流量、提升用户体验。边缘计算的核心价值1. 降低延迟传统架构:用户请求 → CDN 边缘节点 → 源站/云端处理 → 返回结果延迟:200-500ms边缘计算架构:用户请求 → CDN 边缘节点(本地处理)→ 返回结果延迟:10-50ms优势:减少网络传输距离避免跨区域访问实时响应用户请求2. 减少回源流量场景对比:传统方式:所有动态请求都需要回源源站负载高带宽成本高边缘计算方式:大部分请求在边缘处理仅少量请求需要回源显著降低源站负载和带宽成本3. 提升用户体验用户体验提升:更快的响应速度更高的可用性更好的个性化服务4. 数据隐私保护隐私优势:数据在本地处理,减少传输符合数据本地化法规降低数据泄露风险CDN 边缘计算的应用场景1. API 网关和路由功能:请求路由和转发API 版本管理请求/响应转换示例:// Cloudflare Workers 示例addEventListener('fetch', event => { event.respondWith(handleRequest(event.request))})async function handleRequest(request) { const url = new URL(request.url) // API 路由 if (url.pathname.startsWith('/api/v1')) { return fetch('https://api-v1.example.com' + url.pathname + url.search) } else if (url.pathname.startsWith('/api/v2')) { return fetch('https://api-v2.example.com' + url.pathname + url.search) } return new Response('Not Found', { status: 404 })}2. 动态内容生成应用:个性化内容推荐A/B 测试实时内容修改示例:// 根据用户地理位置返回不同内容async function handleRequest(request) { const country = request.cf.country if (country === 'CN') { return new Response('欢迎访问中国版网站') } else if (country === 'US') { return new Response('Welcome to US version') } return new Response('Welcome to our website')}3. 图片处理和优化功能:实时图片缩放、裁剪格式转换(WebP、AVIF)质量优化示例:// 图片处理async function handleRequest(request) { const url = new URL(request.url) const width = url.searchParams.get('width') || 800 const height = url.searchParams.get('height') || 600 // 从源站获取原始图片 const originalImage = await fetch('https://origin.example.com/image.jpg') const imageBuffer = await originalImage.arrayBuffer() // 处理图片 const processedImage = await processImage(imageBuffer, width, height) return new Response(processedImage, { headers: { 'Content-Type': 'image/jpeg' } })}4. 认证和授权功能:JWT 验证API Key 验证权限检查示例:// JWT 验证async function handleRequest(request) { const authHeader = request.headers.get('Authorization') if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response('Unauthorized', { status: 401 }) } const token = authHeader.substring(7) try { const decoded = await verifyJWT(token) // 继续处理请求 return fetch('https://api.example.com/data', { headers: { 'X-User-ID': decoded.userId } }) } catch (error) { return new Response('Invalid token', { status: 401 }) }}5. 限流和防爬功能:请求频率限制爬虫识别和拦截恶意请求过滤示例:// 基于 IP 的限流const rateLimiter = new Map()async function handleRequest(request) { const ip = request.headers.get('CF-Connecting-IP') const now = Date.now() if (!rateLimiter.has(ip)) { rateLimiter.set(ip, { count: 1, resetTime: now + 60000 }) return fetch(request) } const data = rateLimiter.get(ip) if (now > data.resetTime) { rateLimiter.set(ip, { count: 1, resetTime: now + 60000 }) return fetch(request) } if (data.count >= 100) { return new Response('Too many requests', { status: 429 }) } data.count++ return fetch(request)}6. 数据聚合和缓存功能:多数据源聚合智能缓存数据预处理示例:// 聚合多个 API 的数据async function handleRequest(request) { const cacheKey = 'aggregated-data' const cachedData = await cache.get(cacheKey) if (cachedData) { return new Response(cachedData) } // 并行请求多个数据源 const [users, products, orders] = await Promise.all([ fetch('https://api.example.com/users').then(r => r.json()), fetch('https://api.example.com/products').then(r => r.json()), fetch('https://api.example.com/orders').then(r => r.json()) ]) const aggregatedData = { users: users.data, products: products.data, orders: orders.data, timestamp: Date.now() } // 缓存 5 分钟 await cache.put(cacheKey, JSON.stringify(aggregatedData), { expirationTtl: 300 }) return new Response(JSON.stringify(aggregatedData))}主流 CDN 边缘计算平台1. Cloudflare Workers特点:基于 V8 引擎支持 JavaScript/TypeScript全球 200+ 数据中心免费套餐可用优势:部署简单性能优秀生态丰富示例:addEventListener('fetch', event => { event.respondWith(handleRequest(event.request))})async function handleRequest(request) { return new Response('Hello from Cloudflare Workers!')}2. AWS Lambda@Edge特点:与 CloudFront 集成支持 Node.js、Python自动扩展优势:与 AWS 生态无缝集成高可用性按使用付费示例:exports.handler = async (event) => { const request = event.Records[0].cf.request // 修改请求 request.headers['x-custom-header'] = [{ value: 'custom-value' }] return request}3. Fastly Compute@Edge特点:基于 WebAssembly支持 Rust、C++、JavaScript高性能优势:性能极佳支持多种语言灵活性高4. Cloudflare Workers KV特点:全球分布式键值存储与 Workers 集成低延迟读写使用场景:配置存储用户会话计数器示例:async function handleRequest(request) { const key = 'visit-count' let count = await KV.get(key) || 0 count = parseInt(count) + 1 await KV.put(key, count.toString()) return new Response(`Visit count: ${count}`)}边缘计算的最佳实践1. 无状态设计原则:不依赖本地存储使用外部存储服务设计可重入的函数示例:// 好的做法:使用外部存储async function handleRequest(request) { const data = await KV.get('key') return new Response(data)}// 不好的做法:依赖本地变量let counter = 0async function handleRequest(request) { counter++ return new Response(counter.toString())}2. 错误处理和降级策略:优雅降级重试机制超时控制示例:async function fetchWithTimeout(url, timeout = 5000) { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), timeout) try { const response = await fetch(url, { signal: controller.signal }) clearTimeout(timeoutId) return response } catch (error) { clearTimeout(timeoutId) // 返回缓存数据或默认值 return new Response('Service temporarily unavailable', { status: 503 }) }}3. 性能优化优化技巧:减少外部依赖使用缓存优化代码大小示例:// 使用缓存减少外部请求const CACHE_TTL = 300 // 5 分钟async function handleRequest(request) { const cacheKey = request.url const cached = await cache.get(cacheKey) if (cached) { return cached } const response = await fetch(request) await cache.put(cacheKey, response.clone(), { expirationTtl: CACHE_TTL }) return response}4. 监控和日志监控指标:请求成功率响应时间错误率日志记录:async function handleRequest(request) { const startTime = Date.now() try { const response = await fetch(request) console.log(`Request completed in ${Date.now() - startTime}ms`) return response } catch (error) { console.error(`Request failed: ${error.message}`) throw error }}边缘计算的挑战1. 开发和调试困难挑战:本地环境与生产环境差异调试工具有限日志查看不便解决方案:使用模拟环境完善的日志记录逐步部署2. 资源限制限制:CPU 时间限制内存限制执行时间限制应对策略:优化代码性能减少不必要的计算使用异步操作3. 冷启动延迟问题:首次请求可能有延迟影响用户体验解决方案:预热函数保持函数活跃使用缓存面试要点回答这个问题时应该强调:理解边缘计算的核心价值和优势了解边缘计算的主要应用场景掌握至少一种边缘计算平台的使用有实际的边缘计算开发经验了解边缘计算的挑战和解决方案
阅读 0·2月21日 17:01

GraphQL 有哪些高级概念和架构设计模式

GraphQL 高级概念与架构设计GraphQL 不仅仅是查询语言,它还包含许多高级概念和架构设计模式,掌握这些对于构建大规模 GraphQL 应用至关重要。1. 联合类型(Union Types)定义联合类型union SearchResult = User | Post | Commenttype Query { search(query: String!): [SearchResult!]!}实现联合类型 Resolverconst resolvers = { SearchResult: { __resolveType: (obj) => { if (obj.email) { return 'User'; } if (obj.title) { return 'Post'; } if (obj.text) { return 'Comment'; } return null; } }, Query: { search: async (_, { query }) => { const users = await User.search(query); const posts = await Post.search(query); const comments = await Comment.search(query); return [...users, ...posts, ...comments]; } }};使用联合类型query SearchResults($query: String!) { search(query: $query) { ... on User { id name email } ... on Post { id title author { name } } ... on Comment { id text author { name } } }}2. 接口类型(Interface Types)定义接口interface Node { id: ID! createdAt: DateTime!}type User implements Node { id: ID! createdAt: DateTime! name: String! email: String!}type Post implements Node { id: ID! createdAt: DateTime! title: String! content: String!}type Query { node(id: ID!): Node}实现接口 Resolverconst resolvers = { Node: { __resolveType: (obj) => { if (obj.email) { return 'User'; } if (obj.title) { return 'Post'; } return null; } }, Query: { node: async (_, { id }) => { // 尝试从不同数据源获取 const user = await User.findById(id); if (user) return user; const post = await Post.findById(id); if (post) return post; throw new Error('Node not found'); } }};3. 自定义指令(Custom Directives)创建自定义指令directive @auth(requires: Role) on FIELD_DEFINITIONdirective @cache(ttl: Int) on FIELD_DEFINITIONdirective @transform(type: String) on FIELD_DEFINITIONenum Role { USER ADMIN}实现指令const { makeExecutableSchema } = require('@graphql-tools/schema');const { defaultFieldResolver } = require('graphql');const authDirective = (schema) => { return makeExecutableSchema({ typeDefs: schema, resolvers: { Query: { // ... existing resolvers } }, directiveResolvers: { auth: (next, source, args, context) => { const { requires } = args; if (!context.user || context.user.role !== requires) { throw new Error('Unauthorized'); } return next(); }, cache: async (next, source, args, context) => { const { ttl } = args; const cacheKey = `cache:${JSON.stringify(source)}`; // 尝试从缓存获取 const cached = await redis.get(cacheKey); if (cached) { return JSON.parse(cached); } // 执行 resolver const result = await next(); // 缓存结果 await redis.setex(cacheKey, ttl, JSON.stringify(result)); return result; } } });};4. 订阅(Subscriptions)定义订阅type Subscription { postCreated: Post! postUpdated(postId: ID!): Post! commentAdded(postId: ID!): Comment!}实现订阅const { PubSub } = require('graphql-subscriptions');const RedisPubSub = require('graphql-redis-subscriptions').RedisPubSub;const pubsub = new RedisPubSub({ connection: { host: 'localhost', port: 6379 }});const POST_CREATED = 'POST_CREATED';const POST_UPDATED = 'POST_UPDATED';const COMMENT_ADDED = 'COMMENT_ADDED';const resolvers = { Subscription: { postCreated: { subscribe: () => pubsub.asyncIterator([POST_CREATED]) }, postUpdated: { subscribe: (_, { postId }) => { const filteredIterator = pubsub.asyncIterator([POST_UPDATED]); return { [Symbol.asyncIterator]() { return (async function* () { for await (const event of filteredIterator) { if (event.postUpdated.id === postId) { yield event; } } })(); } }; } }, commentAdded: { subscribe: (_, { postId }) => { const filteredIterator = pubsub.asyncIterator([COMMENT_ADDED]); return { [Symbol.asyncIterator]() { return (async function* () { for await (const event of filteredIterator) { if (event.commentAdded.postId === postId) { yield event; } } })(); } }; } } }, Mutation: { createPost: async (_, { input }) => { const post = await Post.create(input); pubsub.publish(POST_CREATED, { postCreated: post }); return post; }, updatePost: async (_, { id, input }) => { const post = await Post.update(id, input); pubsub.publish(POST_UPDATED, { postUpdated: post }); return post; }, addComment: async (_, { input }) => { const comment = await Comment.create(input); pubsub.publish(COMMENT_ADDED, { commentAdded: comment }); return comment; } }};5. 数据加载器模式(DataLoader Pattern)批量加载const DataLoader = require('dataloader');class DataLoaderContext { constructor() { this.userLoader = new DataLoader(this.batchGetUsers); this.postLoader = new DataLoader(this.batchGetPosts); } async batchGetUsers(userIds) { const users = await User.findAll({ where: { id: userIds } }); return userIds.map(id => users.find(user => user.id === id)); } async batchGetPosts(postIds) { const posts = await Post.findAll({ where: { id: postIds } }); return postIds.map(id => posts.find(post => post.id === id)); }}在 Resolver 中使用const resolvers = { Post: { author: (post, _, context) => { return context.dataLoaders.userLoader.load(post.authorId); }, comments: (post, _, context) => { return context.dataLoaders.commentLoader.load(post.id); } }, Query: { posts: async (_, __, context) => { const posts = await Post.findAll(); return posts; } }};6. Schema 拆分与组合Schema 拆分# user.graphqltype User { id: ID! name: String! email: String!}extend type Query { user(id: ID!): User users: [User!]!}extend type Mutation { createUser(input: CreateUserInput!): User updateUser(id: ID!, input: UpdateUserInput!): User deleteUser(id: ID!): Boolean}# post.graphqltype Post { id: ID! title: String! content: String! author: User!}extend type Query { post(id: ID!): Post posts: [Post!]!}extend type Mutation { createPost(input: CreatePostInput!): Post updatePost(id: ID!, input: UpdatePostInput!): Post deletePost(id: ID!): Boolean}Schema 组合const { mergeTypeDefs } = require('@graphql-tools/merge');const { loadFilesSync } = require('@graphql-tools/load-files');const typeDefsArray = loadFilesSync(path.join(__dirname, './schemas'));const typeDefs = mergeTypeDefs(typeDefsArray);7. 微服务架构中的 GraphQL网关模式const { ApolloServer } = require('apollo-server');const { ApolloGateway } = require('@apollo/gateway');const { readFileSync } = require('fs');const gateway = new ApolloGateway({ supergraphSdl: readFileSync('./supergraph.graphql').toString(), serviceList: [ { name: 'users', url: 'http://localhost:4001/graphql' }, { name: 'posts', url: 'http://localhost:4002/graphql' }, { name: 'comments', url: 'http://localhost:4003/graphql' } ]});const server = new ApolloServer({ gateway, subscriptions: false});server.listen().then(({ url }) => { console.log(`Gateway ready at ${url}`);});联邦模式# users serviceextend type Post @key(fields: "id") { id: ID! @external author: User}type User @key(fields: "id") { id: ID! name: String! email: String!}# posts servicetype Post @key(fields: "id") { id: ID! title: String! content: String! authorId: ID! author: User}extend type User @key(fields: "id") { id: ID! @external posts: [Post!]!}8. 错误处理与重试自定义错误类class GraphQLError extends Error { constructor(message, code, extensions = {}) { super(message); this.name = 'GraphQLError'; this.code = code; this.extensions = extensions; }}class ValidationError extends GraphQLError { constructor(message, field) { super(message, 'VALIDATION_ERROR', { field }); }}class AuthenticationError extends GraphQLError { constructor(message = 'Authentication required') { super(message, 'AUTHENTICATION_ERROR'); }}class AuthorizationError extends GraphQLError { constructor(message = 'Not authorized') { super(message, 'AUTHORIZATION_ERROR'); }}错误处理中间件const formatError = (error) => { if (error instanceof GraphQLError) { return { message: error.message, code: error.code, extensions: error.extensions }; } // 生产环境不暴露详细错误 if (process.env.NODE_ENV === 'production') { return { message: 'Internal server error', code: 'INTERNAL_SERVER_ERROR' }; } return error;};9. 测试策略Resolver 单元测试const { resolvers } = require('./resolvers');describe('User resolvers', () => { describe('Query.user', () => { it('should return user by id', async () => { const mockUser = { id: '1', name: 'John', email: 'john@example.com' }; User.findById = jest.fn().mockResolvedValue(mockUser); const result = await resolvers.Query.user(null, { id: '1' }); expect(result).toEqual(mockUser); expect(User.findById).toHaveBeenCalledWith('1'); }); it('should throw error if user not found', async () => { User.findById = jest.fn().mockResolvedValue(null); await expect( resolvers.Query.user(null, { id: '1' }) ).rejects.toThrow('User not found'); }); });});集成测试const { ApolloServer } = require('apollo-server');const { createTestClient } = require('apollo-server-testing');const { typeDefs, resolvers } = require('./schema');describe('GraphQL API', () => { const server = new ApolloServer({ typeDefs, resolvers }); const { query, mutate } = createTestClient(server); describe('Query.users', () => { it('should return all users', async () => { const GET_USERS = ` query GetUsers { users { id name email } } `; const { data, errors } = await query(GET_USERS); expect(errors).toBeUndefined(); expect(data.users).toBeDefined(); expect(Array.isArray(data.users)).toBe(true); }); }); describe('Mutation.createUser', () => { it('should create a new user', async () => { const CREATE_USER = ` mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name email } } `; const variables = { input: { name: 'John Doe', email: 'john@example.com' } }; const { data, errors } = await mutate(CREATE_USER, { variables }); expect(errors).toBeUndefined(); expect(data.createUser).toBeDefined(); expect(data.createUser.name).toBe('John Doe'); }); });});10. 高级架构模式CQRS 模式const resolvers = { Query: { // 读操作 - 使用优化的查询 user: async (_, { id }, { readDb }) => { return readDb.User.findById(id); } }, Mutation: { // 写操作 - 使用事件溯源 createUser: async (_, { input }, { writeDb, eventBus }) => { const user = await writeDb.User.create(input); await eventBus.publish('USER_CREATED', { user }); return user; } }};事件溯源模式class EventStore { async saveEvent(aggregateId, eventType, payload) { await Event.create({ aggregateId, eventType, payload: JSON.stringify(payload), timestamp: new Date() }); } async getEvents(aggregateId) { const events = await Event.findAll({ where: { aggregateId }, order: [['timestamp', 'ASC']] }); return events.map(event => ({ ...event, payload: JSON.parse(event.payload) })); }}11. 高级概念总结| 概念 | 用途 | 优势 ||------|------|------|| 联合类型 | 处理多种类型的返回结果 | 灵活的数据结构 || 接口类型 | 定义共享字段 | 代码复用、类型安全 || 自定义指令 | 声明式功能 | 可重用、可组合 || 订阅 | 实时数据推送 | 实时性、低延迟 || DataLoader | 批量加载 | 性能优化、减少查询 || Schema 组合 | 模块化设计 | 可维护性、团队协作 || 微服务 | 分布式架构 | 可扩展性、独立部署 || 错误处理 | 统一错误管理 | 一致性、可调试性 || 测试策略 | 质量保证 | 可靠性、可维护性 || 架构模式 | 解决复杂问题 | 可扩展性、灵活性 |
阅读 0·2月21日 17:00