线上使用spring cloud

2019-11-24  本文已影响0人  晚上吃什么啊

刚从零开始上线了一套基于SpringCloud的微服务应用,遇到了一些坑,在三月份的最后一天做一个总结

技术选型

首先讲下技术选型,在微服务领域两大巨头,Dubbo和SpringCloud,近期关注微服务的朋友们可能知道,例如Erueka,Hystrix,Zuul这些貌似都已经停止更新了,虽然他们官网上说,现有的版本已经满足现有的需求,但软件领域,睡一觉就是另一个样,但好在,Spring积极在解决这些问题,基本上也都有替代品,比如全套的阿里系微服务spring-cloud-alibaba。

为什么放弃dubbo这个微服务框架呢,首先第一点,dubbo基于RPC通信,类似thrift,用过thrift的朋友,都知道,虽然thrift这个框架非常厉害,它可以调用其他语言的服务,java调python啊,python调c啊这些,而且性能非常不错。但有一点,一旦你的接口有变动,那所有涉及到这个接口的实现服务,都需要重新生成一遍对应语言的接口并依赖新生成的接口完成实现,实在是非常麻烦

Dubbo没有thrift那么麻烦,但如果新加入了接口类,就一定要在提供方和调用方同时配置一堆东西,如果分组开发,由此产生的沟通成本将非常昂贵,我们项目要求快速迭代,因此,放弃Dubbo的RPC协议,使用基于Http的Sping Cloud体系,而且由业务推倒,后期服务可能不会过于复杂,因此,服务降级显得不那么重要,如果后期涉及相关问题,可以采用阿里套餐中[Sentinel]组建,以资源的方式来帮助我们处理服务降级相关的问题。

构建

为了践行OpsDev快速迭代,必然是要采用自动化部署的,选用的是老牌自动化部署工具[Jenkins],但也由此产生了一个小问题,Jenkins是从git上获取代码,本地编译后通过脚本部署的,但git无法拉取某个路径下的代码,所以只能吧项目分开,使用多个git仓库管理,原本项目是使用svn进行托管,微服务项目可以放在一个工程下,同时打开整个微服务工程,但现在看来没办法这么做了。

分享一下部署脚本

#!/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/jdk1.8.0_191/bin:/usr/local/elasticsearch-6.5.4/bin:/root/bin
PATH=.:$PATH
export PATH

JENKINS_NAME=$1
ARTIFACT_ID=$2
SPRING_PROFILE=$3

echo $JENKINS_NAME $ARTIFACT_ID $SPRING_PROFILE

if [ ! -d "/data/webapp/$JENKINS_NAME" ]; then
  mkdir /data/webapp/$JENKINS_NAME
fi

SING_STR="\$\\"
cd /data/webapp/deployScript
mv $JENKINS_NAME-0.0.1-SNAPSHOT.jar $JENKINS_NAME-0.0.1-SNAPSHOT_bak.jar

scp -P22  xxxx@xxx.xxx.xxx.xxx:"/data/webapp/.jenkins/jobs/$JENKINS_NAME/modules/com.thinkerjet$SING_STR$ARTIFACT_ID/lastSuccessful/archive/com.thinkerjet/$ARTIFACT_ID/0.0.1-SNAPSHOT/$ARTIFACT_ID-0.0.1-SNAPSHOT.jar" ./$JENKINS_NAME-0.0.1-SNAPSHOT.jar

spring_pid=`ps -ef | grep $JENKINS_NAME | grep -v grep | grep -v deploy_lexin_boot.sh| awk '{print $2}'`
[ -n "$spring_pid" ] && kill -9 $spring_pid

cp $JENKINS_NAME-0.0.1-SNAPSHOT.jar /data/webapp/$JENKINS_NAME/$JENKINS_NAME-0.0.1-SNAPSHOT.jar
cd /data/webapp/$JENKINS_NAME
/usr/bin/nohup java -jar $JENKINS_NAME-0.0.1-SNAPSHOT.jar --spring.profiles.active=${SPRING_PROFILE} > /data/webapp/$JENKINS_NAME/catalina.out 2>&1 &

