微服务SpringCloud入门
文章目录Spring Cloud介绍SpringCloud架构微服务开发三要素dubbo发布的服务的三个要素SpringCloud发布的服务三个要素微服务入门工程实例1. 创建微服务父工程Spring Cloud介绍Spring cloud微服务是一种架构方式,最终肯定需要技术架构去实施。微服务的实现方式很多,Spring Cloud是一种最火的微服务实现方式。Spring Cloud 是一套完整
文章目录
1. SpringCloud前置知识点
1.1 RestFul API介绍
rest:(Representational State Transfer),直接翻译:表现层状态转移。
用于URL定位资源,用HTTP动词(GET、POST、DELETE、PUT)描述进行操作。
将动作(增删改查)隐藏起来,用请求方式来处理(兼容不同语言后缀)
作用:
rest描述的是在网络中client和server的一种交互形式;REST本身不实用,实用的是如何设计 RESTful API(REST风格的网络接口)。
Server提供的RESTful API中,URL中只使用名词来指定资源,原则上不使用动词。“资源”是REST架构或者说整个网络处理的核心。
1.1.1 Rest介绍
Rest是用HTTP协议里的动词来实现资源的添加,修改,删除等操作。即通过HTTP动词来实现资源的状态扭转。
通过url地址就知道请求的是什么,通过http的请求方式method就知道具体是干什么,通过http的返回状态码(status code)就知道结果怎么样。
1.1.2 RestFul案例
User
@Data //lombok,可以省略get/set、构造方法、toString等方法
public class User {
private int id;
private String uasername;
private String password;
}
UserController
@RestController
@Slf4j //lombok日志
public class UserController {
//普通的地址表示方式(通过具体的地址来访问) http://localhost:8080/users/find.do?id=1
@RequestMapping(path = "/users/find.do", method = {RequestMethod.GET})
public Object find(int id) {
//到数据库中查询模拟
User user = new User();
user.setId(id);
user.setUasername("strive_day");
user.setPassword("111111");
return user;
}
// restful表示方式
//method = get,查询
//1: 占位符 /user/{id}
//2: 取path上的值 user/1 使用注解来取 (http://localhost:8080/users/21)
@RequestMapping(path = "/users/{id}", method = {RequestMethod.GET})
public Object get(@PathVariable("id") int userId) {//@PathVariable取得id的值1,赋值给与占位符变量的同名
//模拟数据库查询
log.info("get id "+userId);
User user = new User();
user.setId(userId);
user.setUasername("strive_day");
user.setPassword("222222");
return "Get方式查询"+user;
}
//method = delete 删除
@RequestMapping(path = "/users/{id}", method = {RequestMethod.DELETE})
public Object delete(@PathVariable int id) {
//到数据库中查询模拟
log.info("删除了 id "+id);
User user = new User();
user.setId(id);
user.setUasername("strive_day");
user.setPassword("333333");
return "delete方式删除" + user;
}
}
通过Postman
工具来访问
- 普通方式,通过url地址不同区分来访问
- 通过RestFul来访问,选择不同的请求方式,访问不同的方法
get请求方式
delete请求方式
postman可以选择不同的请求方式来进行测试
1.2 RestTemplate介绍
1)一般情况下有如下三种http客户端工具类包都可以方便的进行http服务调用。
- httpClient apache
- okHttp
- JDK原生URLConnection
2)RestTemplate是spring提供的工具类对上述的3种http客户端工具类进行了封装,可在spring项目中使用RestTemplate进行服务调用。
- 请求地址 + 请求方式
- 请求参数
- 返回值
3)RestTemplate的作用
类似postman 可以模拟 http请求(get,post,delete,put等请求)
4)RestTemplate特点
比如post请求处理:
postForObject(url, requestMap, ResponseBean.class)
- 参1 请求地址
- 参2 请求参数
- 参3 HTTP响应转换被转换成的对象类型
使用RestTemplate模拟调用请求
//RestTemplate(类似postman),可以模拟 http请求,如(get,post,delete,put)
@SpringBootTest
class Demo02RestTemplateApplicationTests {
@Test
void test01() {
RestTemplate template = new RestTemplate();
//发送delete请求
template.delete("http://localhost:8080/users/21");
}
//restTemplate还可以将返回的json数据直接转换成java对象
@Test
void test02() {
RestTemplate template = new RestTemplate();
//发送get请求,将返回的json数据直接转换成java对象
User user=template.getForObject("http://localhost:8080/users/21", User.class);
System.out.println(user);
}
}
1.3 dependencyManager
1.3.1 dependencies介绍
dependencies是
pom.xml
元素,表示依赖项的集合。
在**父子工程
中使用**,所有生命在dependencies里的依赖都会自动引入对应的jar包,如果放到父工程中默认被所有的子项目继承。
1.3.2 dependencyManager介绍
- dependencyManager是是pom.xml的一个元素,多模块(项目)场景下的版本管理。
多个子项目中,必须确保应用的各个项目的依赖项和版本一致,才能保证测试的和发布的是相同的结果。
比如mysql8驱动需要的时区参数。- dependencyManagement的作用
用来管理jar包的版本,让子项目中引用一个依赖而不用显示的列出版本号。
原理:Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用在这个dependencyManagement元素中指定的版本号。- dependencyManagement特点
子项目还得写依赖,但不需要写版本
(在父工程中)本质上是声明依赖与版本(只是说有这个东西,但没有真正加进来,真正的加载进来需要在子工程中手动引入)
在父工程中pom.xml
声明,只是管理版本
<!--声明(锁定jar的版本)mysql,但是不将mysql引入到父工程中 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
</dependencies>
</dependencyManagement>
在子工程pom.xml
中如果需要就引入才能使用(如果没有指定具体版本,才会从父项目中继承,如果子项目中指定了版本号,那么会使用子项目中指定的jar版本)
<!-- 引入父工程的mysql依赖,不需要版本(父工程指定) -->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
1.3.3 dependencyManagement与dependencies区别【**】
dependencies
即使在子项目中不写该依赖项,那么子项目仍然会从父项目中继承该依赖项(并且是全部继承)。
dependencyManagement
里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。
如果不在子项目中声明依赖,是不会从父项目中继承下来的。
只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;
另外如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。
1.4 tk.mybatis(通用mapper)
Mabtis工具包:
tk.mybatis
TKmybatis
是基于MyBatis框架开发的一个工具,可以极大的提高项目的开发效率,通过调用它提供的方法实现对单表数据的操作(crud),不需要写任何的sql
语句。
注意事项:
使用步骤
pom.xml
导入依赖TkMybatis的maven依赖
pom.xml
<!-- https://mvnrepository.com/artifact/tk.mybatis/mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/tk.mybatis/mapper-spring-boot-starter -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
- 实体类javabean的相关配置
@id
主键注解(只能有一个),@Table
数据库表名,@Column
对应数据库列名(属性名和表列名不一致情况),@Transient
是冗余字段,不与数据库任何字段对应,即忽略
import lombok.Data;
import javax.persistence.*;
@Table(name="tb_user") //表名
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //主键自增
private int id;
@Column(name="girl_friend") //属性名和列名不一致
private String girlFriend;
private String name;
private String password;
private String username;
@Transient //@Transient注解:忽略,不与数据库表任何字段对应
private boolean isCheck; //跟表无关,是否选择
}
- Mapper接口继承tkMyBatis的Mapper接口
import tk.mybatis.mapper.common.Mapper;
//tk.mybatis 提供Mapper父接口,继承就可以直接使用
public interface UserDao extends Mapper<User> {
//update、delete、select、insert方法可以全部省略,由tkmybatis实现
}
- 在启动类Application上使用@MapperScan()扫描Mapper接口
@SpringBootApplication
@MapperScan(basePackages = "com.xgf.demo03_tkmybatis.mapper") //扫描包
public class Demo03TkmybatisApplication {
public static void main(String[] args) {
SpringApplication.run(Demo03TkmybatisApplication.class, args);
}
}
application.properties
配置文件配置数据库属性
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=861221293
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
# mybatis
mybatis.type-aliases-package=com.xgf.demo03_tkmybatis.domain
# use xml 使用xml配置的时候需要
mybatis.mapper-locations=classpath*:com/xgf/demo03_tkmybatis/domain/mapper/*.xml
- 使用TKMybatis提供的sql测试执行
@SpringBootTest
class UserServiceTest {
@Autowired
UserService userService;
@Test
void test01() {
//高级搜索页面的表单
User user = new User();
//user.setName("strive_day");
user.setUsername("strive_gf@163.com");
List<User> list = userService.queryByEntity(user);
System.out.println(list);
}
}
测试结果:
2. Spring Cloud入门
Spring cloud
微服务是一种架构方式,最终肯定需要技术架构去实施。
微服务的实现方式很多,Spring Cloud是一种最火的微服务实现方式。
Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,它是一个大的容器,将市面上较好的微服务框架集成进来,从而简化了开发者的代码量。
Spring Cloud
是Spring旗下的项目之一
官网地址:http://projects.spring.io/spring-cloud/
2.1 SpringCloud架构
Eureka:注册中心,美
[juˈriːkə]
Zuul 或者 Gateway:服务网关
Ribbon: 负载均衡,美[ˈrɪbən]
Feign:服务调用,美[feɪn]
Hystrix或Resilience4j:熔断器,美[hɪst’rɪks]
美[rɪˈzɪliəns]
2.2 微服务开发三要素
dubbo发布的服务的三个要素
- 提供者provider
- 消费者consumer
- 注册中心(zookeeper)
SpringCloud发布的服务三个要素
- 提供者provider
- 消费者consumer
- 注册中心
2.3 微服务入门工程实例
创建工程规划
- 创建微服务父工程
xxx-parent
添加SpringBoot
父坐标和管理其它组件的依赖- 创建用户服务工程
demo01_provider_user_8001
整合mybatis查询数据库中用户数据,提供查询用户服务- 创建服务消费工程
demo02_consumer_user_8002
利用查询用户服务获取用户数据并输出到浏览器
1. 创建微服务父工程
创建一个maven的project空项目:micro_service_spring_cloud_parent
2. 搭建配置服务(provider)工程
2.1 选择起步依赖(web工程):
demo01_provider_user_8001
(user是对什么表操作,8001是端口号)
2.2 修改配置文件中的参数application.properties
这里端口号改为8001
# publicsh service name
spring.application.name=demo01_provider_user_8001
# server port
server.port=8001
2.3 编写Controller测试访问
UserController
@RestController
@RequestMapping("/users")
public class UserController {
//http://localhost:8001/users/21
@RequestMapping(path = "/{id}",method = {RequestMethod.GET})
public Object get(@PathVariable long id){
//模拟数据库查询一个数据
User user = new User();
user.setId(id);
user.setUsername("strive_day"+id);
user.setPassword("123456");
return user; //RestController将user转成json
}
}
2.4 通过postman进行测试
http://localhost:8001/users/21
3. 搭建配置消费(consumer)工程
3.1 选择起步依赖(web工程):demo02_consumer_user_8002
(user是对什么表操作,8002是端口号),使用RestTemplate
获取provider服务提供者提供的数据
3.2 修改配置文件中的参数application.properties
这里将端口改为8002
spring.application.name=demo02_consumer_user_8002
server.port=8002
3.3 编写Controller,使用RestTemplate
获取provider服务提供者提供的数据。
@RestController
@RequestMapping("/consumer")
@Slf4j
public class CustomerController {
//需要在启动类创建当前对象RestTemplate,然后由Autowired去获取
@Autowired
RestTemplate rt; //当前是使用RestTemplate调用demo01_provider服务提供者的数据
//
@RequestMapping(path = "/{id}", method = RequestMethod.GET)
public Object get(@PathVariable long id) {
//消费工程,只是调用其他服务来获得想要的数据
String url = "http://localhost:8001/users/"+id; //参1: 地址
String json = rt.getForObject(url, String.class); //参2:数据要转换成什么类型
return json;
}
}
3.4 通过postman进行测试
http://localhost:8002/consumer/21
4. 搭建eureka-server工程
Eureka注册中心说明
Eureka
是SpringCloud将它集成在其子项目spring-cloud-netflix
中,以实现SpringCloud的服务发现功能。
Eureka
的主要功能:
- 进行服务管理
- 定期检查服务状态
- 返回服务地址列表。
心跳:检查服务器是否挂掉。
4.1 选择起步依赖Eureka server创建工程:
demo03_eureka_server_10001
,Eureka是服务注册中心,只做服务注册,自身并不提供服务也不消费服务。
4.2 添加启动器依赖(选择Eureka server起步依赖自动增加了)
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
因为当前阿里云平台还没有更新,所以会保留官网的仓库地址(用来下载)
<!-- 阿里云目前还没有更新,所以需要保留官网的仓库地址 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
4.3 修改配置文件application.properties(端口,应用名称,是否注册自己等)
# 服务器名称
spring.application.name=demo03_register_center_eureka_10001
# 端口号
server.port=10001
# eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka}
# not register myself 不注册自己
eureka.client.register-with-eureka=false
# not query myself 不拉取服务
eureka.client.fetch-registry=false
4.3 在启动引导类Application(添加Eureka的服务注解@EnableEurekaServer
)和配置文件
@SpringBootApplication
@EnableEurekaServer //添加Eureka的服务注解,表示作为一台服务器启动
public class Demo03RegisterCenterEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(Demo03RegisterCenterEurekaApplication.class, args);
}
}
4.5 测试运行
修改配置文件application.properties(是不注册注册自己)
# not register myself 不注册自己
eureka.client.register-with-eureka=false
# not query myself 不拉取服务
eureka.client.fetch-registry=false
2.4 优化,将相同的依赖抽取到父工程pom.xml中
- 将所有的pom.xml抽出来放到画图等软件中
- 找出相同的配置,复制到父工程中
父工程micro_service_spring_cloud_parent的pom.xml
<!-- 父类,自动继承 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 父工程打包方式为pom,给子工程继承 -->
<packaging>pom</packaging>
<!-- 子模块 -->
<modules>
<module>demo01_provider_user_8001</module>
<module>demo02_consumer_user_8002</module>
<module>demo03_register_center_eureka_10001</module>
</modules>
<!-- 1.8版本相同放父工程 -->
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.0-M5</spring-cloud.version>
</properties>
<!-- dependencyManagement锁定版本,不会自动引入 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- dependencies自动引入,所有子工程都继承lombok -->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.4.RELEASE</version>
</plugin>
</plugins>
</build>
<!-- 仓库 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
所有子工程都继承父工程pom.xml
<parent>
<!-- 继承父工程 -->
<groupId>com.xgf</groupId>
<artifactId>micro_service_spring_cloud_parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
服务提供者provider子工程和消费者consumer子工程的pom.xml
是客户端client
<!-- 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
server子工程时服务端server
<!-- 从父工程中找,父工程已经提供了仓库 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
3. 服务的注册(provider)与发现(consumer)【**】
步骤:
(1)将服务注册到eureka并在消费端中可以根据服务名称进行调用。
(2)provider服务注册。
(3)consumer服务发现。
3.1 provider服务注册
在服务提供工程上添加Eureka客户端依赖;自动将服务注册到EurekaServer服务地址列表。
- pom.xml添加Eureka的客户端依赖。
<!-- 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 修改配置文件application.properties,设置Eureka 服务地址。
# register address 注册地址
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka}
- 改造启动引导类,添加开启Eureka客户端发现的注解。
@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient //注册为客户端
public class Demo01ProviderUser8001Application {
public static void main(String[] args) {
SpringApplication.run(Demo01ProviderUser8001Application.class, args);
}
}
- 运行Eureka看是否注册进去
http://localhost:10001/
3.2 consumer通过注册中心Eureka查询到需要的服务provider
服务发现
在服务消费工程consumer上添加Eureka客户端client依赖,可以使用工具类根据服务名称获取对应的服务地址列表。
- pom.xml添加Eureka的客户端依赖
<!-- client端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 修改配置文件application.properties,设置Eureka服务地址。
# query address 查询地址
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka}
- 改造启动引导类,添加开启Eureka客户端发现的注解。
@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient //注册为客户端
public class Demo02ConsumerUser8002Application {
public static void main(String[] args) {
SpringApplication.run(Demo02ConsumerUser8002Application.class, args);
}
//将RestTemplate对象放入容器中
@Bean
public RestTemplate getRt() {
RestTemplate rt = new RestTemplate();
return rt;
}
}
-
重启consumer,运行Eureka看是否注册进去
http://localhost:10001/
-
改造处理器类
ConsumerController
,使用工具类DiscoveryClient
根据服务名称获取对应服务地址列表provider。
通过DiscoveryClient
来获取注册中心中的provider服务提供者的信息(比如主机Host、端口号port)
ConsumerController
@RestController
@RequestMapping("/consumer")
@Slf4j
public class CustomerController {
//需要在启动类创建当前对象RestTemplate,然后由Autowired去获取
@Autowired
RestTemplate rt; //当前是使用RestTemplate调用demo01_provider服务提供者的数据
@Autowired
DiscoveryClient discoveryClient; //DiscoveryClient,用来获取注册中心provider的信息
@RequestMapping(path = "/{id}", method = RequestMethod.GET)
public Object get(@PathVariable long id) {
//通过DiscoveryClient获取 provider的ip与端口
List<ServiceInstance> list = discoveryClient.getInstances("demo01_provider_user_8001");// 参数:服务id
//获取运行中的服务对象
ServiceInstance serviceInstance = list.get(0); //获取第一个
//获取主机
String host = serviceInstance.getHost();
//获取端口
int port = serviceInstance.getPort();
log.info("provider主机host: "+host);
log.info("provider端口port: "+port);
//消费工程,只是调用其他服务来获得想要的数据
String url = "http://"+host+":"+port+"/users/"+id; //参1: 地址(不用写死主机和端口号)
// String url = "http://localhost:8001/users/"+id; //参1: 地址
String json = rt.getForObject(url, String.class); //参2:数据要转换成什么类型
return json;
}
}
- 通过postman访问consumer的controller
http://localhost:8002/consumer/21
查看控制台输出:
4. Eureka高可用配置【**】
为什么需要高可用?
提供者provider将数据注册到服务中心,消费者consumer从注册中心拿取数据,如果服务Server挂了,那么provider和consumer都不能正常工作。
热备份:比如有两个服务中心A和B,如果A挂掉,可以访问B(数据是同步的【服务同步】,可以直接访问服务B)。
冷备份:比如有服务中心A和B,当前只启动了A(B停用),如果A挂掉,马上将数据备份到B中,并启动B提供服务。高可用概念
“高可用性”(High Availability):通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性。
比如:给Eureka Server 搞一个备份。服务同步原理
多个Eureka Server
之间也会互相注册
为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。
因此,无论客户端访问到Eureka Server集群中的任意一个节点(服务器),都可以获取到完整的服务列表信息。热备份高可用的实现图
4.1 Eureka高可用集群配置(热备份)
描述:
搭建两台EurekaServer
的集群(服务注册中心)
端口分别为:10001和10002
如果有一个挂掉,能够直接使用另一个服务处理。
4.1.1 application配置将自己注册的设置为可见
把application.properties的register-with-eureka
和fetch-registry
修改为true或者注释掉。(注释掉)
## not register myself 不注册自己
#eureka.client.register-with-eureka=false
## not query myself 不拉取服务
#eureka.client.fetch-registry=false
然后访问Eureka查看自己是否可以见
http://localhost:10001/
4.1.2 配置JVM虚拟机参数【**】
通过虚拟机给当前程序传递参数,然后在application配置文件中根据指定的参数来获取值。
4.1.3 修改配置文件application
实现从JVM虚拟机参数中读取端口号port和地址
# 端口号 使用JVM虚拟机参数,设置VM options的值(键值对),如果JVM存有值port,就使用port作为端口号,否则使用默认值10001
server.port=${port:10001}
# eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址,defaultZone是JVM虚拟机参数
# 注册两台(互相注册,通过JVM虚拟机参数配置,如果没有参数就只注册一台)
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka}
${}表示在jvm启动时候若能找到对应port或者defaultZone参数则使用,若无则使用后面的默认值
${port:10001} 取VM options的值 -Dport=10086
》把service-url的值改成了另外一台EurekaServer的地址,而不是自己
4.1.4 实现互相注册(10001和10002互相注册)
10001端口JVM参数VM options:
-Dport=10001 -DdefaultZone=http://127.0.0.1:10002/eureka
(截图配置少了http://后面的两个斜杆/
)
4.1.5 通过复制配置一份注册中心端口10002
修改JVM参数端口号为10002和数据共享10001
10002端口JVM虚拟机参数:-Dport=10002 -DdefaultZone=http://127.0.0.1:10001/eureka
(截图配置少了http://后面的两个斜杆/
)
4.1.6 重启10001和10002的服务中心并访问
然后访问:http://localhost:10001/
,发现10001和10002互相注册成功
然后访问:http://localhost:10002/
,发现10001和10002互相注册成功
4.2 客户端注册服务到集群
因为
EurekaServer
不止一个(这里有10001和10002两个),因此provider
项目注册服务或者consumer
获取服务的时候,service-url参数需要为对应的所有Server地址。
4.2.1 服务提供者provider服务注册
application.properties
# register address 注册地址(有两个server10001和10002都需要注册,用逗号隔开)
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka},${defaultZone:http://127.0.0.1:10002/eureka}
4.2.2 服务消费者consumer服务查询
application.properties
# query address 查询地址(两个server,用逗号隔开)
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka},${defaultZone:http://127.0.0.1:10002/eureka}
4.2.3 测试运行
postman测试调用provider和consumer的controller方法:
测试调用provider的controller方法:
http://localhost:8001/users/21
测试调用consumer的controller方法:
http://localhost:8002/consumer/21
5. 微服务Eureka客户端与服务配置(了解)
- Eureka客户端工程
1)服务提供
a. 服务地址使用ip方式。
b. 实现续约。
2) 服务消费:获取服务地址的频率。- Eureka服务端工程 eureka-server
1)实现失效剔除
2)实现自我保护
5.1 Eureka客户端provider配置使用ip优先
默认情况下,注册时使用的是主机名或者localhost,如果想用ip进行注册,可以在user-service 中添加配置如下:
# 客户端配置ip地址(当前ip地址)
eureka.instance.ip-address=127.0.0.1
# 开启,设置注册时ip优先,读配置的ip地址(这样consumer读取的时候读取的是配置的ip值,而不是主机名值)
eureka.instance.prefer-ip-address=true
配置完成通过访问consumer获取服务提供者provider的信息
http://localhost:8002/consumer/21
发现host主机已经变成了ip地址(provider注册的ip地址)
5.2 Eureka客户端provider配置服务续约
服务续约概念:(客户端配置)
在注册服务完成以后,服务提供者provider会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:"我还活着”。这个我们称为服务的续约(renew)。【如果挂掉了,就从服务中心server中去除(防止挂掉还一直被访问)】
两个重要参数可以修改服务续约的行为
可以在中添加如下配置项:
lease-renewal-interval-in-seconds
:服务续约(renew)的间隔,默认为30秒、心跳包的时间间隔。
lease-expiration-duration-in-seconds
:服务失效时间,默认值90秒,没收到心跳包的最长时间(超时就认为服务挂掉,就从服务中心去掉)
provider服务提供者配置application.properties
# 心跳包的时间间隔(服务续约(renew)的间隔,默认为30秒)
eureka.instance.lease-renewal-interval-in-seconds=30
# 服务失效时间,默认值90秒,没收到心跳包的最长时间
eureka.instance.lease-expiration-duration-in-seconds=90
5.3 Eureka消费端consumer获取服务列表 fetch
当服务消费者consumer启动时,会检测application配置文件的
eureka.client.fetch-registry=true
参数的值,如果为true,则会从Eureka Server服务的列表拉取只读备份,然后缓存在本地。并且每隔30秒 会重新拉取并更新数据。
consumer端配置application.properties,设置消费端可以定时获取注册中心的服务列表,并缓存时间
# 开启,通过fetch从服务端取数据(注册信息),,如果为true,则会从Eureka Server服务的列表拉取只读备份
eureka.client.fetch-registry=true
# 每隔30秒,会重新拉取并更新数据
eureka.client.registry-fetch-interval-seconds=30
5.4 Eureka服务端server设置
- 服务下线
当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。- 失效剔除
有时我们的服务可能由于内存溢出或网络故障
等原因使得服务不能正常的工作,而服务注册中心并未收到 “服务下线” 的请求。相对于服务提供者的 “服务续约” 操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。
eureka.server.eviction-interval-timer-in-ms
:参数对其进行修改,单位是毫秒。
服务中心:维持有效的服务列表,剔除无效的服务列表(provider列表)【provider服务提供有效保留,无效挂掉就剔除】。
在服务server注册中心的application.properties配置失效剔除时间
# 配置失效剔除:创建定时任务,每隔一段时间(单位:毫秒)将当前清单中provider服务提供者超时(默认为90秒)没有续约的服务剔除
eureka.server.eviction-interval-timer-in-ms=90000
Eureka服务端server配置自我保护是否开启
自我保护
当我们关停一个服务provider,很可能会在Eureka面板看到一条警告信息(超市剔除)
在服务server注册中心的application.properties配置是否开启自我保护(默认为true开启,缺省为打开)
# 关闭,配置自我保护(默认为true开启),开启自我保护,如果关停一个服务,就会在Eureka面板出现警告信息
eureka.server.enable-self-preservation=false
6. 微服务-负载均衡Ribbon【***】(provider集群)
6.1 负载均衡Ribbon介绍
- 负载均衡概念
负载均衡(Load Balance)其意思就是分摊到多个操作单元上进行执行(使用特定算法【默认轮询】将请求分配到对应服务提供者provider)。
本质:负载均衡是一个算法,可以通过该算法实现从地址列表中获取一个地址进行服务调用。
就是一台机器,受到太多的请求,响应不过来,通过负载均衡,分配给不同的执行单元(机器)来处理请求
在Spring Cloud中提供了负载均衡器:Ribbon 美[ˈrɪbən]
- 在Ribbon中提供了
轮询、随机
两种负载均衡算法(默认是轮询,一个个询问)实现,可以实现从地址列表中使用负载均衡算法获取地址进行服务调用。
6.2 Ribbon负载均衡应用案例
实际环境中,往往会开启很多个 user-service(服务提供者provider) 的集群。此时获取的服务列表provider中就会有多个,到底该访问哪一个呢?
可以使用Ribbon负载均衡:在执行RestTemplate发送服务地址请求的时候,使用负载均衡拦截器拦截,根据服务名获取服务地址列表,使用Ribbon负载均衡算法从服务地址列表中选择一个服务地址,访问该地址获取服务数据。
1. 配置两个provider服务提供者
配置启动两个服务提供者(provider) user-service 实例,一个8000,一个8001.
复制配置端口为8000的provider
修改provider的application设置端口获取优先从JVM虚拟机获取
# server port ${}从JVM中获取参数,如果有用port,如果没有用默认值8001
server.port=${port:8001}
启动8000和8001的provider服务提供者,通过Eureka访问
http://localhost:10002/
2. 修改consumer的启动器添加@LoadBalanced注解
@LoadBalanced
注解:设置当前方法的返回对象,使用负载均衡算法。
@SpringBootApplication
@EnableEurekaClient //注册为客户端
public class Demo02ConsumerUser8002Application {
public static void main(String[] args) {
SpringApplication.run(Demo02ConsumerUser8002Application.class, args);
}
//将RestTemplate对象放入容器中
@Bean //1、产生多个provider实例 8000 和 8001
@LoadBalanced //2、设置当前方法的返回对象,使用负载均衡算法
public RestTemplate getRt() {
RestTemplate rt = new RestTemplate();
return rt;
}
}
3. 集群名称不能有下划线
修改provider服务提供者的service name的命名(集群名称),将下划线全部改为-
减号或者别的符号
application.properties
# 服务器名称
spring.application.name=demo03_register_center_eureka_10001
4. 修改CosumerController通过集群调用。
通过调用provider集群名访问(具体调用provider集群的哪一个具体的provider是通过负载均衡实现的)
//集群调用provider(不能写死主机和端口号)
@RequestMapping(path = "/{id}", method = RequestMethod.GET)
public Object get(@PathVariable long id) {
//3、此处不能写死 provider 的 ip和port端口 ,只能写服务名。不能使用_下划线,可以将下划线替换成-
//调用服务提供者provider的集群demo01-provider-user(这里有两个8000和8001可调用),通过负载均衡调用具体哪一个
String url = "http://demo01-provider-user/users/"+id;//参1: 地址
String json = rt.getForObject(url, String.class);//参1:返回数据,参2:数据要转换成什么类型
return json;
}
通过postman来测试
http://localhost:8002/consumer/21
7. 微服务-熔断器Hystrix【***】
7.1 熔断器Hystrix介绍
- 熔断器概念:
熔断器(fuse)是指当电流超过规定值时,以本身产生的热量使熔体熔断,断开电路的一种电器,如(空气开关)。
在微服务的架构系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是依赖服务。有的时候某些依赖服务出现故障也是很正常的。- 熔断器
Hystrix
概念(发音:美[hɪst’rɪks]
)
Hystrix
是一个延迟和容错库。
Hystrix
可以让我们在对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。Hystrix通过将依赖服务进行资源隔离,进而组织某个依赖服务出现故障的时候,这种故障在整个系统所有的依赖服务调用中进行蔓延,同时Hystrix还提供故障时的fallback降级机制。总而言之,Hystrix通过这些方法帮助我们提升系统的可用性和稳定性。
Hystrix工作流程
- 构造一个
HystrixCommand
或HystrixObservableCommand
对象,用于封装请求,并在构造方法配置请求被执行需要的参数。- 执行命令,Hystrix提供的4种执行命令的方法。
K
value = command.execute();
FuturefValue = command.queue();
ObservableohValue = command.observe();
//hot observable
ObservableocValue = command.toObservable();
//cold observable
执行同步调用execute方法,会调用queue().get()方法,queue()又会调用toObservable().toBlocking().toFuture()。
所以,所有的方法调用都依赖Observable的方法调用,只是取决于是需要同步还是异步调用;
- 判断是否使用缓存响应请求,若启用了缓存,且缓存可用,直接使用缓存响应请求。Hystrix支持请求缓存,但需要用户自定义启动。
4.判断熔断器是否打开,如果打开,跳到第8步。- 判断线程池/队列/信号量是否已满,已满则跳到第8步;
- 执行业务请求
HystrixObservableCommand.construct()
或HystrixCommand.run()
,如果执行失败或者超时,跳到第8步。否则,跳到第9步。- 统计熔断器监控指标。
- 走Fallback备用逻辑。
- 返回请求响应。
7.2 熔断器Hystrix作用
使用服务降级,线程隔离解决雪崩问题。
7.2.1 雪崩问题【***】
微服务业务的某一个服务发生异常,请求阻塞,用户请求就不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。
微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路。
比如下面这个业务请求,就会调用A、P、H、I四个服务,这四个服务又可能调用其它服务。
当
微服务I
发生异常时,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,一直卡在这里。
于是越来越多的用户请求到来(请求服务器I
发生阻塞),越来越多的线程会阻塞。
服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。
7.2.2 Hystrix解决雪崩问题的手段
- 线程隔离
- 服务熔断
主要是通过服务降级来解决雪崩问题
7.2.3 Hystrix 线程隔离、服务降级【***】
1)线程隔离:
Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。
2) 服务降级:
优先保证核心服务,而非核心服务不可用或弱可用。用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。
3)触发Hystix服务降级的情况:
- 线程池已满
- 请求超时
4)服务降级的分类(两类):
- 自动降级
超时、失败次数、故障、限流等情况。
A. 配置好超时时间。
B. 不稳的的api调用次数达到一定数量进行降级。
C. 调用的远程服务出现故障。(dns、http服务错误状态码、网络故障、Rpc服务异常),直接进行降级。
- 人工降级
秒杀、双十一大促降级非重要的服务。
6)服务降级 - 超时降级配置
修改Hystrix对请求超时的判断时间(默认为两秒,可修改)
两种修改方式:
- 注解配置(加到方法上)
@HystrixCommand(commandProperties = {@HystrixProperty(name =“execution.isolation.thread.timeoutInMilliseconds” ,value =“2000” )})
- 属性配置(application.properties)
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000
7.2.4 服务熔断介绍
服务熔断是为了防止整个系统的故障,而采用了一些保护措施。
一般是某个服务异常引起的,相当于 “保险丝” 的作用,当某个异常条件被触发,直接熔断整个服务,不是等到此服务超时。
比如:A调B,B很慢的话,A也很慢。熔断让A不再调用B,而改成调用C ( 退路方法)
7.2.5 熔断状态机模型(了解)
熔断状态机的三个状态:
- Closed:关闭状态(断路器关闭)
所有请求都正常访问。- Open:打开状态(断路器打开)
所有请求都会被降级。Hystrix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。- Half Open:半开状态
不是永久的,断路器打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会关闭断路器,否则继续保持打开,再次进行休眠计时。
7.2.6 服务熔断的application配置
# 错误率,默认50%
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
# 滑动窗口的大小,默认为20
hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
# 过多长时间,熔断器再次检测是否开启(单位:毫秒),默认为5000,即5s钟
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000
上述配置的意思是:
每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。
达到熔断之后,那么后面它就直接不去调该微服务。
7.2.7 服务熔断和服务降级异同
相同点:
- 从可用性和可靠性触发,都是为了防止系统崩溃。
- 最终让用户体验到的是某些功能暂时不能用。
不同点:
- 服务熔断一般是下游服务故障导致的,而服务降级一般是从整体系统负荷考虑,由调用方控制。
- 两者的触发原因不同
8. 微服务- Feign
8.1 Feign介绍
Feign:(音标:
美[feɪn]
,意思是假装,装作,佯装)。
在SpringCloud微服务中Feign是一个开源库,用来编写HTTP请求。
Feign项目的github主页地址:https://github.com/OpenFeign/feign
微服务使用Feign
的目的是(Feign makes writing java http clients easier)
让编写Http请求更容易,简化拼接url,拼接参数等等操作
Feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。Spring Cloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。
8.2 Feign工作流程图
原理概述:
- 启动时,程序会进行包扫描,扫描所有包下所有
@FeignClient
注解的类,并将这些类注入到spring的IOC容器中。当定义的Feign中的接口被调用时,通过JDK的动态代理来生成RequestTemplate
。RequestTemplate
中包含请求的所有信息,如请求参数,请求URL等。RequestTemplate
声场Request,然后将Request交给client处理,这个client默认是JDK的HTTPUrlConnection
,也可以是OKhttp
、Apache
的HTTPClient
等。- 最后client封装成
LoadBaLanceClient
,结合Ribbon
负载均衡地发起调用。
8.3常用FeignClient注解属性
属性名 | 默认值 | 作用 |
---|---|---|
value | 空字符串 | 调用服务名称,和name属性相同 |
serviceId | 空字符串 | 服务id,作用和name属性相同 |
name | 空字符串 | 调用服务名称,和value属性相同 |
url | 空字符串 | 全路径地址或hostname,http或https可选 |
decode404 | false | 配置响应状态码为404时是否应该抛出FeignExceptions |
configuration | {} | 自定义当前feign client的一些配置 |
fallback | void.class | 熔断机制,调用失败时,走的一些回退方法,可以用来抛出异常或给出默认返回数据。 |
path | 空字符串 | 自动给所有方法的requestMapping前加上前缀,类似与controller类上的requestMapping |
primary | true |
8.4 Feign使用案例
- pom.xml导入启动器依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- Application类通过注解
@EnableFeignClients
上开启Feign功能
@EnableFeignClients //注解开启feign
@SpringCloudApplication
public class Demo02ConsumerUser8002Application
- 编写Feign客户端(本质上是一个接口,Feign会通过
动态代理
生成实现类)
//String url = "http://demo01-provider-users/users/"+id;
//调用demo01-provider-user提供者,获取json数据,转成User对象
@FeignClient("demo01-provider-user")
public interface UserClient {
//定义方法
@GetMapping("/users/{id}")
User callProvider(@PathVariable long id);
}
Feign创建动态代理类的流程图
- 编写处理器
ConsumerFeignController
,注入Feign客户端并使用;
@RestController
@RequestMapping("/feign")
@Slf4j
public class ConsumerFeignController {
@Autowired
UserClient userClient;
@RequestMapping(path = "/{id}", method = RequestMethod.GET)
public Object get(@PathVariable long id) throws InterruptedException {
User user = userClient.callProvider(id);
return user;
}
}
更多推荐
所有评论(0)