电商商城定制开发【SpringCloud】学习笔记-p1(服务拆分&远程调用&Eureka注册中心&Ribbon负载均衡)

文章目录

SpringCloud学习笔记-p1

视频指路👉超极推荐!!!

1.初识

1-1 单体架构

单体架构:电商商城定制开发将业务的所有功能集中电商商城定制开发在一个项目中开发,电商商城定制开发打成一个包部署

优点:架构简单,电商商城定制开发部署成本低

缺点:耦合度高(维护困难、升级困难)

1-2 电商商城定制开发电商商城定制开发电商商城定制开发分布式架构

分布式架构:电商商城定制开发根据业务功能对系统做拆分,电商商城定制开发每个业务功能模块作为电商商城定制开发独立项目开发,电商商城定制开发称为一个服务

优点:电商商城定制开发降低服务耦合,电商商城定制开发有利于服务升级和拓展

**缺点:**电商商城定制开发服务调用关系错综复杂

电商商城定制开发分布式架构虽然降低了服务耦合,电商商城定制开发但是服务拆分时也有很电商商城定制开发多问题需要思考:

  • 电商商城定制开发服务拆分的粒度如何界定?
  • 电商商城定制开发服务之间如何调用?
  • 电商商城定制开发服务的调用关系如何管理?

电商商城定制开发因此人们需要制定一套电商商城定制开发行之有效的标准来约束分布式架构,电商商城定制开发由此微服务诞生了

1-3 微服务

微服务的架构特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
  • 自治:团队独立、技术独立、数据独立,独立部署和交付
  • 面向服务:服务提供统一标准的接口,与语言和技术无关
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合

因此,可以认为微服务是一种经过良好架构设计的分布式架构方案

但方案该怎么落地?选用什么样的技术栈?全球的互联网公司都在积极尝试自己的微服务落地方案

其中在Java领域最引人注目的就是SpringCloud提供的方案了

微服务技术对比

1-4 SpringCloud

SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud

SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验

常见的组件包括:

另外,SpringCloud底层是依赖于SpringBoot的,并且有版本的兼容关系,如下:

接下来学习的版本是Hoxton.SR10,因此对应的SpringBoot版本是2.3.x版本

1-5 总结

  • 单体架构:简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统

  • 分布式架构:松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝

  • 微服务:一种良好的分布式架构方案

    ①优点:拆分粒度更小、服务更独立、耦合度更低

    ②缺点:架构非常复杂,运维、监控、部署难度提高

  • SpringCloud是微服务架构的一站式解决方案,集成了各种优秀微服务功能组件

2.服务拆分和远程调用

任何分布式架构都离不开服务的拆分,微服务也是一样

2-1 服务拆分原则

微服务拆分时的几个原则:

  • 单一职责:不同微服务,不要重复开发相同业务
  • 数据独立:不要访问其它微服务的数据库
  • 面向服务:将自己的业务暴露为接口,供其它微服务调用

2-2 服务拆分示例

案例源码:https://git.acwing.com/dashboard/projects

案例结构:

cloud-demo:父工程,管理依赖

  • order-service:订单微服务,负责订单相关业务
  • user-service:用户微服务,负责用户相关业务

要求:

  • 订单微服务和用户微服务都必须有各自的数据库,相互独立
  • 订单服务和用户服务都对外暴露Restful的接口
  • 订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库

准备数据库环境

首先,将准备的cloud-order.sqlcloud-user.sql分别导入到mysql中的cloud_ordercloud_user数据库中

cloud-user表中初始数据如下:

cloud-order表中初始数据如下:

cloud-order表中持有cloud-user表中的id字段

导入demo工程

用IDEA导入提前准备的Demo,项目结构如下:

按快捷键Alt+8显示Services窗口:

注意:如果出现报错,检查JDK版本是否一致,本项目用的 JDK1.8

2-3 实现远程调用案例

访问 http://localhost:8080/order/101

可以看出user为null,因为cloud-order中没有该字段,我们要想获取则需要通过userId调用另一个数据库的cloud-user表,但是要求是我们不能只能调用用户服务的Restful接口不能查询用户数据库

案例需求

修改order-service中的根据id查询订单业务,要求在查询订单的同时,根据订单中包含的userId查询出用户信息,一起返回

