jetcache 再一次踩坑

本贴最后更新于 782 天前,其中的信息可能已经时过境迁

先给我吐槽一下,简直就是玩我个 der,不过打铁还得自身硬,自己还是太菜了,玩个毛球哦。

故事

今天突然发现,哎,服务缓存失效了,具体原因就是因为 redis 数据库设置密码时候设置了个 @ 符号,好家伙,就是因为这个东西,加上 jetcache 采用 lettuce 进行连接,而在 lettuce 连接时采用解析 uri 的形式,但是 uri 解析时候 @ 会当作分割符号进行处理,这样就导致 redis 连接不上,之后看了源码后进行更改,更改后又给我报了一个 redis 不支持集群,哦豁,最后没得办法改用 redis 的 jedis 连接方式,果断放弃了 lettuce,问题得以解决。

问题一: lettuce 解析 uri 中的 @ 分割符

描述

在配置 jetcache 文件时候,由于 redis 数据库密码存在 @,由于是线上环境,不能更改,然后 jetcache 采用 lettuce 连接,解析 uri 时出错,导致连接失败,缓存失效。具体配置如下:

jetcache:
  statIntervalMinutes: 1 #统计间隔
  areaInCacheName: false
  local:
    default: #默认area
      type: caffeine
      keyConvertor: fastjson
      limit: 10000              #本地缓存最大个数
      defaultExpireInMillis: 10000   #缓存的时间全局 默认值
  remote:
    default:
      type: redis.lettuce #使用lettuce
      keyConvertor: fastjson
      valueEncoder: java
      valueDecoder: java
      poolConfig:
        minIdle: 1
        maxIdle: 50
        maxTotal: 1000
        maxWait: 1000
      uri: redis://xxx@xxx@ip:6379/0  #redis://密码@IP:端口/库

分析源码

先看源码(只拿出来关键的看):

package io.lettuce.core.cluster;
public class RedisClusterClient extends AbstractRedisClient {
  
    private final Iterable<RedisURI> initialUris;
  
    //传入uri,采用URI.create(uri)进行解析,获取到URIs的list集合
    public static RedisClusterClient create(String uri) {
        LettuceAssert.notEmpty(uri, "URI must not be empty");
        return create((Iterable)RedisClusterURIUtil.toRedisURIs(URI.create(uri)));
    }
    //之后开始调用create(Iterable<RedisURI> redisURIs)进行创建
    public static RedisClusterClient create(Iterable<RedisURI> redisURIs) {
        assertNotEmpty(redisURIs);
        assertSameOptions(redisURIs);
        return new RedisClusterClient((ClientResources)null, redisURIs);
    }
    //创建RedisClusterClient
    protected RedisClusterClient(ClientResources clientResources, Iterable<RedisURI> redisURIs) {
        super(clientResources);
        assertNotEmpty(redisURIs);
        assertSameOptions(redisURIs);
        this.initialUris = Collections.unmodifiableList(LettuceLists.newList(redisURIs));
        this.setDefaultTimeout(this.getFirstUri().getTimeout());
        this.setOptions(ClusterClientOptions.builder().build());
    }
    
}

解析 uri 源码:

package java.net;
public final class URI implements Comparable<URI>, Serializable{
    
    //解析字符串创建URI
    public static URI create(String str) {
        try {
            return new URI(str);
        } catch (URISyntaxException x) {
            throw new IllegalArgumentException(x.getMessage(), x);
        }
    }
    //构建URI
    public URI(String str) throws URISyntaxException {
        new Parser(str).parse(false);
    }
    
    
     private class Parser {
        //就是这里可以看出它采用@进行分割,
          // [<userinfo>@]<host>[:<port>]
        //
        private int parseServer(int start, int n)
            throws URISyntaxException
        {
            // userinfo
            q = scan(p, n, "/?#", "@");
            if ((q >= p) && at(q, n, '@')) {
                checkChars(p, q, L_USERINFO, H_USERINFO, "user info");
                userInfo = substring(p, q);
                p = q + 1;              // Skip '@'
            }

            // port
            if (at(p, n, ':')) {
              ...
            }
            if (p < n)
                failExpecting("port number", p);

            return p;
        }
     }
    
}

解决方式

既然是因为解析 URI 出现的问题,那么我们是不是重新实现一下就好了,于是我的解决方式是写一个 redisConfig 配置文件,通过直接调用 RedisClusterClient.create()来解决问题。

关键代码如下:

  @Bean
    public RedisClusterClient redisClient(){
        ArrayList<RedisURI> list = new ArrayList<>();
        RedisURI redisURI = new RedisURI();
        redisURI.setHost(host);
        redisURI.setPassword(password);
        redisURI.setPort(Integer.parseInt(port));
        redisURI.setDatabase(Integer.parseInt(database));
        list.add(redisURI);
        return RedisClusterClient.create(list);
    }

