spring cloud使用心得

API网关

选型:nginx、zuul

结论:

Zuul1 的设计模式和 Nigix 较像,每次 I/O 操作都是从工作线程中选择一个执行,请求线程被阻塞直到工作线程完成,但是差别是 Nginx 用 C++ 实现,Zuul 用 Java 实现,而 JVM 本身有第一次加载较慢的情况。Zuul2 的性能肯定会较 Zuul1 有较大的提升,此外,Zuul 的第一次测试性能较差,但是从第二次开始就好了很多,可能是由于 JIT(Just In Time)优化造成的吧。

zuul的配置

代码:

@SpringBootApplication
@EnableZuulProxy
public class GatewayApplication {

	public static void main(String[] args) {
		SpringApplication.run(GatewayApplication.class, args);
	}

}

其中,@EnableZuulProxy简单理解为@EnableZuulServer的增强版,当Zuul与Eureka、Ribbon等组件配合使用时,我们使用@EnableZuulProxy。

zuul转发丢失session的问题

浏览器到zuul的session,在zuul转发给业务服务时,默认会丢弃掉,这对依赖session的功能会造成影响。解决方法:

zuul.sensitiveHeaders=

表示zuul不会过滤任何信息。

路由配置

传统配置:

eureka.client.service-url.defaultZone=http://localhost:8761/eureka

