(学习笔记)SpringCloud微服务快速入门实战课程【2020版】

 学习视频:这是自己买的课,现在有点过期了,好多不是最新的。学习笔记里是有更改的。

(55条消息) SpringCloud微服务快速入门实战课程【2020版】-1-微服务简介-汤小洋的在线视频教程-CSDN程序员研修院
学习笔记:

1. 微服务

1.1微服务简介

将单一应用程序划分成多个微小的服务, 每个服务完成单一功能,这样的每个服务叫做一个微服务。

1.2微服务架构 

微服务是一种架构模式。

  • 将应用的每个功能放到一个独立的服务中,每个服务对应一个进程。
  • 使用一组小型服务来开发单个应用,每个服务运行在独立的进程中,服务与服务之间通过HTTP的方式进行互相通信。
  • 每个服务都是一个可独立替换和独立升级的软件单元, 并且能够被独立的部署到生产环境。

优点:

  • 分而治之:单个服务功能内聚,复杂性低,方便团队的拆分和管理。
  • 可伸缩:能够单独的对指定的服务进行伸缩。
  • 迭代周期短:支持快速的迭代开发。
  • 独立部署,独立开发。

缺点:

  • 运维要求高:应用流程通常跨多个微服务,不易进行问题的定位。
  • 分布式的复杂性:微服务需要使用分布式,而由于分布式本身的复杂性,导致微服务架构也变得复杂起来。

2. SpringCloud技术栈

2.1 简介

SpringCloud是一套完整的微服务解决方案,基于SpringBoot框架
SpringCloud是一系列框架有序集合,它利用SpringBoot的开发便利性简化了分布式系统的开发。
SpringCloud为开发人员提供了快速构建分布式系统的一些工具,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等。

2.2技术栈

微服务内容技术实现
服务的注册与发现

Eureka(类似zookeeper)

服务的接口调用Feign(通过Http的方式)
服务的熔断器 Hystrix
服务网关 Zuul
负载均衡Ribbon
服务监控 Zabbix
全链路跟踪ZipKin
配置管理Archaius
服务的配置中心SpringCloud Config
数据流操作SpringCloud Stream
事件、消息总线SpringCloud Bus

注:重点橙色部分。

微服务之间通过HTTP的方式进行互相通信,此时Web API的设计就显得非常重要,会使用Restful API设计方式。

3.Restful API 

3.1简介

 Representational State Transfer,简称为REST, 即表现层状态转化。

  • 资源 Resources:指的是网络上的某个数据, 如一个文件、一种服务等。
  • 表现层 Representational:资源的表现层,指的是资源的具体呈现形式,如HTML、JSON等。
  • 状态转化 State Transfer:指的是状态变化,通过HTTP方法来实现:

                GET   获取资源
                POST   新建资源
                PUT   更新资源
                DELETE 删除资源

简单来说,客户端通过HTTP方法对服务器的资源进行操作, 实现表现层状态转化。

3.2设计原则

Restful 是目前最流行的 API 设计规范,用于 Web 数据接口的设计。
Restful API设计原则:

  • 尽量将API 部署在一个专用的域名下,如  http://api.itany.com、http://api.github.com。
  • API的版本应该在URL中体现,如  http://api.itany.com/v2
  • URL中不要使用动词应使用资源名词,且使用名词的复数形式,如
功能说明请求类型URL
获取用户列表GEThttp://api.itany.com/v2/users
根据id获取用户GEThttp://api.itany.com/v2/users/id
添加用户POSThttp://api.itany.com/v2/users
根据id删除用户DELETEhttp://api.itany.com/v2/users/id
修改用户PUThttp://api.itany.com/v2/users

注:简单来说,可以使用同一个 URL ,通过约定不同的 HTTP 方法来实施不同的业务。

  • 服务器响应时返回JSON对象,应包含HTTP状态码、消息、结果等,如

{code:200,message:"success",result:{id:1001,name:"tom"}}

  • 最好在返回的结果中提供链接, 指向其他的API方法,使得用户不用查文档, 就能知道下一步该怎么做。

3.3用法

cloud­-provider:构建Restful API服务
cloud-­consumer:

  • 使用RestTemplate调用Rest服务。
  • RestTemplate是Spring提供的用于访问Rest服务的客户端,提供了访问远程Http服务的方法。

3.3.1实践cloud­-provider

新建一个SpringBoot项目:

 使用yml文件,并改端口号:

 要提供一个用户的服务,属于公共的资源,所以新建一个公共的工程。来一个普通的Maven项目:

 一个IDEA界面如何同时打开多个项目 - 小南的歌 - 博客园

 

 在新项目cloud-common的pom.xml中添加依赖lombok:

 在项目cloud-common的文件夹com.itly.entity中新建实体类User:

package com.itly.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;
}

因为项目cloud-provider-8001要通过web的方式提供服务,所以在项目cloud-provider-8001中添加一个Controller,叫UserController:

package com.itly.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController //Restful API设计原则:所有的请求都会采用JSON的方式进行响应。
@RequestMapping("/users")  //Restful API设计原则:URL使用资源名称,且采用复数形式。
public class UserController {
    //获取用户列表
    public ResponseResult getUserList(){
        
    }
}

因为现在还没有ResponseResult(爆红了),所以需要在项目cloud-common的com.itly.vo中新建ResponseResult类:

package com.itly.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseResult implements Serializable {
    private Integer code;//状态码
    private String message;//消息
    private Object result;//结果
}

然后因为项目cloud-provider-8001需要依赖项目cloud-common,所以要在项目cloud-provider-8001中添加依赖:

 

 然后在项目cloud-provider-8001中的Controller中UserController里面就可以返回ResponseResult:

package com.itly.controller;

import com.itly.entity.User;
import com.itly.vo.ResponseResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@RestController //Restful API设计原则:所有的请求都会采用JSON的方式进行响应。
@RequestMapping("/users")  //Restful API设计原则:URL使用资源名称,且采用复数形式。
public class UserController {
    //现在先不写service、deo、数据库,只是模拟一下数据库。
    // 所以定义一个集合存储用户的信息,把用户id作为key,把用户对象作为value。

    //模拟数据库
    static Map<Integer, User> usersMap = new HashMap<>();

