设计学习思考

2017/12/15

平台架构理论

结合《Java工程师修炼之道》读书理论。

后端基础设施

  1. 请求统一入口–API网关
    • 负载均衡
    • API访问权限控制
    • 用户鉴权

一般是Nginx做负载均衡,然后在每个业务应用里做API接口的访问权限控制和用户鉴权,更优化一点的方式是把后两者做成公共类库供所有业务调用。API网关:将三者需求集成到一个服务,比如开源的Kong。

但是API网关一个问题是所有API请求都要经过网关,它很容易成为系统性能瓶颈。因此,可以让业务应用直接对接统一认证中心,在基础框架层面保证每个API调用都需要先通过认证中心的认证,这里可以采取缓存认证结果避免认证中心产生过大的请求压力。

  1. 缓存、数据库、搜索引擎、消息队列
    • 缓存:通常被用来解决热点数据的访问问题,提供数据查询性能。
    • 数据库:主流关系型数据库Mysql和PostgreSQL,NoSQL HBase用于大数据领域的列数据库,一般不用于业务数据库。
    • 搜索引擎:针对全文检索以及数据各种维度查询设计的软件,Solr和Elasticsearch,它们都是基于Lucene实现的。
    • 消息队列:数据传输的一种方式,包括为日志设计的Kafka以及重事务的RabbitMQ等。
  2. 统一认证中心
    • 用户注册、登录验证、Token鉴权
    • 内部信息系统用户的管理和登录鉴权 能够集中对所有平台的信息进行管理,提供统一的认证服务。尤其在很多业务需要共享用户数据的时候。
  3. 单点登录系统 只需要一次用户登录,就能够进入多个业务应用(权限可以不相同)。

  4. 统一配置中心 在Java后端应用中,通常将配置文件写在Properties等文件中,修改的时候只需要更新文件重新部署即可。统一配置中心是对所有业务的相关配置文件进行管理的服务,能够在线动态修改配置并生效,配置文件可以区分环境(开发、测试、生产等),在Java中可以通过注解、XML配置的方式引入相关配置。百度开源的Disconf或者基于Zookeeper实现的配置存储方案。

  5. 服务治理框架 外部调用服务端API一般都是通过HTTP协议或者Socket,内部服务之间的调用,一般都是通过RPC机制进行的。目前主流的RPC协议有:RMI、Hessian、Thrift、Dubbo。当系统服务逐渐增多,RPC调用链越来越复杂时。就需要有一个服务来管理这些服务。

传统ESB企业总线本质上也是一个服务治理方案,但是ESB是作为一种Proxy角色存在于Client和Server端,所有请求都要经过ESB,ESB很容易成为性能瓶颈。

新的一种以配置中心为枢纽的服务治理方案,调用关系只存在于Client和提供服务的Server之间。这种服务治理方案需要支持:

  • 服务提供方的注册、管理
  • 服务消费者的注册、管理
  • 服务的版本管理、负载均衡、流量控制、服务降级、资源隔离
  • 服务的容错、熔断

阿里开源的Dubbo目前针对以上做了很好的实现。

  1. 统一调度中心 定时调度,比如定时去抓取数据、定时刷新订单的状态等,通常做法是针对各自业务,依赖Linux的Cron机制或者Java中的Quartz来实现。统一调度中心是对所有的调度任务进行管理。

  2. 统一日志服务 日志是线上服务能够定位、排查异常最直接的来源。统一日志服务使用单独的日志服务器记录日志,各业务应用通过统一的日志框架将日志输出到日志服务器上。可以通过实现Log4j或者LogBack的appender来实现统一日志框架,然后通过RPC调用将日志打印到日志服务器上。

  • 日志收集:场景日志收集方案有Kafka和Flume,以及Logstash,更倾向于数据的预处理 ELK架构。
  • 日志传输:通过消息队列将数据传输到数据处理服务中。通常选择Kafka。

还有一个就是数据库和数据仓库间的数据同步问题,将需要分析的数据从数据库同步到诸如Hive这种数据仓库。可以使用Apache Sqoop进行基于时间戳的数据同步,以及阿里开源的Canal基于Binlog的增量同步。

离线数据分析,数据可以有延迟,常用的离线数据分析技术有Hadoop和Spark。

