当前位置: 首页 > 知识库问答 >
问题:

为什么在使用spring数据的绝地武士时,数据会被存储在Redis中的奇怪的密钥中?

彭宏义
2023-03-14

我正在使用spring数据雷迪斯与绝地武士。我正在尝试使用键vc:${list_id}存储哈希。我能成功插入Redis。但是,当我使用redis-cli检查键时,我看不到键vc:501381。相反,我看到的是\xac\xed\x00\x05t\x00\tvc:501381

为什么会发生这种情况?我该如何改变这种情况?

共有2个答案

赫连黎昕
2023-03-14

我知道这个问题已经有一段时间了,但是最近我又对这个话题做了一些研究,所以我想在这里通过翻阅部分spring源代码来分享一下这个“半散列”密钥是如何生成的。

首先,spring利用AOP解析诸如@cacheable、@cacheevict或@cacheput等注释。advice类是来自spring上下文依赖的CacheInterceptor,它是CacheAspectSupport(也来自spring上下文)的子类。为了便于解释,我将使用@cacheable作为示例来浏览这里的部分源代码

当调用注释为@cacheable的方法时,AOP会将其路由到此方法受保护的集合<?从CacheAspectSupport类扩展Cache>getCaches(CacheOperationInvocationContext context,CacheResolver CacheResolver) ,它将尝试在其中解析此@Cacheable批注。反过来,它导致在实现CacheManager中调用此方法公共缓存getCache(String name)。对于这个解释,实现缓存的是rediscacheManager(来自spring-data-redis dependency)。

如果没有命中缓存,它将继续创建缓存。下面是RediscacheManager中的关键方法:

protected Cache getMissingCache(String name) {
    return this.dynamic ? createCache(name) : null;
}

@SuppressWarnings("unchecked")
protected RedisCache createCache(String cacheName) {
    long expiration = computeExpiration(cacheName);
    return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
            cacheNullValues);
}

本质上,它将实例化rediscache对象。为此,它需要4个参数,即cacheName、prefix(这是回答这个问题的关键参数)、redisOperation(aka,配置的redisTemplate)、expiration(默认值为0)和cacheNullValues(默认值为false)。下面的构造函数显示了关于rediscache的更多细节。

/**
 * Constructs a new {@link RedisCache} instance.
 *
 * @param name cache name
 * @param prefix must not be {@literal null} or empty.
 * @param redisOperations
 * @param expiration
 * @param allowNullValues
 * @since 1.8
 */
public RedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations,
        long expiration, boolean allowNullValues) {

    super(allowNullValues);

    Assert.hasText(name, "CacheName must not be null or empty!");

    RedisSerializer<?> serializer = redisOperations.getValueSerializer() != null ? redisOperations.getValueSerializer()
            : (RedisSerializer<?>) new JdkSerializationRedisSerializer();

    this.cacheMetadata = new RedisCacheMetadata(name, prefix);
    this.cacheMetadata.setDefaultExpiration(expiration);
    this.redisOperations = redisOperations;
    this.cacheValueAccessor = new CacheValueAccessor(serializer);

    if (allowNullValues) {

        if (redisOperations.getValueSerializer() instanceof StringRedisSerializer
                || redisOperations.getValueSerializer() instanceof GenericToStringSerializer
                || redisOperations.getValueSerializer() instanceof JacksonJsonRedisSerializer
                || redisOperations.getValueSerializer() instanceof Jackson2JsonRedisSerializer) {
            throw new IllegalArgumentException(String.format(
                    "Redis does not allow keys with null value ¯\\_(ツ)_/¯. "
                            + "The chosen %s does not support generic type handling and therefore cannot be used with allowNullValues enabled. "
                            + "Please use a different RedisSerializer or disable null value support.",
                    ClassUtils.getShortName(redisOperations.getValueSerializer().getClass())));
        }
    }
}

