本次SpringCloud学习以一个订单-支付微服务Demo工程为载体,版本如下
SpringBoot 2.2.2
SpringCloud H
SpringCloud Alibaba 2.1.0Demo工程搭建详情请看本人Gitee:https://gitee.com/wang_01zh/spring-cloud-learning-library
1. SpringCloud
1.1 服务注册与发现
1.1.2 Eureka(属于netflix 已停更)
1.1.2.1 单机版eureka
新键eureka注册中心模块
application.yml配置eureka属性
server:
port: 7001
eureka:
instance:
hostname: localhost # eureka服务端的名称
client:
register-with-eureka: false # 不用注册自己
fetch-registry: false # 检索服务,后期负载均衡要用的,说白了就是能在注册中心通过服务名找到你,eureka本身当然不需要咯
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # eureka地址
main主函数加@EnableEurekaServer
代表注册中心本身
@SpringBootApplication
@EnableEurekaServer // 表示本工程是eureka注册中心
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
配置完成后即可在localhost:7001访问eureka界面查看,虽然此时没有服务注册进来。
服务提供方注册进eureka
application.yml配置eureka属性
server:
port: 8001
spring:
application:
name: netflix-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123
eureka:
client:
register-with-eureka: true # 我要注册进eureka当然要开启了
fetch-registry: true # 检索服务,后期负载均衡要用的,说白了就是要在注册中心通过服务名找到你
service-url:
defaultZone: http://localhost:7001/eureka # 注册进eureka
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.wzh.springcloud.entites
main主函数加@EnableEurekaClient
代表eureka客户端
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
搞定,在eureka界面看到注册信息
服务消费方注册进eureka
和服务提供方一样,pom引入依赖,application.yml配置eureka
属性,主函数加@EnableEurekaClient
在eureka界面看到注册信息,服务提供方,消费方就都有了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jm9WqtWN-1625385982193)(…/…/Typora/Picture/屏幕截图 2021-06-25 160906.png)]
当然,服务调用也是依然正常~
1.1.2.2 集群eureka
配置eureka集群很简单,application.yml中相互注册。
- eureka服务端名字不能相同
- 注册地址都填对方eureka的地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qtpLLUEd-1625385982195)(…/…/Typora/Picture/屏幕截图 2021-06-25 165935.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ozSt1iIq-1625385982196)(…/…/Typora/Picture/屏幕截图 2021-06-25 170100.png)]
搞定~
服务提供方和消费方注册进eureka集群
极其简单,就是在application.yml中把所有eureka集群地址都写上去,逗号隔开。这里就不演示了。
服务提供方集群配置
服务集群配置也很简单,就是再新键模块,新服务的pom,业务这些和原服务一摸一样,只不过要修改端口号、主函数名称这些而已,然后注册进eureka就行了。
服务集群的关键问题是负载均衡问题,由于集群服务都是以相同的服务名注册进注册中心的,在注册中心同一个服务名下可能有好几个集群服务,所以这时候eureka不知道该调用哪个
所以这时候需要解决这个问题!两步:
- 服务调用方使用restTemplate时加上Ribbon的@LoadBalanced注解开启负载均衡
- 调用地址换成服务名
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
这样默认开启了轮询的负载均衡~
1.1.2.3 服务显示设置
我们在注册中心看到,注册进去的服务默认显示的是这样
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h3WQUGuv-1625385982198)(…/…/Typora/Picture/屏幕截图 2021-06-25 221106.png)]
会显示你的主机名称,并且鼠标悬浮上没有显示ip地址,但在实际开发中我们不想显示主机名称,但是想显示ip地址,所以需要在application.yml修改
eureka:
client:
register-with-eureka: true # 我要注册进eureka当然要开启了
fetch-registry: true # 检索服务,后期负载均衡要用的,说白了就是能在注册中心通过服务名找到你
service-url:
defaultZone: http://localhost:7001/eureka
instance:
instance-id: payment8001 # 在注册中心显示的名字
prefer-ip-address: true # 访问路径显示ip地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EABiCRgA-1625385982200)(…/…/Typora/Picture/屏幕截图 2021-06-25 222211.png)]
1.1.2.4 服务发现
通过主函数加
@EnableDiscoveryClient
注解,开启服务发现功能,让其他服务能获取你这个服务的信息。不管注册到哪种注册中心,为了能获取服务的信息,一般都需要加上@EnableDiscoveryClient
开启发现功能后,类中注入DiscoveryClient
接口,调用以下两个方法获取服务信息
discoveryClient.getServices();
获得注册中心上所有服务名discoveryClient.getInstances("xxx")
获得注册中心上对应服务名的每个服务,获取到每个服务之后可以进一步获取服务的名称,主机号,端口号或uri
1.1.2.6 eureka自我保护机制
如果eureka中某个服务突然不可用了,eureka不会立刻清除它 (CAP中的AP)
(好死不如赖活~)
1.1.3 Zookeeper
1.1.4 Consul(SpringCloud自己的)
Consul服务端不用我们开发,下载后直接cmd运行consul agent -dev
,访问默认8500端口
1.1.4.1 服务注册进Consul
服务开发依旧是pom,application.yml,主函数那一套
application.yml
server:
port: 8006
spring:
application:
name: consul-payment-service
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mV0d688L-1625385982201)(…/…/Typora/Picture/屏幕截图 2021-06-26 143443.png)]
1.1.5 三种注册中心比较
CAP | |
---|---|
Eureka | AP 先保证高可用,哪怕服务暂时宕掉了也不会马上删除,一般商城或社区类的服务会考虑AP,因为商城或者社区要优先保证服务高可用,数据暂时有点不一致可以接受 |
Zookeeper | CP 先保证数据一致, |
Consul | CP 先保证数据一致 |
1.2 服务调用(调用方)
1.2.1 Ribbon
1.2.1.1 Ribbon基础
Ribbon是本地负载均衡客户端,他的负载均衡+RestTemplate实现了负载均衡的服务调用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GcsRqySm-1625385982201)(…/…/Typora/Picture/屏幕截图 2021-06-26 201259.png)]
restTemplate.getForObject()
读,返回JsonrestTemplate.getForEntity()
读,返回responseEntity对象restTemplate.postForObject()
写,返回JsonrestTemplate.postForObject()
写,返回responseEntity对象
1.2.1.2 各种负载均衡算法
ribbon的负载均衡都是IRule
接口下的实现类,大概有这几种:
RoundRobinRule
默认轮询RandomRule
随机RetryRule
先轮询,如果有失败的话再重试WeightedResponseTimeRule
轮询扩展,响应速度越快权重越大BestAvailableRule
先过滤掉服务熔断的服务,再选择并发量小的服务AvailabilityFilteringRule
先过滤发生故障的服务,再选择并发量小的服务ZoneAvoidanceRule
根据服务性能等方面选择服务
1.2.1.3 更换负载均衡算法
在非主函数所在的包下@Configretion创建配置类,里面配置各种负载均衡方法bean
我这里写这么多只是为了演示,实际只写一个就好!
@Configuration
public class MyRibbonRule {
@Bean
public IRule myRule(){
return new RandomRule(); // 随机
}
@Bean
public IRule myRule2(){
return new RetryRule(); // 轮询重试
}
@Bean
public IRule myRule3(){
return new WeightedResponseTimeRule(); // 响应 权重
}
@Bean
public IRule myRule4(){
return new BestAvailableRule(); // 先过滤熔断,再选低并发
}
}
然后在主函数上通过@RibbonClient
指定使用配置的负载均衡算法
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(value = "netflix-payment-service",configuration = MyRibbonRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
1.2.1.4 手写轮询算法
1.2.2 OpenFeign
1.2.2.1 openFeign基础
Feign已经继承了Ribbon,所以调用默认是负载均衡的
首先在主函数上加上@EnableFeignClients
开启Feign功能
再在服务调用方编写Feign接口
@Component
@FeignClient("netflix-payment-service") // 表示这个是Feign接口,用来调用别的接口
public interface PaymentFeignService {
// 保持和接口提供方完全一致
@GetMapping("/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
@PostMapping("/payment/create")
CommonResult<Payment> create(@RequestBody Payment payment);
}
Feign接口写好了就可以在controller注入然后调用接口了~
1.2.2.2 接口调用超时控制
Feign调用接口时默认最长等待1s,如果超过1s会返回报错页面。
如果接口运行比较复杂确认可能会超过1s,我们可以配置Feign超时时间
在application.yml中配置
server:
port: 80
spring:
application:
name: netflix-order-feign-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
instance:
instance-id: consumer80
prefer-ip-address: true
ribbon: # 虽然是openFeign调用超时,但是因为openFeign整合了Ribbon,所以是用Ribbon属性设置
ReadTimeout: 5000 # 建立连接时间
ConnectTimeout: 5000 # 建立连接后从服务器读取可用资源时间
1.2.2.3 OpenFeign日志
通过打印调用日志监控OpenFeign接口调用情况
日志级别:
- NONE:默认不显示日志
- BASIC:调用方法、URL、响应状态码、调用时间
- HEADERS:BASIC + 请求和响应的头信息
- FULL:所有信息(HEADERS + 请求和响应的正文和元数据)
我们先通过@Configretion创建配置类,在里面配置哪种日志级别
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
再在application.yml中配置logging
logging:
level:
# 以哪个级别监控哪个Feign接口
com.wzh.springcloud.service.PaymentFeignService: debug
1.3 服务降级,服务熔断
1.3.1 Hystrix(属于netflix 已停更)
1.3.1.1 重要概念
- 服务降级
fallback
:给出错(运行时异常、超时、线程池打满)的程序提供兜底的解决方法- 服务熔断
break
:达到熔断条件后熔断服务- 服务限流
flowlimit
:对大流量做排队,每秒N个,有序进行
1.3.1.2 高并发压测
在一个微服务中,多个方法,如果某个时间点有很大流量打进某一个方法,服务的资源就会都被这个方法消耗,而其他本来响应速度很快的方法会因此受到牵连,导致响应速度变慢!
以下是压测的例子:
一个服务中的两个方法,本来第一个方法响应速度极快
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-biF0nCuQ-1625385982202)(…/…/Typora/Picture/屏幕截图 2021-06-27 132841.png)]
但是现在我们模拟两万个并发访问,全部打到第二个方法,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HoYKGJ44-1625385982203)(…/…/Typora/Picture/屏幕截图 2021-06-27 133013.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PuNxnXvW-1625385982203)(…/…/Typora/Picture/屏幕截图 2021-06-27 132953.png)]
这时候再测试第一个方法,会变得慢了起来。
我们这里虽然是在接口提供方自己服务内测试的,但是用feign调用的话也是一样的效果,也会变慢!
究其原因,是因为服务的tomcat的线程池工作线程被大量消耗。因此就需要有Hystrix这种服务保护装置。
1.3.1.3 服务降级 fallback
服务超时
运行时异常
服务宕机
断路器打开
以上4种都会触发降级!!!
服务降级既可以在服务提供方设置,也可以在调用方设置,一般是在调用方设置,但我这里全都演示
服务提供方
@EnableCircuitBreaker
主函数开启降级熔断限流功能@DefaultProperties
类上指定全局fallback兜底方法@HystrixCommand
对方法开启降级功能,没指定的话就走全局fallback,指定了就走自己指定的fallback
@Service
@DefaultProperties(defaultFallback = "global_FallbackMethod") // 全局兜底方法
public class PaymentService {
/**
* @Description: 正常方法
*/
@HystrixCommand // 开启降级功能,走全局fallback
public String paymentInfo_OK(Integer id){
return "线程池:" + Thread.currentThread().getName() + "正常方法执行";
}
/**
* @Description: 超时方法
*/
@HystrixCommand(fallbackMethod = "timeOut_FallbackMethod",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")})
// 开启降级功能,走指定fallback
// commandProperties中的@HystrixProperty表示设置限制条件,达到这个条件也会走fallback
// 比如这里设置方法执行时间不能超过5s,超过了就走fallback
public String paymentInfo_TimeOut(Integer id){
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + "超时方法执行";
}
/**
* @Description: 特定兜底方法
*/
public String timeOut_FallbackMethod(Integer id){
return "特定兜底方法,请稍后重试";
}
/**
* @Description: 全局fallback方法
*/
public String global_FallbackMethod(){
return "全局异常,请稍后重试";
}
}
服务调用方
先在application.yml配置feign开启hystrix功能
为什么在服务提供方不用开启?
是因为服务提供方只是自身内部的事,而这里调用方是通过feign去调用的,所以必须要feign支持hystrix的这些功能
feign:
hystrix:
enabled: true
然后主函数@EnableHystrix
(@EnableHystrix继承了@EnableCircuitBreaker)
最后在feign接口注解里指定fallback方法.Class
注意!这个方法就是feign接口的实现类!!而里面重写方法的方法体就是兜底的方法实现!!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Ak7ZMyj-1625385982204)(…/…/Typora/Picture/屏幕截图 2021-06-27 160031.png)]
1.3.1.4 服务熔断 break
我们给服务方法设置断路器,断路器设置熔断条件,当达到条件后断路器熔断服务,不能访问;一定时间后再自动恢复
再理解一下这个流程:
服务正常访问时无事发生~,当发生错误后服务降级,走fallback,当错误次数太多或频率太高达到熔断条件后,服务被熔断,这时候正确请求也会走fallback,当正确请求打进来一定时间后,断路器关闭,恢复访问。
例子:
当10s内的20次请求中,失败率达到50%,就从降级触发到熔断
/**
* @Description: 熔断
*/
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), // 开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "20"), // 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间范围
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50") // 失败率
})
public String paymentBreaker(Integer id){
if (id < 0){
throw new RuntimeException("id不能为负数!");
}
String uuid = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "调用成功,当前流水号:" + uuid;
}
1.3.1.5 Hystrix Dashboard
新建单独的dashboard模块,pom中一定要引入这个依赖!所有有关图形化的模块不管是监控还是被监控都要引入!
<!--图形化标配依赖!-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在application.yml中只用配一个端口号就行,主函数加上@EnableHystrixDashboard
开启仪表盘功能!
访问localhost:端口号/hystrix就能看到豪猪了
被监控的服务的主函数加上以下配置
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@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;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qfu26qAQ-1625385982204)(…/…/Typora/Picture/屏幕截图 2021-06-27 173806.png)]
注意:监控路径后面的hystrix.stream是固定写法!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GPyV40DI-1625385982205)(…/…/Typora/Picture/屏幕截图 2021-06-27 173946.png)]
1.4 服务网关
1.4.1 GateWay
1.4.1.1 三个概念
Route 路由
:由ID、URI、和一系列断言,过滤器组成,如果断言为true则匹配该路由Predicate 断言
:各种匹配规则Filter 过滤
:Spring的GateFilter的实例,可以在请求被路由前后对请求做修改
1.4.1.2 Gateway基础
网关也是一个微服务所以也需要新建模块注册进注册中心。
application.yml
server:
port: 9527
spring:
application:
name: netflix-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启动态路由,也就是根据服务名路由
routes:
- id: payment_route1 # 路由id,要唯一
uri: lb://netflix-payment-service # 断言匹配后路由的地址,lb:// 表示负载均衡
predicates:
- Path=/payment/get/** # 断言,请求路径
- id: payment_route2
uri: lb://netflix-payment-service
predicates:
- Path=/payment/create
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
- 原本请求:localhost:8001/payment/get/32
- 现在请求:localhost:9527/payment/get/32
网关经过动态路由的转发也进行了负载均衡处理,这样整个调用链路从网关—>调用方(经过网关负载均衡)—>提供方(OpenFeign负载均衡)
1.4.1.3 各种Predicate
predicates:
- Path=/payment/get/** # 断言,请求路径
- After=2021-01-12T15:45:20.485+08:00[Asia/Shanghai] # 在这个时间之后才能访问
- Before=2021-01-12T15:45:20.485+08:00[Asia/Shanghai] # 之前
- Cookie=username,wzh # 必须带这个cookie (key-value键值对)
- Method=GET # 请求方式
1.4.1.4 Filter
gateway提供的一些过滤器,使用方法和predicates一样,但是实际使用的不多,实际使用比较多的是自定义过滤器
自定义过滤器
实现一个功能:请求参数第一个必须有username
@Component
@Slf4j
public class MyGatewayLiter implements GlobalFilter, Ordered {
// exchange 既可以获取请求信息,也可以设置响应信息
// chain 负责调用链路
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String firstParam = exchange.getRequest().getQueryParams().getFirst("username");
if (firstParam != null){
log.info("非法请求,用户名为空!");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
// 加载过滤器的顺序,数字越小,优先级越高。
@Override
public int getOrder() {
return 0;
}
}
1.5 服务配置中心
1.5.1 Config
配置中心作用是,把多个服务的公共配置放到Git的仓库上,config服务端连接到这个仓库,其他客户端服务连接服务端,从而间接的读取到自己想要的配置文件
1.5.1.1 Config服务端
先在Gitee上建一个仓库来存放一些配置(顺便复习一下git)
- 首先gitee建好仓库
- 本地创建文件夹,进入这个文件夹,
git init
用这个文件夹作为本地仓库 git remote add origin xxx
本地仓库和git上的仓库远程连接,xxx是仓库地址touch xxx.yml
创建一个文件git add xxx.yml
将这个文件存到暂存区git commit -m "xx"
将这个文件提交到本地仓库,xxx是提交说明git push -u origin master
将文件提交到远程仓库
这样远程和本地仓库就已经统一,然后我们再新建配置中心模块
主函数加上@EnableConfigServer
代表这是个config服务端
application.yml
server:
port: 3344
spring:
application:
name: config-center
cloud:
config:
server:
git:
uri: https://gitee.com/wang_01zh/spring-cloud-config.git # 连接gitee上的仓库
search-paths: SpringCloud-Config # 仓库名
label: master # 分支
eureka:
client:
service-url: http://localhost:7001/eureka
模块连接到gitee上的仓库
可以通过以下连接查看配置文件
master:分支; config-dev.yml:配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1T6roLnf-1625385982205)(…/…/Typora/Picture/屏幕截图 2021-06-28 175946.png)]
1.5.1.2 Config客户端
config客户端模块通过连接config服务端,间接的读取到Git上的配置文件。
注意:这里客户端配置文件需要用bootstrap.yml。bootstrap.yml优先级高于application.yml,所以是先加载bootstrap.yml,再加载application.yml
bootstrap.yml
server:
port: 3355
spring:
application:
name: config-client
cloud:
config:
label: master # 分支
name: config # 要读取哪个配置文件 的名字
profile: dev # 后缀
uri: http://localhost:3344 # 读取配置中心模块
# 连接 http://localhost:3344 这个服务端,通过服务端读取Git上的master分支下的config-dev.yml文件
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
所以配置中心整个流程是:
服务端连接Git上的仓库,客户端连接服务端,从而可以选择读取仓库中的某个配置文件
1.5.1.3 动态刷新
以上配置中心存在一个问题:当Git上的配置文件内容修改后,config服务端能自动接收,但config客户端不能自动接收,必须要重启服务才行
1.6 Bus 消息总线
用来解决上面配置动态刷新的问题
1.6.1 设计思想、选型
消息总线的设计思想是由Bus通知config服务端,由服务端通过RabbitMQ再广播给客户端
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kYySBCaW-1625385982206)(…/…/Typora/Picture/屏幕截图 2021-06-29 104729.png)]
1.6.2 全局广播
config服务端:RabbitMQ和暴露端点配置
server:
port: 3344
spring:
application:
name: config-center
cloud:
config:
server:
git:
uri: https://gitee.com/wang_01zh/spring-cloud-config.git # 连接gitee上的仓库
search-paths:
- SpringCloud-Config # 仓库名
username: 17620593885
password: wswzh199553
label: master # 读取分支
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
config客户端:RabbitMQ和暴露端点配置
server:
port: 3355
spring:
application:
name: config-client-3355
cloud:
config:
label: master # 分支
name: config # 要读取哪个配置文件 的名字
profile: dev # 后缀
uri: http://localhost:3344 # 读取配置中心模块
# 连接 http://localhost:3344 这个服务端,通过服务端读取Git上的master分支下的config-dev.yml文件
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
注意,此时如果controller类中注入了从Git上读取的配置的话,需要在controller上加上
@RefreshScope
注解
原理:config客户端都会监听MQ中springCloudBus这个topic,当数据刷新的时候会把这个信息放入到这个topic中,这样监听同一个topic的服务就都能得到通知
1.7 Stream 消息驱动
1.7.1 消息驱动是什么
微服务中可能存在各种消息中间件:RabbitMQ,ActiveMQ,Kafka等等,为了能统一封装消息中间件,节约学习成本,出现了Stream消息驱动
1.7.2 消息生产者
application.yml
server:
port: 8801
spring:
application:
name: stream-provider
cloud:
stream:
binders: # 配置rabbit信息
defaultRabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 消息整合处理
output: # 我是个生产者
destination: studyExchange # 交换机名字
content-type: application/json # 消息类型,这次是json,如果是文本就写成“text-plain”
binder: defaultRabbit
eureka:
client:
service-url:
defaultZone: http://localhost:7001.eureka
instance:
lease-expiration-duration-in-seconds: 5
lease-renewal-interval-in-seconds: 2
instance-id: send-8001
prefer-ip-address: true
先不学这个玩意先。。
2. Cloud Alibaba
2.1 Nacos
下载后启动nacos,访问8848端口,即可看到nacos
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imTdjU2L-1625385982206)(…/…/Typora/Picture/屏幕截图 2021-06-29 152353.png)]
2.1.1 注册中心
2.1.1.1 配置服务提供方集群
主函数
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderMain9002 {
public static void main(String[] args) {
SpringApplication.run(ProviderMain9002.class,args);
}
}
application.yml
server:
port: 9001
spring:
application:
name: NACOS-PROVIDER
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
management:
endpoints:
web:
exposure:
include: "*"
以上配置好了一个服务,再配置集群也就是再新建模块配置不同的端口号就好了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-okGTB4VQ-1625385982206)(…/…/Typora/Picture/屏幕截图 2021-06-29 155616.png)]
2.1.1.2 配置服务调用方
application.yml
server:
port: 83
spring:
application:
name: NACOS-CONSUMER-83
cloud:
nacos:
discovery:
server-addr: localhost:8848
想要测试服务调用再整合openFeign即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ODvZmwAV-1625385982207)(…/…/Typora/Picture/屏幕截图 2021-06-29 162951.png)]
2.1.2 配置中心
nacos就自带配置中心功能,也就是说nacos = Git+config服务端,配置文件直接放到nacos上,我们自己的服务直接连接到nacos就能读取到配置文件,并且还自带动态刷新!
2.1.2.1 基础
客户端要能读取到nacos上的配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3tAbD5rj-1625385982207)(…/…/Typora/Picture/屏幕截图 2021-06-29 170730.png)]
nacos配置文件命名规则
${服务名}-${激活环境}.${文件格式}
也就是说我现在nacos上针对这个服务可能有很多环境的配置文件比如:
CONFIG-CLIENT-dev.yaml
CONFIG-CLIENT-test.yaml
CONFIG-CLIENT-prod.yaml而我现在读取的是dev这个环境的配置
注意:如果要使用配置文件的属性的话记得在controller上加上@RefreshScope
支持动态刷新
2.1.2.2 分类管理
命名空间>Group>Data Id
以下例子:
按命名空间分为前台后台两大模块,按Group分为支付服务和订单服务,每个服务里又有不同环境的配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nWNXyyS4-1625385982208)(…/…/Typora/Picture/屏幕截图 2021-06-29 211854.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H0McBWhH-1625385982208)(…/…/Typora/Picture/屏幕截图 2021-06-29 211917.png)]
bootstrap.yml
server:
port: 3377
spring:
application:
name: CONFIG-CLIENT
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848 # 配置中心地址
file-extension: yaml # yml格式配置
group: PAYMENT_GROUP
namespace: d6f0391f-ee5d-4609-98d2-db268957b425
2.1.2.3 nacos集群和持久化
【持久化】
nacos为什么能存这些配置文件,并且重登之后还在,是因为nacos内部维护了一个数据库derby,这些文件都存到derby了,但是如果要做nacos集群的话,每个nacos都维护一个自己的数据库,这样会产生数据一致性的问题,所以我们要把derby替换成mysql数据库,nacos集群共享mysql数据库,然后mysql再做集群
【持久化步骤】
- 找到nacos文件夹下的
nacos-mysql.sql
文件,把里面的sql执行到本机mysql中 - 打开nacos文件夹下的
application.properties
文件,增加以下mysql支持
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123
2.2 Sentinel
2.2.1 sentinel基础
sentinel相对于hystrix就相当于nacos相对于eureka,sentinel自己有监控服务台,不需要我们新建模块。我们下载sentinel的jar包,运行起来就可以在localhost:8080访问到sentinel服务台了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pruTSf3i-1625385982209)(…/…/Typora/Picture/屏幕截图 2021-06-30 120909.png)]
2.2.2 监控
服务想要被sentinel监控,需要在配置文件中配置sentinel
server:
port: 8401
spring:
application:
name: SENTINEL-SERVICE
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 # 监控服务台地址
port: 8719 # 与sentinel服务端交互
management:
endpoints:
web:
exposure:
include: "*"
配置完成即可被sentinel监控。由于sentinel是懒加载,所以需要先访问一次服务的方法才能显示监控信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xlyGhgZr-1625385982209)(…/…/Typora/Picture/屏幕截图 2021-06-30 142529.png)]
2.2.3 服务流控
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-McOE2xcs-1625385982210)(…/…/Typora/Picture/屏幕截图 2021-06-30 145632.png)]
可以根据访问的QPS或者线程数来设置
流控模式:
- 直接:达到阈值后直接决绝访问
- 关联:关联资源达到这里设置的阈值后,本资源拒绝访问
- 链路:
流控效果:
- 快速失败:直接失败
- Warm Up:预热,QPS默认从3,经过预热时长升至设置的阈值
- 排队等待:超过阈值的请求会排队
2.2.4 服务降级
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4oeXidmR-1625385982210)(…/…/Typora/Picture/屏幕截图 2021-06-30 154348.png)]
降级策略:
- RT:前提条件QPS>5,意思是在每秒请求超过5个的情况下,每次请求处理时间如果超过200ms,断路器就工作
- 异常比例:前提条件QPS>5,意思是每秒请求超过5个的情况下,请求发生异常的比例烧超过多少,断路器就工作。这里比例范围是0-1.0,20%就是0.2
- 异常数:一分钟内,发生异常的次数
2.2.5 热点key限流
先开发一个存在热点key的方法:
@GetMapping("/testHotKey")
// 这里SentinelResource的value的值只是代表这是个资源存在sentinel,也就是说这个方法目前在sentinel有两个资源名,
// 一个是rest路径"/testHotKey";另一个就是这个"testHotKey"
// blockHandler指定sentinel兜底方法
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
return "正常执行";
}
public String deal_testHotKey(String p1, String p2, BlockException exception){
return "兜底方法";
}
再对其中的某个热点参数做限流,参数索引从0开始,意思是:p1这个参数QPS超过100就走blockHandler指定的兜底方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D3FCVf34-1625385982211)(…/…/Typora/Picture/屏幕截图 2021-06-30 162506.png)]
【特殊情况】
下面配置意思是:p1在普通情况下,QPS不能高于1,但是加入p1等于5或者10,那他的QPS就变成不能高于200了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y38rrV1G-1625385982212)(…/…/Typora/Picture/屏幕截图 2021-06-30 165810.png)]
2.2.6 @SentinelResource
@SentinelResource
blockHandler
负责sentinel监控方面的异常,比如限流,降级热点key等等
fallBack
负责java代码运行时异常,比如代码写错,空指针异常等等如果方法既存在运行时异常又存在sentinel监控异常,优先走blockHandler
如果想忽略某个异常,可以用
exceptionToIgnore={xxxException.class}
调用方记得在配置文件加上
Feign开启sentinel支持
feign:
sentinel:
enabled: true
2.2.7 snetinel持久化
为了能把sentinel配置持久化,我们把sentinel配置存到nacos,在服务配置文件中:
port: 8719
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
然后在nacos配置列表中增加配置,Data Id用上面配置文件中的dataId其实也就是服务名,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FSY0IeVI-1625385982212)(…/…/Typora/Picture/屏幕截图 2021-07-01 131140.png)]
- resource:资源名
- limitApp:来源应用(直接默认)
- grade:阈值类型,0表示线程数,1表示QPS
- strategy:流控模式,0表示直接,1表示关联,2表示链路
- controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
- clusterMode:是否集群
2.3 Seata 分布式事务
2.3.1 一个Id,三个概念
-
Tranction ID XID:全局事务ID
-
TC 事务协调器:维护全局事务提交,回滚
-
TM 事务管理器:开启全局事务,发起全局事务提交,回滚的决议
-
RM 资源管理器:控制分支事务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-998vv3h1-1625385982212)(…/…/Typora/Picture/屏幕截图 2021-07-01 133624.png)]
大概流程:
- @GlobalTranctional标注的方法作为TM向TC申请开启一个全局事务,并拿到对应的全局事务ID
- XID会在整条调用链路传播
- 整个链路上可能调用了很多服务,各自服务的RM向TC注册本地分支事务,并且这些事务都属于XID
- RM在执行本地业务的时候会在业务前后生成前置和后置的快照用来保存前后状态,如果需要回滚本地事务就会先比较当前数据和后置快照的数据,如果相同说明没有脏写,然后查询前置快照恢复到之前的数据
- TM最终向TC发起XID的事务提交或回滚决定
- TC将XID下的所有分支事务提交或回滚