SpringCloud使用笔记

介绍SpringCloud

单体架构

优点:

- 架构简单
- 部署成本低

缺点:

- 耦合度高(维护困难、升级困难)

 

分布式架构

根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务。

分布式架构的优缺点:

优点:

- 降低服务耦合
- 有利于服务升级和拓展

缺点:

- 服务调用关系错综复杂

 

微服务的架构特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责

  • 自治:团队独立、技术独立、数据独立,独立部署和交付

  • 面向服务:服务提供统一标准的接口,与语言和技术无关

  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题


SpringCloud是目前国内使用最广泛的微服务框架;

SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。

 


 

Eureka注册中心

  • 搭建注册中心服务端

创建eureka-server服务

 引入eureka依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

  编写启动类

给eureka-server服务编写一个启动类,一定要添加一个@EnableEurekaServer注解,开启eureka的注册中心功能

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

  

编写配置文件

 

server:
  port: 10086
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url: 
      defaultZone: http://127.0.0.1:10086/eureka

  

 启动服务

启动微服务,然后在浏览器访问:http://127.0.0.1:10086

 

 服务注册

在user-service中引入依赖

 

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

  

配置文件

在user-service中,修改application.yml文件,添加服务名称、eureka地址

spring:
  application:
    name: userservice
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

  

服务发现

引入依赖,在order-service的pom文件中,引入下面的eureka-client依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

  

配置文件

spring:
  application:
    name: orderservice
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

  

Ribbon负载均衡

orderservice去eureka-server中拉取user-service服务的实例列表,并且实现负载均衡。
@Configuration
public class Config {

    @Bean
    @LoadBalanced   // RestTemplate 已经被 Ribbon 代理
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

  

LoadBalancerInterceptor,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。

 

 服务调用

    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private RestTemplate restTemplate;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);
        //利用发送请求
        String url="http://userservice/user/"+order.getUserId();
        User user = restTemplate.getForObject(url, User.class);
        order.setUser(user);
        // 4.返回
        return order;
    }

 

负载均衡策略

 

 

内置负载均衡规则类规则描述
RoundRobinRule 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule

对以下两种服务器进行忽略:

