一、Redis简介

Redis是一种非关系型(K-V)数据库,也叫做缓存数据库,类似于Memcached ,但有着以下优点:
(1)Redis不仅仅支持简单的字符串类型的K-V数据,还支持hash 、list、set、zset等多种数据类型。
(2)Redis支持 master-slave(主—从)模式应用,memcache 支持分布式
(3)Redis支持数据的备份,即master-slave模式的数据备份
(3)Redis支持数据的持久化,也就是缓存的数据不仅能放在缓存中,还能放到磁盘中。memcache 一旦断电,数据全部丢失, redis 可以利用快照和 AOF 把数据存到磁盘中,当恢复时又从磁盘中读取到内存中,当 物理内存使用完毕后,可以把数据写入到磁盘中。
(4)Redis的过期策略不同,memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10

二、Redis缓存技术的本质

Redis的调用过程实际上是客户端像Redis服务器发送网络通信,然后Redis收到请求,并将结果返回给客户端的过程

Redis的Java的客户端在发送请求时,又分为三层

(1)传输层(transfer),用套接字socket进行通讯

(2)消息协议层(message),发送的消息体

(3)api,jedis提供的的各种调用redis服务器的方法

jedis跟spring版本 jedis和redistemplate区别_jedis

三、Spring集成Redis的两种方式

3.1 Jedis(Redis官方推荐的集成方式)

示例来自Redis官网:https://github.com/xetorthio/jedis

集成步骤:
1.添加依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.10.2</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>

最简单Jedis的使用方法

Jedis jedis = new Jedis("localhost",6379);
jedis.set("foo", "bar");
String value = jedis.get("foo");

可以很容易的想到输出 “bar”
打开源码看一下

public Jedis(final String host, final int port) {
    super(host, port);
  }
public class BinaryJedis implements BasicCommands, BinaryJedisCommands, MultiKeyBinaryCommands,
    AdvancedBinaryJedisCommands, BinaryScriptingCommands, Closeable {
  protected Client client = null;
  public BinaryJedis(final String host, final int port) {
    client = new Client(host, port);
  }
  }
public class Connection implements Closeable {

  private static final byte[][] EMPTY_ARGS = new byte[0][];

  private String host = Protocol.DEFAULT_HOST;
  private int port = Protocol.DEFAULT_PORT;
  private Socket socket;
  private RedisOutputStream outputStream;
  private RedisInputStream inputStream;
  private int pipelinedCommands = 0;
  private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
  private int soTimeout = Protocol.DEFAULT_TIMEOUT;
  private boolean broken = false;
  private boolean ssl;
  private SSLSocketFactory sslSocketFactory;
  private SSLParameters sslParameters;
  private HostnameVerifier hostnameVerifier;

  public Connection() {
  }

  public Connection(final String host) {
    this.host = host;
  }

  public Connection(final String host, final int port) {
    this.host = host;
    this.port = port;
  }

结合上述代码,我们在初始化Jedis的时候,实际上是先初始化与Redis的连接Connection,但是还没有发起连接。
下面调用Jedis的set方法:

public String set(final String key, String value) {
    checkIsInMultiOrPipeline();
    client.set(key, value);
    return client.getStatusCodeReply();
  }
@Override
  public void set(final String key, final String value) {
    set(SafeEncoder.encode(key), SafeEncoder.encode(value));
  }
public void set(final byte[] key, final byte[] value) {
    sendCommand(Command.SET, key, value);
  }

调用jedis.set(),先将key和value转化为字节数组bete[] key和 byte[] value,调用sendCommand()方法

消息协议层 - sendCommand()方法,此方法定义了与Redis发送请求的消息体Message
protected Connection sendCommand(final Command cmd, final byte[]... args) {
    try {
      connect();
      Protocol.sendCommand(outputStream, cmd, args);
      pipelinedCommands++;
      return this;
    } catch (JedisConnectionException ex) {
      /*
       * When client send request which formed by invalid protocol, Redis send back error message
       * before close connection. We try to read it to provide reason of failure.
       */
      try {
        String errorMessage = Protocol.readErrorLineIfPossible(inputStream);
        if (errorMessage != null && errorMessage.length() > 0) {
          ex = new JedisConnectionException(errorMessage, ex.getCause());
        }
      } catch (Exception e) {
        /*
         * Catch any IOException or JedisConnectionException occurred from InputStream#read and just
         * ignore. This approach is safe because reading error message is optional and connection
         * will eventually be closed.
         */
      }
      // Any other exceptions related to connection?
      broken = true;
      throw ex;
    }
  }

分析:
(1)在调用set方法的时候,去发起连接,为了频繁建立连接耗费资源,先检验当前是否已连接,如果未连接,发送Socket,并初始化当前Connection的outputStream和inputStream

public void connect() {
    if (!isConnected()) {
      try {
        socket = new Socket();
        // ->@wjw_add
        socket.setReuseAddress(true);
        socket.setKeepAlive(true); // Will monitor the TCP connection is
        // valid
        socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
        // ensure timely delivery of data
        socket.setSoLinger(true, 0); // Control calls close () method,
        // the underlying socket is closed
        // immediately
        // <-@wjw_add

        socket.connect(new InetSocketAddress(host, port), connectionTimeout);
        socket.setSoTimeout(soTimeout);

      /** 省略 **/

        outputStream = new RedisOutputStream(socket.getOutputStream());
        inputStream = new RedisInputStream(socket.getInputStream());
      } catch (IOException ex) {
        broken = true;
        throw new JedisConnectionException(ex);
      }
    }
  }

(2)sendCommand(Command.SET, key, value)方法,可以看到在调用set的方法的时候,使用的是枚举类Command.SET

public static enum Command {
    PING, SET, GET, QUIT, EXISTS, DEL, TYPE, FLUSHDB, KEYS, RANDOMKEY, RENAME, RENAMENX, RENAMEX, DBSIZE, EXPIRE, EXPIREAT, TTL, SELECT, MOVE, FLUSHALL, GETSET, MGET, SETNX, SETEX, MSET, MSETNX, DECRBY, DECR, INCRBY, INCR, APPEND, SUBSTR, HSET, HGET, HSETNX, HMSET, HMGET, HINCRBY, HEXISTS, HDEL, HLEN, HKEYS, HVALS, HGETALL, RPUSH, LPUSH, LLEN, LRANGE, LTRIM, LINDEX, LSET, LREM, LPOP, RPOP, RPOPLPUSH, SADD, SMEMBERS, SREM, SPOP, SMOVE, SCARD, SISMEMBER, SINTER, SINTERSTORE, SUNION, SUNIONSTORE, SDIFF, SDIFFSTORE, SRANDMEMBER, ZADD, ZRANGE, ZREM, ZINCRBY, ZRANK, ZREVRANK, ZREVRANGE, ZCARD, ZSCORE, MULTI, DISCARD, EXEC, WATCH, UNWATCH, SORT, BLPOP, BRPOP, AUTH, SUBSCRIBE, PUBLISH, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBSUB, ZCOUNT, ZRANGEBYSCORE, ZREVRANGEBYSCORE, ZREMRANGEBYRANK, ZREMRANGEBYSCORE, ZUNIONSTORE, ZINTERSTORE, ZLEXCOUNT, ZRANGEBYLEX, ZREVRANGEBYLEX, ZREMRANGEBYLEX, SAVE, BGSAVE, BGREWRITEAOF, LASTSAVE, SHUTDOWN, INFO, MONITOR, SLAVEOF, CONFIG, STRLEN, SYNC, LPUSHX, PERSIST, RPUSHX, ECHO, LINSERT, DEBUG, BRPOPLPUSH, SETBIT, GETBIT, BITPOS, SETRANGE, GETRANGE, EVAL, EVALSHA, SCRIPT, SLOWLOG, OBJECT, BITCOUNT, BITOP, SENTINEL, DUMP, RESTORE, PEXPIRE, PEXPIREAT, PTTL, INCRBYFLOAT, PSETEX, CLIENT, TIME, MIGRATE, HINCRBYFLOAT, SCAN, HSCAN, SSCAN, ZSCAN, WAIT, CLUSTER, ASKING, PFADD, PFCOUNT, PFMERGE, READONLY, GEOADD, GEODIST, GEOHASH, GEOPOS, GEORADIUS, GEORADIUSBYMEMBER, BITFIELD;

    public final byte[] raw;

    Command() {
      raw = SafeEncoder.encode(this.name());
    }

  }

(2)也就是不管我们使用Jedis的何种API,比如PING,SET,PUT指令,都去复用的SendCommandd的方法
根据传入的类型不同,拼装不同的指令

//字符串
  public static final byte DOLLAR_BYTE = '$';
  //长度
  public static final byte ASTERISK_BYTE = '*';
  //空字符串Simple Strings
  public static final byte PLUS_BYTE = '+';
  //Errors,报错信息 
  public static final byte MINUS_BYTE = '-';
  //Integers,数字
  public static final byte COLON_BYTE = ':';
public static void sendCommand(final RedisOutputStream os, final Command command,
      final byte[]... args) {
    sendCommand(os, command.raw, args);
  }

  private static void sendCommand(final RedisOutputStream os, final byte[] command,
      final byte[]... args) {
    try {
      os.write(ASTERISK_BYTE);
      os.writeIntCrLf(args.length + 1);
      os.write(DOLLAR_BYTE);
      os.writeIntCrLf(command.length);
      os.write(command);
      os.writeCrLf();

      for (final byte[] arg : args) {
        os.write(DOLLAR_BYTE);
        os.writeIntCrLf(arg.length);
        os.write(arg);
        os.writeCrLf();
      }
    } catch (IOException e) {
      throw new JedisConnectionException(e);
    }
  }

比如jedis,set(“wukong”,2018),就会输出

jedis跟spring版本 jedis和redistemplate区别_jedis_02

3.2 RestTemplate方式(比较常用)
配置方式同Jedis,只是使用RedisTemplate进行数据操作

2.2.1引入依赖

<dependency>  
    <groupid>org.springframework.data</groupid>  
     <artifactid>spring-data-redis</artifactid>  
     <version>1.7.2.RELEASE</version>  
 </dependency> 
   或者
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
@Configuration
@EnableCaching
public class RedisTemplateConfig extends CachingConfigurerSupport {

  /**
   * redisTemplate相关配置.
   *
   * @param factory 工厂类
   * @return
   */
  @Bean
  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);

    //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
        new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper objectMapper = new ObjectMapper();
    // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
    objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

    // 值采用json序列化
    template.setValueSerializer(jackson2JsonRedisSerializer);
    //使用StringRedisSerializer来序列化和反序列化redis的key值
    template.setKeySerializer(new StringRedisSerializer());

    // 设置hash key 和value序列化模式
    template.setHashKeySerializer(new StringRedisSerializer());
    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    template.afterPropertiesSet();

    return template;
  }
//测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RedisConfig.class})
public class RedisTest {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;   
    @Test
    public void testRedisObj() {
        Map<String, Object> properties = new HashMap<>();
        properties.put("123", "hello");
        properties.put("abc", 456);
        redisTemplate.opsForHash().putAll("hash", properties);
        Map<Object, Object> ans = redisTemplate.opsForHash().entries("hash");
        System.out.println("ans: " + ans);
    }
}

输出结果
ans: {123=hello, abc=456}

四、Jedis和Redis Template的区别

Jedis是Redis官方推荐的面向Java的操作Redis的客户端,而RedisTemplate是SpringDataRedis中对JedisApi的高度封装。

SpringDataRedis相对于Jedis来说可以方便地更换Redis的Java客户端,比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用如:SpringCache