因此,我们需要在order-service中 向user-service发起一个http的请求,调用http://localhost:8081/user/{userId}这个接口

步骤:

  • 注册一个RestTemplate的实例到Spring容器
  • 修改order-service服务中的OrderService类中的queryOrderById方法,根据Order对象中的userId查询User
  • 将查询的User填充到Order对象,一起返回

注册

首先,我们在order-service服务中的OrderApplication启动类中,注册RestTemplate实例:

@SpringBootApplicationpublic class OrderApplication {    public static void main(String[] args) {        SpringApplication.run(OrderApplication.class, args);    }    /**    * 创建RestTemplate 并注入Spring容器    * @return    */    @Bean    public RestTemplate restTemplate() {        return new RestTemplate();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

实现远程调用

修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法:

@Servicepublic class OrderService {    @Autowired    private OrderMapper orderMapper;    @Autowired    private RestTemplate restTemplate;    public Order queryOrderById(Long orderId) {        // 1.查询订单        Order order = orderMapper.findById(orderId);        //2.利用RestTemplate发起http请求查询用户        //2.1 url路径        String url = "http://localhost:8081/user/" + order.getUserId();        //2.2 发送http请求,实现远程调用        //将json反序列化为User类型        User user = restTemplate.getForObject(url, User.class);        //3.封装user到Order        order.setUser(user);        // 4.返回        return order;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

在访问 http://localhost:8080/order/101

2-4 提供者与消费者

在服务调用关系中,会有两个不同的角色:

  • 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
  • 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)

但是,服务提供者与服务消费者的角色并不是绝对的,而是相对于业务而言。

如果服务A调用了服务B,而服务B又调用了服务C,服务B的角色是什么?

  • 对于A调用B的业务而言:A是服务消费者,B是服务提供者
  • 对于B调用C的业务而言:B是服务消费者,C是服务提供者

因此,服务B既可以是服务提供者,也可以是服务消费者。由此注册中心产生了

3.注册中心

在学习Eureka注册中心之前,我们先来看几个问题:

假如我们的服务提供者user-service部署了多个实例,如图:

大家思考几个问题:

  • order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?
  • 有多个user-service实例地址,order-service调用时该如何选择?
  • order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?

3-1 Eureka的结构和作用

最广为人知的注册中心就是Eureka,其结构如下:

看了结构图后,我们就可以回答之前几个问题了:

问题1:消费者该如何获取服务提供者具体信息?

  • 服务提供者启动时向eureka注册自己的信息
  • eureka保存这些信息
  • 消费者根据服务名称向eureka拉取提供者信息

问题2:如果有多个服务提供者,消费者该如何选择?

  • 服务消费者利用负载均衡算法,从服务列表中挑选一个

问题3:消费者如何感知服务提供者健康状态?

