Spring Cloud入门篇(适合新手入门)

1.微服务架构:Spring-Cloud

记得关注下公众号哦!
在这里插入图片描述

1.1 什么是微服务?

​ 微服务就是把原本臃肿的一个项目的所有模块拆分开来并做到互相没有关联,甚至可以不使用同一个数据库。

​ 比如:项目里面有商品模块和订单模块,但是商品模块和订单模块并没有直接关系,仅仅只是一些数据需要交互,那么就可以吧这2个模块单独分开来,当商品需要调用订单的时候,订单是一个服务方,但是订单需要调用商品的时候,商品又是服务方了, 所以,他们并不在乎谁是服务方谁是调用方,他们都是2个独立的服务,这时候,微服务的概念就出来了。

1.2 经典问题:微服务和分布式的区别

​ 谈到区别,我们先简单说一下分布式是什么,所谓分布式,就是将偌大的系统划分为多个模块(这一点和微服务很像)部署到不同机器上(因为一台机器可能承受不了这么大的压力或者说一台非常好的服务器的成本可能够好几台普通的了),各个模块通过接口进行数据交互,其实 分布式也是一种微服务。 因为都是吧模块拆分开来变为独立的单元,提供接口来调用.

​ 那么 他们本质的区别在哪呢? 他们的区别主要体现在“目标”上, 何为目标,就是你这样架构项目要做到的事情。 分布式的目标是什么? 我们刚刚也看见了, 就是一台机器承受不了的,或者是成本问题 , 不得不使用多台机器来完成服务的部署, 而微服务的目标 只是让各个模块拆分开来,不会被互相影响,比如模块的升级亦或是出现BUG等等…

​ 讲了这么多,可以用一句话来理解:分布式也是微服务的一种,而微服务他可以是在一台机器上

1.3 微服务与Spring-Cloud的关系(区别)

​ 微服务只是一种项目的架构方式,或者说是一种概念,就如同我们的MVC架构一样, 那么Spring-Cloud便是对这种技术的实现。

1.4 微服务一定要使用Spring-Cloud吗?

​ 我们刚刚说过,微服务只是一种项目的架构方式,如果你足够了解微服务是什么概念你就会知道,其实微服务就算不借助任何技术也能实现,只是有很多问题需要我们解决罢了例如:负载均衡,服务的注册与发现,服务调用,路由。。。。等等等等一系列问题,所以,Spring-Cloud 就出来了,Spring-Cloud将处理这些问题的的技术全部打包好了,就类似那种开袋即食的感觉。

2. Spring-Cloud项目的搭建

2.1 版本对照

​ 因为spring-cloud是基于spring-boot项目来的,所以我们项目得是一个spring-boot项目,至于spring-boot项目,这节我们先不讨论,这里要注意的一个点是spring-cloud的版本与spring-boot的版本要对应下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTe7AuNX-1570702583323)(C:\Users\皇甫\AppData\Roaming\Typora\typora-user-images\1556945470030.png)]

2.2 Spring Cloud父项目pom文件

我只着重介绍SpringCloud,有关maven以及SpringBoot相关知识,自行查看官网

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.csdn</groupId>
    <artifactId>spring-cloud-csdn</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>eureka-server</module>
        <module>product-server</module>
        <module>user-client</module>
        <module>product-server-1</module>
        <module>config-server</module>
        <module>zuul</module>
    </modules>


    <name>spring-cloud-csdn</name>
    <description>CSDN项目测试</description>
    <!--springboot项目-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.9.RELEASE</version>
        <relativePath/>
    </parent>
    <!--SpringCloud 父项目-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--测试包-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <!--lombok插件包-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--json工具-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.57</version>
        </dependency>

        <!--SpringBootweb项目包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--SpringBoot测试包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <!--Maven启动打包插件-->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.3 Spring Cloud组件

2.3.1 eureka 注册中心

​ eureka是Netflix的子模块之一,也是一个核心的模块,eureka里有2个组件,一个是EurekaServer(一个独立的项目) 这个是用于定位服务以实现中间层服务器的负载平衡和故障转移,另一个便是EurekaClient(我们的微服务) 它是用于与Server交互的,可以使得交互变得非常简单:只需要通过服务标识符即可拿到服务。

