传统单体架构介绍及优缺点
一个项目包(war包,归档包)包含了应用的所有功能, 在没有出现微服务概念之前,基本上都是这种架构形式存在, 我们一般把程序打包成一个文件后,扔到tomcat或者jetty, jboss等应用服务器中即可
特点:
部署很简单,符合我们的思维;项目臃肿;技术债务;部署频率低;扩展性差;阻碍技术创新
单体架构到微服务架构的改造及优缺点
把每个独立的模块单独抽出来作为一个独立运行的服务,服务之间采用轻量级Rest方式调用
微服务特点:
每个小组专注于一个微型服务,致力于该服务的稳定性,可用性,服务性能,以及业务的迭代开发
每个微服务可以独立运行(独立一个进程运行)
多个微服务或者说一系列微服务组合起来就构建了一个或者多个独立的系统
每个微服务只针对独立的业务开发基础的服务,也就是说一个微服务只关注某个特定的功能
每个微服务可以使用不同的技术实现,以及每个微服务有自己独立的数据库
每个微服务之间通过一些轻量的通讯机制进行通讯,例如REST API
更加容易部署,而且可以全自动部署
运维要求高
分布式固有的复杂性
接口调用成本高
微服务与SOA联系及区别
SOA(面向服务架构)是集成多个较大组件(一般是应用)的一种机制,它们将整体构成一个彼此协作的套件,是一种粗粒度、松耦合服务架构,服务之间通过简单、精确定义接口进行通讯
微服务架构中,业务逻辑被拆分成一系列小而松散耦合的分布式组件,共同构成了较大的应用,每个组件都被称为微服务
微服务架构是SOA架构的子集,微服务架构的粒度更加细
我么们看个小例子:
基于Spring boot 构建服务:用户微服务;订单微服务
调用流程:客户端---->订单微服务------>
用户的controller
@Controller
@RequestMapping("/person")
public class PersonController {
private Logger logger = LoggerFactory.getLogger(PersonController.class);
@Autowired
private PersonMapper mapper;
@RequestMapping("/getPersonById")
@ResponseBody
public Person getPersonById(Integer id){
logger.info("param id : {}",id);
return mapper.selectByPrimaryKey(id);
}
}
order的controller
@RestController
@RequestMapping("/order")
public class OrderController {
private Logger logger = LoggerFactory.getLogger(OrderController.class);
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/person/gerPerssonById")
public Object getPersonById(Integer id){
String url = "http://localhost:8080/person/getPersonById?id="+id;
logger.debug("param userId : {}, request url : {}", id, url);
ResponseEntity<Object> result = restTemplate.getForEntity(url, Object.class);
return result;
}
}
分别测试:
上面就是通过RestTemplate来实现了从订单访问用户,但是当用户微服务有多台实例提供高可用服务或者负载均衡服务的时候,订单微服务怎么来调用用户微服务呢?同时怎么支持服务动态水平增加或者减少呢?
- Nginx(服务端的负载均衡),但如果微服务很多,那么你的nginx也要很多,这样子维护起来就很困难了。
- 服务发现组件
服务提供者、服务消费者、服务发现组件这三者之间的关系大致如下:
- 各个微服务在启动时,将自己的网络地址等信息注册到服务发现组件中,服务发现组件会存储这些信息。
- 服务消费者可从服务发现组件查询服务提供者的网络地址,并使用该地址调用服务提供者的接口。
- 各个微服务与服务发现组件使用一定机制(例如心跳)通信。服务发现组件如长时间无法与某微服务实例通信,就会注销该实例。
- 微服务网络地址发生变更(例如实例增减或者IP端口发生变化等)时,会重新注册到服务发现组件。使用这种方式,服务消费者就无须人工修改提供者的网络地址了。
综上,服务发现组件应具备以下功能。
- 服务注册表:是服务发现组件的核心,它用来记录各个微服务的信息,例如微服务的名称、IP、端口等。服务注册表提供查询API和管理API,查询API用于查询可用的微服务实例,管理API用于服务的注册和注销。
- 服务注册与服务发现:服务注册是指微服务在启动时,将自己的信息注册到服务发现组件上的过程。服务发现是指查询可用微服务列表及其网络地址的机制。
- 服务检查:服务发现组件使用- -定机制定时检测已注册的服务,如发现某实例长时间无法访问,就会从服务注册表中移除该实例。
综上,使用服务发现的好处是显而易见的。SpringCloud提供了多种服务发现组件的支持,例如Eureka、Consul和Zookeeper等。
服务注册与发现组件Eureka
Eureka简介
Eureka是Netfix开源的服务发现组件,本身是一个基于REST的服务。它包含Server和Client两部分。Spring Cloud将它集成在子项目Spring Cloud Netfix中,从而实现微服务的注册与发现。
Region和Availbility Zone均是AWS的概念。其中,Region表示AWS中的地理位置,每个Region都有多个Availability Zone,各个Region之间完全隔离。AWS通过这种方式实现了最大的容错和稳定性。
Spring Cloud默认使用的Region是us-east-1 ,在非AWS环境下,可以将Availability Zone理解成机房,将Region理解为跨机房的Eureka集群。
理解Region和Availability Zone后,来分析一下Eureka的原理, Eureka架构
- Application Service相当于服务提供者。
- Application Client相当的服务消费者。
- Make Remote Call,可以理解成调用RESTful API的行为。
- us-east-lc、 us-east- ld等都是zone,它们都属于us-east-1 这个region。
由图可知,Eureka 包含两个组件: Eureka Server和Eureka Client,它们的作用如下:
- Eureka Server提供服务发现的能力,各个微服务启动时,会向Eureka Server注册自己的信息(例如IP、端口、微服务名称等), Eureka Server会存储这些信息。
- Eureka Client是一一个Java客户端,用于简化与Eureka Server的交互。
- 微服务启动后,会周期性(默认30秒)地向EurekaServer发送心跳以续约自已的 “租期”。
- 如果Eureka Server 在一定时间内没 有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。
Eureka的应用
编写 Eureka Server
- 默认情况下, Eureka Server同时也是Eureka Client。 多个Eureka Server实例,互相之间通过复制的方式,来实现服务注册表中数据的同步。
- Eureka Client会缓存服务注册表中的信息。这种方式有- -定的优势一首先, 微服务
- 无须每次请求都查询Eureka Server,从而降低了Eureka Server的压力;其次,即使EurekaServer所有节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者并完成调用。
- 综上,Eureka 通过心跳检查、客户端缓存等机制,提高了系统的灵活性、可伸缩性和可用性。
编写一个EurekaServer
再springboot项目中的pom文件加入spring cloud父POM及spring-cloud-starter-eureka-server
<!-- 加入spring cloud 的pom -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 加入eureka的服务器依赖包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
2.编写一个启动类,在启动类上面添加@EnableEurekaServer注解,声明这是一个Eureka Server
package com.cym.eurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
3,在配置文件中添加以下的内容。
server.port=8761
#取消向eureka server(注册中心)注册
eureka.client.register-with-eureka=false
#取消向eureka server(注册中心)获取注册信息
eureka.client.fetch-registry=false
#eureka 提供服务发现的地址
#eureka.client.service-url.defaultZone=http://cym:123@localhost:8761/eureka
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
# 安全模块
#security.basic.enabled=true
#security.user.name=cym
#security.user.password=123
简要讲解一下application.yml中的配置属性:
- eureka.client.registerwithEureka:表示是否将自己注册到Eureka Server,默认为true。由于当前应用就是Eureka Server,故而设为false。
- eureka.client.fetchRegistry: 表示是否从Eureka Server获取注册信息,默认为true。因为这是-一个单点的Eureka Server,不需要同步其他的Bureka Server节点的数据,故而设为false。
- eureka.client.serviceUrl.defaultZone: 设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。默认是http://localhost:8761/eureka; 多个地址可使用,分隔。
- 这样一个Eureka Server就编写完成了。
测试:启动eureka的启动类
注意:在启动的时候出现以下的错误
java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder.<init>([Ljava/lang/Object;)V
这是你springboot的版本和spring cloud的版本不一致造成的。(可以修改springboot的版本号或者springcloud的版本号)
启动成功后访问:http://localhost:8761/
注意:不要访问 http://localhost:8761/eureka,如果访问这个你是没有办法打开的。
将微服务注册在Eureka上
在已经创建好的的微服务(我是使用上面的person和order两个微服务程序)
分别在person和order的pom文件中加入
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
person的启动类:
package com.cym.microserviceorder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class MicroservicePersonApplication {
public static void main(String[] args) {
SpringApplication.run(MicroservicePersonApplication.class, args);
}
}
person的application.properties
#数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
#eureka的client
spring.application.name=microservice-person
server.port=9090
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
#eureka.client.serviceUrl.defaultZone=http://cym:123@localhost:8761/eureka
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.application.name}:${spring.application.instance_id:${server.port}}
- 其中,spring. application. name用于指定注册到Eureka Server.上的应用名称;
- eureka. instance.prefer- ip-address = true表示将自己的IP注册到Eureka Server。
- 如不配置该属性或将其设置为false,则表示注册微服务所在操作系统的hostname到EurekaServer。
用同样的方式配置order。
不过order的启动类有点稍稍的不同,就是增加一个负载均衡的注解配置
package com.cym.micreoserviceorder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
public class MicreoserviceOrderApplication {
@Bean
@LoadBalanced//增加负载均衡的配置
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MicreoserviceOrderApplication.class, args);
}
}
order的application.properties
server.port=9091
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
spring.application.name=microservice-order
#eureka.client.serviceUrl.defaultZone=http://cym:123@localhost:8761/eureka
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.application.name}:${spring.application.instance_id:${server.port}}
测试
首先启动:eureka-server
然后启动: micreoservice-order和micreoservice-person
通过order的controller来调用person
package com.cym.micreoserviceorder.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/order")
public class OrderController {
private Logger logger = LoggerFactory.getLogger(OrderController.class);
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/person/gerPerssonById")
public Object getPersonById(Integer id){
// String url = "http://localhost:8080/person/getPersonById?id="+id;
//有了eureka的服务注册列表后,我们就不需要硬编码url了,我们可以直接通过eureka中获得服务提供者的ip和端口
String url = "http://microservice-person/person/getPersonById?id="+id;
logger.debug("param userId : {}, request url : {}", id, url);
ResponseEntity<Object> result = restTemplate.getForEntity(url, Object.class);
return result;
}
}
其中注意的是:String url = "http://microservice-person/person/getPersonById?id="+id;中的microservice-person是虚拟路径,这个是在person的application.properties文件中配置的。
为EurekaServer添加用户认证,其实很简单,就是在原来的基础上添加一个
<!-- 安全模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
application.properties
server.port=8761
#取消向eureka server(注册中心)注册
eureka.client.register-with-eureka=false
#取消向eureka server(注册中心)获取注册信息
eureka.client.fetch-registry=false
#eureka 提供服务发现的地址
eureka.client.service-url.defaultZone=http://cym:777@localhost:8761/eureka
#eureka.client.service-url.defaultZone=http://localhost:8761/eureka
# 安全模块
security.basic.enabled=true
security.user.name=cym
security.user.password=123
同样 在person和order的配置文件的也要修改以下的配置
eureka.client.serviceUrl.defaultZone=http://cym:123@localhost:8761/eureka
测试:
当启动两个两个服务类提供者的时候,一样会往eureka中注册的。