什么是分布式系统
将一个系统拆分成多个,部署在不同的机器上,接口与接口之间通过网络通信来请求和响应。
为什么要用dubbo?
各个系统之间,可以直接基于spring mvc,纯http接口互相通信。但是因为http接口通信维护起来成本很高,需要考虑超时重试、负载均衡等问题,而dubbo作为rpc框架,会代理本地接口调用请求,跟远程机器网络通信,帮我们处理负载均衡、服务实例上下线自动感知、超时重试等问题。
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执行的性能。一般来说,单表到几百万的时候,性能就会相对差一些了。把一个表的数据放到多个表中,然后查询的时候就查一个表。
分库:一个库最多支撑到并发2000,而且一个健康的单库并发值最好保持在每秒1000左右,那么可以将一个库的数据拆分到多个库中,访问的时候就访问一个库。
不同的分库分表中间件都有什么优点和缺点?
cobar:阿里b2b团队开发和开源的,属于proxy层方案。不支持读写分离、存储过程、跨库join和分页等操作,使用较少
TDDL:淘宝团队开发的,属于client层方案。不支持join、多表查询等语法,但是支持读写分离。目前使用的也不多
sharding-jdbc:当当开源的,属于client层方案。确实之前用的还比较多一些,因为SQL语法支持也比较多,没有太多限制,而且目前推出到了2.0版本,支持分库分表、读写分离、分布式id生成、柔性事务(最大努力送达型事务、TCC事务)。目前社区一直在开发和维护,还算是比较活跃,使用的公司比较多
mycat:基于cobar改造的,属于proxy层方案,支持的功能非常完善,是目前不断流行的数据库中间件,社区很活跃。但是确实相比于sharding jdbc来说,年轻一些,经历的锤炼少一些。
sharding-jdbc这种client层方案的优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合sharding-jdbc的依赖;
mycat这种proxy层方案的缺点在于需要部署,自己及运维一套中间件,运维成本高,但是好处在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。
具体如何对数据库如何进行垂直拆分或水平拆分?
水平拆分:把一个表的数据放到多个库的多个表里去,但是每个库的表结构都一样,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来抗更高的并发,还有就是用多个库的存储容量来进行扩容。
垂直拆分:把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较少的访问频率很高的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。
range拆分:好处在于后面扩容的时候很容易,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用range,要看场景,你的用户不是仅仅访问最新的数据,而是均匀的访问现在的数据以及历史的数据
hash拆分:好处在于可以平均分配没给库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的这么一个过程
现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?
停机迁移方案
提前写好一个导数据的一次性工具,停掉系统,然后将单库单表的数据读出来,写到分库分表里面去。导数完了之后,修改系统的数据库连接配置,那你就用最新的代码,然后直接启动连到新的分库分表上去。
双写迁移方案(常用)
在线上系统里面,所有写库的地方,增删改操作,除了对老库增删改,都加上对新库的增删改,这就是所谓双写,然后系统部署之后,新库数据差太远,用导数据工具,跑起来读老库数据写新库,写的时候要根据gmt_modified这类字段判断这条数据最后修改的时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。接着导完一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环,直到两个库每个表的数据都完全一致为止。接着当数据完全一致了,基于仅仅使用分库分表的最新代码,重新部署一次。
分库分表之后,id主键如何处理?
数据库自增id
每次都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个id。拿到这个id之后再往对应的分库分表里去写入。缺点就是单库生成自增id,会有高并发瓶颈;改进的话,可以专门开一个服务出来,这个服务每次就拿到当前id最大值,然后自己递增几个id,一次性返回一批id,然后再把当前最大id值修改成递增几个id之后的一个值;但是无论怎么说都是基于单个数据库。
适合的场景:分库分表一般两个原因,单库并发太高或者单库数据量太大;如果是并发不高,但是数据量太大导致的分库分表扩容,可以用这个方案,因为可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键即可。并发很低,几百/s,但是数据量大,几十亿的数据,所以需要靠分库分表来存放海量的数据
uuid
好处就是本地生成,不要基于数据库;不好之处就是,uuid太长了,作为主键性能太差了,不适合用于主键。
适合的场景:如果你是要随机生成个什么文件名了,编号之类的,你可以用uuid,但是作为主键是不能用uuid的。
UUID.randomUUID().toString().replace(“-”, “”) -> sfsdf23423rr234sfdaf
获取系统当前时间
这个就是获取当前时间即可,但是问题是,并发很高的时候,比如一秒并发几千,会有重复的情况,基本就不用考虑了。
适合的场景:将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号,订单编号,时间戳 + 用户id + 业务含义编码
snowflake算法
分布式session解决方案
1.tomcat + redis
使用Tomcat RedisSessionManager,让所有我们部署的tomcat都将session数据存储到redis
2.spring session + redis
配置sping session基于redis来存储session数据,然后配置了一个spring session的过滤器,session相关操作交给spring session来管理
3.CAS单点登陆
分布式事务解决方案
1、两阶段提交方案/XA方案
通过事务管理器,协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。
2、TCC 方案
Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。
Confirm 阶段:这个阶段说的是在各个服务中执行实际的操作。
Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
3、本地消息表
4、可靠消息最终一致性方案
5、最大努力通知方案