0. 反馈与复习
反馈:
复习:
1. 今日内容
1.模拟微服务
2.cloud介绍
3.eureka使用
a.使用
b.集群
c.自动配置实现
d.详解
4.ribbon使用
a.使用
b.随机负载策略配置
c.服务调用(源码查看)
d.分析ribbon组件
5.hystrix使用
a.介绍
b.雪崩效应
c.熔断器使用
d.扩展使用
e.原理分析
f.监控使用
一、模拟微服务
1. 创建用户服务
需求:
根据id查询用户
步骤:
- 创建tb_user表
- 创建用户服务:lombok、web、mybatis、mysql
- 创建三层架构提供根据id查询用户的功能
- 在配置文件中配置db配置、mybatis配置
- 访问测试
过程:
-
建表语句
DROP TABLE if EXISTS tb_user; CREATE TABLE `tb_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(100) DEFAULT NULL COMMENT '用户名', `password` varchar(100) DEFAULT NULL COMMENT '密码', `name` varchar(100) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄', `sex` int(11) DEFAULT NULL COMMENT '性别,1男,2女', `birthday` date DEFAULT NULL COMMENT '出生日期', `created` date DEFAULT NULL COMMENT '创建时间', `updated` date DEFAULT NULL COMMENT '更新时间', `note` varchar(1000) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户信息表'; -- ---------------------------- -- Records of tb_user -- ---------------------------- INSERT INTO `tb_user` VALUES ('1', 'zhangsan', '123456', '张三', '13', '1', '2006-08-01', '2019-05-16', '2019-05-16', '张三'); INSERT INTO `tb_user` VALUES ('2', 'lisi', '123456', '李四', '13', '1', '2006-08-01', '2019-05-16', '2019-05-16', '李四'); -- ---------------------------- -- select tb_user -- ---------------------------- SELECT * FROM tb_user;
-
代码
//pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;//主键id
private String username;//用户名
private String password;//密码
private String name;//姓名
private Integer age;//年龄
private Integer sex;//性别 1男性,2女性
private Date birthday; //出生日期
private Date created; //创建时间
private Date updated; //更新时间
private String note;//备注
}
//controller
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
/*
* @RequestMapping(value = "findById",method = RequestMethod.GET) 等同于 @GetMapping("findById")
*
* @RequestMapping(value = "findById",method = RequestMethod.POST) 等同于 @PostMapping
*
* 请求地址: http://localhost:8080/user/findById?id=1
*/
//@RequestMapping(value = "findById",method = RequestMethod.POST)
@GetMapping("findById")
public User findById(Integer id){
return userService.findById(id);
}
@GetMapping("findUserById/{id}")
public User findUserById(@PathVariable Integer id){
return userService.findById(id);
}
/*
请求地址: http://localhost:8080/user/findUserById/2
参数不一致如何去对应的情况
*/
@GetMapping("findUserById02/{id}")
public User findUserById02(@PathVariable("id") Integer uid){
return userService.findById(uid);
}
}
public interface UserService {
User findById(Integer id);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User findById(Integer id) {
return userDao.findById(id);
}
}
@Mapper
@Repository
public interface UserDao {
User findById(Integer id);
}
-
sql
<?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.itheima.dao.UserDao"> <select id="findById" resultType="user"> select * from tb_user WHERE id = #{id}; </select> </mapper>
-
配置
#db spring: datasource: #serverTimezone=UTC url: jdbc:mysql://127.0.0.1/itheima?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root #mybatis mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.itheima.pojo
2. 创建消费者服务
需求:
使用RestTemplate完成用户服务接口的调用
步骤:
- 创建消费者服务
- 使用RestTemplate调用用户服务
过程:
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("findUserById/{id}")
public User findUserById(@PathVariable Integer id){
//第一次的url
String url = "http://localhost:9091/user/findUserById/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
3. 存在问题
1.写死了---》url硬编码----》Eureka注册中心解决
2.无法完成服务的负载均衡调用-----》Ribbon负载调用解决
3.消费者服务无法感知用户服务的状态,导致用户服务宕机后消费者服务直接异常-----》Hystrix熔断器来解决
........
二、Cloud介绍
1. 概念
springcloud不是一个框架,是一个全家桶(KFC)。cloud是由一个一个的组件构成的,这些组件是用springboot来进行开发的。
2. 组件
3. 版本
三、Eureka使用
1. 搭建Eureka注册中心
步骤:
- 创建一个eureka book项目—》web/eureka-server
- 在启动类上添加EurekaServer注解声明是一个eureka服务
- 编写配置文件----》声明注册中心的地址
过程:
- 选择依赖
-
启动类
@SpringBootApplication @EnableEurekaServer //声明是一个注册中心 public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
-
配置文件
eureka: client: service-url: defaultZone: http://localhost:8761/eureka #注册中心的地址 register-with-eureka: false #禁止注册至 http://localhost:8761/eureka fetch-registry: false #禁止拉取服务列表 server: port: 8761 #服务应用名称 spring: application: name: eureka-server
2. 改造用户服务
需求:
将用户服务注册至eureka注册中心
步骤:
- 导入cloud版本管理
- 导入eureka-client依赖坐标
- 在启动类添加EurekaClient注解,声明是一个eureka客户端
- 修改配置文件----》服务的应用名称、声明eureka注册中心的地址
3. 改造消费者服务
需求:
将用户服务注册至eureka注册中心
步骤:
- 导入cloud版本管理
- 导入eureka-client依赖坐标
- 在启动类添加EurekaClient注解,声明是一个eureka客户端
- 修改配置文件----》服务的应用名称、声明eureka注册中心的地址
解决url硬编码问题:
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
/*
可以根据服务的应用名称从注册中心将服务的列表(ip+port)拉取下来
*/
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("findUserById/{id}")
public User findUserById(@PathVariable Integer id){
//第一次的url
/*
1.写死了---》url硬编码----》Eureka注册中心解决
2.无法完成服务的负载均衡调用-----》Ribbon负载调用解决
3.消费者服务无法感知用户服务的状态,导致用户服务宕机后消费者服务直接异常-----》Hystrix熔断器来解决
........
*/
//String url = "http://localhost:9091/user/findUserById/" + id;
//第二次的url --->Eureka注册中心解决
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
String url = instances.get(0).getUri() + "/user/findUserById/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
@GetMapping("getInstances")
public List<ServiceInstance> getInstances(){
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
return instances;
}
}
4. Eureka集群搭建(了解)
目的:
为了防止单点故障问题,大号废了练小号。
步骤:
- 修改hosts
- 复制两个总配置文件—》application-server1.yml、application-server2.yml,并且需要将总配置文件注释
- 使用idea创建eureka两个启动类—》需要指定启动类的配置文件
- 直接访问测试
过程:
-
hosts
127.0.0.1 eureka1 eureka2
-
多配置文件
-
application-server1.yml
eureka: client: service-url: defaultZone: http://eureka2:8762/eureka #注册中心的地址 instance: hostname: eureka1 server: port: 8761 #服务应用名称 spring: application: name: eureka-server
-
application-server2.yml
eureka: client: service-url: defaultZone: http://eureka1:8761/eureka #注册中心的地址 instance: hostname: eureka2 server: port: 8762 #服务应用名称 spring: application: name: eureka-server
-
-
idea创建多启动类
注意:
- 需要将总配置文件注释掉
5. 自动配置实现
-
在启动类添加@EnableEurekaServer----》会创建Marker.class
-
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
6. Eureka详解
客户端:
- 每隔30s发送一次心跳至eureka注册中心,最长时间是90s;
- 每隔60s拉取一次服务列表至本地(思考? 如果eureka服务停止运行,那么消费者服务是否还能够调用到用户服务?)
服务端:
- 失效剔除
- 每隔60s会执行一次定时任务去扫描过往90s内服务的状态,如果一次心跳都没有送达那么会将服务主动从服务列表移除
- 服务下线
- 客户端发送下线请求至eureka注册中心,eureka将服务从服务列表移除
- 自我保护机制
- 如果服务的心跳比例低于85%,那么会触发自我保护,一旦触发自我保护那么会锁定服务的列表,保证大部分服务的可用。
四、Ribbon
1. 使用
步骤:
- 通过idea创建两个用户服务的启动类
- 在RestTemplate上面添加ribbon注解
- 改造url请求地址----》通过服务名称的方式来调用
- 在用户服务的controller中获取端口号
过程:
- 指定用户服务的端口号: --server.port=9091
-
消费者服务添加注解
@Bean @LoadBalanced //使用ribbon进行负载调用 public RestTemplate restTemplate(){ return new RestTemplate(); } //改造url请求地址----》通过服务名称的方式来调用 String url = "http://user-service/user/findUserById/" + id; User user = restTemplate.getForObject(url, User.class);
-
在用户服务的controller中获取端口号
@Value("${server.port}") private String port; @GetMapping("findUserById/{id}") public User findUserById(@PathVariable Integer id){ User user = userService.findById(id); user.setNote("生产者端口号是: " + port); return user; }
2. 随机负载策略配置
>**默认负载策略是availabilityFilteringRule,是将健康服务过滤出来进行轮训的操作。**
# 修改服务地址轮询策略,默认是轮询,配置之后变随机,RandomRule
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
3. 服务调用(源码查看)
--org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
--org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor#intercept
--org.springframework.cloud.client.loadbalancer.LoadBalancerClient#execute
--org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute
--org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute
//根据服务的应用名称获取服务列表
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
//根据负载规则选取服务
Server server = this.getServer(loadBalancer, hint);
//根据选取出的服务进行远程调用
RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
4. Ribbon组件
IRule:制定负载均衡策略
Ping: 心跳机制,用于检测服务是否健康
ServerList: 包含所有的服务列表
ServerListFilter:包含的是满足过滤规则的服务列表
ServerListUpdater: 更新的服务列表
LoadBalancer: 负责调度
五、Hystrix
1. 介绍
图标是豪猪,近亲—》老鼠。满身有刺,刺起到了保护作用。hystrix是netfilx公司出品的一个容错库,可以提供服务的降级与熔断。
2. 雪崩效应
由于下游服务发生异常导致上游服务级联失败的情况就称为是雪崩效应
3. 熔断器使用
改造消费者步骤:
- 整合熔断器依赖
- 在启动类开启熔断器支持
- 修改consumerController编写默认的服务降级方法
过程:
-
导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
启动开启熔断器支持
/* @SpringBootApplication @EnableEurekaClient //声明是一个eureka客户端 ---》@EnableDiscoveryClient @EnableDiscoveryClient:支持的注册中心有:eureka/zk/nacos/apollo @EnableEurekaClient: 仅支持eureka注册中心 @EnableCircuitBreaker //开启熔断器的支持 */ @SpringCloudApplication //一个顶三 public class ConsumerServiceApplication { public static void main(String[] args) { SpringApplication.run(ConsumerServiceApplication.class, args); } @Bean @LoadBalanced //使用ribbon进行负载调用 public RestTemplate restTemplate(){ return new RestTemplate(); } }
-
编写默认熔断方法
-
返回值类型必须与原方法类型一致
-
参数列表必须与原方法一致
-
@GetMapping("findUserById/{id}") @HystrixCommand(fallbackMethod = "fallback") public User findUserById(@PathVariable Integer id){ //........ } /** * @Description: 默认服务降级方法 * 1.返回值类型必须与原方法一致 * 2.参数列表必须与原方法一致 */ public User fallback(Integer id){ User user = new User(); user.setId(id); user.setNote("默认服务降级方法..."); return user; }
-
4. 扩展使用(了解即可,一般不使用)
@RestController
@RequestMapping("consumer")
@DefaultProperties(defaultFallback = "defaultFall")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
/*
可以根据服务的应用名称从注册中心将服务的列表(ip+port)拉取下来
*/
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("findUserById/{id}")
//@HystrixCommand(fallbackMethod = "fallback")
@HystrixCommand
public User findUserById(@PathVariable Integer id){
//第一次的url
/*
1.写死了---》url硬编码----》Eureka注册中心解决
2.无法完成服务的负载均衡调用-----》Ribbon负载调用解决
3.消费者服务无法感知用户服务的状态,导致用户服务宕机后消费者服务直接异常-----》Hystrix熔断器来解决
........
*/
//String url = "http://localhost:9091/user/findUserById/" + id;
//第二次的url
/* List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
String url = instances.get(0).getUri() + "/user/findUserById/" + id;*/
//第三次url
String url = "http://user-service/user/findUserById/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
/**
* @Description: 默认服务降级方法
* 1.返回值类型必须与原方法一致
* 2.参数列表必须与原方法一致
*/
public User fallback(Integer id){
User user = new User();
user.setId(id);
user.setNote("默认服务降级方法...");
return user;
}
/*
* @Description: 当前类所有方法的默认服务降级方法要求: 1.返回值类型必须与原方法一致;2.不能有参数列表
*/
public User defaultFall(){
User user = new User();
user.setNote("当前类默认服务降级方法...");
return user;
}
/**
* @Description: 获取服务列表
*/
@GetMapping("getInstances")
public List<ServiceInstance> getInstances(){
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
return instances;
}
}
5. 原理分析
# 配置熔断策略:
hystrix:
command:
default:
circuitBreaker:
forceOpen: false #强制打开熔断器 默认false关闭的
requestVolumeThreshold: 20 # 熔断触发最小请求次数,默认值是20
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 5000 # 熔断后休眠时长,默认值5秒
execution:
isolation:
thread:
timeoutInMilliseconds: 1000 #熔断超时设置,默认为1秒
服务降级:
调用默认的服务降级方法,但是对于正常的访问资源不会降级。
服务的熔断:
调用默认的服务降级方法,区别是即使可以正常访问的资源都会进入服务降级方法
场景:
- 在userController中添加判断如果id=1抛出异常
- 测试—》请求id=1的资源5次,请求id=2的资源,验证是否可以请求成功?
- 测试—》请求id=1的资源20次,请求id=2的资源,验证是否可以请求成功?