实时数据分析,对数据有实时要求的业务场景,如广告结算、订单结算等。常用的技术有Storm和Spark Streaming。Spark Streaming是基于批量计算的,如果对延迟很敏感的场景,应该使用Storm。最近比较或的有Flink分布式实时计算框架。

实时数据处理一般是基于增量处理的,但是一旦出现故障或数据处理失败,相对于离线来说并不是很靠谱。因此结合离线+实时是目前比较普遍的数据处理方案,有Lambda架构。实时数据分析中还有一个比较常见的场景:多维数据实时分析,即能够组合任意维度进行数据展示和分析,ROLAP和MOLAP方案。

  1. 故障监控 将故障监控分为两个层面:
    • 系统监控:主要指针对主机的带宽、I/O、CPU、内存、硬盘等硬件资源的监控,可以使用Nagios、Cacti等开源监控软件。
    • 业务监控:包括监控和告警。

Java后端技术概览

1.软件开发的核心原则

  • 不做重复性劳动
  • 不要过度设计和过早优化,这样有利于后续的维护和进一步的扩展
  • 只添加必要的功能到应用中
  • 开发最佳思路就是先把东西做出来,再去迭代优化。如果一开始就面面俱到,考虑到各种细节,那么很容易转牛角尖而延误项目进度。
  • 技术选型很关键,不能盲目求新,一定要选择最合适的。合适自己、合适团队、合适公司目前阶段的。
  1. 计算机基础科学知识
    • 数据结构,经典的数据结构有字符串、数组、链表、哈希表、树(二叉树、平衡树、红黑树、B树)、堆栈、队列、图。
    • 算法:排序和查找,冒泡排序、插入排序、选择排序、归并排序、快速排序、希尔排序、堆排序以及二分查找等。在函数/方法中要注意递归和迭代各自的优缺点。衡量算法性能的空间复杂度和时间复杂度。
    • 业务相关算法,压缩算法、LRU缓存算法、缓存一致性、编译原理中的状态机等。
    • 计算机网络,HTTP网络协议,基于HTTP的HTTPS安全协议。业务层面,基于HTTP协议的RESTful规范,OAuth2.0对外开放平台协议。SMTP基于TCP/IP的应用协议。
    • 设计模式,能够是软件可重用、可扩展、可维护。经典的工厂模式、简单工厂模式、单例模式、观察者模式、代理模式、建筑者模式、门面模式、适配器模式、装饰器模式。

知其然更要知其所以然!“逃离舒适区”,自我总结

Java项目与工程化

  1. 主流构建工具–Maven,“约定优于配置”,约定好的一些规范无需再配置。

数据存储

关系数据库-MySQL,主要两种存储引擎,MyISAM和InnoDB。 MyISAM: 1)不支持事务 2)不支持行锁

架构设计

工程结构

  1. 在细分的时候,controller和api层是两个平行的层,都去调用service层,不要使用controller调用api层;
  2. controller的方法上的注解最好使用PostMapping、GetMapping;少使用RequestMapping,Spring5以后RequestMapping有些的参数赋值不支持;
  3. 分层由上到下 API==>Service==>Manager==>DAO 层。

前后端分离

对于并发量较大的应用,可以将前后端分离开,这样对于前端的资源就可以使用nginx等效率高的服务器,并且数据是在前端渲染,不是在服务端通过jsp、freemarker等渲染后返回前端。相当于把原本服务端处理的任务分散到用户端浏览器,可以很大程度的提高页面响应速度。前后端分离主要考虑的应该就是跨域的问题了,对于跨域主要考虑以下场景:

  • 不跨域,建议使用这种方式。主要实现是将js、css、图片等静态资源放到CDN,使用nginx反向代理来区分html(或使用nodejs的服务端)和服务端数据的请求。这样能够保证前端和后端的请求都在同一个域名之下。这样做的好处主要是不用考虑跨域的问题,也可以避免跨域的一些坑,以支持更多的场景(比如RESTful)。采用这种方式的麻烦点主要是涉及到CDN、nginx、应用服务器的配置,所以在版本升级的时候需要有一些自动化的工具来提高效率、避免手动出现一些错误,并且要有一个较好的机制来保障版本升级的兼容性,比如CDN中的资源可以考虑在请求路径上增加一个版本号(在 动静分离 中也会提到)
  • 服务端跨域。主要是在服务端配置以支持跨域请求。这种主要需要考虑的是服务端的权限处理,因为跨域默认是不会将访问的域的cookie传到服务端的,所以需要其他方式来传递一个请求的标志,用来控制权限
  • 客户端跨域。这种方式不太适合业务类型系统,主要适用于一些公开的服务,比如:天气查询、手机号归属地查询等等

