SpringCloud
什么是微服务?
将单一的应用程序分成一组小的服务,服务间可以相互协调,配合(轻量级,)
什么是springcloud?
微服务架构的一站式解决方案
技术栈
服务注册与发现
EUREKA(停更),Zookeeper,Consul,Nocos
服务调用
RIBBON(NETFLIX网飞), LoadBalancer, FELGN(停更) , OpenFeign
服务熔断降级
HYSTRIX (不推荐) ,resilience4j (国外推荐) ,sentienl (国内推荐)
服务网关
ZUUL (不推荐) , gateWay
服务分布配置
SpringCloud Config (不推荐) , Nocos
服务总线
Bus (不推荐) , Nocos
服务开发
SpringBoot
Eureka
服务治理?
管理服务于服务间的依赖关系,实现:负载均衡,容错,服务调用
服务注册?
Service Provider 去 Eureka Server注册服务
Eureka Server
提供注册服务,存储可以的服务节点
Eureka Client
通过注册中心进行访问,也就是Service Provider,内置Ribbon负载均衡器(轮询),心跳周期(30s)
Service Consumer如何调用服务?
先去Eureka Server中找有没有该服务,再去调用Service Provider
单点故障
Eureka
服务注册
单机Eureka构建
EurekaServer
pom.xml
<!-- 图形监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
application.yml
server:
port: 7001
spring:
application:
name: cloud-eureka-service
eureka:
server:
enable-self-preservation: false #出产默认,自我保护机制是开启的
instance:
# eureka服务端的实例名称
hostname: localhost
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
fetch-registry: false
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class);
}
}
EurekaClient
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml
server:
port: 8001
tomcat:
uri-encoding: UTF-8
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2020?useUnicode=true&characterEncoding=UTF8
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.wu.icloud.entities
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka/
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
instance:
instance-id: payment8001
prefer-ip-address: true #可以显示ip
主启动类
@EnableEurekaClient
@EnableEurekaClient
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class);
}
}
Eureka集群构建
原则:互相注册,相互守望,每一个Eureka服务都要有其他Eureka服务的相关信息
eurekaServer7001
application.yml
server:
port: 7001
spring:
application:
name: cloud-eureka-service
eureka:
server:
enable-self-preservation: false #出产默认,自我保护机制是开启的
instance:
# eureka服务端的实例名称
hostname: eureka7001.com
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
fetch-registry: false
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://eureka7002.com:7002/eureka/ #集群
eurekaServer7002
application.yml
server:
port: 7002
spring:
application:
name: cloud-eureka-service2
eureka:
instance:
hostname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
简单的支付微服务集群
payment8001
application.yml
server:
port: 8001
tomcat:
uri-encoding: UTF-8
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2020?useUnicode=true&characterEncoding=UTF8
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.wu.icloud.entities
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
instance:
instance-id: payment8001
prefer-ip-address: true #可以显示ip
PaymentController
@RestController
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@Resource
private DiscoveryClient discoveryClient;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment) {
int result = paymentService.create(payment);
if (result > 0) {
return new CommonResult(200, "插入数据成功!serverPort:" + serverPort, result);
} else {
return new CommonResult(400, "插入数据失败!");
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
Payment paymentById = paymentService.getPaymentById(id);
if (paymentById != null) {
return new CommonResult(200, "查询成功!serverPort:" + serverPort, paymentById);
} else {
return new CommonResult(400, "查询失败!");
}
}
@GetMapping("/payment/discovery")
public Object discovery() {
// 通过服务发现来获得该服务的信息
List<String> services = discoveryClient.getServices();
for (String element : services) {
System.out.println("----element:"+element);
}
//一个微服务下的全部实例
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance is:instances){
System.out.println("----:"+is.getScheme()+"-"+is.getServiceId()+"-"+is.getHost()+"-"+is.getUri());
}
return this.discoveryClient;
}
}
payment8002
application.yml
server:
port: 8002
tomcat:
uri-encoding: UTF-8
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2020?useUnicode=true&characterEncoding=UTF8
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.wu.icloud.entities
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
instance:
# 主机名称:服务名称修改
instance-id: payment8002
prefer-ip-address: true #可以显示ip
ServiceConsumer80
消费者
application.yml
server:
port: 80
spring:
application:
name: cloud-order-service
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机
# defaultZone: http://localhost:7001/eureka
# # 集群
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
OrderController
@RestController
public class OrderController {
//单机版
// public static final String URL_PATH = "http://localhost:8001";
//集群 --eureka中查找
public static final String URL_PATH = "http://CLOUD-PAYMENT-SERVICE"; //CLOUD-PAYMENT-SERVICE服务名称,他会自动调度
@Resource
private RestTemplate restTemplate;
@PostMapping(value = "/consumer/payment/create")
public CommonResult create(Payment payment) {
CommonResult commonResult = restTemplate.postForObject(URL_PATH + "/payment/create", payment, CommonResult.class);
return commonResult;
}
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
return restTemplate.getForObject(URL_PATH + "/payment/get/" + id,CommonResult.class);
}
}
ApplicationConfiguration
@Configuration
public class ApplicationConfiguration {
@Bean
@LoadBalanced //负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
可以看出serverPort会轮询的发生变化
服务发现
获取注册进Eureka的服务信息
/**
* @program: iSpringCloud
* @author: Mr-Jies
* @create: 2020-04-14 17:45
**/
@RestController
public class PaymentController {
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/payment/discovery")
public Object discovery() {
// 通过服务发现来获得该服务的信息
List<String> services = discoveryClient.getServices();
for (String element : services) {
System.out.println("----element:"+element);
}
//一个微服务下的全部实例
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance is:instances){
System.out.println("----:"+is.getScheme()+"-"+is.getServiceId()+"-"+is.getHost()+"-"+is.getUri());
}
return this.discoveryClient;
}
}
Eureka的自我保护
如果一个微服务不可用了,Eureka不会立刻清理.依旧会对该服务的信息进行保存
CAP
CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),这三个要素最多只能同时实现两点,不可能三者兼顾。
- 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
- 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
- 分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,
如何解除自卫保护??
实例:
- eurekaServer7001
eureka:
server:
enable-self-preservation: false #出产默认,自我保护机制是开启的
eviction-interval-timer-in-ms: 2000 #主动失效检测间隔
- payment8001
instance:
instance-id: payment8001
prefer-ip-address: true #可以显示ip
lease-renewal-interval-in-seconds: 5 #发送心跳的间隔时间
lease-expiration-duration-in-seconds: 5 #eureka服务端收到最后一次心跳后等待的是时间上限;; 默认为90秒
Zookeeper
支付服务注册进Zookeeper
pom.xml
<dependencies>
<!--web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--SpringBoot测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入自己的API通用包,可以使用Payment支付Entity-->
<dependency>
<groupId>com.wu.sc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
</dependencies>
application.yml
server:
port: 8004
spring:
application:
name: cloud-payment-service
cloud:
zookeeper:
connect-string: *****:2181
PaymentController
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String port;
@RequestMapping(value = "payment/zk")
public String paymentZk(){
return "SpringCloud with zookeeper ,server port:"+port+"\t"+ UUID.randomUUID().toString();
}
}
PaymentMain8004
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class,args);
}
}
测试
1->
2->
root@----:~# docker exec -it 48354534acbe /bin/bash
root@48354534acbe:/apache-zookeeper-3.6.0-bin# cd bin/
root@48354534acbe:/apache-zookeeper-3.6.0-bin/bin# zkCli.sh -server 127.0.0.1:2181
[zk: 127.0.0.1:2181(CONNECTED) 1] ls /
[dubbo, services, zookeeper]
[zk: 127.0.0.1:2181(CONNECTED) 2] ls /services
[cloud-payment-service] <-<-----------与 spring:application:name: 一样
[zk: 127.0.0.1:2181(CONNECTED) 3] ls /services/cloud-payment-service
[84e3db6a-5554-415a-81f5-fc3465e247df]
[zk: 127.0.0.1:2181(CONNECTED) 4] ls /services/cloud-payment-service/84e3db6a-5554-415a-81f5-fc3465e247df
[]
[zk: 127.0.0.1:2181(CONNECTED) 5] get /services/cloud-payment-service/84e3db6a-5554-415a-81f5-fc3465e247df
{"name":"cloud-payment-service","id":"84e3db6a-5554-415a-81f5-fc3465e247df","address":"localhost","port":8004,"sslPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"application-1","name":"cloud-payment-service","metadata":{}},"registrationTimeUTC":1589523333353,"serviceType":"DYNAMIC","uriSpec":{"parts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":":","variable":false},{"value":"port","variable":true}]}}
值得注意的服务节点是Zookeeper是临时节点,服务干掉了话,一段时间后Zookeeper会在注册表中删除掉服务的相关信息
Consul
服务提供者注册到Consul
pom.xml
<dependencies>
<!--consul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--SpringBoot测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入自己的API通用包,可以使用Payment支付Entity-->
<dependency>
<groupId>com.wu.sc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
application.yml
server:
port: 8006
spring:
application:
name: cloud-payment-service
cloud:
consul:
host: 127.0.0.1
port: 8500
discovery:
hostname: 127.0.0.1
service-name: ${spring.application.name}
PaymentController
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String port;
@RequestMapping(value = "/payment/consul")
public String paymentConsul(){
return "SpringCloud with consul ,server port:"+port+"\t"+ UUID.randomUUID().toString();
}
}
消费者注册到Consul
pom.xml与主启动,yml配置文件,ApplicationConfiguration都一样