面试题:分布式,微服务相关
1.CAPC:一致性,所有节点在同一时间的数据要完全一致,强一致性A:可用性:服务一直可用,不出现用户操作失败或者超时等影响用户体验的情况P:分区容错性:分布式系统遇到某个节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务CP和AP:分区容错是必须保证的,当发生网络分区时,要继续服务,那么强一致性和可用性只能二选一2.BASE:即使无法做到强一致性,但每个应用可以根据自身业务特点,
·
1.CAP
- C:一致性,所有节点在同一时间的数据要完全一致,强一致性
- A:可用性:服务一直可用,不出现用户操作失败或者超时等影响用户体验的情况
- P:分区容错性:分布式系统遇到某个节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务
- CP和AP:分区容错是必须保证的,当发生网络分区时,要继续服务,那么强一致性和可用性只能二选一
2.BASE:即使无法做到强一致性,但每个应用可以根据自身业务特点,采取适当的方式实现弱一致性
- BA:基本可用:响应时间上的损失或者系统功能上的损失
- S:软状态:数据同步允许一定的延迟
- E:最终一致性:系统中的数据副本,经过一段时间,最终能达到一致性
3.负载均衡算法、类型
1.随机 2.轮询 3.加权轮询 4.最小连接数(最小访问量) 5.源地址哈希 6.加权随机
4.分布式架构下session共享有什么方案:
1.redis存储token
2.服务器间session同步
3.ip绑定策略,nginx中可以设置同一个ip只能在同一个机器访问,这样失去了负载均衡的意义
5.分布式Id生成方案
1.uuid:生成快,性能好,无序,长度长,字符串 2.数据库自增id:依赖db,存在单点问题 3.分布式数据库自增id:每次获取id都要请求一次数据库,而且维护困难 4.leaf-segment:每次获取一个id段,比如1-100的id段,存到本地备用,只会请求一次数据库 5.双buffer:将获取一个号段,变成获取两个号段,先获取了100个id,然后用到10%的时候,再异步获取一百个 6.基于redis,mongo,zk等中间件 7.雪花算法:64为的全局id,引入时间戳和保持自增 1.最高位为符号位,位0 2.41位的时间戳,精确到毫秒级 3.10位机器标识,最多可支持到1024台机器 4.12位的计数序列号,支持每毫秒4096个Id生成
6.分布式锁的解决方案
1.zookeeper分布式锁 1.zk通过临时节点,解决了死锁问题,因为节点挂掉,那么这个临时节点也会自动删除掉,客户端就能获取到锁 2.redis分布式锁 1.setnx,如果key不存在,则返回1并设置,存在则返回0 2.设置超时时间,解决死锁问题 4.加锁和设置锁的超时时间并不是原子操作,所以可能产生死锁,可以set命令 5.高并发场景下,锁的超时时间设置可能产生问题,比如进程A加锁,但是还没释放锁就自动过期了,线程B恰好又进来加锁,结果A释放的是B的锁 所以set的值最好为一个Uuid,释放的时候判断是否是这个uuid 6.redission加锁,看门狗机制,自动刷新超时时间,lua原子操作,可重入性和锁续期 7.redis分布式锁最大问题是集群模式下,master节点宕机,锁丢失
7.分布式事务解决方案:
1.两种分布式事务: 1.在一个Method中操作了两种数据库,用@Trancational没有用,可以用JTA解决 2.A节点调用B节点,A节点调用DBA,B节点调用DBB 2.XA规范:分布式事务规范,定义了分布式事务模型 1.四个角色:事务管理器(协调者TM)、资源管理器(参与者RM)、应用程序AP、通信资源管理器CRM 2.全局事务:一个横跨多个数据库的事务,要么同时提交,要么同时回滚 3.两阶段协议 1.第一阶段:每个参与者执行本地事务但不提交,进入ready状态,并通知协调者已经准备就绪 2.第二阶段:当协调者确认每个参与者都ready后,通知参与者进行commit操作,如果有参与者fall,则发送rollback命令,各参与者回滚 3.问题 1.单点故障:一旦事务管理器出现故障,整个系统不可用(参与者都会阻塞住),并且出现一系列问题 4.项目:这种方案比较适合用于单应用里面,比如一个方法调用了两个数据库,效率很低,不适合高并发,用spring + JTA就能实现,一般我们 很少用,其实一个服务操作两个不同的库,是不合规的,微服务基本上要求一个服务只能操作一个库,然后想要用这个服务就通过rest或者rpc调用 4.三阶段协议:主要解决两阶段的单点故障问题 1.第一阶段:发送cancommit消息,确认数据库环境正常 2.第二阶段:发送precommit消息,完成sql语句操作,但未提交事务 3.第三阶段:发送docommit消息,完成事务的提交和回滚 4.超时机制:如果precommit成功了,但一定时间内还未收到docommit消息,则认为协调者挂了,则自己会执行docommit操作 4.项目:三阶段协议其实和两阶段差不多,只不过多了个确认数据库环境是否异常的阶段,并且增加了超时重试的机制,也不用 5.TCC补偿事务(Try,Confirm,Cancel) 1.try:做业务检查和资源预留 2.confirm:做业务确认 3.cancel:实现一个与try相反的回滚操作,是业务上的回滚,不是数据库的回滚,比如你try加了100元钱,那么这个回滚就需要减少100元钱 4.TM首先发起所有分支的try操作,一旦有一个失败,TM会发起所有事务的cancel操作,全部成功,则发起confirm 5.问题 1.对业务侵入性很大 6.项目:这个方式用的也比较小,一般先检查数据的条数或者关键数据,然后调用其他库的服务时,如果抛出异常,就硬编码去回滚,比如某个数量 + 1, 那么出现问题就得手动调用减1的服务,代码量巨大,而且承受不住并发,难以维护 6.消息队列的事务 1.发送prepare消息到中间件 2.发送成功后,执行本地事务 1.执行成功,则commit,消息中间件将消息下发至消费端 2.如果失败,则回滚,消息中间件删除消息 3.消费端接收到消息进行消费,如果消费失败,则不断重试 7.阿里开源的分布式事务解决方案seata 8.本地消息表:字段有,id,业务id,消息状态,消息内容,重试次数等 1.消息生产方,需要额外建一个消息表,并且记录消息的发送状态。消息表和业务数据要在一个库里面 2.消息经过mq发送给消费方,发送失败则自动重试 3.消息消费方处理这个消息,完成业务逻辑,然后将是否成功的消息通过mq发送给消息生产方 4.生产方监听消费方是否消费成功,再修改本地消息表中的状态 5.如果消息在mq中,或者网络中发送失败,则在保证接口幂等的情况下,引入一个定时任务,读取消息发送失败的状态 6.本地消息表中失败的消息,就重新读取发送到mq中重试,直到重试次数阈值之前成功,若不成功,则短信告警,人工介入或者回滚 7.缺点:与业务耦合,难以做通用性,高并发下有瓶颈
8.接口幂等性
1.根据具体业务来判断怎么处理 1.token + redis 机制(举例订单支付业务) 1.当到了支付的场景时,生成一个全局唯一的token,存入到redis中,并且返回给客户端 2.发起支付操作的时候附带这个token 3.接口处理: 1.获取分布式锁(处理并发情况) 2.判断redis中是否存在token 3.存在,执行支付业务逻辑,执行后删除token,否则返回订单已支付 4.释放分布式锁 2.CAS保证接口幂等 1.状态机制幂等(举例订单支付状态 0 待支付 1 支付中) 2.update order set status = 1 where status = 0 and orderId = ? 3.要进行支付,上来先更新一下订单的支付状态 3.乐观锁方案,版本控制 1.大部分订单系统都是分布式部署的,订单和库存业务独立部署,由于网络原因,可能请求延迟了,重新发起请求,就可能导致幂等性问题 2.update t_goods set count = count - 1 where good_id = 2 3.如果请求两次,没保证幂等,则库存减了两个 4.借鉴数据库乐观锁或者版本控制:update t_goods set count = count - 1,version = version + 1 where good_id = 2 and version = 1 4.防重表 1.数据库建立防重表,加唯一索引,订单有状态控制,则可以支付成功后删除订单号,没有的话也可以不删除
9.高并发预约系统设计:疫情期间,政府免费发放口罩的预约服务,服务升级
1.硬件参数: 1.一台16核,64g内存的服务器 2.mysql服务器,单台 2.产生问题: 1.系统qps四五百多 2.预约页面在高峰时期页面加载卡顿,url访问缓慢,系统崩溃掉 (解决方法7,5) 3.netstat 查看当前80端口连接数 netstat -ant|awk '/^tcp/ {++S[$NF]} END {for(a in S) print (a,S[a])}' (解决方法8) 1.状态是established的连接数量为500多 2.状态是time_wait的连接数量为???(这个值需要深思熟虑一下) 1.TIME_WAIT出现的原因是客户端请求到linux,会占用一个端口。如果请求了,服务端没返回数据,就回出现TIME_WAIT 4.服务中调用了三个接口,并且都是顺序执行的,比如身份信息校验接口,失信人接口之类的,必须获取到数据之后才能进行下一步操作 三个接口的调用大概花费2-3s,原因是网络环境复杂,接口数据需要经过两三家公司的层层封装调用返回(解决方法9) 5.会有人在预约开始之前一分钟就不断地对服务发起请求,有点像黄牛用代码写的程序,毕竟当时疫情刚开始,口罩是很值钱的(解决方法3) 6.没有采用缓存,已经预约的人还能再次请求,查询数据库之后才返回(解决方法5.4) 7.oracle的连接数有到了800多,查询变得缓慢,有些日志中报超时错误(解决方法2) 8.出现超约现象,原本是定只能约500人,但是多约了3个(解决方法5.5) 9.页面的所有资源加起来大小有3m,还有两张300k的图片,每次加载页面需要大约4m的带宽(解决方法7) 10.记录预约成功或者失败的日志到oracle中,产生压力(解决方法5.3) 11.预约的时候从预约,到预约成功,中间经过了5s左右(解决方法5.6,5.7) 3.当时的解决方案: 1.软件方面 1.采用redis做缓存 2.建立数据库主从复制,读写分离 3.nginx配置对某个ip请求频率高的或者请求次数多的拦截 4.已预约的手机号码防止再次进行预约,将预约时段和手机号码作为Key,存入redis 5.预约主要业务逻辑 1.缓存预热,页面上需要展示的一些通过接口获取的数据,库存预热,预约的时段和数量存入redis 2.mq异步,削峰,预约成功则扣减redis数量,redis新增一条预约的人员的Key,值为1(预约状态,0表示预约失败,1表示正在预约中,2表示预约成功), 值大于1就拦截,防止重复预约,然后发送一mq消息到业务处理逻辑 3.日志消息发送到mq中收集 4.mq处理成功,则redis的预约人员的key设置为2,处理失败,则预约状态变为0,redis库存+1 5.整个预约的主要逻辑加上redision分布式锁,锁的是预约时间段,并且设置超时时间 6.返回一个正在预约中的状态给前端,前端每隔1s发起一个短轮询,ajax请求,获取是否预约成功,获取到的值为0则表示失败,数据库查询失败原因返回 7.是否预约成功的逻辑是直接查询redis的时间段和手机号的key,成功则返回,查询不到则重试5次自动失败 6.nginx作负载均衡,部署三台服务,分散系统压力 7.nginx反向代理前端代码,开启gzip压缩技术,静态资源缓存 8.解决tcp连接time_wait问题 1.linux可以tcp端口数开放到65535个 2.linux可以开启端口快速重用 9.新增全局线程池,设置核心40线程,最大线程 2 核心线程,多线程同时获取3个接口数据,CountDownLatch控制返回 2.硬件方面 1.mysql主从集群,分散读并发压力 2.增加两台 16核,64g内存的服务器 4.优化后的系统 1.QPS:5000多 5.现在的解决方案(目的是解决更大的并发与流量,基于以上的解决方案新增以下几点): 1.引入Springcloud Gateway 网关服务统一鉴权与服务间负载均衡 2.引入Springcloud Alibaba Sentinal,作服务间的限流与熔断,降级,隔离,保证高可用与集群的正常运行 3.redis作redis-cluster集群,主从模式,保证数据的高可用性,独写分离,解决内存瓶颈
更多推荐
已为社区贡献1条内容
所有评论(0)