微服务系统设计(07)——微服务远程调用设计

摘要

已经引入 Nacos 基础组件,完成了服务注册与发现机制,可以将所有服务统一的管理配置起来,方便服务间调用。本篇将结合需求点,进行服务间调用,完成功能开发。

一、常用的服务调用的方式

服务间调用常见的两种方式:RPC 与 HTTP,RPC 全称 Remote Produce Call 远程过程调用,速度快,效率高,早期的 WebService 接口,现在热门的 Dubbo、gRPC 、Thrift、Motan 等,都是 RPC 的典型代表,有兴趣的小伙伴可以查找相关的资料,深入了解下。

HTTP 协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的 WWW 文件都必须遵守这个标准。对服务的提供者和调用方没有任何语言限定,更符合微服务语言无关的理念。时下热门的 RESTful 形式的开发方式,也是通过 HTTP 协议来实现的。本次案例更多的考虑到简捷性以及 Spring Cloud 的基础特性,决定采用 HTTP 的形式,进行接口交互,完成服务间的调用工作。Spring Cloud 体系下常用的调用方式有:RestTemplate 、 Ribbon 和 Feign 这三种。

  1. RestTemplate,是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 Http 服务的方法,能够大大提高客户端的编写效率。
  2. Ribbon,由 Netflix 出品,更为人熟知的作用是客户端的 Load Balance(负载均衡)。
  3. Feign,同样由 Netflix 出品,是一个更加方便的 HTTP 客户端,用起来就像调用本地方法,完全感觉不到是调用的远程方法。内部也使用了 Ribbon 来做负载均衡功能。

由于 Ribbon 已经融合在 Feign 中,下面就只介绍 RestTemplate 和 Feign 的使用方法。

二、RestTemplate 的应用

功能需求:会员绑定手机号时,同时给其增加相应的积分。会员绑定手机号在会员服务中完成,增加会员积分在积分服务中完成。请求路径是客户端->会员服务->积分服务。

响应客户端请求的方法

 @RequestMapping(value = "/bindMobileUseRestTemplate", method = RequestMethod.POST)
    public CommonResult<Integer> bindMobileUseRestTemplate(String json) throws BusinessException{
        CommonResult<Integer> result = new CommonResult<>();
        log.info("bind mobile param = " + json);
        int rtn = memberService.bindMobileUseRestTemplate(json);
        result.setRespData(rtn);
        return result;
    }

做好 RestTemplate 的配置工作,否则无法正常使用。

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory simpleClientHttpRequestFactory){
        return new RestTemplate(simpleClientHttpRequestFactory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        return factory;
    }
}

在 MemberService 中处理请求,逻辑如下:

@Autowired
RestTemplate restTemplate;

@Override
    public int bindMobileUseRestTemplate(String json) throws BusinessException {
        Member member = JSONObject.parseObject(json, Member.class);
        int rtn = memberMapper.insertSelective(member);
        // invoke another service
        if (rtn > 0) {
            MemberCard card = new MemberCard();
            card.setMemberId(member.getId());
            card.setCurQty("50");

            MultiValueMap<String, String> requestMap = new LinkedMultiValueMap<String, String>();
            requestMap.add("json", JSONObject.toJSONString(card).toString());
            HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(
                    requestMap, null);

            String jsonResult = restTemplate.postForObject("http://localhost:10461/card/addCard", requestEntity,
                    String.class);

            log.info("creata member card suc!" + jsonResult);
        }

        return rtn;
    }

采用 postForObject 形式请求积分服务中的生成积分记录的方法,并传递相应参数。积分子服务中方法比较简单,接受调用请求的方法:

@RequestMapping("card")
@RestController
@Slf4j
public class MemberCardController {

    @Autowired
    MemberCardService cardService;

    @RequestMapping(value = "/addCard", method = RequestMethod.POST)
    public CommonResult<Integer> addCard(String json) throws BusinessException {
        log.info("eclise service example: begin add member card = " + json);
        //log.info("jar service example: begin add member card = " + json);
        CommonResult<Integer> result = new CommonResult<>();
        int rtn = cardService.addMemberCard(json);
        result.setRespData(rtn);
        return result;
    }
}

 实际业务逻辑处理部分由 MemberCardService 接口中完成。

@Service
@Slf4j
public class MemberCardServiceImpl implements MemberCardService {

    @Autowired
    MemberCardMapper cardMapper;

    @Override
    public int addMemberCard(String json) throws BusinessException {
        MemberCard card = JSONObject.parseObject(json,MemberCard.class);
        log.info("add member card " +json);
        return cardMapper.insertSelective(card);
    }
}

分别启动会员服务、积分服务两个项目,通过 Swagger 接口 UI 作一个简单测试。

RestTemplate 默认依赖 JDK 提供 HTTP 连接的能力,针对 HTTP 请求,提供了不同的方法可供使用,相对于原生的 HTTP 请求是一个进步,但经过上面的代码使用,发现还是不够优雅。能不能像调用本地接口一样,调用第三方的服务呢?下面引入 Feign 的应用,绝对让你喜欢上 Feign 的调用方式。

三、Feign 的应用

