一、主流开发架构
1、SOA架构
面向服务编程
降低耦合
服务粒度比较粗
- Apache Dubbo
- 协议:RPC【远程过程调用】
- 应用开发的对内接口
- 协议:RPC【远程过程调用】
2、微服务架构
完全解耦
服务粒度:根据业务,单独把功能抽取成一个独立的服务,对外暴露HTTP接口
电商类
- 分类服务
- 商品服务【增加、删除、修改、简单条件的搜索(对卖家)】
- 搜索服务(对买家)【Elasticsearch】
- 首页服务
- 内容服务
- …
教育类
- www.qfedu.com
- SpringCloud Netflix
- Eureka、Ribbon、OpenFeign、Hystrix、Zuul、Config、Bus…
- SpringCloud Alibaba
- Nacos、Sentinel…
是一套工具集,是多个框架的组合
二、微服务
1、概述
-
是一种开发设计的思想,是SOA架构的进化后产物
-
微服务架构风格是将具备独立功能的业务模块拆成一个服务【应用】,对外暴露HTTP接口,提供对其他服务进行远程通信的功能。
-
一个微服务可以具备自己独立的业务功能和自己独立的数据库及独立的开发语言
-
一个微服务就是一个SpringBoot项目
三、SpringCloud
1、概述
-
微服务架构是一种风格,是一个种思想,是一种理念
-
SpringCloud是具体的落地实现,是技术栈
-
是多个框架的组合
-
实现技术栈
-
SpringCloud Netflix
Eureka、Ribbon、OpenFeign、Hystrix、Zuul、Config、Bus…
-
SpringCloud Alibaba
Nacos、Sentinel…
-
-
2、微服务存在问题
- 服务数量多,调用关系复杂【服务注册与发现 —- Eureka(优瑞卡)】【Nacos】
- 服务治理【Eureka(优瑞卡)】
- 服务之间如何相互通信【Ribbon(瑞本)】
- 服务熔断与降级【Hystrix(害四缺个四)】【Sentinel】
- 统一拦截或过滤【Zuul】【认证、限流】
- 分布式配置中心、Bus【Config、Bus(RabbitMQ)】【Nacos、Bus(RocketMQ)】
四、版本选择
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E |
---|
- SpringBoot:2.3.2.RELEASE
- SpringCloud:Hoxton.SR9
- SpringCloud Alibaba:2.2.6.RELEASE
五、实现HTTP远程接口调用
1、需求
- 准备两个微服务
- 设备服务提供者【device-provider-8001】
- 设备服务消费者【device-consumer-9001】
- 服务消费者去调用服务提供者的一个接口
- hello
2、实现
2.1 父工程【java2107-springcloud-parent】
- pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
2.2 子模块-服务提供者【device-provider-8001】
继承父工程
- pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
-
通过插件生成引导类和配置文件
插件
|
- 引导类
@SpringBootApplication
public class DeviceProviderApplication8001 {
public static void main(String[] args) {
SpringApplication.run(DeviceProviderApplication8001.class, args);
}
}
- 配置文件
server:
port: 8001
- Controller接口
@RestController
@RequestMapping("/device")
public class DeviceController {
@Value("${server.port}")
String port;
@GetMapping("/hello")
public String hello(){
return "hello:" + port;
}
}
2.2 子模块-服务消费者【device-consumer-9001】
继承父工程
- pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
-
通过插件生成引导类和配置文件
- 引导类
@SpringBootApplication public class DeviceConsumerApplication9001 { public static void main(String[] args) { SpringApplication.run(DeviceConsumerApplication9001.class, args); } }
- 配置文件
server: port: 9001
-
增加远程调用接口bean【RestTemplate】
RestTemplate |
---|
|
- Controller接口
@RestController
@RequestMapping("/device/consumer")
public class DeviceConsumerController {
@Autowired
RestTemplate restTemplate;
@Value("${server.port}")
String port;
@GetMapping("/hello")
public String hello(){
//调用8001的 /device/hello 接口
/**
* 参数一:请求的url地址
* 参数二:目标接口返回值类型
*/
String url = "http://localhost:8001/device/hello";
String result = restTemplate.getForObject(url, String.class);
return port + " --> " + result;
}
}
3、RestTemplate
3.1 API
- Get请求
getForObject方法 |
---|
getForEntity方法 |
---|
- Post请求
- postForObject、postForEntity
请求方式跟Get不一样,其他功能一模一样
3.2 具体实现
3.2.1 新增服务提供者8002
除Controller接口和端口号不一样,其他都跟8001服务提供者一样
- Controller接口
@RestController
@RequestMapping("/device")
public class DeviceController {
@Value("${server.port}")
String port;
@GetMapping("/get")
public String testGetVars(String name, Integer num){
return "get:" + name + "-" + num;
}
}
3.2.2 新增服务消费者9002
除Controller接口和端口号不一样,其他都跟9001服务提供者一样
- Controller接口
/**
* RestTemplate getForEntity
* @param name
* @param num
* @return
*/
@GetMapping("/get/map/entity")
public String getMapEntity(String name, Integer num){
//调用8002的 /device/get 接口,需要传递两个参数
//http://localhost:8002/device/get?name=lucy&num=10
String url = "http://localhost:8002/device/get?name={name}&num={num}";
Map<String, Object> map = new HashMap<>();
map.put("name", name);
map.put("num", num);
/**
* 参数一:请求的url地址
* 参数二:目标接口返回值类型
* 参数三:Map
*/
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, map);
System.out.println("---------响应头---------");
HttpHeaders headers = responseEntity.getHeaders();
for (String key : headers.keySet()) {
System.out.println(key + ":" + headers.get(key));
}
System.out.println("----------状态码-----------");
HttpStatus status = responseEntity.getStatusCode();
System.out.println(status.value() + ":" + status.getReasonPhrase());
System.out.println("----------响应体-----------");
//响应体
String result = responseEntity.getBody();
System.out.println(result);
return port + " --> " + result;
}
/**
* RestTemplate Map参数,map中的key由占位符的名称决定
* @param name
* @param num
* @return
*/
@GetMapping("/get/map")
public String getMap(String name, Integer num){
//调用8002的 /device/get 接口,需要传递两个参数
//http://localhost:8002/device/get?name=lucy&num=10
String url = "http://localhost:8002/device/get?name={name}&num={num}";
Map<String, Object> map = new HashMap<>();
map.put("name", name);
map.put("num", num);
/**
* 参数一:请求的url地址
* 参数二:目标接口返回值类型
* 参数三:Map
*/
String result = restTemplate.getForObject(url, String.class, map);
return port + " --> " + result;
}
/**
* RestTemplate 可变参数:参数只需要占位符即可 {1}
* @param name
* @param num
* @return
*/
@GetMapping("/get/vars")
public String getVars(String name, Integer num){
//调用8002的 /device/get 接口,需要传递两个参数
//http://localhost:8002/device/get?name=lucy&num=10
String url = "http://localhost:8002/device/get?name={1}&num={2}";
/**
* 参数一:请求的url地址
* 参数二:目标接口返回值类型
* 参数三:可变参数
*/
String result = restTemplate.getForObject(url, String.class, name, num);
return port + " --> " + result;
}
getForObject【传递Map参数】 |
---|
3.2.3 测试
9002访问8002即可 |
---|
3.3 传递Cookie
使用Post请求
3.3.1 8002新增接口
8002新增接口 |
---|
3.3.2 9002新增接口
9002新增接口 |
---|
3.3.3 postman测试
postman测试 |
---|
3.4 微服务太多,可以使用DashBoard
DashBoard |
---|
六、服务的注册与发现
1、概述
- 注册:每个微服务,都需要注册到注册中心【名称=服务列表】
- 发现:服务的消费者在需要调用服务提供者接口时,可以根据嗠名称从注册中心订阅服务列表,选择一个可用服务提供者进行远程调用
可以实现服务注册与发现的组件
- SpringCloud Netflix:Eureka
- SpringCloud Alibaba:Nacos
- Apache Dubbo:Zookeeper
2、工作原理
工作原理 |
---|
3、EurekaServer搭建
3.0 父工程锁定SpringCloud的版本
<!-- 使用import来实现类似多继承 -->
<dependencyManagement>
<!-- spring-cloud的依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<!-- import: 可以实现类似于多重继承,子模块的版本就不用写了 -->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3.1 创建SpringBoot应用
- eureka-server-8761
3.2 导入一个依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
3.3 application.yml配置文件
server:
port: 8761
spring:
application:
name: eureka-server
#服务注册中心
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka/
#是否把自己注册到注册中心
register-with-eureka: false
#是否要拉取服务
fetch-registry: false
3.4 引导类打一个注解
@EnableEurekaServer |
---|
3.5 启动、访问
localhost:8761 |
---|
4、服务注册【搭建Eureka的客户端】
4.1 在服务提供者与服务消费者导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
4.2 application.yml配置文件
- 服务提供者
spring:
application:
name: device-provider
#服务注册中心
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
- 服务消费者
spring:
application:
name: device-consumer
#服务注册中心
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
4.3 引导类打注解【目前版本可以不打】
服务提供者【@EnableDiscoveryClient】 |
---|
- 服务消费者一样
4.4 访问Eureka注册中心
localhost:8761 |
---|
5、服务发现
服务消费者要使用服务提供者的接口
- 9001调用8001,能够成功即可
七、客户端负载均衡【Ribbon】
客户端:服务消费者9001
1、在服务消费者【device-consumer-9001】导入依赖
spring-cloud-starter-netflix-eureka-client 已经包含了ribbon |
---|
2、在RestTemplate上打一个注解
@LoadBalanced |
---|
3、Controller新增一个接口
@GetMapping("/hello2")
public String hello2(){
//使用@LoadBalanced后,需要指定服务名称来调用
String url = "http://DEVICE-PROVIDER/device/hello";
String result = restTemplate.getForObject(url, String.class);
return port + " --> " + result;
}
4、实现负载均衡效果
4.1 准备两台服务提供者
- 复制
device-provider-8001
,修改相关端口为8003 - 启动
- 查看注册中心,看到
device-provider
有两个实例
- 查看注册中心,看到
4.2 查看负载均衡效果
发现:默认是轮询
- 访问
http://localhost:9001/device/consumer/hello2
,能够看到在服务提供者的两个实例中轮询
5、负载均衡策略
5.1 核心接口
- IRule:可以来指定负载均衡策略
5.2 支持负载均衡策略
- RandomRule:随机策略
- RoundRibbonRule:轮询策略【默认】
- WeightedResponseTimeRule:在轮询策略的基础上,后续会根据服务的响应时间,自动给你分配权重
- BestAvailableRule:根据被调用方并发数最小的去分配
- RetryRule:重试,调用失败过多久再次尝试【默认为500ms】
5.3 配置
- 注解【全局】
在服务消费者的RibbonConfig中增加如下配置
/**
* 指定负载均衡策略为随机
* @return
*/
@Bean
public IRule rule(){
return new RandomRule();
}
- 配置文件【可以指定具体服务】
注释掉全局的配置,然后在application.yml中增加如下配置
#指定具体服务的负载均衡策略
DEVICE-PROVIDER: # 编写服务名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 具体负载均衡使用的类
八、Eureka的细节
Eureka的细节与自我保护机制 |
---|
1、心跳机制
-
Eureka客户端会定时【每30秒】向注册中心发送心跳包进行续约
-
Eureka客户端超过指定时间【90秒】都没有向注册中心发送心跳包,那么注册中心就会认为服务已不可用,就会从服务列表中把该服务给剔除
-
Eureka客户端【消费者】会定时向注册中心更新需要服务列表的信息
配置在EurekaClient
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10086/eureka/
#每隔30秒去注册中心更新服务提供者的列表信息[配置在服务消费者]
registry-fetch-interval-seconds: 30
instance:
#每30秒向注册中心发送心跳包
lease-renewal-interval-in-seconds: 30
#90秒没向注册中心发送心跳包,那么注册中心就会剔除服务
lease-expiration-duration-in-seconds: 90
2、自我保护机制
- 当短时间内,统计续约失败的比例,如果达到一定阈值【15分钟,续约的失败比例超过85%】,则会触发自我保护的机制。在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。自我保护开关(eureka.server.enable-self-preservation: false),默认是自动开启的
配置在EurekaServer
eureka:
server:
#关闭自我保护机制
enable-self-preservation: false
3、高可用
如果Eureka宕机了,是否会影响服务之间的调用?
- 对于服务消费者来说,是否经过一次调用了,如果调用过并且成功,会有本地缓存,可以进行远程调用。没有经过调用,那么无法进行远程调用
- 对于服务提供者来说,新服务就无法注册,老服务就无法续约
- Eureka保证的是AP
- 在分布式系统中,CAP是无法都满足的,且P【分区容错性是一定要保证的】
- C:一致性
- A:可用性
- P:分区容错性
- 在保证分区容错性的前提性,是选择保证CP还是AP
- Eukera 保证的AP
- 在无法做到数据一致性的情况下,保证系统可用是比较重要的。因为数据不一致,是可以有补救措施的,相对而言,付出的代价会小一点【张三、李四在同一时间买同一件商品,价格不一样,这时候,一般返补差价就可以解决了】
- Zookeeper 保证的CP
- 在无法做到系统可用的情况下,保证数据一致性是比较重要的。因为系统可用不可用,是可以有补救措施的,相对而言,付出的代价会大很多【张三在早上8点发现淘宝不可用,下午淘宝就上头条新闻了,造成的损失是很大的】
- 避免单点故障
3.1 实现步骤
3.1.1 搭建两台Eureka
- 需要向对方注册自己的信息
- 允许拉取服务列表
- 修改
eureka-server-8761
的配置文件【application.yml】
#单机版
#eureka:
# client:
# service-url:
# defautZone: http://localhost:8761/eureka/
# fetch-registry: false
# register-with-eureka: false
#集群
eureka:
client:
service-url:
defautZone: http://localhost:8762/eureka/
fetch-registry: true
register-with-eureka: true
-
复制
eureka-server-8761
项目,修改端口为8762- 配置文件【application.yml】
#集群 eureka: client: service-url: defautZone: http://localhost:8761/eureka/ fetch-registry: true register-with-eureka: true
3.1.2 修改device-provider-8001
和device-consumer-9001
配置文件
eureka:
client:
service-url:
#单机版
#defaultZone: http://localhost:8761/eureka/
#向两个注册中心注册信息
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
-
测试
- 启动两个Eureka,再启动两个微服务,正访问
- 停止8761的注册中心
- 过一会,大概1分钟左右,刷新8762,可以看到8001、9001服务信息被同步过来了
- 再尝试进行http调用,结果仍旧可以调得通
停止8761的注册中心,等8762同步数据
|
九、OpenFegin
1、概述
- 可以把服务之间的调用过程,变成基于接口的调用方法,如同
Controller调用Service一样
- 支持SpringMVC注解
- 底层是通过动态代理实现
- Feign是一个客户端的调用技术,是一个接口,用来替换掉RestTemplate
- 会动态生成代理对象,让代理对象去调用目标接口
- 默认集成了ribbon的负载均衡策略
https://spring.io/projects/spring-cloud-openfeign
2、实现
2.1 创建了一个新工程
- 【device-consumer-openfeign-9002】【服务提供者】
大家也可以改9001项目,增加openfeign依赖
- 导入Feign依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
2.2 创建一个Feign接口
- 接口上标记一个注解@FeignClient,指定需要调用的服务名
- 复制提供者控制器的方法,去掉方法体,修改注解为@RequestMapping,写上value
//path : 请求的统一前缀
@FeignClient(name = "device-provider", path = "/device")
public interface DeviceFeign {
//@RequestMapping(value = "/device/hello") //不指定path属性
@RequestMapping(value = "/hello")
String hello();
}
2.3 在引导类标记注解
@EnableFeignClients |
---|
2.4 修改消费者控制器的方法为Feign接口调用
消费者控制器接口 |
---|
3、Feign底层【动态代理】
Feign底层 |
---|
4、Feign参数处理
4.1 参数分类
- 普通参数:需要加上@RequestParam(name=“参数名”)
- 路径传参:需要加上@PathVariable(name=“参数名”)
- JSON:需要加上@RequestBody
- Cookie:需要通过拦截器来实现Cookie传递
4.2 服务提供者【8001】增加测试接口
//=================fegin参数传递开始==========
//普通参数
@GetMapping("/testVar")
public String testVar(Integer id){
return "testVar " + port + ":" + id;
}
//路径传参
@GetMapping("/testUrlVar/{name}")
public String testUrlVar(@PathVariable String name){
return "testVar " + port + ":" + name;
}
//多个参数
@GetMapping("/testManyVar")
public String testManyVar(Integer id, String name){
return "testManyVar " + port + ":" + id + " " + name;
}
//实体参数
@PostMapping("/testJson")
public Device testJson(@RequestBody Device device){
return device;
}
//实体参数
@PostMapping("/testJsonCookie")
public String testJsonCookie(@RequestBody Device device, @CookieValue(name = "token", required = false) String token){
System.out.println("device = " + device);
return token;
}
//=================fegin参数传递结束==========
4.3 修改服务消费者【9002】的Feign接口
- 增加拦截器【组装Cookie】
public class CookieInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
String token = "";
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttr = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = requestAttr.getRequest();
Cookie[] cookies = request.getCookies();
if(!Objects.isNull(cookies)) {
for (Cookie cookie : cookies) {
//客户端传递过来的是login_token
if("login_token".equals(cookie.getName())){
token = "token=" + cookie.getValue();
break;
}
}
}
System.out.println("token-->" + token);
requestTemplate.header(HttpHeaders.COOKIE, token);
}
}
- Feign接口增加方法
//=================fegin参数传递开始==========
//普通参数
@RequestMapping(value = "/testVar")
String testVar(@RequestParam(name = "id") Integer id);
//路径传参
@RequestMapping(value = "/testUrlVar/{name}")
String testUrlVar(@PathVariable(name = "name") String name);
//多个参数
@RequestMapping(value = "/testManyVar")
String testManyVar(@RequestParam(name = "id") Integer id, @RequestParam(name = "name") String name);
//实体参数
@RequestMapping(value = "/testJson")
Device testJson(@RequestBody Device device);
//实体参数
@RequestMapping(value = "/testJsonCookie")
//public String testJson(@RequestBody Device device, @CookieValue(name = "token") String token);
String testJsonCookie(@RequestBody Device device);
//=================fegin参数传递结束==========
- Fegin类上增加配置属性
Fegin类上增加配置属性 |
---|
- 控制器增加方法【DeviceConsumerController】
//使用Feign调用,参数详解
//普通参数
@GetMapping("/testVar")
public String testVar(Integer id){
String result = deviceFeign.testVar(id);
return port + "-->" + result;
}
//路径传参
@GetMapping("/testUrlVar/{name}")
public String testUrlVar(@PathVariable String name){
String result = deviceFeign.testUrlVar(name);
return port + "-->" + result;
}
//多个参数
@GetMapping("/testManyVar")
public String testManyVar(Integer id, String name){
String result = deviceFeign.testManyVar(id, name);
return port + "-->" + result;
}
//实体参数
@PostMapping("/testJson")
public String testJson(@RequestBody Device device){
Device device1 = deviceFeign.testJson(device);
return port + "-->" + device1.getDeviceName() + (device1.getPrice() * 100);
}
//实体参数
@PostMapping("/testJsonCookie")
public String testJsonCookie(@RequestBody Device device, @CookieValue(name = "login_token", required = false) String token){
//result : 就是token
String result = deviceFeign.testJsonCookie(device);
return port + "-->" + result;
}
//=================fegin参数传递结束==========
4.4 测试
测试Cookie |
---|
十、Hystrix
1、概述
- 熔断器、断路器【理解成生活中的
触电保护器
】 - 作用:就是保护微服务的正常调用,避免服务雪崩
2、微服务的雪崩现象
服务雪崩 |
---|
3、解决方案
2.1 服务降级
- 当某个服务不可用时,让对应的接口快速返回一个失败的结果【托底的数据】,以防止线程阻塞
2.2 线程隔离
- 让客户端和微服务之间使用的线程池
- 客户端请求到微服务:使用的是Tomcat的线程池
- 微服务之间的调用:使用的Hystrix的线程池
2.3 信号量
- 由信号量来合理的分配Tomcat的线程池
4、服务不可用的几种情况
- 网络延迟(故障)
- 服务在Eureka中被剔除了
- 服务超时【默认是1S】
- 异常
5、Hystrix的使用
5.1 在Ribbon去使用Hystrix
9001应用
- 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 开启熔断【引导类加注解】
@EnableHystrix //开启熔断
引导类加注解 |
---|
-
为需要熔断的接口编写降级方法
跟目标接口一样,只是方法名与方法体不一样
控制器方法
|
-
测试
- 先启动各个服务,正常调用
- 停掉8001,再调用,就会出现服务降级
http://localhost:9001/device/consumer/hello
|
5.2 在OpenFeign去使用Hystrix
5.2.1 修改提供者应用
-
让目标接口出现服务不可用的情况
- 修改8001的
/device/testVar
接口
制造异常 - 修改8001的
|
5.2.2 修改消费者应用【9002】
- 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 配置文件【application.yml】开启熔断开关
#开启熔断
feign:
hystrix:
enabled: true
- 如果使用了拦截器,则需要增加如下配置【application.yml】
#openfeign中如果使用到了拦截器,需要加该配置【信号量管理】
hystrix:
command:
default:
execution:
isolation:
#隔离策略之信号量
strategy: SEMAPHORE
- 编写一个服务降级实现类,让Feign使用该降级类
DeviceFeignFallback |
---|
DeviceFeign |
---|
5.3 测试超时熔断
提供者接口 |
---|
- 设置超时时间
消费者设置服务超时时间【全局配置】 |
---|
#openfeign中如果使用到了拦截器,需要加该配置【信号量管理】
hystrix:
command:
default:
execution:
isolation:
#隔离策略之信号量
strategy: SEMAPHORE
#线程隔离
thread:
timeoutInMilliseconds: 2000
#默认超时时间为1S
ribbon:
ReadTimeout: 2000
ConnectionTimeout: 2000
6、熔断器的工作原理
工作原理 |
---|
- 经过测试
- 版本更换一下
- SpringBoot:2.2.5.RELEASE
- SpringCloud:SR3
- 版本更换一下
6.1 配置熔断仪表盘【了解】
- 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
- 配置Servlet
@WebServlet("/hystrix.stream")
public class HystrixDashboardServlet extends HystrixMetricsStreamServlet {
}
- 引导类加注解
@EnableHystrixDashboard
@ServletComponentScan(basePackages = "com.qf.java2107.device.servlets")
- 访问
http://localhost:9001/hystrix
http://localhost:9001/hystrix |
---|
第一次看到的是一个Loading
监控熔断的界面 |
---|
6.2 测试熔断器效果
- 增加熔断方法
static Integer count = 0;
@HystrixCommand(fallbackMethod = "hystrixError", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
//请求总数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
//失败比例
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
//休眠时间
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000")
})
@RequestMapping("/test/hystrix")
public String testHystrix(Integer id){
if(id == 1) {
throw new RuntimeException("id不能为1");
}
return "服务正常:" + id;
}
public String hystrixError(Integer id){
count++;
return "熔断成功:" + count;
}
- 引导类增加注解
@EnableCircuitBreaker //开启熔断
- 测试
- 测试成功的接口
- 测试失败的接口,失败的比例达到你自己的配置,就会发现熔断器打开,那么这个时候,即使成功的请求,也会被熔断
- 如果没有配置,则使用的默认配置
达到熔断器打开的条件 |
---|
四阶段学习要求
1.课堂纪律
- 课堂上只做一件事情:就是听
- 跟着思路来听,不能走神,如果犯困,就站起来。
2.授课时间
-
8:00-8:50 晨读 (前一天的知识、面试题)
-
8:50-9:05 晨考(前一天的知识)
-
9:05-10:20 第一节课
-
10:20-10:40 课间休息
-
10:40-11:30 第二节课
-
11:30-12:00 整理上午的内容 上午答疑 完成上午的任务
-
14:00-15:20 第一节课
-
15:20-15:40 课间休息
-
15:40-16:30 第二节课
-
16:30-16:50 课间休息
-
16:50-18:00 整理整天的内容 下午答疑 完成作业
-
19:00-21:30 自习 自己思考:瞻前 顾后,需要独立思考的时间,这个时间绝对不能被看视频占用了。
-
21:30-22:30 小组成员一对一互面 (班长:拍一个小视频10s,发到微信群)
3.交流的通道
碰到问题怎么办?
- 思考:前端?后端?数据层?
- 自己查
- 相互讨论或者问老师
十一、服务网关-Zuul
1.网关的作用
在服务的调用过程中,如果都需要通过服务的ip和端口来调用,由于ip地址是变化的,所以调用方需要频繁的调整ip,这样就比较麻烦,通过网关来解决服务转发的功能。除此之外,网关还具有其他的作用:
- 智能路由(服务转发)
- 安全控制
- 限流和降级
- 服务聚合
2.网关的选型
网关非常重要,在企业中网关是一定要先开发的。网关可以使用以下组件来实现:
- nginx:负载均衡、路由的效果,但是功能较为单一,配置也不友好
- spring cloud gateway: 网关模仿了zuul
- spring cloud netflix Zuul: 目前较为主流、值得推荐使用的网关。Zuul具备以下功能:
- 路由转发(服务聚合)
- 错误回调(熔断器)
- 过滤器(服务降级、限流、安全控制)
- 自研的网关
3.Zuul的路由转发功能
- 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
- 编写配置文件
spring:
application:
name: zuul-server
server:
port: 8763
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# zuul路由表
zuul:
routes:
api-ribbon-consumer:
path: /api/ribbon/**
serviceId: DEVICE-CONSUMER
api-feign-consumer:
path: /api/feign/**
serviceId: DEVICE-CONSUMER-OPENFEIGN
- 启动类上打上注解@EnableZuulProxy
@SpringBootApplication
@EnableZuulProxy
public class ZuulServer8763Application {
public static void main(String[] args) {
SpringApplication.run(ZuulServer8763Application.class, args);
}
}
- 启动服务,通过path来访问指定的服务
访问服务A:http://localhost:8763/api/ribbon/device/consumer/hello
访问服务B:http://localhost:8763/api/feign/device/consumer/hello
4.路由表中的详细配置
- 统一前缀和剥离前缀
# zuul路由表
zuul:
routes:
api-ribbon-consumer:
path: /ribbon/**
serviceId: DEVICE-CONSUMER
# path中的路径没有被剥离,也就是说path中的路径是在服务中真实存在的。
strip-prefix: false
api-feign-consumer:
path: /feign/**
serviceId: DEVICE-CONSUMER-OPENFEIGN
# 统一前缀
prefix: /api
# strip-prefix: false
- 保护敏感路径
# zuul路由表
zuul:
routes:
api-ribbon-consumer:
path: /ribbon/**
serviceId: DEVICE-CONSUMER
# path中的路径没有被剥离,也就是说path中的路径是在服务中真实存在的。
strip-prefix: false
api-feign-consumer:
path: /feign/**
serviceId: DEVICE-CONSUMER-OPENFEIGN
# 统一前缀
prefix: /api
# 保护敏感路径:一旦路径中带有admin,表示敏感路径,不做转发
ignored-patterns: /**/admin/**
- 保护敏感请求头——携带cookie的话必须要配置
spring:
application:
name: zuul-server
server:
port: 8763
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# zuul路由表
zuul:
routes:
api-ribbon-consumer:
path: /ribbon/**
serviceId: DEVICE-CONSUMER
# path中的路径没有被剥离,也就是说path中的路径是在服务中真实存在的。
strip-prefix: false
api-feign-consumer:
path: /feign/**
serviceId: DEVICE-CONSUMER-OPENFEIGN
# 统一前缀
prefix: /api
# 保护敏感路径:一旦路径中带有admin,表示敏感路径,不做转发
# ignored-patterns: /**/admin/**
# 保护敏感头 把默认的sensitive-headers配置的cookie 删除掉
sensitive-headers:
# strip-prefix: false
5.实现错误回调(熔断)
- 编写错误回调配置类,实现FallbackProvider接口
- 指定要回调的服务
- 指定要回调的内容
package com.qf.zuul.server.fallback;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author Thor
* @公众号 Java架构栈
*/
@Component
public class MyZuulFallback implements FallbackProvider {
/**
* 如果要对zuul中配置的所有路由的服务进行错误回调(熔断),return null
* 如果要对指定的服务进行错误回调,return "服务ServiceID"
* @return
*/
@Override
public String getRoute() {
return "DEVICE-CONSUMER";
//return null;
}
/**
* 当服务出现错误后具体要返回的内容,在这个方法里定义
* @param route
* @param cause
* @return
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
//响应行:状态码
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
//响应行
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
}
//响应行
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.toString();
}
@Override
public void close() {
}
/**
* 响应体:
* 返回一个指定的json字符串
* @return
* @throws IOException
*/
@Override
public InputStream getBody() throws IOException {
//把对象转换成json字符串——jackson
ObjectMapper objectMapper = new ObjectMapper();
//封装一个携带数据的map
Map<String,Object> map = new HashMap<>();
map.put("code",1000);
map.put("message","fall back");
map.put("data",null);
//把map转换成json
String json = objectMapper.writeValueAsString(map);
//获得拥有json字节数据的字节流
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
return byteArrayInputStream;
}
/**
* 响应头:
* 返回的数据是json
* @return
*/
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
6.实现过滤器
1)zuul中过滤器的介绍
zuul有四种过滤器:
- pre
- routing
- post
- error
2)实现过滤器
继承ZuulFilter,配置过滤器
package com.qf.zuul.server.filters;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @author Thor
* @公众号 Java架构栈
*/
@Component
public class MyZuulFilter extends ZuulFilter {
@Autowired
private RedisTemplate redisTemplate;
/**
* 过滤器的类型
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 相同类型的过滤器的顺序
* 0 表示第一个执行
* @return
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 是否执行该过滤器:过滤器会被调用,但是可以通过这个方法来手动告知是否执行该过滤器。
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 存放的具体的业务逻辑,怎么过滤?
* 做一个计数器限流:
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
//获得一个操作zuul过滤器的上下文对象
RequestContext context = RequestContext.getCurrentContext();
String limitRedisKey = "my:limit";
Long count = redisTemplate.opsForValue().increment(limitRedisKey);
if(count<5){
if(count==1){
redisTemplate.expire(limitRedisKey,60, TimeUnit.SECONDS);
}
//放行
context.setSendZuulResponse(true);
return null;
}
//不放行
context.setSendZuulResponse(false);
context.setResponseStatusCode(200);
context.setResponseBody("request limited!");
return null;
}
}
3)实现多个过滤器的配合使用
- 第一个过滤器
package com.qf.zuul.server.filters;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* 验证登录的过滤器
* @author Thor
* @公众号 Java架构栈
*/
@Component
public class MyLoginFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
/**
* 验证是否已登陆
* 看cookie中有没有login_token
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
//1.获得Cookie
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest httpServletRequest = context.getRequest();
Cookie[] cookies = httpServletRequest.getCookies();
if(Objects.nonNull(cookies)){
for (Cookie cookie : cookies) {
if("login_token".equals(cookie.getName())){
//表示已登陆: ->sso的验证是否已登陆的接口 返回是否已登陆
context.set("loginToken",cookie.getValue());
//放行?
context.setSendZuulResponse(true);
return null;
}
}
}
//未登陆
//不放行?
context.setSendZuulResponse(false);
context.setResponseStatusCode(200);
context.setResponseBody("no login!");
return null;
}
}
- 第二个过滤器
package com.qf.zuul.server.filters;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @author Thor
* @公众号 Java架构栈
*/
@Component
public class MyZuulFilter extends ZuulFilter {
@Autowired
private RedisTemplate redisTemplate;
/**
* 过滤器的类型
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 相同类型的过滤器的顺序
* 0 表示第一个执行
* @return
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 是否执行该过滤器:过滤器会被调用,但是可以通过这个方法来手动告知是否执行该过滤器。
* 如果未登陆,第二个过滤器就不执行
* @return
*/
@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
String loginToken = (String) context.get("loginToken");
if(StringUtils.isNotBlank(loginToken)){
//已登陆
return true;
}
//未登陆
return false;
}
/**
* 存放的具体的业务逻辑,怎么过滤?
* 做一个计数器限流:
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
//获得一个操作zuul过滤器的上下文对象
RequestContext context = RequestContext.getCurrentContext();
String limitRedisKey = "my:limit";
Long count = redisTemplate.opsForValue().increment(limitRedisKey);
if(count<5){
if(count==1){
redisTemplate.expire(limitRedisKey,60, TimeUnit.SECONDS);
}
//放行
context.setSendZuulResponse(true);
return null;
}
//不放行
context.setSendZuulResponse(false);
context.setResponseStatusCode(200);
context.setResponseBody("request limited!");
return null;
}
}
7.服务聚合
十二、分布式配置中心-Spring Cloud Config
1.应用场景
服务越来越多,对服务的配置文件也需要统一管理及实现服务配置文件的热更新。
此时,就需要一个配置中心的服务专门来做这个事情。
2.实现分布式配置中心的服务端
- 准备动作
新建git仓库,往仓库中存放配置文件
- 创建分布式配置中心项目,引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
- 编写配置文件,注意端口得写死:8888
spring:
application:
name: config-server
cloud:
config:
server:
git:
# git仓库的地址
uri: https://gitee.com/nn214490523/java2107.git
# 具体的文件夹
search-paths: repo
# 默认的分支
default-label: master
# 账号密码
# username: zh
# password: 123456
server:
port: 8888
- 启动类上打上注解@EnableConfigServer
package com.qf.config.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServer8764Application {
public static void main(String[] args) {
SpringApplication.run(ConfigServer8764Application.class, args);
}
}
3.实现分布式配置中心客户端
在已有的服务中,去连接分布式配置中心服务端来获取配置文件。
- 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
- 编写配置文件
spring:
cloud:
config:
uri: http://localhost:8888
# 要获取的配置文件的名称
name: device-consumer
# 环境
profile: prod
# 分支
label: master
4.在外部切换多环境配置
- 方式一:idea中切换
把多个环境的文件准备好
在idea中启动配置中配置profile->dev
- 方式二:在启动jar包指定项目的环境
打包命令:
mvn clean package -Dmaven.skip.test=true
使用命令来指明环境
java -jar my-consumer-1.0-SNAPSHOT.jar --spring.profiles.active=test
十三、Nacos组件
1.Nacos介绍
Nacos组件是springcloud组件库中的一个较为推荐的注册中心和配置中心的实现组件。
- 作为注册中心
- 作为配置中心
2.安装Nacos注册中心
- 去官网下载 https://github.com/alibaba/nacos/releases/tag/1.4.2
- 安装:解压缩后,进入到bin文件夹中,在cmd中执行如下命令:
startup -m standalone
- 访问控制台:
localhost:8848
3.编写服务提供者注册到Nacos上
springboot初始化器连的代理服务器地址:https://start.aliyun.com
- 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 编写配置文件
# 应用名称
spring.application.name=my-provider
# 应用服务 WEB 访问端口
server.port=8001
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
spring.cloud.nacos.discovery.server-addr=localhost:8848
# 注册到 nacos 的指定 namespace,默认为 public
spring.cloud.nacos.discovery.namespace=public
- 编写服务接口
package com.qf.my.provider.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Thor
* @公众号 Java架构栈
*/
@RestController
@RequestMapping("/device")
public class MyDeviceController {
@GetMapping("/hello")
public String hello(){
return "hello nacos!";
}
}
- 启动服务即可
4.编写服务消费者
- 引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
- 编写配置文件
# 应用名称
spring.application.name=my-consumer
server.port=9001
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
spring.cloud.nacos.discovery.server-addr=localhost:8848
# 注册到 nacos 的指定 namespace,默认为 public
spring.cloud.nacos.discovery.namespace=public
- 编写bean配置类,注入RestTemplate,实现ribbon的通信
package com.qf.my.consumer.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author Thor
* @公众号 Java架构栈
*/
@Configuration
public class RestConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
- 编写接口,声明要向nacos订阅的服务,进行远程调用
package com.qf.my.consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.xml.ws.FaultAction;
/**
* @author Thor
* @公众号 Java架构栈
*/
@RestController
@RequestMapping("/device")
public class DeviceController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/sayHello")
public String sayHello() {
String url = "http://my-provider/device/hello";
String result = restTemplate.getForObject(url, String.class);
return result;
}
}
5.Nacos健康状态检查机制
服务注册:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。
服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。
服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存
服务健康检查:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)
6.Nacos实现配置中心的功能
跟Spring Cloud Config相同,Nacos实现的配置中心也需要两个角色:服务端(已经和注册中心整合在一起了)、客户端。
1)服务端设置配置文件
2)客户端的配置
- 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
- 编写bootstrap.properties(早于application.properties加载)
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
spring.cloud.nacos.config.username=nacos
spring.cloud.nacos.config.password=nacos
#spring.cloud.nacos.config.contextPath=/nacos
## 设置配置中心服务端地址
spring.cloud.nacos.config.server-addr=localhost:8848
# Nacos 配置中心的namespace。需要注意,如果使用 public 的 namcespace ,请不要填写这个值,直接留空即可
#spring.cloud.nacos.config.namespace=
spring.cloud.nacos.config.name=my-config
spring.cloud.nacos.config.file-extension=properties
spring.cloud.nacos.config.group=DEFAULT_GROUP
- 启动项目,体验热更新(在服务端修改配置,客户端能实时获取)
package com.qf.my.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MyConsumerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(MyConsumerApplication.class, args);
//反复的获取配置文件中的最新的user.name
for (;;) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
String userName = applicationContext.getEnvironment().getProperty("user.name");
System.out.println("user name :" +userName);
}
}
}
十四、整合springcloud组件完成demo
1.需求分析
2.建数据库表
- tb_user
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- tb_address
DROP TABLE IF EXISTS `tb_address`;
CREATE TABLE `tb_address` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`uid` bigint DEFAULT NULL COMMENT '用户id',
`address` varchar(255) DEFAULT NULL COMMENT '收件地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3.创建操作数据库实体的服务
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/65612.html