zuul.routes.scilab-url.path=/**
zuul.routes.scilab-url.url=http://127.0.0.1:8001

面向服务的配置(需要利用Eureka的自动发现能力):

zuul.routes.scilab-mate=/**

其中,scilab-mate是我们的服务ID。这种方式还会自动做负载均衡

路由规则的前缀问题

zuul转发时默认会将匹配的前缀去掉,比如下面的规则:

zuul:
  sensitive-headers:
  routes:
    fmumgmt-service:
      path: /prj_prefix/fmu_mgmt/**

/prj_prefix/fmu_mgmt/v1/lib转给业务微服务时变成了/v1/lib。有时我们不希望这样,可使用stripPrefix=false禁止:

zuul:
  sensitive-headers:
  routes:
    fmumgmt-service:
      path: /ares_rainbow/fmu_mgmt/**
      stripPrefix: false

路由规则的先后顺序

如果要确定路由规则的先后顺序,不能使用properties文件,只能使用yaml格式,因前者会丢失顺序信息。

比如下面例子:

zuul:
  sensitive-headers:
  routes:
    fmumgmt-service:
      path: /prj_prefix/fmu_mgmt/**
      stripPrefix: false
    scilab-mate: /**

/prj_prefix/fmu_mgmt就在/之前被匹配。

路由规则参考

可以参考zuul的文档说明:

https://cloud.spring.io/spring-cloud-netflix/multi/multi__router_and_filter_zuul.html

代码定制路由规则

静态资源

静态资源请求也会走zuul,这从zuul的debug日志可以看出来,设置zuul的debug日志:

logging:
  file: gateway.log
  level:
    com.netflix: DEBUG

zuul转发时url结尾自动加/

这个不是zuul的问题,而是后端web server的问题,像flask无此问题,django则有。

查看实际生效的路由

使用/actuator/routes查看生效的路由,例如:

http://127.0.0.1:5555/actuator/routes

不过,必须在application.yml里配置,否则该监控端点不会默认打开:

management:
  endpoints:
    web:
      exposure:
        include: '*'

通过查看/actuator/routes的结果,我们能看到,除了我们指定的url,zuul为我们自动生成了一些默认端点,可以使用ignored-services去掉这些默认端点:

zuul:
  routes:
    fmumgmt-service:
      path: /prj_prefix/fmu_mgmt/**
      stripPrefix: false
    media-service:
      path: /prj_prefix/media/**
      stripPrefix: false
    scilab-mate:
      path: /prj_prefix/scilab_mate/**
      stripPrefix: false
  ignored-services: '*'

避免/actuator被zuul路由规则覆盖

如果我们在zuul路由规则里配置了/,会导致/actuator也被分发到后端服务上,这不是我们想要的,有两种解决方法:

  1. 设置management.server.port
  2. 使用ignoredPatterns

个人推荐方法1。

上传文件大小限制

这个没得说,修改spring boot配置:

spring:
  application:
    name: api-gateway
  servlet:
    multipart:
      max-file-size: 20MB
      max-request-size: 20MB

Zuul安全认证

参考:

https://blog.csdn.net/cdy1996/article/details/80960215

docker化

zuul服务的dockerfile

FROM my-docker-registry:5000/java

ADD target/zuul_gateway-0.0.1-SNAPSHOT.jar /zuul.jar

EXPOSE 5555

CMD ["java", "-jar", "/zuul.jar"]

build之:

docker build -t zuul:0.0.1 .

注意:最后一个.不要省略,表示使用当前目录下的dockerfile。

最后拉起来:

docker run -d --network host zuul:0.0.1

因为zuul要访问宿主机器上的eureka,所以这里使用了docker的host网络。更好的做法是使用docker-compose。

与eureka的结合

我们使用docker-compose,把zuul和eureka link起来,这样可以不必使用docker的host网络。

version: "2.0"
services:
    eureka1:
        build: discovery
        expose:
            - 8761
        ports:
            - "8761:8761"    
            
    zuul1:
        build: gateway/zuul_gateway
        ports:
            - "8080:5555"      
        links:
            - eureka1

执行:

docker-compose up

注意:这里虽然用的是build,但docker-compose并不会傻乎乎的每次去重新构造docker镜像,一旦镜像首次建好,docker-compose会复用已存在的镜像。同理,若容器已存在,docker-compose也是复用已存在的容器。

eureka的注册方式引发的问题

docker化的情况下,zuul会在单独的容器里,这时如果同一台机器上待转发的服务使用localhost注册到eureka,zuul是无法转发的。解决方法是使用ip注册,因为zuul的容器里是能ping通宿主ip的,但默认无法ping通宿主hostname。使用ip注册,对于那些在容器里的服务也是一样有效的。

Zuul首次请求报SendErrorFilter的错误

只在做了“面向服务的配置”下才会出现。

有几个因素需考虑:

  1. Zuul内部使用了Ribbon实现负载均衡,而Ribbon默认是惰性加载的,这样可能导致首次请求较慢,触发了Hystrix熔断。
  2. Hystrix超时和Ribbon超时设置

使用“传统配置”则没有该问题,因为传统配置下,不会用到Ribbon、eureka和Hystrix。

解决方法:

  1. 设置Ribbon饥饿加载:
# avoid first call too slow
zuul.ribbon.eager-load.enabled=true
  1. 加大Hystrix超时和Ribbon超时(单位:毫秒)
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds= 11000

ribbon.ConnectTimeout= 10000
ribbon.ReadTimeout: 10000

理论依据:

If Zuul is using service discovery there are two timeouts you need to be concerned with, the Hystrix timeout (since all routes are wrapped in Hystrix commands by default) and the Ribbon timeout. The Hystrix timeout needs to take into account the Ribbon read and connect timeout PLUS the total number of retries that will happen for that service. By default Spring Cloud Zuul will do its best to calculate the Hystrix timeout for you UNLESS you specify the Hystrix timeout explicitly.
The Hystrix timeout is calculated using the following formula:
(ribbon.ConnectTimeout + ribbon.ReadTimeout) * (ribbon.MaxAutoRetries + 1) * 
(ribbon.MaxAutoRetriesNextServer + 1)

参考

https://stackoverflow.com/questions/55084722/zuulexception-senderrorfilter-at-first-call

https://www.jianshu.com/p/3124c536b92f

服务注册与发现

Eureka/consul 对比:

https://blog.csdn.net/ZYC88888/article/details/81453647

负载均衡策略

自定义eureka的负载均衡策略,可参看:

https://blog.csdn.net/jayjjb/article/details/71552861

sticky session

源码参考:

https://github.com/alejandro-du/vaadin-microservices-demo/blob/master/proxy-server/src/main/java/com/example/StickySessionRule.java

增加这个类后,还要在zuul的application.yml里配置NFLoadBalancerRuleClassName为该类类名:

scilab-mate:
  ribbon:
    NFLoadBalancerRuleClassName: com.lee.zuul_gateway.StickySessionRule

其中,scilab-mate是某个service-id。

eureka无法找到正确的服务地址

可能跟eureka清理服务节点不及时有关,无奈之下,只能重启eureka。

redis支持

application.yml文件:

spring:
  application:
    name: api-gateway 
  redis:
    host: redis
    port: 6379

代码里注入redisTemplate:

public class GatewayApplication {

	@Autowired
	private StringRedisTemplate redisTemplate;

注意:这里要使用StringRedisTemplate,才能正确的获得结果,否则取得的值都是null,原因见:

https://stackoverflow.com/questions/35783004/spring-data-redis-ping-works-key-is-there-no-data-returned

关键点摘录如下:

RedisOperations uses serializers to translate Java objects into Redis data structure values. The serializer defaults to JdkSerializationRedisSerializer. The JDK serializer translates your String object into a Java-serialized representation that is not compatible with ASCII or UTF-8. Check out the docs, you might be interested in StringRedisSerializer.

意思是你传的java参数会被序列化器转成redis数据结构,默认的序列化器转出的结果与UTF8不兼容,必须使用StringRedisSerializer或StringRedisTemplate.

redis连接报Connection reset by peer

具体错误信息是:

Redis exception; nested exception is io.lettuce.core.RedisException: java.io.IOException: Connection reset by peer

lettuce是一个redis的client library,就像jedis那样。

原因:

lettuce池底层使用一个共享连接,该连接永远不会关闭,因此默认也不会校验是否可用。一旦redis-server关闭tcp通道,客户端再去执行redis命令,自然就报Connection reset by peer错误。

解决方法:

客户端每次拿到connection后做个检查,看是否是合法的连接:

@Configuration
public class RedisConfig {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisConfig.class);

    @Autowired
    private RedisProperties redisProperties;

    @Bean
    public RedisConnectionFactory getConnectionFactory() {
        LOGGER.info("redis info host:{}, port:{}, db:{}", redisProperties.getHost(), redisProperties.getPort(),
                redisProperties.getDatabase());

        //standalone mode
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(redisProperties.getHost());
        configuration.setPort(redisProperties.getPort());

        //cluster mode
        //RedisClusterConfiguration configuration2 = new RedisClusterConfiguration();

        LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);

        // validate before use, avoid Connection reset by peer
        factory.setValidateConnection(true);

        //share native connection
        //factory.setShareNativeConnection(true);

        return factory;
    }
}

网上有文章说修改redis的服务端保活,修改redis.conf:

# A reasonable value for this option is 60 seconds.
tcp-keepalive 60

经试验,该方法并无效果。

spring session

默认的session是进程内的,这样request到了其他微服务就拿不出session了,除非有一个集中的地方统一存放,比如redis。spring session就提供了spring微服务间的session共享。但是,非spring微服务就享受不到这样的好处了,比如一个存在异构系统的集群中,可能还有flask、django服务。

这种情况下,我们可使用SSO+token方案。

资源占用优化

spring cloud每个微服务默认启动申请的内存很大,事实上完全不需要这么多,可通过JVM参数来限制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值