springcloud初窥门径-附完整源码

重点⚠️⚠️

  • ⚠️源码在文章末尾⚠️

一、概述

SpringCloud组成部分

在这里插入图片描述

SpringCloud主流组件

在这里插入图片描述

组件选型,结合SpringCloudAlibaba

在这里插入图片描述

二、构建项目

1.父工程构建注意事项

  1. 构建方式使用<packaging>pom</packaging>, 用于管理子工程的依赖和版本,并不会生成真实的构建物
  2. 编码规范
    在这里插入图片描述

2.Alibaba工程规约

可以参考文档:阿里巴巴代码规范-黄山版

  1. Alibaba工程规约,主要指分层,和各层传输对象的命名方式
  • 项目分层:
    在这里插入图片描述
  • 命名规约
    在这里插入图片描述
  1. 个人理解
  • VO
    VO作为Controller层向外展示的对象
    VO在进入service层之前,需要转换为DTO
  • DTO
    DTO作为service向controller返回的对象
    DTO也可以作为Controller方法的入参(保存,更新等)
    DTO也作为service层方法的入参
    DTO在进入dao层之前需要转换为DO
  • DO
    DO作为dao层的方法入参
    DO也作为dao层的方法出参
  • Query
    Query作为controller方法的入参(多个查询参数的组合)
    Query在service层不用转换,可以直接作为service方法的入参
    Query在进入dao层之前转换为DO

3.如果lombok失效,查看一下下面是否勾选(每个模块都要勾选)

在这里插入图片描述

三、Eureka

充当服务注册发现中心,Zookeeper和Nacos也可以
在这里插入图片描述

1.概述

  1. Eureka包含两个组件
  • Eureka Server
    提供服务注册
  • Eureka Client
    通过注册中心发现服务

2.搭建单机版Eureka Server

  1. 添加依赖
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
  1. 添加yml配置
  • application.yml
server:
  port: 8001

spring:
  profiles:
    active: eureka
  application:
    name: cloud-eureka-service
  • application-eureka.yml
spring:
  config:
#    引入其他配置文件
    import: application.yml
eureka:
  instance:
    hostname: localhost
  client:
#    表示不注册eureka自己
    register-with-eureka: false
#    维护服务实例,不需要检索服务
    fetch-registry: false
    service-url:
#      设置eureka的地址
     defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka

3.启动类开启Eureka

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApp {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApp.class,args);
    }
}
  1. 检验是否搭建好
  • 输入localhost:8001查看
    在这里插入图片描述

3.微服务提供者,添加至EurekaClient端

  1. 添加pom依赖
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  1. 添加yml文件
spring:
  config:
#    引入其他配置文件
    import: application.yml
eureka:
  instance:
    hostname: localhost
  client:
#    表示不注册eureka自己
    register-with-eureka: true
#    维护服务实例,不需要检索服务
    fetch-registry: true
    service-url:
#      设置eureka的地址
     defaultZone: http://${eureka.instance.hostname}:8001/eureka
  1. 启动类添加client注解
@SpringBootApplication
@EnableEurekaClient
public class PaymentApp {

    public static void main(String[] args) {
        SpringApplication.run(PaymentApp.class,args);
    }
}

4.微服务消费者,添加至EurekaClient端

和微服务提供者一样的步骤

5.搭建集群版Eureka Server

  1. server端
  • 再搭建一个eureka server,设置成彼此的eureka地址即可
    在这里插入图片描述
  1. client端
  • 将client端的配置,eureka改成集群地址即可
    在这里插入图片描述
  1. 效果
    在这里插入图片描述

6.服务提供者搭建集群版

  1. 多个服务的服务名应一样,地址不一样(IP,端口)
    在这里插入图片描述

7.消费者通过微服务调用

在本次演示中,消费者使用RestTemplate调用,底层也是Http协议

  1. 在不使用微服务的情况下,调用方式如下
  • 配置RestTemplate,RestTemplate主要用于服务之间的HTTP请求调用
@Configuration
public class ApplicationContextConfig {

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
  • 配置调用URL
provider:
  payment:
    host: http://localhost:9000
  • 程序中调用
	@Value("${provider.payment.host}")
    private String paymentHost;

