微服务学习
一、微服务的优点
- 快速响应变更:单一职责,独立部署
- 精粒度业务控制:降级熔断,局部限流
- 独立扩展:边界清晰,不过度受制于技术栈
- 面向业务/领域模型:不依赖数据模型,易于抽象
二、微服务的缺点
- 部署结构复杂:模块众多,一堆额外组件
- 依赖平台支撑:依赖微服务组件,研发成本
- 分布式问题:一致性,异常补偿
- 拆分水平:粒度过程/过细
三、分布式系统CAP定理
- C:一致性
强一致性:一次更新终身有效
弱一致性:更新过后,一部分或者全部请求都拿不到数据
最终一致性:经过一定的时间以后,所有数据保持一直 - A:可用性
- P:分区容错性
- CAP大定理:分布式系统只能三选二,不能全占
服务治理
一、Eureka
- 服务治理的伟大目标:高可用性、分布式调用、生命周期管理、健康度检查
- 服务注册 - 服务提供方自报家门
服务发现 - 服务消费者拉取注册数据
心跳检测,服务续约
服务剔除 一套由服务提供方和注册中心配合完成的去伪存真的过程
服务下线 - 服务提供方发起主动下线 - 服务治理三大门派:Consul、Eureka、Nacos
- 服务注册是为了解决Who are you这个问题,即获取所有服务节点的身份信息和服务名称,从注册中心的角度来说我们有以下两种比较直观的解决方案:
三顾茅庐 由注册中心主动访问网络节点中所有机器
等待戈多 注册中心坐等服务节点上门注册 - 注册中心的日常任务:当我期盼的戈多到来了以后,他会告诉关于他的三件事情
他会的技能(所提供的微服务是什么,比如“洗剪吹”)
他住在哪里(IP地址+端口)
他的状态(通常注册完成时的服务状态就是UP)
在等待戈多的过程中,其实这两位流浪汉也没有那么闲,他在空余时间还是干了两件微小的事:
心跳检测和服务剔除 已经注册过的戈多们,会时不时来跟我打声招呼(心跳检测),如果隔段时间没见着他们了,我就只好从注册名单中把他们删除(服务剔除)。
注册信息同步 我们两个流浪汉分别接待不同的戈多,有的戈多在我这里注册,没有在他那里注册。我抽空就会把我这里的戈多名单和他做分享。
创建服务注册中心
-
创建项目
spring-clound-demo下的eureka文件中创建eureka-server
-
修改spring-clound-demo的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cmy</groupId>
<artifactId>spring-clound-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>eureka-server</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 修改eurka-server项目的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-clound-demo</artifactId>
<groupId>com.cmy</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
- 创建启动类EurekaServerApplication
/**
* @Auther: chenmy
* @Date: 2021/05/17/23:10
* @Description:
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(EurekaServerApplication.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
}
我们在启动类上加了一个 @EnableEurekaServer 注解,
在 Spring Cloud 中,包含 @EnableEurekaServer 注解的服务意味着就是一个 Eureka 服务器组件
- 创建配置文件application.properties
spring.application.name=eureka-server
server.port=20000
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
6.启动项目访问
成功了!!!
注册服务提供方
- 创建项目
eureka-client
- 修改pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-clound-demo</artifactId>
<groupId>com.cmy</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>eureka-client</artifactId>
<name>eureka-client</name>
<dependencies>
<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>
</dependencies>
</project>
- 创建启动类
package com.cmy.springclound;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @Auther: chenmy
* @Date: 2021/05/18/1:34
* @Description:
*/
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(EurekaClientApplication.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
}
- 控制层
package com.cmy.springclound;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @Auther: chenmy
* @Date: 2021/05/18/1:37
* @Description:
*/
@RestController
@Slf4j
public class Controller {
@Value("${server.port}")
private String port;
@GetMapping("/sayHi")
private String sayHi(){
return "This is"+port;
}
@PostMapping("/sayHi")
public Friend sayHiPost(@RequestBody Friend friend) {
log.info("You are " + friend.getName());
friend.setPort(port);
return friend;
}
}
- 其他类
package com.cmy.springclound;
import lombok.Data;
/**
* @Auther: chenmy
* @Date: 2021/05/18/1:39
* @Description:
*/
@Data
public class Friend {
private String name;
private String port;
}
- 配置文件
spring.application.name=eureka-client
server.port=3000
eureka.client.serviceUrl.defaultZone=http://localhost:20000/eureka/
-
启动访问即可
创建服务消费者 -
创建项目
eureka-consumer
-
修改pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-clound-demo</artifactId>
<groupId>com.cmy</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-consumer</artifactId>
<packaging>jar</packaging>
<name>eureka-consumer</name>
<dependencies>
<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>
</dependencies>
</project>
- 创建启动类
package com.cmy.springclound;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* @Auther: chenmy
* @Date: 2021/05/18/22:35
* @Description:
*/
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaConsumerApplication {
@Bean
public RestTemplate register() {
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(EurekaConsumerApplication.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
}
- 控制层类
package com.cmy.springclound;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @Auther: chenmy
* @Date: 2021/05/18/22:35
* @Description:
*/
@RestController
@Slf4j
public class Controller {
@Autowired
private LoadBalancerClient client;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/hello")
public String hello() {
ServiceInstance instance = client.choose("eureka-client");
if (instance == null) {
return "No available instances";
}
String target = String.format("http://%s:%s/sayHi",
instance.getHost(),
instance.getPort());
log.info("url is {}", target);
return restTemplate.getForObject(target, String.class);
}
@PostMapping("/hello")
public Friend helloPost() {
ServiceInstance instance = client.choose("eureka-client");
if (instance == null) {
return null;
}
String target = String.format("http://%s:%s/sayHi",
instance.getHost(),
instance.getPort());
log.info("url is {}", target);
Friend friend = new Friend();
friend.setName("Eureka Consumer");
return restTemplate.postForObject(target, friend, Friend.class);
}
}
- 实体类
package com.cmy.springclound;
import lombok.Data;
@Data
public class Friend {
private String name;
private String port;
}
- 配置文件
spring.application.name=eureka-consumer
server.port=31000
eureka.client.serviceUrl.defaultZone=http://localhost:20000/eureka/
- 启动前面的三个项目访问如图
服务心跳
启用心跳检测
1.在eureka-client的配置文件中添加以下配置
# 每隔5秒钟,向服务中心发送一条续约指令
eureka.instance.lease-renewal-interval-in-seconds=5
# 如果30秒内,依然没有收到续约请求,判定服务过期(上西天)
eureka.instance.lease-expiration-duration-in-seconds=30
- 在eureka-server的配置文件中添加以下配置
# 强制关闭服务自保(自动开关不起作用)
eureka.server.enable-self-preservation=false
# 每隔多久触发一次服务剔除
eureka.server.eviction-interval-timer-in-ms=10000
- 启动服务隔一会就会发现服务被剔除了
注册中心的高可用方案
- 搭建两个注册中心