【Spring Cloud 基础】 + 【Spring Cloud开发课程查询项目】项目

Spring Cloud 基础

微服务基础

简介

  • 微服务的基本概念、设计与拆分原则
  • 微服务和Spring Cloud的关系
  • 微服务常见的组件和功能
  • 课程查询案例基本介绍、系统架构设计和接口设计
  • 分模块构建Spring Cloud 项目
  • 课程服务整合,服务注册与发现
  • 整合Feign实现服务间调用
  • 网管集成与开发,并接入服务
  • 引入服务的熔断与降级,并进行实操演练

微服务基础

  • 什么是微服务

  • 微服务的特点

  • 微服务的优缺点

  • 微服务两大门派

    • SpringCloud
    • Dubbo
  • 微服务拆分

  • 微服务扩展

  • 微服务重要模块

什么是微服务

  • 单体应用的痛点
    • 部署效率低下
    • 团队协作开发成本高
    • 系统高可用性差
  • 什么是服务化
    • 把传统的单机应用中的本地方法调用,改造成RPC、HTTP产生的远程方法调用
    • 把模块从单体应用中拆分出来
    • 用户模块就可以独立开发、测试、上线、 运维,可以交由专门的团队来做,与主模块不耦合
  • 一种架构风格
  • 开发单个应用作为一系列小型服务的套件,其中每个服务都运行在自己的进程中,并且通过轻量级的机制实现彼此的通信,这通常是HTTP资源API
  • 这些服务是围绕这业务功能构建的,并且可以通过完全自动化的部署机制进行独立部署
  • 这些服务的集中式管理做到了最小化(例如docker相关技术),每一种服务都可以通过不同的编程语言进行编写,并且可以使用不同的数据存储技术
  • 一系列、一部分(选用一部分适合的组件)

微服务的特点

  • 组件以服务形式提供
  • 产品不是项目
  • 轻量级通信、独立进程
  • 分散治理、去中心化治理
  • 容错性设计
  • 会带来组织架构的调整

微服务优缺点

  • 优点:

    • 服务简单、便与学习上手,相对易于维护

    • 独立部署、灵活扩展

    • 技术栈丰富

  • 缺点:

    • 运维成本过高
    • 接口可能不匹配
    • 代码可能重复
    • 架构复杂度提高

微服务两大模块

  • Spring Cloud:众多子项目

  • Dubbo: 高性能、轻量级的开源java RPC 框架,它提供了三大核心能力

    • 面向接口的远程方法调用
    • 智能容错和负载均衡
    • 服务自动注册和发现
  • Dubbo提供的能力是SpringCloud的一个子集

核心组件 Dubbo Spring Cloud
服务注册中心 Zookeepper Spring Cloud Netflix Eureka
服务调用方式 RPC REST API
服务网关 Spring Cloud Netflix Zuul
断路器 不完善 Spring Cloud Netflix Hystrix
分布式配置 Spring Cloud Config
服务跟踪 Spring Cloud Sleuth
消息总线 Spring Cloud Bus
数据流 Spring Cloud Stream
批量任务 Spring Cloud Task
  • 通信协议对比
    • RPC vs REST
    • RPC服务提供方与调用接口依赖方式太强
    • Dubbo 服务对平台敏感,难以简单复用
  • 文档质量
    • Dubbo 有中文文档
    • Spring Cloud文档体量大,更多是偏向整合,更深入的使用方法还是需要查看其整合组件的详细文档
  • 比喻:组装电脑、品牌机
  • 需要根据自身的研发水平和所处的阶段选择

微服务的拆分

  • 什么时候进行服务化拆分
    • 第一阶段: 只要目标是快速开发和验证想法
    • 进一步增加更多新特性来吸引更多的目标用户
    • 同时进行开发人员超过了10人,这个时候就该考虑进行服务化拆分
  • 不适合拆分的情况
    • 小团队,技术基础薄弱
    • 流量不高,压力小,业务变化也不大
    • 对延迟很敏感的低延迟、高并发系统
  • 服务化拆分的两种姿势
    • 纵向拆分
    • 横向拆分
    • 综合业务综合分析

服务扩展

  • X轴-水平复制

  • Y轴-功能解耦

  • Z轴-数据分区

  • 自动按需扩展

    • 根据CPU负责程度、特定时间(比如周末)、消息中间件的队列长度、业务具体规则、预测等来解决是否扩展

    • 自动分配一个新的服务实例,提高可用性

    • 提高了可伸缩性(双十一之后自动减少服务器)

    • 具有最佳使用率,节约成本

微服务重要模块

  • 服务描述
  • 注册中心
  • 服务框架
  • 负载均衡
  • 熔断和降级
  • 网关

总结

Spring Cloud开发课程查询功能

