Java学习指南
  • Java 编程的逻辑
  • Java进阶
  • Java FrameWorks
  • 了解 USB Type-A,B,C 三大标准接口
  • 深入浅出DDD
  • 重构:改善既有代码的设计
  • 面试大纲
  • 云原生
    • 什么是无服务器(what is serverless)?
  • 博客
    • 深入分析Log4j 漏洞
  • 博客
    • Serverless之快速搭建Spring Boot应用
  • 博客
    • 使用 Prometheus + Grafana + Spring Boot Actuator 监控应用
  • 博客
    • 使用 Prometheus + Grafana 监控 MySQL
  • 博客
    • 使用Github Actions + Docker 部署Spring Boot应用
  • 博客
    • Redis分布式锁之Redisson的原理和实践
  • 博客
    • 数据库中的树结构应该怎样去设计
  • 学习&成长
    • 如何成为技术大牛
  • 开发工具
    • Git Commit Message Guidelines
  • 开发工具
    • git命名大全
  • 开发工具
    • Gradle vs Maven Comparison
  • 开发工具
    • Swagger2常用注解及其说明
  • 开发工具
    • 简明 VIM 练级攻略
  • 微服务
    • 十大微服务设计模式和原则
  • 微服务
    • 微服务下的身份认证和令牌管理
  • 微服务
    • 微服务坏味道之循环依赖
  • 设计模式
    • 设计模式 - JDK中的设计模式
  • 设计模式
    • 设计模式 - Java三种代理模式
  • 设计模式
    • 设计模式 - 六大设计原则
  • 设计模式
    • 设计模式 - 单例模式
  • 设计模式
    • 设计模式 - 命名模式
  • 设计模式
    • 设计模式 - 备忘录模式
  • 设计模式
    • 设计模式 - 概览
  • 设计模式
    • 设计模式 - 没用的设计模式
  • 质量&效率
    • Homebrew 替换国内镜像源
  • 质量&效率
    • 工作中如何做好技术积累
  • Java FrameWorks
    • Logback
      • 自定义 logback 日志过滤器
  • Java FrameWorks
    • Mybatis
      • MyBatis(十三) - 整合Spring
  • Java FrameWorks
    • Mybatis
      • MyBatis(十二) - 一些API
  • Java FrameWorks
    • Mybatis
      • Mybatis(一) - 概述
  • Java FrameWorks
    • Mybatis
      • Mybatis(七) - 结果集的封装与映射
  • Java FrameWorks
    • Mybatis
      • Mybatis(三) - mapper.xml及其加载机制
  • Java FrameWorks
    • Mybatis
      • Mybatis(九) - 事务
  • Java FrameWorks
    • Mybatis
      • Mybatis(二) - 全局配置文件及其加载机制
  • Java FrameWorks
    • Mybatis
      • Mybatis(五) - SqlSession执行流程
  • Java FrameWorks
    • Mybatis
      • Mybatis(八) - 缓存
  • Java FrameWorks
    • Mybatis
      • Mybatis(六) - 动态SQL的参数绑定与执行
  • Java FrameWorks
    • Mybatis
      • Mybatis(十) - 插件
  • Java FrameWorks
    • Mybatis
      • Mybatis(十一) - 日志
  • Java FrameWorks
    • Mybatis
      • Mybatis(四) - Mapper接口解析
  • Java FrameWorks
    • Netty
      • Netty 可靠性分析
  • Java FrameWorks
    • Netty
      • Netty - Netty 线程模型
  • Java FrameWorks
    • Netty
      • Netty堆外内存泄露排查盛宴
  • Java FrameWorks
    • Netty
      • Netty高级 - 高性能之道
  • Java FrameWorks
    • Shiro
      • Shiro + JWT + Spring Boot Restful 简易教程
  • Java FrameWorks
    • Shiro
      • 非常详尽的 Shiro 架构解析!
  • Java FrameWorks
    • Spring
      • Spring AOP 使用介绍,从前世到今生
  • Java FrameWorks
    • Spring
      • Spring AOP 源码解析
  • Java FrameWorks
    • Spring
      • Spring Event 实现原理
  • Java FrameWorks
    • Spring
      • Spring Events
  • Java FrameWorks
    • Spring
      • Spring IOC容器源码分析
  • Java FrameWorks
    • Spring
      • Spring Integration简介
  • Java FrameWorks
    • Spring
      • Spring MVC 框架中拦截器 Interceptor 的使用方法
  • Java FrameWorks
    • Spring
      • Spring bean 解析、注册、实例化流程源码剖析
  • Java FrameWorks
    • Spring
      • Spring validation中@NotNull、@NotEmpty、@NotBlank的区别
  • Java FrameWorks
    • Spring
      • Spring 如何解决循环依赖?
  • Java FrameWorks
    • Spring
      • Spring 异步实现原理与实战分享
  • Java FrameWorks
    • Spring
      • Spring中的“for update”问题
  • Java FrameWorks
    • Spring
      • Spring中的设计模式
  • Java FrameWorks
    • Spring
      • Spring事务失效的 8 大原因
  • Java FrameWorks
    • Spring
      • Spring事务管理详解
  • Java FrameWorks
    • Spring
      • Spring计时器StopWatch使用
  • Java FrameWorks
    • Spring
      • 详述 Spring MVC 框架中拦截器 Interceptor 的使用方法
  • Java FrameWorks
    • Spring
      • 透彻的掌握 Spring 中@transactional 的使用
  • Java
    • Java IO&NIO&AIO
      • Java IO - BIO 详解
  • Java
    • Java IO&NIO&AIO
      • Java NIO - IO多路复用详解
  • Java
    • Java IO&NIO&AIO
      • Java N(A)IO - Netty
  • Java
    • Java IO&NIO&AIO
      • Java IO - Unix IO模型
  • Java
    • Java IO&NIO&AIO
      • Java IO - 分类
  • Java
    • Java IO&NIO&AIO
      • Java NIO - 基础详解
  • Java
    • Java IO&NIO&AIO
      • Java IO - 常见类使用
  • Java
    • Java IO&NIO&AIO
      • Java AIO - 异步IO详解
  • Java
    • Java IO&NIO&AIO
      • Java IO概述
  • Java
    • Java IO&NIO&AIO
      • Java IO - 设计模式
  • Java
    • Java IO&NIO&AIO
      • Java NIO - 零拷贝实现
  • Java
    • Java JVM
      • JVM 优化经验总结
  • Java
    • Java JVM
      • JVM 内存结构
  • Java
    • Java JVM
      • JVM参数设置
  • Java
    • Java JVM
      • Java 内存模型
  • Java
    • Java JVM
      • 从实际案例聊聊Java应用的GC优化
  • Java
    • Java JVM
      • Java 垃圾回收器G1详解
  • Java
    • Java JVM
      • 垃圾回收器Shenandoah GC详解
  • Java
    • Java JVM
      • 垃圾回收器ZGC详解
  • Java
    • Java JVM
      • 垃圾回收基础
  • Java
    • Java JVM
      • 如何优化Java GC
  • Java
    • Java JVM
      • 类加载机制
  • Java
    • Java JVM
      • 类字节码详解
  • Java
    • Java 基础
      • Java hashCode() 和 equals()
  • Java
    • Java 基础
      • Java 基础 - Java native方法以及JNI实践
  • Java
    • Java 基础
      • Java serialVersionUID 有什么作用?
  • Java
    • Java 基础
      • Java 泛型的类型擦除
  • Java
    • Java 基础
      • Java 基础 - Unsafe类解析
  • Java
    • Java 基础
      • Difference Between Statement and PreparedStatement
  • Java
    • Java 基础
      • Java 基础 - SPI机制详解
  • Java
    • Java 基础
      • Java 基础 - final
  • Java
    • Java 基础
      • Java中static关键字详解
  • Java
    • Java 基础
      • 为什么说Java中只有值传递?
  • Java
    • Java 基础
      • Java 基础 - 即时编译器原理解析及实践
  • Java
    • Java 基础
      • Java 基础 - 反射
  • Java
    • Java 基础
      • Java多态的面试题
  • Java
    • Java 基础
      • Java 基础 - 异常机制详解
  • Java
    • Java 基础
      • 为什么要有抽象类?
  • Java
    • Java 基础
      • 接口的本质
  • Java
    • Java 基础
      • Java 基础 - 枚举
  • Java
    • Java 基础
      • Java 基础 - 泛型机制详解
  • Java
    • Java 基础
      • Java 基础 - 注解机制详解
  • Java
    • Java 基础
      • 为什么 String hashCode 方法选择数字31作为乘子
  • Java
    • Java 并发
      • Java 并发 - 14个Java并发容器
  • Java
    • Java 并发
      • Java 并发 - AQS
  • Java
    • Java 并发
      • Java 并发 - BlockingQueue
  • Java
    • Java 并发
      • Java 并发 - CAS
  • Java
    • Java 并发
      • Java 并发 - Condition接口
  • Java
    • Java 并发
      • Java 并发 - CopyOnWriteArrayList
  • Java
    • Java 并发
      • Java 并发 - CountDownLatch、CyclicBarrier和Phaser对比
  • Java
    • Java 并发
      • Java 并发 - Fork&Join框架
  • Java
    • Java 并发
      • Java 并发 - Java CompletableFuture 详解
  • Java
    • Java 并发
      • Java 并发 - Java 线程池
  • Java
    • Java 并发
      • Java 并发 - Lock接口
  • Java
    • Java 并发
      • Java 并发 - ReentrantLock
  • Java
    • Java 并发
      • Java 并发 - ReentrantReadWriteLock
  • Java
    • Java 并发
      • Java 并发 - Synchronized
  • Java
    • Java 并发
      • Java 并发 - ThreadLocal 内存泄漏问题
  • Java
    • Java 并发
      • Java 并发 - ThreadLocal
  • Java
    • Java 并发
      • Java 并发 - Volatile
  • Java
    • Java 并发
      • Java 并发 - 从ReentrantLock的实现看AQS的原理及应用
  • Java
    • Java 并发
      • Java 并发 - 公平锁和非公平锁
  • Java
    • Java 并发
      • Java 并发 - 内存模型
  • Java
    • Java 并发
      • Java 并发 - 原子类
  • Java
    • Java 并发
      • Java 并发 - 如何确保三个线程顺序执行?
  • Java
    • Java 并发
      • Java 并发 - 锁
  • Java
    • Java 的新特性
      • Java 10 新特性概述
  • Java
    • Java 的新特性
      • Java 11 新特性概述
  • Java
    • Java 的新特性
      • Java 12 新特性概述
  • Java
    • Java 的新特性
      • Java 13 新特性概述
  • Java
    • Java 的新特性
      • Java 14 新特性概述
  • Java
    • Java 的新特性
      • Java 15 新特性概述
  • Java
    • Java 的新特性
      • Java 8的新特性
  • Java
    • Java 的新特性
      • Java 9 新特性概述
  • Java
    • Java 调试排错
      • 调试排错 - Java Debug Interface(JDI)详解
  • Java
    • Java 调试排错
      • 调试排错 - CPU 100% 排查优化实践
  • Java
    • Java 调试排错
      • 调试排错 - Java Heap Dump分析
  • Java
    • Java 调试排错
      • 调试排错 - Java Thread Dump分析
  • Java
    • Java 调试排错
      • 调试排错 - Java动态调试技术原理
  • Java
    • Java 调试排错
      • 调试排错 - Java应用在线调试Arthas
  • Java
    • Java 调试排错
      • 调试排错 - Java问题排查:工具单
  • Java
    • Java 调试排错
      • 调试排错 - 内存溢出与内存泄漏
  • Java
    • Java 调试排错
      • 调试排错 - 在线分析GC日志的网站GCeasy
  • Java
    • Java 调试排错
      • 调试排错 - 常见的GC问题分析与解决
  • Java
    • Java 集合
      • Java 集合 - ArrayList
  • Java
    • Java 集合
      • Java 集合 - HashMap 和 ConcurrentHashMap
  • Java
    • Java 集合
      • Java 集合 - HashMap的死循环问题
  • Java
    • Java 集合
      • Java 集合 - LinkedHashSet&Map
  • Java
    • Java 集合
      • Java 集合 - LinkedList
  • Java
    • Java 集合
      • Java 集合 - PriorityQueue
  • Java
    • Java 集合
      • Java 集合 - Stack & Queue
  • Java
    • Java 集合
      • Java 集合 - TreeSet & TreeMap
  • Java
    • Java 集合
      • Java 集合 - WeakHashMap
  • Java
    • Java 集合
      • Java 集合 - 为什么HashMap的容量是2的幂次方
  • Java
    • Java 集合
      • Java 集合 - 概览
  • Java
    • Java 集合
      • Java 集合 - 高性能队列Disruptor详解
  • 分布式
    • RPC
      • ⭐️RPC - Dubbo&hsf&Spring cloud的区别
  • 分布式
    • RPC
      • ⭐️RPC - Dubbo的架构原理
  • 分布式
    • RPC
      • ⭐️RPC - HSF的原理分析
  • 分布式
    • RPC
      • ⭐️RPC - 你应该知道的RPC原理
  • 分布式
    • RPC
      • ⭐️RPC - 动态代理
  • 分布式
    • RPC
      • 深入理解 RPC 之协议篇
  • 分布式
    • RPC
      • RPC - 序列化和反序列化
  • 分布式
    • RPC
      • ⭐️RPC - 服务注册与发现
  • 分布式
    • RPC
      • RPC - 核心原理
  • 分布式
    • RPC
      • ⭐️RPC - 框架对比
  • 分布式
    • RPC
      • ⭐️RPC - 网络通信
  • 分布式
    • 分布式事务
      • 分布式事务 Seata TCC 模式深度解析
  • 分布式
    • 分布式事务
      • 分布式事务的实现原理
  • 分布式
    • 分布式事务
      • 常用的分布式事务解决方案
  • 分布式
    • 分布式事务
      • 手写实现基于消息队列的分布式事务框架
  • 分布式
    • 分布式算法
      • CAP 定理的含义
  • 分布式
    • 分布式算法
      • Paxos和Raft比较
  • 分布式
    • 分布式算法
      • 分布式一致性与共识算法
  • 分布式
    • 分布式锁
      • ⭐️分布式锁的原理及实现方式
  • 分布式
    • 搜索引擎
      • ElasticSearch与SpringBoot的集成与JPA方法的使用
  • 分布式
    • 搜索引擎
      • 全文搜索引擎 Elasticsearch 入门教程
  • 分布式
    • 搜索引擎
      • 十分钟学会使用 Elasticsearch 优雅搭建自己的搜索系统
  • 分布式
    • 搜索引擎
      • 腾讯万亿级 Elasticsearch 技术解密
  • 分布式
    • 日志系统
      • Grafana Loki 简明教程
  • 分布式
    • 日志系统
      • 分布式系统中如何优雅地追踪日志
  • 分布式
    • 日志系统
      • 如何优雅地记录操作日志?
  • 分布式
    • 日志系统
      • 日志收集组件—Flume、Logstash、Filebeat对比
  • 分布式
    • 日志系统
      • 集中式日志系统 ELK 协议栈详解
  • 分布式
    • 消息队列
      • 消息队列 - Kafka
  • 分布式
    • 消息队列
      • 消息队列 - Kafka、RabbitMQ、RocketMQ等消息中间件的对比
  • 分布式
    • 消息队列
      • 消息队列之 RabbitMQ
  • 分布式
    • 消息队列
      • 消息队列 - 使用docker-compose构建kafka集群
  • 分布式
    • 消息队列
      • 消息队列 - 分布式系统与消息的投递
  • 分布式
    • 消息队列
      • 消息队列 - 如何保证消息的可靠性传输
  • 分布式
    • 消息队列
      • 消息队列 - 如何保证消息的顺序性
  • 分布式
    • 消息队列
      • 消息队列 - 如何保证消息队列的高可用
  • 分布式
    • 消息队列
      • 消息队列 - 消息队列设计精要
  • 分布式
    • 监控系统
      • 深度剖析开源分布式监控CAT
  • 大数据
    • Flink
      • Flink架构与核心组件
  • 微服务
    • Dubbo
      • 基于dubbo的分布式应用中的统一异常处理
  • 微服务
    • Dubbo
      • Vim快捷键
  • 微服务
    • Service Mesh
      • Istio 是什么?
  • 微服务
    • Service Mesh
      • OCTO 2.0:美团基于Service Mesh的服务治理系统详解
  • 微服务
    • Service Mesh
      • Service Mesh是什么?
  • 微服务
    • Service Mesh
      • Spring Cloud向Service Mesh迁移
  • 微服务
    • Service Mesh
      • 数据挖掘算法
  • 微服务
    • Service Mesh
      • Seata Saga 模式
  • 微服务
    • Spring Cloud
      • Seata TCC 模式
  • 微服务
    • Spring Cloud
      • Spring Cloud Config
  • 微服务
    • Spring Cloud
      • Seata AT 模式
  • 微服务
    • Spring Cloud
      • Spring Cloud Gateway
  • 微服务
    • Spring Cloud
      • Spring Cloud OpenFeign 的核心原理
  • 微服务
    • Spring Cloud
      • Seata XA 模式
  • 数据库
    • Database Version Control
      • Liquibase vs. Flyway
  • 数据库
    • Database Version Control
      • Six reasons to version control your database
  • 数据库
    • MySQL
      • How Sharding Works
  • 数据库
    • MySQL
      • MySQL InnoDB中各种SQL语句加锁分析
  • 数据库
    • MySQL
      • MySQL 事务隔离级别和锁
  • 数据库
    • MySQL
      • MySQL 索引性能分析概要
  • 数据库
    • MySQL
      • MySQL 索引设计概要
  • 数据库
    • MySQL
      • MySQL出现Waiting for table metadata lock的原因以及解决方法
  • 数据库
    • MySQL
      • MySQL的Limit性能问题
  • 数据库
    • MySQL
      • MySQL索引优化explain
  • 数据库
    • MySQL
      • MySQL索引背后的数据结构及算法原理
  • 数据库
    • MySQL
      • MySQL行转列、列转行问题
  • 数据库
    • MySQL
      • 一条SQL更新语句是如何执行的?
  • 数据库
    • MySQL
      • 一条SQL查询语句是如何执行的?
  • 数据库
    • MySQL
      • 为什么 MySQL 使用 B+ 树
  • 数据库
    • MySQL
      • 为什么 MySQL 的自增主键不单调也不连续
  • 数据库
    • MySQL
      • 为什么我的MySQL会“抖”一下?
  • 数据库
    • MySQL
      • 为什么数据库不应该使用外键
  • 数据库
    • MySQL
      • 为什么数据库会丢失数据
  • 数据库
    • MySQL
      • 事务的可重复读的能力是怎么实现的?
  • 数据库
    • MySQL
      • 大众点评订单系统分库分表实践
  • 数据库
    • MySQL
      • 如何保证缓存与数据库双写时的数据一致性?
  • 数据库
    • MySQL
      • 浅谈数据库并发控制 - 锁和 MVCC
  • 数据库
    • MySQL
      • 深入浅出MySQL 中事务的实现
  • 数据库
    • MySQL
      • 浅入浅出MySQL 和 InnoDB
  • 数据库
    • PostgreSQL
      • PostgreSQL upsert功能(insert on conflict do)的用法
  • 数据库
    • Redis
      • Redis GEO & 实现原理深度分析
  • 数据库
    • Redis
      • Redis 和 I/O 多路复用
  • 数据库
    • Redis
      • Redis分布式锁
  • 数据库
    • Redis
      • Redis实现分布式锁中的“坑”
  • 数据库
    • Redis
      • Redis总结
  • 数据库
    • Redis
      • 史上最全Redis高可用技术解决方案大全
  • 数据库
    • Redis
      • Redlock:Redis分布式锁最牛逼的实现
  • 数据库
    • Redis
      • 为什么 Redis 选择单线程模型
  • 数据库
    • TiDB
      • 新一代数据库TiDB在美团的实践
  • 数据库
    • 数据仓库
      • 实时数仓在有赞的实践
  • 数据库
    • 数据库原理
      • OLTP与OLAP的关系是什么?
  • 数据库
    • 数据库原理
      • 为什么 OLAP 需要列式存储
  • 系统设计
    • DDD
      • Domain Primitive
  • 系统设计
    • DDD
      • Repository模式
  • 系统设计
    • DDD
      • 应用架构
  • 系统设计
    • DDD
      • 聊聊如何避免写流水账代码
  • 系统设计
    • DDD
      • 领域层设计规范
  • 系统设计
    • DDD
      • 从三明治到六边形
  • 系统设计
    • DDD
      • 阿里盒马领域驱动设计实践
  • 系统设计
    • DDD
      • 领域驱动设计(DDD)编码实践
  • 系统设计
    • DDD
      • 领域驱动设计在互联网业务开发中的实践
  • 系统设计
    • 基础架构
      • 容错,高可用和灾备
  • 系统设计
    • 数据聚合
      • GraphQL及元数据驱动架构在后端BFF中的实践
  • 系统设计
    • 数据聚合
      • 高效研发-闲鱼在数据聚合上的探索与实践
  • 系统设计
    • 服务安全
      • JSON Web Token 入门教程
  • 系统设计
    • 服务安全
      • 你还在用JWT做身份认证嘛?
  • 系统设计
    • 服务安全
      • 凭证(Credentials)
  • 系统设计
    • 服务安全
      • 授权(Authorization)
  • 系统设计
    • 服务安全
      • 理解OAuth2.0
  • 系统设计
    • 服务安全
      • 认证(Authentication)
  • 系统设计
    • 架构案例
      • 微信 Android 客户端架构演进之路
  • 系统设计
    • 高可用架构
      • 业务高可用的保障:异地多活架构
  • 计算机基础
    • 字符编码
      • Base64原理解析
  • 计算机基础
    • 字符编码
      • 字符编码笔记:ASCII,Unicode 和 UTF-8
  • 计算机基础
    • 操作系统
      • 为什么 CPU 访问硬盘很慢
  • 计算机基础
    • 操作系统
      • 为什么 HTTPS 需要 7 次握手以及 9 倍时延
  • 计算机基础
    • 操作系统
      • 为什么 Linux 默认页大小是 4KB
  • 计算机基础
    • 操作系统
      • 磁盘IO那些事
  • 计算机基础
    • 操作系统
      • 虚拟机的3种网络模式
  • 计算机基础
    • 服务器
      • mac终端bash、zsh、oh-my-zsh最实用教程
  • 计算机基础
    • 服务器
      • Nginx强制跳转Https
  • 计算机基础
    • 服务器
      • curl 的用法指南
  • 计算机基础
    • 网络安全
      • 如何设计一个安全的对外接口?
  • 计算机基础
    • 网络安全
      • 浅谈常见的七种加密算法及实现
  • 计算机基础
    • 网络编程
      • MQTT - The Standard for IoT Messaging
  • 计算机基础
    • 网络编程
      • 两万字长文 50+ 张趣图带你领悟网络编程的内功心法
  • 计算机基础
    • 网络编程
      • 为什么 TCP 协议有 TIME_WAIT 状态
  • 计算机基础
    • 网络编程
      • 为什么 TCP 协议有性能问题
  • 计算机基础
    • 网络编程
      • 为什么 TCP 协议有粘包问题
  • 计算机基础
    • 网络编程
      • 为什么 TCP 建立连接需要三次握手
  • 计算机基础
    • 网络编程
      • 为什么 TCP/IP 协议会拆分数据
  • 计算机基础
    • 网络编程
      • 使用 OAuth 2 和 JWT 为微服务提供安全保障
  • 计算机基础
    • 网络编程
      • 四种常见的 POST 提交数据方式
  • 计算机基础
    • 网络编程
      • 有赞TCP网络编程最佳实践
  • 计算机基础
    • 网络编程
      • 看完这篇HTTP,跟面试官扯皮就没问题了
  • 计算机基础
    • 网络编程
      • 详细解析 HTTP 与 HTTPS 的区别
  • 质量&效率
    • 快捷键
      • Idea快捷键(Mac版)
  • 质量&效率
    • 快捷键
      • Shell快捷键
  • 质量&效率
    • 快捷键
      • conduit
  • 质量&效率
    • 敏捷开发
      • Scrum的3种角色
  • 质量&效率
    • 敏捷开发
      • Scrum的4种会议
  • 质量&效率
    • 敏捷开发
      • ThoughtWorks的敏捷开发
  • 质量&效率
    • 敏捷开发
      • 敏捷开发入门教程
  • 运维&测试
    • Docker
      • Docker (容器) 的原理
  • 运维&测试
    • Docker
      • Docker Compose:链接外部容器的几种方式
  • 运维&测试
    • Docker
      • Docker 入门教程
  • 运维&测试
    • Docker
      • Docker 核心技术与实现原理
  • 运维&测试
    • Docker
      • Dockerfile 最佳实践
  • 运维&测试
    • Docker
      • Docker开启Remote API 访问 2375端口
  • 运维&测试
    • Docker
      • Watchtower - 自动更新 Docker 镜像与容器
  • 运维&测试
    • Kubernetes
      • Kubernetes 介绍
  • 运维&测试
    • Kubernetes
      • Kubernetes 在有赞的实践
  • 运维&测试
    • Kubernetes
      • Kubernetes 学习路径
  • 运维&测试
    • Kubernetes
      • Kubernetes如何改变美团的云基础设施?
  • 运维&测试
    • Kubernetes
      • Kubernetes的三种外部访问方式:NodePort、LoadBalancer 和 Ingress
  • 运维&测试
    • Kubernetes
      • 谈 Kubernetes 的架构设计与实现原理
  • 运维&测试
    • 压测
      • 全链路压测平台(Quake)在美团中的实践
  • 运维&测试
    • 测试
      • Cpress - JavaScript End to End Testing Framework
  • 运维&测试
    • 测试
      • 代码覆盖率-JaCoCo
  • 运维&测试
    • 测试
      • 浅谈代码覆盖率
  • 运维&测试
    • 测试
      • 测试中 Fakes、Mocks 以及 Stubs 概念明晰
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP中的Bean是如何被AOP代理的
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP原生动态代理和Cglib动态代理
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP实现方式(xml&注解)
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP是如何收集切面类并封装的
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP概述
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP的底层核心后置处理器
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP的延伸知识
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(一)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(三)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(二)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(五)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(四) - 循环依赖与解决方案
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - 启动引导
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot JarLauncher
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot Web Mvc 自动装配
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot 使用ApplicationListener监听器
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot 声明式事务
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot 嵌入式容器
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot引起的“堆外内存泄漏”排查及经验总结
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot的启动流程
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot自动化配置源码分析
  • Java FrameWorks
    • Spring
      • Spring Boot
        • 如何自定义Spring Boot Starter?
  • Java FrameWorks
    • Spring
      • Spring IOC
        • IOC - 模块装配和条件装配
  • Java FrameWorks
    • Spring
      • Spring IOC
        • IOC - 配置源(xml,注解)
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Environment
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring ApplicationContext
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanDefinition
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanFactory
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanFactoryPostProcessor
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanPostProcessor
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(一) - 概述
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(三) - 实例化阶段
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(二) - BeanDefinition
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(五) - 销毁阶段
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(四) - 初始化阶段
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring ComponentScan
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Events
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC 基础篇
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC 总结
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC 进阶篇
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC容器的生命周期
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Resource
  • Java FrameWorks
    • Spring
      • Spring MVC
        • DispatcherServlet的初始化原理
  • Java FrameWorks
    • Spring
      • Spring MVC
        • DispatcherServlet的核心工作原理
  • Java FrameWorks
    • Spring
      • Spring MVC
        • WebMvc的架构设计与组件功能解析
  • Java FrameWorks
    • Spring
      • Spring Security
        • Spring Boot 2 + Spring Security 5 + JWT 的单页应用 Restful 解决方案
  • Java FrameWorks
    • Spring
      • Spring Security
        • Spring Security Oauth
  • Java FrameWorks
    • Spring
      • Spring Security
        • Spring Security
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • DispatcherHandler的工作原理(传统方式)
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • DispatcherHandler的工作原理(函数式端点)
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • WebFlux的自动装配
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • 快速了解响应式编程与Reactive
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • 快速使用WebFlux
  • 分布式
    • 协调服务
      • Zookeeper
        • Zookeeper - 客户端之 Curator
  • 分布式
    • 协调服务
      • Zookeeper
        • 详解分布式协调服务 ZooKeeper
  • 分布式
    • 协调服务
      • etcd
        • 高可用分布式存储 etcd 的实现原理
  • 数据库
    • Database Version Control
      • Flyway
        • Database Migrations with Flyway
  • 数据库
    • Database Version Control
      • Flyway
        • How Flyway works
  • 数据库
    • Database Version Control
      • Flyway
        • Rolling Back Migrations with Flyway
  • 数据库
    • Database Version Control
      • Flyway
        • The meaning of the concept of checksums
  • 数据库
    • Database Version Control
      • Liquibase
        • Introduction to Liquibase Rollback
  • 数据库
    • Database Version Control
      • Liquibase
        • LiquiBase中文学习指南
  • 数据库
    • Database Version Control
      • Liquibase
        • Use Liquibase to Safely Evolve Your Database Schema
  • 系统设计
    • 流量控制
      • RateLimiter
        • Guava Rate Limiter实现分析
  • 系统设计
    • 流量控制
      • Sentinel
        • Sentinel 与 Hystrix 的对比
  • 系统设计
    • 流量控制
      • Sentinel
        • Sentinel工作主流程
  • 系统设计
    • 流量控制
      • 算法
        • 分布式服务限流实战
  • 系统设计
    • 解决方案
      • 秒杀系统
        • 如何设计一个秒杀系统
  • 系统设计
    • 解决方案
      • 红包系统
        • 微信高并发资金交易系统设计方案--百亿红包背后的技术支撑
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 什么是预排序遍历树算法(MPTT,Modified Preorder Tree Traversal)
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 加密算法
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 推荐系统算法
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • linkerd
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 查找算法
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 缓存淘汰算法中的LRU和LFU
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 负载均衡算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - Paxos算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - Raft算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - Snowflake算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - ZAB算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - 一致性Hash算法
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - Bitmap & Bloom Filter
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - Map & Reduce
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - Trie树/数据库/倒排索引
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 分治/hash/排序
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 双层桶划分
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 外(磁盘文件)排序
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 布隆过滤器
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理算法
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 文本预处理:后缀树(Suffix Tree)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 模式预处理:BM 算法 (Boyer-Moore)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 模式预处理:KMP 算法(Knuth-Morris-Pratt)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 模式预处理:朴素算法(Naive)(暴力破解)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 分支限界算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 分治算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 动态规划算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 回溯算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 贪心算法
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 十大排序算法
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(一)之3种简单排序(选择,冒泡,直接插入)
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(三)之堆排序
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(二)之希尔排序
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(四)之归并排序
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 树的高度和深度
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 红黑树深入剖析及Java实现
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 线性结构 - Hash
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 线性结构 - 数组、链表、栈、队列
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 逻辑结构 - 树
  • 运维&测试
    • 测试
      • Spock
        • Groovy 简明教程
  • 运维&测试
    • 测试
      • Spock
        • Spock 官方文档
  • 运维&测试
    • 测试
      • Spock
        • Spock单元测试框架介绍以及在美团优选的实践
  • 运维&测试
    • 测试
      • TDD
        • TDD 实践 - FizzFuzzWhizz(一)
  • 运维&测试
    • 测试
      • TDD
        • TDD 实践 - FizzFuzzWhizz(三)
  • 运维&测试
    • 测试
      • TDD
        • TDD 实践 - FizzFuzzWhizz(二)
  • 运维&测试
    • 测试
      • TDD
        • 测试驱动开发(TDD)- 原理篇
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Nacos
          • Nacos 服务注册的原理
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Nacos
          • Nacos 配置中心原理分析
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • 服务调用过程
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • Spring Cloud Bus
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • Spring Cloud Consul
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • Spring Cloud Stream
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Sentinel
          • Sentinel 与 Hystrix 的对比
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Sentinel
          • Sentinel
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • How Hystrix Works
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • Hystrix
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • Hystrix原理与实战
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • Spring Cloud Hystrix基本原理
由 GitBook 提供支持
在本页
  • 1. BFF的由来
  • 2. BFF背景下的核心矛盾
  • 3. BFF应用模式分析
  • 3.1 后端BFF模式
  • 3.2 前端BFF模式
  • 4. 基于GraphQL及元数据的信息聚合架构设计
  • 4.1 整体思路
  • 4.2 核心设计
  • 5. 针对GraphQL的优化实践
  • 5.1 使用简化
  • 5.2 性能优化
  • 6. 新架构对研发模式的影响
  • 6.1 聚焦业务的开发模式
  • 6.2 研发流程升级
  • 7. 总结
  • 8. 参考文献