(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。

(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的<clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit属性进行配置。

WeightedResponseTimeRule 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
BestAvailableRule 忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule 随机选择一个可用的服务器。
RetryRule 重试机制的选择逻辑

默认的实现就是ZoneAvoidanceRule,是一种轮询方案

 

自定义负载均衡策略

 

通过定义IRule实现可以修改负载均衡规则,有两种方式:

  1. 代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:

 
@Bean
public IRule randomRule(){
    return new RandomRule();
}

  

  1. 配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:

 
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 

  一般用默认的负载均衡规则,不做修改。

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过在OrderService下面配置开启饥饿加载:

ribbon:
  eager-load:
    enabled: true
    clients: userservice

  

Nacos注册中心

 

Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。

 


引入到父工程依赖

在cloud-demo父工程的pom文件中的`<dependencyManagement>`中引入SpringCloudAlibaba的依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.6.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

  

 然后在user-service和order-service中的pom文件中引入nacos-discovery依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

  

 配置nacos地址

spring:
  cloud:
    nacos:
      server-addr: localhost:8848

  重启微服务后,登录nacos管理页面,可以看到微服务信息

 

服务分级存储模型

一个服务可以有多个实例,例如我们的user-service,可以有:

- 127.0.0.1:8081
- 127.0.0.1:8082
- 127.0.0.1:8083

假如这些实例分布于全国各地的不同机房,例如:

- 127.0.0.1:8081,在上海机房
- 127.0.0.1:8082,在上海机房
- 127.0.0.1:8083,在杭州机房

Nacos就将同一机房内的实例 划分为一个集群。

 微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。

给user-service配置集群

修改user-service的application.yml文件,添加集群配置:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 集群名称

  重启两个user-service实例后,我们可以在nacos控制台看到下面结果

同集群优先的负载均衡

默认的ZoneAvoidanceRule并不能实现根据同集群优先来实现负载均衡。

因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例。

 修改order-service的application.yml文件,修改负载均衡规则:

userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 

  

权重配置

 

实际部署中会出现这样的场景:

服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。

但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。

 

因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。

 

在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重:

 

环境隔离

Nacos提供了namespace来实现环境隔离功能。

  • nacos中可以有多个namespace

  • namespace下可以有group、service等

  • 不同namespace之间相互隔离,例如不同namespace的服务互相不可见

nacos和eureka

  • Nacos与eureka的共同点

    • 都支持服务注册和服务拉取

    • 都支持服务提供者心跳方式做健康检测

  • Nacos与Eureka的区别

    • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式

    • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除

    • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时

    • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

Nacos统一配置管理

在nacos中添加配置文件

 

从微服务拉取配置

微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。

但如果尚未读取application.yml,又如何得知nacos地址呢?

因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取。

第一步

引入nacos-config依赖

首先,在user-service服务中,引入nacos-config的客户端依赖

<!--nacos配置管理依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

第二步

添加bootstrap.yaml

spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev #开发环境,这里是dev 
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名

 

 

Feign远程调用

 

引入依赖

在调用端引入

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

  

添加注解

在order-service(调用端)的启动类添加注解开启Feign的功能:@EnableFeignClients

 

编写Feign的客户端

创建一个client包,创建接口

import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

  

这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:

  • 服务名称:userservice

  • 请求方式:GET

  • 请求路径:/user/{id}

  • 请求参数:Long id

  • 返回值类型:User

这样,Feign就可以帮助我们发送http请求。

测试

@RestController
@RequestMapping("order")
public class OrderController {

   @Autowired
   private OrderService orderService;

    @GetMapping("{orderId}")
    public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
        // 根据id查询订单并返回
        return orderService.queryOrderById(orderId);
    }
}

  

使用Feign的步骤:

① 引入依赖

② 添加@EnableFeignClients注解

③ 编写FeignClient接口

④ 使用FeignClient中定义的方法

Feign的最佳实践

Feign的客户端与服务提供者的controller代码非常相似,通过抽取简化这种重复的代码编写

将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用。

 首先创建一个module,命名为feign-api

在feign-api中然后引入feign的starter依赖

 
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

  然后,order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中

在order-service中使用feign-api

在order-service的pom文件中中引入feign-api的依赖

<dependency>
    <groupId>cn.itcast.demo</groupId>
    <artifactId>feign-api</artifactId>
    <version>1.0</version>
</dependency>

解决扫描包问题

方式一:

@EnableFeignClients(clients = {UserClient.class})

方式二:

@EnableFeignClients(basePackages = "cn.itcast.feign.clients")

  

 Gateway服务网关

 为微服务架构提供一种简单有效的统一的 API 路由管理方式。

网关的核心功能特性

  • 请求路由

  • 权限控制

  • 限流

权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。

路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。

限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

 

创建gateway服务,引入依赖

创建服务:gateway微服务

依赖:

<!--网关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

  

编写启动类

 

编写基础配置和路由规则

创建application.yml文件,内容如下

server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求

  

网关搭建步骤:

  1. 创建项目,引入nacos服务发现和gateway依赖

  2. 配置application.yml,包括服务基本信息、nacos地址、路由

路由配置包括:

  1. 路由id:路由的唯一标示

  2. 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡

  3. 路由断言(predicates):判断路由的规则,

  4. 路由过滤器(filters):对请求或响应做处理

 

解决跨域问题

spring:
  cloud:
    gateway:
      # 。。。
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求 
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

  

服务熔断Hystrix

雪崩效应

 在微服务架构中,一个请求需要调用多个服务是非常常见的。
 如客户端访问A服务,而A服务需要调用B服务,B服务需要调用C服务,
 由于网络原因或者自身的原因,如果B服务或C服务不能及时响应
 ,A服务将处于阻塞状态,直到B服务C服务响应。
 此时如果有大量的请求涌入,容器的线程资源会被消耗完毕,
 导致服务瘫痪。服务和服务之间的依赖性,故障会传播,造成连锁反应,
 会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
 
 雪崩是系统中的蝴蝶效应,导致其发生的原因多种多样,有不合理的容量设计,
 或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。
 从源头上我们无法完全杜绝雪崩源头的发生,
 但是雪崩的根本原因来源于服务之间的强依赖,
 所以我们可以提前评估,做好熔断、隔离、限流。

  

服务隔离

 顾名思义,它是指系统按照一定的原则划分若干个服务模块,
 各个模块之间相互独立,无强依赖性。
 当有故障发生时,能将问题和影响隔离在某个模块内部,
 而不扩散风险,不涉及到其他模块,不影响整体的系统服务。

  

熔断降级

 当下游服务因为当问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,
 
 可以暂时切断对下游服务的调用。
 
 这种牺牲局部,保全整体的措施就叫熔断。
 
  所谓降级,就是当某个服务熔断之后,服务将不再被调用,
 
 此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。也可以理解为兜底。
 
 ##

服务限流

 限流可以认为是服务降级的一种,限流就是限制系统的输入和输出流量来达到保护系统的目的。
 
 一般来说,系统的吞吐量是可以被测算的,为了保证系统的稳固运行,一旦到达需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。
 
 比如:推迟解决、拒绝解决或者是部分拒绝解决等等。

  

Feign实现服务降级

 引入依赖

 <!-- openfeign -->
 
 <dependency>
 
    <groupId>org.springframework.cloud</groupId>
 
    <artifactId>spring-cloud-starter-openfeign</artifactId>
 
 </dependency>

  修改yml在Feign中开启Hystrix

 feign:
 
  hystrix: # 开启Feign中的Hystrix
 
    enabled: true

  

自定义Feign接口的实现类,这个实现类就是熔断触发的降级逻辑

 import com.sunxiaping.domain.Product;
 
 import com.sunxiaping.feign.ProductFeignClient;
 
 import org.springframework.stereotype.Component;
 
 
 /**
 
  * 自定义Feign接口的实现类
 
  */
 
 @Component
 
 public class ProductFeignClientCallBack implements ProductFeignClient {
 
    @Override
 
    public Product findById(Long id) {
 
        Product product = new Product();
 
        product.setProductName("熔断降级了");
 
        return product;
 
    }
 
 }

  

修改Feign接口添加降级方法的支持

 import com.sunxiaping.domain.Product;
 
 import com.sunxiaping.feign.impl.ProductFeignClientCallBack;
 
 import org.springframework.cloud.openfeign.FeignClient;
 
 import org.springframework.web.bind.annotation.GetMapping;
 
 import org.springframework.web.bind.annotation.PathVariable;
 
 
 @FeignClient(value = "service-product",fallback = ProductFeignClientCallBack.class)
 
 public interface ProductFeignClient {
 
 
    @GetMapping(value = "/product/findById/{id}")
 
    Product findById(@PathVariable(value = "id") Long id);
 
 
 }

 

posted @ 2021-08-24 12:46  CooperMiNi  阅读(150)  评论(0)    收藏  举报