Redis 相关问题

281 阅读5分钟

Windows 使用 Redis

Windows环境下启动服务端

方法一

创建 .bat 文件
输入 redis-server.exe redis.windows.conf(配置文件名)
启动.bat文件

方法二

打开命令行,进入redis目录
输入redis-server.exe --service-install redis.windows.conf --Service-name RedisServerName --loglevel verbose
命令行输入redis-server --service-start --service-name RedisServerName

Windows环境下启动客户端

创建 .bat 文件
输入 redis-cli.exe -h 127.0.0.1 -p 6379
启动.bat文件

配置文件

配置文件行前一定不能有空格

#redis使用端口
port 6379

#允许所有人访问该redis,http://t.csdn.cn/lWXXw
bind 0.0.0.0

#日志文件地址,默认无日志
logfile "d:/redislog/redis.log"

#日志等级
loglevel notice

#快照持久化配置

#从最近一次创建快照后开始计算,如60秒内有1000次写入,则使用BGSAVE更新快照,配置多个save选项时,同时生效。
save 60 1000 

#是否在创建快照失败后停止写入
stop-writes-on-bgsave-error no

# rdbcompression 配置为 yes,那么即代表 redis 进行 RDB 文件生成中,如果遇到字符串对象并且其中的字符串值占用超过 20 个字节,那么就会对字符串进行 LZF 算法进行压缩。
rdbcompression yes

#RDB文件名
dbfilename filename.rdb

#rdbchecksum 配置 redis 是否使用 CRC64 校验算法校验 RDB 文件是否发生损坏,默认开启状态,如果你需要提升性能,可以选择性关闭。

rdbchecksum yes
################################################################

#AOF配置

#是否使用AOF持久化

appendonly no

#同步频率,有三个选项,always,no,everysec,对应每次,操作系统决定,每秒,建议每秒
appendfsync everysec

#aof文件名,默认是"appendonly.aof

 appenddilename aof文件名

#为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。
no-appendfsync-on-rewrite no

#aof自动重写配置,当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写
auto-aof-rewrite-percentage 100

#设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写。
auto-aof-rewrite-min-size 64mb

#如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。默认值为 yes。

aof-load-truncated yes

#aof和rdb文件目录

dir ./

Redis两种安装包

Redis两种安装包 - 掘金 (juejin.cn)

redisTemplate封装指令和redis connect原生指令混用导致json解析失败

redis connect原生指令在存储值时要注意转json格式,单行字符串不转json要记得两边加双引号,否则redisTemplate取数据json转对象时会解析失败。

存Long取出时是Integer及类似问题

项目中redis序列化配置组件统一使用

Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =newJackson2JsonRedisSerializer(Object.class);
Map<String, Long> objectMaps = redisTemplate.<String, Long>opsForHash().entries("hashkey");
spaceId = objectMaps.get(key)

会出BUG

 java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

一步一步看源码(DEBUG过程中不涉及到的代码会删除)
首先将redis中数据转成转成Map<byte[], byte[]>,然后反序列化。

class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> implements HashOperations<K, HK, HV> {

DefaultHashOperations(RedisTemplate<K, ?> template) {
   super((RedisTemplate<K, Object>) template);
}

AbstractOperations(RedisTemplate<K, V> template) {
   this.template = template;
}

public Map<HK, HV> entries(K key) {

   byte[] rawKey = rawKey(key);
   Map<byte[], byte[]> entries = execute(connection -> connection.hGetAll(rawKey));

   return entries != null ? deserializeHashMap(entries) : Collections.emptyMap();
}
}

反序列化代码
调用反序列代码时没有指定<HK, HV>,等效于返回了一个Map<>,JVM在编译时擦除泛型,HK, HV会替换成Object
问题出在下面的代码。
举个例子
下面一段代码(称为代码one)会绕过类型检查,不会抛出异常和警告,其中类A是上面class DefaultHashOperations的简写,类B是下面反序列化代码的简写,代码one这种编写方式使得A类调用B类时不会发出异常和警告,只会在Map拿出来数据时出现类型转换错误。

