1 什么是微服务
微服务是一种架构,这种架构是将单个的整体应用程序分割成更小的项目关联的独立的服务。一个服务通常实现一组独立的特性或功能,包含自己的业务逻辑和适配器。各个微服务之间的关联通过暴露api来实现。这些独立的微服务不需要部署在同一个虚拟机,同一个系统和同一个应用服务器中
# Spring Cloud:
- Spring Cloud NetFlix
基于美国Netflix公司开源的组件进行封装,提供了微服务一栈式的解决方案。
- Spring Cloud alibaba
在Spring cloud netflix基础上封装了阿里巴巴的微服务解决方案。
- Spring Cloud Spring
目前spring官方趋势正在逐渐吸收Netflix组件的精华,并在此基础进行二次封装优化,打造spring专有的解决方案
# 1.springcloud核心组件说明
- eurekaserver、consul、nacos 服务注册中心组件
- rabbion & openfeign 服务负载均衡 和 服务调用组件
- hystrix & hystrix dashboard 服务断路器 和 服务监控组件
- zuul、gateway 服务网关组件
- config 统一配置中心组件
- bus 消息总线组件
......
结构图:
3 springcloud_parent搭建
springcloud版本与springboot版本关系:https://spring.io/projects/spring-cloud
搭建环境:
- springboot 2.2.5.RELEASE
- springcloud Hoxton.SR6
- java8
- maven 3.3.9
- idea 2018.3.5
构建springcloud_parent 空的maven项目并删除src:
pom.xml:
4.0.0
org.example
springcloud_parent_hsz
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.2.5.RELEASE
1.8
Hoxton.SR6
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
4 构建服务注册中心:
所谓服务注册中心就是在整个的微服务架构中单独提出一个服务,这个服务不完成系统的任何的业务功能,仅仅用来完成对整个微服务系统的服务注册和服务发现,以及对服务健康状态的监控和管理功能。
服务注册中心
- 可以对所有的微服务的信息进行存储,如微服务的名称、IP、端口等
- 可以在进行服务调用时通过服务发现查询可用的微服务列表及网络地址进行服务调用
- 可以对所有的微服务进行心跳检测,如发现某实例长时间无法访问,就会从服务注册表移除该实例。
开发Eureka Server:
在springcloud_parent_hsz下构建空的maven项目springcloud_01eureka_server:
pom文件:
springcloud_parent_hsz
org.example
1.0-SNAPSHOT
4.0.0
springcloud_01eureka_server
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
resource文件夹下编写配置application.properties:
server.port=8761
spring.application.name=eurekaserver
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
#不再将自己同时作为客户端进行注册
eureka.client.register-with-eureka=false
#关闭作为客户端时从eureka server获取服务信息
eureka.client.fetch-registry=false
#关闭自我保护
eureka.server.enable-self-preservation=false
#超时3s自动清除
eureka.server.eviction-interval-timer-in-ms=3000
java文件夹下创建
package com.example;
@SpringBootApplication
@EnableEurekaServer
public class Eurekaserver8761Application {
public static void main(String[] args) {
SpringApplication.run(Eurekaserver8761Application.class, args);
}
}
访问Eureka的服务注册页面
- http://localhost:8761/eureka
5 构建client并注册到eureka
构建空的maven项目springcloud_01eureka_client:
pom文件:
springcloud_parent_hsz
org.example
1.0-SNAPSHOT
4.0.0
springcloud_01eureka_client
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
resource文件夹下编写配置application.properties:
#服务端口号
server.port=8888
#服务名称唯一标识
spring.application.name=eurekaclient8888
#eureka注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
#用来修改eureka server默认接受心跳的最大时间 默认是90s
eureka.instance.lease-expiration-duration-in-seconds=10
#指定客户端多久向eureka server发送一次心跳 默认是30s
eureka.instance.lease-renewal-interval-in-seconds=5
java文件夹下创建
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class Eurekaserver8888Application {
public static void main(String[] args) {
SpringApplication.run(Eurekaserver8888Application.class, args);
}
}
访问Eureka的服务注册页面
- http://localhost:8761/eureka
# 0.服务频繁启动时 EurekaServer出现错误
- EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
# 1.自我保护机制
- 官网地址: https://github.com/Netflix/eureka/wiki/Server-Self-Preservation-Mode
- 默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。Eureka Server在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期。这种设计的哲学原理就是"宁可信其有不可信其无!"。自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。
6 eureka server 集群:
复制多个配置:
选定配置后,改application.properties:
8761的pom.xml:
server.port=8761
spring.application.name=eurekaserver
eureka.client.service-url.defaultZone=http://localhost:8762/eureka,http://localhost:8763/eureka
#不再将自己同时作为客户端进行注册
eureka.client.register-with-eureka=false
#关闭作为客户端时从eureka server获取服务信息
eureka.client.fetch-registry=false
#关闭自我保护
eureka.server.enable-self-preservation=false
#超时3s自动清除
eureka.server.eviction-interval-timer-in-ms=3000
8762的pom.xml:
server.port=8762
spring.application.name=eurekaserver
eureka.client.service-url.defaultZone=http://localhost:8761/eureka,http://localhost:8763/eureka
#不再将自己同时作为客户端进行注册
eureka.client.register-with-eureka=false
#关闭作为客户端时从eureka server获取服务信息
eureka.client.fetch-registry=false
#关闭自我保护
eureka.server.enable-self-preservation=false
#超时3s自动清除
eureka.server.eviction-interval-timer-in-ms=3000
8763的pom.xml:
server.port=8763
spring.application.name=eurekaserver
eureka.client.service-url.defaultZone=http://localhost:8761/eureka,http://localhost:8762/eureka
#不再将自己同时作为客户端进行注册
eureka.client.register-with-eureka=false
#关闭作为客户端时从eureka server获取服务信息
eureka.client.fetch-registry=false
#关闭自我保护
eureka.server.enable-self-preservation=false
#超时3s自动清除
eureka.server.eviction-interval-timer-in-ms=3000
启动这三个,并访问http://localhost:8761/或http://localhost:8762或http://localhost:8763:
7eureka client集群:
复制三个配置:
8887 pom文件:
#服务端口号
server.port=8887
#服务名称唯一标识
spring.application.name=eurekaclient
#eureka注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka,http://localhost:8762/eureka,http://localhost:8763/eureka
#用来修改eureka server默认接受心跳的最大时间 默认是90s
eureka.instance.lease-expiration-duration-in-seconds=10
#指定客户端多久向eureka server发送一次心跳 默认是30s
eureka.instance.lease-renewal-interval-in-seconds=5
8888 pom文件:
#服务端口号
server.port=8888
#服务名称唯一标识
spring.application.name=eurekaclient
#eureka注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka,http://localhost:8762/eureka,http://localhost:8763/eureka
#用来修改eureka server默认接受心跳的最大时间 默认是90s
eureka.instance.lease-expiration-duration-in-seconds=10
#指定客户端多久向eureka server发送一次心跳 默认是30s
eureka.instance.lease-renewal-interval-in-seconds=5
8889 pom文件:
#服务端口号
server.port=8889
#服务名称唯一标识
spring.application.name=eurekaclient
#eureka注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka,http://localhost:8762/eureka,http://localhost:8763/eureka
#用来修改eureka server默认接受心跳的最大时间 默认是90s
eureka.instance.lease-expiration-duration-in-seconds=10
#指定客户端多久向eureka server发送一次心跳 默认是30s
eureka.instance.lease-renewal-interval-in-seconds=5
启动这三个,并访问http://localhost:8761/或http://localhost:8762或http://localhost:8763:查看结果。
eureka2.x已停止维护。
8 consul作为注册中心:
https://www.consul.io/downloads
将windows版本consul_1.8.0_windows_amd64解压到不带中文的文件夹下,并在consul.exe处打开cmd窗口执行consul agent -dev
9 consul client开发
创建空的maven项目:
引入依赖:
springcloud_parent
com.baizhi
1.0-SNAPSHOT
4.0.0
springcloud_03consulclient
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
application.properties:
server.port=8082
spring.application.name=CONSULCLIENT
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.service-name=${spring.application.name}
#spring.cloud.consul.discovery.register-health-check=true
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication //代表这一个springboot入口应用
@EnableDiscoveryClient //作用:通用服务注册客户端注解 代表 consul client zk client nacos client
public class ConsulClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsulClientApplication.class,args);
}
}
10 注册中心区别:
# 1.CAP定理
- CAP定理:CAP定理又称CAP原则,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
`一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
`可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
`分区容忍性(P),就是高可用性,一个节点崩了,并不影响其它的节点(100个节点,挂了几个,不影响服务,越多机器越好)
# 2.Eureka特点
- Eureka中没有使用任何的数据强一致性算法保证不同集群间的Server的数据一致,仅通过数据拷贝的方式争取注册中心数据的最终一致性,虽然放弃数据强一致性但是换来了Server的可用性,降低了注册的代价,提高了集群运行的健壮性。
# 3.Consul特点
- 基于Raft算法,Consul提供强一致性的注册中心服务,但是由于Leader节点承担了所有的处理工作,势必加大了注册和发现的代价,降低了服务的可用性。通过Gossip协议,Consul可以很好地监控Consul集群的运行,同时可以方便通知各类事件,如Leader选择发生、Server地址变更等。
# 4.zookeeper特点
- 基于Zab协议,Zookeeper可以用于构建具备数据强一致性的服务注册与发现中心,而与此相对地牺牲了服务的可用性和提高了注册需要的时间。
11 RestTemplate调用
spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。
restTemplate是直接基于服务地址调用没有在服务注册中心获取服务,也没有办法完成服务的负载均衡如果需要实现服务的负载均衡需要自己书写服务负载均衡策略。
12 Ribbon
官方网址: https://github.com/Netflix/ribbon
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
Ribbon已停止维护!!!
13 openfeign
https://cloud.spring.io/spring-cloud-openfeign/reference/html/
category调用product案例:
package com.example.controller;
import com.baizhi.entity.Product;
import com.baizhi.vos.CollectionVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@RestController
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
@Value("${server.port}")
private int port;
@GetMapping("/productList")
public Map findByCategoryIdAndPage(Integer page, Integer rows, Integer categoryId){
log.info("当前页: {} 每页显示记录数:{} 当前类别id:{} ",page,rows,categoryId);
//根据类别id分页查询符合当前页集合数据 List select * from t_product where categoryId=? limt ?(page-1)*rows,?(rows)
//根据类别id查询当前类别下总条数 totalCount select count(id) from t_product where categoryId=?
Map map = new HashMap<>();
List products = new ArrayList<>();
products.add(new Product(1,"短裙",23.23,new Date()));
products.add(new Product(2,"超短裙",23.23,new Date()));
products.add(new Product(3,"超级超短裙",23.23,new Date()));
int total = 1000;
map.put("rows",products);
map.put("total", total);
return map;
}
@GetMapping("/products")
public List findByCategoryId(Integer categoryId){
log.info("类别id: {}",categoryId);
//调用业务逻辑根据类别id查询商品列表
List products = new ArrayList<>();
products.add(new Product(1,"短裙",23.23,new Date()));
products.add(new Product(2,"超短裙",23.23,new Date()));
products.add(new Product(3,"超级超短裙",23.23,new Date()));
return products;
}
//定义一个接口接收id类型参数,返回一个基于id查询的对象
@GetMapping("/product/{id}")
public Product product(@PathVariable("id") Integer id){
log.info("id:{}",id);
return new Product(id,"超短连衣裙",23.23,new Date());
}
//定义一个接口接受集合类型参数
//springmvc 不能直接接受集合类型参数,如果想要接收集合类型参数必须将集合放入对象中,使用对象的方式接收才行
//oo: oriented(面向) object(对象) 面向对象
// vo(value object): 用来传递数据对象称之为值对象
// dto:(data transfer(传输) object):数据传输对象
@GetMapping("/test4")
public String test4(CollectionVO collectionVO){
collectionVO.getIds().forEach(id-> log.info("id:{} ",id));
return "test4 ok,当前服务端口为: "+port;
}
//定义个接口接受数组类型参数
@GetMapping("/test3")
public String test3(String[] ids){
for (String id : ids) {
log.info("id: {}",id);
}
//手动转为list List strings = Arrays.asList(ids);
return "test3 ok,当前服务端口为: "+port;
}
//定义一个接受对象类型参数接口
@PostMapping("/test2")
public String test2(@RequestBody Product product){
log.info("product:{}",product);
return "test2 ok,当前服务端口为: "+port;
}
//定义一个接受零散类型参数接口 路径传递参数
@GetMapping("/test1/{id}/{name}")
public String test1(@PathVariable("id") Integer id,@PathVariable("name") String name){
log.info("id:{} name:{}",id,name);
return "test1 ok,当前服务端口为: "+port;
}
//定义一个接受零散类型参数接口 queryString
@GetMapping("/test")
public String test(String name, Integer age){
log.info("name:{} age:{}",name,age);
return "test ok,当前服务端口为: "+port;
}
@GetMapping("/product")
public String product() throws InterruptedException {
log.info("进入商品服务.....");
//Thread.sleep(2000);
return "product ok,当前提供服务端口:"+port;
}
@GetMapping("/list")
public String list(HttpServletRequest request,String color){
String header = request.getHeader("User-Name");
System.out.println("获取对应请求参数 color: "+color);
System.out.println("获取请求头信息: "+header);
log.info("商品列表服务");
return "list ok当前提供服务端口:"+port;
}
}
package com.example.entity;
import java.util.Date;
public class Product {
private Integer id;
private String name;
private Double price;
private Date bir;
public Product() {
}
public Product(Integer id, String name, Double price, Date bir) {
this.id = id;
this.name = name;
this.price = price;
this.bir = bir;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", bir=" + bir +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Date getBir() {
return bir;
}
public void setBir(Date bir) {
this.bir = bir;
}
}
package com.example.vos;
import java.util.List;
//定义用来接收集合类型参数的对象
public class CollectionVO {
private List ids;//接收集合声明在这里
public List getIds() {
return ids;
}
public void setIds(List ids) {
this.ids = ids;
}
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class,args);
}
}
application.properties:
server.port=8788
spring.application.name=PRODUCT
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
#mybatis
#redis
#es
#mq
pom.xml
springcloud_parent
com.baizhi
1.0-SNAPSHOT
4.0.0
springcloud_07product
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
package com.example.controller;
import com.example.entity.Product;
import com.example.feignclient.ProductClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class CategoryController {
private static final Logger log = LoggerFactory.getLogger(CategoryController.class);
@Autowired
private ProductClient productClient;
@GetMapping("/list")
public String list(){
return "list ok";
}
@GetMapping("/category")
public String category(){
log.info("category service ....");
//1.RestTemplate 2.RestTemplate+Ribbon() 3.OpenFeign
//String result = productClient.test("小陈", 23);
//String result = productClient.test1(21,"xiaoming");
String result = productClient.test2(new Product(1, "超短裙", 23.23, new Date()));
//String result = productClient.test3(new String[]{"21","23","24"});
//String result = productClient.test4(new String[]{"21","23","24"});
//Product product = productClient.product(21);
//log.info("product: {}",product);
//List products = productClient.findByCategoryId(1);
//products.forEach(product -> log.info("product: {}",product));
/*String result = productClient.findByCategoryIdAndPage(1, 5, 1);
System.out.println(result);
//自定义json反序列化 对象转为json 序列化 on字符串转为对象
JSONObject jsonObject = JSONObject.parseObject(result);
System.out.println(jsonObject.get("total"));
Object rows = jsonObject.get("rows");
System.out.println(rows);
//二次json反序列化
List products = jsonObject.parseArray(rows.toString(), Product.class);
products.forEach(product -> {
log.info("product:{}",product);
});*/
//String result = productClient.product();
return result;
}
}
package com.example.entity;
import java.util.Date;
public class Product {
private Integer id;
private String name;
private Double price;
private Date bir;
public Product() {
}
public Product(Integer id, String name, Double price, Date bir) {
this.id = id;
this.name = name;
this.price = price;
this.bir = bir;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", bir=" + bir +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Date getBir() {
return bir;
}
public void setBir(Date bir) {
this.bir = bir;
}
}
package com.example.feignclient;
import com.baizhi.entity.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
//调用商品服务接口
@FeignClient(value="PRODUCT") //value: 用来书写调用服务服务id
public interface ProductClient {
//声明调用商品服务根据类别id查询分页查询商品信息 以及总条数
@GetMapping("/productList")
String findByCategoryIdAndPage(@RequestParam("page") Integer page,@RequestParam("rows") Integer rows,@RequestParam("categoryId") Integer categoryId);
///声明调用商品服务根据类别id查询一组商品信息
@GetMapping("/products")
List findByCategoryId(@RequestParam("categoryId") Integer categoryId);
//声明调用根据id查询商品信息接口
@GetMapping("/product/{id}")
Product product(@PathVariable("id") Integer id);
//声明调用商品服务中test4接口 传递一个list集合类型参数 test4?ids=21&ids=22
@GetMapping("/test4")
String test4(@RequestParam("ids") String[] ids);
//声明调用商品服务中test3接口 传递一个数组类型 queryString /test3?ids=21&ids=22
@GetMapping("/test3")
String test3(@RequestParam("ids") String[] ids);
//声明调用商品服务中test2接口 传递一个商品对象
@PostMapping(value = "/test2")
String test2(@RequestBody Product product);
//声明调用商品服务中test1接口 路径传递数据
@GetMapping("/test1/{id}/{name}")
String test1(@PathVariable("id") Integer id, @PathVariable("name") String name);
//声明调用商品服务中test?name=xxx&age=23接口传递name,age
@GetMapping("/test")
String test(@RequestParam("name") String name,@RequestParam("age") Integer age);
//调用商品服务
@GetMapping("/product")
String product();
@GetMapping("/list")
String list();
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient//开启服务注册
@EnableFeignClients //开启openfein客户端调用
public class CategoryApplication {
public static void main(String[] args) {
SpringApplication.run(CategoryApplication.class,args);
}
}
application.properties:
server.port=8787
spring.application.name=CATEGORY
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
#配置指定服务连接超时
#feign.client.config.PRODUCT.connectTimeout=5000
#配置指定服务等待超时
#feign.client.config.PRODUCT.readTimeout=5000
#配置所有服务连接超时
feign.client.config.default.connectTimeout=5000
#配置所有服务等待超时
feign.client.config.default.readTimeout=5000
#开启指定服务日志展示
feign.client.config.PRODUCT.loggerLevel=full
#全局开启服务日志展示
#feign.client.config.default.loggerLevel=full
#指定feign调用客户端对象所在包,必须是debug级别
logging.level.com.baizhi.feignclient=debug
pom.xml:
springcloud_parent
com.baizhi
1.0-SNAPSHOT
4.0.0
springcloud_06category
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-openfeign
com.alibaba
fastjson
1.2.76
14 服务雪崩
hystrix 用来保护微服务系统 实现 服务降级 服务熔断 https://github.com/Netflix/Hystrix
15 服务熔断
16服务降级
熔断降级总结:
# 1.共同点
- 目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
- 最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
- 粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改);
- 自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段;sentinel
# 2.异同点
- 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
- 管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务边缘服务开始)
# 3.总结
- 熔断必会触发降级,所以熔断也是降级一种,区别在于熔断是对调用链路的保护,而降级是对系统过载的一种保护处理
17 服务熔断实现
package com.example.config;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeansConfig {
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
package com.example.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@GetMapping("/demo1")
@HystrixCommand(defaultFallback = "defaultFallBack")
public String demo1(){
System.out.println("demo1 ok!!");
return "demo1 ok !!!";
}
@GetMapping("demo")
@HystrixCommand(defaultFallback = "defaultFallBack")//熔断之后处理 fallbackMethod 书写快速失败方法名
public String demo(Integer id){
System.out.println("demo ok !!!");
if(id<=0){
throw new RuntimeException("无效id!!!!");
}
return "demo ok !!!";
}
//默认的处理方法
public String defaultFallBack(){
return "网络连接失败,请重试!!!";
}
//自己备选处理
public String demoFallBack(Integer id){
return "当前活动过于火爆,服务已经被熔断了!!!";
}
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication //代表springboot应用
@EnableDiscoveryClient //代表服务注册中心客户端 consul client
@EnableCircuitBreaker //开启hystrix服务熔断
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class,args);
}
}
application.properties:
server.port=8990
spring.application.name=HYSTRIX
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
pom.xml:
springcloud_parent
com.baizhi
1.0-SNAPSHOT
4.0.0
springcloud_08hystrix
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
18 服务降级实现
# 服务降级: 站在系统整体负荷角度 实现: 关闭系统中某些边缘服务 保证系统核心服务运行
Emps 核心服务 Depts 边缘服务
# 1.客户端openfeign + hystrix实现服务降级实现
- 引入hystrix依赖
- 配置文件开启feign支持hystrix
- 在feign客户端调用加入fallback指定降级处理
- 开发降级处理方法
package com.example.controller;
import com.baizhi.feignclients.HystrixClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestDemoController {
//注入openfeign 客户端对象
@Autowired
private HystrixClient hystrixClient;
@GetMapping("test")
public String test(){
System.out.println("test ok !!!");
String demoResult = hystrixClient.demo(-1);
System.out.println("demo result : "+demoResult);
return "test ok ";
}
}
package com.example.feignclients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "HYSTRIX",fallback = HystrixClientFallBack.class) //fallback: 这个属性用来指定当前调用服务不可用时,默认的备选处理
public interface HystrixClient {
@GetMapping("/demo")
String demo(@RequestParam("id") Integer id);
}
package com.example.feignclients;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
// 自定义HystrixClient 默认备选处理
@Configuration
public class HystrixClientFallBack implements HystrixClient {
@Override
public String demo(Integer id) {
return "当前服务不可用,请稍后再试! id: "+id;
}
}
package example.baizhi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient//开启服务注册客户端 consul server
@EnableFeignClients //开启OpenFeign调用
public class OpenFeignHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(OpenFeignHystrixApplication.class,args);
}
}
application.properties:
server.port=8991
spring.application.name=OPENFEIGNHYSTRIX
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
feign.hystrix.enabled=true
pom.xml:
springcloud_parent
com.baizhi
1.0-SNAPSHOT
4.0.0
springcloud_09openfeign_hystrix
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-openfeign
19 hystrixboard
package com.example;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
//@EnableDiscoveryClient //注意: 默认只要引入discovery client依赖 该注解无须显示声明 自动注册 consul zk nacos
@EnableHystrixDashboard //注意: 这个注解作用用来开启当前应用为仪表盘应用
public class HystrixDashBoardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashBoardApplication.class,args);
}
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
application.properties:
server.port=9909
spring.application.name=HYSTRIXDASHBOARD
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
pom.xml:
springcloud_parent_hsz
org.example
1.0-SNAPSHOT
4.0.0
springcloud_01hystrix_dash_board
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
在熔断的代码里加
将修改好的spring-cloud-netflix-hystrix-dashboard-2.2.3.RELEASE.jar替换到本地maven仓库中。maven install项目后进行重启
调用带有@HystrixCommand注解的controller接口可看到效果:
20 gateway
predicate(断言,验证):https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#the-cookie-route-predicate-factory
filter:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#gatewayfilter-factories
package com.example.filters;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定义网关全局filter
*/
@Configuration
public class CustomerGlobalFilter implements GlobalFilter, Ordered {
//类似javaweb doFilter
//exchange : 交换 request response 封装了 request response
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//httprequest对象
ServerHttpRequest request = exchange.getRequest();
//httpresponse对象
ServerHttpResponse response = exchange.getResponse();
System.out.println("经过全局Filter处理.......");
Mono filter = chain.filter(exchange);//放心filter继续向后执行
System.out.println("响应回来Filter处理......");
return filter;
}
//order 排序 int数字:用来指定filter执行顺序 默认顺序按照自然数字进行排序 -1 在所有filter执行之前执行
@Override
public int getOrder() {
return -1;
}
}
package com.baizhi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
RoutePredicateFactory routePredicateFactory;
GatewayFilterFactory gatewayFilterFactory;
SpringApplication.run(GatewayApplication.class,args);
}
}
application.yaml:
server:
port: 7979
spring:
application:
name: GATEWAY
cloud:
consul:
host: localhost
port: 8500
gateway:
routes:
- id: product_router #路由对象唯一标识
#uri: http://localhost:8788 #用来类别服务地址 http://localhost:8788/list
uri: lb://PRODUCT #实现请求负载均衡处理 /product/product/list
predicates: #断言 用来配置路由规则
- Path=/product/**
#- After=2021-04-20T10:20:22.124+08:00[Asia/Shanghai]
#- Before=2021-04-20T10:23:22.124+08:00[Asia/Shanghai]
#- Between=2021-04-20T10:23:22.124+08:00[Asia/Shanghai],2021-04-20T10:25:22.124+08:00[Asia/Shanghai]
#- Cookie=name,chenyn
# - Cookie=name,[A-Za-z0-9]+
#cmd 窗口 curl http://localhost:8989/user/findAll --cookie "username=zhangsna"
#- Header=X-Request-Id,\d+
#- Method=GET
filters:
- AddRequestHeader=User-Name, chenyn
- AddRequestParameter=color, blue
- AddResponseHeader=X-Response-Red, Blue
#- PrefixPath=/product #加入指定前缀filter
- StripPrefix=1 #去掉请求路径中n级前缀
- id: category_router #路由对象唯一标识
#uri: http://localhost:8787 #用来类别服务地址 http://localhost:8787/list
uri: lb://CATEGORY
predicates: #断言 用来配置路由规则
- Path=/category/**
filters:
- StripPrefix=1 # /list
management:
endpoints:
web:
exposure:
include: "*" # http://localhhost:7979/actuator/gateway/routes
pom.xml:
springcloud_parent
com.baizhi
1.0-SNAPSHOT
4.0.0
springcloud_11gateway
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-gateway
junit
junit
4.12
test
21 springcloud config server
1 gitee上创建远程public仓库
安装rabbitmq:
1.将rabbitmq安装包上传到linux系统中
erlang-22.0.7-1.el7.x86_64.rpm
rabbitmq-server-3.7.18-1.el7.noarch.rpm
2.安装Erlang依赖包
rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
3.安装RabbitMQ安装包(需要联网)
yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm
注意:默认安装完成后配置文件模板在:/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example目录中,需要
将配置文件复制到/etc/rabbitmq/目录中,并修改名称为rabbitmq.config
4.复制配置文件
cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
5.查看配置文件位置
ls /etc/rabbitmq/rabbitmq.config
6.修改配置文件(参见下图:)
vim /etc/rabbitmq/rabbitmq.config
7.执行如下命令,启动rabbitmq中的插件管理
rabbitmq-plugins enable rabbitmq_management
出现如下说明:
Enabling plugins on node rabbit@localhost:
rabbitmq_management
The following plugins have been configured:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
Applying plugin configuration to rabbit@localhost...
The following plugins have been enabled:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
set 3 plugins.
Offline change; changes will take effect at broker restart.
8.启动RabbitMQ的服务
systemctl start rabbitmq-server
systemctl restart rabbitmq-server
#systemctl stop rabbitmq-server
9.查看服务状态(见下图:)
systemctl status rabbitmq-server
11.访问web管理界面
http://10.15.0.8:15672/
# 6.修改远程配置后在配置中心服务通过执行post接口刷新配置
- curl -X POST http://localhost:8848/actuator/bus-refresh
- 指定端口刷新某个具体服务: curl -X POST http://localhost:7878/actuator/bus-refresh/configclient:9090
- 指定服务id刷新服务集群节点: curl -X POST http://localhost:7878/actuator/bus-refresh/configclient
package com.example.filters;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@Component
public class UrlFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
String url = new String(httpServletRequest.getRequestURI());
//只过滤/actuator/bus-refresh请求
if (!url.endsWith("/bus-refresh")) {
chain.doFilter(request, response);
return;
}
//获取原始的body
String body = readAsChars(httpServletRequest);
System.out.println("original body: "+ body);
//使用HttpServletRequest包装原始请求达到修改post请求中body内容的目的
CustometRequestWrapper requestWrapper = new CustometRequestWrapper(httpServletRequest);
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {
}
private class CustometRequestWrapper extends HttpServletRequestWrapper {
public CustometRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.read() == -1 ? true:false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
}
public static String readAsChars(HttpServletRequest request){
BufferedReader br = null;
StringBuilder sb = new StringBuilder("");
try
{
br = request.getReader();
String str;
while ((str = br.readLine()) != null)
{
sb.append(str);
}
br.close();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if (null != br)
{
try
{
br.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
return sb.toString();
}
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer //代表我是统一配置中心服务
@ServletComponentScan(basePackages = "com.example.filters")
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class,args);
}
}
application.properties
server.port=8848
spring.application.name=CONFIGSERVER
#consul server
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.config.server.git.uri=https://gitee.com/xxxshuzhe/configs.git
spring.cloud.config.server.git.default-label=master
#spring.cloud.config.server.git.username= 私有仓库访问用户名
#spring.cloud.config.server.git.password= 私有仓库访问密码
#rabbitmq地址
spring.rabbitmq.host=14.18.49.xx
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
#暴露web端点
management.endpoints.web.exposure.include=*
pom.xml:
springcloud_parent_hsz
org.example
1.0-SNAPSHOT
4.0.0
springcloud_01config_server
8
8
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-config-server
org.springframework.cloud
spring-cloud-starter-bus-amqp
config client:
package com.example.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope //作用: RefreshScope 用来在不需要重启微服务情况下,将当前scope域中信息刷新为最新配置信息
public class DemoController {
private static final Logger log = LoggerFactory.getLogger(DemoController.class);
@Value("${name}")
private String name;
@GetMapping("/demo")
public String demo(){
log.info("demo ok!!!");
return "demo ok: " +name;
}
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class,args);
}
}
bootstrap.properties:
server.port=8990
spring.application.name=CONFIGCLIENT
spring.cloud.config.discovery.service-id=CONFIGSERVER
spring.cloud.config.discovery.enabled=true
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.config.label=master
spring.cloud.config.name=configclient
spring.cloud.config.profile=prod
#暴露web端点
management.endpoints.web.exposure.include=*
#在没有拉取到配置时,允许失败
spring.cloud.config.fail-fast=true
spring.rabbitmq.host=14.18.49.xx
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
pom.xml:
springcloud_parent_hsz
org.example
1.0-SNAPSHOT
4.0.0
springcloud_01config_client
8
8
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-bus-amqp
21 springcloudalibaba
https://spring.io/projects/spring-cloud-alibaba
- 阿里云为分布式应用开发提供了一站式解决方案。它包含了开发分布式应用程序所需的所有组件,使您可以轻松地使用springcloud开发应用程序。
- 有了阿里云,你只需要添加一些注解和少量的配置,就可以将Spring云应用连接到阿里的分布式解决方案上,用阿里中间件搭建一个分布式应用系统。
# 1. spring cloud alibaba 特点
- a.服务降级和流量控制 sentinel 替换 hystrix
- b.服务注册与发现 nacos 替换 eureka consul
- c.分布式配置& 事件驱动消息总线 nacos 替换 config & bus
- d.分布式事务&dubbo seta
# 3.springcloud 组件
- 服务注册与发现组件 eureka consul nacos
- 服务间通信组件 restTemplate+ribbon,Openfeign restTemplate+ribbon,Openfeign
- 服务降级和熔断 hystrix hystrix dashboard sentinel
- 服务网关组件 gateway gateway
- 统一配置中心组件 消息总线组件 config bus nacos
nacos_client开发:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
//@EnableDiscoveryClient //开启服务注册 consul server nacos server 这个注解可以省略不写
public class NacosClientApplication {
public static void main(String[] args) {
SpringApplication.run(NacosClientApplication.class,args);
}
}
application.properties
server.port=8989
spring.application.name=NACOSCLIENT
spring.cloud.nacos.server-addr=14.18.49.40:8848
spring.cloud.nacos.discovery.server-addr=${spring.cloud.nacos.server-addr}
spring.cloud.nacos.discovery.service=${spring.application.name}
pom.xml:
springcloudalibaba_hsz
org.example
1.0-SNAPSHOT
4.0.0
nacos_client
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
nacos_user:
package com.example.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class BeansConfig {
@Bean
@LoadBalanced //负载均衡客户端
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
package com.example.controller;
import com.example.feignclients.ProductClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class UserController {
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@Autowired
private DiscoveryClient discoveryClient;//服务发现客户端
@Autowired
private LoadBalancerClient loadBalancerClient; //负载均衡客户端组件
@Autowired
private RestTemplate restTemplate; //这个对象才是具有负载均衡restTemplate
@Autowired
private ProductClient productClient;
//推荐http rest方式通信: 1.restTemplate
@GetMapping("/invoke")
public String invokeProduct(){
log.info("调用用户服务....");
//调用商品服务 缺点: 1.无法实现请求负载均衡 2.路径写死代码中不利于后续维护
//String result = new RestTemplate().getForObject("http://localhost:9090/product?id=21", String.class);
//1.discoveryClient使用ribbon组件实现负载均衡 1.引入ribbon依赖(nacos client 存在这个依赖) 2.使用ribbon完成负载均衡 DiscoveryClient LoadBalanceClient @LoadBalance
//List serviceInstances = discoveryClient.getInstances("PRODUCTS");
//for (ServiceInstance serviceInstance : serviceInstances) {
// log.info("服务主机:{} 服务端口:{} 服务uri:{}",serviceInstance.getHost(),serviceInstance.getPort(),serviceInstance.getUri());
//}
//2.loadbalanceClient
//ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCTS"); //已经进行负载均衡之后返回节点 轮询
//String result =new RestTemplate().getForObject(serviceInstance.getUri()+"/product?id=21",String.class);
//3.@LoadBalance注解
//String result = this.restTemplate.getForObject("http://PRODUCTS/product?id=21", String.class);
String result = productClient.product(21);
log.info("商品服务调用结果: {}",result);
return result;
}
}
package com.example.feignclients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient("PRODUCTS")
public interface ProductClient {
@GetMapping("/product")
String product(@RequestParam("id") Integer id);
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients //开启openfeign调用支持
public class UsersApplication {
public static void main(String[] args) {
SpringApplication.run(UsersApplication.class,args);
}
}
application.properties:
server.port=8989
spring.application.name=USERS
spring.cloud.nacos.server-addr=14.18.49.40:8848
pom.xml:
springcloudalibaba_hsz
org.example
1.0-SNAPSHOT
4.0.0
nacos_user
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-openfeign
nacos_product(开启多个端口,生成多个服务):
package com.example.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
@Value("${server.port}")
private int port;
@GetMapping("/product")
public String product(Integer id){
log.info("id: {}",id);
return "商品服务返回: "+id+",当前提供服务端口为: "+port;
}
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProductsApplication {
public static void main(String[] args) {
SpringApplication.run(ProductsApplication.class,args);
}
}
application.properties:
server.port=9091
spring.application.name=PRODUCTS
spring.cloud.nacos.server-addr=14.18.49.40:8848
pom.xml:
springcloudalibaba_hsz
org.example
1.0-SNAPSHOT
4.0.0
nacos_product
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
nacos_config:
package com.example.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope //允许远端配置修改自动刷新
public class DemoController {
private static final Logger log = LoggerFactory.getLogger(DemoController.class);
@Value("${customer.username}")
private String username;
@GetMapping("/demo")
public String demo(){
log.info("demo ok !!!");
return "demo ok !!! username:"+ username;
}
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConfigcClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigcClientApplication.class,args);
}
}
bootstrap.properties:
spring.profiles.active=prod
#spring.profiles.active=dev
bootstrap-prod.properties:
#¸æËßconfig serverµØÖ·
spring.cloud.nacos.config.server-addr=14.18.49.40:8848
#¸æËß´ÓÄĸöÃüÃû¿Õ¼ä»ñÈ¡ÅäÖÃ
spring.cloud.nacos.config.namespace=6342def2-9fdd-4b0d-8dc9-c26e63339267
#¸æËß´ÓÄĸö×é½øÐÐÅäÖûñÈ¡
spring.cloud.nacos.config.group=DEFAULT_GROUP
# µÚ¶þÖÖ»ñÈ¡ÅäÖÃÎļþ·½Ê½ dataId = prefix + env + file-extension
#spring.cloud.nacos.config.prefix=configclient
#spring.profiles.active=prod
#spring.cloud.nacos.config.file-extension=properties
# µÚÒ»ÖÖ»ñÈ¡ÅäÖÃÎļþ·½Ê½ dataId = name + file-extension
#´ÓÕâ¸ö×éÖÐÀÈ¡ÄǸöÅäÖÃÎļþ
spring.cloud.nacos.config.name=configclient-prod
##ÀÈ¡Õâ¸öÅäÖÃÄǸöºó׺µÄÅäÖÃÎļþ
spring.cloud.nacos.config.file-extension=properties
pom.xml:
springcloudalibaba_hsz
org.example
1.0-SNAPSHOT
4.0.0
nacos_config
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
22 sentinel
官网:
https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/en-us/index.html#_how_to_use_sentinel
- sentinel提供了两个服务组件:
一个是 sentinel 用来实现微服务系统中服务熔断、降级等功能。 这点和hystrix 类似
一个是 sentinel dashboard 用来监控微服务系统中流量调用等情况 流控 熔断 降级 配置。 这点和hystrix dashboard类似
# 下载
- https://github.com/alibaba/Sentinel/releases
#启动
- 仪表盘是个jar包可以直接通过java命令启动 如: java -jar 方式运行 默认端口为 8080
- java -Dserver.port=9191 -jar sentinel-dashboard-1.7.2.jar
#访问web界面
http://14.18.49.40:9191/#/login
# 登录
- 用户名&密码: sentinel
- 流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
- 同一个资源可以创建多条限流规则。FlowSlot 会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕
- 一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
resource:资源名,即限流规则的作用对象
count: 限流阈值
grade: 限流阈值类型(QPS 或并发线程数)
limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
strategy: 调用关系限流策略 ( 直接 关联 链路
controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
- 流量控制主要有两种统计类型,一种是统计并发线程数,另外一种则是统计 QPS
- 更多细节参见官网:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
- 直接:标识流量控制规则到达阈值直接触发流量控制
- 关联: 当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为 RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。
- 链路限流: https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
- 直接拒绝:(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。
- Warm Up:(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
更多:https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8
- 匀速排队:(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。 只能对请求进行排队等待
更多:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6-%E5%8C%80%E9%80%9F%E6%8E%92%E9%98%9F%E6%A8%A1%E5%BC%8F
SentinelResource注解
https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
package com.example.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
private static final Logger log = LoggerFactory.getLogger(DemoController.class);
//demo
@GetMapping("/demo") //blockHandler 使用sentinel进行不同规则控制时的默认处理方案 fallback:自定义业务出错时默认处理方案 defaultFallback 指定一个业务错误时默认方案
@SentinelResource(value = "aaaa",blockHandler = "blockHandler",fallback = "fall",defaultFallback = "defaultFall") //作用代表这是一个sentinel资源
public String demo(Integer id) {
log.info("demo ok ....");
if (id < 0) throw new RuntimeException("id无效");
return "demo ok !!!";
}
//
public String blockHandler(Integer id, BlockException e){
if(e instanceof FlowException){
return "当前请求过于火爆,您已被流控!!!";
}
if(e instanceof DegradeException){
return "当前请求过于火爆,您已被降级!!!";
}
if(e instanceof ParamFlowException){
return "当前请求过于火爆,您已被热点参数限流!!!";
}
return "服务器快爆了,请稍后再试!!!";
}
public String fall(Integer id){
return "自定义服务器出错啦!!!";
}
public String defaultFall(){
return "默认服务器出错啦!!!";
}
@GetMapping("/test")
public String test(){
log.info("test ok ....");
return "test ok ";
}
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelApplication.class,args);
}
}
application.properties:
server.port=8998
spring.application.name=SENTINEL
# nacos server
spring.cloud.nacos.server-addr=localhost:8848
spring.cloud.sentinel.enabled=true
spring.cloud.sentinel.transport.dashboard=localhost:9191
spring.cloud.sentinel.transport.port=8719
#spring.cloud.sentinel.transport.clientIp=14.18.49.40
spring.cloud.sentinel.eager=true
pom.xml:
springcloudalibaba_hsz
org.example
1.0-SNAPSHOT
4.0.0
nacos_sentinel
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
org.springframework.boot
spring-boot-maven-plugin
2.2.5.RELEASE