    /*以前使用@RequestMapping(method = RequestMethod.GET),后来简化了:
    现在在spring的新版本4.0以后,新增了一个注解@GetMapping*/
    //获取用户列表,使用get请求方式,表示这个方法只接收get请求。且不用再写路径(“/”)。
    @GetMapping
    public ResponseResult getUserList(){
        //从数据库中找出来的结果,一般是个list集合。
        List<User> list = new ArrayList<>(usersMap.values());
        ResponseResult result = new ResponseResult();
        result.setCode(200);
        result.setMessage("success");
        result.setResult(list);
        return result;
    }
}

然后可以启动CloudProvider8001Application.class,尝试一下http://localhost:8001/users

{"code":200,"message":"success","result":[]}

因为此时User还没有数据,所以result是空的。

 那我们把result也写一下:

     static Integer id = 1;    
    //获取用户列表
    //获取指定的用户,要传参数,参数从路径变量里面来@PathVariable
    @GetMapping("/{id}")
    public ResponseResult getUser(@PathVariable Integer id){
      User user = usersMap.get(id);
        ResponseResult result = new ResponseResult();
        result.setCode(200);
        result.setMessage("success");
        result.setResult(user);
        return result;

    }

这时,我们发现,每次都要写以下这几行:

ResponseResult result = new ResponseResult();
result.setCode(200);
result.setMessage("success");
result.setResult(user);

那是不是可以简化一下代码,把这几行代码作为一个方法封装进项目cloud-common的ResponseResult类的一个方法中:

package com.itly.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseResult implements Serializable {
    private Integer code;//状态码
    private String message;//消息
    private Object result;//结果
    //当成功的时候,调用这个success方法。
    public static ResponseResult success(Object result){
        ResponseResult rs = new ResponseResult();
        rs.setCode(200);//这里先写死状态码,实际情况中,应该定义为http的常量
        rs.setMessage("success");
        rs.setResult(result);
        return rs;
    }
    //无参的
    public static ResponseResult success(){
        ResponseResult rs = new ResponseResult();
        rs.setCode(200);//这里先写死状态码,实际情况中,应该定义为http的常量
        rs.setMessage("success");
        return rs;
    }
}

那么在项目cloud-provider-8001中UserController类就可以写成:顺便把添加用户也写上。

package com.itly.controller;

import com.itly.entity.User;
import com.itly.vo.ResponseResult;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController //Restful API设计原则:所有的请求都会采用JSON的方式进行响应。
@RequestMapping("/users")  //Restful API设计原则:URL使用资源名称,且采用复数形式。
public class UserController {
    //现在先不写service、deo、数据库,只是模拟一下数据库。
    // 所以定义一个集合存储用户的信息,把用户id作为key,把用户对象作为value。
    //模拟数据库
    static Map<Integer, User> usersMap = new HashMap<>();
    static Integer id = 1;

    /*以前使用@RequestMapping(method = RequestMethod.GET),后来简化了:
    现在在spring的新版本4.0以后,新增了一个注解@GetMapping*/
    //获取用户列表,使用get请求方式,表示这个方法只接收get请求。且不用再写路径(“/”)。
    @GetMapping
    public ResponseResult getUserList(){
        //从数据库中找出来的结果,一般是个list集合。
        List<User> list = new ArrayList<>(usersMap.values());
        return ResponseResult.success(list);
    }
    //获取用户列表
    //获取指定的用户,要传参数,参数从路径变量里面来@PathVariable
    @GetMapping("/{id}")
    public ResponseResult getUser(@PathVariable Integer id){
       //User user = usersMap.get(id);
       return ResponseResult.success(usersMap.get(id));
    }
       //添加用户
    @PostMapping
    public ResponseResult postUser(User user){
        user.setId(id++);//模拟数据库,每次新建一个数据,id自增一
        user.setUsername("丽萨");
        user.setPassword("123456");
        usersMap.put(user.getId(), user);//放入map集合中
        return ResponseResult.success();//没有数据要传输。调用无参的ResponseResult.success()
    }
    //删除用户
    @DeleteMapping("/{id}")
    public ResponseResult deleteUser(@PathVariable Integer id){
        usersMap.remove(id);
        return ResponseResult.success();
    }
    //更新用户信息
    @PutMapping
    public ResponseResult putUser(User user){
        User u = usersMap.get(user.getId());
        u.setUsername(user.getUsername());
        u.setPassword(user.getPassword());
        return ResponseResult.success();
    }
}

这里大概看看就好,不能运行,因为没有数据库。重在理模块相互调用、增删改查用什么注解、ResponseResult响应。

就对应了:

 3.3.2实践cloud-­consumer 消费者

新建一个springBoot项目:cloud-consumer-8080

 

那么cloud-consumer-8080项目怎么去调用项目cloud-provider-8001?

在cloud-consumer-8080项目中编写UserController类:

package com.itly.controller;

import com.itly.vo.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/users")
public class UserController {
    /*
     * 在java代码里如何发送http请求:以前用HttpClient。
     *现在在Spring中提供了一个工具:RestTemplate调用Rest服务。
     *RestTemplate是Spring提供的用户访问Rest服务的客户端,提供了访问远程Http服务的方法。
     * */
    @Autowired       //
    private RestTemplate restTemplate;

    @GetMapping
    public ResponseResult getUserList(){

        return ResponseResult.success();
    }

}

希望Spring能够自动注入restTemplate,但是Spring里没有自动装配这个RestTemplate工具类,需要我们自己去配置。

在cloud-consumer-8080项目中的文件com.itly.config中创建SpringConfig类:

package com.itly.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class SpringConfig {
    @Bean
    public RestTemplate restTemplate(){
        return  new RestTemplate();
    }
}

那么,在cloud-consumer-8080项目中编写UserController类:发送请求,对方返回的是键值对象,所以使用

//发送get请求,参数分别表示:请求的路径url、响应数据类型ResponseResult、占位符的值id。

//发送get请求,参数分别表示:请求的路径url、响应数据类型ResponseResult。

package com.itly.controller;

