当前位置: 首页 > article >正文

SpringCloud常用企业级别框架整合--上篇

软件架构

单体架构(ALL IN ONE)

集群架构

分布式架构

理解:一个大型应用被拆分成很多小应用,分布部署在各个机器

集群与分布式区分:集群是一种物理形态,而分布式是一种工作方式或架构方式

微服务

微服务架构风格,就像是把一个单独的应用程序开发为一套小服务,每个小服务运行在自己进程中,并使用轻量级机制通信,通常是 HTTP API。这些服务围绕业务能力来构建, 并通过完全自动化部署机制来独立部署。这些服务使用不同的编程语言书写,以及不同数据存储技术,并保持最低限度的集中式管理。

简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行。

远程调用

在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的需要互相调用,我们称为远程调用。 SpringCloud 中使用 HTTP+JSON 的方式完成远程调用

 

负载均衡

分布式系统中,A 服务需要调用 B 服务,B 服务在多台机器中都存在,A 调用任意一个服务器均可完成功能。 为了使每一个服务器都不要太忙或者太闲,我们可以负载均衡的调用每一个服务器,提升网站的健壮性。

常见的负载均衡算法:

  • 轮询:为第一个请求选择健康池中的第一个后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环。
  • 最小连接:优先选择连接数最少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。
  • 散列:根据请求源的 IP 的散列(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。如果你的应用需要处理状态而要求用户能连接到和之前相同的服务器,可以考虑采取这种方式。

 

服务注册/发现&注册中心

A 服务调用 B 服务,A 服务并不知道 B 服务当前在哪几台服务器有,哪些正常的,哪些服务已经下线。解决这个问题可以引入注册中心;

如果某些服务下线,我们其他人可以实时的感知到其他服务的状态,从而避免调用不可用的服务

配置中心

每一个服务最终都有大量的配置,并且每个服务都可能部署在多台机器上。我们经常需要变更配置,我们可以让每个服务在配置中心获取自己的配置。

配置中心用来集中管理微服务的配置信息

 

服务熔断&服务降级

在微服务架构中,微服务之间通过网络进行通信,存在相互依赖,当其中一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。

 

1)、服务熔断

设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启断路保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据

2)、服务降级

在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心业务降级运行。降级:某些服务不处理,或者简单处理【抛异常、返回 NULL、调用 Mock 数据、调用 Fallback 处理逻辑】

API 网关

在微服务架构中,API Gateway 作为整体架构的重要组件,它抽象了微服务中都需要的公共功能,同时提供了客户端负载均衡服务自动熔断灰度发布统一认证限流流控日志统计等丰富的功能,帮助我们解决很多 API 管理难题。

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现配置管理服务管理平台。

服务注册发现

由于每个微服务之间可能会有共同需要使用的工具类,数据库实体类型,以及远程调用的OpenFeign接口等,当这些公共类,放到其中的一个微服务就会出现其它微服务不可调用的情况,为了解决这一问题并简化代码编写,可以将这些公共部分统一抽取到一个Model里面,以导入依赖的形式,在其它的微服务里面进行引用就可以了。

服务注册

跟据以下流程来进行服务注册,可以不在配置文件中声明Nacos地址,因为默认地址就是我们所配置的地址ip,如果想看看集群效果可以在服务板块直接进行配置复制,然后只修改端口号,快速实现集群效果

===============================相关依赖=============================================
        <!--        服务发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

在Nacos服务列表里面。我们可以观看服务的详细信息

服务发现

