sbc(二)高可用Eureka+声明式服务调用

前言

上一篇简单入门了SpringBoot+SpringCloud 构建微服务。但只能算是一个demo级别的应用。
此次会按照实际生产要求来搭建这套服务。html

Swagger应用

上次提到咱们调用本身的http接口的时候采用的是PostMan来模拟请求,这个在平时调试时天然没有什么问题,但当咱们须要和前端联调开发的时候效率就比较低了。前端

一般来讲如今先后端分离的项目通常都是后端接口先行。java

后端大大们先把接口定义好(入参和出参),前端大大们来肯定是否知足要求,能够了以后后端才开始着手写实现,这样总体效率要高上许多。node

但也会带来一个问题:在接口定义阶段频繁变动接口定义而没有一个文档或相似的东西来记录,那么双方的沟通加上前端的调试都是比较困难的。git

基于这个需求网上有各类解决方案,好比阿里的rap就是一个不错的例子。github

可是springCould为咱们在提供了一种在开发springCloud项目下更方便的工具swaggerspring

实际效果以下:后端

01.png
01.png

配置swagger

sbc-order为例我将项目分为了三个模块:api

├── order                                    // Order服务实现 
│   ├── src/main
├── order-api                                // 对内API
│   ├── src/main
├── order-client                             // 对外的clientAPI
│   ├── src/main
├── .gitignore                               
├── LICENSE                
├── README.md复制代码

由于实现都写在order模块中,因此只须要在该模块中配置便可。springboot

首先须要加入依赖,因为我在order模块中依赖了:

<dependency>
    <groupId>com.crossoverJie</groupId>
    <artifactId>order-api</artifactId>
    <version>${target.version}</version>
</dependency>复制代码

order-api又依赖了:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <scope>compile</scope>
</dependency>复制代码

接着须要配置一个SwaggerConfig

@Configuration
@EnableSwagger2
/** 是否打开swagger **/
@ConditionalOnExpression("'${swagger.enable}' == 'true'")
public class SwaggerConfig {


    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.crossoverJie.sbcorder.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("sbc order api")
                .description("sbc order api")
                .termsOfServiceUrl("http://crossoverJie.top")
                .contact("crossoverJie")
                .version("1.0.0")
                .build();
    }

}复制代码

其实就是配置swagger的一些基本信息。
以后启动项目,在地址栏输入http://ip:port/swagger-ui.html#/便可进入。
能够看到如上图所示的接口列表,点击以下图所示的参数例子便可进行接口调用。

02.jpg
02.jpg

自定义开关Swagger

swagger的便利能给咱们带来不少好处,但稍有不慎也可能出现问题。

好比若是在生产环境还能经过IP访问swagger的话那后果但是不堪设想的。
因此咱们须要灵活控制swagger的开关。

这点能够利用spring的条件化配置(条件化配置能够配置存在于应用中,一旦知足一些特定的条件时就取消这些配置)来实现这一功能:

@ConditionalOnExpression("'${swagger.enable}' == 'true'")复制代码

该注解的意思是给定的SpEL表达式计算结果为true时才会建立swaggerbean

swagger.enable这个配置则是配置在application.properties中:

# 是否打开swagger
swagger.enable = true复制代码

这样当咱们在生产环境时只须要将该配置改成false便可。

ps:更多spring条件化配置:

@ConditionalOnBean                 //配置了某个特定Bean
@ConditionalOnMissingBean          //没有配置特定的Bean
@ConditionalOnClass                //Classpath里有指定的类
@ConditionalOnMissingClass         //Classpath里缺乏指定的类
@ConditionalOnExpression           //给定的Spring Expression Language(SpEL)表达式计算结果为true
@ConditionalOnJava                 //Java的版本匹配特定值或者一个范围值
@ConditionalOnJndi                 //参数中给定的JNDI位置必须存在一个,若是没有给参数,则要有JNDI InitialContext
@ConditionalOnProperty             //指定的配置属性要有一个明确的值
@ConditionalOnResource             //Classpath里有指定的资源
@ConditionalOnWebApplication       //这是一个Web应用程序
@ConditionalOnNotWebApplication    //这不是一个Web应用程序
(参考SpringBoot实战)复制代码

高可用Eureka

在上一篇中是用Eureka来作了服务注册中心,全部的生产者都往它注册服务,消费者又经过它来获取服务。

可是以前讲到的都是单节点,这在生产环境风险巨大,咱们必须作到注册中心的高可用,搭建Eureka集群。

这里简单起见就搭建两个Eureka,思路则是这两个Eureka都把本身当成应用向对方注册,这样就能够构成一个高可用的服务注册中心。

在实际生产环节中会是每一个注册中心一台服务器,为了演示起见,我就在本地启动两个注册中心,可是端口不同。

首先须要在本地配置一个host:

127.0.0.1 node1 node2复制代码