import com.itly.vo.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/users")
public class UserController {
    /*
     * 在java代码里如何发送http请求:以前用HttpClient。
     *现在在Spring中提供了一个工具:RestTemplate调用Rest服务。
     *RestTemplate是Spring提供的用户访问Rest服务的客户端,提供了访问远程Http服务的方法。
     * */
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    public ResponseResult getUserList(){
        //发送get请求,参数分别表示:请求的路径url、响应数据类型ResponseResult
        return restTemplate.getForObject("http://localhost:8001/users",ResponseResult.class);
        //将返回的JSON字符串转换为ResponseResult对象。
    }
    @GetMapping("/{id}")
    public ResponseResult getUser(@PathVariable Integer id){

        //http://localhost:8001/users/1,这样发请求。id=1。
        //发送get请求,参数分别表示:请求的路径url、响应数据类型ResponseResult
        //return restTemplate.getForObject("http://localhost:8001/users/"+id,ResponseResult.class);
        //发送get请求,参数分别表示:请求的路径url、响应数据类型ResponseResult、占位符的值{id}。
        return restTemplate.getForObject("http://localhost:8001/users/{id}",ResponseResult.class,id);
        //将返回的JSON字符串转换为ResponseResult对象。
    }
}

 测试一下:

启动cloud-consumer-8080项目CloudConsumer8080Application主程序类。

启动cloud-provider-8001项目CloudProvider8001Application主程序类。

 得到地址访问结果:

 然后我们来编写添加用户的操作:

其中restTemplate.postForObject();的第二个参数Object request必须要接收封装成MultiValueMap的值。而且params必须使用add()方法存放键值对,不能用put()。

    @PostMapping
    public ResponseResult postUser(User user){
        //必须使用MultiValueMap来封装参数
        MultiValueMap params = new LinkedMultiValueMap() ;
        params.add("username",user.getUsername());
        params.add("password",user.getPassword());
        //发送post请求,参数分别表示:请求路径、请求参数、响应数据类型
        return restTemplate.postForObject(GLOBAL_URL,params,ResponseResult.class);
    }

测试一下:这里注释掉!!!

 启动cloud-consumer-8080项目CloudConsumer8080Application主程序类。

启动cloud-provider-8001项目CloudProvider8001Application主程序类。

因为我们是后端,在浏览器中按回车,都是get请求,要想测试post(前端页面表格可以)、delete(前端页面ajax请求可以)、put(前端页面ajax请求可以),并且不写前端页面,需要用到一个IDEA的工具:Tools-》HTTP Client-》Test RESTful Web Service。

 参看示例,然后编写:

得到结果:

 老版本的IDEA中可能是这样的:

 编写删除:

删除操作delete()没有返回值,返回值处写的是void。

同样的,我们可以看到修改操作put()也是没有返回值的。

但是cloud-provider-8001项目删除以后,要有一个响应的结果。如果需要有返回值,只能调用他们的底层方法exchange()。

 @DeleteMapping("/{id}")
    public ResponseResult deleteUser(@PathVariable Integer id){
        /*
        * 参与delete和put请求,默认是无法接收返回值的。
        * 如果需要有返回值,只能调用他们的底层方法exchange()。
        * */
        //restTemplate.delete();//没有返回值
        ResponseEntity<ResponseResult> responseEntity =
                restTemplate.exchange(
                        GLOBAL_URL + "/{id}",
                        HttpMethod.DELETE,
                        null,
                        ResponseResult.class,
                        id);
        /*exchange()参数:
            请求路径URL、请求方式(delete、put、post、get等)、
            请求体(这里没有,就写null)、响应类型、未知变量的值(id)。
        exchange()返回值:响应体ResponseEntity<T>。
        在responseEntity.getBody()中的到真正的返回值。
        */
        return responseEntity.getBody();
    }

 测试:

启动cloud-consumer-8080项目CloudConsumer8080Application主程序类。

启动cloud-provider-8001项目CloudProvider8001Application主程序类。

编写

### Send POST request with body as parameters
POST http://localhost:8080/users
Content-Type: application/x-www-form-urlencoded

username=111&password=12574991

### Send POST request with body as parameters
POST http://localhost:8080/users
Content-Type: application/x-www-form-urlencoded

username=222&password=9487265


### Send POST request with body as parameters
POST http://localhost:8080/users
Content-Type: application/x-www-form-urlencoded

username=333&password=5498461

###
GET http://localhost:8080/users

###
GET http://localhost:8080/users/2

###
DELETE http://localhost:8080/users/2

 修改操作:

    @PutMapping
    public ResponseResult putUser(User user){
        //此处只能在URL中通过键值对的形式进行参数的传递。
        //防止问号?后面没有参数,所以随意加一个参数”tmp=1“,这个参数不会被接收,所以可以写v=1或者s=3这种任意写即可。
        String url = new StringBuffer(GLOBAL_URL)
                .append("?tmp=1")
                .append("&id={id}")
                .append("&username={username}")
                .append("&password={password}")
                .toString();
/*        ResponseEntity<ResponseResult> responseEntity =
                restTemplate.exchange(
                        url,
                        HttpMethod.PUT,
                        null,
                        ResponseResult.class,
                        user.getId(),user.getUsername(),user.getPassword());*/
        Map params = new HashMap();
        params.put("id",user.getId());
        params.put("username",user.getUsername());
        params.put("password",user.getPassword());
        ResponseEntity<ResponseResult> responseEntity =
                restTemplate.exchange(
                        url,
                        HttpMethod.PUT,
                        null,
                        ResponseResult.class,
                        params);
        return responseEntity.getBody();
    }

 测试:

启动cloud-consumer-8080项目CloudConsumer8080Application主程序类。

启动cloud-provider-8001项目CloudProvider8001Application主程序类。

编写

### Send POST request with body as parameters
POST http://localhost:8080/users
Content-Type: application/x-www-form-urlencoded

username=111&password=12574991

### Send POST request with body as parameters
POST http://localhost:8080/users
Content-Type: application/x-www-form-urlencoded

username=222&password=9487265


### Send POST request with body as parameters
POST http://localhost:8080/users
Content-Type: application/x-www-form-urlencoded

username=333&password=5498461

###
GET http://localhost:8080/users

###
GET http://localhost:8080/users/2

###
DELETE http://localhost:8080/users/2
###
PUT http://localhost:8080/users
Content-Type: application/x-www-form-urlencoded

id=3&username=three3three&password=333333

3.4使用postman

Postman是一款非常优秀的调试工具,可以用来模拟发送各类HTTP请求,进行接口测试。

以后在实际的开发过程中,自己要测试好这些接口,然后再对接前端工程师,不能自己不测试,让前端去帮你测!!!