    @Override
    public PaymentDTO getPaymentById(Integer id) {
        // 定义要发送的请求 URL,使用占位符来表示路径参数
        String url = paymentHost + "/payment/{id}";
        // 构建请求并获取响应
        ResponseEntity<JSONObject> response = restTemplate.getForEntity(url, JSONObject.class, id);
        JSONObject responseBodyJson = response.getBody();
        JSONObject dataJson = responseBodyJson.getJSONObject("data");
        String dataString = dataJson.toJSONString();
        PaymentDTO paymentDTO = JSONObject.parseObject(dataString, PaymentDTO.class);
        return paymentDTO;
    }
  1. 使用微服务调用方式如下:
  • 需要将RestTemplate配置为负载均衡,不然单依靠微服务名称无法寻找到到底是哪个提供者提供了服务
@Configuration
public class ApplicationContextConfig {

    /**
     * @LoadBalanced 赋予RestTemplate负载均衡能力
     * @param
     * @return org.springframework.web.client.RestTemplate
     */
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
  • 需要修改yml配置,将远程的ip换成微服务名称
provider:
  payment:
    host: http://CLOUD-PAYMENT-SERVICE

8.actuator信息完善,设置EurekaServer别名

  1. 将Eureka的yml配置修改一下,添加一个instance-id
spring:
  config:
#    引入其他配置文件
    import: application.yml
eureka:
  instance:
    instance-id: payment9000
  client:
#    表示不注册eureka自己
    register-with-eureka: true
#    维护服务实例,不需要检索服务
    fetch-registry: true
    service-url:
#      设置eureka的地址
     defaultZone: http://eureka8001.com:8001/eureka,http://eureka8002.com:8002/eureka
  • 修改后的名称
    在这里插入图片描述

9.通过接口查找Eureka的注册信息

  1. 首先在启动类添加注解@EnableDiscoveryClient
  • 该注解的作用是:让注册中心能够发现,扫描到该服务
  • 和@EnableEurekaClient的区别:@EnableEurekaClient针对于Eureka注册中心,而@EnableDiscoveryClient针对其他注册中心(Zookeeper,Nacos)
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentApp {

    public static void main(String[] args) {
        SpringApplication.run(PaymentApp.class,args);
    }
}
  1. 业务代码
	@GetMapping("discovery-service")
    public ResultVO discoveryService(){
        try {
            // 获取微服务清单
            List<String> services = discoveryClient.getServices();
            for (String service : services){
                List<ServiceInstance> instances = discoveryClient.getInstances(service);
                for (ServiceInstance instance : instances){
                    log.info("service : {} , host : {} ,port : {}, uri: {}",instance.getServiceId(),instance.getHost(),instance.getPort(),instance.getUri());
                }
            }
            return ResultVO.success();
        } catch (Exception e) {
            return ResultVO.failure(ResultCode.EUREKA_ERROR);
        }
    }

10.保护模式

某时刻某个微服务不可用了(有可能因为网络分区导致短暂的不可用),Eureka不会立即注销该服务,而是将该服务的信息保存
可以取消自我保护,具体可以百度,这里不做过多解释
在这里插入图片描述

四、ZooKeeper

⚠️注意⚠️

如果使用docker启动zookeeper,记住把端口映射,并且关闭防火墙(或者开放端口)

1.概述

2.ZooKeeper

  1. 修改配置文件,application-zookeeper.yml
spring:
  config:
#    引入其他配置文件
    import: application.yml

  cloud:
    zookeeper:
      connect-string: localhost:2181
  1. 主启动类开启服务发现
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentApp {

    public static void main(String[] args) {
        SpringApplication.run(PaymentApp.class, args);
    }
}
  1. 进入zookeeper中查看
docker exec -it zookeeper /bin/bash

cd bin

zkCli.sh

ls /services
  1. zookeeper服务节点为临时节点

五、consul

1.准备

  1. docker启动consul
  2. 引入依赖
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
  1. yml配置:
spring:
  config:
    #    引入其他配置文件
    import: application.yml
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

六、Ribbon

1.背景

  1. 原本使用RestTemplate调用远程服务,使用轮询算法。Ribbon是客户端使用多种策略调用远程微服务的中间件,进行负载均衡
  2. Ribbon本地负载均衡 VS Nginx服务端负载均衡
  • Nginx是在用户访问的第一层进行负载均衡,将用户请求均匀分散到多个服务中。进入服务后,Ribbon首先获取远端微服务接口,在本地进行分散请求,按照某个算法请求到远端微服务(轮询、随机等)。因此Nginx又叫集中式负载均衡,Ribbon叫进程内负载均衡。

2. 使用