//======================服务发现相关API功能测试===========================================
@SpringBootTest
public class DIscoveryTest {
​
    @Autowired//注入DiscoveryClient
    //测试服务发现组件,Spring原生组件
    private DiscoveryClient discoveryClient;
​
    @Autowired//注入NacosServiceDiscovery
    //测试服务发现组件,只有Nacos才有这个组件
    private NacosServiceDiscovery nacosServiceDiscovery;
​
    @Test
    public void discoveryClientTest(){
        System.out.println(discoveryClient);
        //获取服务列表
        List<String> services = discoveryClient.getServices();
        services.forEach(service -> {
            System.out.println("service: " + service);
            //根据服务名称,获取实例信息
            List<ServiceInstance> instances = discoveryClient.getInstances(service);
            instances.forEach(instance -> {
                System.out.println("\t" + instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort());
            });
        });
​
​
    }
​
    @Test
    public void nacosServiceDiscoveryTest() throws NacosException {
        System.out.println(nacosServiceDiscovery);
        //获取服务列表名称
        List<String> services = nacosServiceDiscovery.getServices();
        for (String service : services) {
            System.out.println("service: " + service);
            //根据服务名称,获取实例信息。一个服务可能有多个实例
            List<ServiceInstance> instances = nacosServiceDiscovery.getInstances(service);
            for (ServiceInstance instance : instances) {
                System.out.println("\t" + instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort());
            }
        }
​
​
    }
}
//=============================远程调用================================================
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
​
    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private RestTemplate restTemplate;
    @Override
    public Order createOrder(Long userId, Long productId) {
        Order order = new Order();
        Product product = getProductById(productId);
        order.setId(1l);
        //TODO:查询商品单价,数量来计算总价
        BigDecimal price = product.getPrice();
        int num = product.getNum();
        order.setTotalAmount(new BigDecimal(num).multiply(price));
        order.setUserId(userId);
        order.setNickName("胡尔摩斯");
        order.setAddress("贝克街");
        //TODO:跟据商品id查询商品详情列表
        order.setProductList(Arrays.asList(product));
        return order;
    }
​
    private Product getProductById(Long productId) {
        //远程调用商品服务查询商品详情
        List<ServiceInstance> instances = discoveryClient.getInstances("service-product");//获取商品服务的实例列表
        ServiceInstance serviceInstance = instances.get(0);//拿出其中一个实例
        //拼接远程调用的url地址
        //http://localhost:8000/product/4
        String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/product/" + productId;
        log.info("开始远程调用url:{}", url);
        //发起远程调用,get请求
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }
​
}

在上述代码实现中我们发现只能对单一的服务实例进行远程调用只有当此实例宕机下线后才会对其它实例进行远程调用,我们想达到一种调用平衡,不让某一个服务实例承受大量请求,让所有服务实例都可以参与进来。由此我们引入负载均衡这一理论。

负载均衡

======依赖导入======================================================================
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>
 
//=============================负载均衡远程调用=============================================
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
​
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Override
    public Order createOrder(Long userId, Long productId) {
        Order order = new Order();
        Product product = getProductByIdWithLoadBalancer(productId);
        order.setId(1l);
        //TODO:查询商品单价,数量来计算总价
        BigDecimal price = product.getPrice();
        int num = product.getNum();
        order.setTotalAmount(new BigDecimal(num).multiply(price));
        order.setUserId(userId);
        order.setNickName("胡尔摩斯");
        order.setAddress("贝克街");
        //TODO:跟据商品id查询商品详情列表
        order.setProductList(Arrays.asList(product));
        return order;
    }
​
   //测试负载均衡ApI
    private Product getProductByIdWithLoadBalancer(Long productId) {
        //远程调用商品服务查询商品详情
        ServiceInstance choose = loadBalancerClient.choose("service-product");//负载均衡算法选择一个实例,不需要进行手动选择,内置算法实现负载均衡
        //拼接远程调用的url地址
        //http://localhost:8000/product/4
        String url = "http://" + choose.getHost() + ":" + choose.getPort() + "/product/" + productId;
        log.info("开始远程调用url:{}", url);
        //发起远程调用,get请求
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }
​
}

终极模式:基于注解形态的负载均衡,上述负载均衡可以理解为注解形态的底层逻辑代码实现,作为了解。注解形态的负载均衡策略才是开发所常用的,要求掌握。