​ 我们可以将它看作一个中央转接器,任何客户端想要调用任何远程服务,都要到Eureka来寻找;同样,自己本身的服务如果想要外部调用,也必须向Eureka声明注册!

2.3.1.1与spring-cloud的关系

​ Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现(可以对比Zookeeper)。

​ Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka 的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server 来监控系统中各个微服务是否正常运行。SpringCloud 的一些其他模块(比如Zuul)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的逻辑。

2.3.1.2 角色关系图:(假设商品服务需要调取订单服务)

在这里插入图片描述

2.3.1.3 如何使用?

1.新建 spring-cloud-csdn module 导入上述pom依赖

2.在 spring-cloud-csdn 新建 module eureka-server

1.导入maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-csdn</artifactId>
        <groupId>com.csdn</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka-server</artifactId>
    <name>eureka-server</name>
    <description>Eureka注册中心</description>


    <dependencies>
        <!--Eureka客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>

2.创建SpringBoot启动类 关于有些注释,代码注释上写的很清楚

package com.cloud;

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

/**
 * SpringCloud注册中心启动类
 * @author 皇甫
 * @SpringBootApplication SpringBoot启动类注解
 * @EnableEurekaServer Eureka服务端启用注解
 */
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class);
    }
}

3.配置启动文件 application.yml

spring:
  application:
    name: eureka-server #服务名称
server:
  port: 3000 #服务端口号
eureka:
  instance:
    instance-id: eureka-server0 #服务id名称
    hostname: 127.0.0.1 #主机名称
  server:
    enable-self-preservation: false #关闭服务器的自我保护机制(Eureka高可用额度关键)
    eviction-interval-timer-in-ms: 30000 #设置失效服务的间隔
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
    register-with-eureka: false #本身就是注册中心服务,不需要将自己注册到本身的服务
    fetch-registry: false #本身就是提供注册信息的服务,不需要从其他服务获取信息(单机情况下这样设置,集群必须开启)

启动服务 浏览器访问 localhost:3000

看到这个界面,表示注册中心启动完成

2.3.2 服务提供者

在这里插入图片描述

2.3.2.1 如何是使用

​ 我们在创建服务时,可能某些资源是服务提供方和服务消费方都需要的,所以我们此时封装一些操作,将一些未来可能共同使用的资源单独抽取到一个模块里,避免冗余!

​ 新建 public-resources module 来存放公共资源文件(入实体类,工具类,映射类等)》

为了模拟商品服务,此时我们需要在公共资源模块立书写一个商品的实体类

package com.cloud.entrty;

import lombok.Data;

import java.math.BigDecimal;

/**
 * 商品的实体类
 * @author 皇甫
 * @Data 插件 自动生成getter/Setter 自行百度lombock的使用方法
 */
@Data
public class Product {
    /**
     * id
     */
    private String id;
    /**
     * 商品名称
     */
    private String name;
    /**
     * 商品价格
     */
    private BigDecimal price;

    public Product() {
    }