Fegin 的调用最大的便利之处在于,屏蔽底层的连接逻辑,让你可以像调用本地接口一样调用第三方服务,代码量更少更优雅。当然,必须在服务注册中心的协调下才能正常完成服务调用,而 RestTemplate 并不关心服务注册心是否正常运行。

3.1 引入 Feign

Feign 是由 Netflix 开发出来的另外一种实现负载均衡的开源框架,它封装了 Ribbon 和 RestTemplate,实现了 WebService 的面向接口编程,进一步的减低了项目的耦合度,因为它封装了 Riboon 和 RestTemplate ,所以它具有这两种框架的功能。 在会员模块的 pom.xml 中添加 jar 引用

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

在模块启动类中增加@EnableFeignClients注解,才能正常使用 Feign 相关功能,启动时开启对 @FeignClient 注解的包扫描,并且扫描到相关的接口客户端。

//#client 目录为 feign 接口所在目录
@EnableFeignClients(basePackages = "com.mall.parking.member.client")
前端请求响应方法

    @RequestMapping(value = "/bindMobile", method = RequestMethod.POST)
    public CommonResult<Integer> bindMobile(String json) throws BusinessException{
        CommonResult<Integer> result = new CommonResult<>();
        log.info("bind mobile param = " + json);
        int rtn = memberService.bindMobile(json);
        result.setRespData(rtn);
        return result;
    }

接口编写

编写 MemberCardClient,与积分服务调用时使用,其中的接口与积分服务中的相关方法实行一对一绑定。

@FeignClient(value = "card-service")
public interface MemberCardClient {

    @RequestMapping(value = "/card/addCard", method = RequestMethod.POST)
    public CommonResult<Integer> addCard(@RequestParam(value = "json") String json) throws BusinessException;

    @RequestMapping(value = "/card/updateCard", method = RequestMethod.POST)
    public CommonResult<Integer> updateCard(@RequestParam(value = "json") String json) throws BusinessException;
}

注意是 RequestParam 不是 PathVariablePathVariable 是从路径中取值,RequestParam 是从参数中取值,用法不一。

使用时,直接@Autowried像采用本地接口一样使用即可,至此完成代码的编写,下面再来验证逻辑的准确性。

  1. 保证 nacos-server 启动中
  2. 分别启动 parking-member,parking-card 子服务
  3. 通过 parking-member 的 swagger-ui 界面,调用会员绑定手机号接口(或采用 PostMan 工具)

正常情况下,park-member,park-card 两个库中数据表均有数据生成。

那么,fallback 何时起作用呢?很好验证,当积分服务关闭后,再重新调用积分服务中的生成积分方法,会发现直接调用的是 MemberCardServiceFallback 中的方法,直接响应给调用方,避免了调用超时时,长时间的等待。

3.2 负载均衡

前文已经提到 Feign 中已经默认集成了 Ribbon 功能,所以可以通过 Feign 直接实现负载均衡。启动两个 card-service 实例,打开 Nacos 控制台,发现实例已经注册成功。再通过 swagger-ui 或 PostMan 工具访问多访问几次 bindMobile 方法,通过控制台日志输出,可以发现请求在两个 card-service 实例中轮番执行。

如何改变默认的负载均衡策略呢?先弄清楚 Ribbon 提供了几种负载策略:随机、轮询、重试、响应时间权重和最空闲连接,分别对应如下:

  • com.netflix.loadbalancer.RandomRule
  • com.netflix.loadbalancer.RoundRobinRule
  • com.netflix.loadbalancer.RetryRule
  • com.netflix.loadbalancer.WeightedResponseTimeRule
  • com.netflix.loadbalancer.BestAvailableRule

由于是客户端负载均衡,必须配置在服务调用者项目中增加如下配置来达到调整的目的。

card-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

也可以通过 Java 代码的形式调整,放置在项目启动类的下面

      @Bean
    public IRule ribbonRule() {
        // 负载均衡规则,改为随机
        return new RandomRule();
//        return new BestAvailableRule();
//        return new WeightedResponseTimeRule();
//        return new RoundRobinRule();
//        return new RetryRule();
    }

至此,我们通过一个"会员绑定手机号,并增加会员相应积分"的功能,通过两种方式完成一个正常的服务调用,并测试了客户端负载均衡的配置及使用。

掌握了服务间调用后,在不考虑非业务功能的情况下,基本可以将本案例中大部分业务逻辑代码编写完成,可参照自己拆解的用户故事,或者主要的业务流程图,现在就动手,把代码完善起来吧。服务调用是微服务间频繁使用的功能,选定一个简捷的调用方式尤其重要。照例留下一道思考题吧:本文用到了客户端负载均衡技术,它与我们时常提到的负载均衡技术有什么不同吗?

客户端均衡和服务端均衡的区别?

服务器端负载均衡:例如Nginx,通过Nginx进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。

客户端负载均衡:例如spring cloud中的ribbon,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配。Ribbo是一个基于HTTP和TCP的客户端负载均衡器,当我们将Ribbon和Eureka一起使用时,Ribbon会从Eureka注册中心去获取服务端列表,然后进行轮询访问以到达负载均衡的作用,客户端负载均衡中也需要心跳机制去维护服务端清单的有效性,当然这个过程需要配合服务注册中心一起完成。

博文参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

庄小焱

我将坚持分享更多知识

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值