3.4.1下载安装

需要去官网下载对应的安装包:Postman API Platform | Sign Up for Free

 下载完之后,注册,登录即可:

 进入首页:

 

 设置存储位置和字体大小。

 3.4.2使用操作

 

 进入工作台:

尝试一下:

3.5 使用Swagger2

通常情况下,我们会创建一份Restful API文档来记录所有的接口细节,供其他开发人员使用提供的接口服务,但会存在以下的问题:

  • 接口众多,并且细节复杂。
  • 需要根据接口的变化,不断修改API文档,非常麻烦,费时费力。

Swagger2的出现就是为了解决上述的这些问题,减少创建API文档的工作量:

  • 后端人员在代码里添加接口的说明内容,就能够生成可预览的API文档,无须再维护Word文档。
  • 让维护文档和修改代码整合为一体,在修改代码逻辑的同时方便的修改文档说明。
  • 提供了强大的页面测试功能,便于对接口进行测试。

 使用步骤:更新了Swagger2的使用方法,具体可参看:

1. 添加Swagger2依赖

    <!--添加Swagger2依赖-->
    <!--1.Swagger2核心依赖-->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-boot-starter</artifactId>
      <version>3.0.0</version>
    </dependency>
<!--老版本是添加这两个,但是现在只需要添加一个springfox-boot-starter就可以了-->
    <!--  <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>3.0.0</version>
      </dependency>
      <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>3.0.0</version>
      </dependency>-->
    <!--2.解决Swagger2默认生成的文档在类型转换时的bug,添加如下两个依赖-->
    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-annotations</artifactId>
      <version>1.6.8</version>
    </dependency>
    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-models</artifactId>
      <version>1.6.8</version>
    </dependency>

因为在这个项目中,响应结果对象是放在cloud-common中的,所以把这几个依赖加在这里:

2. 创建Swagger2配置类

@EnableOpenApi
@Configuration
/**
 * @EnableWebMvc 解决springboot版本太高问题
 */ 
@EnableWebMvc
//@EnableSwagger2 //启用Swagger2;旧版添加这个依赖,现在舍弃了。
public class Swagger2Config {
    /*
    * 创建Restful API文档内容
    * */
    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //指定要暴露给Swagger来展示的接口所在的包
                .apis(RequestHandlerSelectors.basePackage("com.itly.controller"))
                .paths(PathSelectors.any())
                .build();
    }
    /*
    * 创建API的基本信息,这些信息会展现在文档页面中
    * */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("使用Swagger2构建Restful API文档")//标题
                .description("欢迎访问后端API接口文档")//描述
                .contact(new Contact("sly","https://blog.csdn.net/qq_41915723?type=blog","XXXX@163.com"))//联系人
                .version("1.0")//版本号
                .build();
    }
}

 添加到cloud-provider-8001中:

3.添加文档内容
使用Swagger2提供的注解对接口进行说明,常用注解:

  • @Api 标注在类上,对类进行说明
  • @ApiOperation  标注在方法上,对方法进行说明
  • @ApiImplicitParams  标注在方法上,对方法的多个参数进行说明
  • @ApiImplicitParam  标注在方法上,对方法的一个参数进行说明
  • @ApiModel  标注在模型Model上,对模型进行说明
  • @ApiModelProperty  标注在属性上,对模型的属性进行说明
  • @ApiIgnore  标注在类或方法上,表示忽略这个类或方法

例如我们在这里尝试一下,给D:\javaExampleTest\springboot\cloud-provider-8001\src\main\java\com\itly\controller\UserController.java,这个类添加@Api注解

4.查看Restful API的页面,并测试接口

在新版本的Swagger2中,可以在IDEA中搜索查看这个类SwaggerUiWebMvcConfigurer.class:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package springfox.boot.starter.autoconfigure;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

public class SwaggerUiWebMvcConfigurer implements WebMvcConfigurer {
    private final String baseUrl;

    public SwaggerUiWebMvcConfigurer(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        String baseUrl = StringUtils.trimTrailingCharacter(this.baseUrl, '/');
        registry.addResourceHandler(new String[]{baseUrl + "/swagger-ui/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/springfox-swagger-ui/"}).resourceChain(false);
    }

    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController(this.baseUrl + "/swagger-ui/").setViewName("forward:" + this.baseUrl + "/swagger-ui/index.html");
    }
}

可以知道启动SpringBoot程序后,可以访问 http://localhost:8001/swagger-ui/index.html。进入链接可以看到:

再尝试一下其他的:

 

尝试:

 根据下面Models这个可知,把Model标注在cloud-common的ResponseResult.class里面。

 

 结果:

实际上还可以在User.class上加(如上图):但是没什么意义,在http://localhost:8001/swagger-ui/index.html中也看不到。

还可以在页面测试,但是并没有postman好用。

4.Eureka

4.1简介

Eureka是一个基于REST的服务,主要用于服务的注册和发现,以达到负载均衡和中间层服务故障转移的目的。
作用与zookeeper类似,都可以作为服务注册中心。

4.2结构体系

执行流程:
1. 服务提供者在启动时,向注册中心注册自己提供的服务。
2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
3. 注册中心返回服务提供者地址给消费者。
4. 服务消费者从提供者地址中调用提供者。
两个组件:
Eureka Server 服务端

  • 指的是服务注册中心,提供服务的注册和发现。
  • 注册中心会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。

Eureka Client 客户端

  • 指的是服务提供者和消费者,在应用程序启动时会和服务端进行交互,注册或订阅服务。

4.3 搭建服务注册中心

步骤:
1. 创建项目cloud-eureka-7001,勾选Spring Cloud Discovery->Eureka Server。

2. 编辑pom.xml文件,配置依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2021.0.4</spring-cloud.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

 3.编辑application.yml文件,配置eureka

server:
  port: 7001
eureka:
  instance: #实例
    hostname: localhost #主机名
  client: #客户端
    #设置服务注册中心的地址   #default Zone默认区域
    service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
    #是否将当前项目注册到Eureka Server中,默认为true。但现在这个项目就是注册中心,所以要修改为false。
    register-with-eureka: false #注册eureka
    #是否从Eureka Server中心获取服务提供者的注册信息,默认为true。但现在这个项目就是注册中心,所以要修改为false。只有消费者菜需要为true。
    fetch-registry: false  #fetch(拿来,取得) registry获取注册表