  1. Ribbon不能和@ComponentScan放在一个包下面,及@SpringBootApplication主启动类的包下面(该注解内部包含@ComponentScan)
  2. 自定义规则
@Configuration
public class MyRule {

    @Bean
    public IRule myRule(){
        // d定义为随机
        return new RandomRule();
    }
}
  1. 启动类开启开启Ribbon客户端
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD_PAYMENT_SERVICE", configuration = MyRule.class)
public class OrderApp {

    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }

}

七、openfeign

1.准备

  1. 添加依赖
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. 服务消费者启动类添加注解@EnableFeignClients
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderApp {

    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }

}
  1. 添加接口,注意请求接口的参数需要指定名字,比如@PathVariable(“id”
@Service
@FeignClient(name = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    @GetMapping("payment/{id}")
    ResultVO feignGetPayment(@PathVariable("id") Integer id);

}
  1. controller调用该接口就行
@GetMapping("feign/payment/{id}")
    public ResultVO feignGetPayment(@PathVariable Integer id){
        ResultVO resultVO = paymentFeignService.feignGetPayment(id);
        return resultVO;
    }

2.openfeign超时控制

  • 在配置文件中设置

八、Hystrix

背景

  1. 服务降级是什么
    当服务达到某种状态时,返回给客户端一个响应,避免让客户端一直等待
  2. 什么时候会出发服务降级
  • 程序运行异常
  • 超时
  • 服务熔断
  • 线程池打满
  1. 服务熔断是什么
    服务达到最大并发事,直接拒绝访问
  2. 服务限流
    将服务的访问限制在一个访问内,有序进行

1.服务降级

一般服务降级放在消费端

服务端使用
  1. 当服务端请求压力变大,客户端请求会存在等待比较久,一直阻塞。或者服务端报错,都需要给消费端提供一个友情提示,又叫服务降低,就是有个兜底的方案
  • 启动类加注解@EnableCircuitBreaker
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableCircuitBreaker
public class PaymentApp {

    public static void main(String[] args) {
        SpringApplication.run(PaymentApp.class,args);
    }
}
  • 业务类使用hystrix提供降级方案
@HystrixCommand(fallbackMethod = "requestErrorHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")})
    @Override
    public String requestError(Integer id) {
        try {
            Thread.sleep(5000);
            return Thread.currentThread().getName()+",error,"+id;
        } catch (InterruptedException e) {
            return "error";
        }
    }

    public String requestErrorHandler(Integer id){
        return Thread.currentThread().getName()+"系统繁忙,请稍后再试,"+id;
    }

在这里插入图片描述

消费端使用
  1. 当消费者请求服务者,消费者也需要做一个超时的限制
  • 启动类添加注解@EnableHystrix
