月夜星空-liulq

--愿美好接踵而至

前言

    说起微服务框架相信大家都不陌生了,概念也就不赘述了,简单的说,就是把以前的单体服务拆分成多个服务,减轻每个服务的压力,增加服务的灵活性。今天开始,我们一起来进行微服务系列的学习,本系列不会具体的一步一步的操作演示,而是会更多的把经历放在各个组件的原理层面来学习。

    本系列会已网上最常见的一个例子为切入点,就是订单-仓储-支付-积分等几个服务之间的关系展开微服务的学习。

    本文是该系列的第一篇文章,主要会概述一些我服务相关的概念及组件,不会讲解底层原理及源码,原理及源码会在后续的章节中详细讲解说明。

注册中心

    首先试想这样一个情况,我们有一个电商平台,用户下订单,我们的后台服务需要做哪些操作:

  1. 调用订单服务下订单;
  2. 订单服务调用支付服务形成支付订单;
  3. 订单服务调用库存服务,库存减掉相应的数量(更实际的应用可能是库存锁定一定数量,待支付成功后或者形成物流后再真正的减掉库存,这里不对这样的业务做详细的讨论);
  4. 订单服务调用积分服务,为用户增加积分;

    这里面订单服务形成了订单,并且需要调用其他的多个服务,那么订单服务怎么知道要调用的服务的地址是什么呢?如果没做过微服务的同学第一个反应可能就是把其他服务的地址写到订单服务的配置文件中,使用时直接从配置文件中读取。从程序实现的角度来说,这个思路没问题,完全可以实现,但是试想一下,这里只提到了三个其他的服务,以后多了的情况呢,10个呢?30个呢... ... 以后某一个服务更换服务器了呢,是不是所有调用他的服务都需要修改配置文件?

    为了解决这个问题,注册中心就出现了,当我们启动服务时,会把服务的IP端口(不仅仅这两个)写入注册表,这样以后需要调用其他服务时,直接到注册表中查找就可以了,大致的流程如下图所示:

 

     注册中心的常用组件有哪些?

  1.  Zookeeper;
  2. Eureka;
  3. Consul;
  4. Nacos;

 服务间调用

    现在订单服务已经知道怎么找到支付服务、仓储服务、积分服务了,那么订单服务应该如何调用这几个服务呢,在远古时期,我们调用服务可能会在底层写很多的代码,到了Spring流行起来之后,框架为我们提供了一些组件来帮助我们完成底层的代码的编写工作,让我们可以把注意力从底层编码上转移出来,常见的服务间调用的方式有RestTemplate、Fegin、openFegin。先来看看RestTemplate:

@RestController
@RequestMapping("/orderInfo")
public class OrderInfoController {
    @GetMapping(value = "/addOrder")
    public void addOrder() {
        System.out.println("创建了订单");
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getForObject("http://localhost:8003/dmscloud.stock/basedata/partInfos/updateStorage",String.class);
    }
}

    我们可以通过RestTemplate这个类完成服务间的调用,但是试想这么一种场景,就是我们的仓储服务调用的比较多,所以我们需要搭建多台的仓储服务来减轻一台的压力,那么单纯的上面的这种写法可能就无法满足需求了,毕竟我们的地址写入了程序里面,那么有没有一种方式可以解决这个问题呢,答案是肯定的。

    SpringCloud为我们提供了一个组件--ribbon,ribbon是用来专门处理负载均衡问题的组件,它会从注册中心找到相同名称的服务,按照一定的规则(默认轮询)来调用相应的服务,写法上也比较简单,代码如下:

  @Autowired
    @LoadBalanced
    RestTemplate restTemplate;
    @GetMapping(value = "/addOrder")
    public void addOrder() {
        System.out.println("创建了订单");
restTemplate.getForObject("http://dmscloud.part/basedata/partInfos/updateStorage",String.class);
    }

    我们去掉了访问的地址,直接写入了服务的名称即可完成服务的调用,并且还实现了负载均衡的功能,确保了单一服务节点瘫痪时的服务的可用性。

    我们再来看看fegin,fegin本身集成了ribbon,自然实现了负载均衡的功能,只需要在配置文件中做些配置即可,先看看fegin的写法:

