单节点eureka
1、搭建springboot项目
引入Eureka-server
2、编写application.yml
spring:
application:
name: cloud-eureka-server7001
server:
port: 7001
eureka:
instance:
namespace: eurekaserver7001
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port} #以ip+port发方式显示服务名
client:
fetch-registry: false #单机版Eureka,不需要去其它EurekaServer获取注册信息
register-with-eureka: false #当前EurekaServer不需要注册到其它EurekaServer
#我作为Eureka服务器,其它服务如果需要注册到Eureka服务端,注册地址在这里指定。
service-url:
defaultZone: http://eurekaserver7001:7001/eureka/
#defaultZone: http://eurekaserver7002:7002/eureka/,http://eurekaserver7003:7003/eureka/
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 2000
#设置eureka和服务通信时间为2秒(默认90s),超时删除服务
3、服务提供者
引入eureka-client、web依赖
编写yml文件
server:
port: 80
spring:
application:
name: cloud-eureka-consumer80
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka/
在主启动类上加:@EnableEurekaClient
4、服务消费者
引入eureka-client、web依赖
编写yml文件
server:
port: 8001
spring:
application:
name: cloud-eureka-provider8001
datasource:
username: root
password: root
url: jdbc:mysql://localhost:8080/test?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eurekaserver7001:7001/eureka/
#集群搭建
#defaultZone: http://eurekaserver7001:7001/eureka/, http://eurekaserver7002:7002/eureka/, http://eurekaserver7003:7003/eureka/
instance:
#eureka客户端向服务端发送心跳的时间默认30s
lease-expiration-duration-in-seconds: 1
#eureka服务在收到最后一次心跳后等待时间上线,默认90秒,超时删除服务
lease-renewal-interval-in-seconds: 2
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
在主启动类上加:@EnableEurekaClient
搭建eureka集群
1、在 C:\Windows\System32\drivers\etc\hosts 文件中添加如下内容:
创建映射关系
127.0.0.1 eurekaserver7001
127.0.0.1 eurekaserver7002
127.0.0.1 eurekaserver7003
2、复制单节点,修改artifactid,修改端口号
各个模块的artifactId: 各个模块的启动端口号:集群中各个节点占用自己的端口号,否则启动不起来 各个模块spring.application.name: 各节点的应用名称保持相同,对外提供的服务相同
eureka.instance.hostname: eurekaserver700x
eureka.client.fetch-registry: true
eureka.client.register-with-eureka: true
eureka.client.service-url.defaultZone: 其它两个eureka主机名关联地址(设置注册中心地址,指向另外两个注册中心地址)
spring:
application:
name: cloud-eureka-server7001
server:
port: 7001
eureka:
instance:
namespace: eurekaserver7001
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
fetch-registry: false #单机版Eureka,不需要去其它EurekaServer获取注册信息
register-with-eureka: false #当前EurekaServer不需要注册到其它EurekaServer
#我作为Eureka服务器,其它服务如果需要注册到Eureka服务端,注册地址在这里指定。
service-url:
defaultZone: http://eurekaserver7002:7002/eureka/,http://eurekaserver7003:7003/eureka/
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 2000
#设置eureka和服务通信时间为2秒(默认90s),超时删除服务
修改eureka客户端的yml文件,向eureka集群注册服务,而不是单个注册
defaultZone: http://eurekaserver7001:7001/eureka/, http://eurekaserver7002:7002/eureka/, http://eurekaserver7003:7003/eureka/
关闭eureka的自我保护机制
eureka默认客户端连接到服务端server并保持心跳连接,默认30秒连接一次,eureka服务端在最后一次收到心跳后等待时间上限,默认9秒,90秒之后如果服务不可用删除服务。
客户端的配置
#eureka客户端向服务端发送心跳的时间默认30s
eureka.instance.lease-expiration-duration-in-seconds: 1
#eureka服务在收到最后一次心跳后等待时间上线,默认90秒,超时删除服务
eureka.instance.lease-renewal-interval-in-seconds: 2
服务端配置
eureka.server.enable-self-preservation: false #关闭自我保护机制
eureka.server.eviction-interval-timer-in-ms: 2000
#设置eureka和服务通信时间为2秒(默认90s),超时删除服务
修改微服务在客户端显示
# 更倾向使用ip地址,而不是host名
prefer-ip-address: true
#设置成当前客户端ip
instance-id: ${spring.cloud.client.ip-address}:${server.port}
提示: 在微服务中,pojo类最好都实现序列化接口,方便远程传输。
springcloud之负载均衡Ribbon
ribbon介绍
Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具。
简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法
和服务调用。
Ribbon 客户端组件提供一系列完善的配置项,如:连接超时,重试等。
简单的说,就是在配置文件中列出 Load Balancer(简称 LB)后面所有的机器,Ribbon 会自
动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。
我们很容易使用 Ribbon 实现自定义的负载均衡算法。
总之: Ribbon=客户端负载均衡+RestTemplate 远程调用(用户服务调用电影服务)
如何使用 Ribbon
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
1)、引入 Ribbon 的 Starter(其实 eureka-client 依赖中已经包含了该 ribbon 的依赖信息)
2)、配置使用 Ribbon 功能;底层使用 RestTemplate 的工具来给远程发送请求
Ribbon 在工作时分成两步:
第一步,先选择 EurekaServer,它优先选择在同一个区域内负载较少的 server。
第二步,再根据用户指定的策略,在从 server 取到的服务注册列表中选择一个地址。其
中 Ribbon 提供了多种策略。比如:轮询、随机和根据响应时间加权。
总结:Ribbon 其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结
合使用,和 eureka 结合只是其中的一个实例。
ribbon和nginx对比
1,nginx 是服务器端的负载均衡器,所有请求发送到 nginx 之后,nginx 通过反向代理
的功能分发到不同的服务器(比如 tomcat),做负载均衡
2,ribbon 是客户端的负载均衡器,他是通过将 eureka 注册中心上的服务,读取下来,
缓存在本地,本地通过轮询算法,实现客户端的负载均衡(比如 dubbo、springcloud)
ribbon具体使用
eureka-client依赖包含ribbon
<!-- 引入ribbon实现远程调用和负载均衡功能 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
RestTemplate
在配置类中,加了@Configuration或@SpringBootConfiguration的类中注册RestTemplate的bean对象
@SpringBootConfiguration
public class RestTemplateConfig {
@LoadBalanced//开启客户端负载均衡
@Bean
public RestTemplate getTestTemplate(){
return new RestTemplate();
}
}
在服务消费者调用Resttempalte进行远程调用。
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private RestTemplate restTemplate;
public User getById(Integer id) {
return userDao.getById(id);
}
public Map<String, Object> getAll(Integer userId) {
HashMap<String, Object> map = new HashMap<>();
User byId = getById(userId);
map.put("user",byId);
Movie forObject = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER8001/movie/list", Movie.class);
map.put("move",forObject);
return map;
}
}
RestTemplate远程调用方法传参
使用get请求携带参数
使用map传参或者在路径后面直接拼接
@Autowired
private RestTemplate restTemplate;
// @HystrixCommand(fallbackMethod = "getAllFallback")
public Map<String, Object> getAll(Integer userId) {
HashMap<String, Object> map = new HashMap<>();
User byId = getById(userId);
map.put("user",byId);
//使用map传递参数
//Movie forObject = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER8001/movie/list?id="+id, Movie.class,map1);
HashMap<String, String> map1 = new HashMap<>();
map1.put("id",userId.toString());
Movie forObject = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER8001/movie/list?id={id}", Movie.class,map1);
map.put("move",forObject);
return map;
}
@org.springframework.web.bind.annotation.RestController
@RequestMapping()
public class RestController {
@Autowired
private RestTemplate restTemplate;
/**
* 使用getForObject url直接携带参数访问 返回的结果是String
* @return
*/
@GetMapping("/get1")
public String getTest1(@RequestParam String id){
String resop = restTemplate.getForObject("http://localhost:6666/gettest/?id=1", String.class);
return resop+"远程调用get1";
}
/**
* 使用getForObject url的参数使用map方式 需要使用这种格式id={id} 返回的结果是String
* @param id
* @return
*/
@GetMapping("/get2")
public String getTest2(@RequestParam String id){
Map<String,String> map = new HashMap();
map.put("id","1");
String resop = restTemplate.getForObject("http://localhost:6666/gettest/?id={id}", String.class,map);
return resop+"远程调用get2";
}
/**
* 使用getForEntity url直接携带参数访问 返回的结果是ResponseEntity类型
* response.getBody()得到 get返回的data
* @param id
* @return
*/
@GetMapping("/get3")
public String getTest3(@RequestParam String id){
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:6666/gettest/?id=1",String.class);
String body = response.getBody();
return body+"远程调用get3";
}
/**
* 使用getForEntity url直接参数使用map 返回的结果是ResponseEntity类型
* response.getBody()得到 get返回的data
* @param id
* @return
*/
@GetMapping("/get4")
public String getTest4(@RequestParam String id){
Map<String,String> map = new HashMap();
map.put("id","1");
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:6666/gettest/?id={id}",String.class,map);
String body = response.getBody();
return body+"远程调用get4";
}
/**
* 使用exchange 请求头部body携带json数据
* * 当使用get请求需要携带 body中的参数的时候 需要重写Http 版本号必须是4.3以上
* 定义HttpGetRequestWithEntity实现HttpEntityEnclosingRequestBase抽象类,以支持GET请求携带body数据
* @param id
* @return
*/
@GetMapping("/get5")
public String getTest5(@RequestParam String id){
/**
* 1)url: 请求地址;
* 2)method: 请求类型(如:POST,PUT,DELETE,GET);
* 3)requestEntity: 请求实体,封装请求头,请求内容
* 4)responseType: 响应类型,根据服务接口的返回类型决定
* 5)uriVariables: url中参数变量值
*/
String jsonData = "{\"test\":\"1234\",\"score\":\"1\"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> httpEntity = new HttpEntity<>(jsonData, headers);
ResponseEntity<String> responseEntity = restTemplate.exchange("http://localhost:6666/getbody/?id=1", HttpMethod.GET, httpEntity, String.class);
return responseEntity.getBody()+"远程调用get5"+responseEntity.getStatusCode();
}
/**
* url参数 使用map 方式
* @param id
* @return
*/
@GetMapping("/get6")
public String getTest6(@RequestParam String id){
//封装url参数
Map<String,String> map = new HashMap();
map.put("id","1");
//设置头部body携带json数据
String jsonData = "{\"test\":\"6666\",\"score\":\"88888\"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> httpEntity = new HttpEntity<>(jsonData, headers);
ResponseEntity<String> responseEntity = restTemplate.exchange("http://localhost:6666/getbody/?id={id}", HttpMethod.GET, httpEntity, String.class,map);
return responseEntity.getBody()+"远程调用get6"+responseEntity.getStatusCode();
}
/**
* 使用本接口访问 直接携带json数据 封装成远程调用的参数 去访问
*
* 当使用get请求需要携带 body中的参数的时候 需要重写Http 版本号必须是4.3以上
* 定义HttpGetRequestWithEntity实现HttpEntityEnclosingRequestBase抽象类,以支持GET请求携带body数据
*
*/
@GetMapping("/getbody")
public String getBody(@RequestParam String id, @RequestBody JSONObject json){
//封装url参数
Map<String,String> map = new HashMap();
map.put("id",id);
//设置头部body携带json数据
String jsonData = json.toJSONString();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> httpEntity = new HttpEntity<>(jsonData, headers);
ResponseEntity<String> responseEntity = restTemplate.exchange("http://localhost:6666/getbody/?id={id}", HttpMethod.GET, httpEntity, String.class,map);
return responseEntity.getBody()+"远程调用get7"+responseEntity.getStatusCode();
}
/**
* postForObject 直接带参数
* @param id
* @return
*/
@PostMapping("/post1")
public String postTest1(@RequestParam String id){
String s = restTemplate.postForObject("http://localhost:6666/posttest/?id=1", null, String.class);
return id+"测试post"+s;
}
/**
* postForObject 使用map 封装 参数
* @param id
* @return
*/
@PostMapping("/post2")
public String postTest2(@RequestParam String id){
//封装url参数
Map<String,String> map = new HashMap();
map.put("id",id);
String s = restTemplate.postForObject("http://localhost:6666/posttest/?id={id}", null, String.class, map);
return id+"测试post"+s;
}
/**
* postForObject 头部携带Json 数据
* @param id
* @param json
* @return
*/
@PostMapping("/postbody")
public String postBody(@RequestParam String id, @RequestBody JSONObject json){
//封装url参数
Map<String,String> map = new HashMap();
map.put("id",id);
//设置头部body携带json数据
String jsonData = json.toJSONString();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> httpEntity = new HttpEntity<>(jsonData, headers);
String s = restTemplate.postForObject("http://localhost:6666/postbody/?id={id}", httpEntity, String.class, map);
return id+"测试post接收json数据"+s;
}
/**
* post 使用通用的 exchange 访问
* 返回的结果是ResponseEntity 从中拿数据getBody
*/
@PostMapping("/postbody2")
public String postBody2(@RequestParam String id, @RequestBody JSONObject json){
//封装url参数
Map<String,String> map = new HashMap();
map.put("id",id);
//设置头部body携带json数据
String jsonData = json.toJSONString();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> httpEntity = new HttpEntity<>(jsonData, headers);
ResponseEntity<String> exchange = restTemplate.exchange("http://localhost:6666/postbody/?id={id}", HttpMethod.POST, httpEntity, String.class, map);
return "测试post接收json数据"+exchange.getBody()+":"+exchange.getStatusCode();
}
使用post请求将方法名换为postXXX即可
ribbon负载均衡
默认使用轮询的策略
修改负载均衡
@Bean
public IRule getIrule(){
return new RandomRule();//随机策略
// return new WeightedResponseTimeRule();//权重策略
}
ribbon的核心组件Irule
public interface IRule {
Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
springcloud之声明式调用Feign
1、为什么使用feign
在前面的学习中,使用了 Ribbon 的负载均衡功能,大大简化了远程调用时的代码:
Movie movie = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER8001/movie/latest", Movie.class);
result.put("movie", movie);//暂时为null
如果就学到这里,你可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一
样。有没有更优雅的方式,来对这些代码再次优化呢?这就是接下来要学的 Feign 的功能
了。
Feign 也叫伪装,Feign 可以把 Rest 的请求进行隐藏,伪装成类似 SpringMVC 的
Controller 一样。你不用再自己拼接 url,拼接参数等等操作,一切都交给 Feign 去做。
Feign 项目主页:https://github.com/OpenFeign/feign
2、feign的声明式调用
Feign 就是 Ribbon+RestTemplate,采用 RESTful 接口的方式实现远程调用,取代了 Ribbon 中通过 RestTemplate 的方式进行 远程通信。
SpringCloud 对 Feign 进 行 了 封 装 , 使 其 支 持 SpringMVC 标 准 注 解 和 HttpMessageConverters。Feign 可与 Eureka 和 Ribbon 组合使用以支持负载均衡。 Openfeign 是在Feign 的基础上支持了 SpringMVC 注解(例如@RequestMapping 等), 通过动态代理的方式实现负载均衡调用其他服务,说白了,OpenFign 是 SpringCloud 对 netflix 公司的 Feign 组件的进一步封装,封装之后 OpenFeign 就成了 SpringCloud 家族的一个组件了。
3、具体使用
导入web,eureka-client,OpenFeign依赖
编写yml文件
server:
port: 80
spring:
application:
name: cloud-consumer-feign-consumer80
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eurekaserver7001:7001/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
#心跳发送频率
lease-renewal-interval-in-seconds: 1
#服务剔除时间
lease-expiration-duration-in-seconds: 2
4、远程调用注意点
被调用的接口
@GetMapping("/latest/{id}")
public Movie getNewMovie(@PathVariable("id") Integer id){
System.out.println(id);
System.out.println(port);
return movieService.getNewMovie();
}
当被调用接口为GetMapping时传递参数必须使用@RequestParm注解,@RequestMapping注解传参可以不设置该注解
@GetMapping("/movie/list")
public Movie getById(@RequestParam("id") Integer id);
5、feign超时设置
feign默认超时时间为1秒但是,服务端处理需要超过 1 秒钟,导致 Feign 客户 端不想等待了,直接报错。
为了避免这样的情况,有时候我们需要设置 Feign 客户端的超时控制,也即 Ribbon 的超 时时间,因为 Feign 集成了 Ribbon 进行负载均衡。,
ribbon:
ConnectTimeout: 5000
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
6、查看 OpenFeign 的远程调用日志
配置Feign的Legger.Level
@Bean
public Logger.Level getLevel(){
return Logger.Level.FULL;
}
必须设置feign接口的日志级别,必须为debug级别
logging:
level:
com.qwpra.cloud.client.MovieFeignClient: debug
2021-07-08 11:29:24.419 DEBUG 2936 --- [p-nio-80-exec-1] c.qwpra.cloud.client.MovieFeignClient : [MovieFeignClient#getById] <--- HTTP/1.1 200 (3223ms)
2021-07-08 11:29:24.419 DEBUG 2936 --- [p-nio-80-exec-1] c.qwpra.cloud.client.MovieFeignClient : [MovieFeignClient#getById] connection: keep-alive
2021-07-08 11:29:24.419 DEBUG 2936 --- [p-nio-80-exec-1] c.qwpra.cloud.client.MovieFeignClient : [MovieFeignClient#getById] content-type: application/json
2021-07-08 11:29:24.420 DEBUG 2936 --- [p-nio-80-exec-1] c.qwpra.cloud.client.MovieFeignClient : [MovieFeignClient#getById] date: Thu, 08 Jul 2021 03:29:24 GMT
2021-07-08 11:29:24.420 DEBUG 2936 --- [p-nio-80-exec-1] c.qwpra.cloud.client.MovieFeignClient : [MovieFeignClient#getById] keep-alive: timeout=60
2021-07-08 11:29:24.420 DEBUG 2936 --- [p-nio-80-exec-1] c.qwpra.cloud.client.MovieFeignClient : [MovieFeignClient#getById] transfer-encoding: chunked
2021-07-08 11:29:24.420 DEBUG 2936 --- [p-nio-80-exec-1] c.qwpra.cloud.client.MovieFeignClient : [MovieFeignClient#getById]
2021-07-08 11:29:24.423 DEBUG 2936 --- [p-nio-80-exec-1] c.qwpra.cloud.client.MovieFeignClient : [MovieFeignClient#getById] {"mid":1,"mName":"hello,树先生"}
2021-07-08 11:29:24.423 DEBUG 2936 --- [p-nio-80-exec-1] c.qwpra.cloud.client.MovieFeignClient : [MovieFeignClient#getBy
Feign使用小结
第一步:引入Feign依赖
第二步:编写Feign客户端
第三步:开启Feign客户端支持
Feign中已经自动集成了Ribbon负载均衡,因此不需要自己定义RestTemplate进行负载均衡的配置。
SpringCloud 之熔断器 Hystrix
服务雪崩:服务之间调用,一个服务的不可用导致整个系统不可用。(原因:服务器的大量连接被出现异常的请求占用着,导致其他正常的请求得不到连接,所以导致整个系统不可用)
服务降级Fallback:服务器出现故障,给用户返回一个友好提示,不让客户端等待
服务熔断Breaker:达到最大访问后,直接拒绝访问,类似保险丝,直接断电,然后采用服务降级的方式,返回提示
服务熔断诱因:服务的降级->进而熔断->恢复调用链路
服务限流Flowlimit:
限制某个服务每秒的调用本服务的频率,例如秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟 N 个,有序进行
Hystrix 断路器
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖
不可避免的会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况
下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。“断路器”本身是一
种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),
向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是长时间的等待或
者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占
用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
Ribbon+Hystrix组合
1、导入hystrix依赖
<!-- 引入hystrix进行服务熔断 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、主启动类加入注解,开启断路保护@EnableCircuitBreaker
3、局部降级
在需要降级的方法上面加上注解@HystrixCommnd,fallbackMethod中是降级方法的方法名,降级方法的方法名,参数和原来的一样
@HystrixCommand(fallbackMethod = "getAllFallback")
public Map<String, Object> getAll(Integer userId) {
HashMap<String, Object> map = new HashMap<>();
User byId = getById(userId);
map.put("user",byId);
HashMap<String, String> map1 = new HashMap<>();
map1.put("id",userId.toString());
Movie forObject = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER8001/movie/list?id={id}", Movie.class,map1);
map.put("move",forObject);
return map;
}
public Map<String, Object> getAllFallback(Integer userId) {
HashMap<String, Object> map = new HashMap<>();
User byId = getById(userId);
map.put("user",byId);
// Movie forObject = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER8001/movie/list", Movie.class);
map.put("move",new Movie(1,"网络异常"));
return map;
}
4、全局降级
1、在降级的方法上加上注解@HystrixCommand
2、将类上加上注解@DefaultProperties(defaultFallback = “getAllFallback”)
@Service
@DefaultProperties(defaultFallback = "getAllFallback")
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private RestTemplate restTemplate;
public User getById(Integer id) {
return userDao.getById(id);
}
// @HystrixCommand(fallbackMethod = "getAllFallback")
@HystrixCommand
public Map<String, Object> getAll(Integer userId) {
HashMap<String, Object> map = new HashMap<>();
User byId = getById(userId);
map.put("user",byId);
HashMap<String, String> map1 = new HashMap<>();
map1.put("id",userId.toString());
Movie forObject = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER8001/movie/list?id={id}", Movie.class,map1);
map.put("move",forObject);
return map;
}
public Map<String, Object> getAllFallback() {
HashMap<String, Object> map = new HashMap<>();
User byId = getById(1);
map.put("user",byId);
// Movie forObject = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER8001/movie/list", Movie.class);
map.put("move",new Movie(1,"网络异常"));
return map;
}
}
注意:
1.@DefaultProperties(defaultFallback = “defaultFallBack”):在类上指 明统一的失败降级方法;该类中所有方法返回类型要与处理失败的方法的返回类型一致。
2.对于全局降级方法必须是无参的方法
触发服务降级的情况
程序运行异常
超时自动降级(默认 1 秒)
服务熔断触发服务降级
线程池/信号量打满也会导致服务降级
Hystrix的超时设置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000 #设置hystrix的超时等待时间
熔断器原理
Closed:关闭状态(断路器关闭),所有请求都正常访问。
Open:打开状态(断路器打开),所有请求都会被降级。Hystrix会对请求情况计数,当一
定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值
是50%,请求次数最少不低于20次。
Half Open:半开状态,不是永久的,断路器打开后会进入休眠时间(默认是5S)。随后断
路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会关闭断
路器,否则继续保持打开,再次进行休眠计时。
服务熔断策略
默认的服务熔断是20次,触发条件达到50%,休眠5秒
修改服务熔断策略
hystrix:
command:
default:
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是2
Feign+Hystrix组合
1、引入依赖
<!-- 引入hystrix进行服务熔断 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、开启断路保护@EnableCircuitBreaker
@EnableCircuitBreaker//开启断路保护功能
3、开启feign对Hystrix的支持
feign:
hystrix:
enabled: true
4、编写服务熔断的接口
/*使用Hystrix进行服务的熔断
* 1)、引入Hystrix的starter
* 2)、开启xxx功能 :@EnableCircuitBreaker
* 3)、@FeignClient(value="CLOUD-PROVIDER-MOVIE",fallback=指定这个接口的异常处 理类(异常处理类必须实现这个接口)) */
@FeignClient(value = "CLOUD-EUREKA-PROVIDER8001",fallback = MovieFeignImpl.class)//被调用方在服务端的名称
public interface MovieFeignClient {
//方法及@RequestMapping映射要和被调用接口保持完全一致
//feign默认集成了ribbon的负载均衡(默认轮询策略)。
@GetMapping("/movie/list")
public Movie getById(@RequestParam("id") Integer id);
}
5、编写该接口的实现类,放在容器中
//Feign客户端异常处理类,当Feign客户端的方法调用失败时,会调当前类的方法处理请求
@Component
public class MovieFeignImpl implements MovieFeignClient {
@Override
public Movie getById(Integer id) {
return new Movie(1,"网络延迟稍后再试1");
}
}
SpringCloud之网关
1、GateWay介绍
不同的微服务一般有不同的网络地址,而外部的客户端可能需要调用多个服务的接口才 能完成一个业务需求。比如一个电影购票的手机 APP,可能会调用电影分类微服务,用户 微服务,支付微服务等。如果客户端直接和微服务进行通信,会存在以下问题:
客户端会多次请求不同微服务,增加客户端的复杂性
存在跨域请求,在一定场景下处理相对复杂
认证复杂,每一个服务都需要独立认证
随着项目的迭代,当需要重新划分微服务时,项目变得难以重构
某些微服务可能使用了其他协议,直接访问有一定困难
网关包含了对请求的路由和过滤两个最主要的功能:
路由: 负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础 过滤:负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础,以后的访问微服务都是通过网关跳转后获得。
总体来说,网关提供了代理、路由、断言和过滤的功能。
springCloud 1.x 版本中都是采用的 Zuul 网关,2.x 版本中,SpringCloud 自己研发了一个网关代替 Zuul,那就是 SpringCloud Geteway
2、工作流程
-
客户端向Spring Cloud Gateway发出请求。
-
在Gateway Handler Mapping中找到与请求匹配的路由,将其发送到Gateway Web Handler.
3.Handler再通过指定的过滤器链来将请求发送给我们实际的服务执行业务逻辑,然后返回
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)
执行业务逻辑。Filter 在**“pre”****类型**的过滤器可以做参数校验、权限校验、流量监控、日
志输出、协议转换等,在**“post”****类型**的过滤器中可以做响应内容、响应头的修改,日志的
输出,流量控制等有着非常重要的作用。
3、三大核心组件和作用
断言(Predicate):只有断言成功后才会匹配到微服务进行路由,路由到代理的微服务。
路由(Route):分发请求 。
过滤(Filter):对请求或者响应报文进行处理
4、配置
1、导入gateway依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2、在主启动类加上注解@EnableEurekaClient
3、编写配置文件
server:
port: 9527
spring:
application:
name: cloud-gateway-gateway9527
cloud:
gateway:
routes:
#通过路径和服务名匹配路由
- id: cloud-eureka-provider8001 #唯一标识符,通常写被拦截的服务在eureka注册中心的应用名称
uri: http://localhost:8001 #lb://CLOUD-EUREKA-PROVIDER8001,服务名匹配路由,负载均衡
predicates: #断言
- Path=/movie/**
- id: cloud-eureka-consumer80 #唯一标识符,通常写被拦截的服务在eureka注册中心的应用名称
uri: http://localhost
predicates: #断言
- Path=/user/**
#http://localhost:9527/user/all
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eurekaserver7001:7001/eureka
4、通过配置类配置路由
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator getRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("att", new Function<PredicateSpec, Route.AsyncBuilder>() {
@Override
public Route.AsyncBuilder apply(PredicateSpec predicateSpec) {
Route.AsyncBuilder uri = predicateSpec.path("/**").uri("http://news.baidu.com/");
return uri;
}
//http://localhost:9527/guonei
});
return routes.build();
}
}
特别提醒:上面的配置类用完之后一定要注释或者删除,否则影响配置文件中的路由配置。
5、路由断言
- Path=/customer/*,/user/* #断言,路径相匹配的进行路由
- After=2020-05-01T08:00:00.0+08:00 # 断言,在此时间后请求才会被匹配
- Before=2020-05-01T09:08+08:00 # 断言,在此时间前请求才会被匹配
- Between=2020-05-01T08:00:00.0+08:00,2020-05-02T09:10+08:00 # 断言, 在此时间区间内访问的请求才会被匹配
- Cookie=username,qwpra # 断言,请求头中携带Cookie: username=qwpra才可以匹配
- Cookie=id,9527
- Header=X-Request-Id,\d+ # 断言,请求头中要有X-Request-Id属性并且值 为整数的正则表达式
- Method=POST # 断言,请求方式为post方式才会被匹配
- Query=pwd,[a-z0-9_-]{6} # 断言,请求参数中包含pwd并且值长度为6才会 被匹配
6、过滤器
请求被网关服务断言成功后即将请求交给微服务处理之前或微服务处理请求响应回来之后 即将响应给客户端之前可以通过过滤器对请求响应报文进行修改、或者进行日志输出、流控
分类:
1.根据 filter 的作用时机:
局部作用的 filter:GatewayFilter(一般使用系统自带的)
pre 类型的在请求交给微服务之前起作用
post 类型的在响应回来时起作用
全局作用的 filter:GlobalFilter(一般需要自定义)
1、使用系统自带的过滤器
routes:
- id: cloud-consumer-feign-consumer80
uri: http://localhost
predicates:
- Path=/feign/**
filters:
- AddRequestParameter=requestid,10001 # 在匹配请求的请求参数中添加一对请求 参数
- AddResponseHeader=company,qwpra # 在匹配的请求的响应头中添加一对响应头
2、自定义局部过滤器:继承AbstractGatewayFilterFactory
@Component
public class MyCheckNameFilter extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Object config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
String name = params.getFirst("name");
if (StringUtils.isEmpty(name)){
//结束本次请求,返回响应报文
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}else {
//放行
return chain.filter(exchange);
}
}
};
}
@Override
public String name() {
return "MyCheckNameFilter";
}
}
在配置文件中加入过滤器
spring:
application:
name: cloud-gateway-gateway9527
cloud:
gateway:
routes:
- id: cloud-eureka-provider8001 #唯一标识符,通常写被拦截的服务在eureka注册中心的应用名称
uri: http://localhost:8001 #lb://CLOUD-EUREKA-PROVIDER8001
filters:
- MyCheckNameFilter
predicates: #断言
- Path=/movie/**
3、全局过滤器: 实现GlobalFilter, Ordered
@Component
public class MyBlobalFIlter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
String token = params.getFirst("token");
if (StringUtils.isEmpty(token)){
//结束本次请求,返回响应报文
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
//全局过滤器的执行顺序
@Override
public int getOrder() {
return 0;
}
}
SpringCloud之分布式链路请求追踪技术
在分布式系统中,微服务有多个,服务之间调用关系也比较复杂,如果有的微服务网络或者 服务器出现问题会导致服务提供失败,如何快速便捷的去定位出现问题的微服务, SpringCloud Sleuth 给我们提供了解决方案,它集成了 Zipkin、HTrace 链路追踪 工具,用服务链路追踪来快速定位问题。Zipkin 使用较多。Zipkin 主要由四部分构成: 收集器、数据存储、查询以及 Web 界面。Zipkin 的收集器负责将各系统报告过来的追踪 数据进行接收;而数据存储默认使用 Cassandra,也可以替换为 MySQL;查询服务用来 向其他服务提供数据查询的能力,而 Web 服务是官方默认提供的一个图形用户界面。
Sleuth Zipkin 下载启动
https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
zipkin-server-2.12.9-exec.jar
运行 zipkin-server-2.12.9-exec
java -jar zipkin-server-2.12.9-exec.jar
访问 Zipkin 控制台
Zipkin 默认端口号 9411: http://localhost:9411/zipkin/
使用
1、在需要使用Sleuth的地方添加依赖
<!--包含了sleuth+zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
2、配置文件添加配置
spring:
zipkin:
#将指定数据提交到zipkin服务端接收
base-url: http://localhost:9411
sleuth:
sampler:
#采样值,在0-1之间,1表示全部采样
probability: 1
}
return chain.filter(exchange);
}
//全局过滤器的执行顺序
@Override
public int getOrder() {
return 0;
}
}
# SpringCloud之分布式链路请求追踪技术
在分布式系统中,微服务有多个,服务之间调用关系也比较复杂,如果有的微服务网络或者 服务器出现问题会导致服务提供失败,如何快速便捷的去定位出现问题的微服务, SpringCloud Sleuth 给我们提供了解决方案,它集成了 Zipkin、HTrace 链路追踪 工具,用服务链路追踪来快速定位问题。Zipkin 使用较多。Zipkin 主要由四部分构成: 收集器、数据存储、查询以及 Web 界面。Zipkin 的收集器负责将各系统报告过来的追踪 数据进行接收;而数据存储默认使用 Cassandra,也可以替换为 MySQL;查询服务用来 向其他服务提供数据查询的能力,而 Web 服务是官方默认提供的一个图形用户界面。
## **Sleuth Zipkin 下载启动**
https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
zipkin-server-2.12.9-exec.jar
### **运行 zipkin-server-2.12.9-exec**
java -jar zipkin-server-2.12.9-exec.jar
### **访问 Zipkin 控制台**
Zipkin 默认端口号 9411: http://localhost:9411/zipkin/
### 使用
**1、在需要使用Sleuth的地方添加依赖**
org.springframework.cloud spring-cloud-starter-zipkin ```
2、配置文件添加配置
spring:
zipkin:
#将指定数据提交到zipkin服务端接收
base-url: http://localhost:9411
sleuth:
sampler:
#采样值,在0-1之间,1表示全部采样
probability: 1