那么prefix在这个重排中有什么用呢?-->如构造函数about所示,在此语句this.cacheMetadata=new RedisCacheMetadata(name,prefix);中使用,下面的RedisCacheMetadata的构造函数显示了更多详细信息:

/**
     * @param cacheName must not be {@literal null} or empty.
     * @param keyPrefix can be {@literal null}.
     */
    public RedisCacheMetadata(String cacheName, byte[] keyPrefix) {

        Assert.hasText(cacheName, "CacheName must not be null or empty!");
        this.cacheName = cacheName;
        this.keyPrefix = keyPrefix;

        StringRedisSerializer stringSerializer = new StringRedisSerializer();

        // name of the set holding the keys
        this.setOfKnownKeys = usesKeyPrefix() ? new byte[] {} : stringSerializer.serialize(cacheName + "~keys");
        this.cacheLockName = stringSerializer.serialize(cacheName + "~lock");
    }

至此,我们知道一些前缀参数已经设置为rediscacheMetadata,但是这个前缀到底是如何在Redis中用来形成密钥的(例如您提到的\xac\xed\x00\x05t\x00\tvc:501381)?

基本上,CacheInterceptor随后将向前移动,从上述Rediscache对象调用方法私有RedisCacheKey getRedisCacheKey(对象键),该对象通过使用来自RediscacheMetadata的前缀和来自Redisoperation的keySerializer返回RedisCacheKey的实例。

private RedisCacheKey getRedisCacheKey(Object key) {
    return new RedisCacheKey(key).usePrefix(this.cacheMetadata.getKeyPrefix())
            .withKeySerializer(redisOperations.getKeySerializer());
}

达到这一点,CacheInterceptor的“pre”建议就完成了,它将继续执行@cacheable注释的实际方法。并且在完成实际方法的执行之后,它将执行cacheinterceptor的“post”建议,这实质上是将结果重新discache。下面是将结果放入redis缓存的方法:

public void put(final Object key, final Object value) {

    put(new RedisCacheElement(getRedisCacheKey(key), toStoreValue(value))
            .expireAfter(cacheMetadata.getDefaultExpiration()));
}

/**
 * Add the element by adding {@link RedisCacheElement#get()} at {@link RedisCacheElement#getKeyBytes()}. If the cache
 * previously contained a mapping for this {@link RedisCacheElement#getKeyBytes()}, the old value is replaced by
 * {@link RedisCacheElement#get()}.
 *
 * @param element must not be {@literal null}.
 * @since 1.5
 */
public void put(RedisCacheElement element) {

    Assert.notNull(element, "Element must not be null!");

    redisOperations
            .execute(new RedisCachePutCallback(new BinaryRedisCacheElement(element, cacheValueAccessor), cacheMetadata));
}

RediscacheputCallback对象内,其回调方法DoInRedis()实际上调用了一个方法来形成redis中的实际键,方法名为RediscacheKey实例中的GetKeyBytes()。下面展示了这种方法的详细内容:

/**
 * Get the {@link Byte} representation of the given key element using prefix if available.
 */
public byte[] getKeyBytes() {

    byte[] rawKey = serializeKeyElement();
    if (!hasPrefix()) {
        return rawKey;
    }

    byte[] prefixedKey = Arrays.copyOf(prefix, prefix.length + rawKey.length);
    System.arraycopy(rawKey, 0, prefixedKey, prefix.length, rawKey.length);

    return prefixedKey;
}

正如我们在getKeyBytes方法中看到的,它同时使用了原始键(在您的示例中为vc:501381)和前缀键(在您的示例中为\xac\xed\x00\x05t\x00\t)。

乔丁雨
2023-03-14

好的,搜索了一会儿,在http://java.dzone.com/articles/spring-data-redis上找到了帮助。

它的发生是因为Java连载。

redisTemplate的密钥序列化程序需要配置为StringRedisserializer,即如下所示:

<bean 
    id="jedisConnectionFactory" 
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" 
    p:host-name="${redis.server}" 
    p:port="${redis.port}" 
    p:use-pool="true"/>