jenkins来执行远程脚本,通过scp命令获取打包好的文件,然后执行一下就OK了,另外,不要用lsof -i 这个天杀的命令
它会把所有跟那个端口有通讯的进程都查出来,比如,你如果用lsof -i:eureka端口,那么你部署在这台机器上的其他微服务,全被kill了

开发

基于Greenwich.SR1版本开发
未完待续

遇到的坑

  1. 使用OpenFeign时,使用@FeignClient注解,会在Bean容器中生成Bean对象,如多个@FeignClient注释的类使用相同服务,无法启动

    • 解决方法:@FeignClient(name = "服务名称", contextId = "类名")
  2. 使用OpenFeign时,服务间调用默认1秒失败,会出现服务调用成功,但响应超时 - ReadTimeOut,使数据不一致

    • 解决方法:设置ribbon超时时间 (OpenFeign底层调用ribbon以及Hystrix),高版本中默认Hystrix不开启
        ribbon:
          ReadTimeout: 60000
          ConnectTimeout: 60000
    
  3. 微服务部署时,需要先让路由getway停止路由到被部署服务,以防请求过程中服务重启

    • 解决方法:使用spring-boot-starter-actuator优雅关闭服务
    • 添加spring-boot-starter-actuator,默认shutdown等端点为关闭状态,启动shutdown端点,访问http:localhost:xxxx/actuator/shutdown
        management:
          endpoint:
            shutdown:
              enabled: true
          endpoints:
            web:
              exposure:
                include: shutdown
              base-path: /
    
  4. 配置拦截器无法Autowired

    • 解决方法:使用WebApplicationContextUtils.getRequiredWebApplicationContext方法获取bean工厂,后加载
        private Foo foo;
        ...
        if (foo == null) {
            BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
            foo = factory.getBean(Foo.class);
        }
    
  5. 日志输出

    • 方法:使用SpringBoot内置文件输出,注:该配置只应在生产环境中使用
        logging:
            path: 此处建议设置为服务器中jar路径下,新建工程同名文件夹,用来保存该项目日志
    
  6. 接口保护

    • 方法:在getway中配置2级路径,如不希望外界路由该接口,则在第一级路径区分,内部服务使用/provider,提供给外部的服务使用/api
        spring:
          application:
            name: gateway-service
          profiles:
            active: dev
          cloud:
            gateway:
              routes:
                - id:  xxxx
                  uri: lb://business
                  predicates:
                    - Path=/business/**
                  filters:
                    - StripPrefix=1     //该配置表示如访问该服务,则不传递/business/路径
                                    //例如前台访问/businsess/foo, 则服务真实控制器路径为/foo
                - id:  interface
                  uri: lb://interface
                  predicates:
                    - Path=/api/**      //表示只有/api/为一级路径的控制器才可以被路由访问
    
  7. 生产环境Eureka高可用,及unavailable-replicas问题

    • eureka.instance.appname必须与spring.application.name相同
    • eureka.instance.hostname必须使用hosts名称映射,修改hosts文件
    • eureka.client.fetch-registryeureka.client.register-with-eureka要么不写,要么必须为true
        server:
          port: 8761
        eureka:
          instance:
            leaseRenewalIntervalInSeconds: 5
            leaseExpirationDurationInSeconds: 10
          server:
            enable-self-preservation: true
            evictionIntervalTimerInMs: 5000
            useReadOnlyResponseCache: false
          client:
            fetch-registry: true
            register-with-eureka: true
        spring:
          application:
            name: xxx
          profiles:
            active: prod-1
        logging:
          level:
            com.netflix: INFO
        ---
        server:
          port: 8761
        spring:
          profiles: prod-1
        eureka:
          instance:
            hostname: service-eureka-01
            appname: xxx
          client:
            serviceUrl:
              defaultZone: http://service-eureka-02:8762/eureka
        ---
        server:
          port: 8762
        spring:
          profiles: prod-2
        eureka:
          instance:
            hostname: service-eureka-02
            appname: xxx
          client:
            serviceUrl:
              defaultZone: http://service-eureka-01:8761/eureka
    

总结

上一篇下一篇

猜你喜欢

热点阅读