    public Product(String id, String name, BigDecimal price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

创建一个结果映射器

package com.cloud.vo;

import lombok.Data;

/**
 * 结果返回统一格式映射类
 * @author 皇甫
 * @Data 插件 自动生成getter/Setter 自行百度lombock的使用方法
 */
@Data
public class ResultVo<T> {
    private Integer code;
    private String msg;
    private T data;

    public ResultVo(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public ResultVo() {
    }
}

创建一个枚举类,用来表示商品的状态

package com.cloud.enums;

import lombok.Getter;

/**
 * 结果返回状态码 枚举
 * @author 皇甫
 */
@Getter
public enum  ResultEnum {
    SUCCESS(100,"成功"),
    PRODUCT_DOES_NOT_EXIST(101,"商品不存在")
    ;
    private Integer code;
    private String msg;

    ResultEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

创建一个工具类,方便结果传输

package com.cloud.utils;

import com.cloud.enums.ResultEnum;
import com.cloud.vo.ResultVo;

/**
 * 结果返回格式工具类
 * @author 皇甫
 */
public class ResultVoUtil {
    /**
     * 成功
     * @param data
     * @return
     */
    public static ResultVo success(Object data){
        ResultVo<Object> resultVo = new ResultVo<Object>();
        resultVo.setCode(ResultEnum.SUCCESS.getCode());
        resultVo.setMsg(ResultEnum.SUCCESS.getMsg());
        resultVo.setData(data);
        return resultVo;
    }

    /**
     * 无参成功
     * @return
     */
    public static ResultVo success(){
        ResultVo<Object> resultVo = new ResultVo<Object>();
        resultVo.setCode(ResultEnum.SUCCESS.getCode());
        resultVo.setMsg(ResultEnum.SUCCESS.getMsg());
        return resultVo;
    }

    /**
     * 无参失败
     * @return
     */
    public static ResultVo error() {
        ResultVo<Object> resultVo = new ResultVo<Object>();
        resultVo.setCode(5000);
        resultVo.setMsg("失败");
        return resultVo;
    }
    /**
     * 有参失败
     * @return
     */
    public static ResultVo error(String msg) {
        ResultVo<Object> resultVo = new ResultVo<Object>();
        resultVo.setCode(5000);
        resultVo.setMsg(msg);
        return resultVo;
    }
}

创建一个自定义异常类,方便对异常的处理

package com.cloud.exception;

import com.cloud.enums.ResultEnum;

/**
 * 本工程出现的自定义异常
 * @author 皇甫
 */
public class ApplicationException extends RuntimeException {
    private Integer code;
    public ApplicationException(ResultEnum resultEnum) {
        super(resultEnum.getMsg());
        this.code = resultEnum.getCode();
    }
}

新建 product-server module,并添加如下maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-csdn</artifactId>
        <groupId>com.csdn</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product-server-1</artifactId>


    <dependencies>
        <!--公共资源-->
        <dependency>
            <groupId>com.csdn</groupId>
            <artifactId>public-resources</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--Eureka Client依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

    </dependencies>
</project>

新建启动文件 application.yml

spring:
  application:
    name: PRODUCT-SERVER #服务名称
server:
  port: 7000 #服务端口
eureka:
  instance:
    hostname: 127.0.0.1 #服务主机名
    instance-id: PRODUCT-SERVER-0 #服务id  集群搭建
    eurekaPort: 3000 #服务器注册端口
    prefer-ip-address: true #显示ip地址
    lease-renewal-interval-in-seconds: 10 #给注册中心发送心跳间隔时间
    lease-expiration-duration-in-seconds: 30 #服务器接收最后一次心跳后间隔多少秒被剔除
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${eureka.instance.eurekaPort}/eureka #把本服务注册到Eureka的注册中心

新建启动类

package com.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * 商品服务的启动类
 * @author 皇甫
 * @SpringBootApplication SpringBoot启动类注解
 * @EnableDiscoveryClient Eureka客户端启用注解
 */
@SpringBootApplication
@EnableDiscoveryClient
public class ProductServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductServerApplication.class);
    }
}

创建服务

package com.cloud.controller;

import com.cloud.entrty.Product;
import com.cloud.enums.ResultEnum;
import com.cloud.exception.ApplicationException;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.util.UUID;

/**
 * 模拟商品查询单个商品 以及增加功能  这要是参数格式
 * @author 皇甫
 */
@RestController
@RequestMapping("product")
public class ProductController {
    final String PRODUCT_ID = "1";
    /**
     * 模拟根据商品id查询
     * @param id
     * @return
     */
    @RequestMapping("getProduct")
    public Product getProduct(@RequestParam("id") String id) throws InterruptedException {
        //Thread.sleep(2000);
        if(PRODUCT_ID.equals(id)){
            Product product = new Product(UUID.randomUUID().toString(),"皮蛋粥",new BigDecimal(10.5));
            return product;
        }else{
            throw new ApplicationException(ResultEnum.PRODUCT_DOES_NOT_EXIST);
        }
    }

    /**
     * 添加成功
     * @param product
     * @return
     */
    @RequestMapping("addProduct")
    public Product addProduct(@RequestBody Product product){
        return product;
    }
}

启动服务,观察注册中心 很清楚的看到,服务被注册了上来 开发服务发布方完成

在这里插入图片描述

2.3.3 服务调用者

2.3.3.1 如何是使用

创建 user-client module 导入maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-csdn</artifactId>
        <groupId>com.csdn</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-client</artifactId>
    <name>user-client</name>
    <description>调用方</description>


    <dependencies>
        <!--公共资源-->
        <dependency>
            <groupId>com.csdn</groupId>
            <artifactId>public-resources</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--Eureka Client依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--远程方法调用依赖 feign组件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--hystrix断路器-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

    </dependencies>

</project>

创建启动配置文件 application.yml

server:
  port: 9000
spring:
  application:
    name: user-client
feign:
  hystrix:
    enabled: true #手动开启feign组件
eureka:
  instance:
    hostname: 127.0.0.1
    instance-id: user-client0
    eurekaPort: 3000
    prefer-ip-address: true #显示ip地址
    lease-renewal-interval-in-seconds: 10 #给注册中心发送心跳间隔时间
    lease-expiration-duration-in-seconds: 30 #服务器接收最后一次心跳后间隔多少秒被剔除
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${eureka.instance.eurekaPort}/eureka #把本服务注册到Eureka的注册中心

创建启动类

package com.cloud;

import com.cloud.role.ProductRole;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * 调用方服务启动类
 * @author 皇甫
 * @SpringBootApplication SpringBoot启动类注解
 * @EnableDiscoveryClient Eureka客户端启用注解
 * @EnableFeignClients feign组件 开启远程方法调用
 * @EnableCircuitBreaker 使用Hystrix组件 断路器
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.cloud.service"})
@EnableCircuitBreaker
public class UserClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserClientApplication.class);
    }
}

2.3.3.2 feign组件

掉用远程方法,使用feign组件

package com.cloud.service;

import com.cloud.entrty.Product;
import com.cloud.service.impl.ProductFeignServiceImpl;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 调用商品服务
 * @author 皇甫
 * @FeignClient feign组件
 *      name:要调用的服务名称
 *      fallback: 调用失败后的降级方法
 */
@FeignClient(value = "PRODUCT-SERVER",fallback = ProductFeignServiceImpl.class)
public interface ProductFeignService {
    /**
     * 调用商品服务
     * @param id
     * @return
     *  @GetMapping value="要调用的方法路径"
     */
    @RequestMapping("/product/getProduct")
    String getProduct(@RequestParam("id") String id);

    /**
     * TODO 重点-------------------------------------------
     * 这里注意 RequestBody 标注的参数,其访问类型必须为POST方式
     * TODO 重点-------------------------------------------
     * @param product
     * @return
     */
    @RequestMapping("product/addProduct")
    String addProduct(@RequestBody Product product);
}

使用feign的降级方法

什么叫做降级?

​ 方法调用失败后的一种提示策略,给用户友好的体验,在双十一或者节日的时候,某宝有时候会出现,亲亲,被挤爆了哦!请稍后再试,这就是方法的降级!在方法执行失败后,将设置的降级方法当作主逻辑实现,执行降级方法里面的代码,这样可以有效的避免服务雪崩

在这里插入图片描述

​ 如上图:当服务5出现异常时,或者响应慢的时候!服务4就需要等待服务5!服务4处于阻塞状态,此时如果再有其他请求访问,也同样会阻塞在服务4,服务4被阻塞,调用链就无法继续执行,导致服务1、2、3全部被阻塞,最终导致整个工程挂掉,史称服务雪崩,降级方法的出现,如果服务五出现超时,或错误时,直接执行降级方法,并不影响其他服务继续对外提供服务!

使用如下:

package com.cloud.service.impl;