这有帮助吗?

  1. 系统设计
  2. 数据聚合

GraphQL及元数据驱动架构在后端BFF中的实践

上一页数据聚合下一页系统设计

最后更新于2年前

这有帮助吗?

转载:GraphQL及元数据驱动架构在后端BFF中的实践

GraphQL是Facebook提出的一种数据查询语言,核心特性是数据聚合和按需索取,目前被广泛应用于前后端之间,解决客户端灵活使用数据问题。本文介绍的是GraphQL的另一种实践,我们将GraphQL下沉至后端BFF(Backend For Frontend)层之下,结合元数据技术,实现数据和加工逻辑的按需查询和执行。这样不仅解决了后端BFF层灵活使用数据的问题,这些字段加工逻辑还可以直接复用,大幅度提升了研发的效率。

本文介绍的实践方案已经在美团部分业务场景中落地,并取得不错效果,希望这些经验能够对大家有帮助。

1. BFF的由来

BFF一词来自Sam Newman的一篇博文《Pattern:Backends For Frontends》,指的是服务于前端的后端。BFF是解决什么问题的呢?据原文描述,随着移动互联网的兴起,原适应于桌面Web的服务端功能希望同时提供给移动App使用,而在这个过程中存在这样的问题:

  • 移动App和桌面Web在UI部分存在差异。

  • 移动App涉及不同的端,不仅有iOS、还有Android,这些不同端的UI之间存在差异。

  • 原有后端功能和桌面Web UI之间已经存在了较大的耦合。