4.3.1 Spring Cloud Eureka 常用配置及说明

 Spring Cloud Eureka 常用配置及说明

配置参数

默认值

说明

服务注册中心配置

  

Bean类:org.springframework.cloud.netflix

.eureka.server.EurekaServerConfigBean

eureka.server.enable-self-preservation

false

关闭注册中心的保护机制,Eureka 会统计15分钟之内心跳失败的比例低于85%将会触发保护机制,不剔除服务提供者,如果关闭服务注册中心将不可用的实例正确剔除

服务实例类配置

  

Bean类:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean

eureka.instance.prefer-ip-address

false

不使用主机名来定义注册中心的地址,而使用IP地址的形式,如果设置了

eureka.instance.ip-address 属性,则使用该属性配置的IP,否则自动获取除环路IP外的第一个IP地址

eureka.instance.ip-address

  

IP地址

eureka.instance.hostname

  

设置当前实例的主机名称

eureka.instance.appname

  

服务名,默认取 spring.application.name 配置值,如果没有则为 unknown

eureka.instance.lease-renewal-interval-in-seconds

30

定义服务续约任务(心跳)的调用间隔,单位:秒

eureka.instance.lease-expiration-duration-in-seconds

90

定义服务失效的时间,单位:秒

eureka.instance.status-page-url-path

/info

状态页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置

eureka.instance.status-page-url

  

状态页面的URL,绝对路径

eureka.instance.health-check-url-path

/health

健康检查页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置

eureka.instance.health-check-url

  

健康检查页面的URL,绝对路径

服务注册类配置

  

Bean类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean

eureka.client.service-url.

  

指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为 defaultZone;默认的Value为 http://localhost:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。

如果服务注册中心加入了安全验证,这里配置的地址格式为: http://<username>:<password>@localhost:8761/eureka 其中 <username> 为安全校验的用户名;<password> 为该用户的密码

eureka.client.fetch-registery

true

检索服务

eureka.client.registery-fetch-interval-seconds

30

从Eureka服务器端获取注册信息的间隔时间,单位:秒

eureka.client.register-with-eureka

true

启动服务注册

eureka.client.eureka-server-connect-timeout-seconds

5

连接 Eureka Server 的超时时间,单位:秒

eureka.client.eureka-server-read-timeout-seconds

8

读取 Eureka Server 信息的超时时间,单位:秒

eureka.client.filter-only-up-instances

true

获取实例时是否过滤,只保留UP状态的实例

eureka.client.eureka-connection-idle-timeout-seconds

30

Eureka 服务端连接空闲关闭时间,单位:秒

eureka.client.eureka-server-total-connections

200

从Eureka 客户端到所有Eureka服务端的连接总数

eureka.client.eureka-server-total-connections-per-host

50

从Eureka客户端到每个Eureka服务主机的连接总数

4.编辑启动类,启用Eureka服务器

package com.ly;

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

@SpringBootApplication
@EnableEurekaServer //启动Eureka服务器
public class CloudEureka7001Application {

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

}

5.访问Eureka­ Server服务管理平台
通过浏览器访问 localhost:7001

4.4注册服务(服务提供者 去 注册中心 注册)

步骤:注册服务,就是客户端,这里使用cloud-provider-8001项目
1. 编辑pom.xml文件,配置Eureka­ Client依赖

 <properties>
       <java.version>17</java.version>
       <spring-cloud.version>2021.0.4</spring-cloud.version>
 </properties>
 <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>3.1.4</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

  注:如果是新建项目,可以勾选Eureka Discovery Client

2.编辑application.yml文件,配置eureka

server:
  port: 8001
spring:
  application:
    name: user-provider #应用名,在注册中心显示的服务名
eureka:
  instance:
    hostname: localhost
  client:
    service-url: #指定服务注册中心的地址
      defaultZone: http://${eureka.instance.hostname}:7001/eureka

 3.编辑启动类,启用Eureka客户端

package com.itly;

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

@EnableEurekaClient //启动Eureka客户端
@SpringBootApplication
public class CloudProvider8001Application {

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

}

 启动localhost:7001:Eureka的自动保护机制,默认是开启的,不用管。

 刷新之后:可以看到应用名和注册端口。

 4.5自我保护机制 

在某个时刻,如果某个服务不可用了,Eureka不会立即的清理该服务, 依旧会对该服务的信息进行保存。

默认情况下,微服务在Eureka上注册后,会每30秒发送心跳包,Eureka通过心跳来判断服务是否健康,如果Eureka的Server在一定时间内(默认90s),没有接收到某个微服务实例的心跳,将会注销该实例。
但是当网络发生故障时,通常会导致Eureka Server在短时间内无法收到大批微服务的心跳,但微服务自身是正常的,只是网络通信出现了故障。

考虑到这种情况,Eureka设置了一个阀值,当心跳失败的比例在15分钟之内低于85%时,Eureka Server认为很大程度上出现了网络故障,将不再删除心跳过期的微服务,尽可能的保护这些注册信息,自动进入自我保护模式。

当网络故障被解决时,服务将自动退出 自我保护模式。

 可以关闭自我保护机制eureka.server.enable-self-preservation=false。(默认是true。一般不建议关闭。)

5. Feign客户端

5.1 简介 

Feign是一个HTTP客户端,可以更快捷、优雅地调用HTTP服务,使编写HTTPClient变得更简单。

在Spring Cloud中使用Feign非常简单,只需要创建一个接口,然后在接口上添加一些注解就可以了。

5.2 用法 

步骤:
1. 在cloud-consumer-8080项目中编辑pom.xml文件,配置Feign和Eureka­Client依赖

    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2021.0.4</spring-cloud.version>
    </properties>
    <dependencies>
        <!--如果出现spring-cloud-starter-openfeign引入不了。检查maven配置,要配置成本地Maven和仓库。-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>3.1.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>3.1.4</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

2. 编辑application.yml文件,配置eureka

server:
  port: 8080
eureka:
  client:
    service-url: #指定注册中心的地址
      defaultZone: http://localhost:7001/eureka/
    register-with-eureka: false #是否将自己注册到EurekaServer中,默认为true
    fetch-registry: true #获取注册表,默认为true。

3. 编辑启动类,启用Eureka客户端

