Java工程师进阶:分布式系统

什么是分布式系统
    就是原来20万行代码的系统,现在拆分成20个小系统,每个小系统1万行代码。原本代码之间直接就是基于spring调用,现在拆分开来了,20个小系统部署在不同的机器上,得基于dubbo搞一个rpc调用,接口与接口之间通过网络通信来请求和响应。

拆分后不用dubbo可以吗?
    各个系统之间,直接基于spring mvc,纯http接口互相通信。但是这个肯定是有问题的,因为http接口通信维护起来成本很高,你要考虑超时重试、负载均衡等等各种乱七八糟的问题,比如说你的订单系统调用商品系统,商品系统部署了5台机器,你怎么把请求均匀地甩给那5台机器?这不就是负载均衡?
    dubbo是一种rpc框架,就是本地进行接口调用,但是dubbo会代理这个调用请求,跟远程机器网络通信,给你处理掉负载均衡了、服务实例上下线自动感知了、超时重试了,等等问题。

dubbo工作原理
    第一层:service层,接口层,给服务提供者和消费者来实现的
    第二层:config层,配置层,主要是对dubbo进行各种配置的
    第三层:proxy层,服务代理层,透明生成客户端的stub和服务单的skeleton
    第四层:registry层,服务注册层,负责服务的注册与发现
    第五层:cluster层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
    第六层:monitor层,监控层,对rpc接口的调用次数和调用时间进行监控
    第七层:protocol层,远程调用层,封装rpc调用
    第八层:exchange层,信息交换层,封装请求响应模式,同步转异步
    第九层:transport层,网络传输层,抽象mina和netty为统一接口
    第十层:serialize层,数据序列化层

一次rpc请求的工作流程:
    1)第一步,provider向注册中心去注册
    2)第二步,consumer从注册中心订阅服务,注册中心会通知consumer注册好的服务
    3)第三步,consumer调用provider
    4)第四步,consumer和provider都异步的通知监控中心

注册中心挂了可以继续通信吗?
    可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信

dubbo支持不同的通信协议
    1)dubbo协议
    dubbo://192.168.0.1:20188
    默认就是走dubbo协议的,单一长连接,NIO异步通信,基于hessian作为序列化协议
    适用的场景就是:传输数据量很小(每次请求在100kb以内),但是并发量很高
    为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就100个连接。然后后面直接基于长连接NIO异步通信,可以支撑高并发请求。
    否则如果上亿次请求每次都是短连接的话,服务提供者会扛不住。
    而且因为走的是单一长连接,所以传输数据量太大的话,会导致并发能力降低。所以一般建议是传输数据量很小,支撑高并发访问。
    2)rmi协议
    走java二进制序列化,多个短连接,适合消费者和提供者数量差不多,适用于文件的传输,一般较少用
    3)hessian协议
    走hessian序列化协议,多个短连接,适用于提供者数量比消费者数量还多,适用于文件的传输,一般较少用
    4)http协议
    走json序列化
    5)webservice
    走SOAP文本序列化

dubbo支持的序列化协议
    dubbo实际基于不同的通信协议,支持hessian、java二进制序列化、json、SOAP文本序列化多种序列化协议。但是hessian是其默认的序列化协议。

dubbo的spi思想是什么?
    service provider interface,你有个接口,现在这个接口有3个实现类,那么在系统运行的时候对这个接口到底选择哪个实现类呢?这就需要spi了,需要根据指定的配置或者是默认的配置,去找到对应的实现类加载进来,然后用这个实现类的实例对象。

为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?
    分表:单表数据量太大,会极大影响sql执行的性能,到了后面sql可能就跑的很慢了。一般来说,单表到几百万的时候,性能就会相对差一些了。把一个表的数据放到多个表中,然后查询的时候就查一个表。
    分库:一个库最多支撑到并发2000,而且一个健康的单库并发值最好保持在每秒1000左右,那么可以将一个库的数据拆分到多个库中,访问的时候就访问一个库。