import com.cloud.entrty.Product;
import com.cloud.service.ProductFeignService;
import org.springframework.stereotype.Component;

/**
 * 商品服务降级方法
 * @author 皇甫
 */
@Component
public class ProductFeignServiceImpl implements ProductFeignService {
    @Override
    public String getProduct(String id) {
        return null;
    }

    @Override
    public String addProduct(Product product) {
        return null;
    }
}

开发消费者api

package com.cloud.controller;

import com.alibaba.fastjson.JSON;
import com.cloud.entrty.Product;
import com.cloud.enums.ResultEnum;
import com.cloud.exception.ApplicationException;
import com.cloud.service.ProductFeignService;
import com.cloud.utils.ResultVoUtil;
import com.cloud.vo.ResultVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

/**
 * 调用方API
 * @author 皇甫
 */
@RestController
@RequestMapping("user")
public class UserController {
    final String PRODUCT_ID = "1";
    //注入feign类
    @Autowired
    private ProductFeignService productFeignService;

    @GetMapping("findProduct")
    public ResultVo findProduct(@RequestParam("id") String id){
        if(PRODUCT_ID.equals(id)){
            String productJson = productFeignService.getProduct(id);
            if(StringUtils.isEmpty(productJson)){
                return ResultVoUtil.error();
            }
            Product product = JSON.parseObject(productJson, Product.class);
            return ResultVoUtil.success(product);
        }else{
            throw new ApplicationException(ResultEnum.PRODUCT_DOES_NOT_EXIST);
        }
    }
    @GetMapping("addProduct")
    public ResultVo addProduct(@RequestBody Product product){
        if(product != null){
            product = JSON.parseObject(productFeignService.addProduct(product),Product.class);
            return ResultVoUtil.success(product);
        }
        return ResultVoUtil.error("添加失败");
    }
}

测试访问 访问地址 http://localhost:9000/user/findProduct?id=1 如图所示,访问成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OT3mQ3Bx-1570702583325)(C:\Users\皇甫\AppData\Roaming\Typora\typora-user-images\1556949245829.png)]

2.3.3.2 Ribbon 组件

Ribbon 组件,开启负载均衡 如图所示

在这里插入图片描述

​ 如何使用呢?我们只需要把上方的商品服务(product-server)复制一份,修改一下配置文件(application.yml),修改的内容如下 主要是端口号和insert-id

spring:
  application:
    name: PRODUCT-SERVER #服务名称
server:
  port: 7001 #服务端口------------------------------------修改
eureka:
  instance:
    hostname: 127.0.0.1 #服务主机名
    instance-id: PRODUCT-SERVER-1 #服务id  集群搭建--------------------------修改
    eurekaPort: 3000 #服务器注册端口
    prefer-ip-address: true #显示ip地址
    lease-renewal-interval-in-seconds: 10 #给注册中心发送心跳间隔时间
    lease-expiration-duration-in-seconds: 30 #服务器接收最后一次心跳后间隔多少秒被剔除
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${eureka.instance.eurekaPort}/eureka #把本服务注册到Eureka的注册中心

​ 启动后,ribbon默认使用轮询的负责均衡方案,也可自定义修改,修改如下:建立config包,确保SpringBoot自动扫描可以被扫描到的地方,或者手动指定

  • 返回负载均衡策略具体的实现类
    • RetryRule服务挂掉一定时间阿内会不访问 剩余的服务使用轮询
    • RoundRobinRule默认是轮询
    • AvailabilityFilteringRule由于连续连接或读取失败,处于断路器跳闸状态
    • BestAvailableRule跳过具有“跳闸”断路器的服务器的规则并选择
    • RandomRule随机
    • WeightedResponseTimeRule使用平均/百分位响应时间*的规则为每个服务器分配动态“权重”,然后以“加权循环”方式使用。
    • ZoneAvoidanceRule返回的“始终为真”谓词
    • 如果要设置不同的服务使用不同的负载均衡策略,则需要将负载均衡器放置到SpringBoot的
    • 扫描范围,在启动类上增加配置即可
package com.cloud.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @author 皇甫
 */
