大型网站系统架构的演进都是随着业务增长不断演进,所有的出发点都是为了满足业务需求。最初访问量下,功能简单时,单体软件可以解决所有问题;后来访问量逐渐增大,功能愈加丰富,此时单体软件的架构逐渐成为开发和运维的瓶颈。所以微服务拆分,集群化部署,消息中间件,内存数据库,数据库中间件等解决方案逐渐走进视野。
下图为简略版的Web系统架构,本文围绕此图展开,简要介绍其中涉及组件的功能和应用场景。
![]()
回到最初,Web项目的源头几乎全来源于用户的请求(此处忽略运维相关的定时监控等任务),当海量的请求并发量超过Tomcat服务器的峰值时,Tomcat的集群化应运而生,与之同时反向代理组件Ngnix走进视野。Ngnix本身作为入口,其稳定性是关键因素,所以利用keepalived实现高可用是正常思维。
综上,可以看到集群化的目的在于解决单服务能力不足的问题,高可用的机器备份方案是为了实现系统的稳定运行。但是,在集群化时又引入了负载均衡,高可用时怎样实现异常故障自动切换等技术问题,这里均不深入讨论,仅引入一个概念基础。另,每一种组件有很多相关的产品,这里仅针对图中列出的组件进行介绍。
拆分后的微服务部署在不同的机器上,服务间如何通信实现业务调用?先解释一个概念,调用有同步和异步两种,同步是在调用时发起方会阻塞线程等待调用结果返回后再往下执行,异步调用是调用方按约定将消息发送出去,不关注调用的执行结果,两者适用的场景不同。对应到上图中同步调用的方案为Dubbox,消息中间件ActiveMQ为异步调用。
Dubbox 是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,Dubbox 就是个服务框架,只有在分布式的时候,才有Dubbox 这样的分布式服务框架的需求,起本质上是个远程服务调用的分布式框架。
![]()
节点角色说明:
· Provider: 暴露服务的服务提供方。
· Consumer: 调用远程服务的服务消费方。
· Registry: 服务注册与发现的注册中心。
· Monitor: 统计服务的调用次调和调用时间的监控中心。
· Container: 服务运行容器。
调用关系说明:
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbox推荐使用 zookeeper 注册中心。注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。所以在服务集成时需要同步部署Zookeeper。
消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。对于消息中间件,常见的角色为Producer(生产者)、Consumer(消费者)。
ActiveMQ 是Apache的开源项目,是能力强劲的消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的JMS Provider实现。
什么是JMS?
JMS(Java Messaging Service)是Java平台上有关面向消息中间件的技术规范,它便于消息系统中的Java应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口简化企业应用的开发。
JMS本身只定义了一系列的接口规范,是一种与厂商无关的 API,用来访问消息收发系统。它类似于 JDBC(java Database Connectivity):这里,JDBC 是可以用来访问许多不同关系数据库的 API,而 JMS 则提供同样与厂商无关的访问方法,以访问消息收发服务。许多厂商目前都支持 JMS,包括 IBM 的 MQSeries、BEA的 Weblogic JMS service和 Progress 的 SonicMQ,这只是几个例子。 JMS 使您能够通过消息收发服务(有时称为消息中介程序或路由器)从一个 JMS 客户机向另一个 JML 客户机发送消息。消息是 JMS 中的一种类型对象,由两部分组成:报头和消息主体。报头由路由信息以及有关该消息的元数据组成。消息主体则携带着应用程序的数据或有效负载。
JMS规定的消息类型有两种:点对点(一对一),发布/订阅模式(一对多),我们在使用消息中间件时只需遵从JMS提供的操作接口进行开发。
ActiveMQ集群化有两种方式Master/Slave(ActiveMQ5.10开始支持使用Zookeeper搭建集群),Broker Cluster。
- Master/Slave实现了主从复制,实现高可用;
- Broker Cluster实现了负载均衡。
可以将两种方式结合起来同时实现负载均衡和高可用。
为了进一步缓解服务集群的压力,可以将高频访问量大或者资源消耗严重的服务单独拆分出来,如图中的搜索和文件服务。
SolrCloud是Solr提供的分布式搜索方案,当你需要大规模,容错,分布式索引和检索能力时使用SolrCloud。当一个系统的索引数据量少的时候是不需要使用SolrCloud的,当索引量很大,搜索请求并发很高,这时需要使用SolrCloud 来满足这些需求。
SolrCloud 是基于Solr和Zookeeper的分布式搜索方案,它的主要思想是使用 Zookeeper作为集群的配置信息中心。
它有几个特色功能:
1)集中式的配置信息
2)自动容错
3)近实时搜索
4)查询时自动负载均衡
问题来了,何为Solr?
Apache Solr是一个流行的开源搜索服务器,它通过使用类似REST的HTTP API,构建搜索应用程序。它建立在Lucene(全文搜索引擎)之上。 Solr是企业级的,快速的和高度可扩展的。 使用Solr构建的应用程序非常复杂,可提供高性能。
Solr可以和Hadoop一起使用。由于Hadoop处理大量数据,Solr帮助我们从大的源中找到所需的信息。不仅限于搜索,Solr也可以用于存储目的。像其他NoSQL数据库一样,它是一种非关系数据存储和处理技术。
使用Solr时需要整合中文分析器,例如IK Analyzer。
IK Analyzer 是一个开源的,基亍 java 语言开发的轻量级的中文分词工具包。IK已发展为面向 Java 的公用分词组件,独立亍 Lucene 项目,同时提供了对 Lucene 的默认优化实现。在2012版本中,IK 实现了简单的分词歧义排除算法,标志着 IK 分词器从单纯的词典分词向模拟语义分词衍化。
Solr如何使用?
我们可以利用Spring将Solr服务集成到项目中。Spring Data Sol就是为了方便Solr的开发所研制的一个框架,其底层是对SolrJ(官方API)的封装。搜索服务的功能开发参考Spring Data Solr相关接口。
FastDFS 是用 c 语言编写的一款开源的分布式文件系统。FastDFS 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。
Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。
Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storage server 没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。可以将Storage称为存储服务器。
![]()
服务端两个角色:
Tracker:管理集群,tracker 也可以实现集群。每个 tracker 节点地位平等。收集 Storage 集群的状态。
Storage:实际保存文件 Storage 分为多个组,每个组之间保存的文件是不同的。每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。
由上图可知,FastDFS集群搭建完成后,我们需要开发client端向服务器发起请求实现文件上传下载功能,相关开发接口在client的jar包中提供如:fastdfs_client_v1.20.jar。
最后,说说数据库在大型软件系统中的改变。
为何要搭建Redis集群。Redis是在内存中以Key-Value格式保存数据的,而电脑内存一般都不大,这也就意味着Redis不适合存储大数据,Redis更适合处理高并发。为了扩展设备的存储能力,搭建服务集群是正常思路。
Redis 3.0之后版本支持Redis-Cluster集群,它是Redis官方提出的解决方案,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。其架构图如下:
![]()
客户端与 Redis 节点直连,不需要中间代理层。客户端不需要连接集群所有节点连接集群中任何一个可用节点即可。所有的 Redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽。
Redis-Cluster分布存储机制-槽
(1)Redis-Cluster 把所有的物理节点映射到[0-16383] slot上,cluster 负责维护node<->slot<->value。
(2)Redis-Cluster中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,Redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个key 都会对应一个编号在 0-16383 之间的哈希槽,Redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
Redis-Cluster容错机制-投票
(1)选举过程是集群中所有master参与,如果半数以上master节点与故障节点通信超过(cluster-node-timeout),认为该节点故障,自动触发故障转移操作。故障节点对应的从节点自动升级为主节点。
(2)什么时候整个集群不可用(cluster_state:fail)?
如果集群任意master挂掉,且当前master没有slave。集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入fail状态.。
为何要使用Redis?
高频高并发的访问数据库会给数据库造成很大的访问压力,甚至是瘫痪。为了解决此问题,我们可以使用Redis将常用数据进行缓存,分流访问流量。
如何使用Redis?
类似Solr,Spring提供了Spring Data Redis框架,在Srping应用中通过简单的配置访问Redis服务。Spring Data Redis对Reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了Redis各种操作、异常处理及序列化,支持发布订阅,并对Spring 3.1 cache进行了实现。
Spring Data Redis针对JRedis提供了如下功能:
1.连接池自动管理,提供了一个高度封装的“RedisTemplate”类。
2.针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口。
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
MyCat是基于cobar(阿里开源产品)演变而来,对 cobar 的代码进行了彻底的重构,使用 NIO 重构了网络模块,并且优化了 Buffer 内核,增强了聚合,Join 等基本特性,同时兼容绝大多数数据库成为通用的数据库中间件。
简单的说,MyCat就是:一个新颖的数据库中间件产品支持MySQL集群,或者mariadb cluster,提供高可用性数据分片集群。你可以像使用MySQL一样使用MyCat。对于开发人员来说根本感觉不到MyCat的存在。
![]()
MyCat分片
分片是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果。
数据的切分(Sharding)根据其切分规则的类型,可以分为两种切分模式。
(1)一种是按照不同的表(或者Schema)来切分到不同的数据库(主机)之上,这种切分可以称之为数据的垂直(纵向)切分。
![]()
(2)另外一种则是根据表中的数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(主机)上面,这种切分称之为数据的水平(横向)切分。
![]()
MyCat分片策略:
![]()
数据库的读写分离:
数据库读写分离对于大型系统或者访问量很高的互联网应用来说,是必不可少的一个重要功能。对于MySQL来说,标准的读写分离是主从模式,一个写节点Master后面跟着多个读节点,读节点的数量取决于系统的压力,通常是1-3个读节点的配置。
![]()
MyCat读写分离和自动切换机制,需要MySQL的主从复制机制配合。