用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?
    cobar:阿里b2b团队开发和开源的,属于proxy层方案。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库join和分页等操作。
    TDDL:淘宝团队开发的,属于client层方案。不支持join、多表查询等语法,就是基本的crud语法是ok,但是支持读写分离。目前使用的也不多,因为还依赖淘宝的diamond配置管理系统。
    atlas:360开源的,属于proxy层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在5年前了。所以,现在用的公司基本也很少了。
    sharding-jdbc:当当开源的,属于client层方案。确实之前用的还比较多一些,因为SQL语法支持也比较多,没有太多限制,而且目前推出到了2.0版本,支持分库分表、读写分离、分布式id生成、柔性事务(最大努力送达型事务、TCC事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从2017年一直到现在,是不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也可以选择的方案。
    mycat:基于cobar改造的,属于proxy层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于sharding jdbc来说,年轻一些,经历的锤炼少一些。

sharding-jdbc这种client层方案的优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合sharding-jdbc的依赖;
mycat这种proxy层方案的缺点在于需要部署,自己及运维一套中间件,运维成本高,但是好处在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。

你们具体是如何对数据库如何进行垂直拆分或水平拆分的?
    水平拆分:把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来抗更高的并发,还有就是用多个库的存储容量来进行扩容。
    垂直拆分:把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较少的访问频率很高的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。
    range拆分:好处在于后面扩容的时候很容易,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用range,要看场景,你的用户不是仅仅访问最新的数据,而是均匀的访问现在的数据以及历史的数据
    hash拆分:好处在于可以平均分配没给库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的这么一个过程

现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?
    停机迁移方案
        大家伙儿凌晨12点开始运维,网站或者app挂个公告,说0点到早上6点进行运维,无法访问,接着到0点,停机,停掉系统,没有流量写入了,此时老的单库单表数据库静止了。然后你之前得写好一个导数据的一次性工具,此时直接跑起来,然后将单库单表的数据哗哗哗读出来,写到分库分表里面去。导数完了之后,就ok了,修改系统的数据库连接配置啥的,包括可能代码和SQL也许有修改,那你就用最新的代码,然后直接启动连到新的分库分表上去。
    双写迁移方案(常用)
        简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,都除了对老库增删改,都加上对新库的增删改,这就是所谓双写,然后系统部署之后,新库数据差太远,用之前说的导数工具,跑起来读老库数据写新库,写的时候要根据gmt_modified这类字段判断这条数据最后修改的时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。接着导完一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环,直到两个库每个表的数据都完全一致为止。接着当数据完全一致了,就ok了,基于仅仅使用分库分表的最新代码,重新部署一次。

分库分表之后,id主键如何处理?
    数据库自增id
        这个就是说你的系统里每次得到一个id,都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个id。拿到这个id之后再往对应的分库分表里去写入。
        这个方案的好处就是方便简单,谁都会用;缺点就是单库生成自增id,要是高并发的话,就会有瓶颈的;如果你硬是要改进一下,那么就专门开一个服务出来,这个服务每次就拿到当前id最大值,然后自己递增几个id,一次性返回一批id,然后再把当前最大id值修改成递增几个id之后的一个值;但是无论怎么说都是基于单个数据库。
        适合的场景:你分库分表就俩原因,要不就是单库并发太高,要不就是单库数据量太大;除非是你并发不高,但是数据量太大导致的分库分表扩容,你可以用这个方案,因为可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键即可。并发很低,几百/s,但是数据量大,几十亿的数据,所以需要靠分库分表来存放海量的数据
    uuid
        好处就是本地生成,不要基于数据库来了;不好之处就是,uuid太长了,作为主键性能太差了,不适合用于主键。
        适合的场景:如果你是要随机生成个什么文件名了,编号之类的,你可以用uuid,但是作为主键是不能用uuid的。
        UUID.randomUUID().toString().replace(“-”, “”) -> sfsdf23423rr234sfdaf
    获取系统当前时间
        这个就是获取当前时间即可,但是问题是,并发很高的时候,比如一秒并发几千,会有重复的情况,这个是肯定不合适的。基本就不用考虑了。
        适合的场景:一般如果用这个方案,是将当前时间跟很多其他的业务字段拼接起来,作为一个id,如果业务上你觉得可以接受,那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号,订单编号,时间戳 + 用户id + 业务含义编码
    snowflake算法

展开阅读全文

没有更多推荐了,返回首页