//====================自定义配置类,在容器里面放入RestTemplate实例对象=========================
@Configuration
public class OrderConfig {
​
    //RestTemplate是线程安全的在容器中只有唯一实例对象存在,
    //在使用用RestTemplate进行远程调用时,如果RestTemplate实例有@LoadBalanced注解支撑,那么使用RestTemplate进行远程调用就是负载均衡的。
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
//=============================负载均衡远程调用=============================================
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
​
    @Autowired
    private RestTemplate restTemplate;
​
    @Override
    public Order createOrder(Long userId, Long productId) {
        Order order = new Order();
        Product product = getProductByIdWithLoadBalancerAnnotation(productId);
        order.setId(1l);
        //TODO:查询商品单价,数量来计算总价
        BigDecimal price = product.getPrice();
        int num = product.getNum();
        order.setTotalAmount(new BigDecimal(num).multiply(price));
        order.setUserId(userId);
        order.setNickName("胡尔摩斯");
        order.setAddress("贝克街");
        //TODO:跟据商品id查询商品详情列表
        order.setProductList(Arrays.asList(product));
        return order;
    }
​
   //测试注解形式负载均衡
      private Product getProductByIdWithLoadBalancerAnnotation(Long productId) {
        //拼接远程调用的url地址
        //http://localhost:8000/product/4
        String url = "http://service-product/product/" + productId;
        //service-product 就是服务名称,底层会通过负载均衡算法选择一个实例
        log.info("开始基于注解形式远程调用url:{}", url);
        //发起远程调用,get请求
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }
}

经典面试题:注册中心宕机,远程调用还能成功吗?

答:如果每次远程调用都要去注册中心获取所有实例信息才完成远程调用,那么性能肯定不好,而且对资源有一定的浪费,所以实际上在向注册中心拿到其中的实例后都会放入缓存中(实例缓存),下一次调用就不需要再到注册中心获取,直接使用缓存数据。注册中心起到同步实时更新缓存的作用。

简而言之:如果没有进行过远程调用,那么当注册中心宕机后,远程调不能通过。如果进行过远程调用,那么当注册中心宕机后,远程调用可以通过。

配置中心

        <!--        引入nacos配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
@RefreshScope//刷新配置,实时更新配置信息
@RestController
public class OrderController {
​
​
    @Value("${order.timeout}")
    private String timeout;
    @Value("${order.configname}")
    private String configname;
​
    //从Nacos配置中拉取到配置信息
    @GetMapping("/Config")
    public String getConfig(){
        return "timeout:"+timeout+" configname:"+configname;
    }
​
}

也可以使用SpringBoot的属性绑定机制来从Nacos配置中心来拉取配置信息,此方法会自带配置属性的自动刷新功能

//=================================属性绑定================================================
@Data
@Component
@ConfigurationProperties(prefix = "order")
public class OrderProperties {
​
    private String timeout;
    private String configname;
}
//=================================测试===================================================
@RestController
public class OrderController {
​
   @Autowired
    private OrderProperties orderProperties;
​
    //从Nacos配置中拉取到配置信息
@GetMapping("/Config")
    public String getConfig(){
        return "timeout:"+orderProperties.getTimeout()+" configname:"+orderProperties.getConfigname();
    }
​
}

经典面试题:微服务项目里有配置在配置中心里面又有声明,那么谁为准?

答:以配置中心为标准,方便配置信息管理,外部配置优先。高优先级的配置会覆盖低优先级的配置。如果一口气导入了多个外部配置,那么以导入的先后顺序来进行配置,先导入配置优先。

数据隔离

在Nacos配置中心完成以下的布局配置操作

server:
  port: 8000
​
spring:
  application:
    name: service-order
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: dev
​
​
  config:
    import:
      - nacos:common.properties?refresh=true?group=order
      - nacos:database.properties?refresh=true?group=order

选择性导入相关配置信息

server:
  port: 8000
​
spring:
  profiles:
    active: dev
  application:
    name: service-order
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        config:
          namespace: ${spring.profiles.active}
​
---
spring:
  config:
    import:
      - nacos:common.properties?refresh=true&group=order
      - nacos:database.properties?refresh=true&group=order
    activate:
      on-profile: dev
​
---
spring:
  config:
    import:
      - nacos:common.properties?refresh=true&group=order
      - nacos:database.properties?refresh=true&group=order
      - nacos:haha.properties?refresh=true&group=order
    activate:
      on-profile: test
​
---
spring:
  config:
    import:
      - nacos:common.properties?refresh=true&group=order
      - nacos:database.properties?refresh=true&group=order
    activate:
      on-profile: prod

总结

OpenFeign