  • 服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
  • eureka会更新记录服务列表信息,心跳不正常会被剔除
  • 消费者就可以拉取到最新的信息

注意:一个微服务,既可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client

因此,接下来我们动手实践的步骤包括:

3-2 搭建注册中心

首先大家注册中心服务端:eureka-server,这必须是一个独立的微服务

创建eureka-server服务

在cloud-demo父工程下,创建一个maven项目的子模块:

引入eureka依赖

pom文件引入SpringCloud为eureka提供的starter依赖:

<dependencies>    <!--eureka服务器-->    <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>    </dependency></dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

编写启动类

给eureka-server服务编写一个启动类,一定要添加一个@EnableEurekaServer注解,开启eureka的注册中心功能:

@EnableEurekaServer@SpringBootApplicationpublic class EurekaApplication {    public static void main(String[] args) {        SpringApplication.run(EurekaApplication.class,args);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

编写配置文件

编写一个application.yml的配置文件,内容如下:

server:  port: 10086  #服务端口spring:  application:    name: eureka-server #eureka 的服务名称eureka:  client:    service-url:  #eureka 的地址信息      defaultZone: http://127.0.0.1:10086/eureka
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

启动服务

启动微服务,然后在浏览器访问:http://127.0.0.1:10086

看到下面结果应该是成功了:

3-3 服务注册

下面,我们将user-service注册到eureka-server中去

引入依赖

在user-service的pom文件中,引入下面的eureka-client依赖:

<!--eureka客户端依赖--><dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

配置文件

在user-service中,修改application.yml文件,添加服务名称、eureka地址:

spring:  application:    name: userservice #user的服务名称eureka:  client:    service-url: #eureka 的地址信息      defaultZone: http://127.0.0.1:10086/eureka
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

启动多个user-service实例

为了演示一个服务有多个实例的场景,我们添加一个SpringBoot的启动配置,再启动一个user-service

首先,复制原来的user-service启动配置:

然后,在弹出的窗口中,填写信息:

现在,SpringBoot窗口会出现两个user-service启动配置:

启动两个user-service实例,查看eureka-server管理页面:

3-4 服务发现

下面,我们将order-service的逻辑修改:向eureka-server拉取user-service的信息,实现服务发现

引入依赖配置文件这两步与user-service一致,注意修改对应名字即可

服务拉取和负载均衡

最后,我们要去eureka-server中拉取user-service服务的实例列表,并且实现。不过这些动作不用我们去做,只需要添加一些注解即可

在order-service的OrderApplication中,给RestTemplate这个Bean添加一个@LoadBalanced注解:

@SpringBootApplicationpublic class OrderApplication {    public static void main(String[] args) {        SpringApplication.run(OrderApplication.class, args);    }    /**     * 创建RestTemplate 并注入Spring容器     * @return     */    @LoadBalanced //负载均衡    @Bean    public RestTemplate restTemplate(){        return new RestTemplate();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法。修改访问的url路径,用服务名代替ip、端口

spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡

4.复制均衡

上一节中,我们添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢?

4-1 复制均衡原理

SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的

那么我们发出的请求明明是http://userservice/user/1,怎么变成了http://localhost:8081的呢?

4-2 源码跟踪

为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。

显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id

我们进行源码跟踪:

LoadBalancerIntercepor

可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:

  • request.getURI():获取请求uri,本例中就是 http://user-service/user/8
  • originalUri.getHost():获取uri路径的主机名,其实就是服务id,user-service
  • this.loadBalancer.execute():处理服务id 和用户请求

这里的this.loadBalancerLoadBalancerClient类型,我们继续跟入

LoadBalancerClient

继续跟入execute方法:

代码是这样的:

  • getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。
  • getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了8082端口的服务

放行后,再次访问并跟踪,发现获取的是8081:

果然实现了负载均衡

负载均衡策略IRule

在刚才的代码中,可以看到获取服务使通过一个getServer方法来做负载均衡:

继续跟入:

继续跟踪源码 chooseServer() 方法,发现这么一段代码:

我们看看这个 rule 是谁:

这里的 rule 默认值是一个 RoundRobinRule ,看类的介绍:

不就是轮询的意思嘛。到这里,整个负载均衡的流程我们就清楚了

总结

SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改。用一幅图来总结一下:

基本流程如下:

  • 拦截我们的RestTemplate请求http://userservice/user/1
  • RibbonLoadBalancerClient会从请求url中获取服务名称,也就是user-service
  • DynamicServerListLoadBalancer根据user-service到eureka拉取服务列表
  • eureka返回列表,localhost:8081、localhost:8082
  • IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
  • RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求

4-3 负载均衡策略

负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:

不同规则的含义如下:

内置负载均衡规则类规则描述
RoundRobinRule简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。
WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
BestAvailableRule忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule随机选择一个可用的服务器。
RetryRule重试机制的选择逻辑

默认的实现就是ZoneAvoidanceRule,是一种轮询方案

4-4 自定义负责均衡策略

通过定义IRule实现可以修改负载均衡规则,有两种方式:

方法一:代码方式

order-service中的OrderApplication类中,定义一个新的IRule:

@Beanpublic IRule randomRule(){    return new RandomRule();}
  • 1
  • 2
  • 3
  • 4

方法二:配置文件方式

order-serviceapplication.yml文件中,添加新的配置也可以修改规则:

userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务  ribbon:    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 
  • 1
  • 2
  • 3

注意:一般用默认的负载均衡规则,不做修改

4-5 饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载,在order-serviceapplication.yml文件中:

ribbon:  eager-load:    enabled: true #开启饥饿加载    clients: userservice	#指定对userservice 这个服务饥饿加载
  • 1
  • 2
  • 3
  • 4

最后喜欢的小伙伴,记得三联哦!😏🍭😘

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发