因为端的差异性存在,服务端的功能要针对端的差异进行适配和裁剪,而服务端的业务功能本身是相对单一的,这就产生了一个矛盾——服务端的单一业务功能和端的差异性诉求之间的矛盾。那么这个问题怎么解决呢?这也是文章的副标题所描述的"Single-purpose Edge Services for UIs and external parties",引入BFF,由BFF来针对多端差异做适配,这也是目前业界广泛使用的一种模式。

2021-05-09-1BOffe

在实际业务的实践中,导致这种端差异性的原因有很多,有技术的原因,也有业务的原因。比如,用户的客户端是Android还是iOS,是大屏还是小屏,是什么版本。再比如,业务属于哪个行业,产品形态是什么,功能投放在什么场景,面向的用户群体是谁等等。这些因素都会带来面向端的功能逻辑的差异性。

在这个问题上,笔者所在团队负责的商品展示业务有一定的发言权,同样的商品业务,在C端的展示功能逻辑,深刻受到商品类型、所在行业、交易形态、投放场所、面向群体等因素的影响。同时,面向消费者端的功能频繁迭代的属性,更是加剧并深化了这种矛盾,使其演化成了一种服务端单一稳定与端的差异灵活之间的矛盾,这也是商品展示(商品展示BFF)业务系统存在的必然性原因。本文主要在美团到店商品展示场景的背景下,介绍面临的一些问题及解决思路。

