单体架构是最简单的软件架构,常用于传统的应用软件开发以及传统 Web 应用。传统 Web 应用,一般是将所有功能模块都打包(jar、war)在一个 Web 容器(Tomcate等)中部署、运行。
- 当某一天使用单体架构发现很难推进需求的开发、以及日积月累的技术债时,很多企业会开始做单体服务的拆分,拆分的方式一般有水平拆分和垂直拆分。垂直拆分是把一个应用拆成松耦合的多个独立的应用,让应用可以独立部署,有独立的团队进行维护;水平拆分是把一些通用的,会被很多上层服务调用的模块独立拆分出去,形成一个共享的基础服务,这样拆分可以对一些性能瓶颈的应用进行单独的优化和运维管理,也在一定程度上防止了垂直拆分的重复造轮子。
- SOA 也叫面向服务的架构,从单体服务到 SOA 的演进,需要结合水平拆分及垂直拆分。SOA 强调用统一的协议进行服务间的通信,服务间运行在彼此独立的硬件平台但是需通过统一的协议接口相互协作,也即将应用系统服务化。举个易懂的例子,单体服务如果相当于一个快餐店,所有的服务员职责都是一样的,又要负责收银结算,又要负责做汉堡,又要负责端盘子,又要负责打扫,服务员之间不需要有交流,用户来了后,服务员从前到后负责到底。SOA
相当于让服务员有职责分工,收银员负责收银,厨师负责做汉堡,保洁阿姨负责打扫等,所有服务员需要用同一种语言交流,方便工作协调。
这里不引用书本上的复杂概论了,简单来说微服务就是很小的服务,小到一个服务只对应一个单一的功能,只做一件事。这个服务可以单独部署运行,服务之间可以通过RPC来相互交互,每个微服务都是由独立的小团队开发,测试,部署,上线,负责它的整个生命周期。
微服务架构算是SOA架构的一种拓展,主要关注的是服务个体的独立性、拆分粒度更小。相对于SOA架构来说,微服务拥有以下优势:
何为分布式?
分布式服务顾名思义服务是分散部署在不同的机器上的,一个服务可能负责几个功能,是一种面向SOA架构的,服务之间也是通过rpc来交互或者是webservice来交互的。逻辑架构设计完后就该做物理架构设计,系统应用部署在超过一台服务器或虚拟机上,且各分开部署的部分彼此通过各种通讯协议交互信息,就可算作分布式部署,生产环境下的微服务肯定是分布式部署的,分布式部署的应用不一定是微服务架构的,比如集群部署,它是把相同应用复制到不同服务器上,但是逻辑功能上还是单体应用。
SpringCloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理
在传统的RPC远程调用框架中,管理每个服务与服务之间的依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务与服务之间依赖关系,可以实现服务调用,负载均衡,容错等,实现服务的注册与发现
用大白话来讲,Eureka就是一个服务中心,将所有的可以提供的服务都注册到它这里来管理,其它各调用者需要的时候去注册中心获取,然后再进行调用,避免了服务之间的直接调用,方便后续的水平扩展、故障转移等
Eureka系统架构:
Eureka包含两个组件:EurekaServer和EurekaClient
Eureka Server: 提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到
Eureka Client: 通过注册中心进行访问
是一个java客户端,用于简化EurekaServer的交互,客户端同时也具备一个内置的、使用轮询负载算法的负载均衡器。在应用启动中,将会向EurekaServer发送心跳(默认30秒)。如果EurekaServer在多个心跳周期内没有接受到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90)秒
1.建立父工程:springcloud
pom.xml:方便统一管理子模块的jar包版本
<packaging>pompackaging>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<junit.version>4.12junit.version>
<log4j.version>1.2.17log4j.version>
<lombok.version>1.16.18lombok.version>
<mysql.version>5.1.47mysql.version>
<druid.version>1.1.16druid.version>
<mybatis.spring.boot.version>1.3.0mybatis.spring.boot.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.2.2.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>${druid.version}version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>${mybatis.spring.boot.version}version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
<optional>trueoptional>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<fork>truefork>
<addResources>trueaddResources>
configuration>
plugin>
plugins>
build>
2.建立子模块:EurekaServer:cloud-eureka-server7001
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
<dependency>
<groupId>com.baidugroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
dependency>
dependencies>
application.yml:
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置eureka server 交互的地址查询服务和注册服务都需要这个地址
#集群配置:互相注册,相互守望
# defaultZone: http://eureka7002.com:7002/eureka/
#单机配置
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主启动类:EurekaMain7001
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class, args);
}
}
3.建立子模块:支付服务:cloud-provider-payment8001,将支付服务注册进Eureka中
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.baidugroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
application.yml:
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/cloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
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
instance:
instance-id: payment8001
prefer-ip-address: true
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.badiu.springcloud.entity # 所有Entity别名类所在包
主启动类:PaymentMain8001
@MapperScan("com.baidu.springcloud.mapper")
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
controller:
@RestController
@Slf4j
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@PostMapping("/payment/create")
public CommonResult creatPayment(@RequestBody Payment payment){
int result = paymentService.create(payment);
log.info("-----插入结果:"+result);
if (result>0){
return new CommonResult(200,"插入数据成功,serverPort"+serverPort,result);
}else{
return new CommonResult(444,"插入数据失败",null);
}
}
@GetMapping("/payment/get/{id}")
public CommonResult creatPayment(@PathVariable("id")Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("-----查询结果:"+payment);
if (payment != null){
return new CommonResult(200,"查询成功,serverPort"+serverPort,payment);
}else{
return new CommonResult(404,"查询失败,没有ID:"+id,null);
}
}
}
4.建立子模块:订单服务:cloud-consumer-order80,订单服务通过Eureka远程调用支付服务
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.baidugroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
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
instance:
instance-id: order80
prefer-ip-address: true
主启动类:OrderMain80
@SpringBootApplication
@EnableEurekaClient
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
config/ApplicationContextConfig:配置restTemplate调用
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
controller:
@RestController
@Slf4j
public class OrderController {
//public static final String PAYMENT_URL = "http://localhost:8001";
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment){
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class);
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
}
5.启动测试
Eureka中已经有了订单服务和支付服务
订单服务调用支付服务成功
微服务RPC远程服务调用的核心是什么?
我想回答的一定是高可用,试想你的注册中心只有一个,万一它出故障了,会导致整个微服务环境不可用,所以需要搭建Eureka注册中心集群,实现负载均衡和故障容错
1.参照cloud-eureka-server7001新建cloud-eureka-server7002的EurekaServer:互相注册,相互守望
application.yml:
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置eureka server 交互的地址查询服务和注册服务都需要这个地址
defaultZone: http://eureka7001.com:7001/eureka/
2.修改cloud-eureka-server7001配置文件
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置eureka server 交互的地址查询服务和注册服务都需要这个地址
#集群配置:互相注册,相互守望
defaultZone: http://eureka7002.com:7002/eureka/
3.建立cloud-provider-payment8002,建立支付服务集群,实现订单服务调用支付服务负载均衡
application.yml:将8002注册到两个注册中心去
server:
port: 8002
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/cloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
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
instance:
instance-id: payment8002
prefer-ip-address: true
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.badiu.springcloud.entity # 所有Entity别名类所在包
4.修改cloud-provider-payment8001配置文件为集群
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
5.使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
@Configuration
public class ApplicationContextConfig {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
6.启动测试
两个注册中心相互守望,一个服务坏了,另一个也能继续维持系统的使用
订单服务调用支付服务集群时,也达到了交替访问,负载均衡的效果,注意端口号
对于注册进Eureka里面的微服务,可以通过服务发现来获取该服务的信息
1.修改8001端口的支付服务的controller
@Resource
private DiscoveryClient discoveryClient;
@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info("*****service: "+service);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
2.只启动类上添加注解@EnableDiscoveryClient
3.启动测试
1.概述
保护模式主要用于一组客户端和EurekaServer之间存在网络分区场景下的保护。一旦进入保护模式,EurekaServer将会尝试保护其服务注册表中的信息,不再删除服务注册表的数据,也就是不会注销任何微服务。
如果在EurekaServer首页看到一下这段提示,则说明Eureka进入了保护模式:
2.为什么会产生Eureka自我保护机制
为了防止EurekaClient可以正常运行,但是与EurekaServer网络不通等情况下,EurekaServer不会立刻将EurekaClient服务删除
3.什么是自我保护机制
默认情况下,EurekaClient会定时向EurekaServer端发送心跳。如果EurekaServer端在一定时间内(默认90秒)没有收到EurekaClient发送心跳,就会直接从服务注册列表中剔除该服务。但是当网络分区出现故障时(延迟,卡顿,拥挤),微服务与EurekaServer之间无法正常通信,以上行为就会变得很危险,因为服务本身其实是健康的,此时不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失大量的服务实例心跳时(可能出现了网络分区故障),那么这个节点就会进入自我保护模式。
综上所述,自我保护模式是一种应对网络异常的安全保护措施。他的哲学架构是宁可同时保留所有的微服务(健康的和不健康的),也不盲目注销任何一个健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定