Spring Cloud入门上
1 微服务概述
2.项目搭建
- 2.1 建立父项目
- 2.2 服务提供者-支付模块
- 2.3 服务消费者-订单模块
- 2.4 抽取公用模块-common
3.注册中心
- 3.1 eureka注册中心
- 3.1.1 建立eureka server
- 3.1.2 支付服务注册到eureka server
- 3.1.3 搭建eureka server集群
- 3.1.4 多实例注册到eureka server
- 3.2 zookeeper注册中心
- 3.2.1 centos7搭建zk
- 3.2.2 服务注册
- 3.2.3 服务调用
- 3.3 consul注册中心
- 3.3.1 consul概述及环境搭建
- 3.3.2 服务注册
- 3.3.3 服务消费
- 3.3.4 三个注册中心异同点
- 3.1 eureka注册中心
4.ribbon-负载均衡
- 4.1 概述
- 4.2 getForObject和getForEntity区别
- 4.3 替换负载规则
- 4.4 手写负载算法
5.Feign/OpenFeign-服务调用
- 5.1 概述
- 5.2 服务调用
- 5.3 超时控制
- 5.4 日志增强
6.断路器-Hystrix
- 6.1 概述
- 6.2 高延迟高并发场景模拟
- 6.3 消费者高延迟场景
- 6.4 降级
- 6.5 熔断
- 6.6 限流
- 6.7 仪表盘
- 6.8 仪表盘监控服务
7.网关Gateway
- 7.1 概述
- 7.2 三大核心概念
- 7.3 入门配置
- 7.4 编码方式实现路由转发
- 7.5 通过微服务名动态路由
- 7.6 断言-Predicate
- 7.7 过滤器-Filter
8.配置中心-Spring Cloud Config
- 8.1 概述
- 8.2 服务端获取配置信息
- 8.3 客户端获取配置信息
- 8.4 配置实时刷新
9.消息总线-Spring Cloud Bus
- 9.1 概述
- 9.2 rabbitmq环境搭建
- 9.3 Bus动态刷新全局广播配置
- 9.4 Bus动态刷新定点通知
1 微服务概述
概念
单一服务分为一组小的服务,每个服务运行中独立线程中,服务之间相互协调
维度
库存\订单\支付...
spring cloud是微服务架构的一站式解决方案
eureka已停更,注册发现推荐nacos
spring cloud技术栈
注册发现 eureka
负载调用 ribbon
熔断降级 hystrix
网关 zuul
配置 spring cloud config
版本
本教程使用spring boot 2.2.2和spring cloud H版SR1
java 8+
maven 3.5+
组件停更及替换
被动修复bugs\不再合并请求\不发布新版本
eureka停更->zk | consul(不推荐) | nacos(完美替换)
ribbon进入维护状态 -> loadbalancer
feign停更 -> openfeign(推荐)
hystrix停更 -> resilience4j(国外) | sentinel(国内推荐)
zuul -> gateway
config -> apolo(携程) | nacos(推荐)
bus -> nacos
参考资料
官方文档
2.项目搭建
2.1 建立父项目
新建maven项目scdemo
a.字符集
setting->editor->file encoding
全部选择utf-8
b.注解
settings->build->compiler->annotation processer
enable annotation proccessing
c.编译版本
settings->build->compiler->java compiler
target bytecode version: 8
d.隐藏文件
settings->editor->file types
ignore files and folders: 添加.iml;.idea;
e.依赖
pom.xml
...<packaging>pom</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>12</maven.compiler.source><maven.compiler.target>12</maven.compiler.target><junit.version>4.12</junit.version><lombok.version>1.18.10</lombok.version><log4j.version>1.2.17</log4j.version><mysql.version>8.0.18</mysql.version><druid.version>1.1.16</druid.version><mybatis.spring.boot.version>2.1.1</mybatis.spring.boot.version></properties><dependencyManagement><dependencies><dependency><groupId>org.apache.maven.plugins</groupId><artifactId>maven-project-info-reports-plugin</artifactId><version>3.0.0</version></dependency><!--spring boot 2.2.2--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.2.2.RELEASE</version><type>pom</type><scope>import</scope></dependency><!--spring cloud Hoxton.SR1--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Hoxton.SR1</version><type>pom</type><scope>import</scope></dependency><!--spring cloud 阿里巴巴--><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><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version><scope>runtime</scope></dependency><!-- druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>${druid.version}</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>${mybatis.spring.boot.version}</version></dependency><!--junit--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>${junit.version}</version></dependency><!--log4j--><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version></dependency></dependencies></dependencyManagement>
</project>
dependencyManagement只声明,不引入实现
跳过maven单元测试
右侧maven projects点击闪电图标
2.2 服务提供者-支付模块
新建模块 scdemo-payment
a.依赖
pom.xml
<dependencies><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.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.18</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
b.配置
resources/application.yml
server:port: 8001spring:application:name: cloud-payment-servicedatasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/springcloud2020?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8username: rootpassword: rootmybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.gaoliang.springcloud.payment.entity
c.启动类 com.gaoliang.springcloud.payment.PaymentApplication
@SpringBootApplication
public class PaymentApplication {public static void main(String[] args) {SpringApplication.run(PaymentApplication.class, args);}
}
d.建表语句
CREATE TABLE `payment`(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`serial` VARCHAR(20) DEFAULT '',
PRIMARY KEY(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
e.实体类
订单实体类
com.gaoliang.springcloud.payment.entity.Payment
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {private Long id;private String serial;
}
统一结果类
com.gaoliang.springcloud.payment.entity.CommonResult
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> implements Serializable {private Integer code;private String msg;private T data;public CommonResult(Integer code, String msg) {this(code, msg, null);}public CommonResult(ResultEnum resultEnum, T data) {this(resultEnum.getCode(), resultEnum.getMsg(), data);}public static CommonResult success() {return new CommonResult(ResultEnum.SUCCESS, null);}public static<T> CommonResult success(T data) {return new CommonResult(ResultEnum.SUCCESS, data);}public static<T> CommonResult success(String msg, T data) {return new CommonResult(ResultEnum.SUCCESS.getCode(), msg, data);}public static CommonResult fail() {return new CommonResult(ResultEnum.FAIL, null);}
}
结果枚举
com.gaoliang.springcloud.payment.entity.ResultEnum
@Getter
@AllArgsConstructor
public enum ResultEnum {SUCCESS(200, "操作成功"),FAIL(-1, "操作失败");private int code;private String msg;
}
f.mapper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.gaoliang.springcloud.payment.dao.PaymentDao"><insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">insert into payment(serial) values(#{serial})</insert><resultMap id="BaseResultMap" type="com.gaoliang.springcloud.payment.entity.Payment"><id column="id" property="id" jdbcType="BIGINT" /><id column="serial" property="serial" jdbcType="VARCHAR" /></resultMap><select id="getById" parameterType="Long" resultMap="BaseResultMap">select * from payment where id=#{id}</select>
</mapper>
g.dao
com.gaoliang.springcloud.payment.dao.PaymentDao
@Mapper
public interface PaymentDao {int create(Payment payment);Payment getById(@Param("id") Long id);
}
h.service
com.gaoliang.springcloud.payment.service.PaymentService
public interface PaymentService {int create(Payment payment);Payment getById(Long id);
}
i.service impl
com.gaoliang.springcloud.payment.service.impl.PaymentServiceImpl
@Service
public class PaymentServiceImpl implements PaymentService {@Resourceprivate PaymentDao paymentDao;@Overridepublic int create(Payment payment) {return paymentDao.create(payment);}@Overridepublic Payment getById(Long id) {return paymentDao.getById(id);}
}
j.controller
com.gaoliang.springcloud.payment.controller.PaymentController
@RestController
@Slf4j
public class PaymentController {@Resourceprivate PaymentService paymentService;@PostMapping(value="/payment/service")public CommonResult create(@RequestBody Payment payment) {int result = paymentService.create(payment);return result > 0 ? CommonResult.success(result) : CommonResult.fail();}@GetMapping(value="/payment/get/{id}")public CommonResult getById(@PathVariable Long id) {Payment payment = paymentService.getById(id);return payment != null ? CommonResult.success(payment) : CommonResult.fail();}
}
k.启动
启动项目,浏览器访问:
http://localhost:8001/payment/get/1
l.开启热部署
i.父模块pom添加插件
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><fork>true</fork><addResources>true</addResources></configuration></plugin></plugins></build>
ii.子模块添加依赖
<!--热部署插件--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency>
iii.设置
settings->build->compiler
勾选Display...和Build Project...
iv.ctrl + alt + shift + /,点击1.Registry...
勾选compiler.automake.allow.when.app.running
v.生产环境要关闭热部署
2.3 服务消费者-订单模块
a.新建模块
scdemo-order
pom复制scdmo-payment的pom,修改artifactId
b.配置
配置端口及数据源
虽然该模块目前没有用到数据源,但不配置会报错...
c.拷贝scdemo-payment的实体类
d.配置类
com.gaoliang.springcloud.order.config.ApplicationContextConfig
@Configuration
public class ApplicationContextConfig {@Beanpublic RestTemplate getRestTemplate() {return new RestTemplate();}
}
e.controller
com.gaoliang.springcloud.order.controller.OrderController
@RestController
@Slf4j
public class OrderController {public static final String PAYMENT_URL = "http://localhost:8001";@Resourceprivate RestTemplate restTemplate;@PostMapping("/consumer/payment/create")public CommonResult create(@RequestBody Payment payment) {// 使用restTemplate调用服务提供者接口return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);}@GetMapping("/consumer/payment/get/{id}")public CommonResult getPayment(@PathVariable Long id) {return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);}}
f.启动,测试
使用idea的run dashboard启动两个模块
使用postman测试
url: http://localhost/consumer/payment/create
headers:
Content-Type: application/json
body: raw
{"id": 1,"serial": "111"
}
2.4 抽取公用模块-common
a.新建模块scdemo-common
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.1.0</version></dependency></dependencies>
b.copy实体类包到该模块下
c.其他两个模块依赖该模块
<dependency><groupId>cn.dfun</groupId><artifactId>scdemo-common</artifactId><version>${project.version}</version></dependency>
d.修改mybatis实体类前缀
scdemo-payment模块application.yml
mybatis:
...type-aliases-package: com.gaoliang.springcloud.common.entity
3.注册中心
3.1 eureka注册中心
服务注册
注册中心配多个避免单点故障
服务与注册中心维持心跳
3.1.1 建立eureka server
a.新建模块
scdemo-eureka-server
依赖
<dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><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></dependencies><build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build>
b.配置
server:port: 7001eureka:instance:# eureka服务端实例名称hostname: localhostclient:# 不向注册中心注册自己register-with-eureka: false# 不去检索服务fetch-registry: false# 服务查询地址service-url:defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
c.启动类
com.gaoliang.springcloud.eurekaserver.EurekaServerApplication
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);}
}
3.1.2 支付服务注册到eureka server
a.依赖
scdemo-payment pom添加
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>
b.配置
添加
eureka:client:service-url:defaultZone: http://localhost:7001/eurekaregister-with-eureka: truefetch-registry: false
c.注解
启动类添加@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class PaymentApplication {
...
浏览器访问:
http://localhost:7001/
可以看到scdemo-payment服务
d 订单服务注册到eureka server
同支付服务类似
配置
register-with-eureka: falsefetch-registry: true
3.1.3 搭建eureka server集群
避免注册中心单点故障
原理是多个节点相互注册
a.配置
# 无参数启动
spring:application:name: eureka-serverserver:port: 7001eureka:client:service-url:defaultZone: http://localhost:7001/eureka/---
# 启动参数 --spring.profiles.active=server1
spring:profiles: server1
server:port: 7001
eureka:client:service-url:defaultZone: http://localhost:7002/eureka/
---
# 启动参数 --spring.profiles.active=server2
spring:profiles: server2
server:port: 7002
eureka:client:service-url:defaultZone: http://localhost:7001/eureka/
---
b.启动
Run->Edit Configurations...
分别配置Program argumenents
--spring.profiles.active=server1
--spring.profiles.active=server2
c.服务注册到eureka server集群
修改payment和order的配置
# defaultZone: http://localhost:7001/eurekadefaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
3.1.4 多实例注册到eureka server
a.多个端口启动
payment服务
VMOptions: -Dserver.port=8002
VMOptions: -Dserver.port=8001
b.@LoadBalanced注解
order模块restTemplate添加@LoadBalanced注解
@Bean@LoadBalancedpublic RestTemplate getRestTemplate() {return new RestTemplate();}
c.payment模块controller注入端口号并打印
@Value("${server.port}")private String serverPort;...@GetMapping(value="/payment/get/{id}")public CommonResult getById(@PathVariable Long id) {Payment payment = paymentService.getById(id);String msg = "端口号: " + serverPort;return payment != null ? CommonResult.success(msg, payment) : CommonResult.fail();}
d.order模块修改controller payment服务地址
public static final String PAYMENT_URL = "http://SCDEMO-PAYMENT";
多次访问:
http://localhost/consumer/payment/get/1
可见访问了不同的端口号,默认轮询
e.修改服务在注册中心的显示
eureka:instance:# 服务名:ip:端口instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
f.actuator微服务信息
修改PaymentController
注入discoveryClient
@Resourceprivate DiscoveryClient discoveryClient;
打印相关信息
@GetMapping(value="/payment/discovery")public Object discovery() {// 获取服务清单列表List<String> services = discoveryClient.getServices();for(String element: services) {log.info("############element:" + element);}// 获取实例信息List<ServiceInstance> instances = discoveryClient.getInstances("scdemo-payment");for(ServiceInstance instance : instances) {// 服务名 主机名 端口 urilog.info("##########" + instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri());}return this.discoveryClient;}
g.eureka保护机制
eureka控制台红字
EMERGENCY! EUREKA MAY BE...
当服务不可用时,eureka不会立即将服务剔除
有可能微服务健康,但eureka server网络不通
设计哲学是宁可保留错误的微服务,也不盲目清理
i.禁用自我保护
eureka server
eureka:server:# 禁用自我保护enable-self-preservation: false# 2秒踢除eviction-interval-timer-in-ms: 2000
eureka client
eureka:instance:# Eureka客户端向服务端发送心跳的时间间隔,默认是30秒lease-renewal-interval-in-seconds: 1# Eureka服务端在收到最后一次心跳后等待时间上限,默认是90秒lease-expiration-duration-in-seconds: 2
3.2 zookeeper注册中心
3.2.1 centos7搭建zk
a.zk环境搭建
** 注意:
a.zk使用低版本会报错
b.要下载*bin.tar.gz版本
# 解压
tar -zxvf apache-zookeeper-3.5.8-bin.tar.gz -C /usr/local/
cd /usr/local
mv apache-zookeeper-3.5.8-bin/ zookeeper
cd zookeeper
# 创建数据文件目录
mkdir data# 配置
cp conf/zoo_sample.cfg conf/zoo.cfg
vi conf/zoo.cfg
# 修改dataDir
dataDir=/usr/local/zookeeper/data# 启动
./bin/zkServer.sh start
# 查看启动状态
./bin/zkServer.sh status
3.2.2 服务注册
payment模块
a.依赖
pom添加
<!--SpringBoot整合Zookeeper客户端--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zookeeper-discovery</artifactId></dependency>
b.配置
server:port: 8004spring:profiles: zk
...cloud:zookeeper:connect-string: 192.168.68.101:2181
c.启动类注解
@EnableDiscoveryClient
public class PaymentApplication {
启动
--spring.profiles.active=zk
d.验证
bin/zkCli.sh
ls /
# 显示服务目录
ls /services
# 显示服务注册信息
get /services/scdemo-payment/b603f451-7d50-42f0-b22d-9ef5bb3588fa
浏览器访问:
http://localhost:8004/payment/get/1
补充说明:
zk节点分临时和持久
关闭8004服务后一段时间,zk节点被删除,是临时节点
3.2.3 服务调用
order模块
a.依赖/配置/注解
参考payment模块
b.配置类
RestTemplate与Euraka配置方式相同
注意:
需要加@LoadBalanced注解,否则会报异常
c.启动
--spring.profiles.active=zk
浏览器访问:
http://localhost/consumer/payment/get/1
3.3 consul注册中心
3.3.1 consul概述及环境搭建
a.概述
go语言实现,服务发现和配置管理,支持多数据中心
官网: https://www.consul.io/docs
中文文档: https://www.springcloud.cc/spring-cloud-consul.html
b.环境搭建
下载: https://releases.hashicorp.com/consul/1.6.1/
consul_1.6.1_windows_amd64.zip
下载后直接解压
目录加入到环境变量
# 验证
consul --version
# 启动
consul agent -dev
浏览器访问: http://localhost:8500/
3.3.2 服务注册
payment模块
依赖
注掉zk依赖,添加consul依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-consul-discovery</artifactId></dependency>
配置 application-consul.yml
server:port: 8006spring:profiles: consulapplication:name: scdemo-payment
...# consul注册中心配置cloud:consul:host: localhostport: 8500discovery:service-name: ${spring.application.name}
...
启动
--spring.profiles.active=consul
访问控制台,可以看到注册的服务
测试调用:
http://localhost:8006/payment/get/1
3.3.3 服务消费
依赖\配置参考3.3.2,配置类和controller和zk部分一样
3.3.4 三个注册中心异同点
eureka是AP,主要保证高可用
consul和zk是CP,主要保证数据一致
分区容错性P必须保证
场景:
双十一主要保证可用,使用AP,允许部分节点返回旧值
CP数据同步失败时拒绝请求
4.ribbon-负载均衡
项目代码恢复到eureka注册中心(集群)
4.1 概述
客户端负载均衡组件,已进入维护模式
未来使用loadbalancer替换
ribbon是进程内LB,注册中心获取服务列表缓存到本地
nginx是集中式LB
算法: 轮询\随机\根据访问时间加权
新版eureka-client集成了ribbon,不需单独引入
4.2 getForObject和getForEntity区别
getForObject和getForEntity区别:
Object基本可以理解为json,Entity包含了响应头\状态码\响应体等,推荐使用Object
order模块controller添加方法
@GetMapping("/consumer/payment/getForEntity/{id}")public CommonResult getPaymentEntity(@PathVariable Long id) {ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);return entity.getStatusCode().is2xxSuccessful() ? entity.getBody() : CommonResult.fail();}
4.3 替换负载规则
自带算法七种:
轮询\随机\根据时间加权\过滤故障节点等
ribbon配置不能放到@Component组件能扫描到的当前包和子包下,不然会被所有ribbon客户端共享
配置类
order模块
com.gaoliang.springcloud.myrule.MyRule
@Configuration
public class MyRule {@Beanpublic IRule iRule() {// 随机return new RandomRule();}
}
启动类注解
** 注意这里name要配置服务提供者的名称
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name="scdemo-payment", configuration = MyRule.class)
public class OrderApplication {
...
4.4 手写负载算法
恢复默认轮询算法,注掉@RibbonClient注解
轮询算法:
接口第几次请求数%集群总数=实际调用服务器位置下标
服务重启后rest接口计数从1开始
order模块注掉restTemplate配置类的@LoadBalancer注解
定义接口
com.gaoliang.springcloud.order.lb.LoadBalancer
public interface LoadBalancer {ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
实现类
com.gaoliang.springcloud.order.lb.MyLB
** 注意Component注解
@Component
public class MyLB implements LoadBalancer{private AtomicInteger atomicInteger = new AtomicInteger(0);public final int getAndIncrement() {int current;int next;do {current = this.atomicInteger.get();// 整数越界next = current >= Integer.MAX_VALUE ? 0 : current + 1;} while(!this.atomicInteger.compareAndSet(current, next));System.out.println("***next: " + next);return next;}@Overridepublic ServiceInstance instances(List<ServiceInstance> serviceInstances) {// 当前计数对集群数取余int index = getAndIncrement() % serviceInstances.size();return serviceInstances.get(index);}
}
OrderController添加loadBalancer和discoveryClient注入,并添加实现接口
@Resourceprivate LoadBalancer loadBalancer;@Resourceprivate DiscoveryClient discoveryClient;...@GetMapping("/consumer/payment/lb")public String getPaymentLB() {List<ServiceInstance> instances = discoveryClient.getInstances("scdemo-payment");if(instances == null || instances.size() <= 0) {return null;}ServiceInstance serviceInstance = loadBalancer.instances(instances);URI uri = serviceInstance.getUri();return restTemplate.getForObject(uri + "/payment/lb", String.class);}
PaymentController添加相关接口
@GetMapping(value="/payment/lb")public String lb() {return this.serverPort;}
启动,测试
5.Feign/OpenFeign-服务调用
5.1 概述
feign是一个声明式WebService客户端
feign已停止维护,推荐open feign
使用方法是定义一个服务接口然后在上面添加注解,支持可插拔式的编码器和解码器,spring cloud对feign进行了封装,使其支持spring mvc标准注解和HttpMessageConverter,Feign可与Ribbon结合
5.2 服务调用
新建模块: scdemo-order-feign
可从scdemo-order拷贝,删除冗余代码,注意修改pom中的artifactId和配置中的应用名,并将子模块添加到父模块pom
依赖
在原有基础上添加
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
配置文件无修改,删除配置文件类(RestTemplate配置)
启动类注解
添加@EnableFeignClients
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderFeignApplication {
...
定义feign接口
com.gaoliang.springcloud.order.service.PaymentFeignService
注意@Component注解和@PathVariable("id")
@Component
@FeignClient(value="scdemo-payment")
public interface PaymentFeignService {@GetMapping(value="/payment/get/{id}")CommonResult getById(@PathVariable("id") Long id);
}
controller
@RestController
@Slf4j
public class OrderController {@Resourceprivate PaymentFeignService paymentFeignService;@GetMapping("/consumer/payment/get/{id}")public CommonResult getPayment(@PathVariable Long id) {return paymentFeignService.getById(id);}
}
启动,测试可见自带ribbon负载均衡
5.3 超时控制
提供者和消费者要约定好超时时间
a.模拟超时异常
payment模块controller添加接口
@GetMapping(value="/payment/timeout")public String timeout() {try {// 睡眠3秒,模拟超时TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}return serverPort;}
order模块feign接口添加
@GetMapping(value="/payment/timeout")String timeout();
order模块controller添加
@GetMapping(value="/consumer/payment/timeout")public String timeout() {return paymentFeignService.timeout();}
启动,访问: http://localhost/consumer/payment/timeout
报错:
feign.RetryableException: Read timed out executing GET http://scdemo-payment/payment/timeout
...
b.超时时间设置
order模块application.yml添加
ribbon:# 建立连接所用的时间ReadTimeout: 5000# 建立连接后服务读取到可用资源所用的时间ConnectTimeout: 5000
再次访问a中接口,报错解除
5.4 日志增强
日志级别
NONE 默认,不显示日志
BASIC 仅记录请求方法\url\状态码及执行时间
HEADERS BASIC+请求和响应的头信息
FULL HEADERS+请求和响应的正文及元数据
配置类
com.gaoliang.springcloud.order.config.FeignConfig
@Configuration
public class FeignConfig {@BeanLogger.Level feignLoggerLevel() {return Logger.Level.FULL;}
}
配置
logging:level:# feign日志级别及监控的端口com.gaoliang.springcloud.order.service.PaymentFeignService: debug
访问: http://localhost/consumer/payment/get/1
可见控制台打印出了详细日志
6.断路器-Hystrix
6.1 概述
处理延迟和容错
停更进维,但设计优秀,重要,推荐resilience4j(头条在用其他大厂很少)和sentinel
服务链路复杂,单个服务不可用或响应时间长造成服务雪崩
hystrix类似保险丝,通过故障监控,返回备选响应,避免雪崩
hystrix提供服务降级\熔断和接近实时的监控
降级
对方系统不可用,给友好提示
程序异常\超时\服务熔断\线程池|信号量打满等情况
熔断
达到最大访问号,拒绝访问,调用服务降级给友好提示
降级->熔断->恢复
限流
秒杀高并发等,严禁拥挤,排队处理
6.2 提供者高延迟高并发场景模拟
a.新建scdemo-payment-hystrix模块
可从scdemo-payment模块copy
依赖在scdemo-payment基础上添加
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency>
b.service
简单起见只写实现类
com.gaoliang.springcloud.payment.service.PaymentService
paymentInfoOK为立即返回的接口
paymentInfoTimeout模拟返回时间较长的方法
public String paymentInfoOK(Integer id) {return "线程池: " + Thread.currentThread().getName() + " paymentInfoOK, id: " + id;}public String paymentInfoTimeout(Integer id) {int timeNumber = 3;try {TimeUnit.SECONDS.sleep(timeNumber);} catch (InterruptedException e) {e.printStackTrace();}return "线程池: " + Thread.currentThread().getName() + " paymentInfoTimeout, id: " + id + ", 耗时: " + timeNumber;}
c.controller
@RestController
@Slf4j
public class PaymentController {@Resourceprivate PaymentService paymentService;@Value("${server.port}")private String serverPort;@GetMapping("/payment/hystrix/ok/{id}")public String paymentInfoOK(@PathVariable Integer id) {String result = paymentService.paymentInfoOK(id);log.info("*****result: " + result);return result;}@GetMapping("/payment/hystrix/timeout/{id}")public String paymentInfoTimeout(@PathVariable Integer id) {String result = paymentService.paymentInfoTimeout(id);log.info("*****result: " + result);return result;}
}
d.启动项目,压测
启动jmeter
i.Test Plan右键->Add->Threads->Thread Group
Number of Threads: 200
Loop Count: 100
ii.HTTP Request右键->Add->Sampler->HTTP Request
Server Name or IP: localhost
Port Number: 8001
Path: /payment/hystrix/timeout/1
点击工具栏绿色三角按钮启动
iii.访问: http://localhost:8001/payment/hystrix/ok/1
可见timeout接口高延迟请求堆积导致系统资源占用,其他接口响应也变慢
6.3 消费者高延迟场景
a.新建模块scdemo-order-hystrix
在scdemo-order-feign基础上拷贝,修改pom artifactId,配置文件应用名,父模块引入该子模块
** 注意:
如果未修改artifactId就mvn import会报错,恢复比较麻烦,建议删除idea工程文件后重新导入maven项目,并重新进行项目的各项配置
这里使用eureka server单实例
client:service-url:defaultZone: http://localhost:7001/eureka
# defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
添加依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency>
b.feign接口
@Component
@FeignClient(value="scdemo-payment-hystrix")
public interface PaymentHystrixService {@GetMapping("/payment/hystrix/ok/{id}")public String paymentInfoOK(@PathVariable("id") Integer id);@GetMapping("/payment/hystrix/timeout/{id}")public String paymentInfoTimeout(@PathVariable("id") Integer id);
}
c.controller
@RestController
@Slf4j
public class OrderHystrixController {@Resourceprivate PaymentHystrixService paymentHystrixService;@GetMapping("/consumer/payment/hystrix/ok/{id}")public String paymentInfoOK(@PathVariable("id") Integer id) {String result = paymentHystrixService.paymentInfoOK(id);return result;}@GetMapping("/consumer/payment/hystrix/timeout/{id}")public String paymentInfoTimeout(@PathVariable("id") Integer id) {String result = paymentHystrixService.paymentInfoTimeout(id);return result;}}
d.压测提供者接口导致消费者接口高延迟
参考6.2.d使用jmeter对提供者/hystrix/timeout接口压测,访问消费者接口:
http://localhost/consumer/payment/hystrix/ok/1
可见明显延迟甚至报错
6.4 降级
超时不等待,出错友好提示
解决:
i.提供者超时,消费者不能一直卡死等待,须服务降级;
ii.提供者down机,消费者不能一直等待,须服务降级;
提供者服务ok,消费者故障或者有自我要求(自己等待时间<提供者),自处理降级
a.提供者降级
service超时方法添加@HystrixCommand注解,并实现handler方法
@HystrixCommand(fallbackMethod = "paymentInfoTimeoutHandler", commandProperties = {// 超时时间@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="3000")})public String paymentInfoTimeout(Integer id) {
...public String paymentInfoTimeoutHandler(Integer id) {// 友好提示系统繁忙等return "线程池: " + Thread.currentThread().getName() + " paymentInfoTimeoutHandler, id: " + id;}
启动类添加@EnableCircuitBreaker注解
...
@EnableCircuitBreaker
public class PaymentHystrixApplication {
...
消费者调用:http://localhost/consumer/payment/hystrix/timeout/1
可见触发降级,方法抛异常和超时都会降级
b.消费者降级
降级处理可以加在提供着,也可加在消费者
一般加在消费端
对hystrix注解修改热部署不生效建议重启服务
消费者controller添加@HystrixCommand,并实现handler方法
@GetMapping("/consumer/payment/hystrix/timeout/{id}")@HystrixCommand(fallbackMethod = "paymentInfoTimeoutHandler", commandProperties = {@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="1500")})public String paymentInfoTimeout(@PathVariable("id") Integer id) {
...public String paymentInfoTimeoutHandler(Integer id) {return "消费者降级: 线程池: " + Thread.currentThread().getName() + " paymentInfoTimeoutHandler, id: " + id;}
启动类添加@EnableCircuitBreaker 或者 @EnableHystrix
...
@EnableCircuitBreaker
public class OrderHystrixApplication {
...
访问:
http://localhost/consumer/payment/hystrix/timeout/1
由于消费者超时时间设为1.5秒,小于提供者的超时时间,所以直接触发消费者降级.
c.降级方法膨胀-通用降级方法
如果,每个方法都配置降级方法会导致方法膨胀,可配置一个全局通用的默认降级方法
消费者controller加@DefaultProperties,并实现通用降级方法
...
@DefaultProperties(defaultFallback = "paymentInfoGlobalHandler")
public class OrderHystrixController {
...public String paymentInfoGlobalHandler() {return "Global异常处理...";}
d.降级方法与业务逻辑耦合-feignclient fallback
消费者添加配置配置
# feign开启降级处理
feign:hystrix:enabled: true
继承feign接口,实现fallback类
com.gaoliang.springcloud.order.service.PaymentHystrixService
@Component
@FeignClient(value="scdemo-payment-hystrix", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {@GetMapping("/payment/hystrix/ok/{id}")public String paymentInfoOK(@PathVariable("id") Integer id);@GetMapping("/payment/hystrix/timeout/{id}")public String paymentInfoTimeout(@PathVariable("id") Integer id);
}
@FeignClient指定fallback属性
@FeignClient(value="scdemo-payment-hystrix", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
...
验证,关闭提供者,然后访问消费者接口:
http://localhost/consumer/payment/hystrix/ok/1
可见直接进入了fallback类的方法
6.5 熔断
当链路某个微服务出错或者响应时间长,进行服务降级,熔断该服务的调用
服务可用后自动恢复
熔断源于Martin Fowler,相关资料(慢):
https://martinfowler.com/bliki/CircuitBreaker.html
半开状态尝试请求响应,如果可用则恢复
a.提供者添加service方法配置@HystrixCommand属性,并实现降级方法
com.gaoliang.springcloud.payment.service.PaymentService
@Service
public class PaymentService {
.../*** 服务熔断*/@HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallback", commandProperties = {// 是否开启断路器@HystrixProperty(name="circuitBreaker.enabled", value="true"),// 请求次数@HystrixProperty(name="circuitBreaker.requestVolumeThreshold", value="10"),// 时间窗口期@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds", value="10000"),// 失败率达到多少后跳闸@HystrixProperty(name="circuitBreaker.errorThresholdPercentage", value="60")// 以上参数表示10秒内失败超过6次跳闸})public String paymentCircuitBreaker(@PathVariable("id") Integer id) {if(id < 0) {throw new RuntimeException("*****id不能为负");}// hutool,国产工具类包,https://www.hutool.cn/docs/#/String serialNumber = IdUtil.simpleUUID();return Thread.currentThread().getName() + "\t" + "调用成功, 流水号: " + serialNumber;}public String paymentCircuitBreakerFallback(@PathVariable("id") Integer id) {return "paymentCircuitBreakerFallback: id不能负数,请稍后再试, id: " + id;}}
b.提供者controller
/*** 服务熔断*/@GetMapping("/payment/circuit/{id}")public String paymentCircuitBreaker(@PathVariable Integer id) {String result = paymentService.paymentCircuitBreaker(id);return result;}
测试,访问:
http://localhost:8001/payment/circuit/1
id为整数,服务可用
当短时间连续访问
http://localhost:8001/payment/circuit/-1
触发熔断,再次访问:
http://localhost:8001/payment/circuit/1
发现服务不可用被降级处理
经过一段时间后服务自动恢复
c.其他参数说明:
请求总数阈值,默认20,即请求时间内,必须满足总数阈值才会熔断
默认阈值10秒超过20个请求
默认失败率10秒超过50%的请求失败
一段时间(默认5秒)后,断路器是半开状态,会让一个请求进行转发,如果成功断路器会关闭
6.6 限流
略,参见Sentinel章节
6.7 仪表盘
提供准实时监控,报表形式
新建模块scdemo-payment-hystrix-dashboard
添加依赖
<dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--热部署--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
主要是以下依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId></dependency>
配置
server:port: 9001spring:application:name: scdemo-payment-hystrix-dashboard
启动类注解
@SpringBootApplication
@EnableHystrixDashboard
public class PaymentHystrixDashboardApplication {
...
http://localhost:9001/hystrix
6.8 仪表盘监控服务
对scdemo-payment-hystrix模块进行修改
需要保证服务依赖了
spring-boot-starter-actuator
消费者启动类注入Bean
...
public class PaymentHystrixApplication {
main方法.../*** Hystrix升级的坑,此配置不必深究*/@Beanpublic ServletRegistrationBean getServlet() {HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);registrationBean.setLoadOnStartup(1);registrationBean.addUrlMappings("/hystrix.stream");registrationBean.setName("HystrixMetricsStreamServlet");return registrationBean;}
}
hystrix仪表盘填入url和名称
http://localhost:8001/hystrix.stream
Title随便填
访问payment /payment/circuit/接口(发起多次失败请求),并实时查看仪表盘状态
仪表盘:
7色一圈一线
7.网关Gateway
7.1 概述
zuul过时,zuul2跳票,弃用
gateway,新一代网关,spring社区自研
基于spring boot2.0\webflux\project reactor
网关可以做反向代理\鉴权\监控等
webflux基于netty(Reactor模式)
gateway特性
动态路由\断言\过滤器\支持websocket
非阻塞io(zuul1为阻塞io)
7.2 三大核心概念
路由-Route
构建网关的基本模块
由ID\目标uri\一系列断言和过滤器组成,如果断言为true则匹配该路由
断言-Predicte
参考java8的java.util.function.Predicte
匹配HTTP请求所有内容(请求头和参数),请求与断言相匹配则进行路由
过滤-Filter
在请求被路由之前或者之后进行处理
7.3 入门配置
新建模块,scdemo-gateway
依赖
** 注意不能依赖web和acurator
<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><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--热部署插件--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency></dependencies>
配置
server:port: 9527
spring:application:name: scdemo-gatewaycloud:gateway:routes:# 路由id,没有固定规则但要求唯一,建议配合服务名- id: payment_route# 匹配后提供服务的路由地址uri: http://localhost:8001predicates:# 断言,路径相匹配的进行路由- Path=/payment/get/**- id: payment_route2uri: http://localhost:8001predicates:- Path=/payment/lb/**eureka:instance:hostname: scdemo-gatewayclient:register-with-eureka: truefetch-registry: trueservice-url:defaultZone: http://localhost:7001/eureka
启动相关项目,访问:
http://localhost:9527/payment/get/1
http://localhost:9527/payment/lb
可见通过网关路由到了微服务
7.4 编码方式实现路由转发
com.gaoliang.springcloud.gateway.config.GatewayConfig
@Configuration
public class GatewayConfig {@Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();// 映射地址转发routes.route("path_route_dfun", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();return routes.build();}
}
浏览: http://localhost:9527/guonei
可见路由被转发到了相应地址
7.5 通过微服务名动态路由
问题: 路由写死无法实现负载均衡
scdemo-payment模块启动两个实例8001和8002
VM Options: -Dserver.port=8002
修改网关配置
i 开启动态路由
ii 替换uri
spring:application:name: scdemo-gatewaycloud:gateway:discovery:locator:# 开启从注册中心动态创建路由功能,根据微服务名路由enabled: trueroutes:# 路由id,没有固定规则但要求唯一,建议配合服务名- id: payment_route# 匹配后提供服务的路由地址uri: lb://scdemo-payment
# uri: http://localhost:8001predicates:# 断言,路径相匹配的进行路由- Path=/payment/get/**- id: payment_route2uri: lb://scdemo-payment
# uri: http://localhost:8001predicates:- Path=/payment/lb/**
验证:
多次访问:
http://localhost:9527/payment/lb
可见端口切换
7.6 断言-Predicate
配置
predicates:- Path=/payment/lb/**# 在该时间之后访问才有效 Before和Between的使用类似
# - After=2020-07-27T18:25:25.078+08:00[Asia/Shanghai]# 需要携带此cookie才能访问
# - Cookie=username,vincent# 请求头要有X-Request-Id且属性值为整数的正则表达式- Header=X-Request-Id, \d+# 此外还有Host\Method\Path等
获取上述时间格式的测试类
public class T2 {public static void main(String[] args) {ZonedDateTime zbj = ZonedDateTime.now();System.out.println(zbj);}
}
配置文件predicates注释逐行放开,使用curl测试
# 测试cookie
curl http://localhost:9527/payment/lb --cookie "username=vincent"
# 测试header
curl http://localhost:9527/payment/lb -H "X-Request-Id:123"
7.7 过滤器-Filter
官方提供了一系列的GatewayFilter
生命周期: Pre\Post
类型: 单一\全局
Filter作用:
全局日志记录\统一网关鉴权等
自定义过滤器
com.gaoliang.springcloud.gateway.filter.MyLogGatewayFilter
实现GlobalFilter, Ordered接口
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {/*** 过滤方法*/@Overridepublic 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("******非法用户*******");// 不被接受状态码exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);return exchange.getResponse().setComplete();}return chain.filter(exchange);}/*** 加载过滤器顺序,数值越小优先级越高*/@Overridepublic int getOrder() {return 0;}
}
浏览器访问:
http://localhost:9527/payment/lb?uname=vincent
可以正常访问,去掉uname参数无法访问
8.配置中心-Spring Cloud Config
8.1 概述
cloud config\cloud bus没有停更进维,但渐渐被nacos渠道
nacos,注册中心\配置\bus->稳了
问题
多模块引用同一配置(如数据库配置)以及多环境配置,修改繁琐,重复配置
spring cloud config提供中心化的外部配置
服务端是独立微服务应用,为客户端提供配置信息,以及加密\解密等
客户端启动时从配置中心加载配置,默认通过git存储(有助于版本控制,可通过git客户端方便的管理)
config功能
集中管理配置
多环境配置,动态更新
运行期间动态调整
REST接口暴露配置信息
8.2 服务端获取配置信息
a.新建仓库
github新建仓库,springcloud-config
# 克隆
git clone https://github.com/Object520/springcloud-config.git
b.仓库添加三个配置文件
config-dev.yml
config:info: master branch,springcloud-config/config-dev.yml version=1
config-test.yml
config:info: master branch,springcloud-config/config-test.yml version=1
config-prod.yml
config:info: master branch,springcloud-config/config-prod.yml version=1
c.新建模块
scdemo-config-server模块
依赖
<dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-config-server</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><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.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--热部署插件--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency></dependencies>
配置
server:port: 3344spring:application:name: scdemo-config-servercloud:config:server:git:uri: https://gitee.com/iacs/scdemo-config.gitusername: wu_peizhen@126.compassword: ww458141458141search-paths:- scdemo-config# 读取分支label: master# 服务注册到Eureka
eureka:client:service-url:defaultZone: http://localhost:7001/eureka
启动类
com.gaoliang.springcloud.configserver.ConfigServerApplication
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {public static void main(String[] args) {SpringApplication.run(ConfigServerApplication.class, args);}
}
启动eureka和config模块,访问:
格式: /{label}/{application}-{profile}.yml
http://localhost:3344/master/config-dev.yml
省略分支,默认master
http://localhost:3344/config-dev.yml
可见配置内容
访问:
http://localhost:3344/config/dev/master
可返回json串
8.3 客户端获取配置信息
新建模块
scdemo-config-client
依赖与server相同,只需把spring-cloud-config-server修改为
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId></dependency>
配置
bootstrap.yml是系统级的,优先级更高
server:port: 3355spring:application:name: scdemo-config-client# config客户端配置cloud:config:# 分支名称label: master# 配置文件名称name: config# 读取后缀名称profile: dev# 配置中心地址uri: http://localhost:3344# 服务注册到Eureka
eureka:client:service-url:defaultZone: http://localhost:7001/eureka
c.启动类
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientApplication {public static void main(String[] args) {SpringApplication.run(ConfigClientApplication.class, args);}
}
新建Controller
com.gaoliang.springcloud.controller.ConfigClientController.java
@RestController
@RefreshScope
public class ConfigClientController {// 因为config仓库以rest形式暴露,所以所有客户端都可以通过config服务端访问到github上对应的文件信息@Value("${config.info}")private String configInfo;@Value("${server.port}")private String serverPort;@GetMapping("/configInfo")public String getConfigInfo() {return "serverPort: " + serverPort + "\t\n\n configInfo" + configInfo;}
}
启动项目,访问: http://localhost:3355/configInfo
可以获取到配置信息
修改bootstrap.yml的分支名称和profile即可获取其他分支其他环境的配置
8.4 配置实时刷新
问题1:
修改git上配置文件的内容,服务端获取的配置实时更新但客户端接口获取的配置没有实时更新
客户端重启后可以获取,但生产环境重启繁琐
curl -X POST "http://localhost:3355/actuator/refresh"
客户端配置实时更新
i. 确保依赖了spring-boot-starter-actuator
ii. 配置
# 暴露监控端点
management:endpoints:web:exposure:include: "*"
iii. controller添加@RefreshScope注解
@RefreshScope
public class ConfigClientController {
...
此时配置仍无法实时刷新,需要发送post请求刷新
iv.post请求刷新配置
curl -X POST "http://localhost:3355/actuator/refresh"
问题2:
如果有多台客户端,需要写多个脚本刷新多个客户端配置,繁琐;
使用消息总线解决.
9.消息总线-Spring Cloud Bus
9.1 概述
可实现配置广播刷新,并对个别实例差异化处理
支持两种消息代理: rabbitmq和kafka
使用消息代理构建共用消息主题,消息被所有实例监听和消费
基本原理
实例监听mq中同一个topic,一个服务刷新数据时,把信息放入topic中,其他实例得到通知更新自身配置
9.2 rabbitmq环境搭建
centos7安装rabbitmq(自备环境)
9.3 Bus动态刷新全局广播配置
两种设计思想
i.消息总线触发一个客户端refresh,从而刷新所有客户端配置
ii.消息总线触发一个服务端refresh,从而刷新所有客户端
选型
i打破了微服务唯一职责性,破坏各节点对等性
i具有局限性,如微服务迁移时,网络地址发生变化,要做到自动刷新,就会增加更多的修改
因此选择ii
项目配置
i. scdemo-config-server模块
添加依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bus-amqp</artifactId></dependency>
添加配置
** 注意:
rabbitmq是spring的子项,否则项目不会报错,但执行刷新请求会报错,项目会连接localhost:5672的mq地址
spring:# rabbitmq相关配置rabbitmq.:host: 192.168.68.101port: 5672username: guestpassword: guest# rabbitmq相关配置,暴露bus刷新配置端点
management:endpoints:web:exposure:include: "bus-refresh"
ii. scdemo-config-client模块
添加依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bus-amqp</artifactId></dependency>
添加配置
spring:# rabbitmq相关配置rabbitmq.:host: 192.168.68.101port: 5672username: guestpassword: guest
scdemo-config-client启动3355和3366两个实例
此时修改git配置,config-server实时更新,两个客户端实例不更新
iii.执行脚本
curl -X POST "http://localhost:3355/actuator/refresh"
可见两个客户端配置同时被刷新
rabbitmq管控台可以看到springCloudBus exchange
9.4 Bus动态刷新定点通知
# 通过路径指定实例: scdemo-config-client:3355
curl -X POST "http://localhost:3344/actuator/bus-refresh/scdemo-config-client:3355"
附: 异常
Failed to configure a DataSource: 'url' attribute...
1.mvn install后问题解决
2.项目依赖了mybatis的druid的starter但没有配置数据库,去掉依赖后正常启动
Caused by: org.eclipse.jgit.api.errors.TransportException: https://gitee.com/iacs/scdemo-config.git: Authentication is required but no CredentialsProvider has been registered
Spring Cloud入门上相关推荐
- Spring Cloud 入门 之 Config 篇(六)
一.前言 随着业务的扩展,为了方便开发和维护项目,我们通常会将大项目拆分成多个小项目做成微服务,每个微服务都会有各自配置文件,管理和修改文件起来也会变得繁琐.而且,当我们需要修改正在运行的项目的配置时 ...
- Spring Cloud 入门 之 Zuul 篇(五)
一.前言 随着业务的扩展,微服务会不对增加,相应的其对外开放的 API 接口也势必增多,这不利于前端的调用以及不同场景下数据的返回,因此,我们通常都需要设计一个 API 网关作为一个统一的 API 入 ...
- Spring Cloud 入门 之 Hystrix 篇(四)
一.前言 在微服务应用中,服务存在一定的依赖关系,如果某个目标服务调用慢或者有大量超时造成服务不可用,间接导致其他的依赖服务不可用,最严重的可能会阻塞整条依赖链,最终导致业务系统崩溃(又称雪崩效应). ...
- Spring Cloud 入门 之 Feign 篇(三)
一.前言 在上一篇文章<Spring Cloud 入门 之 Ribbon 篇(二)> 中介绍了 Ribbon 使用负载均衡调用微服务,但存在一个问题:消费端每个请求方法中都需要拼接请求服务 ...
- Spring Cloud 入门 之 Ribbon 篇(二)
一.前言 上一篇<Spring Cloud 入门 之 Eureka 篇(一)> 介绍了微服务的搭建,服务注册与发现.但在文章中留了一个小尾巴--如何正确使用 Eureka 进行服务发现并调 ...
- Spring Cloud 入门 之 Eureka 篇(一)
一.前言 Spring Cloud 是一系列框架的有序集合.它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册.配置中心.消息总线.负载均衡.断路器.数据 ...
- Spring Cloud入门教程(二):客户端负载均衡(Ribbon)
对于大型应用系统负载均衡(LB:Load Balancing)是首要被解决一个问题.在微服务之前LB方案主要是集中式负载均衡方案,在服务消费者和服务提供者之间又一个独立的LB,LB通常是专门的硬件,如 ...
- Spring Cloud入门-Admin服务监控中心(Hoxton版本)
文章目录 Spring Cloud入门系列汇总 摘要 Spring Boot Admin 简介 创建admin-server模块 创建admin-client模块 监控信息演示 结合注册中心使用 修改 ...
- Spring Cloud入门-Sentinel实现服务限流、熔断与降级(Hoxton版本)
文章目录 Spring Cloud入门系列汇总 摘要 Sentinel简介 安装Sentinel控制台 创建sentinel-service模块 限流功能 创建RateLimitController类 ...
最新文章
- day042前端之HTML
- spm oracle cloud,oracle11g新特点——SQLPlanManagement(SPM)-Oracle
- 《Java编程思想》10.6 匿名内部类奇怪的代码
- B1007 素数对猜想
- MyEclipse运行时自动保存
- [多线程] Thread
- 双非本科上岸北大,复试成绩专业第一!
- 大班音乐机器人反思_大班音乐教案:机器人教案及教学反思
- AI产品开发的核心原则:以研究为核心驱动
- 【Python古诗词鉴赏小程序】千古绝唱,精选中国最美古诗句,经典咏流传~
- Origin2021学习版申请与安装
- 2021华为秋招算法工程师面试经历(实习过)
- 计算机网络实验|DNS 域名服务协议
- 财务会计之借贷记账法的【科目方向】和【科目余额方向】分析
- java xml特殊字符_mybatis xml中特殊字符处理及特殊符号
- 负载均衡器之F5和Nginx
- QQ好友不在线也可发送自定义表情(转)
- 支付宝支付(详细版)
- LeetCode刷题记 --- pta 7- 4
- 基于云服务器 B/S模式 JavaWeb RFID 图书借阅管理系统
热门文章
- pulltorefresh刷新
- 企业经济性裁减人员规定——劳动合同法小全书
- 测试手机信号格数软件,手机信号格数显示测试是怎么测试的啊?帮忙指导一下吧...
- 2021-06-23对深度学习模型进行更小,更快,更好的综述——微软研究院
- 深度揭秘亚马逊无货源运营思路,掌握运营技巧
- 咨询入门教程之二--ERP项目咨询顾问的五大能力
- 办公室局域网打印机共享设置
- VK1621/VK1622/VK1623/VK1625有什么稳定的LCD液晶显示驱动芯片支持多种封装,提供专业工程服务
- 【转】与BT下载相关的概念
- 如何开发微信公众号以及如何运营微信公众号