zoukankan      html  css  js  c++  java
  • 分布式系统学习笔记(2)-大型网站架构

    做网站,特别是在很多小公司或则小项目,现在几乎是一个纯体力活了。 得益于JavaEE体系的发展,对于小型网站,几乎都是一些耳熟能详的东西:

    • 选择一个开源的Server作为容器,一般就是tomcat
    • 页面直接使用Jsp/servlet编写
    • 选择一个视图层框架,比如SpringMVC
    • 选择一个ORM框架, MyBatis/Hibernate
    • 选择一个的数据库,一般就是MySQL
    • 一台主机。或则阿里云虚拟机

    如上,这样一个网站就可以运行起来了,对于低访问量或则业务逻辑简单的网站来说,几乎是标配。 但是对于程序开发者来说,很多都是做的CRUD操作
    ,几乎没有什么难度。
    比如我们现在要编写一个简易的购物网站, 支持最起码的基本功能:

    • 用户
      • 用户注册
      • 用户管理
      • 信息维护
      • 。。。
    • 商品
      • 商品展示
      • 商品管理
      • 。。。
    • 交易
      • 创建交易
      • 交易管理
      • 。。。

    而我们的架构演变可能如下:

    1. 在传统架构中,如下的架构是最常用的:

      各个功能模块之间直接通过Java方法通信,应用和数据库通过JDBC或者相应框架进行访问。

    2. 随着访问量不断增大,一台机器上运行服务器和数据库的压力逐渐增大,我们把数据库移到了另外一台电脑上。对于系统的影响也仅仅是JDBC的连接地址修改下。

    3. 应用服务器负载达到极限。
      对于应用服务器的优化可以根据项目类型、监测结果等做多种优化。 比如软件上优化代码,提高效率,JVM调优等等。 这里仅谈使应用服务器走向集群(这也肯定是要走的路,单机的容量始终有限)
      我们引用了如下的架构:

      应用服务器现在从一台变成了两台,他们之间没有直接的交互。都依赖数据库已提供服务。此时,我们有两个问题需要解决:
      3.1 用户访问网站时,对应用服务器的选择问题。 这里可以通过DNS解决, 也可以再在应用服务器前面添加负载均衡设备来解决。 这里选用第二种。
      采用了负载均衡过后,系统架构如下:

      3.2 Session的问题

      Sessioc的介绍如下:

      Session:在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在 Session 对象中。有关使用 Session 对象的详细信息,请参阅“ASP 应用程序”部分的“管理会话”。注意 会话状态仅在支持 cookie 的浏览器中保留

      当我们的应用服务器从一台切换到两台时,就会遇到session的问题了:
      当一个带有会话标识的HTTP请求到了Web服务器后,需要在HTTP请求的处理过程中找到对应的Session,但是,session需要保存在单机上。 如果用户两次访问网站,被分配到了不同的应用服务器,这时Session肯定不同,就会出现问题。
      对于该问题,有如下几种解决方案:

      • Session Sticky
        保证同一个会话在同一个web服务器上。 这需要负载均衡器根据每次请求的会话标识来决定转发到的应用服务器, 这个策略只要保证对于同一个会话转发到同一个应用服务器即可。
        该方式本身非常简单,也可以保证同样session请求都在同一个应用服务器上处理,非常有利于针对Session进行服务器端本地的缓存。但是,有如下的问题:
        • 如果某台应用服务器宕机或则重启,那么会话数据就会丢失。 如果session中有登录状态数据等,用户就需要重新登录
        • 负载均衡器处理应用层(会话标识)数据的代价大
        • 负载均衡变成了一个有状态的节点,需要保存session与应用服务器的映射。 内润消耗会增大,容灾也更麻烦。
      • Session Replication
        应用服务器之间进行session同步。

        这种方式没有影响负载均衡器,不再要求负载均衡器转发到对应的应用服务器了。但是应用服务器则增加了一个功能: session数据同步。 一般的应用容器都支持session Replication方式。该方式也有一些问题:
        • session同步带来了网络开销。 机器越多,开销越大。
        • 应用服务器需要保存所有的session数据,如果同时登陆访问的人很多,那么每台机器用户保存session占用的空间也会很大
          所以说对于机器不多的场景,该方案还是简单可行的,但是机器数量多了,还是不是很合适。
      • Session集中存储
        该方案也非常易于理解: 专门找个地方存储session,这样不会浪费空间也不会影响负载均衡器。 应用服务器从该session集中存储处获取相应的session.

        该方案使得session与应用服务器互相独立,对于session的存储可以采用多种方式(数据库、其他分布式存储系统)等。 给整个系统带来了较大的灵活性,不同应用服务器的session也可以保证相同。
        同样的,该方案会有如下两个问题:

        • 读写session会受到网络的影响
        • 存储session的集群可能出现问题,会影响所有的应用。
      • Cookie Based
        把session数据放在cookie中,相对于集中式存储不依赖外部存储系统使用也没有网络开销。

        当然,该方案也会有如下问题:

      • cookie长度的限制。 这也间接限制了session的长度。
      • 安全性。 session数据本来就是服务端数据,而现在我们把它放到了客户端中。即使说我们可以对该数据加密,但依然会存在安全性上的问题。 这个可以搜索下就会发现安全问题不在少数
      • 带宽消耗。 数据中心的整体外部带宽的消耗
      • 性能影响。 cookie使得每次请求和响应都带有session数据, 毫无疑问响应的数据少服务器支持的并发数量就会更多。
    4. 数据库服务器达到极限
      随着业务的发展,可能应用服务器的数量在增多,数据库服务器也渐渐撑不住了。 对于大型网站来说,有不少业务是读多于写,这时可以考虑读写分类的方式。

      这个结构我们加入了一个读库,该库仅承担读服务。 显而易见的两个问题:

      • 数据复制问题
      • 应用对数据库的选择问题

      我们希望通过读库来减少主库的压力,那么首先需要解决数据怎么复制到读库的问题。数据库系统自身一般提供了数据复制的功能,但是考虑到数据复制延时问题(比如用户修改了信息但是由于延时没有复制到读库上,用户就会以为没有修改成功),以及数据源和目标之间的映射关系及过滤条件的支持问题。 同时,不同的数据库,甚至同一个数据库不同版本对于数据负责提供的支持是不相同的。 这部分很明显应该使用一中间件来完成这个工作。
      对于应用来说,需要根据不同的数据情况选择不同的数据源,也要考虑到读库相对于主库的延迟问题。 所以不同业务下的选择会有差别。
      同时,大型网站的站内搜索也可以理解为一个读库。 纯粹使用数据库进行查找也是可行的,比如like指令,但是这个代价很大。搜索引擎需要根据被搜索的内容动态的添加索引, 索引需要随着源数据的变化而更新。 加入了搜索引擎后,应用也需要知道什么数据应该走搜索什么数据应该走数据库。

      可以看到,搜索集群和读库的使用方式是一样的,只不过我们需要自己建立索引。 可以通过全量、增量方式划分索引构建方式。
      缓存
      缓存,耳熟能详的是CPU的缓存,也就是常见的L1/L2/L3什么的,用于CPU和内存之间的一个过渡性缓存。 而对于网站架构这个概念来讲,大概可以分为两种: 数据缓存和页面缓存。通过缓存较少
      一些非常消耗CPU和IO的操作,对于数据库减压也是非常重要的。

      • 数据缓存
        大型系统中的数据缓存主要用于分担数据库的读压力,从目的上看,类似前面的分库和搜索引擎。

        缓存中存放的数据是有限的,由“业务”层自主选择需要存储的数据,即应用请求某个功能,缓存层会拦截这个请求,然后通过一定的规则来决定是否调用实际的方法还是直接返回已经保存的数据
        。当缓存容量不够时,再按照一定的规则删除数据(一般就是最近最少使用的,LMU)。

      • 页面缓存
        数据缓存可以加快应用的某个请求的处理的速度,但是最终还是需要需要返回页面给用户。 而对于某些动态产生的页面(如JSP),每次都需要使用数据重新渲染,那么可以对这部分内容进行缓存。
        这个缓存层可以放在我们的前置服务器如Apache/Nginx中。 取决于具体的 策略。

      弥补关系型数据库的不足,引入分布式存储系统
      前面的介绍中用于数据存储的主要是数据库,但是某些场景下用数据库不是很合适。 某些数据可能无法枚举或则就只是一些单纯的KEY-VALUE键值又或则是一些小文件等等。
      除了数据库之外,还有其他的存储系统可供选择。
      常见的分布式存储系统有分布式文件系统 、分布式key-value系统、分布式数据库。
      分布式文件系统就是在分布式环境中有多个节点组成的、功能与单机文件系统一样的文件系统 ,它是弱格式的,格式需要使用者自己组织。 常见的分布式文件系统比如淘宝的TFS ,GFS ,这连个是用来存储小文件和大文件的。 还有比如分布式key-value系统来提供半结构化支持的数据库如MongoDB之类
      分布式存储系统自身起到了存储的作用,也就是提供数据的读写支持。相对于读写分离中的“读”库,分布式文件系统直接代替了主库。同时,肯定也带有分布式的特性,通过集群提供一个高容量,高并发,
      数据冗余容灾 的支持。

    5. 读写分离后,数据库依然达到瓶颈
      通过读写分离以及在某些场景采用分布式存储系统过后,能够较大的提升系统的承载能力。不过随着业务增加,主库依然会存在扛不住的情况。 此时,我们的交易、商品、用户等信息都还存储在同一个数据库
      中。解决这个问题我们还可以把数据库进行水平拆分和垂直拆分。

      5.1 专库专用,数据垂直拆分
      垂直拆分的意思就是把现有的单数据库按照不同的业务模型拆分为不同的数据库。

      这样的架构对应用带来了较大的影响: 应用需要配置个数据源,同时,事物也从单机变为分布式的。 同时,原先使用的表关联查询也需要改变实现。 同时,这也使得针对不同的业务特点进行针对性的优化变得
      更加简单。

      5.2 数据水平拆分
      数据水平拆分就是把同一个数据表的数据拆分到两个数据库中。产生数据水平拆分的原因是某个业务的某个表的数据量达到了单个数据库的瓶颈。 水平拆分解决的问题是 “ 数据量大”。 而垂直拆分解决的是“读压力大”

      这对于应用的影响有多个方面,SQL需要路由,比如在操作用户信息时需要知道需要操作的数据在哪个数据库中。同时,主键的处理也变得不同,不能再像之前一样使用简单的使用数据库的自增长字段了。同时在不同的数据库
      也不能直接使用一些数据库的限制来保证主键的唯一性。 同时,当遇到需要查询一些从两个数据库中读取数据的情况,又需要分页就变得比较难处理。

    6. 拆分应用
      在前面已经涉及到了读写分离、分布式存储、数据垂直水平拆分、缓存等着重解决数据访问的问题。 但是随着业务的继续发展,应用的功能会越来越多,而此时所有的应用都还在一个“项目”上、无论是从软件工程的可维护角度还是性能角度讲,应用都需要拆分开来。 对于应用拆分,有如下两种方式:
      6.1 根据业务的特性把应用拆开。 在上面的例子中,主要的业务功能分为交易、商品和用户。 我们可以把原来的一个应用拆分为交易和商品两个应用。对于交易和商品都有涉及用户的地方,我们这两个系统自己完成涉及用户的操作。而类似用户注册、登录等基础的用户工作,可暂时由任意一个完成。

      这样拆分过后,不同的小应用都会依赖下面的数据库、缓存、文件系统、搜索等。 这样的方式可能会带来代码重复性、不同应用的一致性等问题。
      6.2 服务化
      我们把应用分为三层,最上层的Web系统,用于完成不同的业务功能,处于中间的服务中心,不同的服务中心提供不同的功能,处于最下层的则是业务的数据库缓存等。

      这种方式相比于上面6.1的按业务场景分类很大的不同是,业务功能之间的范围不是单机内部的方法调用还引入了远程的服务调用。 其次,共享的代码不再是不同的应用中,这些实现被放在了各个服务中心。最后,数据库的连接一杯放到了服务中,使得前端的web应用更加注重与浏览器交互的工作,而不必过多关注业务逻辑的问题。 服务中心收集了之前散落的各个业务中的功能点,使得更好维护和调试。

      下一章节将转到Java并发编程基础=_=

    I see I come I conquer.
  • 相关阅读:
    Jenkins分享
    Java静态绑定和动态绑定
    SpringBoot中RedisTemplate订阅发布对象
    Idea项目:Failed to create a Maven project ‘…pom.xml’ already exists in VFS 解决
    Java Web不能不懂的知识
    Required String parameter 'id' is not present
    Hive使用druid做连接池代码实现
    Docker Toolbox常见错误解决方案
    初学者手册-MyBatis踩坑记(org.apache.ibatis.binding.BindingException)
    SpringMVC日志管理(自定义异常及自定义注解)
  • 原文地址:https://www.cnblogs.com/yTPety/p/6762870.html
Copyright © 2011-2022 走看看