Ribbon 是微服务架构图中负责负载均衡的 组件。
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
测试如下:
//微服务方式 Ribbon方式
@GetMapping("ribbon/{name}")
public String RibbonTest(@PathVariable String name) {
// ip和域名 更换成 微服务名称
String url = "http://user-service/hello?name="+name;
String forObject = restTemplate.getForObject(url, String.class);
return forObject;
}
Hystrix 熔断:
1、熔断器:当某个服务的失败率达到一定阈值时,Hystrix会打开熔断器,停止对该服务的调用,从而防止故障进一步扩散。熔断器开启后,Hystrix会快速失败并返回备选结果或降级数据,以提高系统的响应速度。
2、资源隔离:Hystrix可以对调用远程服务的线程池进行资源隔离,限制调用分布式服务的资源使用。这样,即使某个服务出现故障,也不会影响其他服务的调用。
3、服务降级:当服务调用失败或资源不足时,Hystrix可以进行降级处理。降级后可以配合降级接口返回托底数据,以保证系统的正常运行。
4、缓存:Hystrix提供了请求缓存和请求合并的实现。通过缓存请求的结果,可以减少对后端服务的调用,提高系统的性能。同时,通过将多个相同类型的请求合并为一个批量请求,可以减少网络开销。
5、监控和告警:Hystrix可以监控服务的调用情况,如调用次数、成功率、响应时间等,并提供告警功能。这样,开发人员可以及时发现并处理潜在的问题。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在application中加入 @EnableCircuitBreaker注解
@EnableDiscoveryClient
@SpringBootApplication
@EnableCircuitBreaker //开启熔断 hytrix
public class ClientServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ClientServiceApplication.class, args);
}
使用方式如下:
方案一: 方法级别的指定
@GetMapping("ribbon/{name}")
@HystrixCommand(fallbackMethod = "ribbonTestFail")
public String RibbonTest(@PathVariable String name) {
// ip和域名 更换成 微服务名称
String url = "http://user-service/hello?name="+name;
String forObject = restTemplate.getForObject(url, String.class);
return forObject;
}
// 服务降级处理 配合使用 @HystrixCommand(fallbackMethod = "ribbonTestFail") 入参出入要一和对应方法一直
public String ribbonTestFail(String name){
return "请求失败";
}
方案二 类级别方案的指定:
@RestController
@RequestMapping("client")
@DefaultProperties(defaultFallback = "TestControllerRequestFail") //指定访问失败时的方法
public class TestController {
//微服务方式 Ribbon方式
@GetMapping("ribbon/{name}")
@HystrixCommand
public String RibbonTest(@PathVariable String name) {
// ip和域名 更换成 微服务名称
String url = "http://user-service/hello?name="+name;
String forObject = restTemplate.getForObject(url, String.class);
return forObject;
}
//统一处理,配合 @DefaultProperties(defaultFallback = "TestControllerRequestFail") 来使用
public String TestControllerRequestFail(){
return "Controller 请求失败";
}
yml 配置:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
circuitBreaker:
errorThresholdPercentage: 50 #触发熔断错误比例阈值,默认值50号
sleepWindowInMilliseconds: 10000 #熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 #熔断触发最小请求次数,默认值是20
Feign
是一个声明式的 HTTP 客户端,它使得编写 HTTP 客户端变得更简单,通过注解和接口的方式定义和调用远程服务。
pom 加入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
最佳实践:
创建一个 xxx-api模块。(Feign 模块由被调用端编写)
<groupId>com.zll</groupId>
<artifactId>user-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.5</version>
</dependency>
</dependencies>
</project>
用于单独存放,Feign 接口,以及 出入参对象。
@Data
public class User {
private String name;
private Integer age;
}
@FeignClient("user-service")
public interface UserControllerApi {
@GetMapping("/hello")
public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name);
@PostMapping("/save_user")
public String saveUser(User u);
}
被调用端:
user-service:
加入pom
<dependency>
<groupId>com.zll</groupId>
<artifactId>user-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
使用API中的入参出参
@Controller
public class UserController {
@GetMapping("/hello")
@ResponseBody
public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
return "Hello " + name;
}
@PostMapping("/save_user")
@ResponseBody
public String saveUser(User u) {
return "user will save: name=" + u.getName() + ", age=" + u.getAge();
}
调用端:
第一步:加入pom
<dependency>
<groupId>com.zll</groupId>
<artifactId>user-api</artifactId>
</dependency>
第二步:开启feign 扫描
@EnableFeignClients(basePackages={"com.zll.api"}) //开启feign功能 并且加入xxx-api的扫描包路径
@SpringBootApplication(basePackages={"com.zll.client","com.zll.api"})
public class ClientServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ClientServiceApplication.class, args);
}
第三步:
public class ClientUserController {
//引入xxx-api的接口对象
@Resource
UserControllerApi userControllerApi;
@GetMapping("/hello")
public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name){
//像调用方法一下,调用请求接口
String hello = userControllerApi.hello(name);
return hello;
}
@PostMapping("/save_user")
public String saveUser(User u){
//像调用方法一下,调用请求接口
String s = userControllerApi.saveUser(u);
return s;
}
注意,使用该种模式,就不支持服务降级处理。
Gateway
创建一个新Module
GateWayApplication
@EnableDiscoveryClient
@SpringBootApplication
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}
pom 部分代码
<parent>
<groupId>com.zll</groupId>
<artifactId>zll-springcloud-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
yml
server:
port: 9000
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service-route #路由id 随意编写
uri: http://127.0.0.1:9092 #代理服务器地址
predicates: #路由断言: 可以匹配映射路径
- Path=/client/**
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka
#========================选配内容 更具需要来定===================
instance:
# 服务续约时间 默认30秒
lease-renewal-interval-in-seconds: 5
# 服务时效时间 默认90秒
lease-expiration-duration-in-seconds: 5
# 优先使用ip
prefer-ip-address: true
#============================================================
gateway网关的规则:
添加路径
所有路径加前缀:
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service-route #路由id 随意编写
uri: lb://client-service #微服务服务注册名称。lb 表示loadBalancer
predicates:
- Path=/**
filters:
- PrefixPath=/client #在路径前面添加 /client
实际下过如下:例如:
访问路径: localhost:9000/hello
实际路径:localhost:9000/client/hello
删除路径
gateway:
routes:
- id: user-service-route #路由id 随意编写
uri: lb://client-service #微服务服务注册名称。lb 表示loadBalancer
predicates:
- Path=/api/client/**
filters:
- StripPrefix=1 #1表示删除一个路径 2表示两个路径
# - PrefixPath=/client #在路径前面添加 /client
示范入戏
访问路径:http://localhost:9000/api/client/hello
实际路径:http://localhost:9000/client/hello
gateway自定义局部拦截器:
第一步在:yml添加拦截器名称和要拦截的参数 拦截器名称 为 MyParam 拦截参数 name
第二步:创建 MyParam拦截器:MyParam + GatewayFilterFactory = MyParamGatewayFilterFactory 前缀 MyParam +固定命名方式,否则会报错Unable to find GatewayFilterFactory with name MyParam
第三步:
/**
* 局部拦截,需要在yml 的fiter 配置
* spring:
* cloud:
* gateway:
* routes:
* uri: lb://client-service #微服务服务注册名称。lb 表示loadBalancer
* predicates:
* - Path=/client/**
* filters:
* - MyParam=name
*/
@Component
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {
public MyParamGatewayFilterFactory() {
super(Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("param");
}
public static class Config{
private String param;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
@Override
public GatewayFilter apply(Config config) {
return ((exchange, chain) ->{
ServerHttpRequest request = exchange.getRequest();
if(request.getQueryParams().containsKey(config.param)){
request.getQueryParams().get(config.param).forEach(item-> System.out.printf("%s==%s",config.param,item));
}
return chain.filter(exchange);
});
}
}
好了,局部拦截器完成。
gateway全局拦截器:
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("======================MyGlobalFilter====================");
// String token = exchange.getRequest().getQueryParams().getFirst("name");
String token = exchange.getRequest().getHeaders().getFirst("Token");
if(StringUtils.isBlank(token)){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
//值越小,执行优先级越高。
return 100;
}
}
gateway跨域解决:
微服务内部,使用feign 外部调用gateway 就用nginx
配置中心
pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
第二步:
在git 创建一个仓库,任意分支创建一个 user-dev.yml 文件,这里以 master 分支为例
user-dev.yml 中随便写你想要的配置
例如:
server:
port: 9091
spring:
application:
name: user-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka
回到项目的yml中:
配置git 地址
server:
port: 10000
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/leonzhang2013/configuration-center.git #这里写你自己的git 地址
default-label: master #不写默认值,config会报NoSuchLabelException: No such label: main。
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka
项目中开启配置功能:
最后启动项目:
访问你编写的git 文件,例如:
其他项目读取配置:
添加配置pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
如果报错:
就再加入下面的包:(新版本这个包被排除了,需要添加这包)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
更换启动文件:
applicaton.yml ⇒ bootstrap.yml
spring:
cloud:
config:
name: user # 要与仓库中配置的profile保持一致 例如 user-dev.yml
profile: dev # 要与仓库中配置的profile保持一致 例如 user-dev.yml
label: master #分支名称
discovery:
enabled: true
service-id: config-server #配置中心的名称
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka
启动配置中心和注册中心服务,最后启动项目,就可以完美读到配置了。项目运行正常
user-server 读到配置,端口为配置的9091
实时动态同步配置(无需重启服务):spring cloud bus
第一步: 安装RabbitMQ
需要配合 RabbitMQ服务使用 ( RabbitMQ for Window 安装指南)
第二步:Config-server服务 添加 actuator 和 rabbit库
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
第三步:Config-server 配置完成
添加 rabbit 配置和actutor 的 management 的路径解封。不然访问不到
server:
port: 10000
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/leonzhang2013/configuration-center.git
default-label: master
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
management:
endpoints:
web:
exposure:
include: "*"
配置使用端 user-server
第一步:添加 pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
第二步:添加rabbit 配置 applition.yml 中:
第三步:添加@RefreshScope 属性注解,表示该类,中的属性需要动态刷新(test.name)
测试:
启动 注册中心,config-server ,user-server 三个服务。
1、修改git 上的配置:例如 test.name =lisi 修改为 zhangsan
2、访问 user-servier 服务中的 测试链接 localhost:9091 查看数据,此时数据应该是 lisi
3、通过postman 访问 config-server 的刷新配置路径 http://localhost:10000/actuator/busrefresh
4、再次访问 localhost:9091 查看数据,此时数据应该是 zhangsan 测试结束。
关于第三步,老版本的是 bus-refresh 新版是 busrefresh
具体是那个可以访问:http://localhost:10000/actuator/mappings 去搜索一下 refresh 看看你的路径是那个。