OpenFeign 是一个声明式远程调用客户端

  • 声明式REST客户端VS编程式REST客户端(RESTTemplate)

    • 声明式REST客户端基于注解进行开发代码编程,简化代码,处理细节

    • 编程式REST客户端使用代码进行功能实现,可以理解为注解编码的底层逻辑

远程调用

===================================导入相关依赖===========================================
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
//==============================开启OpenFegin功能=========================================
@EnableFeignClients//开启Feign功能
@EnableDiscoveryClient//开启服务发现
@SpringBootApplication
public class OrderMainApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderMainApplication.class, args);
    }
}
//=======================定义远程调用接口==================================================
@FeignClient(value = "service-product")
public interface ProductFeigin {
​
    //声明远程调用的方法,
    // 技巧:可以直接从需要远程调用的微服务的controller中拷贝其方法签名
​
​
    //MVC中的Mapping注解有两种用法
    //1在MVC框架里面,表示的是接收一个对应的请求路径
    //2.在Feign框架里面,表示的是远程调用的请求路径(发送请求的路径)
    //注意:Feigin是自带负载均衡的。不需要再进行负载均衡的配置了。
​
    @GetMapping("/product/{id}")
    public Product queryProductById(@PathVariable Long id);
​
}

注意:使用@FeignClient注解后一定不要忘记指定value微服务名称,不然就会报错。还有OpenFegin自带负载均衡功能,不需要再进行负载均衡的配置。

//=======================注入并使用自己定义的Openfegin接口===================================
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
​
    @Autowired
    private ProductFeigin productFeigin;
​
    @Override
    public Order createOrder(Long userId, Long productId) {
        Order order = new Order();
        //使用Feign客户端远程调用商品服务查询商品详情
        Product product = productFeigin.queryProductById(productId);
        order.setId(1l);
        //TODO:查询商品单价,数量来计算总价
        BigDecimal price = product.getPrice();
        int num = product.getNum();
        order.setTotalAmount(new BigDecimal(num).multiply(price));
        order.setUserId(userId);
        order.setNickName("胡尔摩斯");
        order.setAddress("贝克街");
        //TODO:跟据商品id查询商品详情列表
        order.setProductList(Arrays.asList(product));
        return order;
    }

小技巧:如何编写好openFeign声明式的远程调用接口?

  • 业务API:自己调用自己写的业务API,直接复制对方微服务Controller的方法签名即可

  • 第三方API:跟据接口文档确定请求如何发送

经典面试题:客户端负载均衡与服务端负载均衡的区别?

答:可以看有没有经过注册中心,如果经过了注册中心那么OpenFegin会从注册中心通过选择的一种算法实现负载均衡发起远程调用。相反如果OpenFegin没有经过注册中心发起远程调用,就是服务端负载均衡生效。

简言之:OpenFegin经过注册中心-->客户端负载均衡策略生效。OpenFegin没有经过注册中心-->服务端的负载均衡策略生效。

进阶配置

日志

# 开启feign的日志,debug级别,这样可以看到请求的详细信息
logging:
  level:
    com.atguigu.cloud.openfeigin: debug
@Configuration
public class OrderConfig {
​
    //给容器里添加fegin的日志组件
    @Bean
    Logger.Level feignLogger() {
        //打印所有请求和响应的信息
        return Logger.Level.FULL;
    }
}
 

在控制台我们就可以看见远程调用的详细信息,请求地址,请求头内容,请求体里携带的数据。返回的数据等

超时控制

  • 连接超时:指的是两个微服务在建立连接的过程中所耗时间大于系统默认或自定义配置的时间

  • 读取超时:指的是成功建立连接,并发送请求后,处理业务所耗时间大于系统默认或自定义配置的时间

  • OpenFegin默认读取超时时间为60s,连接超时时间为10s

spring:
  cloud:
    openfeign:
      client:
        config:
#          指定微服务超时控制配置,这里的service-product指的是微服务的名称,只对特定的微服务有效  
          service-product:
            loggerLevel: full
            connectTimeout: 3000
            readTimeout: 5000
#          默认微服务超时控制配置,所有的微服务都有效  
          default:
            loggerLevel: full
            connectTimeout: 2000
            readTimeout: 3000

注意:如果想要进行测试看看超时控制效果的话,那么线程睡眠代码要在被远程调用的一端进行编写。(针对于是进行读取超时的测试)

