以单体架构方式打开微服务架构的远程调用

628 阅读7分钟

前言

文章将从单体架构改造成微服务架构思路下手,避免一入代码深似海,忽略了微服务的思想。

我这个"半桶水"就是边学习思想,边研究代码实现,结果感觉"复制粘贴"的时间比我产生智慧的火花还要多。当然,开始学新东西的确不能站在一定高度去把控,所以这次是对过去学习的总结。如果有大佬路过,看见文章有不足之处,请大佬们踊跃发言😁!

说明

文章引入多位大神的文章,来具体展开关键点

单体架构的部署

单体架构(monolithic structure):顾名思义,整个项目中所有功能模块都在一个工程中开发;项目部署时需要对所有模块一起编译、打包,之后扔到服务器上;项目的架构设计、开发模式都非常简单。

当项目规模较小时,这种模式上手快,部署、运维也都很方便,因此早期很多小型项目都采用这种模式。

我们可以发现这样一个特点:商品管理模块,用户管理模块,交易管理模块,用户管理模块购物车模块都部署到一台服务器上,如果模块之间需要相互调用,比如有一个需求

  • 在查询购物车的时候,需要查询商品最新的价格(商品有可能降价了,要查出来,告诉用户快来买🤡)

在这个需求中,就需要在购物车模块调用商品模块,在单体架构中要想调用,很简单,有以下几种方法实现:

  • 通过注解注入商品的 service,如:@Autowire,@Resouce(idea 不建议使用@Autowire)
  • 通过构造函数注入商品的 service
  • 通过 lombok 注解自动生成构造函数(推荐)
  • ......

通过这些方法在购物车模块调用商品模块 service 层的方法 queryItemByIds,就可以查询到商品当前最新价格,之后将新价格封装到 vo 对象,返回给前端。

微服务架构部署

微服务架构, 首先是服务化,就是将单体架构中的功能模块从单体应用中拆分出来,独立部署为多个服务。同时要满足下面的一些特点:

  • 单一职责:一个微服务负责一部分业务功能,并且其核心数据不依赖于其它模块。
  • 团队自治:每个微服务都有自己独立的开发、测试、发布、运维人员,团队人员规模不超过10人
  • 服务自治:每个微服务都独立打包部署,访问自己独立的数据库。并且要做好服务隔离,避免对其它服务产生影响。

也就是说: 用户管理模块,交易管理模块,用户管理模块和购物车模块这四个模块即四个微服务,每个微服务负责一部分业务功能,并且其核心数据不依赖于其它模块,那么问题来了,如何解决刚刚单体项目的需求:

  • 在查询购物车的时候,需要查询商品最新的价格

由于购物车模块,商品模块在不同的服务器上,肯定不可能通过注入的方式吧🤡,这两个功能物理上都不在一块,逻辑上还需尽量保持单一原则。

我们这样思考,现在不是前后端分离吗? (虽然说前后端分离,人不分离🤦‍♂️),前后端也是部署到两个服务器上,虽然分离,但可以通过网络请求来进行交互,同理,微服务之间交互也是可以通过网络请求!(前后端请求可能还会由于浏览器的同源问题而产生跨域带来的问题)

网络请求一般包括

  • 请求方式
  • 请求路径
  • 请求参数
  • 返回值类型

我们可以使用一下几种方式:

  • 使用 spring 提供的 RestTemplate 的 Api,实现Http请求的发送。
    • 其实这个 api 和前端 ajax 比较像,需要包含请求方式 ,请求路径 ,请求参数 ,返回值类型
    • 不常用,但可以用来理解远程调用的实现流程
//在此之前要定义一个配置类,将RestTemplate注册为一个Bean

private void handleCartItems(List<CartVO> vos) {
    // 1.1.利用RestTemplate发起http请求,得到http的响应
    ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
            "http://localhost:8081/items?ids={ids}",//请求路径
            HttpMethod.GET,//请求方式
            null,
            new ParameterizedTypeReference<List<ItemDTO>>() {
            },//返回值类型
            Map.of("ids", CollUtil.join(itemIds, ","))//请求参数
    );
    // 1.2.解析响应
    if(!response.getStatusCode().is2xxSuccessful()){
        // 查询失败,直接结束
        return;
    }
    //其他业务逻辑
  
}
    • 同样需要发送请求方式 ,请求路径 ,请求参数 ,返回值类型
    • 比较常用,可参考大神之作: OpenFeign

我们看请求路径:http://localhost:8081/items?ids={ids},ip 地址和端口号在这里写死了,将来微服务越来越多,每个微服务的 ip 不一样,同样的 ip 又有多个端口号,我咋知道 ip 和端口号哪个是哪个,所以我们需要有一个中心,专门负责管理微服务的 ip 和端口号。

由此引出注册中心的概念:

在微服务远程调用的过程中,包括两个角色:

  • 服务提供者:提供接口供其它微服务访问,比如item-service(商品服务)
  • 服务消费者:调用其它微服务提供的接口,比如cart-service(购物车服务)

关系如下:

流程如下:

  • 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
  • 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
  • 调用者自己对实例列表负载均衡,挑选一个实例
  • 调用者向该实例发起远程调用

当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?

  • 服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
  • 当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
  • 当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
  • 当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表

关于注册中心, java可以使用 Nocos,可以看看大神的链接。

如此, 有了注册中心,我们可以进行服务注册,集中管理 ip 和端口;可以进行服务发现,从注册中心获取对应的 ip 和端口,然后再通过远程调用微服务来完成需求。

又有个问题出现了,之前说,一个微服务有多个端口,那么从注册中心获取哪个呢?

这里其实就是负载均衡,服务调用者必须利用负载均衡的算法,从多个实例中挑选一个去访问。常见的负载均衡算法有:

  • 随机
  • 轮询
  • IP的hash
  • 最近最少访问
  • ...

随着一个一个问题抛出,一个一个问题解决,我们知道微服务之间的调用可以通过远程调用来实现和单体架构同样的效果。

我们可以和单体架构做个对比:

就像单体架构模块与模块之间的调用是通过 注解注入等方式,使得各个模块的功能可以互通,相比这一点,微服务其实就是利用注册中心,通过服务注册和服务发现来解决,和 springboot 把 Bean 对象放到 IOC 容器里,用的时候去容器里面找,好像有些相通之处.