一、注册中心
1、为什么需要用到注册中心?
让消费者服务及时知道提供者服务的状态。例如:是否宕机、是否增加了集群实例等。
2、dubbo和zookeeper
特点:服务消费端订阅注册中心。服务提供端增加实例会把新实例注册到注册中心,注册中心接收到新实例注册之后会通知服务消费端,然后服务消费端会到注册中心重新拉取注册信息。
3、Eureka
①总体机制
特点:Eureka不支持注册信息订阅,消费者**定时**访问注册中心,发现信息更新则拉取到本地。SpringCloud中没有严格意义上的consumer、provider。单独来看时,A服务调用B服务,可以说A是consumer,B是provider,但也可能B反过来调用A。
②微服务B参数
微服务B的心跳机制:每次心跳请求都要发送两个参数给注册中心。
- 计划存活时间:就像租房签合同时写明租多久
- 下次续约时间:就像跟房东续租
假设计划存活时间为90秒,下次续约时间为60秒。如果到60秒时微服务B没有续约,注册中心不会认为微服务B宕机;到90秒之后仍未续约,注册中心才会认为微服务B宕机。
③Eureka参数
- 多久执行一次失效服务剔除,被剔除的服务无法被消费端访问
- 自我保护机制
- 默认开启,实际开发中建议关闭。
- 如果有效服务比例低于85%,则失效服务不会被剔除,也就是会被保留。
> 家里穷的时候破东西也要留着。
4、Nacos
两种都支持:注册中心订阅发布和消费者定时拉取。
5、注册中心核心数据结构
核心结构:Map<微服务名称字符串,List<微服务实例>>
因为微服务实例有可能因为配置了集群而有多个,所以使用List集合保存。
此时Map接口的实现类,倾向于使用ConcurrentHashMap:支持并发读写。
二、ribbon客户端负载均衡
1、为什么叫客户端负载均衡
- 客户端:微服务调用过程中,consumer这一端
- ribbon:
- 导jar包在consumer这边
- 工作在consumer这边
2、负载均衡策略
- 轮询 RoundRobinRule:轮流访问每一个集群实例
- 权重 WeightedResponseTimeRule:权重高的实例被访问到的几率大,而权重值是根据服务提供者的响应时间设定的,响应时间越长权重值越小。它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。
- 随机 RandomRule:从集群服务列表中随机选择一个访问
- 最小连接数 BestAvailableRule:也叫最小并发数,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。
- 重试策略 RetryRule:按照轮询策略来获取服务,如果获取的服务实例为null或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回null。
- 可用性敏感 AvailabilityFilteringRule:先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。
- 区域敏感 ZoneAvoidanceRule:根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似。
> 不管具体采用哪一个策略,最初确定第一个被访问的微服务实例时都是随机选择的。
即使是轮询也只是每一轮访问的顺序一样,第一轮访问的顺序是随机的。
三、熔断器
1、三种服务调用错误
- 连接超时 Connection timeout:HTTP建立连接时三次握手过程超时
- 请求超时 read timeout:HTTP连接成功建立,但是服务器提供者执行请求超时
- 服务提供者抛异常
2、服务降级
虽然服务器端执行请求失败,但是并不是返回报错信息,而是返回比较友好的错误提示,例如:“服务器繁忙,请稍候重试!”。相对于正式的正确结果,友好提示就是“服务降级”。
3、熔断阈值
Hystrix会对请求情况计数,当一定时间失败请求百分比达到阈值,则触发熔断。默认阈值为:
- 请求失败比例50%
- 请求次数最低不少于20次
4、熔断状态间切换
①关闭
消费者可以正常调用提供者。
②全开
满足熔断阈值的要求,熔断器打开,消费者无法调用提供者。持续时间10秒。
③半开
全开状态持续10秒后,进入半开状态。在半开状态下仅允许一个请求访问提供者。
- 请求成功:熔断器关闭。
- 请求失败:回到全开状态。
5、线程隔离
服务消费者处理请求和调用其它服务器使用的是不同线程。而调用其它微服务的线程来自于熔断器内部的线程池。有了线程池,我们就可以把调用其它微服务的线程数量限制在它们的承受能力范围内,对服务提供者来说是一种保护。
四、OpenFeign
1、底层机制
RestTemplate+Ribbon+熔断器
2、顶层设计
让我们可以像调用本地方法一样调用远程方法。
3、底层原理
- 我们只是写了接口,没有提供实现类,可最后调用接口方法可以执行具体操作。
- 从Java语法要求角度来讲,接口里面的抽象方法是必须有实现方法才能执行。
- 明确实现类
- 匿名内部类
- Lambda表达式
- 所以说,表面上是调用了接口,但是实际上肯定是有实现类的。只是这个实现类不是我们写的。
- 此时实现类是框架生成的。对于基于接口生成实现类的场景,通常都是:动态代理。
- 通过debug查看源码能够发现,Feign、Mybatis底层确实都是使用动态代理。
- Feign底层由动态代理生成的类内部会把微服务的名称交给ribbon,由ribbon从注册中心获取目标微服务的信息,然后参考负载均衡策略实现远程调用,具体远程调用由RestTemplate完成。
> 启发:框架做的无非是两件事
●实现原本没有的功能
○注册中心
○熔断器
○网关
○配置中心
●简化开发
○Feign
五、网关
微服务非常多的时候,客户端要记住太麻烦了,所以把网关设定为所有微服务的统一入口。
而有了这样一个入口,就可以在其中执行鉴权、过滤、路由等这样的统一性的操作。
1、网关的转发规则
属性名 | 含义 |
id | 多个微服务需要多套配置,为了区分每一套配置,每套配置都通过id属性设置唯一标识 |
predicates | 断言,请求地址的匹配规则 |
uri | 路由地址,匹配到断言规则之后,请求发送的目标位置 |
2、路由配置
①静态路由
写死的访问地址。例如:
uri: http://localhost:8206
开发时不建议使用,因为从开发环境部署到生产环境时,访问地址会发生变化。
②动态路由
使用微服务名称代替写死的访问地址。例如:
uri: lb://service-product
lb是load balance的缩写,意思是:负载均衡。
然后网关底层根据微服务名称会到注册中心查找微服务的具体信息。
所以网关内部执行负载均衡等操作其实是Ribbon组件在起作用。
3、路由过程
网关把动态路由中的微服务名称传给ribbon,ribbon根据微服务名称到注册中心查询到微服务列表。ribbon把微服务列表拉取到本地,然后根据负载均衡算法决定具体访问哪一个微服务,确定之后由RestTemplate发起远程调用。
六、配置中心
每个微服务单独修改application.yml非常繁琐,所以需要配置中心。配置中心也在注册中心中注册,每个微服务只需要知道注册中心在哪就行,到注册中心找到配置中心的访问地址,然后访问配置中心获取各自的详细配置信息。从而实现所有微服务的配置信息在配置中心统一管理。
在配置中心修改配置之后,大部分配置可以直接修改而不必重启微服务。当然,像端口号这样的配置信息修改之后还是要重启微服务的。
七、总结
应对问题 | 解决方案 |
服务调用时,需要知道对方的状态 | 注册中心 |
provider集群分摊负载 | 负载均衡 |
服务调用过程中可能失败,甚至雪崩 | 熔断器 |
RestTemplate、Ribbon、熔断器调用复杂,需要简化开发代码 | Feign |
微服务数量增加,IP地址、端口号记录麻烦,<br>又需要在请求到达微服务前执行统一性操作 | 网关统一入口 |
微服务数量增加,配置文件管理成本增加 | 配置中心,统一管理配置信息 |