@Configuration
public class AppConfig {
    /**
     * 返回负载均衡策略具体的实现类
     * RetryRule服务挂掉一定时间阿内会不访问 剩余的服务使用轮询
     * RoundRobinRule默认是轮询
     * AvailabilityFilteringRule由于连续连接或读取失败,处于断路器跳闸状态
     * BestAvailableRule跳过具有“跳闸”断路器的服务器的规则并选择
     * RandomRule随机
     * WeightedResponseTimeRule使用平均/百分位响应时间*的规则为每个服务器分配动态“权重”,然后以“加权循环”方式使用。
     * ZoneAvoidanceRule返回的“始终为真”谓词
     * 如果要设置不同的服务使用不同的负载均衡策略,则需要将负载均衡器放置到SpringBoot的
     * 扫描范围,在启动类上增加配置即可
     * @return
     */
    @Bean
    public IRule iRule(){
        // RandomRule 随机方案
        return new RandomRule();
    } 
}

如果你想对不同服务,使用不同的负载均衡方案,可以这样搞:新建一个类

package com.cloud.role;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 商品类的负载均衡实现
 * @author 皇甫
 */
@Configuration
public class ProductRole {
    @Bean
    public IRule productIRule(){
        return new RandomRule();
    }
}

在启动类上加上

@RibbonClients({
        @RibbonClient(name = "PRODUCT-SERVER",configuration = ProductRole.class)
})

完整的启动类

package com.cloud;

import com.cloud.role.ProductRole;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * 调用方服务启动类
 * @author 皇甫
 * @SpringBootApplication SpringBoot启动类注解
 * @EnableDiscoveryClient Eureka客户端启用注解
 * @EnableFeignClients feign组件 开启远程方法调用
 * @RibbonClients 为指定的服务提供精准负载均衡策略
 *      @RibbonClient
 *          name:服务名称
 *          configuration:自定义负载均衡类
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.cloud.service"})
@RibbonClients({
        @RibbonClient(name = "PRODUCT-SERVER",configuration = ProductRole.class)
})
@EnableCircuitBreaker
public class UserClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserClientApplication.class);
    }
}
2.3.3.3 Hystrix组件

​ 服务熔断:当服务处于超时次数多或者错误次数多时,注册中心会认为这个服务不可用,将此服务的断路器开启,未来请求直接执行降级方法!断路器关闭一定时间后,会将断路器执于半开状态,届时将会发送一次请求到指定服务,如果请求成功,断路器关闭!如果失败,继续开启断路器!如图所示:

在这里插入图片描述

我们重新写一个类

package com.cloud.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @author 皇甫
 */
@Configuration
public class AppConfig {
    @Bean
    public IRule iRule(){
        return new RandomRule();
    }
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return  new RestTemplate();
    }
}

package com.cloud.controller;

import com.alibaba.fastjson.JSON;
import com.cloud.entrty.Product;
import com.cloud.enums.ResultEnum;
import com.cloud.exception.ApplicationException;
import com.cloud.utils.ResultVoUtil;
import com.cloud.vo.ResultVo;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @author 皇甫
 */
@RestController
@RequestMapping("userHystrix")
@DefaultProperties(defaultFallback = "defaultFallback")
public class HystrixController {
    private final String PRODUCT_ID = "1";
    private final String PRODUCT_SERVER = "PRODUCT-SERVER";
    @Autowired
    private RestTemplate restTemplate;