//启用Eureka客户端
@EnableEurekaClient
//启动Feign客户端,扫描指定包下所有的feign注解
@EnableFeignClients(basePackages = "com.itly.service")
@SpringBootApplication
public class CloudConsumer8080Application {

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

4. 创建接口并配置

package com.itly.service;

import com.itly.vo.ResponseResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import java.util.Map;

//调用的服务名user-provider,
// 到Eureka中寻找对应的服务名,
// 找到的是:微服务的ip:端口,
//即http://localhost:8001
@FeignClient(value = "user-provider")
@Service
public interface UserService {
    @GetMapping("/users")
    public ResponseResult getUserList();
    @GetMapping("/users/{id}")
    public ResponseResult getUser(@PathVariable(value = "id") Integer id);
    /*Feign传递对象参数
    * 方式一:将对象参数拆为多个简单类型参数,且必须添加@RequestParam注解
    * */
    @PostMapping("/users")
    public ResponseResult postUser(@RequestParam("username")String username,
                                   @RequestParam("password")String password
    );
    @DeleteMapping("/users/{id}")
    public ResponseResult deleteUser(@PathVariable(value = "id") Integer id);
    /*Feign传递对象参数
    * 方式二:使用Map替代对象参数,且必须添加@RequestParam注解
    * */
    @PutMapping("/users")
    public ResponseResult putUser(@RequestParam Map<String,Object> map);
}

 新建类UserControllerFeign.java,把原来的UserController.java里面的内容稍作修改:换成userService.XXX。

package com.itly.controller;

import com.itly.entity.User;
import com.itly.service.UserService;
import com.itly.vo.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/users")
public class UserControllerFeign {
    @Autowired
    private UserService userService;

    @GetMapping
    public ResponseResult getUserList(){
        return userService.getUserList();
    }
    @GetMapping("/{id}")
    public ResponseResult getUser(@PathVariable Integer id){
        return userService.getUser(id);
    }
    @PostMapping
    public ResponseResult postUser(User user){
        return userService.postUser(user.getUsername(),user.getPassword());
    }
    @DeleteMapping("/{id}")
    public ResponseResult deleteUser(@PathVariable Integer id){
        return userService.deleteUser(id);
    }
    @PutMapping
    public ResponseResult putUser(User user){
        Map<String,Object> map = new HashMap<>();
        map.put("id",user.getId());
        map.put("username",user.getUsername());
        map.put("password",user.getPassword());
        return userService.putUser(map);
    }
或
    @PutMapping
    public ResponseResult putUser(@RequestParam Map map){//此处可以直接去使用Map来接收参数。
        return userService.putUser(map);
    }

}

启动项目cloud-eureka-7001、cloud-provider-8001和cloud-consumer-8080,cloud-consumer-8080出现报错:

报错:

Failed to start bean 'documentationPluginsBootstrapper';  nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this. condition" is null

翻译:

启动documentationPluginsBootstrapper bean失败;嵌套异常是java.lang.NullPointerException:不能调用"org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()",因为"这。条件为空

查询原因:

这是整合Swagger3出现的错误,可以降低版本或者使用下面配置的方式

解决办法:

修改SpringConfig.java,添加注解@EnableWebMvc 和@EnableOpenApi。

@Configuration
@EnableWebMvc
@EnableOpenApi
public class SpringConfig {
    @Bean
    public RestTemplate restTemplate(){
        return  new RestTemplate();
    }
}

重新启动:

在postman里测试,可以成功。 

5.3 传参

feign传递对象参数的解决方式:

  • 方式一:将对象参数拆为多个简单类型参数,且必须添加@RequestParam注解。
  • 方式二:使用Map替代对象参数,且必须添加@RequestParam注解。

(如:方式一:

@FeignClient(value = "user-provider")
@Service
public interface UserService {
    /*Feign传递对象参数
    * 方式一:将对象参数拆为多个简单类型参数,且必须添加@RequestParam注解
    * */
    @PostMapping("/users")
    public ResponseResult postUser(@RequestParam("username")String username,
                                   @RequestParam("password")String password
    );
}


@RestController
@RequestMapping("/users")
public class UserControllerFeign {
    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseResult postUser(User user){
        return userService.postUser(user.getUsername(),user.getPassword());
    }
 
}

)

(如:方式二:

@FeignClient(value = "user-provider")
@Service
public interface UserService {
    /*Feign传递对象参数
    * 方式二:使用Map替代对象参数,且必须添加@RequestParam注解
    * */
    @PutMapping("/users")
    public ResponseResult putUser(@RequestParam Map<String,Object> map);
}

@RestController
@RequestMapping("/users")
public class UserControllerFeign {
    @Autowired
    private UserService userService;

    @PutMapping
    public ResponseResult putUser(User user){
        Map<String,Object> map = new HashMap<>();
        map.put("id",user.getId());
        map.put("username",user.getUsername());
        map.put("password",user.getPassword());
        return userService.putUser(map);
    }
或
    @PutMapping
    public ResponseResult putUser(@RequestParam Map map){//此处可以直接去使用Map来接收参数。
        return userService.putUser(map);
    }
}

)

6. Hystrix断路器

Hystrix 豪猪属;猬草属;豪猪;断路器。

6.1 服务熔断和服务降级

服务雪崩:在微服务架构中服务之间会相互调用和依赖,如果某个服务发生故障,可能会导致多个服务故障,从而导致整个系统故障。

解决服务雪崩的方式:

  • 服务熔断

        当服务出现不可用或响应超时时,为了防止整个系统出现雪崩, 暂时停止对该服务的调用,直接返回一个结果,快速释放资源。
        如果检测到目标服务情况好转,则恢复对目标服务的调用。

  • 服务降级

        为了防止核心业务功能出现负荷过载或者响应慢的问题,将非核心服务进行降级,暂时性的关闭或延迟使用,保证核心服务的正常运行。

6.2简介

Hystrix就是用来实现服务熔断,其实就是一种机制,当某个服务不可用时,可以阻断故障的传播,称为断路器或熔断器。

  • Hystrix负责监控服务之间的调用情况,当出现连续多次调用失败的情况时会进行熔断保护。
  • 该服务的断路器就会打开,直接返回一个由开发者设置的fallback(退路)信息。
  • Hystrix会定期再次检查故障的服务,如果故障服务恢复,将继续使用服务。

6.3用法

断路器是安装在服务消费者上,我们需要做的是在服务消费者上开启断路器并配置。
在Feign中使用Hystrix是非常简单的,已经包含了整合Hystrix的依赖。

Spring Cloud环境

