微服务入门
一:微服务概述
1. 微服务是什么
(1)微服务的核心就是把传统的单机应用,根据业务将单机应用拆分成为一个个的服务,将其彻底的解耦,每一个服务都是提供特定的功能,一个服务只做一件事,类似进程,每个服务都能够单独部署,甚至可以拥有自己的数据库。这样的一个个小的服务就是微服务。
(2) 比如传统的单机电商应用,可以将整个服务化为订单,支付,库存,物流,积分等模块(这里的模块就是一个一个的service)。我们根据业务模型来进行拆分,可以拆分为订单服务,支付服务,库存服务,物流服务,积分服务,这一个个的服务就是微服务,而不再是一个个的service
(3)若不拆分的时候,我的非核心业务积分模块出现了重大的bug导致了系统内存溢出,导致整个服务down机,所有模块都不能使用;若拆分了之后,只是说我的积分微服务不可用,我的整个系统核心功能还是能够使用。
(4)图解
① all in one系统以及all in one 的集群版本
② 微服务架构版本
(5)all in one工程的数据存储以及微服务的数据存储
- 可以发现all in one工程的数据库是单一的数据库
- 微服务架构设计模式的数据库可以一个微服务一个,也可以多个微服务共享一个数据库,更加的灵活。
2. 微服务架构是什么?
- 微服务架构是一个架构风格
- 将一个单一应用程序开发为一组小型服务
- 每个服务运行在自己的进程中
- 服务之间通过轻量级的通信机制(Http RestfulAPI)
- 每个服务都能够独立的部署
- 每个服务甚至可以有自己的数据库
- 微服务和微服务架构是两个不同的概念
- 微服务强调的是服务的大小和对外提供的单一功能,而微服务架构是指把一个个的微服务组合管理起来,对外提供一套完整的服务。
3. 微服务的优缺点
-
优点
(1)每个服务足够小,足够内聚,代码更加容易理解,专注一个业务功能点(对比传统的应用,可能改几行代码就需要了解整个系统)
(2)开发简单,一个服务只做一件事
(3)微服务能够被2-5个人的小团队开发,提高效率
(4)服务松耦合,每个服务都能够独立开发
(5)前后端分离
(6)一个服务可以拥有自己的数据库,也可以多个服务连接一个数据库 -
缺点
(1)增加了运维人员的工作量,以前只需要部署一个war包,现在可能需要部署多个war包。
(2)服务之间相互调用,增加通信成本
(3)数据一致性问题(分布式事务问题)
(4)系统监控等
4. SpringCloud微服务技术栈有哪些技术
- SpringCloud的生态圈
- SpringCloud技术栈图示
CAP理论
一: 分布式系统的三个指标
- Consistency(持久性)
- Availability(可用性)
- Partition tolerance(分区容错性)
结论:这三个指标不可能同时做到,这个结论就叫做CAP定理
分区容错性(Partition tolerance)
- 大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思就是,区间通信可能失败(由于网络的原因)。比如,一台服务器放在北京,另一台服务器放在上海,这就是两个区,他们之间由于网络的原因导致不能通信
-
- 上图中,G1和G2是两台跨区的服务器,G1向G2发送一条消息,G2可能没有办法收到,系统设计的时候,必须要考虑这种情况。
- 一般来说,分区容错无法避免,因此可以认为CAP的P总是成立的。CAP定理告诉我们,剩下的C和A无法做到同时满足。
Consistency(一致性)
- 一致性表示的是各个服务器上的数据必须是一致的。
- Consistency中文叫做一致性。意思就是,写操作之后的读操作,必须返回该值。举例来说,某条记录是V0,用户G1发起一个写操作,将其改为V1
- 此时会存在问题:用户有可能向G2发起了读的操作,由于G2的值没有发生变化,因此返回的是V0,G1和G2读操作的结果不一致,这就不满足一致性了
- 解决方案:为了让G2也能变为V1,就要在G1写操作的时候,让G1向G2发送一条消息,要求G2也改成V1
Availability可用性
- 可用性,意思就是说只要收到了用户的请求,服务器就必须给出回应。
- 用户可以选择向G1或者G2发起读的操作。不管是哪一台服务器,只要收到了请求,就必须告诉用户,到底是V0还是V1,否则就不满足可用性。
为什么CAP只能够三选择二
- 一致性和可用性为什么不能够同时成立?其实答案很简单,因为可能会通信失败,就是出现分区容错。
- 如果保证G2的一致性,那么G1必须在写操作的时候,锁定G2的读操作和写操作。只有数据同步之后,才能重新开放G2的读写。锁定期间,G2不能读写,没有可用性。
- 如果保证G2的可用性,那么势必不能锁定G2,所以一致性不成立
- 综上所属,G2无法同时做到一致性和可用性,系统设计的时候只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,就没有办法做到一致性。
Eureka应用入门
一:没有使用注册中心搭建的分布式应用
- 服务消费者 & 服务提供者
- 举例说明
- user服务调用order服务,user服务就是服务的消费者,order服务就是服务的消费者
- 比如我们的User服务端口号是8889,Order服务的端口号是8890,此时我们可以在User服务中通过RestTemplate方式进行调用
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://localhost:8890/queryOrdersByUserId/"+userId,List.class);
- 缺点
(1)从上面看出的缺点就是,我们在调用的时候,请求的IP地址和端口都是硬编码,不灵活;若此时我们更换了服务提供方Order的机器或者端口号,我们是需要修改代码并进行重新部署的。
(2)假设我们的Order服务压力过大,我们需要把order服务作为集群,那么意味着Order是多节点的,这里如果还是这么访问,则达不到集群的效果
(3)我们也可以通过用Nginx做负载均衡,但是如果Order服务过多的话,我们的上游服务配置就会特别的繁琐。
- 服务发现原理初探
-
其实,服务发现的机制非常简单,我们其实可以用Mysql或者redis来实现,说白了就是一张表
-
实际上Eureka采取的是一个双层Map进行实现的
(1)应用启动的时候,自动往registry表中插入一条数据,数据包括服务的名称,ip,端口信息等
(2)应用停止的时候,自动把自己再registry表中的数据的status设置为DOWN
(3)服务调用,每次调用之前,去Mysql中查询select * from register where server_name=‘服务名称’ and status='UP’的记录,然后替换掉原来的调用路径上的地址。 -
如果用Mysql实现,是有很明显的缺点的:
(1)若当前服务DOWN机,我们必须要把自己的对应的数据标记为down
(2)服务每次调用都要发送select语句,mysql的压力很大
(3)若mysql挂了,我们就没办法实现了(实际上,即使服务注册中心挂了,我们也应该提供服务调用)
- 真正的服务注册发现组件应该满足下面的条件才能称为一个合格的服务发现注册组件:
(1)提供一个服务注册接口,其他服务启动的时候,把自己的IP,端口都在服务端注册下来
(2)提供一个服务发现接口,也就是微服务可以通过service_id来获取到对应微服务的网络地址和端口
(3)各个微服务与服务注册组件,使用心跳检查机制,若服务注册组件长时间和某个微服务之间没有通信,那么就应该剔除服务
(4)客户端缓存,各个微服务将需要调用服务的地址缓存到本地,并且使用一定机制进行更新(比如定时任务更新,事件推送更新等)。这样既能降低服务发现组件的压力,同时,即使服务发现组件出问题,也不会影响到服务之间的调用。
Eureka环境搭建
- eureka分为服务端和客户端
搭建Eureka的服务端
- 搭建这些无非就是三个步骤
(1)引入maven依赖
(2)启动类上开启注解
(3)编写配置文件
- 引入maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 注意:这里还需要引入其版本号
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 写注解,写在主启动类上
package com.xiyou.eureka1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author 92823
*/
@SpringBootApplication
@EnableEurekaServer
public class MyEurEkaServer1Application {
public static void main(String[] args) {
SpringApplication.run(MyEurEkaServer1Application.class, args);
}
}
- 在主启动类上加入@EnableEurekaServer
- 编写配置文件
# eureka Server服务端的端口号默认是8761
server:
port: 9000
eureka:
client:
# 是否将自己注册到Eureka Server中,默认是true,因为自己就是Eureka Server没有必要把自己注册进去,所以设置为false
register-with-eureka: false
# 表示是否从Eureka Server获取注册信息,默认是true,因为这是一个单店的Eureka Server,不需要同步其他的Eureka Server节点的数据,故而设置为false
fetch-registry: false
# 暴露给其他eureka Server的注册地址
service-url:
defaultZone: http://localhost:9000/eureka
搭建Eureka的客户端
- 老样子还是走三步
- 加入maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 依然这里也是需要指明版本的,参考上面,这里不再给出代码
- 加入注解@EnableDiscoveryClient
package com.xiyou.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author 92823
*/
@SpringBootApplication
@EnableDiscoveryClient
public class MyConsumer1Application {
public static void main(String[] args) {
SpringApplication.run(MyConsumer1Application.class, args);
}
}
- 编写配置文件
server:
port: 8889
# 注册到eureka服务端的服务名称
spring:
application:
name: my-eureka-consumer-user-1
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: consumer-user-8889
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
- 调用,使用RestTemplate,这里调用的时候直接使用的是注册到Eureka Server的服务名,不再是ip
package com.xiyou.client.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @author 92823
*/
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/queryUerInfoById/{id}")
public String queryUerInfoById(@PathVariable String id) {
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://my-eureka-consumer-order-1/queryOrderById/" + id, String.class);
String body = forEntity.getBody();
return body;
}
}
- 注意下,这里的微服务名字就是server.application.name中的,注意大小写
- RestTemplate的配置
package com.xiyou.client.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* 配置类,声明RestTemplate
* @author 92823
*/
@Configuration
public class MyConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
结果展示
- Eureka Server结果展示
这里我们发现服务端正常启动,并且Order和User客户端正常注入,并且我们访问User接口的时候,也可以通过RestTemplate正常调用到Order提供的接口。
搭建Eureka的服务端集群
- 我们在上面已经进行了Eureka服务端的搭建,这里我们直接再构建一个Eureka Server,并且修改其配置文件
- 我们这里只给出一个Eureka Server的配置文件,配置都是相同的基本上
# eureka Server服务端的端口号默认是8761
server:
port: 9001
eureka:
client:
# 是否将自己注册到Eureka Server中,默认是true,因为自己就是Eureka Server没有必要把自己注册进去,所以设置为false
# register-with-eureka: false
# 因为是集群环境,所以要把自己注册到服务端
register-with-eureka: true
# 表示是否从Eureka Server获取注册信息,默认是true,因为这是一个单店的Eureka Server,不需要同步其他的Eureka Server节点的数据,故而设置为false
# fetch-registry: false
# 这里是集群环境需要从Server上拉环境
fetch-registry: true
# 暴露给其他eureka Server的注册地址
service-url:
defaultZone: http://${spring.security.user.name}:${spring.security.user.name}@localhost:9001/eureka, http://${spring.security.user.name}:${spring.security.user.name}@localhost:9000/eureka
spring:
security:
user:
name: root
password: 123456
- 这里我们还应该导入maven依赖,因为我们开启了eureka server的安全配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 客户端的Eureka Server的注册地址也应该改为:
eureka:
client:
service-url:
defaultZone: http://${security.login.username}:${security.login.pass}@www.eureka9000.com:9000/eureka/
- 加入密码之后我们的访问就需要输入用户名和密码:
Eureka部署架构
- Eureka的部署架构图
这里大家可能会有疑问什么是us-east-1c,us-east-1d, us-east-1e是什么东西?
- 其实这里的us-east-1c可以类比于阿里云服务器,我们在选择阿里云服务器的时候,是不是要选择区域,这个我们选择的区域就可以理解为一个个的Region(us-east-1c)。各个Region之间的内网是不通的
- Availabitlity Zone可用区,这一个个的可用区我们可以理解为我们一个地区比如华南地区,不同城市的机房,zone之间是相通的,内网是相通的,比如华南1,华东1,2等
- Eureka Server:提供服务发现的能力,各个微服务启动的时候,会向Eureka Server注册自己的信息(l如IP,端口,微服务名称等),Eureka Server会存储这些信息
- Eureka Client:是一个Java客户端,用于简化与Eureka Server的交互(启动的时候,会向Server注册自己的信息)
- 服务续约(renew):会周期(默认30s)向Eureka Server发送心跳以续约自己的租期
- 如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳(最后一次续约时间开始计算),Eureka Server将会注销该实例(默认90s)
- 默认的情况下,Eureka Server同时也是Eureka Client。多个Eureka Server实例,互相之间通过增量复制的方式,来实现服务注册表中数据的同步。 Eureka Server默认保证在90s内,Eureka Server集群内的所有实例中的数据达到一致。
- Eureka Client会缓存服务注册表中的信息。这种方式有一定的优势—首先,微服务无需每次请求都查询Eureka Server,从而降低了Eureka Server的压力;其次Eureka Server所有节点都宕机,服务消费者依然可以使用缓存中的信息找到服务提供者并完成调用。
Eureka主要配置讲解
服务注册配置
- eureka.client.register-with-eureka=true
- 该配置说明,是否想注册中心注册自己,在非集群环境下设置为false,表示不注册自己
- eureka.client.fetch-registry=false
- 该配置说明,注册中心是否需要维护服务实例清单,非集群环境下,不需要做检索服务,所以设置为false
服务续约配置
- eureka.instance.lease-renewal-interval-in-seconds=30(默认)
- 该配置说明,服务提供者会维持一个心跳告诉eureka Server 我还活着,这个就是一个心跳周期
- eureka.instance.lease-expiration-duration-in-seconds=90(默认)
- 该配置说明,你的最后一次续约时间开始,往后推90s还没有接受到你的心跳,那么我就需要把你剔除掉
获取服务配置, 注意:这里的前提是eureka.client.fetch-registry为true
- eureka.client.registry-fetch-interval-seconds=30
- 缓存在调用方的微服务实例清单刷新时间。
Eureka的自我保护功能
- 默认的情况下,若eureka server在一段时间内(90s)没有接收到某一个微服务实例的心跳,那么eureka server就会被剔除
- 该实例,当时由于网络分区故障,导致eureka server和服务之间无法通信,此时这个情况就会变的很可怕,因为微服务的实例是健康的,不应该注销。
- 那么eureka server的自我保护模式就起作用了,当eureka server节点短时间(15min是否低于85%),丢失过多客户端是由于网络分区导致,那么该eureka server节点就会进入自我保护模式,eureka server就会自动保护注册表中的微服务实例,不再删除该注册表中微服务的信息,等到网络恢复,eureka server节点就会退出自我保护功能。