微服务
单体架构
一个归档包(可以是JAR、WAR、EAR或其它归档格式)包含所有功能的应用程序,通常称为
单体应用。
优点
- 便于共享: 单个归档文件包含所有功能,便于在团队之间以及不同的部署阶段之间共享。
- 易于测试:
单体应用一旦部署,所有的服务或特性就都可以使用了,这简化了测试过程。
因为没有额外的依赖,每项测试都可以在部署完成后立刻开始。- 易于部署: 只需将单个归档文件复制到单个目录下。
缺点
- 复杂性高:
由于是单个归档文件,所以一个文件 等于 整个项目,文件包含的模块非常多,导致模块的边界模糊。
依赖关系不清晰、代码的质量参差不齐,混乱的堆在一起,使得整个项目非常复杂。
以致每次修改代码,都非常小心,可能添加一个简单的功能,或者修改一个Bug都会带来隐藏的缺陷。- 技术债务:
随着时间的推移、需求的变更和技术人员的更替,会逐渐形成应用程序的技术债务,并且越积越多。- 扩展能力受限:
单体应用只能作为一个整体进行扩展,无法根据业务模块的需要进行伸缩。
(为了提高项目性能可以, 将一个项目复制多份部署多台服务器~)- 阻碍技术创新:
对于单体应用来说,技术是在开发之前经过慎重评估后选定的,
每个团队成员都必须使用相同的开发语言、持久化存储及消息系统。
2014 ,martin fowler马丁·福勒
提出的:微服务架构风格
常听的:分布式微服务
优点:
一个应用拆分为一组小型服务
每一个服务: 运行在自己的进程内,也就是可独立部署和升级,拥有自己独立的数据库。通过HTTP的方式进行互通;
• 优点
• 服务围绕业务功能拆分
• 可以由全自动部署机制独立部署
• 微服务能够被小团队单独开发,这个小团队是2到5人的开发人员组成。
• 微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的。
• 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术;只要实现功能即可~
•微服务只是业务逻辑的代码,不会和HTML,CSS 或其他界面组件混合。
•每个微服务都有自己的存储能力,可以有自己的数据库。也可以有统一数据库。
上图:微服务分布式架构,每一个点表示一个功能, 使用时只需要调用需要的功能模块组合即可!
缺点:
- 运维要求高: 更多的服务意味着要投入更多的运维。
- 分布式固有的复杂性:
使用微服务构建的是分布式系统。对于一个分布式系统,系统容错、网络延迟、
分布式事务等都会带来巨大的问题。- 接口调整成本高:
微服务之间通过接口进行通信。
如果修改某一个微服务的API,可能所有用到这个接口的微服务都需要进行调整。
远程调用、服务发现、负载均衡、服务容错、配置管理、服务监控、链路追踪、日志管理、任务调度……
分布式微服务架构中,把一个应用差分为多个 功能模块; 每个功能模块专注做自己的事情…
当需要实现某一功能时候, 只需要对多个需要的 功能模块直接相互调用即可…
那么每个模块即是 Provider也是Consumer
(提供者和调用者)…
什么叫RPC
远程过程调用
即网络通信
什么是REST
架构风格
,指的是一组架构约束条件和原则。一个应用拆分为多个不同的功能模块,做不同的事情
!
一台服务器,承受不住某个工作,为了减轻服务器压力,做集群 负载均价…多个设备做同一见事情!
选型依据
当前各大IT公司用的微服务架构有哪些?
各微服务框架对比
《分布式系统原理与范型》定义:
“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”
分布式系统(distributed system)是建立在网络之上的软件系统。
单一应用架构——垂直应用架构——分布式服务架构——流动计算架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,
以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:
性能扩展比较难 协同开发问题 不利于升级维护...
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用
以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
通过切分业务来实现各个模块独立部署:
降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点: 公用模块无法重复利用,开发性的浪费
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,
作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。
此时,用于提高业务复用及整合的分布式服务框架(RPC)
是关键。
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,
此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。
提高机器利用率的资源调度 和 治理中心(SOA)[ Service Oriented Architecture]
是关键。
为各个微服务之间提供
依赖的关系
分布式服务架构
为了方便开发使用 Maven高级应用...
开发项目:
当然你也可以不用Maven来测试,多起几个项目工程…
删掉主工程的src (反之也用不上
主工程的pom.xml
中加入公共依赖
<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.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>SpringCloudBJartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<modules>
<module>common_apimodule>
<module>common_orderServicemodule>
<module>common_userServicemodule>
modules>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.6.RELEASEversion>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.4version>
<scope>providedscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<repositories>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/milestoneurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
repositories>
project>
SpringCloud版本更新非常快!A B C D E F G H目前最高已经到H了
我凑!!
2021年初
主要存放项目开发所需要的实体类…
这里只有一个实体类…没有什么太复杂的操作
Order.Java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Data
@AllArgsConstructor
@RequiredArgsConstructor
//创建一个实体类: 并根据lombok 及其注解生产对应的 构造函数,toString(); get/set...
public class Order {
private Integer id;
private String title;
private Double price;
private Integer uid;
}
pom.xml
不用任何添加或改动…看一下我其它Maven子工程如果调用;
<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>SpringCloudBJartifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>common_apiartifactId>
project>
<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>SpringCloudBJartifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>common_orderServiceartifactId>
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
<artifactId>common_apiartifactId>
dependency>
dependencies>
project>
OrderService.Java
import com.wsm.entity.Order;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class OrderService {
//模拟假订单集合...
private static List<Order> orders = new ArrayList<>();
//类加载时候默认获取一些数据..模拟假订单..
static {
orders.add(new Order(1, "订单11", 10.0, 1));
orders.add(new Order(2, "订单21", 10.0, 2));
orders.add(new Order(3, "订单31", 10.0, 1));
orders.add(new Order(4, "订单41", 10.0, 2));
}
//根据输入用户id获取当然用订单集合..
public List<Order> findOrderByUser(Integer uid) {
List<Order> myorders = new ArrayList<>();
//遍历集合从中获取 uid一致的数据存放在集合中返回...
for (Order order : orders) {
if (order.getUid() == uid) {
myorders.add(order);
}
}
return myorders;
}
}
OrderController.Java
import com.wsm.entity.Order;
import com.wsm.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class OrderController {
@Autowired
private OrderService os;
//REST风格进行请求..
@GetMapping("/findOrderByUser/{uid}") //参数是以 /请求名/{参数1}/{参数2} 进行访问的...
public List<Order> findOrderByUser(@PathVariable Integer uid){
//REST风格参数需要使用 @PathVariable获取!!
return os.findOrderByUser(uid);
}
}
SpringBoot的主工程类:MyOrderServer.Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyOrderServer {
public static void main(String[] args) {
SpringApplication.run(MyOrderServer.class, args);
}
}
SpringBoot application.yml
配置文件
server:
port: 6001 #设置端口6001 当有多个微服务时候注意端口号可别冲突了..
spring:
application:
name: order-server #设置当然微服务名,后面的 注册/调用服务,都是根据这个来的;
<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>SpringCloudBJartifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>common_userServiceartifactId>
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
<artifactId>common_apiartifactId>
dependency>
dependencies>
project>
UserService.Java
@Service
@Slf4j //加载lo4g使用...
public class UserService {
@Autowired
private RestTemplate restTemplate;
//通过restTemplate进行网络通信,返回..其它远程模块的数据(虽然现在都是本地的不过就是模拟了..)
public List<Order> currentUserOrder(Integer uid) {
log.info("用户服务调用订单服务");
//硬编码的调用!! 这里的请求都写死的...这好吗?这不好...后面修改;
String myurl = "http://localhost:6001/findOrderByUser/" + uid;
log.info(myurl
);
List<Order> list = restTemplate.getForObject(myurl, List.class);
return list;
}
}
UserController.Java
//编程控制层,接受请求响应结果
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/showUserOrder")
public List<Order> showUserOrder(Integer uid){
return userService.currentUserOrder(uid); //调用Service层
//Service又是通过网络调用 订单模块来响应的结果,因此在此基础上要缺点订单模块是运行中的...
}
}
SpringBoot的主工程类:MyUserServer.java
@SpringBootApplication
public class MyUserServer {
public static void main(String[] args) {
SpringApplication.run(MyUserServer.class, args);
}
@Bean
//实例化 RestTemplate 方便Service
//可别忘了@SpringBootApplication复合注解底层可以有@SpringBootConfiguration 它也是一个Spring的配置类!
public RestTemplate createRestTemplate() {
return new RestTemplate();
}
}
SpringBoot application.yml
配置文件
server:
port: 6002 #设置端口6002 当有多个微服务时候注意端口号可别冲突了..
spring:
application:
name: user-server #设置当然微服务名,后面的 注册/调用服务,都是根据这个来的;
前文已经编写了三个基础的微服务,在用户下单时需要调用商品微服务获取商品数据。
商品微服务提供了供人调用的HTTP接口
Spring框架提供的RestTemplate类可用于在应用中调用rest服务
它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接
至此已经可以通过RestTemplate调用商品微服务的RESTFul API接口。
但是我们把提供者的网络地址(ip,端口)等硬编码到了代码中,这种做法存在许多问题:
注册中心
动态的对服务注册和服务发现以上就是一个典型的分布式模块架构
多模块业务之间的相互调用 但↓↓↓↓
因为是用户模块—引用—订单模块
订单是提供者provide
用户是调用者Consumer
注意!
为了解决这种问题, 注册中心
就出现了
注册中心可以说是微服务架构中的 ”通讯录“,它记录了服务和服务地址 映射关系在分布式架构中
服务会注册到这里,当服务需要调用其它服务时,就这里找到服务的地址,进行调用。
服务注册中心是微服务架构非常重要的一个组件,在微服务架构里主要起到了协调者的一个作用。
注册中心一般包含如下几个功能:
注册中心
心跳♥
告诉注册中心我还在能干活Spring Cloud 封装了 Netflix 公司开发的 Eureka
Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,它是服务注册中心。
Eureka Server
和Eureka Client
服务
EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
使用时先启动注册中心——Provider提供者——Consumer调用者
流动计算架构
基于上面的SpringCloud的分布式服务架构
开发
注册中心
pom.xml
<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>SpringCloudBJartifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>eureka-serverartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
project>
application.yml
server:
port: 7001 #设置注册中心的 端口;
spring:
application:
name: eureka-server #注册中心应用名称;
#配置注册中心....
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ #注册中心对外暴漏的注册地址...
#要不要去注册中心获取其他服务的地址,默认为true (目前只有一个注册中心而且,当前就是注册中心..)
fetch-registry: false
#是否将允许自己注入注册中心,默认为true (目前只有一个注册中心而且,当前就是注册中心..自己注册自己?)
register-with-eureka: false #如果一个应用中有两个注册中心可以开启,达到集群注册中心的目的...
主程序类MyEurekaServer.Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //注解主程序开启 Eureka服务
public class MyEurekaServer {
public static void main(String[] args) {
SpringApplication.run(MyEurekaServer.class, args);
}
}
启动注册中心的服务, 表示当前项目作为SpringCloud中的注册中心
pom.xml
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
以订单的主程序为例子, 其它也都这么做
MyOrderServer.Java
@SpringBootApplication
@EnableEurekaClient
public class MyOrderServer {
public static void main(String[] args) {
SpringApplication.run(MyOrderServer.class, args);
}
}
@EnableEurekaClient 启动注册中心客户端
表示当前主程序注册到注册中心去…
@EnableDiscoveryClient,一种为@EnableEurekaClient,用法上基本一致。
注解
配置文件配置注册中心 Eureka
(以业务order的为例子)
application.yml
server:
port: 6001 #设置端口6001 当有多个微服务时候注意端口号可别冲突了..
spring:
application:
name: order-server #设置当然微服务名,后面的 注册/调用服务,都是根据这个来的;
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ #指定服务注册的注册中心;
instance:
prefer-ip-address: true #在Eureka上使用ip号..
instance-id: ${
spring.cloud.client.ip-address}:${
server.port} #在Eureka上显示ip号..
硬编码解决了
UserService.Java
import com.wsm.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient; //这个DiscoveryClient别搞错
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Service
@Slf4j //加载lo4g使用...
public class UserService {
@Autowired
private RestTemplate restTemplate;
//通过restTemplate进行网络通信,返回..其它远程模块的数据(虽然现在都是本地的不过就是模拟了..)
@Autowired //实现动态调用...
private DiscoveryClient discoveryClient;
public List<Order> currentUserOrder(Integer uid) {
//获取注册中心上的微服模块实例 根据服务名;
//返回一个集合: 有可能这个服务在多个注册中心上存在,`负载均衡~` 所以是一个集合;
List<ServiceInstance> instances = discoveryClient.getInstances("order-server");
ServiceInstance serviceInstance = instances.get(0); //而本次只有一个...
log.info("用户服务调用订单服务");
//动态调用服务 服务的host 服务端口号 这个就是服务controller请求 给其参数uid
String myurl = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/findOrderByUser/" + uid;
log.info(myurl);
List<Order> list = restTemplate.getForObject(myurl, List.class);
return list;
}
}
动态的从注册中心Eureka上获取ip
查看这里的 ip 192.168.1.1
在服务客户端Client 配置的~
默认 30秒心跳 90秒续约时间…
.yml
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ #指定服务注册的注册中心;
instance:
prefer-ip-address: true #在Eureka上显示ip号..
instance-id: ${
spring.cloud.client.ip-address}:${
server.port} #在Eureka上显示ip号..
lease-renewal-interval-in-seconds: 5 #客户端向注册中心发送心跳时间
lease-expiration-duration-in-seconds: 10 #如果没有发送心跳的延迟续约时间...
如果客户端的服务因为某些原因关闭了。Eureka会根据心跳检测到你没了而移除你的服务…
注册中心
发送心跳如果超过时间没有发送 默认30秒两分钟~
如果还没有启动Eureka会把服务剔除…Eureka自我保护机制
服务并不会真的 移除…而是会给你一个时间如果还没有回来~则报错!一般不会关闭自我保护机制,因为如果服务又好了又可以立刻注册会来使用...
在服务端即 注册中心
关闭保护机制。。。
注册中心的.yml
#配置注册中心....
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ #注册中心对外暴漏的注册地址...
#要不要去注册中心获取其他服务的地址,默认为true (目前只有一个注册中心而且,当前就是注册中心..)
fetch-registry: false
#是否将允许自己注入注册中心,默认为true (目前只有一个注册中心而且,当前就是注册中心..自己注册自己?)
register-with-eureka: false #如果一个应用中有两个注册中心可以开启,达到集群注册中心的目的...
server:
enable-self-preservation: false #默认是ture 开启的
eviction-interval-timer-in-ms: 4000 #默认是0 从不剔除!
nice 终于写完了,建议三联!!