这样不管是访问node1仍是node2均可以在本机调用的到(固然不配置host也能够,只是须要经过IP来访问,这样看起来不是那么明显)。

并给sbc-service新增了两个配置文件:

application-node1.properties:

spring.application.name=sbc-service
server.port=8888
eureka.instance.hostname=node1

## 不向注册中心注册本身
#eureka.client.register-with-eureka=false
#
## 不须要检索服务
#eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://node2:9999/eureka/复制代码

application-node2.properties:

spring.application.name=sbc-service
server.port=9999
eureka.instance.hostname=node2

## 不向注册中心注册本身
#eureka.client.register-with-eureka=false
#
## 不须要检索服务
#eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://node1:8888/eureka/复制代码

其中最重要的就是:

eureka.client.serviceUrl.defaultZone=http://node2:9999/eureka/
eureka.client.serviceUrl.defaultZone=http://node1:8888/eureka/复制代码

两个应用互相注册。

启动的时候咱们按照:
java -jar sbc-service-1.0.0-SNAPSHOT.jar --spring.profiles.active=node1启动,就会按照传入的node1或者是node2去读取application-node1.properties,application-node2.properties这两个配置文件(配置文件必须按照application-{name}.properties的方式命名)。

分别启动两个注册中心能够看到如下:

03.jpg
03.jpg


04.jpg
04.jpg

能够看到两个注册中心以及互相注册了。
在服务注册的时候只须要将两个地址都加上便可:
eureka.client.serviceUrl.defaultZone=http://node1:8888/eureka/,http://node2:9999/eureka/

在服务调用的时候能够尝试关闭其中一个,正常状况下依然是能够调用到服务的。

Feign声明式调用

接下来谈谈服务调用,上次提到能够用ribbon来进行服务调用,可是明显很不方便,不如像以前rpc调用那样简单直接。

为此此次使用Feign来进行声明式调用,就像调用一个普通方法那样简单。

order-client

片头说到我将应用分红了三个模块order、order-api、order-client,其中的client模块就是关键。

来看看其中的内容,只有一个接口:

@RequestMapping(value="/orderService")
@FeignClient(name="sbc-order")
@RibbonClient
public interface OrderServiceClient extends OrderService{


    @ApiOperation("获取订单号")
    @RequestMapping(value = "/getOrderNo", method = RequestMethod.POST)
    BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) ;
}复制代码

@FeignClient这个注解要注意下,其中的name的是本身应用的应用名称,在
application.properties中的spring.application.name配置

其中继承了一个OrderServiceorder-api模块中,来看看order-api中的内容。

order-api

其中也只有一个接口:

@RestController
@Api("订单服务API")
@RequestMapping(value = "/orderService")
@Validated
public interface OrderService {

    @ApiOperation("获取订单号")
    @RequestMapping(value = "/getOrderNo", method = RequestMethod.POST)
    BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) ;
}复制代码

这个接口有两个目的。

  1. 给真正的controller来进行实现。
  2. client接口进行继承。

类关系以下:

05.jpg
05.jpg

注解这些都没什么好说的,一看就懂。

order

order则是具体接口实现的模块,就和平时写controller同样。
来看看如何使用client进行声明式调用:

此次看看sbc-user这个项目,在里边调用了sbc-order的服务。
其中的user模块依赖了order-client:

<dependency>
    <groupId>com.crossoverJie</groupId>
    <artifactId>order-client</artifactId>
</dependency>复制代码

具体调用:

@Autowired
    private OrderServiceClient orderServiceClient ;

    @Override
    public BaseResponse<UserResVO> getUserByFeign(@RequestBody UserReqVO userReq) {
        //调用远程服务
        OrderNoReqVO vo = new OrderNoReqVO() ;
        vo.setReqNo(userReq.getReqNo());
        BaseResponse<OrderNoResVO> orderNo = orderServiceClient.getOrderNo(vo);

        logger.info("远程返回:"+JSON.toJSONString(orderNo));

        UserRes userRes = new UserRes() ;
        userRes.setUserId(123);
        userRes.setUserName("张三");

        userRes.setReqNo(userReq.getReqNo());
        userRes.setCode(StatusEnum.SUCCESS.getCode());
        userRes.setMessage("成功");

        return userRes ;
    }复制代码

能够看到只须要将order-client包中的Order服务注入进来便可。

sbc-clientswagger中进行调用:

06.jpg
06.jpg


07.jpg
07.jpg

因为我并没传appId因此order服务返回的错误。

总结

当一个应用须要对外暴露接口时着须要按照以上方式提供一个client包更消费者使用。

其实应用自己也是须要作高可用的,和Eureka高可用同样,再不一样的服务器上再启一个或多个服务并注册到Eureka集群中便可。

后续还会继续谈到zuul网关,容错,断路器等内容,欢迎拍砖讨论。

项目:github.com/crossoverJi…

博客:crossoverjie.top

weixinchat.jpg
weixinchat.jpg
相关文章
相关标签/搜索