服务注册中心
Eureka(停更)、zookeeper、consul、nacos
步骤
- 修改POM
- application
- 启动类加注解
Eureka(服务注册与发现)
在项目中加入服务端依赖,就可以作为服务端使用
server
- 创建项目
- 修改pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--服务信息监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--新版-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 修改application.yml
server:
port: 7001
eureka:
instance:
hostname: localhost #服务端实例名称
client:
register-with-eureka: false #false不想注册中心注册自己
fetch-registry: false #false 表示自己就是注册中心
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 启动类添加注解 @EnableEurekaServer
client
- 修改pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
- application.yml
eureka:
client:
register-with-eureka: true #true 将自己注册到eurekaServer,默认true
fetch-registry: true #true 从eurkaServer抓取已有注册信息,默认true。集群模式必须设置为true配合Ribbon使用负载均衡
service-url:
defaultZone: http://localhost:7001/eureka
- 启动类添加注解@EnableEurekaClient
集群模式服务互相注册 service-url写其他服务的地址
RestTemplate
集群模式,配置文件中 fetch-registry要设置为true
eureka:
client:
fetch-registry: true #从eurkaServer抓取已有注册信息
restTemplate要实现负载均衡
访问路径要通过注册到eurkaServer的应用名称访问
Actuator(监控和管理应用)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
启动8080项目 访问 http://localhost:8080/actuator/health:
{“status”:“UP”}
端口 | 描述 |
---|---|
auditevents | 显示应用暴露的审计事件 (比如认证进入、订单失败) |
info | 显示应用的基本信息 |
health | 显示应用的健康状态 |
metrics | 显示应用多样的度量信息 |
loggers | 显示和修改配置的loggers |
logfile | 返回log file中的内容(如果logging.file或者logging.path被设置) |
httptrace | 显示HTTP足迹,最近100个HTTP request/repsponse |
env | 显示当前的环境特性 |
flyway | 显示数据库迁移路径的详细信息 |
liquidbase | 显示Liquibase 数据库迁移的纤细信息 |
shutdown | 让你逐步关闭应用 |
mappings | 显示所有的@RequestMapping路径 |
scheduledtasks | 显示应用中的调度任务 |
threaddump | 执行一个线程dump |
heapdump | 返回一个GZip压缩的JVM堆dump |
@EnableDiscoveryClient(服务发现)
用法和@EnableEurekaClient基本一致,
如果选用的注册中心是eureka,那么就推荐@EnableEurekaClient,
如果是其他的注册中心,那么推荐使用@EnableDiscoveryClient
- 启动类上添加注解@EnableDiscoveryClient
- 注入DiscoveryClient
@Resource private DiscoveryClient discouveryClient;
- 通过DiscoveryClient可以得到微服务的各种信息
zookeeper
zookeeper就是服务端,所以要安装zookeeper
客户端都注册到zookeeper,进行互相调用,客户端配置如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!-- 如果包冲突,删除自带的zookeeper,然后在手动导入相同版本的
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
-->
</dependency>
启动类添加 @EnableDiscoveryClient
server:
port: 8004
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 192.168.206.128:2181
Consul(分布式服务发现和配置管理系统)
要独立安装Consul服务器
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
spring:
application:
name: consul-provider-payment
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
server:
port: 8006
通过服务调用去调用不同服务的端口。
服务调用
Ribbon
负载均衡+RestTemplate调用
负载均衡替换规则
Ribbon自定义配置类不可以@ComponentScan所扫描的包下,否则会被所有的客户端共享,不能达到定制效果。
@SpringBootApplication注解包含@ComponentScan注解,所以自定义的配置类不能放到启动类的同级目录及以下
策略 | 描述 |
---|---|
RoundRobinRule | 轮询,依次执行每个执行一次(默认) |
RandomRule | 随机 |
BestAvailableRule | 先过滤掉故障实例,再选择并发较小的实例 |
WeightedResponseTimeRule | 响应时间越快服务权重越大,容易被选中的概率就越高 |
RetryRule | 先按照RoundRobinRule(轮询)的策略获取服务,如果获取的服务失败在指定的时间会进行重试,进行获取可用的服务 |
AvailabilityFilteringRule | 先过滤掉多次访问故障而处于断路器跳闸状态的服务,对剩余的服务列表安装轮询策略进行访问 |
ZoneAvoidanceRule | 默认规则,复合判断Server所在区域的性能和Server的可用性选择服务器 |
自定义规则
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();
}
}
启动类添加@RibbonClient注解
@RibbonClient( name = "要调用的服务名称" , configuration = MySelfRule.class)
OpenFeign
声明式的web服务客户端。只需创建一个接口,并在接口上添加注解即可。
第一步
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步
主启动类添加注解@EnableFeignClients
第三步
编写服务调用接口,添加@FeignClient
@Component
@FeignClient( value = "CLOUD-SERVICE")
public interface FeignService{
@GetMapping(value = "/test/{id}")
Result<T> getById(@PathVariable("id") Long id);
}
OpenFeign自带负载均衡
OpenFeign默认等待1秒钟
ribbon:
# 建立连接所用时间 5s
ReadTimeout: 5000
# 读取资源所用时间 5s
ConnectTimeout: 5000
OpenFeign日志增强
日志级别
NONE:默认的,不显示任何日志
BASIC:仅记录请求方法、url、响应状态码及执行时间
HEADERS:除BASIC内容外,还有请求和响应的头信息
FULL:除HEADERS内容外,还有请求和响应的正文及元数据
配置日志bean
@Configuration
public class FeignConfig{
@Bean
Logger.Level feignLoggerLevel(){
// feign.Logger
return Logger.Level.FULL;
}
}
配置文件
logging:
level:
# feign日志要监控的接口和级别
com.springcloud.service.PaymentFeignService: debug
服务降级
Hystrix
- 服务降级(fallback):
不让客户端等待,并立刻返回一个友好提示。
例如:程序运行异常、超时、服务熔断触发服务降级、线程池/信号量打满等 - 服务熔断(break):
- 服务限流(flowlimit):
服务降级
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
@HystrixCommand(fallbackMethod = "timeOut",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String payment(){
return null;
}
public String timeOut(){
return null;
}
@fallbackMethod:服务降级调用的方法
@commandProperties:触发降级方法的条件
@HystrixProperty:具体属性,可以从HystrixCommandProperties类中查看源码
启动类添加:@EnableCircuitBreaker 启动
feign:
hystrix:
enabled: true
通配服务降级
在FeignClient标明回退的类
@FeignClient (value = "eureka-client-user-service", fallback = UserRemoteClientFallback.class)
public interface UserRemoteClient {
@GetMapping("/user/hello")
String hello();
}
回退的类要实现上FeignClient的类
@Component
public class UserRemoteClientFallback implements UserRemoteClient {
@Override
public String hello() {
return "fail";
}
}
断路器
在@HystrixProperty配置参数
重要的参数有三个
- 快照式按键窗口:统计时间范围
- 请求总数阀值:统计时间内的最大请求次数
- 错误百分比阀值:错误百分比
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),//开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),//请求次数超过10,启动断路器
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),//统计时间
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")//失败率超过60跳闸
熔断后正确请求也不会正常转发,在一定时间后才会正常。
hystrix图形化监控
创建新的项目
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
启用注解**@EnableHystrixDashboard**
凡是要显示图形化接面都要导入这两个包,包括被监控项目
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency
http://localhost:8080/hystrix
被监控项目主启动类
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args){
SpringApplication.run(PaymentHystrixMain8001.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;
}
}
输入被监控的项目地址
http://localhost:8001/hystrix.stream
服务网关
GateWay
三大核心内容
- Route(路由):构建网关的基本模块。由ID,目标URI,一系列的断言和过滤器组成,如果断言为true,则匹配该路由。
- Predicate(断言):开发人员可以匹配HTTP请求中的所有内容,如果请求和断言想匹配则进行路由
- Filter(过滤器):GatewayFilter的实例
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
网关作为微服务也要注册到服务注册中心
网关路由配置:
通过配置文件
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名称进行路由
routes:
- id: payment_route # 路由的id,没有规定规则但要求唯一,建议配合服务名
#匹配后提供服务的路由地址
#uri: http://localhost:8001
# lb代表从注册中心获取服务,也就是动态路由配置
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_route # 路由的id,没有规定规则但要求唯一,建议配合服务名
#匹配后提供服务的路由地址
uri: http://news.baidu.com
predicates:
- Path=/mil # 断言,路径相匹配的进行路由
通过代码
@Configuration
public class GatewayConfig {
/**
* 配置了一个id为route-name的路由规则,原则上要不相同,但是相同没有报错,暂不知原因
*
* 当访问地址 http://localhost:9527/guonei时会自动转发到地址: http://news.baidu.com/guonei
* @param builder
* @return
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_route_guonei",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
routes.route("path_route_guoji",
r -> r.path("/guoji")
.uri("http://news.baidu.com/guoji")).build();
return routes.build();
}
}
Predicate断言
// 默认时区
ZonedDateTime now = ZonedDateTime.now();
filter过滤器
自定义全局过滤器
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono< Void > filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("****** come in MyLogGateWayFilter: " + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname == null) {
log.info("*****用户名为null,非法用户,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 加载过滤器的顺序,数字越小,优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
配置中心
Config
服务端,从github读取所有配置
@EnableConfigServer
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
#Github上的git仓库名字
uri: https://github.com/Aizen-Sousuke/my-config.git
#搜索目录.
search-paths:
- my-config
#选择要读取的分支
label: master
启动项目后通过一下规则可以读取到配置信息
http://localhost:8080/分支/配置文件名
客户端,从服务段读取配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
application.yml 用户级资源配置项
bootstrap.yml 系统级,优先级更高
bootstrap.yml
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上诉3个综合就是 master分支上 config-dev.yml
uri: http://localhost:8080
读取config-dev.yml的内容
config-dev.yml
server:
port: 4399
@RestController
public class ConfigClientController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/configInfo")
public String getConfigInfo() {
return "serverPort: " + serverPort;
}
}
config客户端手动动态刷新
客户端依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
#暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/configInfo")
public String getConfigInfo() {
return "serverPort: " + serverPort;
}
}
这些改完之后,仍然不能实现github上边修改内容,config客户端及时读取新内容
需要访问客户端刷新,每次修改github上内容后,在命令行输入:
curl -X POST “http://localhost:8081/actuator/refresh”
需要每个客户端发送一次
消息总线
Bus配合config实现消息动态刷新全局广播配置
Bus
只支持RabbitMQ和Kafka
config的服务端和客户端都要添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
添加mq的配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#rabbitmq相关设置 ,暴露 bus刷新配置的端点
management:
endpoints:
web:
exposure:
include: 'bus-refresh' #服务端要指定bus-refresh,客户端都还用*
github上配置文件修改后,只需要config服务端刷新
curl -X POST “http://localhost:8080/actuator/bus-refresh”
如果需要定点刷新,只需要指明要刷新的服务名称加端口号
curl -X POST “http://localhost:8080/actuator/bus-refresh/config-client:8081”
消息驱动
Stream
屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
组成 | 说明 |
---|---|
Middleware | 中间件,目前只支持RabbitMQ和Kafka |
Binder | 应用与消息中间件的连接的封装 |
@Input | 输入通道,接收消息进入应用程序 |
@Output | 输出通道,发布的消息离开应用程序 |
@StreamListener | 监听队列,接收消息 |
@EnableBinding | 绑定channel和exchange |
使用rabbit
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
发送端,生产者
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: #在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: #设置rabbitmq的相关环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
output: #发送**************
destination: studyExchange #定义的Exchange名称
content-type: application/json #设置消息类型,本次为json,本文要设置为“text/plain”
binder: defaultRabbit #设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30S)
lease-expiration-duration-in-seconds: 5 #如果超过5S间隔就注销节点 默认是90s
instance-id: send-8801.com #在信息列表时显示主机名称
prefer-ip-address: true #访问的路径变为IP地址
service
public interface IMessageProvider {
public String send();
}
impl
/**
*这个调用的是mq的,所以要添加mq的注解@EnableBinding而不是@service
*这里的内容都是stream的
*/
@EnableBinding(Source.class) //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output; // 消息发送管道
@Override
public String send() {
String serial = UUID.randomUUID().toString();//发送的内容
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****serial: " +serial);
return null;
}
}
controller
@RestController
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;
@GetMapping("/sendMessage")
public String sendMessage() {
return messageProvider.send();
}
}
接收端,消费者
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: #在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: #设置rabbitmq的相关环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
input: #接收**************
destination: studyExchange #表示要使用的Exchange名称定义
content-type: application/json #设置消息类型,本次为json,本文要设置为“text/plain”
binder: defaultRabbit #设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30S)
lease-expiration-duration-in-seconds: 5 #如果超过5S间隔就注销节点 默认是90s
instance-id: receive-8802.com #在信息列表时显示主机名称
prefer-ip-address: true #访问的路径变为IP地址
controller
/**
*这个controller是消息处理的controller,并不是controller层
*/
@Component
@EnableBinding(Sink.class)
public class ReceiverMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message) {
System.out.println("消费者1号, -----> 接受到的消息: " + message.getPayload()
+ "\t port: " + serverPort);
}
}
重复消费问题
如果两个消费者同时订阅一个相同的Exchange,会出现重复消费问题。两个消费者会都会接收到信息。
通过分组和持久化属性group解决
分组
stream中同一组内是竞争关系,只有一个可以消费。不同组是可以重复消费的。
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: #在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: #设置rabbitmq的相关环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
input: #这个名字是一个通道的名称
destination: studyExchange #表示要使用的Exchange名称定义
content-type: application/json #设置消息类型,本次为json,本文要设置为“text/plain”
binder: defaultRabbit #设置要绑定的消息服务的具体设置
group: fenzu1 #这个配置就是分组,自动分组且支持持久化。
#如果设置了这个属性,另一个客户端不设置,那么另一个客户端不会受到消息。
分布式请求链路跟踪
Sleuth
Sleuth为服务之间调用提供链路追踪。通过Sleuth可以很清楚的了解到一个服务请求经过了哪些服务,每个服务处理花费了多长。
将信息发送到zipkin
,利用zipkin
的存储来存储信息,利用zipkin ui来展示数据
zipkin jar包下载 zipkin-server-2.12.9-exec.jar
使用 java -jar运行
访问
http://localhost:9411/zipkin/
每个服务都导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
application
spring:
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
#采样取值介于 0到1之间,1则表示全部收集
probability: 1
项目启动后,所有的调用就可以通过上边的地址,查看到图形化接面。
spring cloud alibaba
- 服务限流降级
- 服务注册与发现
- 分布式配置管理
- 消息驱动能力
- 阿里云对象存储
- 分布式任务调度
Nacos
注册中心和配置中心的组合
等价于 Eureka+Config+Bus
linux 安装
- 下载
- 解压
- 执行conf中的sql
- 运行bin下的startup.sh
服务注册
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 192.168.206.128:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
@EnableDiscoveryClient
调用其他服务
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
#消费者将去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
@RestController
@Slf4j
public class OrderNacosController {
@Resource
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}")
private String serverURL;
@GetMapping(value = "/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Integer id) {
return restTemplate.getForObject(serverURL + "/payment/nacos/" + id,String.class);
}
}
配置中心
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- SpringCloud ailibaba nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
和spring-cloud-config一样,要先从配置中心拉取
也是两个配置:bootstrap(放配置中心的) 和 application(放自己的)
bootstrap
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式配置
application
spring:
profiles:
active: dev
#active: dev 表示开发环境
#active: test表示测试环境
#active: prod 表示生产环境
新建配置的 Data ID 有固定的格式:
${spring.application.name}-${spring.profile.active}.${file-extension}
应用名-环境.文件格式
nacos-config-client-dev.yaml
项目运行后,就可以读到nacos上的nacos-config-client-dev.yaml配置文件了
通过Data ID读取配置
上边的就是通过Data ID读取配置文件,没有设置group。读取的都是默认分组DEFAULT_GROUP的文件
通过 Group 读取配置
Data ID 设置成相同的。即spring.profile.active的值一致。
只需要设置分组就可以去不同分组读取配置
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式配置
group: TEST_GROUP #这个属性和nacos创建配置时设置的group一致,就可以读到想要读取的配置文件了
通过命名空间读取
新建命名空间后,会生成对应的命名空间id
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式配置
group: TEST_GROUP
namespace: 18a80a02-398c-4bbf-bc58-2b46a9a85bb1 #会从对应的命名空间去找
@Value属性动态刷新
@SpringBootApplication
@RestController
@RefreshScope//配置中心更改配置后,初始化可以查到新内容,但是调用user()还是旧值,这个配置可以使@Value动态刷新。可以获得新值
public class NacosConfigSampleApplication {
@Value("${user.name}")
private String userName;
@Value("${user.age}")
private int userAge;
@PostConstruct
public void init() {
System.out.printf("[init] user name : %s , age : %d%n", userName, userAge);
}
@RequestMapping("/user")
public String user() {
return String.format("[HTTP] user name : %s , age : %d", userName, userAge);
}
public static void main(String[] args) {
SpringApplication.run(NacosConfigSampleApplication.class, args);
}
}
数据持久化
在nacos/conf中的application.properties中添加
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456
集群化
Sentinel熔断与限流
java -jar sentinel-dashboard-1.7.1.jar
<!-- SpringCloud ailibaba nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- SpringCloud ailibaba sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinal-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentin dashboard地址
dashboard: localhost:8080
# 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
feign:
sentinel:
enabled: true #激活Sentinel 对Feign的支持
@RestController
public class FlowLimitController {
@GetMapping("/test")
public String testA(){
return "----------- testA";
}
}
//sentinel是懒加载,访问一次http://localhost:8401/test
流量控制
- QPS:达到每秒请求最大值,限流
- 线程控制:超过最大线程数,限流
关联
当 /hahaha超过限制,/test就会被限流
WarmUp
预热。刚开始阀值是10/3,经过5秒后,阀值升到10。
熔断降级
热点规则(热点key限流)
/**
*blockHandler:降级调用的方法
*value = "testHotKey":资源名称。可以根据地址或者资源名称设置限流
*/
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false)String p1,
@RequestParam(value = "p2",required = false)String p2) {
return "----testHotKey";
}
public String deal_testHotKey(String p1, String p2, BlockException exception) {
return "----deal_testHotKey, o(╥﹏╥)o"; // sentinel的默认提示都是: Blocked by Sentinel (flow limiting)
}
针对参数限流,访问地址传入参数超过每秒访问请求数会被降级。
参数索引: 指的是testHotKey方法的参数位置,并不是发送参数的顺序
参数例外项: 针对上边限流的参数进行额外处理。当限流参数索引的参数的值是5,阀值是10。
自定义限流处理,SentinelResource配置
@RestController
public class RateLimitController {
/**
*blockHandler:降级回调方法
*
*/
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200,"按照资源名称限流测试",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception) {
return new CommonResult(444,exception.getClass().getCanonicalName() + "\t 服务不可用");
}
/**
*blockHandlerClass:指定降级回调方法所在的类
*/
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class,blockHandler = "handlerException2")
public CommonResult customerBlockHandler() {
return new CommonResult(200,"按照客户自定义限流测试",new Payment(2020L,"serial003"));
}
}
public class CustomerBlockHandler {
public static CommonResult handlerException(BlockException exception) {
return new CommonResult(444,"按照客户自定义的Glogal 全局异常处理 ---- 1",new Payment(2020L,"serial003"));
}
public static CommonResult handlerException2(BlockException exception) {
return new CommonResult(444,"按照客户自定义的Glogal 全局异常处理 ---- 2",new Payment(2020L,"serial003"));
}
}
Sentinel持久化
每次项目重启,sentinel规则就没有了。所以要持久化
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
spring:
application:
name: cloudalibaba-sentinal-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentin dashboard地址
dashboard: localhost:8080
# 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口
port: 8719
# 持久化配置
datasource:
ds1:
nacos:
server-addr: localhost:8848 #Nacos服务注册中心地址
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
在nacos配置管理的配置列表中新建配置
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式:0直接,1关联,2链路
controlBehavior:流控效果;0快速失败,1warm up,2排队等待
clusterMode:是否集群
项目重启
Seata 分布式事务处理
是全局唯一ID+三组件模型
XID(Transaction ID)-全局唯一的事务ID
TC (Transaction Coordinator) - 事务协调者
- 维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
- 定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
- 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
流程:
- TM想TC申请开启一个全局事务,全局事务创建成功生成一个全局唯一XID
- XID在微服务调用链路上下文中传播
- RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
- TM向TC发起针对XID的全局提交或回滚决议
- TC调度XID下管辖的全部分支事务完成提交或回滚
安装
修改registry.conf
修改file.conf
双击seata-server.bat运行
数据库准备
创建不同的数据库。然后再不同的数据库下创建对应的表。
例如 订单、库存、账户。
创建三个不同的数据库,并在这三个数据库下创建对应的表
然后每个数据库都要创建回滚日志表,seata目录下conf中的db_undo_log.sql就是回滚日志表
这时候一共有四个数据库:
- seata库。(db_store.sql创建的 )。有三张表,branch_table,global_table,lock_table
- 订单库,订单表和undo_log
- 库存库,库存表和undo_log
- 账号库,账号表和undo_log
通用项目配置
将file.conf和registry.conf两个配置复制到项目配置文件中
<!--因为下载1.0.0没下载下来,下的是0.9.0,-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
# 自定义事务组名称,刚才file.conf中设置过
tx-service-group: tx_group
nacos:
discovery:
server-addr: 127.0.0.1:8848
datasource:
# 当前数据源操作类型,阿里的
type: com.alibaba.druid.pool.DruidDataSource
# mysql驱动类
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath*:mapper/*.xml
//取消数据源依赖,改为seata的
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan({"com.springcloud.alibaba.dao"})//你的mapper接口路径
public class SeataOrderMain2001 {
public static void main(String[] args) {
SpringApplication.run(SeataOrderMain2001.class,args);
}
}
seata数据源
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
*
*seata数据源
*/
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSourceProxy);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources(mapperLocations));
return bean.getObject();
}
}
每个项目都进行以上配置。
在调用其他服务的方法上添加注解
业务模块中使用openfeign调用其他服务
然后再业务模块启用分布式事务
@GlobalTransactional(name = “create-order”,rollbackFor = Exception.class)
name是唯一的就可以。
例如创建订单
@Override
@GlobalTransactional(name = "create-order",rollbackFor = Exception.class)
public void create(Order order)
{
log.info("----->开始新建订单");
//1 新建订单
//2 扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
log.info("----->订单微服务开始调用库存,做扣减end");
//3 扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
log.info("----->订单微服务开始调用账户,做扣减end");
//4 修改订单状态,从零到1,1代表已经完成
log.info("----->修改订单状态开始");
log.info("----->修改订单状态结束");
log.info("----->下订单结束了,O(∩_∩)O哈哈~");
}
seata服务器就是TC, order服务就是TM, 参与下订单的account服务和 storage服务就是RM