重试机制

远程调用失败后还可以进行重试,再次进行远程调用。默认重试机制是关闭的,如果开启重试机制,默认的重试器为Default,默认重试规则为每100ms进行一次重试实际上是上一次重试间隔时间的1.5倍),最多重试5次,最大间隔时间为1s

@Configuration
public class OrderConfig {
​
    //给容器里添加fegin的重试组件
    @Bean
    Retryer retryer() {
        return new Retryer.Default();
    }
}

  • 拓展

在Spring Cloud官网上有这样的描述,OpenFeign不提供以下的组件(没有默认的组件实现),但是OpenFeign会从IOC容器里面去寻找这些组件。有就使用,没有就不使用

拦截器

//===============================拦截器放入容器里面就会生效=================================
@Component
public class XTokenIntercepter implements RequestInterceptor {
​
    /**
     * 请求模板,可以理解为封装了请求的所有信息,包括url、header、body等
     * @param requestTemplate   请求模板
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header("X-Token", UUID.randomUUID().toString());
        System.out.println("请求拦截器执行了");
    }
}
​
//============================测试从请求中拿出信息===========================================
@RestController
public class ProductController {
​
    @Autowired
    private ProductService productService;
​
    //跟据id查询商品详情
    @GetMapping("/product/{id}")
    public Product queryProductById(@PathVariable Long id,
    HttpServletRequest request){
        String XToken = request.getHeader("X-Token");
        System.out.println("header = " + XToken);
        Product product = productService.queryProductById(id);
        return product;
    }
}

FallBack兜底方法

=======================导入依赖===========================================================
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
      
=========================开启熔断==========================================================
feign:
  sentinel:
    enabled: true
//====================Feigin远程调用=======================================================
@FeignClient(value = "service-product" ,fallback = ProductFeiginFallBack.class)
public interface ProductFeigin {
​
    //声明远程调用的方法,
    // 技巧:可以直接从需要远程调用的微服务的controller中拷贝其方法签名
​
​
    //MVC中的Mapping注解有两种用法
    //1在MVC框架里面,表示的是接收一个对应的请求路径
    //2.在Feign框架里面,表示的是远程调用的请求路径(发送请求的路径)
    //注意:Feigin是自带负载均衡的。不需要再进行负载均衡的配置了。
​
    @GetMapping("/product/{id}")
    public Product queryProductById(@PathVariable Long id);
​
}
​
​
//=================自定义兜底方法,放入IOC容器才能生效=========================================
@Component
public class ProductFeiginFallBack implements ProductFeigin {
    @Override
    public Product queryProductById(Long id) {
        Product product = new Product();
        product.setId(id);
        product.setPrice(BigDecimal.valueOf(0.0));
        product.setProductName("未知商品");
        product.setNum(0);
        return product;
    }
}


http://www.kler.cn/a/569727.html

相关文章:

  • Notepad++下载地址【亲测好用】
  • 【0005】Python变量详解
  • 25西医研究生复试面试问题汇总 西医专业知识问题很全! 西医试全流程攻略 西医考研复试调剂真题汇总
  • 【子网掩码计算器:Python + Tkinter 实现】
  • 机器学习数学基础:35.效度
  • Unix Domain Socket和eventfd
  • Minio搭建并在SpringBoot中使用完成用户头像的上传
  • 【机器学习】Logistic回归#1基于Scikit-Learn的简单Logistic回归
  • Element Plus中el-tree点击的节点字体变色加粗
  • chromadb向量数据库使用 (1)
  • 【漫话机器学习系列】114.逻辑 Sigmoid 函数
  • golang 内存对齐和填充规则
  • Redis Desktop Manager(Redis可视化工具)安装及使用详细教程
  • GPU运维常用命令
  • 云原生监控篇——全链路可观测性与AIOps实战
  • centos 7 停更后如何升级kernel版本 —— 筑梦
  • JMeter 使用 CSV 及随机 ID 进行登录与增删改查示例
  • LeetCode 模拟章节 (持续更新中)
  • 从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(五) 实现登录功能
  • 分类预测 | Matlab实现CPO-SVM冠豪猪算法优化支持向量机多特征分类预测