0%

问题

k8singress使用的是ingress-nginx.

项目中用到了spring cloud gatewayAPI网关,部署环境有dockerk8s等. 测试出现一个问题:
请求的URL路径中如果出现了中文参数,如http://localhost:8080/test?param=你好,如果部署在docker或者裸机上,后台服务接收到的参数param你好,但是如果部署在k8s上,后台接收到的参数可能是UrlEncode之后的%E4%BD%A0%E5%A5%BD,这不是后台需要的. 虽然可以在后台通过URLDecoder.decode(name, "UTF-8")强制解码,不过比较麻烦.

分析

测试中发现,

  • 如果直接将后台服务部署到k8s上,通过service -> ingress暴露出来,不管是通过k8sClusterIP,如http://10.96.240.18:8080/test?param=你好, 还是通过ingress域名http://local.com/test?param=你好请求后台服务,后台能够正确接收到参数为你好,证明问题可能不是因为部署在k8s上造成的,只可能是spring cloud gatewayAPI网关原因;
  • 但是部署在docker或者裸机上,通过spring cloud gateway网关请求后台服务,后台能够正确接收到参数为你好,证明网关也是没有问题的,问题只可能是部署环境k8s的原因;
  • 如果将网关服务部署到k8s上,又有两种情况:
    • 通过ingress域名请求网关 -> 后台,后台接收到的参数是%E4%BD%A0%E5%A5%BD,看似是网关的问题;
    • 通过ClusterIP请求网关 -> 后台,后台接收到的参数是你好,这又证明网关没问题,看似是ingress的问题,但前面也证明了ingress可以正确传递中文字符,非常的奇怪!
  • spring cloud gateway中所有的Filter全部注释掉,发现通过ingress域名请求网关 -> 后台,后台也能正确的接收到中文参数,证明还是网关的问题,而且这个问题只有部署在k8s中搭配ingress-nginx使用时才会暴露.

解决

依次将注释掉的Filter加回去,证明问题出现在class XXXX extends ForwardedHeaderFilter这个类上.这个类用于设置网关跨域访问和网关本身接口权限验证.

测试发现,即使只注释成下面这样,还是会有问题,但是注释掉@Component,就没有问题.应该是super.filter(exchange, chain)父类这个方法有问题.

@Component
@Slf4j
public class AuthTokenFilter extends ForwardedHeaderFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        //跨域问题 接口权限验证 TODO
        return super.filter(exchange, chain);
    }
}

改成下面这样测试可以解决问题.

@Component
@Slf4j
public class AuthTokenFilter extends ForwardedHeaderFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        //跨域问题 接口权限验证 TODO
        return chain.filter(exchange);
    }
}

问题

spring cloud gateway集成了redis后,一直出现重连的日志,如下:

gateway        | 2020-06-28 10:56:11.133  INFO 1 --- [xecutorLoop-2-2] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.171.0.9/192.171.0.9:6379
gateway        | 2020-06-28 10:56:11.148  INFO 1 --- [llEventLoop-4-6] i.l.core.protocol.ReconnectionHandler    : Reconnected to 192.171.0.9:6379
gateway        | 2020-06-28 11:01:12.431  INFO 1 --- [xecutorLoop-2-3] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.171.0.9/192.171.0.9:6379
gateway        | 2020-06-28 11:01:12.441  INFO 1 --- [llEventLoop-4-8] i.l.core.protocol.ReconnectionHandler    : Reconnected to 192.171.0.9:6379
gateway        | 2020-06-28 11:06:13.231  INFO 1 --- [xecutorLoop-2-4] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.171.0.9/192.171.0.9:6379
gateway        | 2020-06-28 11:06:13.237  INFO 1 --- [llEventLoop-4-2] i.l.core.protocol.ReconnectionHandler    : Reconnected to 192.171.0.9:6379

分析

spring boot 2.0之后spring-boot-starter-data-redis默认不再使用jedis连接redis,而是lettuce.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <!-- 排除lettuce包,使用jedis代替-->
    <!--<exclusions>-->
        <!--<exclusion>-->
            <!--<groupId>io.lettuce</groupId>-->
            <!--<artifactId>lettuce-core</artifactId>-->
        <!--</exclusion>-->
    <!--</exclusions>-->