@SpringBootApplication
@EnableEurekaClient
//@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRule.class)
@EnableFeignClients
@EnableHystrix
public class OrderApp {

    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }

}
  • 业务类处理
	@HystrixCommand(fallbackMethod = "requestPaymentErrorHandler",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    @Override
    public String requestPaymentError(Integer id) {
        String response = restTemplate.getForObject(paymentHost + "/payment/hystrix/error/"+id, String.class);
        return response;
    }

    public String requestPaymentErrorHandler(Integer id){
        return "服务者超时";
    }

在这里插入图片描述

全局降级策略

遇到的问题

  • 每个方法都需要有一个降级措施,导致代码冗余
  • 统一的降级措施和自定义的措施应该分开
  • 降级策略和业务逻辑耦合较高

1.需要解决问题,就需要配置全局降级策略

  • 类上加注解@DefaultProperties(defaultFallback = “globalErrorHandler” )
  • 提供全局降级方案(空参方法)
	public String globalErrorHandler(){
        return "全局降级策略";
    }
  • 对需要进行全局降级处理的方法加注解
	@HystrixCommand
    @Override
    public String requestError(Integer id) {
//            Thread.sleep(5000);
        int a = 10 / 0;
        return Thread.currentThread().getName()+",error,"+id;
    }

解决剪辑策略与业务逻辑耦合较高的问题

观察到一个问题,上面的降级策略与业务逻辑耦合在一起,需要进行解耦

  1. 需要使用OpenFeign进行微服务之间调用,需要开启OpenFeign对Hystrix的支持
feign:
  hystrix:
    enabled: true
  1. 需要将远程调用的微服务接口加入到OpenFeign的接口当中,并且@FeignClient的注解需要添加服务降级处理的类
@Service
@FeignClient(name = "CLOUD-PAYMENT-SERVICE",fallback = PaymentFeignServiceImpl.class)
public interface PaymentFeignService {

    @GetMapping("payment/{id}")
    ResultVO feignGetPayment(@PathVariable("id") Integer id);

    @GetMapping("payment/hystrix/success/{id}")
    String requestPaymentSuccess(@PathVariable("id") Integer id);

    @GetMapping("payment/hystrix/error/{id}")
    String requestPaymentError(@PathVariable("id") Integer id);
}
  1. 服务降级的类需要实现OpenFeign调用微服务的接口,并且在对应的方法中添加降级处理策略
@Service
@Slf4j
public class PaymentFeignServiceImpl implements PaymentFeignService {
    @Override
    public ResultVO feignGetPayment(Integer id) {
        return null;
    }

    @Override
    public String requestPaymentSuccess(Integer id) {
        return "requestPaymentSuccess降级策略";
    }

    @Override
    public String requestPaymentError(Integer id) {
        return "requestPaymentError降级策略";
    }
}
  1. 测试,当停掉远程服务端,消费端会进入降级处理
    在这里插入图片描述

2.服务熔断

  1. 概念

类比保险丝,达到最大访问时,直接拒绝访问,出发服务降级策略,返回友好提示
熔断机制是应对雪崩效应的一种微服务链路保护机制,当某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后(在间歇时间中不检查),恢复调用链路

  1. 步骤
  • 在方法上加注解,开启熔断,制定降级策略
	@HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallBack",commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled",value = "true"), // 是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), // 在滑动窗口中收集请求次数,才会检查断路器状态
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 间歇时间。如果在这段时间内,发生断路的请求数超过了设置数则会熔断;如果发生了熔断,那么在这个时间后会成为半开状态
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), // 错误百分比,到达百分比后会开启断路器
    })
    public String paymentCircuitBreaker(Integer id){
        if (id < 0){
            throw new RuntimeException("id 不能为负数");
        }
        return "调用成功";
    }

    public String paymentCircuitBreakerFallBack(Integer id){
        return "id:" + id + "不能为负数,请稍后再试!";
    }
  • 当调用该方法时,如果id为负数,并且请求次数到熔断策略的要求,会进行服务熔断,直接进入降级策略
    在这里插入图片描述
  • 间歇时间中,请求一直是降级策略
  • 间歇时间过后,断路器进入半开状态。会放开一个请求,如果服务请求正确,则回复主逻辑;如果服务请求错误,继续打开熔断器,休眠窗口重新计数。

3.HystrixDashboard

九、GateWay

背景

  1. 三大核心概念
  • Route(路由):由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
  • Predicate(断言):开发人员可以匹配HTTP请求中的所有内容(例如请求头和请求参数),如果请求与断言相匹配则进入路由
  • Filter(过滤)指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改
  1. 为什么有Nginx了还要GateWay
  • Nginx和GateWay都可以做负载均衡,但是GateWay能够根据服务信息,动态调整路由
  • GateWay可以进行鉴权等功能

使用

  1. 首先添加gateway工程,注意pom依赖只需要三个
	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>
  1. 在gateway的配置文件添加路由和断言,这里目前使用的uri是IP+PORT
spring:
  application:
    name: cloud-gateway
  profiles:
    active: eureka
  cloud:
    gateway:
      routes:
        - id: payment_9000 #名称唯一就行
          uri: http://localhost:9000
          predicates:
            - Path=/payment/lb/**
        - id: payment_9001
          uri: http://localhost:9001
          predicates:
            - Path=/payment/*
  1. 实现动态路由

通过上面的配置,可以观察到uri被写死,如果使用微服务,这种路径是不可以的,因此需要实现动态路由。

  • 使用微服务名进行路由,并且进行负载均衡
spring:
  application:
    name: cloud-gateway
  profiles:
    active: eureka
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_9000
          uri: lb://cloud-payment-service # 提供服务的 微服务 地址
          predicates:
            - Path=/payment/lb/**
        - id: payment_9001
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/*
  1. 常用的predicates
  1. Filter

使用过滤器,在路由前或路由后进行的一些操作
spring cloud filter

  • 生命周期
    pre
    post
  • 种类
    GatewayFilter
    GlobalFilter

尝试自定义过滤器:全局日志记录、统一网关鉴权

  • 添加Filter类
@Service
@Slf4j
public class GatewayLogFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("********come in GatewayLogFilter"+new Date());
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname == null){
            log.info("**********用户名为null,非法参数!");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值