    /**
     *
     * @param id
     * @return
     * @HystrixProperty
     *  execution.isolation.thread.timeoutInMilliseconds 最大延时降级时间
     *  circuitBreaker.enabled 开启熔断器
     *  circuitBreaker.requestVolumeThreshold 断路器的最小请求数10之后 计算错误阈值
     *  circuitBreaker.sleepWindowInMilliseconds 错误率达到阈值 本例60%
     *  circuitBreaker.errorThresholdPercentage 间隔多长时间开启断路器半开状态
     */
    @HystrixCommand(commandProperties = {
            @HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds" ,value ="3000" ),
            @HystrixProperty(name ="circuitBreaker.enabled" ,value ="true" ),
            @HystrixProperty(name ="circuitBreaker.requestVolumeThreshold" ,value ="10" ),
            @HystrixProperty(name ="circuitBreaker.sleepWindowInMilliseconds" ,value ="60" ),
            @HystrixProperty(name ="circuitBreaker.errorThresholdPercentage" ,value ="10000" ),
    })
    @GetMapping("findProduct")
    public ResultVo findProduct(@RequestParam("id") String id){
        if(PRODUCT_ID.equals(id)){
            String productJson = restTemplate.getForObject("http://"+PRODUCT_SERVER+"/product/getProduct?id="+id,String.class);
            if(StringUtils.isEmpty(productJson)){
                return ResultVoUtil.error();
            }
            Product product = JSON.parseObject(productJson, Product.class);
            return ResultVoUtil.success(product);
        }else{
            throw new ApplicationException(ResultEnum.PRODUCT_DOES_NOT_EXIST);
        }
    }

    public ResultVo defaultFallback(){
        return ResultVoUtil.success("就问你难受不难受");
    }
}

2.3.4 配置中心

我们既然要做项目, 那么就少不了配置,传统的项目还好,但是我们微服务项目, 每个微服务就要做独立的配置, 这样难免有点复杂, 所以, config项目出来了,它就是为了解决这个问题: 把你所有的微服务配置通过某个平台:

比如 github, gitlib 或者其他的git仓库 进行集中化管理(当然,也可以放在本地).

可能这样讲有点抽象,我们来看一张图:

在这里插入图片描述

2.3.4.1 怎么使用config?

引入配置中心 maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-csdn</artifactId>
        <groupId>com.csdn</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>config-server</artifactId>
    <name>config-server</name>
    <description>配置中心</description>


    <dependencies>
        <!--配置中心依赖项-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <!--Eureka注册中心-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

编辑启动类以及启动文件

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/huangfusuper/config.git
          username: gitgub的账号 #-----------------没有可以不写
          password: gitgub的密码 #-----------------没有可以不写
eureka:
  instance:
    instance-id: config-server-0
    hostname: 127.0.0.1
    eurekaPort: 3000
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${eureka.instance.eurekaPort}/eureka/
server:
  port: 8000

package com.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;

/**
 * 配置中心启动项
 * @author 皇甫
 */
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class);
    }
}

将配置文件上传带github 启动服务

在这里插入图片描述

服务启动成功后,访问配置文件 http://localhost:8000/product-dev.yml

在这里插入图片描述

此时,代表配置中心搭建完成,我们可以将商品服务的配置放置到git仓库上,然后:重点来了:因为SpringCloud又上下文概念,启动时又先后顺序;所以,我们必须找一个启动顺序比application.yml的优先级还要高的配置文件,此时就是bootstrap.yml;我们再商品服务的module上做如下配置,新建bootstrap.yml

spring:
  cloud:
    config:
      discovery:
        enabled: true #开启远程读取
        service-id: CONFIG-SERVER #配置中心的服务名
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:3000/eureka #把本服务注册到Eureka的注册中心

此时的product-server 的application.yml变为

spring:
  application:
    name: product #Application  文件名称

此时我的项目结构为

在这里插入图片描述

2.3.5 zuul网关的使用

2.3.5.1 zuul是什么

​ Zuul包含了对请求的路由和过滤两个最主要的功能:

​ 其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础.

​ Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。

注意:Zuul服务最终还是会注册进Eureka 如图:我们可以在访问服务之前做一些鉴权限流等操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gGPw9d9z-1570702583327)(C:\Users\皇甫\Desktop\未命名文件.png)]

2.3.5.2 如何使用

依旧时导入maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-csdn</artifactId>
        <groupId>com.csdn</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>zuul</artifactId>

    <name>zuul</name>
    <description>网关</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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-netflix-zuul</artifactId>
        </dependency>
        <!--配置中心依赖项-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <!--限流方案  谷歌令牌桶算法-->
        <dependency>
            <groupId>com.revinate</groupId>
            <artifactId>guava-rate-limiter</artifactId>
            <version>19.0</version>
        </dependency>

    </dependencies>