或者:
      @Bean
    public RedisClusterClient redisClient(){
        ArrayList<RedisURI> list = new ArrayList<>();
        RedisURI redisURI = new RedisURI();
        redisURI.setHost("127.0.0.1");
        redisURI.setPassword("XXXX@XXXX");
        redisURI.setPort(1234);
        list.add(redisURI);
        RedisClusterClient client = RedisClusterClient.create(list);
        client.setOptions(ClusterClientOptions.builder().
                disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
                .build());
        return client;
    }

完整实现:

@Configuration
public class JetCacheConfig{

    @Value(value = "${spring.redis.host}")
    private String host;

    @Value(value = "${spring.redis.port}")
    private String port;

    @Value(value = "${spring.redis.database}")
    private String database;

    @Value(value = "${spring.redis.password}")
    private String password;

    @Bean
    public RedisClusterClient redisClient(){
        ArrayList<RedisURI> list = new ArrayList<>();
        RedisURI redisURI = new RedisURI();
        redisURI.setHost(host);
        redisURI.setPassword(password);
        redisURI.setPort(Integer.parseInt(port));
        redisURI.setDatabase(Integer.parseInt(database));
        list.add(redisURI);
        return RedisClusterClient.create(list);
    }

    @Bean
    public SpringConfigProvider springConfigProvider() {
        return new SpringConfigProvider();
    }

    @Bean
    public GlobalCacheConfig config(SpringConfigProvider configProvider, RedisClusterClient redisClient){
        Map localBuilders = new HashMap();
        EmbeddedCacheBuilder localBuilder = LinkedHashMapCacheBuilder
                .createLinkedHashMapCacheBuilder()
                .keyConvertor(FastjsonKeyConvertor.INSTANCE);
        localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder);

        Map remoteBuilders = new HashMap();
        RedisLettuceCacheBuilder remoteCacheBuilder = RedisLettuceCacheBuilder.createRedisLettuceCacheBuilder()
                .keyConvertor(FastjsonKeyConvertor.INSTANCE)
                .valueEncoder(JavaValueEncoder.INSTANCE)
                .valueDecoder(JavaValueDecoder.INSTANCE)
                .redisClient(redisClient);
        remoteBuilders.put(CacheConsts.DEFAULT_AREA, remoteCacheBuilder);

        GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig();
        //globalCacheConfig.setConfigProvider(configProvider);//for jetcache <=2.5
        globalCacheConfig.setLocalCacheBuilders(localBuilders);
        globalCacheConfig.setRemoteCacheBuilders(remoteBuilders);
        globalCacheConfig.setStatIntervalMinutes(15);
        globalCacheConfig.setAreaInCacheName(false);

        return globalCacheConfig;
    }

}

就在这个时候我以为万事俱备只欠东风的时候,我一启动,哦豁,凉凉。

问题二、采用 lettuce 出现集群禁用问题

问题

redis 设计时候只想到了单机,并没考虑集群,导致 lettuce 进行 RedisClusterClient 连接时候出现 redis 不支持集群操作

io.lettuce.core.RedisCommandExecutionException: ERR This instance has cluster support disabled
    at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:135) ~[lettuce-core-5.1.7.RELEASE.jar:na]

看到网上的解决办法是修改 redis 配置文件,可是这是线上环境,不敢乱动,所以果断放弃,修改方式如下(未实验是否可行):

修改 redis 配置文件 redis.conf

去掉前面的#号
# cluster-enabled yes

解决方式

果断放弃 lettuce,采用 jedis 进行连接

jetcache:
  statIntervalMinutes: 15 #统计间隔
  areaInCacheName: false
  local:
    default: #默认area
      type: caffeine
      keyConvertor: fastjson
      limit: 1000              #本地缓存最大个数
      defaultExpireInMillis: 100000   #缓存的时间全局 默认值
  remote:
    default:
      type: redis
      keyConvertor: fastjson
      valueEncoder: java
      valueDecoder: java
      poolConfig:
        minIdle: 1
        maxIdle: 50
        maxTotal: 1000
        maxWait: 1000
      host: ${spring.redis.host}
      port: ${spring.redis.port}
      database: ${spring.redis.database}
      password: ${spring.redis.password}

区别在原来是 remote.default.type 是 redis.lettuce 现在直接用 redis

ps: 可能会出现

Caused by: java.lang.NoClassDefFoundError: redis/clients/util/Pool

这是由于 jedis 不存在或者是版本不匹配,自己引入就可以

    <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.0.0</version>
        </dependency>
  • JetCache
    1 引用
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3168 引用 • 8207 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...