在将Jedis与Spring Data一起使用时,为什么在Redis中使用奇怪的密钥存储数据?

aru*_*run 42 redis jedis spring-data

我正在使用Spring Data Redis和Jedis.我试图用密钥存储哈希vc:${list_id}.我能够成功插入redis.但是,当我使用redis-cli检查按键时,我看不到按键vc:501381.相反,我明白了\xac\xed\x00\x05t\x00\tvc:501381.

为什么会发生这种情况?如何更改?

aru*_*run 72

好的,谷歌搜索了一会儿,并在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" 
/>
Run Code Online (Sandbox Code Playgroud)

现在redis的关键是vc:501381.

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

<bean 
    id="redisTemplate" 
    class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnectionFactory" 
    p:defaultSerializer-ref="stringRedisSerializer"
/>
Run Code Online (Sandbox Code Playgroud)

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

如果你的价值是一个域对象,那么你可以使用杰克逊串行和配置所提到串行这里即是这样的:

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

并将模板配置为:

<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"
/>
Run Code Online (Sandbox Code Playgroud)

  • 如果更改所有序列化程序引用,则直接更改默认序列化程序可能很有用. (3认同)

Mit*_*ath 9

这是一个非常古老的问题,但我的回答可能对使用 Spring Boot使用Redis时遇到相同问题的人有所帮助。在 redis 中存储哈希类型数据时,我遇到了同样的问题。我已经为RedisTemplate编写了所需的配置文件更改。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.redis")
public class AppCofiguration {

    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory jedisConFactory = new JedisConnectionFactory();
        jedisConFactory.setHostName("127.0.0.1");
        jedisConFactory.setPort(6379);
        return jedisConFactory;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        final RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(jedisConnectionFactory());
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());

        // the following is not required      
        template.setHashValueSerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        return template;
    }

}
Run Code Online (Sandbox Code Playgroud)

如果数据类型是字符串template.setHashValueSerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());则不需要。


xxg*_*xxg 7

使用StringRedisTemplate来代替RedisTemplate.

默认情况下,RedisTemplate使用Java序列化,StringRedisTemplate使用StringRedisSerializer.

<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
Run Code Online (Sandbox Code Playgroud)


ima*_*ang 7

我知道这个问题已经有一段时间了,但我最近又对这个话题进行了一些研究,所以我想分享一下这个"半散列"密钥是如何通过这里的部分Spring源代码生成的.

首先,Spring利用AOP来解析类似的注释@Cacheable, @CacheEvict or @CachePut等.comnce类CacheInterceptor来自Spring-context依赖,它是CacheAspectSupport(也来自Spring-context)的子类.为了便于解释,我将@Cacheable以此为例介绍部分源代码.

当注释的方法@Cacheable被调用时,AOP会将它protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver)CacheAspectSupport类路由到此方法,在该方法中它将尝试解析此@Cacheable注释.反过来,它导致public Cache getCache(String name)在实现CacheManager时调用此方法.对于这个解释,实现CacheManage将是RedisCacheManager(来自Spring-data-redis依赖).

如果没有命中缓存,它将继续创建缓存.以下是关键方法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);
}
Run Code Online (Sandbox Code Playgroud)

本质上,它将实例化一个RedisCache对象.为此,它需要4个参数,即cacheName,prefix(这是回答此问题的关键参数),redisOperation(又名,配置的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())));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

那么prefix在这个RedisCache中有什么用?- >如构造函数中所示,它在此语句中使用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");
    }
Run Code Online (Sandbox Code Playgroud)

此时,我们知道有一些前缀参数已被设置为RedisCacheMetadata,但是这个前缀究竟是如何用于在Redis中形成密钥的(例如,如你所提到的\ xac\xed\x00\x05t\x00\tvc:501381)?

基本上,CacheInterceptor随后将继续前进以private RedisCacheKey getRedisCacheKey(Object key)从上述RedisCache对象调用方法,该方法RedisCacheKey通过利用来自keySerializer 的前缀返回实例.RedisCacheMetadataRedisOperation

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

到达这一点,"预"建议CacheInterceptor就完成了,它将继续执行注释的实际方法@Cacheable.在完成实际方法的执行后,它将执行"post"建议CacheInterceptor,这实际上将结果放到RedisCache中.下面是将结果放入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));
}
Run Code Online (Sandbox Code Playgroud)

RedisCachePutCallback对象中,它的回调方法doInRedis()实际上调用了一个方法来在redis中形成实际的键,方法名称getKeyBytes()来自RedisCacheKey实例.下面显示了此方法的详细信息:

/**
 * 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;
}
Run Code Online (Sandbox Code Playgroud)

正如我们在getKeyBytes方法中看到的,它使用原始密钥(在您的情况下为vc:501381)和前缀密钥(在您的情况下为\ xac\xed\x00\x05t\x00\t).