学习视频:这是自己买的课,现在有点过期了,好多不是最新的。学习笔记里是有更改的。
(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 |
---|---|---|
获取用户列表 | GET | http://api.itany.com/v2/users |
根据id获取用户 | GET | http://api.itany.com/v2/users/id |
添加用户 | POST | http://api.itany.com/v2/users |
根据id删除用户 | DELETE | http://api.itany.com/v2/users/id |
修改用户 | PUT | http://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 常用配置及说明
配置参数 | 默认值 | 说明 |
---|---|---|
服务注册中心配置 |
| 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和EurekaClient依赖
<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配置:端口号和instanceid不同相同
server:
port: 8002 # 不同的端口
spring:
application:
name: userprovider # 相同的服务名
eureka:
client:
serviceurl:
defaultZone: http://localhost:7001/eureka/
instance:
instanceid: userprovider8002 # 不同的实例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 是什么? 有哪些要求
目录
4.3.1 Spring Cloud Eureka 常用配置及说明
4. SpringCloud 和 Dubbo 之间有什么区别
5. Eureka Server 和 ZooKeeper 之间有什么区别
6. StringCloud 和 SpringBoot, 谈一谈你的理解
8. 微服务的优缺点是什么?你在实际开发中遇到过什么样的问题