class  A {
   B=new B();
    @Test
    public void aa() {
        Map<String,Long> map=i.returnMap();
    }
}
class B{
    public<K,V> Map<K,V> returnMap(){
        return new HashMap<>();
    }
}

下面一段代码只会给出警告,在取值时会抛出异常。
但为什么在使用redis api时没有抛出警告?
首先,类似于代码one的编写方式不会有任何异常和警告,完全越过了类型检查。 其次,反序列化中很多方法上使用了@SuppressWarnings("unchecked")注解,这个注解会抑制警告,不是很理解为什么要抑制警告,很差劲的编写习惯。

HashMap hashMap=new HashMap<>();
hashMap.put(1521,"afdsdfa");
Map<String, Long> objectMaps = hashMap;
abstract class AbstractOperations<K, V> {
    @SuppressWarnings("unchecked")
    <HK, HV> Map<HK, HV> deserializeHashMap(@Nullable Map<byte[], byte[]> entries) {
        // connection in pipeline/multi mode
        if (entries == null) {
            return null;
        }

        Map<HK, HV> map = new LinkedHashMap<>(entries.size());

        for (Map.Entry<byte[], byte[]> entry : entries.entrySet()) {
            map.put((HK) deserializeHashKey(entry.getKey()), (HV) deserializeHashValue(entry.getValue()));
        }

        return map;
    }
 @SuppressWarnings("unchecked")
    <HK> HK deserializeHashKey(byte[] value) {
        return (HK) hashKeySerializer().deserialize(value);
    }
@SuppressWarnings("unchecked")
    <HV> HV deserializeHashValue(byte[] value) {
        return (HV) hashValueSerializer().deserialize(value);
    }

RedisSerializer hashKeySerializer() {
   return template.getHashKeySerializer();
}

RedisSerializer hashValueSerializer() {
   return template.getHashValueSerializer();
}
}

Jackson2JsonRedisSerializer中序列化类型在上文项目配置构造方法中指定为 Object.class ,除此之外提供没有别的途径指定反序列化后得到的类型。

public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {

private final JavaType javaType;
public Jackson2JsonRedisSerializer(Class<T> type) {
   this.javaType = getJavaType(type);
}
@SuppressWarnings("unchecked")
public T deserialize(@Nullable byte[] bytes) throws SerializationException {
      return (T) this.objectMapper.readValue(bytes, 0, bytes.length, javaType);
}
}
public class ObjectMapper extends ObjectCodec implements Versioned,java.io.Serializable 
{

public <T> T readValue(byte[] src, int offset, int len, JavaType valueType)
    throws IOException, StreamReadException, DatabindException
{
    return (T) _readMapAndClose(_jsonFactory.createParser(src, offset, len), valueType);
} 

protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
    throws IOException
{
    try (JsonParser p = p0) {
        final Object result;
        final DeserializationConfig cfg = getDeserializationConfig();
        final DefaultDeserializationContext ctxt = createDeserializationContext(p, cfg);
        JsonToken t = _initForReading(p, valueType);
        if (t == JsonToken.VALUE_NULL) {
        
        } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
        
        } else {
            result = ctxt.readRootValue(p, valueType,_findRootDeserializer(ctxt, valueType), null);
        }
        return result;
    }
  

}

public abstract class DefaultDeserializationContext extends DeserializationContext implements java.io.Serializable{
public Object readRootValue(JsonParser p, JavaType valueType,
        JsonDeserializer<Object> deser, Object valueToUpdate)
    throws IOException
{
    if (valueToUpdate == null) {
        return deser.deserialize(p, this);
    }
    return deser.deserialize(p, this, valueToUpdate);
}
}

到这一步,涉及变量类型的只有最初传进来的Obejct.class和下面构造方法定义的四种基本类型,再往下的代码看不懂,但基本可以断定如果没有指定特定类型就会转化为Boolean,Integer,Double,String。