</dependency>
<!-- jedis -->
<!--<dependency>-->
    <!--<groupId>redis.clients</groupId>-->
    <!--<artifactId>jedis</artifactId>-->
    <!--<version>2.9.0</version>-->
<!--</dependency>-->

考虑是不是程序长时间没有使用redis,而导致连接断开.修改连接池最小空闲连接数:

spring.redis.host=localhost
spring.redis.password=
# 连接超时时间(毫秒)
spring.redis.timeout=10000
# Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
spring.redis.database=0
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0, 修改为1
spring.redis.lettuce.pool.min-idle=1

注意修改完之后需要增加新的依赖包,不然会报错

<!--配置lettuce.pool.min-idle需要增加的依赖-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
</dependency>

重启完程序之后,发现每5分钟打印重连日志依然存在.

解决

  • 这个不是错误,只是一个INFO级别的日志,可以日志级别调高,例如:
<logger name="io.lettuce.core.protocol" level="ERROR">
    <appender-ref ref="ERROR_FILE" />
</logger>
  • 原因:这是lettuce-core的实现里,有类似心跳机制的保持长连接方式,不过心跳机制是不停的来回发心跳包直到连接不可用再去被动重新连接,而lettuce的方案是将连接池里处于空闲(idle)状态的client每隔一段时间就主动断开,然后再重新连接。

参考链接

每日算法题【只出现一次的数字】

只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

题解:

  • 使用集合存储数字。遍历数组中的每个数字,如果集合中没有该数字,则将该数字加入集合,如果集合中已经有该数字,则将该数字从集合中删除,最后剩下的数字就是只出现一次的数字。
  • 使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。
  • 使用集合存储数组中出现的所有数字,并计算数组中的元素之和。由于集合保证元素无重复,因此计算集合中的所有元素之和的两倍,即为每个元素出现两次的情况下的元素之和。由于数组中只有一个元素出现一次,其余元素都出现两次,因此用集合中的元素之和的两倍减去数组中的元素之和,剩下的数就是数组中只出现一次的数字。
  • 最优解:位运算,数组中所有数依次做异或运算,最后得到的数就是只出现一次的数。
  1. 任何数和 0 做异或运算,结果仍然是原来的数
  2. 任何数和其自身做异或运算,结果是 0
  3. 异或运算满足交换律和结合律
0^0 = 0
1^0 = 1
0^1 = 1
1^1 = 0

问题

http://localhost:8080/test?name=管理这样参数存在中文情况,spring boot代码

public RoleResponse selectById(@RequestParam(value = "name", required = false) String name){
    return roleService.selectByName(name);
}

可能存在接收到的name%E7%AE%A1%E7%90%86这样的情况,这是浏览器自动为URL做了UrlEncode;

即使你的application.yml配置了UTF-8编码,也不一定能解决这样的问题:

# application.properties
server:
  tomcat:
    uri-encoding: UTF-8
spring:
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true

网上查找的方法一:【添加配置类】

@Configuration
public class CustomMVCConfiguration extends WebMvcConfigurerAdapter {
    @Bean
    public HttpMessageConverter<String> responseBodyConverter() {
        StringHttpMessageConverter converter = new StringHttpMessageConverter(
                Charset.forName("UTF-8"));
        return converter;
    }

    @Override
    public void configureMessageConverters(
            List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);
        converters.add(responseBodyConverter());
    }

    @Override
    public void configureContentNegotiation(
            ContentNegotiationConfigurer configurer) {
        configurer.favorPathExtension(false);
    }
}

测试并未解决问题!

方法二:【强制解码】

public RoleResponse selectById(@RequestParam(value = "name", required = false) String name) throws UnsupportedEncodingException {
    if (name != null){
        name = URLDecoder.decode(name, "UTF-8");
    }
    return roleService.selectByName(name);
}

测试可以解决问题!

命令 说明
build 构建或重建服务
help 命令帮助
kill 杀掉容器
logs 显示容器的输出内容
port 打印绑定的开放端口
ps 显示容器
pull 拉取服务镜像
restart 重启服务,指定服务
rm 删除停止的容器,指定服务
run 运行一个一次性命令
scale 设置服务的容器数目
start 开启服务,指定服务
stop 停止服务,指定服务
up 创建并启动容器,-d放到后台运行