2. BFF背景下的核心矛盾

BFF这层的引入是解决服务端单一稳定与端的差异灵活诉求之间的矛盾,这个矛盾并不是不存在,而是转移了。由原来后端和前端之间的矛盾转移成了BFF和前端之间的矛盾。笔者所在团队的主要工作,就是和这种矛盾作斗争。下面以具体的业务场景为例,结合当前的业务特点,说明在BFF的生产模式下,我们所面临的具体问题。下图是两个不同行业的团购货架展示模块,这两个模块我们认为是两个商品的展示场景,它们是两套独立定义的产品逻辑,并且会各自迭代。

在业务发展初期,这样的场景不多。BFF层系统“烟囱式”建设,功能快速开发上线满足业务的诉求,在这样的情况下,这种矛盾表现的不明显。而随着业务发展,行业的开拓,形成了许许多多这样的商品展示功能,矛盾逐渐加剧,主要表现在以下两个方面:

  • 业务支撑效率:随着商品展示场景变得越来越多,API呈爆炸趋势,业务支撑效率和人力成线性关系,系统能力难以支撑业务场景的规模化拓展。

  • 系统复杂度高:核心功能持续迭代,内部逻辑充斥着if…else…,代码过程式编写,系统复杂度较高,难以修改和维护。

那么这些问题是怎么产生的呢?这要结合“烟囱式”系统建设的背景和商品展示场景所面临的业务,以及系统特点来进行理解。