        现在使用的Spring Cloud版本是

    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2021.0.4</spring-cloud.version>
    </properties>

spring-cloud-starter-openfeign版本是 3.1.4。不同的Spring Cloud版本,对Hystrix的配置不一样。

Feign本身支持Hystrix,默认是关闭Hystrix的,需要在配置文件中开启。但不同的Spring Cloud版本,开启Hystrix的配置不一样。

         1、Spring Cloud 2020之前的版本

          只需在配置文件中设置feign.hystrix.enabled=true

         2、Spring Cloud 2020之后的版本

          feign.hystrix.enabled=true无法解析,需要配置:feign.circuitbreaker.enabled=true

步骤:在项目cloud-consumer-8080中:
1. 编辑application.yml文件,启用断路器。并且在pom.xml中引入依赖。

#启用断路器
feign:
  circuitbreaker:
    enabled: true
        <!--hystrix依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.2.10.RELEASE</version>
        </dependency>

完整的pom.xml文件:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.itly</groupId>
    <artifactId>cloud-consumer-8080</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cloud-consumer-8080</name>
    <description>cloud-consumer-8080</description>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2021.0.4</spring-cloud.version>
    </properties>
    <dependencies>
        <!--如果出现spring-cloud-starter-openfeign引入不了。检查maven配置,要配置成本地Maven和仓库。-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>3.1.4</version>
        </dependency>
        <!--hystrix依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.2.10.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>3.1.4</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.itly</groupId>
            <artifactId>cloud-common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

完整的application.yml文件:

server:
  port: 8080
eureka:
  client:
    service-url: #指定注册中心的地址
      defaultZone: http://localhost:7001/eureka/
    register-with-eureka: false #是否将自己注册到EurekaServer中,默认为true
    fetch-registry: true #获取注册表,默认为true。

#启用断路器
feign:
  circuitbreaker:
    enabled: true

2. 设置fallback信息
在UserService.java的@FeignClient注解中指定fallback参数 

// 调用的服务名,到Eureka中寻找对应的微服务,找到的是:微服务的ip:port
@FeignClient(value = "user-­provider",fallback = UserServiceFallback.class)
public interface UserService {

创建UserService接口的实现类UserServiceFallback.java,并配置返回的信息:

package com.itly.service;

import com.itly.vo.ResponseResult;
import org.springframework.stereotype.Service;
import java.util.Map;

@Service
public class UserServiceFallback implements UserService{

    @Override
    public ResponseResult getUserList() {
        System.out.println("断路器开启。。。。UserServiceFallback.getUserList");
        return ResponseResult.fail("获取用户列表失败");
    }

    @Override
    public ResponseResult getUser(Integer id) {
        return ResponseResult.fail("获取指定用户失败");
    }

    @Override
    public ResponseResult postUser(String username, String password) {
        return ResponseResult.fail("添加用户失败");
    }

    @Override
    public ResponseResult deleteUser(Integer id) {
        return ResponseResult.fail("删除用户失败");
    }

    @Override
    public ResponseResult putUser(Map<String, Object> map) {
        return ResponseResult.fail("修改用户失败");
    }
}

在项目cloud-common\src\main\java\com\itly\vo\ResponseResult.java中:添加方法fail()。

package com.itly.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
@ApiModel(value = "ResponseResult 响应结果对象")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseResult implements Serializable {
    @ApiModelProperty(value = "响应码")
    private Integer code;//状态码
    @ApiModelProperty(value = "响应消息")
    private String message;//消息
    @ApiModelProperty(value = "响应结果")
    private Object result;//结果
    //当成功的时候,调用这个success方法。
    public static ResponseResult success(Object result){
        ResponseResult rs = new ResponseResult();
        rs.setCode(200);//这里先写死状态码,实际情况中,应该定义为http的常量
        rs.setMessage("success");
        rs.setResult(result);
        return rs;
    }
    //无参的
    public static ResponseResult success(){
        ResponseResult rs = new ResponseResult();
        rs.setCode(200);//这里先写死状态码,实际情况中,应该定义为http的常量
        rs.setMessage("success");
        return rs;
    }

    public static ResponseResult fail(String message) {
        ResponseResult rs = new ResponseResult();
        rs.setCode(500);
        rs.setMessage(message);
        return rs;
    }
}

3.测试:

当服务提供者不可用或出现异常时,会暂时停止对该服务的调用。

故意使其故障:D在项目cloud-provider-8001\src\main\java\com\itly\controller\UserController.java中:添加这两行,使其发生空指针异常。

