4.SpringCloud 基本架构

1.SpringCloud概述

Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线,一次性令牌,全局锁,领导选举,分布式会话,集群状态。(用来管理项目)

2.注册中心Eureka

Spring Cloud Eureka 是对Netflix公司的Eureka的二次封装,它实现了服务治理的功能,Spring Cloud Eureka提供服务端与客户端,服务端即是Eureka服务注册中心,客户端完成微服务向Eureka服务的注册与发现。服务端和客户端均采用Java语言编写。

服务器将自己的信息(IP、端口号、服务类) 提供给注册中心,然后每个服务器将所有存在注册中心的注册信息保存一份在自己服务器中,从而实现服务器之间的交流

3. 分布式项目搭建

在一个分布式项目中,通常会采用父项目和子项目的方式来进行模块化开发和管理。父项目用于管理整个项目的共享依赖和配置,而子项目则代表着项目的不同模块或子系统。

3.1 创建父项目

可以删除父项目中的src,父项目不需要写代码

在父项目pom.xml中指定打包方式为pom

<packaging>pom</packaging>

3.2 创建子项目

子项目pom.xml中指定父项目

<parent>

<artifactId>springcloud-teach</artifactId>

<groupId>com.luobei</groupId>

<version>1.0</version>

</parent>

父项目pom.xml中指定子项目

<modules>

<module>eureka-server</module>

</modules>

配置yml配置文件

XML 复制代码
server:
  port: 8081
spring:
  application:
    name: goods   #微服务可以根据微服务的名字自动形成集群,微服务的名字中不要出现下划线
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/    #向这个接口注册
  instance:
    instance-id: goods-8081     #指定当前微服务在注册中心中的id

主启动类上开启 @EnableEurekaClient

java 复制代码
@SpringBootApplication
@EnableEurekaClient
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

3.2.1 创建注册中心

注册中心其实就是一个特殊的子项目,除yml配置不同,其他与别的子项目一致

XML 复制代码
server:
  port: 8761
spring:
  application:
    name: eureka-server   #微服务可以根据微服务的名字自动形成集群,微服务的名字中不要出现下划线
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false   #是否将当前服务器注册到注册中心
    fetch-registry: false    #是否将注册列表的信息拉取到本地
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}    
        #注册中心的接口,其他微服务器通过该接口进行注册

主启动类上开启@EnableEurekaServer

java 复制代码
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

4.微服务调接口

在微服务架构中,服务之间的通信是通过调用接口来实现的。这些接口可以是 RESTful API、gRPC、消息队列等方式实现的。

4.1 RestTemplate(仅了解)

属于SpringBoot

缺点:put、delete请求得不到返回的结果,使用、维护不方便

4.2 openfeign

OpenFeign 是一个基于 Java 的声明式 HTTP 客户端,通常用于微服务架构中不同服务之间的接口调用。它允许开发者通过注解的方式定义接口,然后由 OpenFeign 自动生成具体的实现。这样可以简化服务之间的通信,让接口调用看起来更像是本地方法调用。

4.2.1 openfeign使用

4.2.1.1 导入依赖

父项目:

XML 复制代码
  <dependencyManagement>
    <dependencies>
      <!--openfeign-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-openfeign-core</artifactId>
        <version>2.2.6.RELEASE</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

公共模块:

XML 复制代码
<!--openfeign-->
<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
      <version>2.2.6.RELEASE</version>
</dependency>

子项目:

XML 复制代码
<!--openfeign-->
<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-openfeign-core</artifactId>
</dependency>

4.2.1.2 编写接口

若接口将会被多个微服务调用则写在commons中

java 复制代码
@FeignClient(name="GOODS",path="/goods")
public interface FeignGoodsService {
    @PutMapping("/updateStock")
    ResponseResult<List<Goods>> del(@RequestBody List<OrderGoodsVo> orderGoodsVoList);
}

@FeignClient(name = "GOODS",path = "/goods") 中name为被调用微服务的名字,path为路径前缀,将要调用的接口方法的声明、请求方式全部拷贝过来

4.2.1.3 使用接口

在需要使用接口的微服务的主启动类上扫描feign接口

@EnableFeignClients(basePackages = "com.luobei.commons.service")

然后在需要使用的地方注入就行了

java 复制代码
@Resource
private FeignGoodsService feignGoodsService;
@Override
public OrderVo add(Integer uid, AddOrderVo orderVo) throws OutOffStockException {
    ResponseResult<List<Goods>> del = feignGoodsService.del(Arrays.asList(orderGoodsVo));
    return del;
}

4.2.2 openfeign通信日志