动静分离

动静分离主要也是对于性能上的优化措施,不同人对于动静分离的理解不一样,主要有以下两种

  • 动态数据和静态资源分离。主要是指将静态资源(如:js、css、图片等)放到CDN,这样可以提高静态资源的请求速度,减少应用服务器的带宽占用。需要注意的是,因为使用了CDN,当版本升级的时候可以考虑在静态资源的访问路径上加一个版本号,这样升级之后可以避免CDN不刷新的问题,如果是APP应用,可以避免版本不兼容的问题,所以就需要在部署环节做一些自动化的工具,避免人工操作出现失误
  • 服务端根据动态资源生成对应的静态资源,用户访问的始终是静态资源。这种比较常见于CMS(内容管理系统)、博客等类型的应用。主要方式是提前根据动态数据生成对应的静态资源(即html静态页面),这样用户访问的时候就直接访问html页面了,可以较大程度的提高访问速度。这种方式主要适合数据变化不太频繁的场景

避免过度设计

  • 避免因为少数极端情况做过多处理
  • 避免过度拆分微服务,尽量避免分布式事务
  • 慎用前后端分离,比如一些内部管理型的使用量不高的应用,是没必要做前后端分离的

数据预先处理

对于一些业务场景,可以提前预处理一些数据,在使用的时候就可以直接使用处理结果了,减少请求时的处理逻辑。如对于限制某些用户参与资格,可以提前将用户打好标记,这样在用户请求时就可以直接判断是否有参与资格,如果数据量比较大,还可以根据一定规则将数据分布存储,用户请求时也根据此规则路由到对应的服务去判断用户参与资格,减轻单节点压力和单服务数据量,提高整体的处理能力和响应速度。

补偿机制

对于一些业务处理失败后需要有补偿机制,例如:重试、回退等

  • 重试需要限制重试次数,避免死循环,超过次数的需要及时告警,以便人工处理或其他处理。重试就需要保证幂等性,避免重复处理导致的不一致的问题
  • 回退。当超过重试次数或一些处理失败后,需要回退的,需要考虑周全一些,避免出现数据不一致的情况

幂等性

在实际处理中可能会出现各种各样的情况导致重复处理,就需要保证处理的幂等性,一般可以使用全局唯一的流水号来进行唯一性判断,避免重复处理的问题,主要是在MQ消息处理、接口调用等场景。全局唯一的流水号可以参考tweeter的snowflake算法【sequence-spring-boot-starter】。具体生成的位置就需要根据实际业务场景决定了,主要是需要考虑各种极端的异常情况。

架构方案

  • 单进程,基于事件驱动和无阻塞 I/O,所以非常适合处理并发请求
  • 负载均衡:cluster 模块 / PM2
  • 提供服务接口给客户端
  • 接口不直接 DB 操作,保证并发下的稳定性
  • 数据异步入库
  • 通过程序把数据从:消息队列 =>mysql

高并发优化

高并发除了需要对服务器进行垂直扩展和水平扩展之外,作为后端开发可以通过高并发优化,保证业务在高并发的时候能够稳定的运行,避免业务停滞带来的损失,给用户带来不好的体验

缓存

服务端缓存

内存数据库

  • redis
  • memcache

方式

  • 优先缓存
  • 穿透 DB 问题
  • 只读缓存
  • 更新 / 失效删除

注意

  • 内存数据库的分配的内存容量有限,合理规划使用,滥用最终会导致内存空间不足
  • 缓存数据需要设置过期时间,无效 / 不使用的数据自动过期
  • 压缩数据缓存数据,不使用字段不添加到缓存中
  • 根据业务拆分布式部署缓存服务器
客户端缓存

方式

  • 客户端请求数据接口,缓存数据和数据版本号,并且每次请求带上缓存的数据版本号
  • 服务端根据上报的数据版本号与数据当前版本号对比
  • 版本号一样不返回数据列表,版本号不一样返回最新数据和最新版本号

场景:

  • 更新频率不高的数据

Post Directory