特点一:外部依赖多、场景间取数存在差异、用户体验要求高

图例展示了两个不同行业的团购货架模块,这样一个看似不大的模块,后端在BFF层要调用20个以上的下游服务才能把数据拿全,这是其一。在上面两个不同的场景中,需要的数据源集合存在差异,而且这种差异普遍存在,这是其二,比如足疗团购货架需要的某个数据源,在丽人团购货架上不需要,丽人团购货架需要的某个数据源,足疗团购货架不需要。尽管依赖下游服务多,同时还要保证C端的用户体验,这是其三。

这几个特点给技术带来了不小的难题:1)聚合大小难控制,聚合功能是分场景建设?还是统一建设?如果分场景建设,必然存在不同场景重复编写类似聚合逻辑的问题。如果统一建设,那么一个大而全的数据聚合中必然会存在无效的调用。2)聚合逻辑的复杂性控制问题,在这么多的数据源的情况下,不仅要考虑业务逻辑怎么写,还要考虑异步调用的编排,在代码复杂度未能良好控制的情况下,后续聚合的变更修改将会是一个难题。

特点二:展示逻辑多、场景之间存在差异,共性个性逻辑耦合

我们可以明显地识别某一类场景的逻辑是存在共性的,比如团单相关的展示场景。直观可以看出基本上都是展示团单维度的信息,但这只是表象。实际上在模块的生成过程中存在诸多的差异,比如以下两种差异:

  • 字段拼接逻辑差异:比如以上图中两个团购货架的团购标题为例,同样是标题,在丽人团购货架中的展示规则是:[类型] + 团购标题,而在足疗团购货架的展示规则是:团购标题。

  • 排序过滤逻辑差异:比如同样是团单列表,A场景按照销量倒排序,B场景按照价格排序,不同场景的排序逻辑不同。

诸如此类的展示逻辑的差异性还有很多。类似的场景实际上在内部存在很多差异的逻辑,后端如何应对这种差异性是一个难题,下面是最常见的一种写法,通过读取具体的条件字段来做判断实现逻辑路由,如下所示:

if(category == "丽人") {
  title = "[" + category + "]" + productTitle;
} else if (category == "足疗") {
  title = productTitle;
}

这种方案在功能实现方面没有问题,也能够复用共同的逻辑。但是实际上在场景非常多的情况下,将会有非常多的差异性判断逻辑叠加在一起,功能一直会被持续迭代的情况下,可以想象,系统将会变得越来越复杂,越来越难以修改和维护。

总结:在BFF这层,不同商品展示场景存在差异。在业务发展初期,系统通过独立建设的方式支持业务快速试错,在这种情况下,业务差异性带来的问题不明显。而随着业务的不断发展,需要搭建及运营的场景越来越多,呈规模化趋势。此时,业务对技术效率提出了更高的要求。在这种场景多、场景间存在差异的背景下,如何满足场景拓展效率同时能够控制系统的复杂性,就是我们业务场景中面临的核心问题。

3. BFF应用模式分析

目前,业界针对此类的解决方案主要有两种模式,一种是后端BFF模式;另一种是前端BFF模式。

3.1 后端BFF模式

后端BFF模式指的是BFF由后端同学负责,这种模式目前最广泛的实践是基于GraphQL搭建的后端BFF方案,具体是:后端将展示字段封装成展示服务,通过GraphQL编排之后暴露给前端使用。如下图所示:

这种模式最大的特性和优势是,当展示字段已经存在的情况下,后端不需要关心前端差异性需求,按需查询的能力由GraphQL支持。这个特性可以很好地应对不同场景存在展示字段差异性这个问题,前端直接基于GraphQL按需查询数据即可,后端不需要变更。同时,借助GraphQL的编排和聚合查询能力,后端可以将逻辑分解在不同的展示服务中,因此在一定程度上能够化解BFF这层的复杂性。

但是基于这种模式,仍然存在几个问题:展示服务颗粒度问题、数据图划分问题以及字段扩散问题,下图是基于当前模式的具体案例:

1)展示服务颗粒度设计问题

这种方案要求展示逻辑和取数逻辑封装在一个模块中,形成一个展示服务(Presentation Service),如上图所示。而实际上展示逻辑和取数逻辑是多对多的关系,还是以前文提到的例子说明:

背景:有两个展示服务,分别封装了商品标题和商品标签的查询能力。

情景:此时PM提了一个需求,希望商品在某个场景的标题以“[类型]+商品标题”的形式展示,此时商品标题的拼接依赖类型数据,而此时类型数据商品标签展示服务中已经调用了。

问题:商品标题展示服务自己调用类型数据还是将两个展示服务合并到一起?

以上描述的问题的是展示服务颗粒度把控的问题,我们可以怀疑上述的示例是不是因为展示服务的颗粒度过小?那么反过来看一看,如果将两个服务合并到一起,那么势必又会存在冗余。这是展示服务设计的难点,核心原因在于,展示逻辑和取数逻辑本身是多对多的关系,结果却被设计放在了一起。

2)数据图划分问题

通过GraphQL将多个展示服务的数据聚合到一张图(GraphQL Schema)中,形成一个数据视图,需要数据的时候只要数据在图中,就可以基于Query按需查询。那么问题来了,这个图应该怎么组织?是一张图还是多张图?图过大的话,势必带来复杂的数据关系维护问题,图过小则将会降低方案本身的价值。

3)展示服务内部复杂性 + 模型扩散问题

上文提到过一个商品标题的展示存在不同拼接逻辑的情况,在商品展示场景,这种逻辑特别普遍。比如同样是价格,A行业展示优惠后价格,B行业展示优惠前价格;同样是标签位置,C行业展示服务时长,而D行业展示商品特性等。

那么问题来了,展示模型如何设计?以标题字段为例,是在展示模型上放个title字段就可以,还是分别放个title和titleWithCategory?如果是前者那么服务内部必然会存在if…else…这种逻辑,用于区分title的拼接方式,这同样会导致展示服务内部的复杂性。如果是多个字段,那么可以想象,展示服务的模型字段也将会不断扩散。

总结:后端BFF模式能够在一定程度上化解后端逻辑的复杂性,同时提供一个展示字段的复用机制。但是仍然存在未决问题,如展示服务的颗粒度设计问题,数据图的划分问题,以及展示服务内部的复杂性和字段扩散问题。目前这种模式实践的代表有Facebook、爱彼迎、eBay、爱奇艺、携程、去哪儿等等。

3.2 前端BFF模式

前端BFF模式在Sam Newman的文章中的"And Autonomy"部分有特别的介绍,指的是BFF本身由前端团队自己负责,如下示意图所示:

这种模式的理念是,本来能一个团队交付的需求,没必要拆成两个团队,两个团队本身带来较大的沟通协作成本。本质上,也是一种将“敌我矛盾”转化为“人民内部矛盾”的思路。前端完全接手BFF的开发工作,实现数据查询的自给自足,大大减少了前后端的协作成本。但是这种模式没有提到我们关心的一些核心问题,如:复杂性如何应对、差异性如何应对、展示模型如何设计等等问题。除此之外,这种模式也存在一些前提条件及弊端,比如较为完备的前端基础设施;前端不仅仅需要关心渲染、还需要了解业务逻辑等。

总结:前端BFF模式通过前端自主查询和使用数据,从而达到降低跨团队协作的成本,提升BFF研发效率的效果。目前这种模式的实践代表是阿里巴巴。

4. 基于GraphQL及元数据的信息聚合架构设计

4.1 整体思路

通过对后端BFF和前端BFF两种模式的分析,我们最终选择后端BFF模式,前端BFF这个方案对目前的研发模式影响较大,不仅需要大量的前端资源,而且需要建设完善的前端基础设施,方案实施成本比较高昂。

前文提到的后端GraphQL BFF模式代入我们的具体场景虽然存在一些问题,但是总体有非常大的参考价值,比如展示字段的复用思路、数据的按需查询思路等等。在商品展示场景中,有80%的工作集中在数据的聚合和集成部分,并且这部分具有很强的复用价值,因此信息的查询和聚合是我们面临的主要矛盾。因此,我们的思路是:基于GraphQL+后端BFF方案改进,实现取数逻辑和展示逻辑的可沉淀、可组合、可复用,整体架构如下示意图所示:

从上图可看出,与传统GraphQL BFF方案最大的差别在于我们将GraphQL下放至数据聚合部分,由于数据来源于商品领域,领域是相对稳定的,因此数据图规模可控且相对稳定。除此之外,整体架构的核心设计还包括以下三个方面:1)取数展示分离;2)查询模型归一;3)元数据驱动架构。

我们通过取数展示分离解决展示服务颗粒度问题,同时使得展示逻辑和取数逻辑可沉淀、可复用;通过查询模型归一化设计解决展示字段扩散的问题;通过元数据驱动架构实现能力的可视化,业务组件编排执行的自动化,这能够让业务开发同学聚焦于业务逻辑的本身。下面将针对这三个部分逐一展开介绍。

4.2 核心设计

4.2.1 取数展示分离

上文提到,在商品展示场景中,展示逻辑和取数逻辑是多对多的关系,而传统的基于GraphQL的后端BFF实践方案把它们封装在一起,这是导致展示服务颗粒度难以设计的根本原因。思考一下取数逻辑和展示逻辑的关注点是什么?**取数逻辑关注怎么查询和聚合数据,而展示逻辑关注怎么加工生成需要的展示字段,它们的关注点不一样,放在一起也会增加展示服务的复杂性。**因此,我们的思路是将取数逻辑和展示逻辑分离开来,单独封装成逻辑单元,分别叫取数单元和展示单元。在取数展示分离之后,GraphQL也随之下沉,用于实现数据的按需聚合,如下图所示:

那么取数和展示逻辑的封装颗粒度是怎么样的呢?不能太小也不能太大,在颗粒度的设计上,我们有两个核心考量:1)复用,展示逻辑和取数逻辑在商品展示场景中,都是可以被复用的资产,我们希望它们能沉淀下来,被单独按需使用;2)简单,保持简单,这样容易修改和维护。基于这两点考虑,颗粒度的定义如下:

  • 取数单元:尽量只封装1个外部数据源,同时负责对外部数据源返回的模型进行简化,这部分生成的模型我们称之为取数模型。

  • 展示单元:尽量只封装1个展示字段的加工逻辑。

分开的好处是简单且可被组合使用,那么具体如何实现组合使用呢?我们的思路是通过元数据来描述它们之间的关系,基于元数据由统一的执行框架来关联运行,具体设计下文会展开介绍。通过取数和展示的分离,元数据的关联和运行时的组合调用,可以保持逻辑单元的简单,同时又满足复用诉求,这也很好地解决了传统方案中存在的展示服务的颗粒度问题。

4.2.2 查询模型归一

展示单元的加工结果通过什么样的接口透出呢?接下来,我们介绍一下查询接口设计的问题。

1)查询接口设计的难点

常见查询接口的设计模式有以下两种:

  • 强类型模式:强类型模式指的是查询接口返回的是POJO对象,每一个查询结果对应POJO中的一个明确的具有特定业务含义的字段。

  • 弱类型模式:弱类型模式指的是查询结果以K-V或JSON模式返回,没有明确的静态字段。

以上两种模式在业界都有广泛应用,且它们都有明确的优缺点。强类型模式对开发者友好,但是业务是不断迭代的,与此同时,系统沉淀的展示单元会不断丰富,在这样的情况下,接口返回的DTO中的字段将会愈来愈多,每次新功能的支持,都要伴随着接口查询模型的修改,JAR版本的升级。而JAR的升级涉及数据提供方和数据消费两方,存在明显效率问题。另外,可以想象,查询模型的不断迭代,最终将会包括成百上千个字段,难以维护。

而弱类型模式恰好可以弥补这一缺点,但是弱类型模式对于开发者来说非常不友好,接口查询模型中有哪些查询结果对于开发者来说在开发的过程中完全没有感觉,但是程序员的天性就是喜欢通过代码去理解逻辑,而非配置和文档。其实,这两种接口设计模式都存在着一个共性问题——缺少抽象,下面两节,我们将介绍在接口返回的查询模型设计方面的抽象思路及框架能力支持。

2)查询模型归一化设计

回到商品展示场景中,一个展示字段有多种不同的实现,如商品标题的两种不同实现方式:1)商品标题;2)[类目]+商品标题。商品标题和这两种展示逻辑的关系本质上是一种抽象-具体的关系。识别这个关键点,思路就明了了,我们的思路是对查询模型做抽象。查询模型上都是抽象的展示字段,一个展示字段对应多个展示单元,如下图所示:

在实现层面,同样基于元数据描述展示字段和展示单元之间的关系,基于以上的设计思路,可以在一定程度上减缓模型的扩散,但是还不能避免扩展。比如除了价格、库存、销量等每个商品都有的标准属性之外,不同的商品类型一般还会有这个商品特有的属性。比如密室主题拼场商品才有“几人拼”这样的描述属性,这种字段本身抽象的意义不大,且放在商品查询模型中作为一个单独的字段会导致模型扩张,针对这类问题,我们的解决思路是引入扩展属性,扩展属性专门承载这类非标准的字段。通过标准字段 + 扩展属性的方式建立查询模型,能够较好地解决字段扩散的问题。

4.2.3 元数据驱动架构

到目前为止,我们定义了如何分解业务逻辑单元以及如何设计查询模型,并提到用元数据描述它们之间的关系。基于以上定义实现的业务逻辑及模型,都具备很强的复用价值,可以作为业务资产沉淀下来。那么,为什么用元数据描述业务功能及模型之间的关系呢?

我们引入元数据描述主要有两个目的:1)代码逻辑的自动编排,通过元数据描述业务逻辑之间的关联关系,运行时可以自动基于元数据实现逻辑之间的关联执行,从而可以消除大量的人工逻辑编排代码;2)业务功能的可视化,元数据本身描述了业务逻辑所提供的功能,如下面两个示例:

团单基础售价字符串展示,例:30元。

团单市场价展示字段,例:100元。

这些元数据上报到系统中,可以用于展示当前系统所提供的功能。通过元数据描述组件及组件之间关联关系,通过框架解析元数据自动进行业务组件的调用执行,形成了如下的元数据架构:

整体架构由三个核心部分组成:

  • 业务能力:标准的业务逻辑单元,包括取数单元、展示单元和查询模型,这些都是关键的可复用资产。

  • 元数据:描述业务功能(如:展示单元、取数单元)以及业务功能之间的关联关系,比如展示单元依赖的数据,展示单元映射的展示字段等。

  • 执行引擎:负责消费元数据,并基于元数据对业务逻辑进行调度和执行。

通过以上三个部分有机的组合在一起,形成了一个元数据驱动风格的架构。

5. 针对GraphQL的优化实践

5.1 使用简化

5.1.1 GraphQL直接使用问题

引入GraphQL,会引入一些额外的复杂性,比如会涉及到GraphQL带来的一些概念如:Schema、RuntimeWiring,下面是基于GraphQL原生Java框架的开发过程:

这些概念对于未接触过GraphQL的同学来说,增加了学习和理解的成本,而这些概念和业务领域通常没有什么关系。而我们仅仅希望使用GraphQL的按需查询特性,却被GraphQL本身拖累了,业务开发同学的关注点应该聚焦在业务逻辑本身才对,这个问题如何解决呢?