public class AbstractDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer,  java.io.Serializable
{

private static final long serialVersionUID = 1L;

protected final JavaType _baseType;

protected final ObjectIdReader _objectIdReader;

protected final Map<String, SettableBeanProperty> _backRefProperties;

protected transient Map<String,SettableBeanProperty> _properties;

// support for "native" types, which require special care:

protected final boolean _acceptString;
protected final boolean _acceptBoolean;
protected final boolean _acceptInt;
protected final boolean _acceptDouble;

public AbstractDeserializer(BeanDeserializerBuilder builder,
        BeanDescription beanDesc, Map<String, SettableBeanProperty> backRefProps,
        Map<String, SettableBeanProperty> props)
{
    _baseType = beanDesc.getType();
    _objectIdReader = builder.getObjectIdReader();
    _backRefProperties = backRefProps;
    _properties = props;
    Class<?> cls = _baseType.getRawClass();
    _acceptString = cls.isAssignableFrom(String.class);
    _acceptBoolean = (cls == Boolean.TYPE) || cls.isAssignableFrom(Boolean.class);
    _acceptInt = (cls == Integer.TYPE) || cls.isAssignableFrom(Integer.class);
    _acceptDouble = (cls == Double.TYPE) || cls.isAssignableFrom(Double.class);
}

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
    throws IOException
{
    // 16-Oct-2016, tatu: Let's pass non-null value instantiator so that we will
    //    get proper exception type; needed to establish there are no creators
    //    (since without ValueInstantiator this would not be known for certain)
    ValueInstantiator bogus = new ValueInstantiator.Base(_baseType);
    return ctxt.handleMissingInstantiator(_baseType.getRawClass(), bogus, p,
            "abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information");
}

这是最底层,但我现在看不懂......

public abstract class DeserializationContext
    extends DatabindContext
    implements java.io.Serializable
{

public Object handleMissingInstantiator(Class<?> instClass, ValueInstantiator valueInst,
            JsonParser p, String msg, Object... msgArgs)
        throws IOException
    {
        if (p == null) {
            p = getParser();
        }
        msg = _format(msg, msgArgs);
        LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
        while (h != null) {
            // Can bail out if it's handled
            Object instance = h.value().handleMissingInstantiator(this,
                    instClass, valueInst, p, msg);
            if (instance != DeserializationProblemHandler.NOT_HANDLED) {
                // Sanity check for broken handlers, otherwise nasty to debug:
                if (_isCompatible(instClass, instance)) {
                    return instance;
                }
                reportBadDefinition(constructType(instClass), String.format(
"DeserializationProblemHandler.handleMissingInstantiator() for type %s returned value of type %s",
                    ClassUtil.getClassDescription(instClass),
                    ClassUtil.getClassDescription((instance)
                )));
            }
            h = h.next();
        }

        // 16-Oct-2016, tatu: This is either a definition problem (if no applicable creator
        //   exists), or input mismatch problem (otherwise) since none of existing creators
        //   match with token.
        // 24-Oct-2019, tatu: Further, as per [databind#2522], passing `null` ValueInstantiator
        //   should simply trigger definition problem
        if (valueInst == null ) {
            msg = String.format("Cannot construct instance of %s: %s",
                    ClassUtil.nameOf(instClass), msg);
            return reportBadDefinition(instClass, msg);
        }
        if (!valueInst.canInstantiate()) {
            msg = String.format("Cannot construct instance of %s (no Creators, like default constructor, exist): %s",
                    ClassUtil.nameOf(instClass), msg);
            return reportBadDefinition(instClass, msg);
        }
        msg = String.format("Cannot construct instance of %s (although at least one Creator exists): %s",
                ClassUtil.nameOf(instClass), msg);
        return reportInputMismatch(instClass, msg);
    }

缓存一致性

缓存和数据库一致性问题_止步前行的博客