分布式架构:
分布式系统:(服务间跨网络调用)
将一个大的系统(jvm内存调用)拆分成很多的小系统,各个小系统部署在不同的机器上,通过分布式服务框架(比如dubbo)搞一个rpc调用,接口与 接口之间通过网络通信来进行请求和响应。
为什么要走分布式系统架构?
老的系统存在的问题:
1、应用代码耦合严重,功能扩展难
2、新需求开发交互周期长,测试工作量大
3、新同事熟悉系统需要很长时间
4、升级维护难(改动任何一点都要重新部署整个系统)
5、系统性能提升艰难,可用性低,不稳定
采用分布式架构拆分后的好处
1、代码冲突减少了
2、每次测试自己的功能就好了
3、升级维护只需要发布自己的一个小服务就可以了
系统如何进行拆分?
基于领域驱动设计思想及实战经验总结,同时参考业界的一些常规做法,逐步优化,多轮拆分使得系统以达到一个比较好的状态
例:一个电商系统可以拆分成订单系统,商品系统,店铺系统,会员系统,促销系统,支付系统等等。订单系统有可以拆分出购物车系统,库存系统,价 格系统等等
分布式服务框架:
dubbo:
工作原理:
1、服务容器负责启动,加载,运行服务提供者
2、服务提供者在启动时,向注册中心注册自己提供的服务
3、服务消费者在启动时,向注册中心订阅自己所需的服务
4、注册中心返回服务提供者的地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
5、服务消费者从提供者地址列表中基于软负载均衡算法选一台提供者进行调用,如果调用失败,再选另一台调用
6、服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
dubbo服务的暴露:
spring实例化完bean后,在刷新容器的时候通知实现了ApplicationListener的ServiceBean类进行回调onApplicationEvent事件方法,dubbo 会在这个方法中调用ServiceBean父类ServiceConfig的export方法,实现服务的发布
1、检查各种配置属性,进行填充
2、加载所有的注册中心
3、根据配置的所有协议和注册中心URL分别进行导出
4、如果配置的是remote,则暴露为远程服务
5、如果配置的是local,则做本地导出
6、获取invoker
7、获取完invoker,转换为对外的Exporter,缓存起来
dubbo心跳机制:
provider:dubbo的心跳默认是在60s内如果没有接收到消息,就会发送心跳消息,连着3次没有收到心跳响应,provider会关闭通道
consumer:dubbo的心跳默认是在60s内如果没有接收到消息,就会发送心跳消息,连着3次没有收到心跳响应,consumer会进行重连
dubbo的集群容错模式:
1、故障转移模式:失败自动转换,当失败时重试其他的服务器。
用于读操作,重试会带来更长的延迟,使用retries=‘2’来设置重试次数(不含第一次)
2、快速失败模式:快速失败,只发起一次调用,失败立即报错。
用于非幂等性的写操作,比如新增记录
3、安全失败模式:出现异常直接忽略
用于写入审计日志等操作
4、故障恢复模式:失败自动回复,后台记录失败请求,定时重发。
用于消息通知等操作
5、并行调用模式:并行调用多个服务器,只要一个成功即返回
用于实时性比较高的读操作,但是浪费更多资源,使用forks=‘2’来设置最大并行数
6、广播模式:广播调用所有提供者,逐个调用,任意一台报错则报错,用于通知所有提供者更新缓存或日志等本地资源
dubbo负载均衡策略:
1、随机:按权重设置随机分配
2、轮询
3、最少活跃调用数:某个方法的调用并发数,再调用之前+1调用完-1的一个计数器,发送到最少并发数的那个,相同并发数按随机。
4、一致性:相同参数的请求总是发送到同一提供者
如何基于dubbo进行服务治理、服务降级、失败重试及超时重试?
服务治理:
1、调用链路自动生成,a—>b—>c,d;
2、服务访问压力及时长统计
接口级:每个服务的每个接口每天被调用多少次,TP50/TP90/TP99,三个档次到请求延时分别是多少
链路级:一个完整请求链路经过几十个服务后,完成一次请求,每天全链路走多少次,全链路请求延时的TP50/TP90/TP99分别是多少 3、服务的可用性监控:接口调用成功率
注意⚠️:TP50:满足50%的请求所需的最低耗时
服务降级:
服务A调用服务B,结果服务B挂掉了,重试几次还是不行,那么直接降级,走一个备用的逻辑,给用户返回响应
实现:在接口同一个路径下实现一个Mock类,命名规则是接口名称+Mock,然后在Mock类中实现降级逻辑
失败重试及超时重试:
retries:设置重试次数,如果第一次没有取到数据或报错了,重试指定的次数,尝试再次读取
dubbo服务接口的幂等性如何设计?(多次扣款)
对于每一个请求设一个唯一值,每次处理完成之后设置一个标志值
dubbo服务接口请求的顺序性如何保证?
1、使用分布式锁,2、使用dubbo的一致性hash负载均衡策略
zookeeper:
功能:可以实现发布订阅,负载均衡,命名服务,分布式协调通知,集群管理,Master选举,分布式同步,分布式锁,事件监听及权限控制等功能
保证了顺序一致性,原子性,可靠性,最终一致性
安装:解压后将conf文件中的zoo_sample.cig改为zoo.cfg即可使用
客户端:
zookeeper客户端:原生客户端,操作复杂‘ zkClient客户端:对原生客户端进行了一些封装,比较好用,不用担心session会话超时等问题 curator客户端:进行了比较完整的封装,推荐使用,还提供了分布式锁,链式调用等多种方案
应用:内部封装了简单易用的服务,在保证高性能,高稳定的情况下提供了简单易用的接口,目前Kafka,dubbo等都使用到了zookeeper
权限控制:(zookeeper权限分为增,删,改,查,管理,这5中权限简写为crwda)
IP权限:一个IP或IP段的人创建对节点只有他们可以进行增删改查,其他IP段的不允许进行增删改查
word权限:创建对节点所有人都可以进行增删改查,只在测试时使用
digest权限:通过用户名和密码进行权限控制。使用addauth进行添加用户,添加的用户可以对自己创建对节点进行增删改查,多个人可以
同时使用一个用户进行操作。在添加用户时用户名和密码会经过sha-1加密和base64编码;但是在添加认证用户的时候是通过明文添加的 1)添加用户:
对test01节点添加用户权限设置:root digest:用户名:密码密文:权限。 例:test01 digest:xiaobai:vbhnk6nhs=:crwda
添加认证用户:addauth digest 用户名:密码明文 权限。 例:addauth digest xiaobai:123456 crwda
事件监听:当节点发生改变时可以进行集群通知
原理:
1、当创建zookeeper客户端时,内部就会默认创建两个线程,一个connect线程,负责网络通信连接,一个listener线程,负责监听;
2、客户端通过connect线程连接服务器。使用getChldren(“/”,true),其中“/”表示监听的根目录,true表示监听,false表示不监听;
3、将注册的监听事件放入zookeeper监听列表中。表示这个服务器中的“/”目录被客户端监听
4、被监听的目录下的数据或路径发生改变,zookeeper就会将这个消息发送给listener线程。
5、listener线程内部调用process方法,采取相应的措施
两种监听模式:
观察者模式:一次性触发,对节点进行一次监听,监听完后立即失效,并且不对子节点进行监听
缓存监听模式:提供了重复注册的功能,在客户端缓存了节点的各种状态,不断的和zookeeper集群进行对比,如果未发生变化就会一直发 送true,一旦发生改变就会发送false。
缓存监听模式分为3种:
Path Cache:用来观察znode的子节点并缓存状态,如果znode的子节点被删除,修改,创建,就会触发监听事件
Node Cache:用来观察znode本身的状态,如果znode本身被创建,更新,删除,就会触发监听事件
Tree Cache:用来观察所有节点的数据变化,监听器的注册接口为TreeCacheListener。
springcloud:
是一系列框架的有序集合,利用spring boot简化了分布式系统的开发,它是将目前各家公司开发的比较成熟的服务框架组合起来进行封装,最终给 开发者提供了一套简单易懂,易部署和易维护的分布式系统开发工具包。如服务发现注册,配置中心,消息总线,负载均衡,断路器,数据监控等,都可 以用spring boot的开发风格做到一键启动和部署。
核心组件:
Spring Cloud Netflix:
Eureka(服务注册中心):
Ribbon(客户端负载均衡):多种策略:1、轮询,2、随机,3、根据响应时间加权。使用@LoadBalanced注解
Feign(声明式服务调用):底层依赖于Java的动态代理机制,实现了基于http协议的远程服务调用
注解:@EnableFeignClients:开启Fiegn功能
Hystrix(客户端容错保护):
Zuul(API网关服务):为微服务架构中的服务提供统一的访问入口,
Spring Cloud Alibaba:
Nacos(服务注册中心):
Sentinel(客户端容错保护):
Spring Cloud 原生组件:
Consul(服务注册中心):
Config(分布式配置中心):
Gateway(API网关服务):
Sleuth/Zipkin(分布式链路追踪):
分布式事务:
分布式缓存:
分布式消息队列:
分布式锁:
setnx:
1、系统异常,锁未被删除导致死锁(finally解决)
2、宕机了,锁未被删除导致死锁(加一个过期时间expire)
3、代码处理逻辑复杂,执行时间超过过期时间导致锁被删除,后面的线程进入,前面的线程会删除掉后面线程的锁,导致锁失效(给每个线程赋一 个唯一值作为锁标志,在删除锁的时候做个对比在删除)
4、锁续命,如果过了超时时间的1/3,判断主线程是否持有锁,如果持有则延长它的超时时间
redisson(续命锁):高并发环境可用
RLock redissonlock = redisson.getlock(lockkey); 获取锁对象
redissonlock.lock(); 加锁
redissonlock.unlock(); 释放锁
源码分析:有一段lua脚本(当成一条命令来执行),判断锁是否存在,如果不存在,就使用hset给锁对象赋值,给锁对象设置过期时间,加锁成功后, 异步调用一个定时任务给锁续命
分段加锁:
注意⚠️:
nginx:负载均衡
同步代码块(synchronized):单机环境可用,分布式多节点集群环境不可用
JMeter:压测工具