著名计算机科学家David Wheeler说了一句名言,"All problems in computer science can be solved by another level of indirection"。没有加一层解决不了的问题,本质上是需要有人来对这事负责,因此我们在原生GraphQL之上增加了一层执行引擎层来解决这些问题,目标是屏蔽GraphQL的复杂性,让开发人员只需要关注业务逻辑。

5.1.2 取数接口标准化

首先要简化数据的接入,原生的DataFetcher和DataLoader都是处在一个比较高的抽象层次,缺少业务语义,而在查询场景,我们能够归纳出,所有的查询都属于以下三种模式:

  • 1查1:根据一个条件查询一个结果。

  • 1查N:根据一个条件查询多个结果。

  • N查N:一查一或一查多的批量版本。

由此,我们对查询接口进行了标准化,业务开发同学基于场景判断是那种,按需选择使用即可,取数接口标准化设计如下:

业务开发同学按需选择所需要使用的取数器,通过泛型指定结果类型,1查1和1查N比较简单,N查N我们对其定义为批量查询接口,用于满足"N+1"的场景,其中batchSize字段用于指定分片大小,batchKey用于指定查询Key,业务开发只需要指定参数,其他的框架会自动处理。除此之外,我们还约束了返回结果必须是CompleteFuture,用于满足聚合查询的全链路异步化。

5.1.3 聚合编排自动化

取数接口标准化使得数据源的语义更清晰,开发过程按需选择即可,简化了业务的开发。但是此时业务开发同学写好Fetcher之后,还需要去另一个地方去写Schema,而且写完Schema还要再写Schema和Fetcher的映射关系,业务开发更享受写代码的过程,不太愿意写完代码还要去另外一个地方取配置,并且同时维护代码和对应配置也提高了出错的可能性,能否将这些冗杂的步骤移除掉?

Schema和RuntimeWiring本质上是想描述某些信息,如果这些信息换一种方式描述是不是也可以。我们的优化思路是,在业务开发过程中标记注解,通过注解标注的元数据描述这些信息,其他的事情交给框架来做。解决思路示意图如下:

5.2 性能优化

5.2.1 GraphQL性能问题

虽然GraphQL已经开源了,但是Facebook只开源了相关标准,并没有给出解决方案。GraphQL-Java框架是由社区贡献的,基于开源的GraphQL-Java作为按需查询引擎的方案,我们发现了GraphQL应用方面的一些问题,这些问题有部分是由于使用姿势不当所导致的,也有部分是GraphQL本身实现的问题,比如我们遇到的几个典型的问题:

  • 耗CPU的查询解析,包括Schema的解析和Query的解析。

  • 当查询模型比较复杂特别是存在大列表时候的延时问题。

  • 基于反射的模型转换CPU消耗问题。

  • DataLoader的层级调度问题。

于是,我们对使用方式和框架做了一些优化与改造,以解决上面列举的问题。本章着重介绍我们在GraphQL-Java方面的优化和改造思路。

5.2.2 GraphQL编译优化

1)GraphQL语言原理概述

GraphQL是一种查询语言,目的是基于直观和灵活的语法构建客户端应用程序,用于描述其数据需求和交互。GraphQL属于一种领域特定语言(DSL),而我们所使用的GraphQL-Java客户端在语言编译层面是基于ANTLR 4实现的,ANTLR 4是一种基于Java编写的语言定义和识别工具,Antlr是一种元语言(Meta-Language),它们的关系如下:

GraphQL执行引擎所接受的Schema及Query都是基于GraphQL定义的语言所表达的内容,GraphQL执行引擎不能直接理解GraphQL,在执行之前必须由GraphQL编译器翻译成GraphQL执行引擎可理解的文档对象。而GraphQL编译器是基于Java的,经验表明在大流量场景实时解释的情况下,这部分代码将会成为CPU热点,而且还占用响应延迟,Schema或Query越复杂,性能损耗越明显。

2)Schema及Query编译缓存

Schema表达的是数据视图和取数模型同构,相对稳定,个数也不多,在我们的业务场景一个服务也就一个。因此,我们的做法是在启动的时候就将基于Schema构造的GraphQL执行引擎构造好,作为单例缓存下来。对于Query来说,每个场景的Query有些差异,因此Query的解析结果不能作为单例,我们的做法是实现PreparsedDocumentProvider接口,基于Query作为Key将Query编译结果缓存下来。如下图所示:

5.2.3 GraphQL执行引擎优化

1)GraphQL执行机制及问题

我们先一起了解一下GraphQL-Java执行引擎的运行机制是怎么样的。假设在执行策略上我们选取的是AsyncExecutionStrategy,来看看GraphQL执行引擎的执行过程:

以上时序图做了些简化,去除了一些与重点无关的信息,AsyncExecutionStrategy的execute方法是对象执行策略的异步化模式实现,是查询执行的起点,也是根节点查询的入口,AsyncExecutionStrategy对对象的多个字段的查询逻辑,采取的是循环+异步化的实现方式,我们从AsyncExecutionStrategy的execute方法触发,理解GraphQL查询过程如下:

  1. 调用当前字段所绑定的DataFetcher的get方法,如果字段没有绑定DataFetcher,则通过默认的PropertyDataFetcher查询字段,PropertyDataFetcher的实现是基于反射从源对象中读取查询字段。

  2. 将从DataFetcher查询得到结果包装成CompletableFuture,如果结果本身是CompletableFuture,那么不会包装。

  3. 结果CompletableFuture完成之后,调用completeValue,基于结果类型分别处理。

    • 如果查询结果是列表类型,那么会对列表类型进行遍历,针对每个元素在递归执行completeValue。

    • 如果结果类型是对象类型,那么会对对象执行execute,又回到了起点,也就是AsyncExecutionStrategy的execute。

以上是GraphQL的执行过程,这个过程有什么问题呢?下面基于图上的标记顺序一起看看GraphQL在我们的业务场景中应用和实践所遇到的问题,这些问题不代表在其他场景也是问题,仅供参考:

问题1:PropertyDataFetcherCPU热点问题,PropertyDataFetcher在整个查询过程中属于热点代码,而其本身的实现也有一些优化空间,在运行时PropertyDataFetcher的执行会成为CPU热点。(具体问题可参考GitHub上的commit和Conversion:https://github.com/graphql-java/graphql-java/pull/1815)

问题2:列表的计算耗时问题,列表计算是循环的,对于查询结果中存在大列表的场景,此时循环会造成整体查询明显的延迟。我们举个具体的例子,假设查询结果中存在一个列表大小是1000,每个元素的处理是0.01ms,那么总体耗时就是10ms,基于GraphQL的查机制,这个10ms会阻塞整个链路。

2)类型转换优化

通过GraphQL查询引擎拿到的GraphQL模型,和业务实现的DataFetcher返回的取数模型是同构,但是所有字段的类型都会被转换成GraphQL内部类型。PropertyDataFetcher之所以会成为CPU热点,问题就在于这个模型转换过程,业务定义的模型到GraphQL类型模型转换过程示意图如下图所示:

当查询结果模型中的字段非常多的时候,比如上万个,意味着每次查询有上万次的PropertyDataFetcher操作,实际就反映到了CPU热点问题上,这个问题我们的解决思路是保持原有业务模型不变,将非PropertyDataFetcher查询的结果反过来填充到业务模型上。如下示意图所示:

基于这个思路,我们通过GraphQL执行引擎拿到的结果就是业务Fetcher返回的对象模型,这样不仅仅解决了因字段反射转换带来的CPU热点问题,同时对于业务开发来说增加了友好性。因为GraphQL模型类似JSON模型,这种模型是缺少业务类型的,业务开发直接使用起来非常麻烦。以上优化在一个场景上试点测试,结果显示该场景的平均响应时间缩短1.457ms,平均99线缩短5.82ms,平均CPU利用率降低约12%。

3)列表计算优化

当列表元素比较多的时候,默认的单线程遍历列表元素计算的方式所带来的延迟消耗非常明显,对于响应时间比较敏感的场景这个延迟优化很有必要。针对这个问题我们的解决思路是充分利用CPU多核心计算的能力,将列表拆分成任务,通过多线程并行执行,实现机制如下:

5.2.4 GraphQL-DataLoader调度优化

1)DataLoader基本原理

先简单介绍一下DataLoader的基本原理,DataLoader有两个方法,一个是load,一个是dispatch,在解决N+1问题的场景中,DataLoader是这么用的:

整体分为2个阶段,第一个阶段调用load,调用N次,第二个阶段调用dispatch,调用dispatch的时候会真正的执行数据查询,从而达到批量查询+分片的效果。

2)DataLoader调度问题

GraphQL-Java对DataLoader的集成支持的实现在FieldLevelTrackingApproach中,FieldLevelTrackingApproach的实现会存在怎样的问题呢?下面基于一张图表达原生DataLoader调度机制所产生的问题:

问题很明显,基于FieldLevelTrackingApproach的实现,下一层级的DataLoader的dispatch是需要等到本层级的结果都回来之后才发出。基于这样的实现,查询总耗时的计算公式等于:TOTAL = MAX(Level 1 Latency)+ MAX(Level 2 Latency)+ MAX(Level 3 Latency)+ … ,总查询耗时等于每层耗时最大的值加起来,而实际上如果链路编排由业务开发同学自己来写的话,理论上的效果是总耗时等于所有链路最长的那个链路所耗的时间,这个才是合理的。而FieldLevelTrackingApproach的实现所表现出来的结果是反常识的,至于为什么这么实现,目前我们理解可能是设计者基于简单和通用方面的考虑。

问题在于以上的实现在有些业务场景下是不能接受的,比如我们的列表场景的响应时间约束一共也就不到100ms,其中几十ms是因为这个原因搭进去的。针对这个问题的解决思路,一种方式是对于响应时间要求特别高的场景独立编排,不采用GraphQL;另一种方式是在GraphQL层面解决这个问题,保持架构的统一性。接下来,介绍一下我们是如何扩展GraphQL-Java执行引擎来解决这个问题的。

3)DataLoader调度优化

针对DataLoader调度的性能问题,我们的解决思路是在最后一次调用某个DataLoader的load之后,立即调用dispatch方法发出查询请求,问题是我们怎么知道哪一次的load是最后一次load呢?这个问题也是解决DataLoader调度问题的难点,以下举个例子来解释我们的解决思路:

假设我们查询到的模型结构如下:根节点是Query下的字段,字段名叫subjects,subject引用的是个列表,subject下有两个元素,都是ModelA的对象实例,ModelA有两个字段,fieldA和fieldB,subjects[0]的fieldA关联是ModelB的一个实例,subjects[0]的fieldB关联多个ModelC实例。

为了方便理解,我们定义一些概念,字段、字段实例、字段实例执行完、字段实例值大小等等:

  • 字段:具有唯一路径,是静态的,和运行时对象大小没有关系,如:subjects和subjects/fieldA。

  • 字段实例:字段的实例,具有唯一路径,是动态的,跟运行时对象大小有关系,如:subjects[0]/fieldA和subjects[1]/fieldA是字段subjects/fieldA的实例。

  • 字段实例执行完:字段实例关联的对象实例都被GraphQL执行完了。

  • 字段实例值大小:字段实例引用对象实例的个数,如以上示例,subjects[0]/fieldA字段实例值大小是1,subjects[0]/fieldB字段实例值大小是3。

除了以上定义之外,我们的业务场景还满足以下条件:

  • 只有1个根节点,且根节点是列表。

  • DataLoader一定属于某个字段,某个字段下的DataLoader应该被执行次数等于其下的对象实例个数。

基于以上信息,我们可以得出以下问题分析:

  • 在执行字段实例的时候,我们可以知道当前字段实例的大小,字段实例的大小等于字段关联DataLoader在当前实例下需要执行load的次数,因此在执行load之后,我们可以知道当前对象实例是否是其所在字段实例的最后一个对象。

  • 一个对象的实例可能会挂在不同的字段实例下,所以仅当当前对象实例是其所在字段实例的最后一个对象实例的时候,不代表当前对象实例是所有对象实例中的最后一个,当且仅当对象实例所在节点实例是节点的最后一个实例的时候才成立。

  • 我们可从字段实例大小推算字段实例的个数,比如我们知道subjects的大小是2,那么就知道subjects字段有两个字段实例subjects[0]和subjects[1],也就知道字段subjects/fieldA有两个实例,subjects[0]/fieldA和subjects[1]/fieldA,因此我们从根节点可以往下推断出某个字段实例是否执行完。

通过以上分析,我们可以得出,一个对象执行完的条件是其所在的字段实例以及其所在的字段所有的父亲字段实例都执行完,且当前执行的对象实例是其所在字段实例的最后一个对象实例的时候。

基于这个判断逻辑,我们的实现方案是在每次调用完DataFetcher的时候,判断是否需要发起dispatch,如果是则发起。另外,以上时机和条件存在漏发dispatch的问题,有个特殊情况,当当前对象实例不是最后一个,但是剩下的对象大小都为0的时候,那么就永远不会触发当前对象关联的DataLoader的load了,所以在对象大小为0的时候,需要额外再判断一次。

根据以上的逻辑分析,我们实现了DataLoader调用链路的最优化,达到理论最优的效果。

6. 新架构对研发模式的影响

生产力决定生产关系,元数据驱动信息聚合架构是展示场景搭建的核心生产力,而业务开发模式和过程是生产关系,因此也会随之改变。下面我们将会从开发模式和流程两个角度来介绍新架构对研发带来的影响。

6.1 聚焦业务的开发模式

新架构提供了一套基于业务抽象出的标准化代码分解约束。以前开发同学对系统的理解很可能就是“查一查服务,把数据粘在一起”,而现在,研发同学对于业务的理解及代码分解思路将会是一致的。比如展示单元代表的是展示逻辑,取数单元代表的是取数逻辑。同时,很多冗杂且容易出错的逻辑已经被框架屏蔽掉了,研发同学能够有更多的精力聚焦于业务逻辑本身,比如:业务数据的理解和封装,展示逻辑的理解和编写,以及查询模型的抽象和建设。如下示意图所示:

6.2 研发流程升级

新架构不仅仅影响了研发的代码编写,同时也影响着研发流程的改进,基于元数据架构实现的可视化及配置化能力,现有研发流程和之前研发流程相比有了明显的区别,如下图所示:

以前是“一杆子捅到底”的开发模式,每个展示场景的搭建需要经历过从接口的沟通到API的开发整个过程,基于新架构之后,系统自动具备多层复用及可视化、配置化能力。

情况一:这是最好的情况,此时取数功能和展示功能都已经被沉淀下来,研发同学需要做的只是创建查询方案,基于运营平台按需选择需要的展示单元,拿着查询方案ID基于查询接口就可以查到需要的展示信息了,可视化、配置化界面如下示意图所示:

情况二:此时可能没有展示功能,但是通过运营平台查看到,数据源已经接入过,那么也不难,只需要基于现有的数据源编写一段加工逻辑即可,这段加工逻辑是非常爽的一段纯逻辑的编写,数据源列表如下示意图所示:

情况三:最坏的情况是此时系统不能满足当前的查询能力,这种情况比较少见,因为后端服务是比较稳定的,那么也无需惊慌,只需要按照标准规范将数据源接入进来,然后编写加工逻辑片段即可,之后这些能力是可以被持续复用的。

7. 总结

商品展示场景的复杂性体现在:场景多、依赖多、逻辑多,以及不同场景之间存在差异。在这样的背景下,如果是业务初期,怎么快怎么来,采用“烟囱式”个性化建设的方式不必有过多的质疑。但是随着业务的不断发展,功能的不断迭代,以及场景的规模化趋势,“烟囱式”个性化建设的弊端会慢慢凸显出来,包括代码复杂度高、缺少能力沉淀等问题。

本文以基于对美团到店商品展示场景所面临的核心矛盾分析,介绍了:

  • 业界不同的BFF应用模式,以及不同模式的优势和缺点。

  • 基于GraphQL BFF模式改进的元数据驱动的架构方案设计。

  • 我们在GraphQL实践过程中遇到的问题及解决思路。

  • 新架构对研发模式产生的影响呈现。

目前,笔者所在团队负责的核心商品展示场景都已迁入新架构,基于新的研发模式,我们实现了50%以上的展示逻辑复用以及1倍以上的效率提升。希望本文对大家能够有所帮助。

8. 参考文献

  1. https://samnewman.io/patterns/architectural/bff/

  2. https://www.thoughtworks.com/cn/radar/techniques/graphql-for-server-side-resource-aggregation

  3. 了解电商后台系统,看这篇就够了

  4. 框架定义-百度百科

  5. 高效研发-闲鱼在数据聚合上的探索与实践

  6. 《系统架构-复杂系统的产品设计与开发》

2021-05-09-TSYWbU
2021-05-09-WytqXL
2021-05-09-UbCKUx
2021-05-09-GRReTO
2021-05-09-V7EPXO
2021-05-09-dCvsll
2021-05-09-IUY6du
2021-05-10-CQbzmb
2021-05-10-bzzrbQ
2021-05-10-Mfq49L
2021-05-10-NessgO
2021-05-10-HLZ5be
2021-05-10-PgEoYq
2021-05-10-1yG6fa
2021-05-10-iIx1ln
2021-05-10-d53Q4Q
2021-05-10-9RgnNV
2021-05-10-tGl8LN
2021-05-10-gPAXJK
2021-05-10-O17dty
2021-05-10-UPOR5w
2021-05-10-OBz9p5
2021-05-10-hcaYSZ
2021-05-10-YAOozt
2021-05-10-kMhWHa