openfeign提供了日志打印功能,通过配置日志级别,对接口的调用情况进行监控和输出

日志级别 解释
NONE 默认的,不显示任何日志
BASIC 仅记录请求方法、URL、响应状态码及执行时间
HEADERS 除了 BASIC 中定义的信息之外,还有请求和响应的头信息
FULL 除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据

在调用别的服务器的服务器配置配置类

java 复制代码
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LogConfiguration {
    @Bean
    public Logger.Level level(){
        return Logger.Level.FULL;
    }
}

因为feign日志的输出级别都是debug级别,还需要设置service包的日志级别

logging:

level:

com.commons.service: debug

5. Ribbon负载均衡

Ribbon是一个基于HTTP和TCP客户端的负载均衡器,默认使用轮询的方式

SpringCloud Ribbon是基于Netfix Ribbon实现的一套客户端负载均衡工具

5.1 Ribbon配置

5.1 .1 全局配置

在需要调取多个服务器的服务器中配置配置类

@Configuration

public class RibbonConfiguration {

@Bean

public IRule rule(){

return new RandomRule();

}

}

5.1.2 局部配置

注意:全局与局部同时存在时,ribbon优先使用全局配置

GOODS: #微服务名字

ribbon:

NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule

5.2 Ribbon超时管理

Ribbon默认请求超时时间为1000毫秒,有的时候不够用,因此需要重新配置

ribbon:

http:

client:

enabled: true #开启超时管理

ReadTimeout: 10000 #请求超时

ConnectTimeout: 10000 #连接超时

6.网关(Gataway)

网关是在分布式系统架构中的一种设计模式,用于集中处理和管理请求;微服务架构中常用网关技术有:Zuul、Spring Cloud Gataway等

6.1 网关的作用

1.路由和负载均衡:网关可以根据请求的路径或其他条件将请求路由到不同的后端微服务

2.鉴权和认证:网关可以进行用户身份验证、授权以及访问权限的校验

3.监控和日志:网关可以记录请求和响应的日志,从而监控系统的运行状况、性能和问题

6.2 Zuul

Zuul 是 Netflix 开源的一个微服务架构中的边缘服务(Edge Service),主要用于实现动态路由、负载均衡、鉴权、监控等功能。(性能不如Gataway)

6.2.1 zuul使用

6.2.1.1 创建zuul微服务

连接父项目。。

导入zuul依赖

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-netflix-zuul</artifactId>

</dependency>

6.2.1.2 配置eureka、路由

XML 复制代码
server:
  port: 8500
spring:
  application:
    name: zuul   #微服务名字,很重要,微服务可以根据微服务的名字自动形成集群,微服务的名字中不要出现下划线
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/    #向这个接口注册
  instance:
    instance-id: zuul-8500     #指定当前微服务在注册中心中的id
