微服务架构基础知识

微服务架构有哪些优势
独立开发 – 所有微服务都可以根据各自的功能轻松开发
独立部署 – 基于其服务,可以在任何应用程序中单独部署它们
故障隔离 – 即使应用程序的一项服务不起作用,系统仍可继续运行
混合技术堆栈 – 可以使用不同的语言和技术来构建同一应用程序的不同服务
粒度缩放 – 单个组件可根据需要进行缩放,无需将所有组件缩放在一起
 
微服务环境搭建
以电商项目中的商品、订单、用户为案例
模块设计
springcloud-alibaba 父工程
shop-common 公共模块【实体类,工具类】
shop-user 用户微服务 【端口: 807x】
shop-product 商品微服务 【端口: 808x】
shop-order 订单微服务 【端口: 809x】
微服务调用
在微服务架构中,最常见的场景就是微服务之间的相互调用。我们以电商系统中常见的用户下单为例来演示微服务的调用:客户向订单微服务发起一个下单的请求,在进行保存订单之前需要调用商品微 服务查询商品的信息。 我们一般把服务的主动调用方称为服务消费者,把服务的被调用方称为服务提供者。
在这种场景下,订单微服务就是一个服务消费者, 商品微服务就是一个服务提供者。使用 RestTemplate 使用 http 方式远程访问
RestTemplate restTemplate;
Product p = restTemplate.getForObject
("http://127.0.0.1:8072/product/findProduct/"+pid, Product.class);
这种调用方式的问题:
 一旦服务提供者地址变化,就需要手工修改代码
一旦是多个服务提供者,无法实现负载均衡功能
一旦服务变得越来越多,人工维护调用关系困难

 接下来介绍一种工具来管理并调用这些服务:

Nacos Discovery--服务治理
服务治理介绍
服务治理 是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册与发现。
服务注册: 在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服务的详细信息。并在注册中心形成一张服务的清单,服务注册中心需要去监测清单中 的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。
服务发现: 服务调用方向服务注册中心咨询服务,并获取所有服务的实例清单,实现对具体服务实 例的访问。

通过上面的调用图会发现,除了微服务,还有一个组件是服务注册中心,它是微服务架构非常重要 的一个组件,在微服务架构里主要起到了协调者的一个作用。
注册中心一般包含如下几个功能:
1. 服务发现: 服务注册:存服务保提供者和服务调用者的信息
服务订阅: 服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息
2. 服务配置: 配置订阅:服务提供者和服务调用者订阅微服务相关的配置配置下发:主动将配置推送给服务提供者和服务调用者
3. 服务健康检测 检测服务提供者的健康情况,如果发现异常,执行服务剔除
Nacos 提供了一组简单
易用的特性集,帮助您快速 实现动态服务发现、服务配置、服务元数据及流量管理。 从上面的介绍就可以看出,nacos 的作用就是一个注册中心,用来管理注册上来的各个微服务。

服务调用

注入discoveryClient

@Autowired
DiscoveryClient discoveryClient;
//从 nacos 中获取服务地址
ServiceInstance serviceInstance =
discoveryClient.getInstances("service-product").get(0);
String url = serviceInstance.getHost() + ":" +
serviceInstance.getPort();
//使用
Product p = restTemplate.getForObject( "http://" + url + "/product/" +
pid, Product.class);
服务调用负载均衡
什么是负载均衡
通俗的讲, 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。
基于 Ribbon 实现负载均衡
Ribbon 是 Spring Cloud 的一个组件, 它可以让我们使用一个注解就能轻松的搞定负载均衡
第 1 步:在 RestTemplate 的生成方法上添加@LoadBalanced 注解
第 2 步:修改服务调用的方法
Ribbon 支持的负载均衡策略 Ribbon 内置了多种负载均衡策略,内部负载均衡的顶级接口为 com.netflix.loadbalancer.IRule , 具体的负载策略:
七种负载均衡策略
轮询策略: RoundRobinRule,按照一定的顺序依次调用服务实例。比如一共有 3 个服务,第一次调用服务 1,第二次调用服务 2,第三次调用服务 3,第四次又调用服务1,依次类推。
//application.yml中的配置
service-product: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