@FeignClient(name = "dmscloud.stock", configuration = FeignConfig.class)
public interface FeignRepairServer {
    @GetMapping(value = "/dmscloud.stock/basedata/partInfos/updateStorage")
    Map updateStorage(@RequestParam Map<String, Object> params);
}

    只需要加上FeginClient注解,指定了服务名称,然后下面的按照接口的方式、同时增加了GetMapping等注解即可。组件会从注册中心找到相应的服务,完成调用工作。

服务的雪崩、熔断、降级

    上面我们已经知道了订单服务是如何找到其他的服务,并如何调用到其他的服务了,接下来就该讨论服务的可用性了。有一天服务的访问量突然剧增(比如说双十一时),我们的订单服务的压力瞬间增加,我们的订单服务可能就会崩溃,即使订单服务没有崩溃,那么我们又怎么保证支付服务、仓储服务、积分服务不会崩溃呢,假如积分服务崩溃了,订单服务每次调用积分服务时都要等待它的响应,然后超时,长此以往,我们的订单服务也就崩溃了... ...

    一个服务的崩溃,导致整个系统的崩溃,这就是雪崩。其实从业务的角度来说,积分服务本身没有那么重要,即使积分服务崩溃了,也不应该导致整个系统的崩溃,那么怎么解决这个问题呢,这就得说说熔断降级了,如果积分服务崩溃了,我们的订单服务可以不去访问这个服务,只做一个记录,待积分服务恢复了再解决数据的一致性的问题即可。我们可以设定一个阈值,比如说访问失败率达到50%时,就不再访问积分服务,而是直接返回失败,然后隔5秒之后再次尝试访问积分服务,那么就可以防止订单服务为了等待积分服务而崩溃的场景了,这就是服务的熔断。

    说到这细心的朋友可能会说,直接把错误抛给前台是不是太不友好了,你说的没错,这就要引出降级了,简单的来说,就是在调用失败后执行FallBack方法,我们可以在这里记录失败的数据,同时做出友好的响应。

    常见的解决此问题的组件有哪些?

  1. Hystrix;
  2. Sentinel;  

网关

    现在服务之间的事儿咱聊的差不多了,该聊聊外部调用咱们的这些服务的问题了。每一个微服务对外都提供了一个地址,前端调用服务时需要根据地址来访问,那就是说,前端需要知道每一个服务的地址,我们这里的例子还好,只有订单服务、支付服务、仓储服务、积分服务,加入我们的业务十分服务,有几十个上百个服务呢,前端蒙圈了... ...

    网关就是用来做这个事情的,它对外提供统一的一个地址,然后根据前端发来的请求做出路由解析(一般来说,IP+端口号后面跟的就是服务名称,根据服务名称从注册中心找到相应的服务,然后调用其服务接口)。 

    当然,网关的作用绝不仅限于此,还有很多的事情需要它来做,比如说鉴权问题,什么样的访问允许继续向下调用业务相关的服务?什么样的访问需要拦截,这个都是网关需要做的。

    常见的网关组件有哪些?

  1.  zuul;
  2. gateway;

    到此为止,SpringCloud微服务的必要功能及组件就介绍完了,接下来再说一些其他的非必要的组件吧。

缓存

    缓存在我们的开发中非常常见,相信大家也都有所了解,一些更新不频繁,但是查询比较频繁的数据,我们都可以存储到缓存中,比如登录信息、字典项等等。

    常见的缓存组件?

  1. redis;

消息中间件

    消息中间件主要用来异步更新数据使用,举个例子,上面我们说到了积分服务出现了异常,导致订单服务无法调用的情况,这种情况怎么处理呢?上面并没有给出确切的方案,其实消息中间件就是一个解决思路,当调用失败或出现熔断时,我们可以把消息写入消息中间件,积分服务订阅了该消息,待积分服务恢复以后,自动订阅到消息,完成数据的更新。当然,消息中间件的功能也不仅限于此,还可以做一些其他异步更新数据的操作,从而提高效率。

总结

    微服务相关的概述也就讲完了,本文并没有详细的讲解各个组件的原理、源码及使用,这些都会在这个系列的后续文章中讲解。原本想再画一张图,但是懒惰症犯了,所以从网上偷了一张图,人家画的比我画的好,所以直接偷来用用了:

 

posted on 2022-02-09 12:26  月夜星空-liulq  阅读(219)  评论(0)    收藏  举报