1.MicroService(微服务)架构
1.1.什么是MicroService架构
微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。(摘自百度)
微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被技术选型,独立开发,独立部署,独立运维,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。(摘自百度)
1.2.微服务远程调用方式
1.2.1常用调用方式:
- RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型
- Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。
现在热门的Rest风格,就可以通过http协议来实现
1.2.2.如何选择:
- 速度来看,RPC要比http更快,虽然底层都是TCP,但是http协议的信息往往比较臃肿,不过可以采用gzip压缩。
- 难度来看,RPC实现较为复杂,http相对比较简单
- 灵活性来看,http更胜一筹,因为它不关心实现细节,跨平台、跨语言。
- 如果对效率要求更高,并且开发过程使用统一的技术栈,那么用RPC还是不错的。 dubbo
- 如果需要更加灵活,跨语言、跨平台,显然http更合适 springcloud
那么我们该怎么选择呢?
微服务,更加强调的是独立、自治、灵活。而RPC方式的限制较多,因此微服务框架中,一般都会采用基于Http的Rest风格服务。
1.3实现技术解决方案
1.3.1.SpringBoot解决方案:
Java中可以使用传统ssm ssj等架构,当然更加推荐Springboot。Spring Boot是一套快速配置脚手架,可以基于Spring Boot快速开发单个微服务;
具体SpringBoot的教程,可以参考我的另外三个博客:
SpringBoot基础01
SpringBoot基础02
SpringBoot基础03
这里不做过多赘述。
1.3.2.服务治理框架:
由于微服务架构中存在多个微服务,那么如何管理和协调这些服务呢?就需要服务治理框架,而springcloud就是一个基于Spring Boot实现的服务治理工具包
2.SpringCloud
2.1是什么:
Spring cloud是一个基于Spring Boot实现的服务治理工具包,用于微服务架构中管理和协调服务的。
2.2组成部分:
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Springcloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
主要组成成分为:
而五大神兽则是SpringCloud的灵魂
五大神兽:
服务注册发现——Netflix Eureka : 注册所有微服务的通信地址
客服端负载均衡——Netflix Ribbon\Feign :服务之间的调用问题
断路器——Netflix Hystrix :解决微服务故障问题,微服务的隔离
服务网关——Netflix Zuul :微服务的统一入口
分布式配置——Spring Cloud Config :统一管理微服务的配置文件
但SpringCloud的工具包远不止这5个,这只是列举了一部分,具体的可查阅SpringCloud官网。
2.3搭建一个SpringCloud项目:
由于我使用的是Idea,所以这里首先创建一个普通Maven项目,将它命名为SpringCloud-parent作为父路径
随即删除该目录的Src目录,因为这里作为父目录,所以用不到Src。
之后导入父目录的pom
<?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.penny</groupId>
<artifactId>day72-springcloud-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>product-interface</module>
<module>product-service-8081</module>
<module>eureka-server-7081</module>
<module>order-service-9081</module>
<module>eureka-server-7082</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
<springboot.version>2.0.5.RELEASE</springboot.version>
</properties>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
里面分别导入了SpringCloud和SpringBoot的版本控制依赖以及配置了编码格式和定义了Java版本。
之后便右键我们的Springcloud-parent目录,创建一个Module模块,叫做Product-interface用来放我们的公共代码(P.s:此处仅存放公共代码,并非一个接口)
product-inteface:
pom如下:
<?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>day72-springcloud-parent</artifactId>
<groupId>com.penny</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>product-interface</artifactId>
</project>
创建一个测试用的Product实例:
Product:
package com.penny.domain;
/*提供公共的domain,query,依赖等*/
public class Product {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public Product(Long id, String name) {
this.id = id;
this.name = name;
}
public Product() {
}
}
公共代码模块创建完成后,根据SpringCloud微服务原理,需要一个服务提供者,一个服务消费者才能让该模式正常运作,所以接下来我们创建一个服务提供者,命名为Product-service-8081(P.s:8081为服务端口号)
Product-service-8081:
项目结构如下:
pom如下:
<?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>day72-springcloud-parent</artifactId>
<groupId>com.penny</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>product-service-8081</artifactId>
<dependencies>
<!--公共依赖-->
<dependency>
<groupId>com.penny</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>product-interface</artifactId>
</dependency>
<!--springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--eureka客户端支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
公共依赖的product-interface千万不要忘了加!!!
随后我们来创建服务启动类 ProductService8081App:
ProductService8081App:
@SpringBootApplication
public class ProductService8081App {
public static void main(String[] args) {
SpringApplication.run(ProductService8081App.class, args);
}
}
然后我们来创建一个测试controller
ProductController:
@RestController
@RequestMapping("/product")
public class ProductController {
//restfull风格
@GetMapping("{id}")
public Product getProductById(@PathVariable(name = "id")Long id){
return new Product(id,"阿莫西林");
}
}
随后配置一下application.yml
application.yml:
server:
port: 8081
spring:
application:
name: PRODUCT-SERVICE-8081 #服务的名字
然后启动服务后,在浏览器里输入http://localhost:8081/product/1
即可查看到结果
创建完服务提供者过后,接下来我们创建一个服务的消费者order-service-9081
order-service-9081:
右键SpringCloud-parent新建一个模块,命名为order-service-9081
项目结构如下:
pom如下:
<?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>day72-springcloud-parent</artifactId>
<groupId>com.penny</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-service-9081</artifactId>
<dependencies>
<!--公共依赖-->
<dependency>
<groupId>com.penny</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>product-interface</artifactId>
</dependency>
<!--springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--eureka客户端支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
同样不要忘了加公共依赖的product-interface!!!
随后一个启动类:
OrderService9081App:
@SpringBootApplication
public class OrderService9081App {
public static void main(String[] args) {
SpringApplication.run(OrderService9081App.class, args);
}
}
然后创建一个配置类CfgBean,该配置类主要通过RestTemplate远程调用服务提供者的服务,
CfgBean:
package com.penny.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class CfgBean {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
然后创建一个测试Controller
OrderController:
package com.penny.controller;
import com.penny.domain.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
//通过暴露id远程查询product的接口
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/product/{id}")
public Product getProductById(@PathVariable("id") Long id){
//此处获取服务名要小写
List<ServiceInstance> instances = discoveryClient.getInstances("product-service-8081");
//此时实例只有一个,正式开发时不这样使用
ServiceInstance serviceInstance = instances.get(0);
//serviceInstance.getHost()获取地址
//serviceInstance.getPort()获取端口
String Url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+
"/product/";
//进行远程调用
String url = Url+id;
return restTemplate.getForObject(url, Product.class);
}
}
然后再配置application.yml
application.yml:
server:
port: 9081
spring:
application:
name: ORDER-SERVICE-9081 #服务名字
随后启动服务,在浏览器里访问http://localhost:9081/order/product/1即可看到效果
至此,一个最基础的微服务便完成。
2.4.Eureka注册中心
2.4.1.是什么:
Eureka是netflix的一个子模块,也是核心模块之一,Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务发现和注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务,而不需要修改服务调用的配置文件了,功能类似于dubbo的注册中心,比如zookeeper。
2.4.2.原理:
Eureka采用了C-S的设计架构。Eureka Server作为服务注册功能的服务器,它是服务注册时中心。
而系统中的其他微服务,使用eureka的客户端连接到eureka server并维持心跳连接。这样系统的维护人员就可以通过eureka server来监控系统中各个微服务是否正常运行。SpringCloud的一些其他模块就可以通过eureka server来发现系统中的其他微服务,并执行相关的逻辑。
三大角色:
Eureka server提供服务注册和发现
Eureka Server提供服务注册服务。各个服务提供者节点启动后,会在Eureka Server中进行注册,这样Eureka server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到
Service Provider服务提供方将自身服务注册到Eureka,从而使服务消费方能够找到。(Client)
Service Consumer服务消费方从Eureka获取注册服务列表,从而能够消费服务。(Client)
Eureka client是一个java客户端,用于简化eureka server的交互,在应用启动后,将会向Eureka Server发送心跳。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表把这个服务节点移除。
2.4.3.注册中心解决的问题:
- 服务管理
- 如何自动注册和发现
- 如何实现状态监管
- 如何实现动态路由
- 服务如何实现负载均衡
- 服务如何解决容灾问题
- 服务如何实现统一配置
2.4.5注册中心的搭建:
紧接着之前我们创建服务提供者,服务消费者的基础上,右键SpringCloud-parent创建一个模块,命名为:eureka-server-7081
eureka-server-7081:
项目结构如下:
pom如下:
<?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>day72-springcloud-parent</artifactId>
<groupId>com.penny</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server-7081</artifactId>
<dependencies>
<!--springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--Eureka服务端支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
此处需要注意的是,加了eureka服务端的支持包
<!--Eureka服务端支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
随后创建一个服务启动类EurekaServer7081App
EurekaServer7081App:
package com.penny;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
//标志为Eureka服务端
@EnableEurekaServer
public class EurekaServer7081App {
public static void main(String[] args) {
SpringApplication.run(EurekaServer7081App.class, args);
}
}
并且一定要加上@EnableEurekaServer
该注解,否则它将无法作为一个注册中心服务端启动
完成之后,来配置application.yml
application.yml:
server:
port: 7081
eureka:
instance:
hostname: eurekaServer-7081 #localhost #eurekaServer-7081 #集群名字
client:
registerWithEureka: false #是否要注册到eureka
fetchRegistry: false #表示是否从Eureka Server获取注册信息
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #访问路径
需要注意的是,此处像我一样的hostname-eurekaServer-7081需要在C:\Windows\System32\drivers\etc\的host文件里映射本地地址才可使用
如不想映射,可直接填127.0.0.1或者localhost即可
随后启动服务,在浏览器里访问http://localhost:7081,出现如下界面则证明注册中心配置成功
需要注意的是,如果出现
这是属于eureka的自我保护机制导致,不影响使用
2.4.6.将服务加入注册中心:
步骤进入到此时,注册中心已经搭建好,但要想在注册中心看到我们之前创建的服务,则需要更改一下配置。
在之前创建服务提供者和服务消费者的时候,已经导入了一个叫做spring-cloud-starter-netflix-eureka-client
的依赖包,在注册中心中,服务提供者和服务消费者统一都叫做客户端,注册中心叫做服务端,所以只需在product-service-8081和order-service-9081的pom中导入该依赖即可
<!--eureka客户端支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
然后修改product-service-8081的application.yml配置
server:
port: 8081
spring:
application:
name: PRODUCT-SERVICE-8081 #服务的名字
eureka:
client:
service-url:
defaultZone: http://localhost:7081/eureka/
instance:
prefer-ip-address: true #当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1
然后修改order-service-9081的application.yml配置
server:
port: 9081
spring:
application:
name: ORDER-SERVICE-9081 #服务名字
eureka:
client:
service-url:
defaultZone: http://localhost:7081/eureka/ #指向注册中心
instance:
prefer-ip-address: true #当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1
然后在ProductService8081App和OrderService9081App的启动类上加上@EnableEurekaClient
该注解,表示开启Eureka客户端
@SpringBootApplication
//开启Eureka客户端
@EnableEurekaClient
public class ProductService8081App {
public static void main(String[] args) {
SpringApplication.run(ProductService8081App.class, args);
}
}
@SpringBootApplication
@EnableEurekaClient
public class OrderService9081App {
public static void main(String[] args) {
SpringApplication.run(OrderService9081App.class, args);
}
}
然后依次启动EurekaServer7081App,ProductService8081App,OrderService9081App即可
在浏览器里我们就能看到两个服务被注册了进来
2.4.7.Eureka集群
2.4.7.1.为什么需要集群:
设想如果我们的一台注册中心服务器挂掉了,那该注册中心的所有服务也就一并全部挂掉。为了避免出现这种情况,便出现了集群。在原来一个注册中心的基础上,再加1个或N个注册中心服务器,当其中一台挂掉以后,剩余的其他注册中心服务器继续来完成挂掉的那台的工作,从而不影响注册中心里的内容工作。
2.4.7.2.实现:
要想做集群,我们需要在复制一份eureka-server-7081,然后将它命名为:eureka-server-7082,该模块里内容除了application.yml以外,其余内容都一模一样。
接下来便修改eureka-server-7081的application.yml
server:
port: 7081
eureka:
instance:
hostname: eurekaServer-7081 #localhost #eurekaServer-7081 #集群名字
client:
registerWithEureka: false #是否要注册到eureka
fetchRegistry: false #表示是否从Eureka Server获取注册信息
serviceUrl:
#http://${eureka.instance.hostname}:${server.port}/eureka/ #单机配置
defaultZone: http://eurekaServer-7082:7082/eureka/ #集群配置,此处填对方eureka服务地址
在 defaultZone里配置对方注册中心服务器的地址
然后修改eureka-server-7082的application.yml
server:
port: 7082
eureka:
instance:
hostname: eurekaServer-7082 #localhost #eurekaServer-7081 #集群名字
client:
registerWithEureka: false #是否要注册到eureka
fetchRegistry: false #表示是否从Eureka Server获取注册信息
serviceUrl:
#http://${eureka.instance.hostname}:${server.port}/eureka/ #单机配置
defaultZone: http://eurekaServer-7081:7081/eureka/ #集群配置,此处填对方eureka服务地址
与eurekaServer-7081同理,配置对方注册中心服务器的地址
然后修改product-service-8081的application.yml
server:
port: 8081
spring:
application:
name: PRODUCT-SERVICE-8081 #服务的名字
eureka:
client:
service-url:
#http://localhost:7081/eureka/ #单机配置
defaultZone: http://eurekaServer-7081:7081/eureka/,http://eurekaServer-7082:7082/eureka/ #集群配置
instance:
prefer-ip-address: true #当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1
在defaultZone处填上所有注册中心服务器的地址,地址与地址之间用","逗号隔开
同理配置order-service-9081的application.yml
server:
port: 9081
spring:
application:
name: ORDER-SERVICE-9081 #服务名字
eureka:
client:
service-url:
#http://localhost:7081/eureka/ #单机配置
defaultZone: http://eurekaServer-7081:7081/eureka/,http://eurekaServer-7082:7082/eureka/ #集群配置
instance:
prefer-ip-address: true #当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1
然后启动服务,在浏览器里查看到如下即配置集群完毕
7081中能看到7082,7082中能看到7081。哪怕其中一台服务器挂掉,也不影响Product-Service-8081和Order-Service-9081的使用。
至此,最基本的集群配置完毕。