刚从零开始上线了一套基于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
版本开发
未完待续
遇到的坑
-
使用OpenFeign时,使用
@FeignClient
注解,会在Bean容器中生成Bean对象,如多个@FeignClient
注释的类使用相同服务,无法启动- 解决方法:@FeignClient(name = "服务名称", contextId = "类名")
-
使用OpenFeign时,服务间调用默认1秒失败,会出现服务调用成功,但响应超时 - ReadTimeOut,使数据不一致
- 解决方法:设置ribbon超时时间 (OpenFeign底层调用ribbon以及Hystrix),高版本中默认Hystrix不开启
ribbon: ReadTimeout: 60000 ConnectTimeout: 60000
-
微服务部署时,需要先让路由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: /
- 解决方法:使用
-
配置拦截器无法Autowired
- 解决方法:使用
WebApplicationContextUtils.getRequiredWebApplicationContext
方法获取bean工厂,后加载
private Foo foo; ... if (foo == null) { BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext()); foo = factory.getBean(Foo.class); }
- 解决方法:使用
-
日志输出
- 方法:使用SpringBoot内置文件输出,注:该配置只应在生产环境中使用
logging: path: 此处建议设置为服务器中jar路径下,新建工程同名文件夹,用来保存该项目日志
-
接口保护
- 方法:在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/为一级路径的控制器才可以被路由访问
-
生产环境Eureka高可用,及unavailable-replicas问题
-
eureka.instance.appname
必须与spring.application.name
相同 -
eureka.instance.hostname
必须使用hosts名称映射,修改hosts文件 -
eureka.client.fetch-registry
与eureka.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
-