SpringCloud
时间:2023-01-22 23:00:00
一、Spring的认知
spring全家桶:spring springmvc springboot springcloud---netflix springcloud-alibaba
解决问题:
1、注册中心:监控服务问题
2.服务间调用问题:http调用方法,Feign RestTemplate组件
3.服务降级和熔断:并发量达到一定阈值
4.路由问题:保护微服务架构
……
什么是微服务?
特点:
1.每项服务都是一个完整的项目
2.每个项目都应该独立部署在自己的容器中
3.项目应能够相互调用(HTTP,RPC)
4.服务是独立的,不依赖关系
5.每个项目都可以使用自己的数据库
6.每个项目只能有一个功能(称为微服务)
二、SpringCloud Eureka建设注册中心
1.认知注册中心
基于微服务的分布式架构将有许多服务(项目),每个服务将建立一个集群
问题:
1.如何监控许多服务是否正常运行?
2.服务应相互调用。服务列表(服务地址、端口)的管理是个问题
SpringCloud的Eureka组件可以解决上述问题
叫注册中心
能够完成注册中心功能的微服务框架如下:
1、SpringCloud-netflix公司 Eureka
2、SpringCloud-alibaba nacos
3、Apache Zookeeper
2.建设注册中心
1.创建聚合工程 (jar war pom)
在父工程下,创建了一个父工程
jar:jar包,供其他项目调用,不能独立运行
war:在服务器上部署项目,独立运行
pom:例如,父子项目,统一管理子项目,springcloud版本可以在父工程中统一设置
springboot和springcloud版本要兼容
pom org.springframework.boot spring-boot-starter-parent 2.2.6.RELEASE org.springframework.cloud spring-cloud-dependencies Hoxton.SR4 pom import
2.创建子项目作为注册中心
new--->module--->war
设置为springboot项目
2.1:添加依赖:springboot的依赖,Eureka客户端的依赖
war org.springframework.cloud spring-cloud-starter-netflix-eureka-server org.springframework.boot spring-boot-starter-web
2.2.创建启动程序
package com.qf.pdaeureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer //当前程序是Eureka注册中心的服务器,每个微服务都可以注册 public class PdaEurekaApplication { public static void main(String[] args) { SpringApplication.run(PdaEurekaApplication.class,args); } }
2.3:在注册中编制配置文件
application.yml
server: port: 8080 eureka: instance: hostname: localhost client: register-with-eureka: false #本注册中心是否向其他注册中心注册 false--不注册 true---注册 fetch-registry: false #注册中心是否相互拉取,同步数据 false:不拉取 serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #注册中心地址,其他微服务必须向注册中心注册
2.4:启动注册中心进行访问测试
三、创建聚合工程pom利用微服务拆分项目
1.新申请微服务
2、添加依赖
war org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-eureka-client /code>
3、创建启动程序
package com.qf.pdaapply;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient //是eureka的客户端,可以向eureka注册中心注册
public class PdaApplyApplication {
public static void main(String[] args) {
SpringApplication.run(PdaApplyApplication.class,args);
}
}
4、编写配置文件
server:
port: 8081
# 指定当前服务向注册中心进行注册的注册地址
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka
# 指定当前服务的服务名,作用有两个:监测使用 服务间通过服务名来调用服务
spring:
application:
name: APPLYSERVER
5、编写controller
package com.qf.pdaeureka.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/apply")
public class ApplyController {
@GetMapping("/test")
public String test(){
return "test successful!";
}
}
6、启动申请微服务
测试服务自身
是否注册到注册中心里
【注意】一定先启动注册中心,后启动被调用的服务
出现如下内容,注册成功!
微服务1:pda-apply APPLYSERVER 接收入港申请填写的数据
微服务2:pda-applydao APPLYDAOSERVER
供微服务1来调用,接收到微服务1传递的入港申请数据后,添加到数据库中
与mybatis进行整合
创建微服务2 pda-applydao:
整合mybatis
1、pom.xml
pad
com.qf
1.0-SNAPSHOT
4.0.0
pda-applydao
war
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
mysql
mysql-connector-java
5.0.5
com.alibaba
druid-spring-boot-starter
1.1.10
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.2
2、application.yml
server:
port: 8082
spring:
application:
name: APPLYDAOSERVER
datasource: #连接池的配置信息
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2105
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
mybatis: #mybatis的配置
configuration:
map-underscore-to-camel-case: true
mapper-locations: classpath:mybatis/*.xml
classpath:com/qf/pdaapplydao/dao/*.xml
type-aliases-package: com.qf.springboot2105pro.pojo
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka
3、启动程序进行测试
注册成功
问题:apply 微服务 applydao微服务 都需要入港申请表的实体类
解决方案:新建pda-common程序,是jar 工程
把各个微服务都需要访问的共通资源,放在pda-common项目下,那个微服务需要调用此jar即可
新建一个项目:不是微服务,不能独立运行
pda-common:实体类、工具类等共通资源
是jar工程
pad
com.qf
1.0-SNAPSHOT
4.0.0
pda-common
jar
org.projectlombok
lombok
1.18.2
三、jar工程实体类的搭建
新建了实体类
package com.qf.pdacommon.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class Apply implements Serializable {
//自增id
private int id;
//申请人
private String applyName;
//申请时间
private String applyTime;
//申请货运公司
private String applyCompany;
//申请公司的工商局的注册码
private String companyISBN;
//公司法人
private String artificialPerson;
//联系人电话
private String phone;
//联系人
private String contactPerson;
//货物类型
private String type;
//单位
private String unit;
//数量
private int num;
//计划入港时间
private String joinTime;
//运输方式
private String transportType;
//入港拍照
private String papers;
//申请状态
private int state;
}
其他微服务,在添加对pda-common的依赖后,就可以复用pda-common里的代码了
com.qf
pda-common
1.0-SNAPSHOT
四、微服务的调用---RestTemplate
1、基于微服务分布式的调用方式
在基于微服务的分布式中,服务调用的方式有两种:
1、基于RPC---Dubbo
远程过程调用
使用的是Remote Procedure Call Protocol 远程过程调用协议
不需要了解底层网络技术,就像访问本地资源一样去访问远程的服务
2、基于HTTP方式调用---SpringCloud
基于HTTP协议的
2、http概述
什么是HTTP
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,是一个基于请求与响应模式的、无状态的、应用层的协议,运行于TCP协议基础之上。
HTTP协议特点
-
支持客户端(浏览器)/服务器模式。
-
简单快速:客户端只向服务器发送请求方法和路径,服务器即可响应数据,因而通信速度很快。请求方法常用的有GET、POST等。
-
灵活:HTTP允许传输任意类型的数据,传输的数据类型由Content-Type标识。
-
无连接:无连接指的是每次TCP连接只处理一个或多个请求,服务器处理完客户的请求后,即断开连接。采用这种方式可以节省传输时间。
-
HTTP1.0版本是一个请求响应之后,直接就断开了。称为短连接。
-
HTTP1.1版本不是响应后直接就断开了,而是等几秒钟,这几秒钟之内有新的请求,那么还是通过之前的连接通道来收发消息,如果过了这几秒钟用户没有发送新的请求,就会断开连接。称为长连接。
-
无状态:HTTP协议是无状态协议。
-
无状态是指协议对于事务处理没有记忆能力。
HTTP协议通信流程
-
客户与服务器建立连接(三次握手)。
-
客户向服务器发送请求。
-
服务器接受请求,并根据请求返回相应的文件作为应答。
-
客户与服务器关闭连接(四次挥手)。
请求报文和响应报文
请求报文
当浏览器向Web服务器发出请求时,它向服务器传递了一个数据块,也就是请求信息(请求报文),HTTP请求信息由4部分组成:
1、请求行 请求方法/地址 URI协议/版本
2、请求头(Request Header)
3、空行
4、请求正文
HTTP响应报文
HTTP响应报文与HTTP请求报文相似,HTTP响应也由4个部分组成:
1、状态行
2、响应头(Response Header)
3、空行
4、响应正文
常见状态码
状态代码
状态描述
说明
200
OK
客户端请求成功
302
Found
临时重定向
403
Forbidden
服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因
404
Not Found
请求的资源不存在,例如,输入了错误的URL。
500
Internal Server Error
服务器发生不可预期的错误,导致无法完成客户端的请求。
3、定义RestTemplate对象
在apply启动类中
package com.qf.pdaapply;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient //是eureka的客户端,可以向eureka注册中心注册
public class PdaApplyApplication {
public static void main(String[] args) {
SpringApplication.run(PdaApplyApplication.class,args);
}
//定义RestTemplate对象,存放到spring 的bean工厂中
@Bean
@LoadBalanced //加载默认的负载均衡算法---有集群才有效,稍后
public RestTemplate restTemplate(){
//RestTemplate是springMVC中用于远程服务调用的、基于HTTP协议的对象
return new RestTemplate();
}
}
在pda-apply微服务的controller中注入该对象
//注入RestTemplate对象
@Autowired
private RestTemplate restTemplate;
在pda-applydao微服务中定义调用的接口-----也就是写controller
package com.qf.pdaapplydao.controller;
import com.qf.pdaapplydao.dao.ApplyDao;
import com.qf.pdaapplydao.service.ApplyDaoService;
import com.qf.pdacommon.pojo.Apply;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/applydao")
public class ApplyDaoController {
@Autowired
private ApplyDaoService applyDaoService;
@PostMapping("/add")
//JSON格式传递数据
public String addApplyDao(@RequestBody Apply apply){
boolean result=applyDaoService.addApply(apply);
if(result){
return "success";
}else{
return "failed";
}
}
}
在pda-apply微服务中调用pda-applydao这个接口
package com.qf.pdaapply.controller;
import com.qf.pdacommon.pojo.Apply;
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("/apply")
public class ApplyController {
//注入RestTemplate对象
@Autowired
private RestTemplate restTemplate;
@GetMapping("/test")
public String test(){
Apply apply=new Apply();
return "test successful!";
}
@GetMapping("/add")
public String addApply(Apply apply){
System.out.println("即将调用pda-applydao微服务,数据如下:"+apply);
//1准备调用的微服务的url地址
//在springcloud中,通过微服务的服务名来调用
//不能通过IP制定调用某台服务,放着发生单点故障
//微服务注册到注册中心后,如果是同一服务名,就自动组成集群
String url="http://APPLYDAOSERVER/applydao/add";
//调用
//postForObject() 以post方式调用 getForObject() 以get方式调用
//抵用此方法后,就会发出HTTP请求
//参数1:请求微服务的url
//参数2:传递的值,此处传递的是对象,restTemplate自动将对象转成JSON字符串格式
// 由于传递的是JSON,所以被调用的微服务要使用@RequestBody,把JSON字符串转为对象
//参数3:以类对象方式传递,调用微服务后返回值的类型
String result=restTemplate.postForObject(url,apply,String.class);
return result;
}
}
启动微服务进行测试
【说明】增删改的服务调用方式都相同
4、使用RestTemplate进行条件查询调用
1、在pda-applydao微服务中编写查询方法
@GetMapping("/find")
public List findByCondition(String applyName,String phone){
System.out.println("参数applyName:"+applyName);
System.out.println("参数phone:"+phone);
//自己构建一个集合
List list=new ArrayList();
Apply apply=new Apply();
apply.setApplyName("张三");
Apply apply1=new Apply();
apply.setApplyName("李四");
list.add(apply);
list.add(apply1);
return list;
}
2、在pda-apply微服务中调用pda-applydao微服务中的条件查询接口
//查询调用
@GetMapping("/find")
public List findApplyByCondition(String applyName,String phone){
//调用的url
String url="http://APPLYDAOSERVER/applydao/find?applyName="+applyName+"&phone="+phone;
//get方式调用
//参数1:调用的微服务的url
//参数2:返回值的类型
//接收到的是JSON格式的字符串,RestTemplate把JSON字符串转成List集合
List list=restTemplate.getForObject(url,List.class);
return list;
}
5、注册中心Eureka的相关配置及工作原理
注册中心的安全配置---给注册中心添加账号、密码
任何一个服务不能随意注册到注册中心里,必须提供账号、密码
实现步骤:
在eureka注册中心的pom.xml文件中添加security的依赖
org.springframework.boot
spring-boot-starter-security
在注册中心微服务里编写测试类
package com.qf.pdaeureka.util;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
//忽略掉/eureka/** 请求eureka的子目录的url的时候无需提供账号、密码
//如果不编写此配置文件,设置的账号、密码将不生效
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
在注册中心的application.yml配置文件中添加账号、密码
# 指定Eureka注册中心的账号、密码
spring:
security:
user:
name: root
password: root
配置好注册中心的账号、密码,重启后,微服务将无法注册到注册中心,报错
配置微服务,在注册的时候,提供账号、密码
在其他微服务的application.yml配置文件中
注册中心的高可用
目前的注册中心是一个服务,存在单点故障的风险
搭建注册中心的集群,保证注册中心的高可用
【如果注册中心的服务宕机后,服务之间的调用是否可以完成】
1、如果一个服务在宕机前调用过另一个服务,本地就缓存了所调用服务的服务列表,注册中心宕机后,依然可以调用;
2、如果在注册中心宕机前没有调用过另一个服务,本地没有缓存服务列表,就无法完成服务的调用
搭建Eureka集群
1、采用项目复制,新建一个注册中心
2、如果复制:删除掉imp target文件
3、如果复制:在父工程中,手动添加module
4、如果复制:在复制的项目中,手动改动项目的id
【强调】复制后,会发生粘连现象,改一个项目后,另一个项目也跟着改动
新建一个注册中心的服务
原注册中心也改为true
【注册中心有台服务,各个微服务注册到注册中心的哪台服务上】
配置各个微服务的注册
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8080/eureka,http://root:root@localhost:8083/eureka
注册中心之间会互相拉取数据
注册中心的原理
Eureka的配置
EurekaClient启动是,将自己的信息注册到EurekaServer上,EurekaSever就会存储上EurekaClient的注册信息。
当EurekaClient调用服务时,本地没有注册信息的缓存时,去EurekaServer中去获取注册信息。
EurekaClient会通过心跳的方式去和EurekaServer进行连接。(默认30sEurekaClient会发送一次心跳请求,如果超过了90s还没有发送心跳信息的话,EurekaServer就认为你宕机了,将当前EurekaClient从注册表中移除) eureka中进行添加
#针对于Eureka
eureka:
instance:
lease-renewal-interval-in-seconds: 30 #心跳的间隔
lease-expiration-duration-in-seconds: 90 # 多久没发送,就认为你宕机了
EurekaClient会每隔30s去EurekaServer中去更新本地的注册表 谁调用就写到谁里边 apply
#针对于client
eureka:
client:
registry-fetch-interval-seconds: 30 # 每隔多久去更新一下本地的注册表缓存信息
Eureka的自我保护机制,统计15分钟内,如果一个服务的心跳发送比例低于85%,EurekaServer就会开启自我保护机制
-
不会从EurekaServer中去移除长时间没有收到心跳的服务。
-
EurekaServer还是可以正常提供服务的。
-
网络比较稳定时,EurekaServer才会开始将自己的信息被其他节点同步过去
eureka中进行添加
eureka:
server:
enable-self-preservation: true # 开启自我保护机制
服务间的负载均衡
为了防止单点故障,保障微服务的高可用,任何微服务都要搭建集群
在SpringCloud中,只要注册到注册中心里的微服务的服务名相同,就自动组成集群
1、组建pda-applydao微服务的集群
项目代码相同,只有启动程序的服务名不同,注册到注册中的服务名相同,自动组建集群
2、有集群后,设计集群负载均衡的问题
在springcloud中,是基于客户端的负载均衡
在pda-apply服务上,配置负载均衡策略
添加ribbon的依赖(能够实现负载均衡)
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
配置负载均衡算法
方式一:注解方式
在启动类xxxApplication中
//配置负载均衡算法
@Bean
public IRule iRule(){
//创建负载均衡算法
return new RoundRobinRule(); //轮询负载均衡算法
}
方式二:基于配置方式
在application.yml文件中
APPLYDAOSERVER:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
【说明】推荐使用配置方式,因为针对不同的微服务,配置不同的负载均衡算法
ribbon所支持的负载均衡算法:
负载均衡策略
-
RandomRule:随机策略
-
RoundRobbinRule:轮询策略
-
WeightedResponseTimeRule:默认会采用轮询的策略,后续会根据服务的响应时间,自动给你分配权重
-
BestAvailableRule:根据被调用方并发数最小的去分配
六、微服务间的Feign调用
配置Feign
Feign:以HTTP方式进行服务的调用
1、添加Feign的依赖----在服务的调用方pda-apply
org.springframework.cloud
spring-cloud-starter-openfeign
2、在启动类中添加注解
3、创建接口
在applydao中编写controller
4、在aply的controller中添加Feign文件夹并编写interface类型的接口
package com.qf.pdaapply.controller.feigns;
import com.qf.pdacommon.pojo.Apply;
import com.qf.pdacommon.pojo.ApplyLogin;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(value = "APPLYDAOSERVER") //声明的是要调用的微服务的名字
public interface ApplyFeign {
//每一个调用请求,对应着一个方法
//value:请求对应服务的路径
//method:请求方式
//【注意】如果有参数,必须使用@RequestParam 注解来声明参数,否则认为无参
//【注意】如果有参数,必须使用@RequestParam 注解来生命参数,否则认为无擦
@RequestMapping(value = "/applydao/findcondation",method = RequestMethod.GET)
public List findcondation(@RequestParam String applyName, @RequestParam int state, @RequestParam int page, @RequestParam int limit);
@RequestMapping(value = "/applydao/findApplyPage", method = RequestMethod.GET)
public int findApplyPage(@RequestParam String applyName, @RequestParam int state);
//删除
@RequestMapping(value = "/applydao/deleteapply/{applyId}", method = RequestMethod.GET)
public String deleteApply(@PathVariable("applyId") int applyId);
}
5、在controller中用Feign进行调用
package com.qf.pdaapply.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey;
import com.qf.pdaapply.controller.feigns.ApplyFeign;
import com.qf.pdaapply.service.ApplyService;
import com.qf.pdacommon.pojo.Apply;
import com.qf.pdacommon.pojo.ApplyLogin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@CrossOrigin("*")
@RequestMapping("/apply")
public class ApplyController {
//注入RestTemplate对象
@Autowired
private RestTemplate restTemplate;
//Feign
@Resource
private ApplyFeign applyFeign;
@Autowired
private ApplyService applyService;
//添加进pda-applydao中
@PostMapping("/addapply")
public String addApply(Apply apply){
//1、准备调用的微服务的url地址
//在springcloud中,通过微服务的服务名来调用
//不能通过IP指定调用某台服务器,容易发生单点故障
//微服务注册到注册中心后,如果是同一服务名,就会自动组成集群
String url = "http://APPLYDAOSERVER/applydao/addapply";
//调用
//postForObject() dao的controller中用什么方式请求就用什么方式调用 restful中 post添加 get查询 put修改 delete删除
//抵用此方法后,就会发出http请求
//参数1:请求微服务的url
//参数2:传递的值,此处传递的是对象,restTemplate自动将对象转成JSON字符转
// 由于传递的是JSON,所以被调用的微服务要使用@RequestBody,把JSON字符串转成对象
//参数3:以类对象的方式进行传递,调用微服务后返回值的类型 看的是dao的controller中返回值类型是什么
String result = restTemplate.postForObject(url,apply,String.class);
return result;
}
//查询
@GetMapping("/findapply")
public Map findApply(){
String url = "http://APPLYDAOSERVER/applydao/findapply";
Map map = new HashMap();
List list = restTemplate.getForObject(url, List.class);
map.put("code","0");
map.put("msg","");
map.put("count",list.size());
map.put("data",list);
return map;
}
//分页模糊查询
@GetMapping("/findcondation")
//hystrix 降级服务
// @HystrixCommand(fallbackMethod = "findApplyFallBack")
//线程隔离-----默认的方式
// @HystrixCommand(fallbackMethod = "findApplyFallBack2", commandProperties = {
// @HystrixProperty(name="execution.isolation.strategy", value = "THREAD"),
// @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
// @HystrixProperty(name="execution.timeout.enabled", value = "true")
// })
// //线程隔离-----信号流量
// @HystrixCommand(fallbackMethod = "findApplyFallBack3", commandProperties = {
// @HystrixProperty(name="execution.isolation.strategy", value = "SEMAPHORE"),
// @HystrixProperty(name=
// "execution.isolation.semaphore.maxConcurrentRequests", value = "100")
// })
public Map findcondation(String applyName,int state,int page,int limit){
String url1 = "http://APPLYDAOSERVER/applydao/findApplyPage?applyName="+applyName+
"&state="+state;
String url2 = "http://APPLYDAOSERVER/applydao/findcondation?applyName="+applyName+
"&state="+state+"&page="+page+"&limit="+limit;
Map map = new HashMap();
int count = restTemplate.getForObject(url1,int.class);
List list = restTemplate.getForObject(url2,List.class);
map.put("code","0");
map.put("msg","");
map.put("count",count);
map.put("data",list);
return map;
}
//feign的分页模糊查询
@GetMapping("/findcondation2")
//熔断、断路器
@HystrixCommand(commandProperties = {
//启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
//10秒内有10个请求失败,则断路器由关闭状态到打开状态
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
//请求失败的百分比
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "70"),
//断路器由打开状态到半开状态的时间间隔
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000")
})
public Map findcondation2(String applyName, int state, int page, int limit){
Map map = new HashMap();
int count = applyFeign.findApplyPage(applyName,state);
List list = applyFeign.findcondation(applyName, state, page, limit);
map.put("code","0");
map.put("msg","");
map.put("count",count);
map.put("data",list);
System.out.println("调用的是fegin");
return map;
}
//feign的分页模糊查询
@GetMapping("/findcondation3")
public Map findcondation3(String applyName, int state, int page, int limit){
Map map = new HashMap();
int count = applyFeign.findApplyPage(applyName,state);
List list = applyFeign.findcondation(applyName, state, page, limit);
map.put("code","0");
map.put("msg","");
map.put("count",count);
map.put("data",list);
System.out.println("调用的是fegin");
return map;
}
//删除
@GetMapping("/deleteapply")
public String deleteApply(int applyId){
return applyFeign.deleteApply(applyId);
}
//服务降级后调用的方法
public Map findApplyFallBack(String applyName, int state, int page, int limit){
Map map = new HashMap();
Apply apply = new Apply();
apply.setApplyName("hystrix服务降级的申请示例者数据");
map.put("hystrix",apply);
return map ;
}
//线程隔离-----线程池默认的方式后调用的方法
public Map findApplyFallBack2(String applyName, int state, int page, int limit){
Map map = new HashMap();
Apply apply = new Apply();
apply.setApplyName("hystrix线程隔离---线程池默认的方式 的申请示例者数据");
map.put("hystrix",apply);
return map ;
}
//线程隔离-----信号流量的方式调用的方法
public Map findApplyFallBack3(String applyName, int state, int page, int limit){
Map map = new HashMap();
Apply apply = new Apply();
apply.setApplyName("hystrix线程隔离---信号流量的方式 的申请示例者数据");
map.put("hystrix",apply);
return map ;
}
//请求缓存
@GetMapping("/findcondation4")
public List findcondation4(String applyName, int state, int page, int limit){
List list = applyService.findcondation4(applyName, state, page, limit);
List list1 = applyService.findcondation4(applyName, state, page, limit);
return list;
}
//测试清楚缓存
//请求缓存
@GetMapping("/clearcondation4")
public List clearcondation4(String applyName, int state, int page, int limit){
List list = applyService.findcondation4(applyName, state, page, limit);
applyService.clearcondation4(applyName, state, page, limit);
List list1 = applyService.findcondation4(applyName, state, page, limit);
return list;
}
}
6、启动测试
Feign的微服务调用流程
1、Feign属于HTTP调用
2、HTTP协议的介绍(简介)
3、Feign属于基于接口的调用
4、@EnableFeignClients //才可以使用feign组件进行服务调用 在启动类
5、接口的相关属性介绍
@FeignClient("APPLYDAOSERVER") //声明的是要调用的微服务的名字 接口中
接口中的方法及@RequestMapping注解
6、Feign组件创建HTTP请求对象,设置:协议,请求路径,传递参数
Feign进行微服务调用的传参方式
1、当传递的参数只有一个,或比较少时,建议使用地址传参
被调用的服务中的方法声明 pda-applydao
@GetMapping("/del/{id}")
public String deleteApply(@PathVariable("id") int id){
System.out.println("接收到要删除的数据主键id="+id);
//调用service
boolean result=true;
if(result){
return "success";
}else{
return "failed";
}
}
在Feign文件夹下Feign的接口中
//调用删除请求
@RequestMapping(value="/applydao/del/{id}",method = RequestMethod.GET)
public String deleteApply(@PathVariable("id")int id);
在apply 的 controller中调用
@GetMapping("/del")
public String deleteApply(int id){
return applyFeign.deleteApply(id);
}
2、如果参数较多时,建议使用(@RequestParam)
被调用的服务中的方法声明 pda-applydao
//分页模糊查询
@GetMapping("/findcondation")
public List findcondation(String applyName,int state,int page,int limit){
System.out.println("开始调用1号服务器");
System.out.println(applyName + state + page + limit);
List list = applyDaoService.findApplyCondition(applyName, state, page, limit);
return list;
}
在Feign文件夹下Feign的接口中
@RequestMapping(value = "/applydao/findcondation",method = RequestMethod.GET)
public List findcondation(@RequestParam String applyName, @RequestParam int state, @RequestParam int page, @RequestParam int limit);
在apply 的 controller中调用
@GetMapping("/findcondation")
public Map findcondation2(String applyName, int state, int page, int limit){
Map map = new HashMap();
int count = applyFeign.findApplyPage(applyName,state);
List list = applyFeign.findcondation(applyName, state, page, limit);
map.put("code","0");
map.put("msg","");
map.put("count",count);
map.put("data",list);
System.out.println("调用的是fegin");
return map;
}
3、如果传递对象,需要使用@RequestBody注解
被调用的服务中的方法声明 pda-applydao
正常编写
在Feign文件夹下Feign的接口中
@RequestMapping(value="/applydao/add", method = RequestMethod.POST)
public String add(@RequestBody Apply apply);
在apply 的 controller中调用
正常编写
【说明】在Feign的接口中网络请求必须使用@RequestMapping注解
Feign的服务降级FallBack
当被调用的服务,比较忙、发生异常等,导致无法正常提供,那么此时当前服务应该返回给用户“托底数据”
此种情况称为服务降级
【注意】服务降级是已经调用了服务(请求已经到达了被调用的服务端),只是服务没有正常返回结果而已
使用Feign组件实现服务降级
降级方式一:
1、创建applyFeign接口的实现类继承ApplyFeign,并在实现类的实现方法中,编写托底数据
package com.qf.pdaapply.feigns;
import com.qf.pdacommon.pojo.Apply;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class ApplyFeignImpl implements ApplyFeign {
@Override
public List findApply(String applyName, String phone) {
//调用ApplyFeign接口的findApply 降级时
Apply apply=new Apply();
apply.setApplyName("申请者示例数据");
List list=new ArrayList();
list.add(apply);
return list;
}
@Override
public String add(Apply apply) {
return "无法正常提供服务,请稍后重试";
}
@Override
public String deleteApply(int id) {
return "无法正常提供服务,请稍后重试";
}
}
2、在接口的注解中改为
@FeignClient(value = "APPLYDAOSERVER",fallback = ApplyFeignImpl.class)
3、开启服务降级,在pda-apply的配置文件application.yml中
#开启服务降级
feign:
hystrix:
enabled: true
降级方式二:
托底数据:返回给用户的
能够获取到被调用服务的异常信息
在方式一的基础上进行的(依然需要托底数据的实现类)
1、新建类去实现FallBackFactory
package com.qf.pdaapply.controller.feigns;
import feign.hystrix.FallbackFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ApplyFeignFactory implements FallbackFactory {
//注入托底数据实现对象
@Autowired
private ApplyFeignImpl applyFeignImpl;
@Override
public ApplyFeign create(Throwable throwable) {
System.out.println("被调用的服务发生了异常,异常信息如下:"+throwable.getMessage());
return applyFeignImpl;
}
}
2、修改ApplyFeign的配置
7、Hystrix的使用
解决服务雪崩的四种方式
服务降级
1、在服务的调用者处添加依赖
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2、开启Hystrix服务,在启动类中开启
@EnableCircuitBreaker //开启hystrix服务
3、在controller中实现降级---在controller中编写降级时调用的方法
//查询调用
@GetMapping("/find")
@HystrixCommand(fallbackMethod = "findApplyFallBack")
public List findApplyByCondition(String applyName,String phone){
//调用的url
String url="http://APPLYDAOSERVER/applydao/find?applyName="+applyName+"&phone="+phone;
//get方式调用
//参数1:调用的微服务的url
//参数2:返回值的类型
//接收到的是JSON格式的字符串,RestTemplate把JSON字符串转成List集合
List list=restTemplate.getForObject(url,List.class);
return list;
}
//服务降级后调用的方法
public List findApplyFallBack(String applyName,String phone){
Apply apply=new Apply();
apply.setApplyName("申请者示例数据---zs");
List list=new ArrayList();
list.add(apply);
return list;
}
线程隔离
让一个服务的部分线程去请求另一个服务
实现线程隔离有两种方案:
1、Hystrix线程池:默认的方式 ----- findApplyFallBack方法和以前一样不用改变
在Tomcat中把请求服务的线程,从Tomcat转移到Hystrix线程池里的线程中,耽搁的是Hystrix线程池里的线程,tomcat里的线程照样可以调用别的线程
缺点:效率相对较低,需要请求的转移
优点:充分利用tomcat线程
配置信息
name
value
线程隔离策略
execution.isolation.strategy
THREAD
指定超时时间
execution.isolation.thread.timeoutInMilliseconds
1000
是否开启超时时间配置
execution.timeout.enabled
true
超时之后是否中断线程
execution.isolation.thread.interruptOnTimeout
true
取消任务后知否
execution.isolation.thread.interruptOnCancel
false
//查询调用
@GetMapping("/find")
@HystrixCommand(fallbackMethod = "findApplyFallBack",commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy",value = "THREAD"),
@HystrixProperty(name = "execution.timeout.enabled",value ="true"),
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value ="1000" )
})
public List findApplyByCondition(String applyName,String phone){
//调用的url
String url="http://APPLYDAOSERVER/applydao/find?applyName="+applyName+"&phone="+phone;
//get方式调用
//参数1:调用的微服务的url
//参数2:返回值的类型
//接收到的是JSON格式的字符串,RestTemplate把JSON字符串转成List集合
List list=restTemplate.getForObject(url,List.class);
return list;
}
2、信号流量 --- feign下调用的是feign
限制Tomcat中线程去请求其他服务的线程的数量
配置信息
name
value
线程隔离策略
execution.isolation.strategy
SEMAPHORE
指定信号量的最大并发请求数
execution.isolation.semaphore.maxConcurrentRequests
10
@GetMapping("/find2")
@HystrixCommand(commandProperties ={
@HystrixProperty(name = "execution.isolation.strategy",value ="SEMAPHORE" ),
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "100")
})
public List findApplyByCondition2(String applyName,String phone){
return applyFeign.findApply(applyName,phone);
}
优点:比Hystrix效率要高,没有做请求的转移
缺点:没有充分利用Tomcat中的线程
3、熔断、断路器
【服务降级、熔断的区别】
断路器的工作原理:
1、当A服务调用B服务时,如果在一段时间内,对B服务的请求失败达到一定的数量,或一定的百分比,断路器打开
2、当断路器打开后,A服务对B服务发出的请求,直接返回
3、当过指定的设置时间后,断路器由打开状态转换到半开状态,放行少量的请求,如果放行的请求,请求成功了,那么断路器关闭,所有请求将到达B服务
4、如果放行的少量请求,依然请求失败,则断路器由半开状态,转为打开状态
5、循环转换
断路器的属性(默认10s秒中之内请求数)
配置信息
name
value
断路器的开关
circuitBreaker.enabled
true
失败阈值的总请求数
circuitBreaker.requestVolumeThreshold
20
请求总数失败率达到%多少时
circuitBreaker.errorThresholdPercentage
50
断路器open状态后,多少秒是拒绝请求的
circuitBreaker.sleepWindowInMilliseconds
5000
强制让服务拒绝请求
circuitBreaker.forceOpen
false
强制让服务接收请求
circuitBreaker.forceClosed
false
//feign的分页模糊查询
@GetMapping("/findcondation2")
//熔断、断路器
@HystrixCommand(commandProperties = {
//启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
//10秒内有10个请求失败,则断路器由关闭状态到打开状态
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
//请求失败的百分比
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "70"),
//断路器由打开状态到半开状态的时间间隔
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000")
})
public Map findcondation2(String applyName, int state, int page, int limit){
Map map = new HashMap();
int count = applyFeign.findApplyPage(applyName,state);
List list = applyFeign.findcondation(applyName, state, page, limit);
map.put("code","0");
map.put("msg","");
map.put("count",count);
map.put("data",list);
System.out.println("调用的是fegin");
return map;
}
4、请求缓存
1、在业务逻辑层---pda-apply---service
package com.qf.pdaapply.service.impl;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;
import com.qf.pdaapply.feigns.ApplyFeign;
import com.qf.pdaapply.service.ApplyService;
import com.qf.pdacommon.pojo.Apply;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ApplyServiceImpl implements ApplyService {
@Autowired
private ApplyFeign applyFeign;
@Override
@CacheResult //缓存此方法的查询结果
@HystrixCommand(commandKey = "find3") //使用find3方法里的参数值作为key
public List find3(@CacheKey String applyName, @CacheKey String phone) {
return applyFeign.findApply(applyName,phone);
}
}
2、创建缓存过滤器---初始化请求缓存对象---pdaapply---filters
package com.qf.pdaapply.filters;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*")
public class HystrixFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//初始化请求缓存
HystrixRequestContext.initializeContext();
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
【注意】SpringBoot默认不扫描、加载Servlet以及Filter
需要通过注解的配置,让SpringBoot去扫描加载,在启动类中配置如下:---启动类
@ServletComponentScan(basePackages = "com.qf.pdaapply.filters") //通过配置后,扫描过滤器
3、修改controller,在一次请求内,多次请求pda-applydao微服务 -----pdaapply-controller
@GetMapping("/find3")
public List find3(String applyName,String phone){
List list= applyService.find3(applyName,phone);
List list1=applyService.find3(applyName,phone);
return list;
}
4、测试成功
清理缓存
//清除缓存
@Override
@CacheRemove(commandKey = "findcondation4") //清除findcondation4方法里的参数的key
@HystrixCommand
public void clearcondation4(@CacheKey String applyName,@CacheKey int state,@CacheKey int page,@CacheKey int limit) {
System.out.println("findcondation4方法的请求缓存被清空!");
}
@GetMapping("/find3")
public List find3(String applyName,String phone){
List list= applyService.find3(applyName,phone);
//清理缓存
applyService.clearFind3Cache(applyName,phone);
List list1=applyService.find3(applyName,phone);
return list;
}
8、配置管理Config
微服务个数很多,每个微服务中都有配置文件,对配置文件的维护非常麻烦
Config组件,通过以服务的方式,对其他微服务的配置文件进行统一的管理,
原理:
1、把各个微服务的配置文件移植到Config服务的本地,也可以一直到远程服务器
2、各个微服务在服务启动时,到Config微服务中去获取配置文件
1、新建Config微服务
新建 module
2、添加依赖
war
org.springframework.cloud
spring-cloud-config-server
3、启动程序
package com.qf.pdaconfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class PdaConfigApplication {
public static void main(String[] args) {
SpringApplication.run(PdaConfigApplication.class,args);
}
}
4、pda-config微服务自身的配置文件
两种配置方式
存储在远程仓库
spring:
cloud:
config:
server:
git:
basedir: D:\basedir # 本地仓库的地址
username: zjw_2301211@126.com # 远程仓库用户名
password: z123123 # 远程仓库密码
uri: https://gitee.com/zhengdaxian/config-resp.git # 远程仓库地址
直接存储在本地
server:
port: 8086
spring:
profiles:
active: native
cloud:
config:
server:
native:
search-locations: classpath:configfile
5、把其他微服务的配置文件一直到pda-config微服务中
在configfile文件夹下,新建配置文件pdaapply-local.yml
把pda-apply微服务的application.yml里的内容,复制到pdaapply-local.yml里
6、在pda-apply微服务中
6.1:删除原先微服务里的配置文件
6.2:新建配置文件bootstrap.yml,引用配置服务的配置文件
# 配置---到配置中心中拉取配置文件
spring:
cloud:
config:
uri: http://localhost:8086
name: pdaapply
profile: local
6.3:在当前pda-apply微服务中,添加config-client的依赖
org.springframework.cloud
spring-cloud-config-client
测试即可
一定先启动:注册中心、配置中心
【注意】在配置中心的微服务里,不要引入多于的jar
config服务的jar里包含了springboot的jar
九、路由配置gateway
【路由/网关服务器,是否可以被Nginx服务所替代】
1、使用Nginx暴露了服务的信息
2、微服务太多,Nginx集群维护较为繁琐
1、新建网关微服务
创建 module
2、添加依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
3、启动类