 重启这三个项目cloud-eureka-7001、cloud-provider-8001和cloud-consumer-8080:

并且8080应用服务后台不报错,返回错误信息,8001报错:

7. Zuul网关

7.1简介

Zuul是一个路由网关Gateway,包含两大功能:

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

将Zuul和Eureka进行整合,把Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的信息,对于微服务的访问都要通过Zuul进行跳转。

7.2用法

Zuul本身也是一个项目,一个最终也会注册到Eureka中的微服务。
   步骤:
1. 创建项目cloud-zuul-6001,勾选Zuul(新版IDEA中没有Zuul,先不勾选)和Eureka Discovery Client:

 

2. 编辑pom.xml文件,配置依赖

<!--注意这些版本--> 
 <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2021.0.5</spring-cloud.version>
    </properties>		
<!--Zuul依赖-->
		<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>
			<version>2.2.10.RELEASE</version>
		</dependency>

3. 编辑application.yml文件,配置eureka和zuul

server:
  port: 6001
spring:
  application:
    name: zuul-gateway
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/
    registry-fetch-interval-seconds: 5
  instance:
    prefer-ip-address: true
    instance-id: zuul-Gateway:6001
#路由相关配置
zuul:
  prefix: "/v2"   #请求前缀
  routes: #配置路由表
    user: #对于每个微服务,可以指定唯一一个key值,该值可以任意指定
     path: "/user-service/**"     #将/user-service/开头的请求映射到user-provider这个微服务上(8001里)
     service-id: user-provider:8001
#    order:  路由可以配很多个

#    product:

4. 编辑启动类,启用Zuul,添加注解@EnableZuulProxy //启用Zuul

package com.itly;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
//@EnableZuulProxy是@EnableZuulServer的增强版,当Zuul和Eureka一起使用的时候,是要用@EnableZuulProxy的

@EnableZuulProxy //启用Zuul
@SpringBootApplication  //@SpringCloudApplication 会包含 @EnableEurekaClient,所以其实 @EnableEurekaClient 不需要写
public class CloudZuul6001Application {

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

}

5.修改cloud-consumer-8080的UserService,通过Zuul访问微服务,相当于是代理


//调用的服务名user-provider,
// 到Eureka中寻找对应的服务名,
// 找到的是:微服务的ip:端口,
//即http://localhost:8001
//@FeignClient(value = "user-provider",fallback = UserServiceFallback.class)
//所有的微服务的访问都要通过Zuul进行路由跳转
@FeignClient(value = "zuul-gateway",fallback = UserServiceFallback.class)
@Service
public interface UserService {
   // @GetMapping("/users")
   @GetMapping("/v2/user-service/users")
    public ResponseResult getUserList();
    //@GetMapping("/users/{id}")
    @GetMapping("/v2/user-service/users/{id}")
    public ResponseResult getUser(@PathVariable(value = "id") Integer id);
    /*Feign传递对象参数
    * 方式一:将对象参数拆为多个简单类型参数,且必须添加@RequestParam注解
    * */
    //@PostMapping("/users")
    @PostMapping("/v2/user-service/users")
    public ResponseResult postUser(@RequestParam("username")String username,
                                   @RequestParam("password")String password
    );
    //@DeleteMapping("/users/{id}")
    @DeleteMapping("/v2/user-service/users/{id}")
    public ResponseResult deleteUser(@PathVariable(value = "id") Integer id);
    /*Feign传递对象参数
    * 方式二:使用Map替代对象参数,且必须添加@RequestParam注解
    * */
    //@PutMapping("/users")
    @PutMapping("/v2/user-service/users")
    public ResponseResult putUser(@RequestParam Map<String,Object> map);
}

6.测试

记得注释掉cloud-provider-8001\src\main\java\com\itly\controller\UserController.java里面的异常。

重启cloud-consumer-8080、cloud-eureka-7001、cloud-provider-8001、cloud-zuul-6001。

但是,添加删除等功能用不了,我也解决不了这个问题!!!

8. Ribbon负载均衡

8.1简介

Ribbon是一套客户端负载均衡的工具,用于实现微服务的负载均衡 Load Balance。
Ribbon不需要独立部署,Feign集成了Ribbon,自动的实现了负载均衡。

(以前feign依赖中包含了ribbon,现在不包含了!!!! )

8.2 用法 

步骤:
1. 搭建Provider集群
拷贝 cloud­-provider­-8001为cloud-­provider­-8002和cloud-­provider-­8003。

粘贴:鼠标选择外部库,然后粘贴,粘贴之后看不到文件,需要选择新建导入模块。 

  • 修改pom.xml中名称
  • 修改主启动类的类名
  • 修改application.yml配置:端口号和instance­id不同相同

server:
  port: 8002  # 不同的端口
spring:
  application:
    name: user­provider  # 相同的服务名
eureka:
  client:
    service­url:
      defaultZone: http://localhost:7001/eureka/
  instance:
    instance­id: user­provider8002  # 不同的实例id

启动:7001注册中心;6001路由;8001、8002、8003服务提供者;

 查看到了哪一个地址中:
在每一个UserController.java文件中,加上这三行,对应端口号。

 正常结果:发现获取用户列表的时候,会依次轮询添加进8001、8002、8003中。

 

2. 测试负载均衡
默认使用的是轮询的策略
常见的策略:

  • 轮询 (RoundRobinRule)  (默认)
  • 随机 (RandomRule)
  • 响应时间权重(WeightedResponseTimeRule)响应时间越短的服务器被选中的可能性大
  • 并发量最小可用(BestAvailableRule)选取最少并发量请求的服务器

3.改变负载均衡策略
在Zuul服务(cloud-zuul-6001)中,通过配置类指定要应用的负载均衡策略。

@Configuration
public class RibbonConfig {
    @Bean
    public IRule ribbonRule(){
        return new RandomRule(); //随机策略
    }
}

9. 微服务的面试题

1. 什么是微服务


2. 什么是微服务架构


3. 微服务之间是如何通信的


4. SpringCloud 和 Dubbo 之间有什么区别


5. Eureka Server 和 ZooKeeper 之间有什么区别


6. StringCloud 和 SpringBoot, 谈一谈你的理解


7. 什么是服务熔断?什么是服务降级


8. 微服务的优缺点是什么?你在实际开发中遇到过什么样的问题


9. 简述微服务的技术栈


10. Restful API 是什么? 有哪些要求

目录

1. 微服务

1.1微服务简介

1.2微服务架构 

2. SpringCloud技术栈

2.1 简介

2.2技术栈

3.Restful API 

3.1简介

3.2设计原则

3.3用法

3.3.1实践cloud­-provider:

 3.3.2实践cloud-­consumer 消费者

3.4使用postman

3.4.1下载安装

 3.4.2使用操作

3.5 使用Swagger2

4.Eureka

4.1简介

4.2结构体系

4.3 搭建服务注册中心

4.3.1 Spring Cloud Eureka 常用配置及说明

4.4注册服务(服务提供者 去 注册中心 注册)

 4.5自我保护机制 

5. Feign客户端

5.1 简介 

5.2 用法 

5.3 传参

6. Hystrix断路器

6.1 服务熔断和服务降级

6.2简介

6.3用法

Spring Cloud环境

7. Zuul网关

7.1简介

7.2用法

8. Ribbon负载均衡

8.1简介

8.2 用法 

9. 微服务的面试题

1. 什么是微服务

2. 什么是微服务架构

3. 微服务之间是如何通信的

4. SpringCloud 和 Dubbo 之间有什么区别

5. Eureka Server 和 ZooKeeper 之间有什么区别

6. StringCloud 和 SpringBoot, 谈一谈你的理解

7. 什么是服务熔断?什么是服务降级

8. 微服务的优缺点是什么?你在实际开发中遇到过什么样的问题

9. 简述微服务的技术栈

10. Restful API 是什么? 有哪些要求



版权声明:本文为qq_41915723原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。