zuul:
  routes:
    goods:                 #路由名,用户自定义
      service-id: GOODS    #调用的微服务名
      path: /goods/**      #匹配的路径
    order:
      service-id: ORDER
      path: /order/**
#    GOODS: /goods/**
#    ORDER: /order/**

注意:路由有两种配置方式,都可

6.2.1.3 zuul主启动类添加注解

java 复制代码
@SpringBootApplication
@EnableEurekaClient    //注册
@EnableZuulProxy        //开启路由功能
@EnableHystrix             //解决504报错问题
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
}

注意:使用zuul时请求地址应多一个请求的微服务名(http://localhost:8500/goods/goods/all)

6.2.2 zuul过滤器实现网关限流

zuul过滤器
PRE 请求到达zuul之前执行,可以用来鉴权、判断登录、限流
ROUTING 路由时(将请求转发给对应的微服务)执行
POST 路由完毕时(微服务返回结果)执行
ERROR 在pre、routing、post出现异常时执行

自定义过滤器使用令牌桶算法实现网关限流

java 复制代码
@Slf4j
@Component
public class LimitFilter extends ZuulFilter {

    //创建令牌桶,并设置令牌个数
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(1);

    //指定当前过滤器类型
    @Override
    public String filterType() {
        return "pre";
    }
    //执行顺序,可以为负数,数字越小越先执行
    @Override
    public int filterOrder() {
        return Integer.MIN_VALUE;
    }
    //是否要对当前请求进行过滤
    @Override
    public boolean shouldFilter() {
        //得到请求的uri,判断是否需要登录,如果不需要返回false(放行),否则返回true(执行run()方法)

        //得到上下文对象
        RequestContext context = RequestContext.getCurrentContext();

        HttpServletRequest request = context.getRequest();
        log.info(request.getRequestURI());
        return true;
    }

    //执行过滤业务的代码:例如校验refreshtoken、token
    @Override
    public Object run() throws ZuulException {
        //获取令牌
        if(!RATE_LIMITER.tryAcquire()){
            //令牌用完,至少有100个请求正在处理
            log.info("令牌不足");
            //结束本次请求
            RequestContext context = RequestContext.getCurrentContext();
            context.setSendZuulResponse(false);
            HttpServletResponse response = context.getResponse();
            try {
                response.setContentType("text/html;charset=utf-8");
                response.getWriter().write("系统正忙");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return null;
    }
}

6.2.2.1 漏桶算法

简单来说,把我们的分布式系统理解成一个上方注水下方漏水的桶,固定下方漏水的速率,这样就能限制请求的速度,当水超过桶流量则丢弃

6.2.2.2 令牌桶算法

每个请求向从桶里获取一个令牌,若令牌没有了则拒绝或等待请求处理完成的返回令牌;

6.3 Gataway

SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty,因此Gataway的性能比较高效

注意:gataway基于webflux,因此与spring web 不兼容

6.3.1 使用Gataway

创建Gataway子项目,连接父项目

主启动类开启@EnableEurekaClient

6.3.1.1 配置配置类

XML 复制代码
server:
  port: 8600
spring:
  application:
    name: gataway   #微服务名字,很重要,微服务可以根据微服务的名字自动形成集群,微服务的名字中不要出现下划线
  cloud:
    gateway:
      routes:
        - id: goods             #路由名(任意)
          uri: lb://GOODS       #要调用的微服务   lb:负载均衡
          predicates:           #匹配的路径
            - Path=/goods/**
        - id: order
          uri: lb://ORDER       #要调用的微服务   lb:负载均衡
          predicates:           #匹配的路径
            - Path=/order/**
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/    #向这个接口注册
  instance:
    instance-id: gataway-8600     #指定当前微服务在注册中心中的id

6.3.1.2 配置过滤器

java 复制代码
@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("gateway过滤器");
        //获取uri
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        log.info(path.toString());
        if(requireLogin(path)){
            //需要登录
            List<String> authorization = request.getHeaders().get("authorization");
            if(authorization!=null){
                String token = authorization.get(0);
                log.info(token);
                return chain.filter(exchange);
            }else{
                //没登陆
                ServerHttpResponse response = exchange.getResponse();
                ResponseResult<Boolean> responseResult = new ResponseResult<>(ResponseResult.NO_LOGIN,"请登录",false);
                try {
                    //转换成json字符串
                    String json = new ObjectMapper().writeValueAsString(responseResult);

                    //转byte数组
                    byte[] data = json.getBytes(StandardCharsets.UTF_8);

                    //将数组封装到buffer
                    DataBuffer buffer = response.bufferFactory().wrap(data);

                    //设置响应头
                    response.getHeaders().add("Content-Type","application/json;charset=utf-8");

                    //返回数据,并终止
                    return response.writeWith(Mono.just(buffer));
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
            }
        }
        //放行
        return chain.filter(exchange);
    }
    @Override
    public int getOrder() {
        return 0;
    }
    private boolean requireLogin(String uri){
        //将不需要登录就能操作的uri罗列出来
        String[] uris = {"/user/login","/user/register","/kill/info","/nginx/port"};
        String[] staticResource = {"/goods/info/","/goods/find/","/images/"};
        for (String s : uris) {
            if(uri.equals(s) ){
                return false;
            }
        }
        for (String s : staticResource) {
            if(uri.startsWith(s)){
                return false;
            }
        }
        return true;
    }
}
相关推荐
柏油7 小时前
MySQL InnoDB 行锁
数据库·后端·mysql
咖啡调调。7 小时前
使用Django框架表单
后端·python·django
白泽talk7 小时前
2个小时1w字| React & Golang 全栈微服务实战
前端·后端·微服务
摆烂工程师7 小时前
全网最详细的5分钟快速申请一个国际 “edu教育邮箱” 的保姆级教程!
前端·后端·程序员
yuren_xia7 小时前
IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤
spring·mvc·intellij-idea
一只叫煤球的猫7 小时前
你真的会用 return 吗?—— 11个值得借鉴的 return 写法
java·后端·代码规范
Asthenia04128 小时前
HTTP调用超时与重试问题分析
后端
颇有几分姿色8 小时前
Spring Boot 读取配置文件的几种方式
java·spring boot·后端
AntBlack8 小时前
别说了别说了 ,Trae 已经在不停优化迭代了
前端·人工智能·后端
人生导师yxc8 小时前
Spring MVC
java·spring·mvc