<bean 
    id="stringRedisSerializer" 
    class="org.springframework.data.redis.serializer.StringRedisSerializer"/>

<bean 
    id="redisTemplate" 
    class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnectionFactory" 
    p:keySerializer-ref="stringRedisSerializer"
    p:hashKeySerializer-ref="stringRedisSerializer" 
/>

现在redis中的关键字是vc:501381

或者像@niconic所说的那样,我们也可以将默认序列化器本身设置为字符串序列化器,如下所示:

<bean 
    id="redisTemplate" 
    class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnectionFactory" 
    p:defaultSerializer-ref="stringRedisSerializer"
/>

这意味着我们所有的键和值都是字符串。但是请注意,这可能并不可取,因为您可能希望您的值不仅仅是字符串。

如果您的值是一个域对象,那么您可以使用jackson序列化器并配置一个序列化器,如下所述:

<bean id="userJsonRedisSerializer" class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer">
    <constructor-arg type="java.lang.Class" value="com.mycompany.redis.domain.User"/>
</bean>

并将模板配置为:

<bean 
    id="redisTemplate" 
    class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnectionFactory" 
    p:keySerializer-ref="stringRedisSerializer"
    p:hashKeySerializer-ref="stringRedisSerializer" 
    p:valueSerialier-ref="userJsonRedisSerializer"
/>
 类似资料:
  • 问题内容: 我正在使用Jedis的Spring Data Redis。我试图用key存储哈希。我能够成功插入Redis。但是,当我使用redis- cli检查密钥时,看不到密钥。相反,我看到了。 为什么会发生这种情况,我该如何更改? 问题答案: 好的,谷歌搜索了一段时间,并在http://java.dzone.com/articles/spring-data- redis 找到了帮助。 它的发生是

  • 在我的REST controllers Spring项目中,我想在Redis中存储会话信息。 在我的Application.Properties中,我定义了以下内容: 我还启用了Http Redis会话: 我终于有了一个这样的redis连接工厂: \xac\xx\x00\x05sr\x00\x0ejava.lang.long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x0

  • 我有一个使用线程(实际上是Spark)和Redis(绝地)的Scala程序。我为我的Redis操作定义了一个,其中我为连接定义了一个。我需要每个线程打开一个到Redis的连接,并与之并行工作 连接对象: 当我用一根线的时候,效果很好。但当多个线程使用它时,我会出错。起初,我在每个线程中得到了,其中“4”是一个随机字符() 然后从我尝试设置和,因为我也看到了,有时是redis。客户。绝地武士。例外。

  • 题目描述 Java 数组扩容问题:实现动态的给数组添加元素效果,实现对数组扩容 原始数组 int[] arr = {1,2,3} 增加的元素 4,直接放在数组的最后 arr = {1,2,3,4} 题目来源及自己的思路 定义 arr1 定义 arr2,比 arr1 的长度长 1 在 arr1 的长度内,把 arr1 的值赋值给 arr2 arr2 的最后一个位置赋值为 4,也就是要加入的数据 因为

  • 我的表单已提交,但未存储在表中。如果我对请求执行dd(),则数据在中,但当我执行save()时,它不会按预期工作。我想在backoffice表单上添加用户,仅包含姓名、电子邮件、用户类型和密码。 编辑:我将问题图像更改为代码,以便您更容易理解,很抱歉第一次尝试。编辑2:现在出现了更多的两件事,密码验证确认总是错误的,如果我跳过验证,则会出现以下错误: 对未定义方法App\User::forget(

  • 问题内容: 我有一个MySQL表,该表由大约一百万个纬度和经度组成,每行都有一个主键值。 我想通过Geohashing或lat和lon排序集将此表迁移到Redis。 有人这样做吗?您用于存储和查询数据的方法是什么(例如,在Google Maps的纬度/经度范围内查询数据)。 问题答案: 是的,它已经完成了(使用geohashing …) 例如,您可以检查Geodis程序包后面的数据结构(来自Dvi