项目整体介绍

  • Spring Cloud简介
  • 项目整体设计
  • 课程列表模块开发
  • 课程价格模块开发
  • 服务注册与发现Eureka
  • 服务间调用Feign
  • 负载均衡Ribbon
  • 熔断器Hystrix
  • 网关Zuul
  • 整体测试
  • 项目总结

SpringCloud核心组件介绍

  • Spring Cloud 简介

    • 成熟的微服务框架,定位为开发人员提供工具,以快速构建分布式系统
  • 核心组件

    核心组件 Spring Cloud
    服务注册中心 Spring Cloud Netflix Eureka
    服务调用方式 REST API、Feign、 Ribbon
    服务网关 Spring Cloud Netflix Zuul
    断路器 Spring Cloud Netflix Hystrix

项目技术设计

  • 项目介绍

  • 接口设计

    • 课程列表
    • 单个课程价格
    • 整合课程列表和价格
  • 系统数据流向
    在这里插入图片描述

  • 数据表

    在这里插入图片描述

新建多模块项目

课程列表模块开发

mapper类上边添加的注解

@Mapper
@Repository
public interface CourseMapper {
    
    
    @Select("SELECT * FROM course WHERE valid = 1")
    List<Course> findValidCourse();
}

常见错误排查

序列化时类中一定要有Setter 、Getter 和toString方法的

# 实现自动映射,Mybatis 驼峰配置
mybatis.configuration.map_underscore-to-camel-case = true

课程价格模块开发

Eureka的作用和架构

  • Eureka服务注册与发现模块

  • 为什么需要服务注册与发现

    • IP变化
    • 难以维护
    • 改进
  • 架构

    • Eureka Server

    • Eureka Client

开发Eureka Server

  • 引入Eureka

    • 引入依赖

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
      </dependency>
      ...
      <!-- 表示SpringCloud的版本 -->
      <dependencyManagement>
      	<dependencies>
          	<dependency>
              	<groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-dependencies</artifactId>
                  <version>Greenwich.SR5</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
          </dependencies>
      </dependencyManagement>
      
    • 配置文件

      spring.application.name=eureka-server
      server.port=8000
      eureka.instance.hostname=localhost
      #fetch-registry:获取注册表。不需要同步其他节点数据。
      eureka.client.fetch-registry=false
      #register-with-eureka代表是否将自己注册到Eureka Server,默认是true。
      eureka.client.register-with-eureka=false
      eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
      
    • 启动注解

      • 在启动类上添加@EnableEurekaServer

进行Eureka Client改造

 <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 </dependency>
eureka.client.service-url.defaultZone=http://localhost:8000/eureka/

一些老的版本还需要在启动类上添加 @EnableEurekaClient

利用Feign实现服务间调用

  • 引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
    
  • 配置文件

    #openfeign消费的负载均衡后期再配
    
  • 注解

    //启动类的客户端
    @EnableFeignClients
    
  • 客户端(在调用类(模块)写接口,复制被调用服务(模块)的controller方法)

    package com.bennyrhys.course.client;
    
    import com.bennyrhys.entity.Course;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    
    import java.util.List;
    
    /**
     * 课程列表的Feign客户端
     */
    @FeignClient("course-list")
    public interface CourseListClient {
          
          
        @GetMapping("/course")
        List<Course> getList();
    }
    
  • 验证pom中(自动引入其他服务的依赖)

  • controller(在price服务中调用course服务的方法)

  • 验证

利用Ribbon实现负载均衡

  • 负载均衡的两种类型

    • 客户端负载均衡(Ribbon)

    • 服务端负载均衡(Nginx)

  • 负载均衡策略

    • RandomRule表示随机策略
    • RoundRobinRule表示轮询策略
    • ResponseTimeWeightedRule加权,根据每一个Server的平均响应时间动态加权
  • 配置不同的负载均衡方式

    # Ribbon.NFLoadBanlancerRuleClassName
    # price服务调用course服务的负载均衡设置
    # openfeign消费的负载均衡
    course-list.ribbon.NFLoadBanlancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule
    

利用Hystrix实现断路器

  • 比如获取用户信息卡住,但数据库的连接池一直未被释放。系统崩溃、断路器保护,某一处出现问题,保证不影响全部不可用,避免故障蔓延

  • 引入依赖

    <!--        断路器 客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
    
  • 配置

    #断路器 客户端(默认关闭)
    feign.hystrix.enabled=true
    
  • 启动注解

    @EnableCircuitBreaker
    
  • 断路器实现类CourseListClientHystrix

    package com.bennyrhys.course.client;
    
    import com.bennyrhys.entity.Course;
    import java.util.ArrayList;
    import java.util.List;
    import org.springframework.stereotype.Component;
    
    /**
     * 描述:     断路器实现类
     */
    @Component
    public class CourseListClientHystrix implements CourseListClient{
          
          
    
        @Override
        public List<Course> getList() {
          
          
            List<Course> defaultCourses = new ArrayList<>();
            Course course = new Course();
            course.setId(1);
            course.setCourseId(1);
            course.setCourseName("默认课程");
            course.setValid(1);
            defaultCourses.add(course);
            return defaultCourses;
        }
    }
    
  • 指明调用服务的断路器类

    /**
     * 课程列表的Feign客户端
     */
    @FeignClient(value = "course-list", fallback = CourseListClientHystrix.class)
    @Primary //防止调用服务的controller爆红线不好看
    public interface CourseListClient {
          
          
        @GetMapping("/course")
        List<Course> getList();
    }
    
  • 断路器效果