权重策略: WeightedResponseTimeRule,根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。
 //application.yml中的配置
service-product: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
随机策略: RandomRule,从服务提供者的列表中随机选择一个服务实例。
 //application.yml中的配置
service-product: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
最小连接数策略: BestAvailableRule,也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取
 //application.yml中的配置
service-product: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule
重试策略: RetryRule,按照轮询策略来获取服务,如果获取的服务实例为null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null
 //application.yml中的配置
ribbon:
  ConnectTimeout: 2000 # 请求连接的超时时间
  ReadTimeout: 5000 # 请求处理的超时时间
service-product: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
可用敏感性策略: AvailabilityFilteringRule,先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。
//application.yml中的配置
service-product: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.AvailabilityFilteringRule
区域敏感策略: ZoneAvoidanceRule,根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似。
//application.yml中的配置
service-product: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule
基于 Feign 实现服务调用
Feign 是 Spring Cloud 提供的一个声明式的伪 Http 客户端, 它使得调用远程服务就像调用本地服务 一样, 只需要创建一个接口并添加一个注解即可。Nacos兼容了 Feign, Feign 默认集成 Ribbon, 所以在 Nacos 下使用 Fegin 默认就实现了负载均衡的效果。
Feign 的使用
1.加入 Fegin 的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. 在主类上添加 Fegin 的注解
@EnableFeignClients//开启 Fegin
3. 创建一个 service接口, 并使用 Fegin 实现微服务调用

 4. 修改 controller 代码,并启动验证

Sentinel--服务容错
高并发带来的问题
在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,但是由于网络 原因或者自身的原因,服务并不能保证服务的 100%可用,如果单个服务出现问题,调用这个服务就会 出现网络延迟,此时若有大量的网络涌入,会形成任务堆积,最终导致服务瘫痪。
服务雪崩效应
在分布式系统中,由于网络原因或自身的原因,服务一般无法保证 一定可用。如果一个服务出现了问题,调用这个服务就会出现线程阻塞的情况,此时若有大量的请求涌入,就会出现多条线程阻塞等待,进而导致服务瘫痪。由于服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩效应”
   
我们无法完全杜绝雪崩源头的发生,只有做好足够的容错,保证在一个服务发生问题,不会影响到其它服务的正常运行。
常见容错方案
要防止雪崩的扩散,我们就要做好服务的容错,常见的容错思路有: 隔离、超时、限流、熔断、降级这几种,下面分别介绍一下
隔离
它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的系统服务。
超时
在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应, 就断开请求,释放掉线程。
限流
限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。
熔断
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
服务熔断一般有三种状态:
熔断关闭状态(Closed)
服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制
熔断开启状态(Open)
后续对该服务接口的调用不再经过网络,直接执行本地的 fallback 方法
半熔断状态(Half-Open)
尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断开启状态。
降级
降级其实就是为服务提供一个托底方案,一旦服务无法正常调用,就使用托底方案。
Sentinel 使用及概念
什么是 Sentinel
Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于 服务容错 的综合性解决方案。它以流量 为切入点, 从流量控制、熔断降级、系统负载保护 等多个维度来保护服务的稳定性。
Sentinel 的主要功能就是容错,主要体现为下面这三个:
c流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的数据。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。 Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。
熔断降级
当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。
服务网关
网关简介
在微服务架构中,一个系统会被拆分为很多个微服务。如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用
这样的架构,会存在着诸多的问题: 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性。 认证复杂,每个服务都需要独立认证。 存在跨域请求,在一定场景下处理相对复杂。
上面的这些问题可以借助 API 网关 来解决。
所谓的 API 网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。添加上 API 网关之后,系统的架构图变成了如下所示:

服务网关可以对服务器发来的所有请求进行,认证,过滤等等,再也不用分别对每个微服务请求进行认证等工作。认证通过后,网关会去注册中心调用所需的服务进行访问。服务网关除了能对请求进行认证和过滤,还可以对请求限流,如果一个请求频繁的发出会对其进行限制。

消息队列-MQ
什么是 MQ
MQ 全称(Message Queue)又名消息队列,是一种提供消息队列服务的中间件,也称为消息中间件,是一套提供了消息生产、存储、消费全过程 API的软件系统(消息即数据)。通俗点说,就是一个先进先出的数据结构
MQ 的应用场景
最常见的一个场景是用户注册后,需要发送注册邮件和短信通知,传统的做法要等待注册邮箱和短信通知都完成返回结果后才能进行登录,但是对于注册这一功能,只需要将用户输入的信息存储到数据库中后用户即可进行登录,而不需要等待短信的发送,再此场景下可使用消息队列,实际当数据写入注册系统后,注册系统就可以把其他的操作放入对应的消息队列 MQ 中然后马上返 回用户结果,由消息队列 MQ 异步地进行这些操作。
异步解耦是消息队列 MQ 的主要特点,主要目的是减少请求响应时间和解耦。主要的使用场景就是将比耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时,由于使用了消息队列 MQ,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦合

redis实现分布式锁

分布式锁,即分布式系统中的锁。在单体应用中我们通过 java 中的锁解决多线程访问共享资源 的问题,而分布式锁,就是解决了 分布式系统中控制共享资 源访问 的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程.
redis中有一个setnx方法,该方法在每次存储时,会检测key值是否已经存在,若存在则不会存储,利用这一原理可以实现分布式锁。在线程到来时调用 redisTemplate.opsForValue().setIfAbsent(); 
方法(该方法的底层实现为setnx 方法 )判断所要存储的key是否存在,若不存在则存储key并获取到锁,若存在则等候锁的释放。
第一个版本
但是,以上实现存在一个很大的问题,当客户端 1 拿到锁后,如果发生下面的场景就会造成死锁
1. 程序处理业务逻辑异常,没及时释放锁
2. 进程挂了,没机会释放锁
以上情况会导致已经获得锁的客户端一直占用锁,其他客户端永远无法获取到锁。
对以上代码进行改进,在 finally 中释放锁,以及设置键失效时间.

 

但该写法依旧会有问题,假设有两个线程,当第一个线程执行时间大于健的失效时间时,当执行到一半时锁释放,第二个线程获取到锁并执行,这时第一个线程执行完毕,并且执行了删除key这个步骤,但是他删除的是第二个线程所存储的的key值,这样会导致锁失效。
我们可以使用 UUID.randomUUID().tostring();方法生成版本号,在存储健时加上一个版本号,在最后删除时判断该线程的版本号是否一致,这样以来执行时间超过key失效时间的线程就不会出现误删情况

 redis中提供了一个组件,名叫Redission可以简单的实现分布式锁

redission的实现

导入依赖
 <dependency>
 <groupId>org.redisson</groupId>
 <artifactId>redisson</artifactId>
 <version>3.6.5</version>
 </dependency>
创建 Redisson 对象
 @Bean
public Redisson getRedisson(){
 Config config = new Config();
 config.useSingleServer().setAddress("redis://120.48.37.232:6379").setDatabase(0);
 return (Redisson)Redisson.create(config);
 }

在需要使用该对象的地方使用@Autowired标签注入即可

使用 Redisson 实现加锁,释放锁.

 Redission执行过程如下图

 两个线程同时来到,但只有一个线程可以获取到锁,获取失败的锁会不停的尝试继续获取锁直到超时。获取成功的线程 会用UUID和线程id作为KEY存储到redis中,在此期间还会有一个watch dog机制,每过几秒 查看该线程是否还持有锁,如果有会,加长key的存活时间,直到任务执行完毕,删除KEY释放锁。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值