10

MVC项目如何本地联调其他测试环境

 2 years ago
source link: https://segmentfault.com/a/1190000041266750
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

MVC项目如何本地联调其他测试环境

目前公司还存在诸多前后端未分离项目(后面简称FM项目),开发同学在本地只能联调alpha环境,但是在项目转测转演之后,测试同学提的jira单都是hwbetagamma环境的,因此开发同学为了复现问题场景需要在alpha环境造单,或者麻烦测试同学造单,此过程非常低效。

我们以本地启动webagent网关服务联调本地agentBuy-service服务(Spring MVC服务)为例分析流程。

本地联调不同测试环境 (1).png

  • agentBuy-service服务是一个Spring MVC服务,调用其他远程服务获取数据并注入到页面文件(ftl)中,然后使用freemarker引擎将ftl编译成html并返回给浏览器;
  • 浏览器加载html过程中会向static静态资源服务请求静态资源;
  • 另外,页面还会向其他web服务发送请求,获取业务数据;

以上默认走的alpha环境服务,如果想本地联调hwbetagamma环境的服务,就需要搞清楚其中webagent是如何做路由转发的,远程服务是如何调用的

webagent

webagent是采用SpringCloud Gateway网关组件搭建的,如果想要路由转发到本地服务,需要更改config/routes.yml路由配置文件,例如:

spring:
  cloud:
    gateway:
      httpclient:
        connectTimeout: 1000 #ms
        responseTimeout: 35s #Max Readtimeout
        pool:
          type: FIXED
          maxConnections: ${icec.http.pool.max:600}
          acquireTimeout: 10000
      discovery.locator:
        enabled: false
      #default-filters:
      #- PreserveHostHeader
      #- AddCommonHtml
      forwarded.enabled: false
      routes:
        #- 开发环境需要下面这个路由
        - id: dev-to-test
          predicates:
          - Path=/static/**, /passport/**
          uri: 'https://ec-alpha.casstime.com'
        - id: sellercenter-portal
          predicates:
          - Path=/agentBuy/seller/**
          # uri: 'lb://sellercenter-portal' # 默认走alpha环境
          uri: 'http://localhost:10102' # 路由转发到本地sellercenter服务
        - id: agentbuy-service
          predicates:
          - Path=/agentBuy/**
          # uri: 'lb://agentbuy-service' # 默认走alpha环境
          uri: 'http://localhost:8001' # 路由转发到本地agentBuy-service服务

远程服务调用

Spring Cloud核心组件:Eureka

先来给大家说一个业务场景,假设咱们现在开发一个电商网站,agentBuy-service需要向inquiry-service获取用户地址,但是agentBuy-service压根就不知道所依赖的服务在那台机器上,他就算想要发起一个请求,都不知道发送给谁,有心无力!

这时候,就轮到Spring Cloud Eureka出场了。Eureka是微服务架构中的注册中心,专门负责服务的注册与发现,其官方结构图如下:

eureka.png

Spring-Cloud EurakaSpring Cloud集合中一个组件,它是对Euraka的集成,用于服务注册和发现。EurekaNetflix中的一个开源框架。它和 zookeeperConsul一样,都是用于服务注册管理的,同样,Spring-Cloud 还集成了ZookeeperConsul

Eureka由多个instance(服务实例)组成,这些服务实例可以分为两种:Eureka ServerEureka Client。为了便于理解,我们将Eureka client再分为Service ProviderService Consumer

  • Eureka Server 为服务注册中心,向外暴露自己的地址,负责管理、记录服务提供者的信息,同时将符合要求的服务提供者地址列表返回服务消费者
  • Service Provider 为服务提供者,在服务启动后,服务提供者向Eureka注册自己的IP、端口、提供服务等信息,并定时续约更新自己的状态等
  • Service Consumer为服务消费者,通过Eureka Server发现得到所需服务的提供者地址信息,然后向服务提供者发起远程调用

Eureka (1).png

agentBuy-service是一个Spring MVC服务,不需要向其他服务提供服务,也就不需要将自己注册到Eureka Server,只需要作为Service Consumer调用其他远程服务。agentBuy-service配置成消费者服务如下:

(1)在pom文件中添加Eureka-client依赖

<dependency>
  <groupId>com.netflix.eureka</groupId>
  <artifactId>eureka-client</artifactId>
  <exclusions>
    <exclusion>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
    </exclusion>
  </exclusions>
</dependency>

(2)在eureka-client.properties配置文件中增加相关配置信息,设置不需要注册到Eureka Server,添加eureka.registration.enabled=falseeureka.registerWithEureka=false,详细如下:

// eureka-client.properties
###Eureka Client configuration for Sample Eureka Client

# see the README in eureka-examples to see an overview of the example set up

# note that for a purely client usage (e.g. only used to get information about other services,
# there is no need for registration. This property applies to the singleton DiscoveryClient so
# if you run a server that is both a service provider and also a service consumer,
# then don't set this property to false.
eureka.registration.enabled=false # 表示是否注册到Eureka Server,默认为true

## configuration related to reaching the eureka servers
eureka.preferSameZone=true
eureka.shouldUseDns=false
eureka.registerWithEureka=false # 表示是否将自己注册到Eureka Server,默认为true

eureka.serviceUrl.default=http://10.118.71.204:8761/eureka/ # 注册中心地址

eureka.appinfo.replicate.interval=10

eureka.vipAddress=agentbuy-service # 定义服务名变量
eureka.name=${eureka.vipAddress} # 服务名,SpringCloud中服务调用的依据
eureka.port=8001 # 服务端口
#eureka.ipAddr=192.168.29.170
#eureka.instanceId=${eureka.vipAddress}:${eureka.port}
#eureka.hostname=${eureka.ipAddr}
eureka.homePageUrlPath=/${eureka.vipAddress}
#eureka.statusPageUrlPath=/${eureka.vipAddress}/status
#eureka.healthCheckUrlPath=/${eureka.vipAddress}/health
eureka.lease.renewalInterval=5
eureka.lease.duration=15
eureka.decoderName=JacksonJson

Spring Cloud核心组件:Feign

agentBuy-service还集成Feign实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。不需要自己写一大堆代码,跟其他服务建立网络连接,然后构造一个复杂的请求,接着发送请求过去,最后对返回的响应结果再写一大堆代码来处理。

这是上述流程翻译的代码片段,咱们一起来看看,体会一下这种绝望而无助的感受!!!

img

看完上面那一大段代码,有没有感到后背发凉、一身冷汗?实际上你进行服务间调用时,如果每次都手写代码,代码量比上面那段要多至少几倍,所以这个事儿压根儿就不是地球人能干的。

Feign早已为我们提供好了优雅的解决方案。来看看如果用Feign的话,你的订单服务调用库存服务的代码会变成啥样?

img

看完上面的代码什么感觉?是不是感觉整个世界都干净了,又找到了活下去的勇气!没有底层的建立连接、构造请求、解析响应的代码,直接就是用注解定义一个 FeignClient接口,然后调用那个接口就可以了。人家Feign Client会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应,等等。这一系列脏活累活,人家Feign全给你干了。

那么问题来了,Feign是如何做到这么神奇的呢?很简单,Feign的一个关键机制就是使用了动态代理。咱们一起来看看下面的图,结合图来分析:

  • 首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理
  • 接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心
  • Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址
  • 最后针对这个地址,发起请求、解析响应

Feign.png

agentBuy-service是一个集成Spring MVC搭建的web项目,只要生成了bean就可以使用@Autowired注入实例,但agentBuy-service要生成inquiry-serviceFeign接口实例发起远程调用,不能使用@Autowired注入实例,因为会报错找不到bean “Could not autowire. No beans of 'InquiryClient' type found”,所以项目中使用FeignClientBuilder动态创建FeignClient实例发起远程服务请求,缺陷是每次都需要手动创建,然后在作用域生命周期结束后销毁。

Spring Boot在启动类中使用了@SpringBootApplication,其内部封装了@ComponentScan,@EnableAutoConfiguration,@SpringBootConfiguration

  • @ComponentScan注解就是用来自动扫描被这些注解(@Service,@Repository,@Component,@Controller)标识的类,最终生成IOC容器里的bean
  • @SpringBootConfiguration用来声明当前类是一个配置类,可以通过@Bean注解生成IOC容器管理的bean
  • @EnableAutoConfigurationSpring Boot实现自动化配置的核心注解,通过这个注解把spring应用所需的bean注入容器中。@EnableAutoConfiguration源码通过@Import注入了一个ImportSelector的实现类
    AutoConfigurationImportSelector,这个ImportSelector最终实现根据我们的配置,动态加载所需的bean

因此,使用@Autowired注入实例

参考:简单讲讲@SpringBootApplication

另外,如果生成bean时指定FeignClientBuilder类中configAgentUrl属性(远程服务地址),那么可以跨Eureka定位服务(不需要通过Eureka定位)。Spring Boot服务Feign接口配置中没有指定服务调用的url地址,而采用指定服务名的方式,导致依赖方服务如果想调用Feign接口,必须要和被依赖方服务注册在同一个注册中心上

动态FeignClientBuilder.png

feign接口.png

bean.png

carbon (3).png

carbon (1).png

Spring Cloud核心组件:Ribbon

说完了Feign,还没完。现在新的问题又来了,如果人家库存服务部署在了5台机器上,如下所示:

  • 192.168.169:9000
  • 192.168.170:9000
  • 192.168.171:9000
  • 192.168.172:9000
  • 192.168.173:9000

这下麻烦了!人家Feign怎么知道该请求哪台机器呢?

这时Spring Cloud Ribbon就派上用场了。Ribbon就是专门解决这个问题的。它的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀的把请求分发到各个机器上

Ribbon的负载均衡默认使用的最经典的Round Robin轮询算法。这是啥?简单来说,就是如果订单服务对库存服务发起10次请求,那就先让你请求第1台机器、然后是第2台机器、第3台机器、第4台机器、第5台机器,接着再来—个循环,第1台机器、第2台机器。。。以此类推。

此外,Ribbon是和Feign以及Eureka紧密协作,完成工作的,具体如下:

  • 首先Ribbon会从 Eureka Client里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口号。
  • 然后Ribbon就可以使用默认的Round Robin算法,从中选择一台机器
  • Feign就会针对这台机器,构造并发起请求。

以订单服务远程调用库存服务为例,描述EurekaRibbonFeign三者之间的协同关系:

Eureka、Ribbon、Feign三者之间的关系.png

参考:一文读懂SpringCloud与Eureka,Feign,Ribbon,Hystrix,Zuul核心组件间的关系

agentBuy-service服务本地联调其他环境

上面说到agentBuy-service是一个集成Spring MVC搭建的web项目,服务调用的整体流程,如果需要在本地联调hwbeta、gamma环境,需要注意一下几点:

(1)通过网关webagent走本地agentBuy-service

image-20220111183527675.png

(2)页面调用agentBuy-service接口,agentBuy-service默认调用alpha环境远程服务,需要更改远程服务地址

因为agentBuy-service采用FeignClientBuilder动态创建FeignClient实例去发起远程调用,生成bean时指定FeignClientBuilderconfigAgentUrl可以跨Eureka调用服务,不需要通过Eureka Server定位服务

image-20220111184644237.png

image-20220111183800956.png

(3)页面还可能调用其他web服务接口,而web服务由调用了其他远程服务,因此需要更改webagent配置走本地web服务,并且web服务(Spring Boot)的注册中心地址也需要更改。页面获取广告位数据是调用web-market服务,以此为例描述下怎么更改配置:

# webagent/config/routes.yml
- id: web-market
  predicates:
      - Path=/market/**
  #uri: 'lb://web-market'
  uri: 'http://localhost:9801' # 改成走本地web-market服务

设置项目启动的环境变量spring.profiles.active=hwbeta,启用hwbeta环境的配置

# bootstrap.yml
server:
  port: 9801
  contextPath: /market
  tomcat:
    protocolHeader: X-Forwarded-Proto
encrypt.failOnError: false
spring:
  application:
    name: web-market
  cloud:
    config:
      uri: http://config-center.alpha-intra.casstime.com/conf
      label: alpha
management:
  port: 29801
  contextPath: /hellgate
  security.enabled: false
xmwlServiceUrl: https://xmwltest.casstime.com/edi/kaisi/getLogisticOrderTrace
wenjuanxing.key: 8560a160-d42b-40d6-a97e-48b12cf51e8b
---
spring:
  profiles: hwbeta
  cloud:
    config:
      uri: http://config-center.hwbeta-intra.casstime.com/conf # 改成去配置中心服务拉取hwbeta的配置,里面包含Eureka Server注册中心的地址
      label: hwbeta

---
spring:
  profiles: prod
  cloud:
    config:
      uri: http://config-server.intra.cassmall.com/conf
      label: master
xmwlServiceUrl: https://xmwl.cassmall.com/edi/kaisi/getLogisticOrderTrace

本地联调其他环境的缺点

本地联调hwbeta、gamma环境会导致环境被污染,以下面两个场景为例作下说明:

(1)如果本地打通了hwbeta环境,本地agentBuy-service能调通hwbeta环境的远程服务quote-service,当quote-service发送一个消息推送,此时本地agentBuy-service会接收到消息,消费此消息。假如这条消息表示供应商报价完成,agentBuy-service接收到此消息后需要将shoppinglist表中的询价单状态更改为报价完成,但是如果本地连接的是alpha环境的数据库,这时是查找不到该询价单的,也就无法更改其报价状态,而该询价单正确状态应该是报价完成,实际却是未完成报价,导致hwbeta环境数据被污染了。

本地联调其他环境的缺点.png

(2)假如agentBuy-service依赖了web-sellerweb-market两个web服务,web-seller远程调用了A服务,web-market远程调用了B服务,但是本地联调只更改了web-seller指向A服务的远程调用地址(hwbeta),而没有更改web-market指向B服务远程地址(还是alpha),导致页面提交接口入参数据有部分来自于alpha,有些数据来自hwbeta,因此写入的数据是错误的,hwbeta环境也被污染了。

hwbetagamma环境类似于一个沙箱,几乎完全隔离,确保了测试的可靠性,一旦环境被污染,在该环境测试结果的可靠性就得不到保证,因此并不建议大家通过上述方式本地联调


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK