- 基础构建与回顾
- 前提准备
- Boot和Cloud版本选型
- Cloud组件停更说明
- 父工程创建
- DependencyManagement和Dependencies
- 支付模块构建
- 建module、改POM、写YML、主启动
- 基础业务编写
- 消费者订单模块
- 工程重构
- Eureka
- 基础知识
- EurekaServer单机服务端安装
- 服务8001入驻进eurekaServer
- Eureka集群原理说明
- Eureka集群环境构建
- 订单支付两微服务注册进Eureka集群
- 支付微服务集群配置
- actuator微服务信息完善
- 服务发现Discovery
- Eureka自我保护理论知识
- 怎么禁止自我保护
- 注册中心eurekaServer端7001
- 生产者客户端eurekaClient端8001
- zookeeper
- CAP理论
- Ribbon
- Ribbon入门介绍
- Ribbon的负载均衡和Rest调用
- Ribbon默认自带的负载规则
- Ribbon默认负载轮训算法原理
- 轮询 RoundRobinRule源码分析
- Ribbon之手写轮询算法
- OpenFeign
- 概述
- OpenFeign服务调用
- OpenFeign超时控制
- END
主要学习
截止2021年9月官网推荐
详细版本对应表,需严格按照此表来,格式化后
本次学习选择版本,严格要求
| 项目 | 版本 |
|---|---|
| spring-cloud | Hoxton.SR1 |
| spring-boot | 2.2.2.RELEASE |
| cloud-alibaba | 2.1.0RELEASE |
| Java | Java8 |
| Maven | 3.5以上 |
| MySQL | 5.7以上 |
红框为重点学习
new Project 一个Maven工程
DependencyManagement和Dependencies4.0.0 com.atguigu.springcloud cloud2020 1.0-SNAPSHOT pom UTF-8 1.8 1.8 4.12 1.2.17 1.18.0 5.1.47 1.1.16 1.3.2 org.springframework.boot spring-boot-dependencies 2.2.2.RELEASE pom import org.springframework.cloud spring-cloud-dependencies Hoxton.SR1 pom import com.alibaba.cloud spring-cloud-alibaba-dependencies 2.1.0.RELEASE pom import mysql mysql-connector-java ${mysql.version} com.alibaba druid ${druid.version} org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis.spring.boot.version} log4j log4j ${log4j.version} junit junit ${junit.version} org.projectlombok lombok ${lombok.version} true
这样做的好处就是: 如果有多个子项目都引用同一样的依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样想升级或切换到另一个版本时,只需在顶层父容器里更新,而不需要一个一个子项目的修改l;另外如果某个子项目需要另外的一个版本,只需声明version版本
dependencyManagement里只是声明依赖,并不引入实现,因此子项目需要显示的声明需要用的依赖
支付模块构建 建module、改POM、写YML、主启动new Module 一个Maven工程
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.mybatis.spring.boot mybatis-spring-boot-starter com.alibaba druid-spring-boot-starter 1.1.10 mysql mysql-connector-java org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-test test
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://000.000.000.000:3306/db2020_cloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
基础业务编写
@Data @AllArgsConstructor @NoArgsConstructor public class CommonResult消费者订单模块{ private Integer code; private String message; private T data; public CommonResult(Integer code, String message) { this(code,message,null); } }
利用RestTemplate发起HTTP远程调用,最简单的远程调用
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@RestController
public class OrderController {
private final static String PAYMENT_URI = "http://localhost:8001/";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/create")
public CommonResult create(Payment payment){
return restTemplate.postForObject(PAYMENT_URI+"/payment/create",payment,CommonResult.class);
}
@GetMapping("/consumer/payment/{id}")
public CommonResult getById(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URI+"/payment/"+id,CommonResult.class);
}
}
工程重构
系统中有重复部分,重构
新建module cloud-api-common放公共类
org.projectlombok lombok true cn.hutool hutool-all 5.1.0
抽离其他module中相同的类
被抽取的module加入依赖,并删除被抽离的类和包,公共module clean、install
Eureka 基础知识com.atguigu.springcloud cloud-api-common ${project.version}
什么是服务治理
管理服务与服务之间的依赖,实现服务调用、负载均衡、服务注册与发现、容错等等。
什么是服务注册与发现
当服务启动时,会把自己的服务信息,如服务通信地址等以别名的方式注册到注册中心。另一方(消费者、服务提供者)以改别名的方式去注册中心上获取实际的服务通讯地址,然后再实现本地RPC调用
Eureka的两个组件 EurekaServer和EurekaClient
EurekaServer:提供服务注册
各个微服务节点配置启动后,会在EurekaServer中进行
new一个Module:cloud-eureka-server7001
org.springframework.cloud spring-cloud-starter-netflix-eureka-server com.atguigu.springcloud cloud-api-common ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
server:
port: 7001
spring:
application:
name: cloud-eureka-server7001
eureka:
instance:
hostname: localhost
client:
fetch-registry: false #不注册自己
register-with-eureka: false # 不检索自己
service-url:
# defaultZone: http://www.eureka7002.com:7002/eureka/
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
服务8001入驻进eurekaServer
org.springframework.cloud spring-cloud-starter-netflix-eureka-client
eureka:
client:
# 表示是否酱紫注册进eurekaServer 默认为true
fetch-registry: true
# 是否从EurekaServer抓去已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka
启动类 @EnableEurekaClient
服务80同上
每个eureka互相注册,相互守望
再创建一个cloud-eureka-server7002 同 cloud-eureka-server7001一样
注册地址defaultZone为对方
server:
port: 7002
spring:
application:
name: cloud-eureka-server7002
eureka:
instance:
hostname: eureka7002.com
client:
fetch-registry: false #不注册自己
register-with-eureka: false # 不检索自己
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
订单支付两微服务注册进Eureka集群
只需将Eureka集群的两个地址填入
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/支付微服务集群配置
再new Module 一个 cloud-provider-payment8002
注意application.name与8001保持一致
server:
port: 8002
spring:
application:
name: cloud-payment-service
调用方,调用地址必须为注册中心中服务的别名,且RestTemplate必须加上@LoadBalanced注解,完成对服务的轮询调用
private final static String PAYMENT_URI = "http://CLOUD-PAYMENT-SERVICE";
------------
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
actuator微服务信息完善
主机名称:服务名称修改
访问信息有IP信息提示
eureka:
instance:
instance-id: payment8002
prefer-ip-address: true #访问路径可以显示IP地址
服务发现Discovery
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
启动类@EnableDiscoveryClient开启服务发现
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/discovery")
public Object discovery() {
List services = discoveryClient.getServices();
services.forEach(service->{
System.out.println("----service"+service);
});
List instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
System.out.println(instance.getServiceId()+"t" + instance.getHost()+"t"+ instance.getPort()+"t"+instance.getUri());;
}
return this.discoveryClient;
}
Eureka自我保护理论知识
一句话:某时刻 一个微服务不可用了,Eureka不会立刻清理,依旧会对该服务的信息进行保存
属于CAP里面的AP分支
- C(一致性):所有的节点上的数据时刻保持同步
- A(可用性):每个请求都能接受到一个响应,无论响应成功或失败
- P(分区容错):系统应该能持续提供服务,即使系统内部有消息丢失(分区)
出产默认,自我保护机制是开启的
eureka.server.enable-self-preservation=true
使用eureka.server.enable-self-preservation=false 可以禁用自我保护模式
生产者客户端eurekaClient端8001eureka.instance.lease-renewal-interval-in-seconds=30
Eureka 客户端向服务端发送心跳的时间间隔 , 单位为秒 ( 默认是 30 秒 )
eureka.instance.lease-expiration-duration-in-seconds=90
Eureka 服务端在收到最后一次心跳后等待时间上限 , 单位为秒 ( 默认是 90 秒 ), 超时剔除服务
AP:Eureka
CP:Zookeeper、Consul
是什么
Ribbon目前也进入维护模式:未来替换方案 – SpringCloud loadBalancer
前面我们讲解过了80通过轮询负载访问8001/8002
总结:负载均衡+RestTemplate调用
Ribbon的负载均衡和Rest调用
Ribbon其实就是一个软负载均衡的客户端组件, 他可以和其他所需请求的客户端结合使用,和eureka结合只是其中一个实例.
Eureka依赖中自带整合了Ribbon
getForEntity能否获取到响应体的完整信息, getForOject只获取传输对象的json串
@GetMapping("/consumer/payment/getforentity/{id}")
public CommonResult getById2(@PathVariable("id") Long id){
ResponseEntity entity = restTemplate.getForEntity(PAYMENT_URI + "/payment/" + id, CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();
}
return new CommonResult<>(400,"操作失败");
}
@GetMapping("/consumer/payment/createforentity")
public CommonResult create2(Payment payment){
ResponseEntity entity = restTemplate.postForEntity(PAYMENT_URI + "/payment/create", payment, CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();
}
return new CommonResult<>(400,"操作失败");
}
Ribbon默认自带的负载规则
IRule:根据特定算法从服务列表中选取一个要访问的服务
负载规则替换
1、不能和主启动类在同一包下
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();//定义为随机
}
}
启动类添加注解
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)Ribbon默认负载轮训算法原理 轮询 RoundRobinRule源码分析
choose方法中做服务器选择的具体实现
JUC、CAS、自旋锁
手写一个负载的算法
原理+JUC(CAS+自旋锁的复习)
接口及其实现
public interface LoadBalancer {
ServiceInstance instances(List serviceInstances);
}
@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 >= 2147483647 ? 0 : current + 1;
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("****第几次访问,次数next:" + next);
return next;
}
@Override
public ServiceInstance instances(List serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
调用
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB(){
List instances = discoveryClient.getInstances("cloud-payment-service");
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);
}
OpenFeign
概述
Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需 创建一个接口并在接口上添加注解即可
OpenFeign服务调用1、微服务调用接口+@FeignClient
2、用在消费端,创建一个消费端
关键依赖
org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-netflix-eureka-client
3、YML配置注册地址
server:
port: 80
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
register-with-eureka: false
4、主启动类添加注解@EnableFeignClients
5、业务类,分装在在接口,控制层通过接口调用
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/{id}")
CommonResult getPaymentById(@PathVariable("id") Long id);
}
---------------
@RestController
public class PaymentController {
@Autowired
PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
OpenFeign超时控制
#设置feign客户端超时时间 ribbon: ReadTimeout: 5000 ConnectTimeout: 5000
日志增强
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
logging:
level:
com.atguigu.springcloud.service.PaymentFeignService: debug
END