</project>

导入启动配置文件,这里,我们依旧可以使用配置中心管理配置文件

再github上创建文件为 zuul.yml的文件,写入配置

evn: dev
spring:
  application:
    name: ZUUL-SERVER #服务名称
server:
  port: 8888 #服务端口
eureka:
  instance:
    instance-id: ZUUL-SERVER-0 #服务id  集群搭建
    prefer-ip-address: true #显示ip地址
    lease-renewal-interval-in-seconds: 10 #给注册中心发送心跳间隔时间
    lease-expiration-duration-in-seconds: 30 #服务器接收最后一次心跳后间隔多少秒被剔除
zuul:
  host:
    connect-timeout-millis: 15000 #HTTP连接超时要比Hystrix的大
    socket-timeout-millis: 60000   #socket超时
  routes:
    myProduct:
      path: /myProduct/** #自定义访问路径,也可以不指定,使用服务名进行访问
      serviceId: product-service
ribbon:
  ReadTimeout: 10000
  ConnectTimeout: 10000

bootstrap.yml

spring:
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIG-SERVER
      profile: dev
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:3000/eureka #把本服务注册到Eureka的注册中心

application.yml

spring:
  application:
    name: zuul #配置名称
zuul:
  routes:
    user-client: /myuser/** #简洁写法,与下方相同
    #myuser: #自定义路由规则
      #path: /myuser/** #访问路径
      #serviceId: user-client #要改变的服务名称
  #ignored-patterns:
    #- /**/userHystrix/findProduct #排除某些路由,无法通过路由访问

启动类 ZuulApplication

package com.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

/**
 * 网关启动类
 * @author 皇甫
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class);
    }
}

书写过滤器,检验用户权限

package com.cloud.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/**
 * 鉴权 如果token为空则不通过 提示没有权限 否则正常访问
 * @author 皇甫
 */
@Component
public class TokenFilter extends ZuulFilter {
    /**
     * 前置过滤器
     * @return
     */
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    /**
     * 优先级
     * @return
     */
    @Override
    public int filterOrder() {
        return PRE_DECORATION_FILTER_ORDER-1;
    }

    /**
     * 开启过滤
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 过滤逻辑
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        //判断用户是否存在登陆token  没有则提示没有权限
        String token = request.getParameter("token");
        if(StringUtils.isEmpty(token)){
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        return null;
    }
}

书写过滤器 限流 使用谷歌的令牌桶算法

package com.cloud.filter;

import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVLET_DETECTION_FILTER_ORDER;

/**
 * 限流过滤方案
 * @author 皇甫
 */
@Component
public class RateLimiterFilter extends ZuulFilter {
    /**
     * 创建令牌桶  每一秒放入100个令牌 超过100则抛弃
     * 拿到令牌执行方法 没有拿到执行降级方法
     */
    private final RateLimiter RATELIMITER = RateLimiter.create(100);
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return SERVLET_DETECTION_FILTER_ORDER-1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        //如果没有没拿到令牌
        if(!RATELIMITER.tryAcquire()){
            try {
                throw new Exception("人间不值得");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

开发时,可能会涉及到跨域操作,我提一个方案

package com.cloud;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;

/**
 * 跨域操作
 * @author 皇甫
 */
@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter(){
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        //设置原始域名 http://localhost:7000/product/getProduct
        config.setAllowedOrigins(Arrays.asList("*"));
        //设置过滤那些方法 GET POST等
        config.setAllowedMethods(Arrays.asList("*"));
        //允许的头信息
        config.setAllowedHeaders(Arrays.asList("*"));
        //跨域请求的缓存时间
        config.setMaxAge(300L);
        //对那些域名进行跨域处理
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

https://github.com/huangfusuper/spring-cloud-csdn 源码地址
https://download.csdn.net/download/qq_37561309/11158676 PDF下载
https://spring.io/projects/spring-cloud 本文参考官网,大部分配置,来源于官网

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值