整合两个服务

  • 将课程列表和课程价格进行整合

  • 返回实体CourseAndPrice

    Integer id;
    Integer courseId;
    String name;
    Integer price;
    
  • service

    @Override
        public List<CourseAndPrice> getCoursesAndPrice() {
          
          
            List<CourseAndPrice> courseAndPriceList = new ArrayList<>();
            List<Course> courses = courseListClient.courseList();
            for (int i = 0; i < courses.size(); i++) {
          
          
                Course course = courses.get(i);
                if (course != null) {
          
          
                    CoursePrice coursePrice = getCoursePrice(course.getCourseId());
                    CourseAndPrice courseAndPrice = new CourseAndPrice();
                    courseAndPrice.setPrice(coursePrice.getPrice());
                    courseAndPrice.setName(course.getCourseName());
                    courseAndPrice.setId(course.getId());
                    courseAndPrice.setCourseId(course.getCourseId());
                    courseAndPriceList.add(courseAndPrice);
                }
            }
            return courseAndPriceList;
        }
    }
    
  • 实现

通过网关Zuul实现路由功能

  • 两个特点

    • 签名校验、登录校验冗余问题

    • API网关允许将API请求(内部或外部)路由到正确地址

  • 集成

    • 把自己注册到Eureka这个注册中心
    • 引入依赖
    • 配置路由地址
  • 新建mudle模块sourse-zuul

  • 引入依赖

    <dependencies>
        <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-netflix-zuul</artifactId>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    
  • 配置文件

    spring.application.name=course-gateway
    server.port=9000
    logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}
    mybatis.configuration.map-underscore-to-camel-case=true
    eureka.client.service-url.defaultZone=http://localhost:8000/eureka/
    
    #zuul.prefix=/bennyrhys
    zuul.routes.course-list.path=/list/**
    zuul.routes.course-list.service-id=course-list
    zuul.routes.course-price.path=/price/**
    zuul.routes.course-price.service-id=course-price
    
  • 启动类 注解

    package com.bennyrhys.course;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.cloud.client.SpringCloudApplication;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    
    /**
     * 描述:     网关启动类
     */
    @EnableZuulProxy
    @SpringCloudApplication
    public class ZuulGatewayApplication {
          
          
    
        public static void main(String[] args) {
          
          
            SpringApplication.run(ZuulGatewayApplication.class, args);
        }
    }
    
  • 效果

实现网关过滤器

  • pre 过滤器在路由请求之前运行

  • route 过滤器可以处理请求的实际路由

  • post 路由请求后运行过滤器

  • error 如果在处理请求的过程中发生错误,则过滤器将运行

  • 过滤前

    package com.bennyrhys.course.filter;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
    import org.springframework.stereotype.Component;
    
    /**
     * 描述:     记录请求时间
     */
    @Component
    public class PreRequestFilter extends ZuulFilter {
          
          
    
        @Override
        public String filterType() {
          
          
            //过滤器的类型
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
          
          
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
          
          
            //是否启用过滤器
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
          
          
            RequestContext currentContext = RequestContext.getCurrentContext();
            currentContext.set("startTime", System.currentTimeMillis());
            System.out.println("过滤器已经记录时间");
            return null;
        }
    }
    
  • 过滤后

    package com.bennyrhys.course.filter;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
    import org.springframework.stereotype.Component;
    
    /**
     * 描述:     请求处理后的过滤器
     */
    @Component
    public class PostRequestFilter extends ZuulFilter {
          
          
    
        @Override
        public String filterType() {
          
          
            return FilterConstants.POST_TYPE;
        }
    
        @Override
        public int filterOrder() {
          
          
            return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
        }
    
        @Override
        public boolean shouldFilter() {
          
          
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
          
          
            RequestContext currentContext = RequestContext.getCurrentContext();
            Long startTime = (Long) currentContext.get("startTime");
            long duration = System.currentTimeMillis() - startTime;
            String requestURI = currentContext.getRequest().getRequestURI();
            System.out.println("uri:" + requestURI + ",处理时长:" + duration);
            return null;
        }
    }
    

猜你喜欢

转载自blog.csdn.net/qq_45924975/article/details/129519113