美文网首页
初见spring cloud(1):Eureka集群中心+服务注

初见spring cloud(1):Eureka集群中心+服务注

作者: 神驱一梦 | 来源:发表于2019-06-06 22:31 被阅读0次

    前言

      本文记录了最近几天在下对于spring cloud的摸索使用阶段所遇到的一些坑,以及坑的解决过程。过程中参考了包括spring官方的文档,和各路大神们的blog等等,参考的部分我会使用直接上原链接的方式而不是大段地引用过来。
      目前是只搭建好了后端的分布式环境,但是网关暂时还没加进去,同时前后端交互的问题在下也还没有解决(嘤嘤嘤前端除了JS之外瓦塔西瓦完全不懂desu~),找前端小伙伴问了问说是最近比较流行vue.js(学前端是不可能的,这辈子都不想再碰前端;真香!),后续看缘分能不能出(二)吧。
      本文基于 spring boot 2.1.5.RELEASE 版本
      那么下面开始我的踩坑记录。

    How to begin?

      这一节是专门写给萌新看的。
      众所周知,石原里美是我老婆(大雾)咳咳,应该说,众所周知,spring 全家桶系列现在已经可以很方便地用spring boot集成各种组件了,除了一些参数的配置之外开发者可以说是非常省事。
      想要开始搭建一个spring boot项目,有以下两种方式:

    1. 通过官方提供的 start.spring.io 页面,直接通过简单的选项即可生成一个完整的项目压缩包,下载解压后可以直接用IDE打开。
      过程就不展开了,完全傻瓜式操作,请自行摸索。
    2. 如果你是用的intelj idea的话,终极版用户也可以直接new project的时候使用里面的spring initializer;社区版用户则可以下载一个名为spring assistant插件。用法与1基本一致。

      在本文中所提到的各项功能会应用到不同的依赖,生成项目的时候需要在dependencies里选的,现在这里列举一下,后面讲不同模块的时候也会再说明:

    • Eureka server 中心+安全验证:Cloud Discovery -> Eureka Server ,Security -> Security
    • Eureka client端的服务发现与注册+安全验证:Cloud Discovery -> Eureka Discovery ,Security -> Security ,Web -> Web
    • Ribbon 负载均衡与远程调用:Cloud Routing -> Ribbon
    • Feign 负载均衡与远程调用:Cloud Routing -> Feign

    (P.S.使用网页生成压缩包的小伙伴请忽略箭头前的部分,直接在Denpendencies上写出包的名字就ok了)

    Eureka Server高可用集群中心的搭建

    中心搭建需要依赖 Cloud Discovery -> Eureka Server 这个包
    mvn build成功后在main类加入注解@EnableEurekaServer
    just like this

    @SpringBootApplication
    @EnableEurekaServer
    @ComponentScan("com.borris")
    public class SpringCloudTiyApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringCloudTiyApplication.class, args);
        }
    }
    

    如此即可在启动的时候自动化配置eureka server

    集群中心的搭建参考官方文档的此处:
    spring-cloud-eureka-server-peer-awareness
    但实际配置时不能完全照搬文档上的内容,这么几个注意点:

    spring:
      profiles: center3
      application:
        name: eureka-center
    
    eureka:
      instance:
        hostname : localhost:7001
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
      server:
        #设置健康节点检测间隔(ms)
        eviction-interval-timer-in-ms : 10000
    
    

    如上,文档中hostname用的是域名,如果本地也想用域名作为配置的话需要修改hosts文件,不想改Host的话直接写ip:port这样的形式就好了
    然后配置defaultZone的时候,把多个server的url写上去,形式为
    http://hostname/eureka
    即可,中间以逗号隔开

    配置成功之后,当中心启动起来时会看到页面上显示以下内容:


    配置成功效果图

    如上图,配置成功后会看到DS Replicas中显示集群的hostname,
    同时会显示已注册的Application,名称为上面的spring.application.name
    下面那块则不需要过多关注,defaultZone配置好了会显示一样的内容的,但真正上两块才是真正注册成功的显示

    根据文档中所说,多个yml文件可以合并在同一个文件里,中间用【---】分隔开,以spring.profiles属性作为区分,启动时在program arguments加入

    --spring.profiles.active=profileName
    

    即可启动多个实例,而不需要每个实例改文件内容(方便本地测试用)

    server端还可开启一个节点健康监测的选项,如上的
    eureka.server.eviction-interval-timer-in-ms
    属性,例如我这里设置的是每10s(10000ms)检查一下节点健康,当检查到节点工作状态不正常会自动从列表上删除

    Eureka Client搭建方法

    Eureka Client端需要依赖 Cloud Discovery -> Eureka Discovery ,Web -> Web 两块
    client端需要在main类加入注解@EnableDiscoveryClient,跟上面一样就不重复贴代码了

    client端的配置方式基本上与server端类似其实,请查看下面的配置

    eureka:
      client:
        #表示eureka client间隔多久去拉取服务器注册信息,默认为30秒
        registry-fetch-interval-seconds : 5
        service-url:
          defaultZone : http://localhost:7001/eureka/
      instance:
        #心跳间隔
        lease-renewal-interval-in-seconds : 5
        #心跳停止后的节点过期时间
        lease-expiration-duration-in-seconds : 10
        instance-id : localhost:${server.port}
    

    参考上面的注释和属性,在client端的角度上来说,defaultZone就是它们的注册节点,基本上来说,如果server中心的配置和工作都是正常的话,那么client只注册单个server,server集群会自动把所有的注册信息复制到其他的endpoint上,因此也可以通过client端的注册状态,来验证server集群的配置是否正确

    client注册效果图

    如上图所示,虽然我的client的url只配置了7001这一台server,但是因为server是集群工作的,所以我可以登陆7002和7003也同样看到上图中test-client的注册信息

    但是保险起见,为了避免单个endpoint注册时发生节点宕机或其他的风险,实际生产上运用的时候还是将所有endpoint的配置都配全比较好,多个url之间用逗号隔开,如下:

    defaultZone : http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
    

    另外有一个很奇怪的点,目前观察发现注册的url默认是绑定到了 /eureka 上面去,我觉得应该是可以提供修改的property的,但实际上我查找了文档以及各路大神的blog,发现貌似是定死了不能改的,我只发现了一个文档上一个疑似的

    eureka.instance.namespace eureka Get the namespace used to find properties. Ignored in Spring Cloud.

    看这个property的描述,【获取用于查找属性的命名空间,但在spring cloud中被忽略】
    另外还在源码中找到了以下内容:

    org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

        /**
         * Register the Jersey filter.
         * @param eurekaJerseyApp an {@link Application} for the filter to be registered
         * @return a jersey {@link FilterRegistrationBean}
         */
        @Bean
        public FilterRegistrationBean jerseyFilterRegistration(
                javax.ws.rs.core.Application eurekaJerseyApp) {
            FilterRegistrationBean bean = new FilterRegistrationBean();
            bean.setFilter(new ServletContainer(eurekaJerseyApp));
            bean.setOrder(Ordered.LOWEST_PRECEDENCE);
            //使用了一个常量EurekaConstants.DEFAULT_PREFIX 注册filter的url拦截
            bean.setUrlPatterns(
                    Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));
    
            return bean;
        }
    

    让我们再来看看这个EurekaConstants.DEFAULT_PREFIX 是个什么来头
    org.springframework.cloud.netflix.eureka.EurekaConstants

        /**
         * Default Eureka prefix.
         */
        public static final String DEFAULT_PREFIX = "/eureka";
    

    emmmmm不大明白为什么eureka的注册filter要这样设计一个写死的url,但看到这一行我就知道可以死了心了,目前官方不提供修改服务绑定名的途径,如果不想自己重构的话就先将就着用吧。

    另外client端多节点部署的话,可以直接参考server的多节点搭建,这里就不赘述了。

    加入Security安全验证

    spring cloud 支持client与server之间使用安全验证进行注册,在建立server(client不需要)项目的时候加入Security -> Security 依赖即可,也可以直接在pom.xml上加上

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    

    加入后可以在yml或者properties里加入用户名和密码

    spring:
      security:
        basic:
          #打开安全开关
          enabled: true
        #用户名密码
        user:
          name: name
          password: password
    

    加入这些配置之后重新启动项目,访问中心的时候就会出现登陆画面


    登陆画面

    登陆后即可看到前面部分所提起过首页

    client端的配置变化其实也不大,请参考下面的URL配置:

    eureka:
      client:
        #表示eureka client间隔多久去拉取服务器注册信息,默认为30秒
        registry-fetch-interval-seconds : 5
        service-url:
          defaultZone : http://name:password@localhost:7001/eureka/,http://name:password@localhost:7002/eureka/,http://name:password@localhost:7003/eureka/
    

    如上,只需要将defaultZone的部分加入name:password在中间即可

    特别注意:

    目前的eureka client比较坑的一点是,它会自动化配置CSRF防御机制,然而eureka client并没有加入对其的支持,根据spring 文档(spring-security#csrf)称

    to ensure that you include the CSRF token in all PATCH, POST, PUT, and DELETE methods

    根据此处可以得知,spring security会对上述的http method都认为是有风险的,而如果这些method发送过程中没有带上 CSRF token的话,会被直接拦截并返回 403 forbidden,下面让我们来瞅瞅client是怎么给server发送注册请求的,client启动过程打出的异常日志中,可以跟踪到接收403信息的类,往下跟踪,在下找到了这个类:
    com.netflix.discovery.shared.transport.jersey.JerseyApplicationClient
    我们来看看它的register方法:

        @Override
        public EurekaHttpResponse<Void> register(InstanceInfo info) {
            String urlPath = "apps/" + info.getAppName();
            ClientResponse response = null;
            try {
                Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
                addExtraHeaders(resourceBuilder);
                response = resourceBuilder
                        .header("Accept-Encoding", "gzip")
                        .type(MediaType.APPLICATION_JSON_TYPE)
                        .accept(MediaType.APPLICATION_JSON)
                        .post(ClientResponse.class, info);
                return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
            } finally {
                if (logger.isDebugEnabled()) {
                    logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                            response == null ? "N/A" : response.getStatus());
                }
                if (response != null) {
                    response.close();
                }
            }
        }
    

    细心的小伙伴应该注意到了,它的请求是POST粗去的,而且前面并没有请求获取 CSRF token 的步骤,所以毫不意外地被server给forbidden了。
    按理说这个问题已经出现了很久了,因为我最早看到有人用spring boot 1.5.x的版本已经有这个问题。至于官方为什么不维护eureka client修复这个问题呢?咱也不知道,咱也不敢问……

    但是官方还是给出了解决的方法,具体可以参考 spring cloud issue 2754,里面有大量的讨论,我这边总结的解决方案:

    1. 配置一个@EnableWebSecurity配置类,继承org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter,重写configure方法。
      重写有两种方法,方法①如下:
      1). 使CSRF忽略 /eureka/*的所有链接
    @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().ignoringAntMatchers("/eureka/**");
            super.configure(http);
        }
    

    2). 保持密码验证的同时禁用CSRF防御机制

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //注意,如果直接disable的话会把安全验证也禁用掉
            http.csrf().disable().authorizeRequests()
            .anyRequest()
            .authenticated()
            .and()
            .httpBasic();
        }
    
    1. 我个人觉得其实还有另一个方法,自己重构register方法,使其在发送注册请求前先GET获得一个CSRF token,后续再POST注册请求,但是这个方法改如何实现emmmmmm目前还没有这个想法去搞(你个死肥宅就是懒得动)

    使用上面的方法1之后,在下这边是可以正确注册上了,如果有哪位小伙伴还是不行的话,可以留言或者私信我,我有空的话一起来看看是什么问题~

    本文暂时到这里,后续我会考虑把Feign或者Ribbon这两种远程调用的方式测试对比,然后写初见(2)的总结,有余力的话考虑加入Zuul网关?

    那么有缘再会~

    20190606
    神驱一梦
    于无月之夜
    (P.S.夹个私货,写结尾时的BGM是勾指起誓,在下非常喜欢~ 请务必吃下这个安利~)

    相关文章

      网友评论

          本文标题:初见spring cloud(1):Eureka集群中心+服务注

          本文链接:https://www.haomeiwen.com/subject/miqjxctx.html