分布式方案 - SpringCloud微服务工程思想
就是自己记录记录,也可能不准确。 准备在7天内完成这个板块的学习,根据篇幅可能只拆成3部分 + 1个实战,加油。
前言
带着这些问题来学习:
- 单体系统和分布式系统?
- 为什么要有分布式的工程方案?
- 微服务和分布式有什么关系?
- SprignBoot 和 SpringCloud的关系?
- 该怎么学习微服务?
单体系统的优点与缺点
- 优点:简单,部署方便,一个JAR包搞定。
- 缺点:业务扩展性不好,业务与业务之间一般通过注入的方式,耦合性太强,随着业务的增加维护性差
根本:业务量大的工程,可以使用分布式系统来解决扩展性和维护性的问题。
分布式系统
将整个系统拆分成多个能够独立运行的服务,在物理上是隔离的,服务之间通过网络进行通信。
优点缺点:
- 服务与服务之间是独立的,维护和扩展性好,能实现集群部署。
- 业务拆分,每个业务可以独立的部署,但是怎么拆分是一个问题,拆分的复杂度上升了。
- 工程部署,原本一个服务器部署即可,现在需要多个服务器,手动部署无疑是困难的。
- 负载均衡,多个集群之中如何选择某个服务,需要考虑。
- 网络通信,基于 http 的接口访问,如何保证通信的可靠性。
为了解决扩展性和维护性和一些因为业务大导致的单系统出现的缺点,提出的分布式系统,但是能感受到分布式系统同时也具备着一定的困难,因为有困难所以有框架。
微服务框架
微服务是分布式的一种落地,将工程拆分后形成的一个个独立的服务,我们可以简单理解成微服务。
为了管理这些服务,SringClod无疑是Java技术中首当其冲的框架。
SpringCloud是一个非常大的框架,它基于SpringBoot实现了很多框架的整合,而这里说的很多其他框架就有:
- 网络通信:RestTemplate,Feign
- 服务注册及发现:Nacos、Eureka
- 分布式缓存数据库:Redis、Es...
- 部署及管理:Docker、K8S
- 消息消费:RocketMQ
所以如何去学习微服务,其实应该多做一些项目积累(可能还没不够也没关系),或者多阅读一些文章了解单体工程的缺陷,在大脑中构思出分布式工程的蓝图。根据分布式的特点以及痛点,针对相关的技术点去学习。
上面出现了那些英文字母,其实就是分布式的技术栈,所以 技术栈的学习 + 项目经验 + 原理分析 + 多思考 = 学会了分布式中的微服务。
0. 工程拆分
微服务的工程拆分:
- 每个服务都是独立的,拥有自己的数据库,只负责自己那部分的数据内容。
- 每个服务都能被独立部署,享有一个 ip + 端口,其他服务可以通过 ip + 端口的方式调用该服务的业务。
- 父工程定义版本号以依赖版本管理
- 子工程根据自己的业务导入相应的依赖,无需再指定版本号
1. 服务调用
服务调用:指 “微服务” 向外暴露的接口,其他微服务可以通过http的方式去请求接口,拿到对应服务提供的数据。
前面通过工程拆分,工程之间是独立的了,那么工程之间如何进行相互调用,可以通过以下两种http
的方式:
1.1 RestTemplate
Spring家族中的一个类:org.soringframewoork.web.client.RestTemplate
使用方式:
- 在配置类中注入该类到IOC容器中
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
- 在 service 中需要调用其他微服务接口的地方,加入
restTemplate
,完成调用,方法如下:
restTemplate.getForObject(url, User.class);
url
:微服务暴露的地址User.class
:请求url返回的json反序列成什么类对象
// 1 注入restTemplate
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.查询用户
String url = "http://localhost:8081/user/" + order.getUserId();
// url是服务接口的地址,返回的数据可以反序列为类对象
User user = restTemplate.getForObject(url, User.class);
// 3.封装user信息
order.setUser(user);
// 4.返回
return order;
}
目前缺点:url是以硬编码的方式出现在代码中,后续通过注册中心改进。
1.2 Feign
2. 生产者与消费者
上面的服务之间通过 http 框架产生了调用和被调用关系,通过调用关系可以将服务赋予一个名称:
- 服务提供者:暴露接口给其它微服务调用
- 服务消费者:调用其他微服务提供的接口
- 一个服务可以同时是服务提供者,也可以是服务消费者
我们也知道服务调用时出现了:
- 硬编码,这是服务器消费者如何获取服务提供者的地址信息的问题;
- 分布式是集群的方式,如果A服务有多个,其他服务想调用A服务,如何选择具体是哪个A;
- 如果A服务宕机了,那么又如何保证业务的正常执行。
解决方案呼之欲出:注册中心。
3. 注册中心
以 Eureka 注册中心为例子,它是这样解决上面提出的三个问题的。
- 消费者如何获取服务提供的信息
- 服务提供者向 注册中心 注册自己的信息
- 服务消费者向 注册中心 拉取相应服务的信息
- 如果有多个服务的选择方式
- 使用负载均衡的算法,从服务器列表中挑选一个
- 服务提供者的健壮性保证
- 服务器提供者需要每
30S
向注册中心发送心跳请求 - 注册中心会根据心跳情况更新服务器列表
- 服务器提供者需要每
所以架构中就会出现两个角色:
- 服务端:像Eureka、Nacos的服务器
- 客户端:我们写的模块代码
我们现在来学习一下服务端这个角色:
3.1 Eureka
3.1.1 搭建Eureka
创建一个新的module
,在pom中加入依赖,这个依赖是服务端的意思:
<!--eureka服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
启动类上加上@EnableEurekaServer
注解:
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
application.yml
的配置信息:
server:
port: 10086 #服务端口
spring:
application:
name: eurekaserver #euraka的服务名称,后面其他服务注册也需要带上名字
eureka:
client:
service-url: #euraka的地址信息,如果变成集群就用逗号隔开=
defaultZone: http://127.0.0.1:10086/eureka
3.1.2 将服务注册到Eureka中
同理,其他的服务也需要导入一个依赖,这个依赖是客户端的意思:
<!--erueka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在application.yml
加入配置:
spring:
application:
name: userservice #注册到euraka的服务名称
eureka:
client:
service-url: #euraka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
3.1.3 服务发现及远程调用
服务拉取是基于服务名称获取服务列表,将之前使用 RestTemplate 的服务调用改写一下:
// 2.查询用户
String url = "http://localhost:8081/user/" + order.getUserId();
改成,其实就是将http://localhost:8081
替换成了服务名称:
String url = "http://userservice/user/" + order.getUserId();
如果涉及到多个服务userservice
调用,启动负载均衡即可,只需要在注册RestTemplate上加上@LoadBalanced
注解即可:
@Bean
@LoadBalanced // 负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
(补充)负载均衡 Ribbon
上面的@LoadBalanced
实现了负载均衡,底层是用过 Ribbon 实现的。
首先会被LoadBalancerInterceptor
这个拦截器拦住 客户端发出的http请求。
有两种方式调整负载均衡:
- (全局配置方案)在OrderService服务中,通过 IRule 调整规则,例如调整成随机的负载均衡,之后通过服务调用请求的规则都是下面定义的内容:
@Bean
public IRule randomRule(){
return new RandomRule();
}
- (针对某个服务名称配置负载均衡策略)通过配置文件调整负载均衡,
com.netflix.loadbalancer.RandomRule
和上面的工作是一样的。
请求userservice这个服务时,使用的规则是随机。
userservice: # 根据实际服务修改
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
(补充)饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会长,饥饿加载则会在项目启动时就创建,配置方式如下,在服务消费者的加上如下代码:
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: #这是一个List,所以下面用集合的方式写
- userservice #指定饥饿加载的服务名称
3.2 Nacos
Nacos是阿里巴巴的产品,相比Eureka功能更加丰富,也是国内比较受欢迎的云原生管理平台。地址:home (nacos.io)
入门见这个资料:Nacos 快速开始
整体流程:
- 下载,启动Nacos服务
- 在程序中导包,写相应的配置
3.2.1 下载
Windows & Linux :Releases · alibaba/nacos (github.com)
下载后,解压即可。
3.2.2 配置
在 conf 文件中是配置内容,它是基于SpringBoot工程的,所以配置内容的书写方式和SpringBoot配置文件是一样的,
3.2.3 启动
注:Nacos的运行需要以至少2C4g60g*3的机器配置下运行。
Linux/Unix/Mac
启动命令(standalone代表着单机模式运行,非集群模式):
sh startup.sh -m standalone
如果您使用的是ubuntu系统,或者运行脚本报错提示[[符号找不到,可尝试如下运行:
bash startup.sh -m standalone
Windows
启动命令(standalone代表着单机模式运行,非集群模式):
startup.cmd -m standalone
3.2.4 在代码中配置
- 加入依赖:
<!--nacos的管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
- 注释掉其他注册中心的Client依赖,添加 Nacos 的客户端依赖
<!--<dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
<!--</dependency>-->
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 注释掉其他服务注册的配置,添加 Nacos 的服务注册:
server-addr: localhost:8848
根据实际情况,配置成 Nacos 服务器的地址。
#eureka:
# client:
# service-url: #euraka的地址信息
# defaultZone: http://127.0.0.1:10086/eureka
spring:
application:
name: orderservice #euraka的服务名称
cloud:
nacos:
server-addr: localhost:8848
3.2.5 服务分级存储 - 集群
在 yml 添加
spring.cloud.nacos.discovery.集群名
服务 - 集群 - 实例
Nacos 支持将服务的多个实例,按照某种规则(例如地域)划分成集群,一个集群中有多个实例。
集群出现的意义:服务尽可能选择本地集群的服务,当本地集群不可访问时,再访问其它集群。简单理解就是同个圈子内的服务优先考虑。
配置集群的方式,在 yml 配置文件中添加 discover 内容如下:
spring:
application:
name: orderservice
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 定义一下集群名
(补充)负载均衡 NacosRule
上面配置完实例的集群后,还没有结束,需要通过负载均衡的策略才能去控制同个圈子内的服务优先考虑。
和Eureka配置负载均衡的方式一致,在 yml 中配置,请求userservice
这个服务时,使用的规则是优先同个集群:
userservice: # 根据实际服务修改
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
当同个集群中找不到相应的服务后,才会去考虑其他集群的。
转载自:https://juejin.cn/post/7241439405969981496