Redis 入门浅谈

  • 1、什么是redis,为什么要使用redis?
  • 2、关于Redis 和 Jedis
  • 3、RedisTemplate
  • 4、关于 Redis 存储 Java 对象的问题
  • 5、关于SessionCallback 和 RedisCallback 接口
  • 6、redis的数据类型
  • 6.1 字符串(String)和 散列(Hash)
  • 6.2 列表(List)
  • 6.3 集合(Set)
  • 6.4 有序集合(Zset)
  • 7、关于Redis的事务问题


1、什么是redis,为什么要使用redis?

 

       首先redis是一种NoSql数据库,也被称为数据结构服务器。通俗点来讲,就是一种运行在内存的数据库。需要说明的是Redis是开源的,可基于内存也可持久化的键值数据库,支持多种语言API 。
 
       因为其是基于内存的,所以运行速度非常快,在速度上一般是常用关系性数据库的几倍到几十倍。通过测试,Redis可以在1s内完成10万次的读写,一般将常用的数据存储在Redis中,代替关系型数据库的查询访问,以提高网站性能。
 

2、关于Redis 和 Jedis

 
       Jedis就是与Redis连接的驱动。和Jedis类似的驱动还有Lettuce、Jredis和Srp,不过都不及Jedis 常用,或已被淘汰。
 
       在spring中提供了一个底层的RedisConnectionFactory接口,由该接口可以生成一个RedisConnection接口对象,而Redisconnection接口对象是对 Redis底层接口的封装。同时在Spring中提供了Jedisconnection对象,该对象封装了原有的 Jedis ( redis . clientsjedis . Jedis ) 对象,Jedisconnection 是Redisconnection 接口的实现类.具体可看下图:

Redis作为数据库是如何使用的 用redis当数据库_redis入门

创建RedisConnectionFactory

package com.gsb.manager.api.redis;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @ClassName: RedisConfig
 * @Description:
 * @Date:2019/4/26 11:14
 **/
@Configuration
public class RedisConfig {

     private RedisConnectionFactory connectionFactory = null;

     /**
      *  创建 RedisConnectionFactory 对象
      * @return
      */
     @Bean(name = "redisConnectionFactory")
     public RedisConnectionFactory innitRedisConnectionFactory() {
          if (this.connectionFactory != null)
               return this.connectionFactory;
          JedisPoolConfig poolConfig = new JedisPoolConfig();
          // 最大空闲数
          poolConfig.setMaxIdle(50);
          // 最大连接数
          poolConfig.setMaxTotal(80);
          // 最大等待毫秒数
          poolConfig.setMaxWaitMillis(2000);
          // 创建 Jedis 连接工厂
          JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);
          // 获取单机(服务器)的Redis配置
          RedisStandaloneConfiguration rCfg = connectionFactory.getStandaloneConfiguration();
          // IP
          connectionFactory.setHostName("192.168.11.141");
          // 端口
          connectionFactory.setPort(6379);
          // 密码m
          connectionFactory.setPassword("admin123456");
          this.connectionFactory = connectionFactory;
          return connectionFactory;
     }
}

 

3、RedisTemplate

       在结合Spring或SpringBoot 使用Redis时,我们使用最多的一个类应该是RedisTemplate了,该类对很多具体功能都做了封装。例如:RedisTemplate会自动从上面所说的RedisConnectionFactory工厂中获取连接,然后执行相关的Redis命令,最后关闭Redis连接。

 

4、关于 Redis 存储 Java 对象的问题

       Redis 是基于字符串存储的NoSql,而Java是基于对象的语言,所以对象是无法直接存储到redis中的,不过Java提供了序列化机制,使用中只要实现了java.io.Serializable接口,就代表类的对象能够进行序列化,通过将类对象进行序列化就能得到二进制字符串,redis便能将类对象以字符串进行存储。

       同时,Java也可以将那些二进制字符串通过反序列化转为对象。spring 提供了RedisSerializer接口,该接口中比较常用的是serialize方法,该方法能把可序列化的对象转换成二进制字符串。对于序列化,要注意的还有StringRedisSerializer和JdkSerializationRedisSerializer,其中JdkSerializationRedisSerializer 是 RedisTemplate默认的序列化器,对对象进行序列化和反序列化。StringRedisSerializer可以直接从RedisTemplate中获取。
 
示例代码:

package com.gsb.manager.api.redis;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @ClassName: RedisConfig
 * @Description:
 * @Date:2019/4/26 11:14
 **/
@Configuration
public class RedisConfig {

     private RedisConnectionFactory connectionFactory = null;

     /**
      *  创建 RedisConnectionFactory 对象
      * @return
      */
     @Bean(name = "redisConnectionFactory")
     public RedisConnectionFactory innitRedisConnectionFactory() {
          if (this.connectionFactory != null)
               return this.connectionFactory;
          JedisPoolConfig poolConfig = new JedisPoolConfig();
          // 最大空闲数
          poolConfig.setMaxIdle(50);
          // 最大连接数
          poolConfig.setMaxTotal(80);
          // 最大等待毫秒数
          poolConfig.setMaxWaitMillis(2000);
          // 创建 Jedis 连接工厂
          JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);
          // 获取单机(服务器)的Redis配置
          RedisStandaloneConfiguration rCfg = connectionFactory.getStandaloneConfiguration();
          // IP
          connectionFactory.setHostName("192.168.11.141");
          // 端口
          connectionFactory.setPort(6379);
          // 密码m
          connectionFactory.setPassword("admin123456");
          this.connectionFactory = connectionFactory;
          return connectionFactory;
     }

     /**
      * 创建 RedisTemplate
      * @return
      */
     @Bean(name = "redisTemplate")
     public RedisTemplate<Object, Object> initRedisTemplate() {
          RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
          // RedisTemplate 会自动初始化 StringRedisSerializer,所以这里直接取值获取
          RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
          // 设置字符串序列化器,这样Spring 就会把Redis 的 key 当作字符串处理了
          redisTemplate.setKeySerializer(stringRedisSerializer);
          redisTemplate.setHashKeySerializer(stringRedisSerializer);
          redisTemplate.setHashValueSerializer(stringRedisSerializer);
          redisTemplate.setConnectionFactory(innitRedisConnectionFactory());
          return redisTemplate;
     }

}

 

5、关于SessionCallback 和 RedisCallback 接口

 
       SessionCallback 和 RedisCallback 两个接口都是让RedisTemplate进行回调,通过它们可以在同一条连接上执行多个Redis命令,避免了执行一条redis命令创建一个连接的尴尬境地。

       对比SessionCallback 和 RedisCallback两个接口,SessionCallback对开发者比较友好,提供更为高级的API,可读性更优良,在实际开发中基本都会优先选择使用它。RedisCallback接口比较底层,需要处理的内容比较多,同时其可读性比较差
 
示例代码:

package com.gsb.manager.api.redis;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;

/**
 * @ClassName: CallbackExample
 * @Description:
 * @Date:2019/4/26 16:46
 **/
public class CallbackExample {

    /**
     * 使用RedisCallback
     * 需要处理底层的转换规则,如果不考虑改写底层,尽量不使用RedisCallback
     * @param redisTemplate
     */
    public void useRedisCallback(RedisTemplate redisTemplate) {
           redisTemplate.execute(new RedisCallback() {
               @Override
               public Object doInRedis(RedisConnection connection) throws DataAccessException {
                   connection.set("key_1".getBytes(), "value_1".getBytes());
                   connection.hSet("hash_1".getBytes(), "field".getBytes(), "hvalue_1".getBytes());
                   return null;
               }
           });
        }

    /**
     * 使用 SessionCallback
     * 高级友好接口,一般情况下优先使用 SessionCallback
     * @param redisTemplate
     */
    public void useSessionCallback(RedisTemplate redisTemplate) {
              redisTemplate.execute(new SessionCallback() {
                  @Override
                  public Object execute(RedisOperations operations) throws DataAccessException {
                      operations.opsForValue().set("key_1", "value_1");
                      operations.opsForHash().put("hash_1", "field", "hvalue");
                      return null;
                  }
              });
        }
        
}

 

6、redis的数据类型

这里主要说明Redis的5种数据类型的字符串(String)、散列(Hash)、列表(List)、集合(Set)、有序集合(Zset)。

6.1 字符串(String)和 散列(Hash)

字符串(String):
       string 是 redis 最基本的类型,一个 key 对应一个 value。二进制安全, redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。string 类型的值最大能存储 512MB。
 
散列(Hash):
       Redis 的 hash 是一个键值(key=>value)对集合。是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。每个 hash 可以存储 232 -1 键值对(40多亿)。

 
代码示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: RedisDataTypeController
 * @Description:
 * @Date:2019/4/26 17:43
 **/
@Controller
@RequestMapping("/redis")
public class RedisDataTypeController {

      @Autowired
      private StringRedisTemplate stringRedisTemplate;
      @Autowired
      private RedisTemplate redisTemplate;

      @RequestMapping("/stringAndHash")
      @ResponseBody
      public Map<String, Object> stringAndHash() {
          /** redis 赋值  String字符串类型 */
          redisTemplate.opsForValue().set("key_1", "value_1");
          /** 这里默认使用了JDK的序列化器,故redis 保存时不是整数,不能运算 */
          redisTemplate.opsForValue().set("int_key", "1");
          stringRedisTemplate.opsForValue().set("int_num", "1");
          /** 运算 */
          stringRedisTemplate.opsForValue().increment("int_num", 1);
          /** 获取底层 Jedis 连接 */
          Jedis jedis = (Jedis) stringRedisTemplate.getConnectionFactory()
                                        .getConnection().getNativeConnection();
          /** 减1操作,该命令RedisTemplate不支持,故要先获取底层的连接,然后操作 */
          jedis.decr("int_num");
          Map<String, String> hash = new HashMap<String, String>();
          hash.put("hash_key_1", "hash_value_1");
          hash.put("hash_key_2", "hash_value_2");
          hash.put("hash_key_3", "hash_value_3");
          /** 存储一个散列数据类型 */
          stringRedisTemplate.opsForHash().putAll("hash", hash);
          /** 新增一个字段 */
          stringRedisTemplate.opsForHash()
                  .put("hash", "hash_key_4", "hash_value_4");
          /** 绑定散列操作的key,便可以对同一个散列数据类型进行连续操作 */
          BoundHashOperations hashOperations =
                  stringRedisTemplate.boundHashOps("hash");
          /** 删除两个字段 */
          hashOperations.delete("hash_key_1", "hash_key_2");
          /** 新增一个字段 */
          hashOperations.put("hash_key_5", "hash_value_5");
          Map<String, Object> map = new HashMap<String, Object>();
          map.put("success", true);
          return map;
      }
}

6.2 列表(List)

列表(List):
       Redis的列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或尾部(右边)。列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。

 
代码示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName: RedisDataTypeController
 * @Description:
 * @Date:2019/4/26 17:43
 **/
@Controller
@RequestMapping("/redis")
public class RedisDataTypeController {

      @Autowired
      private StringRedisTemplate stringRedisTemplate;
      @Autowired
      private RedisTemplate redisTemplate;


      /**
       * 链表 List
       * @return
       */
      @RequestMapping("/list")
      @ResponseBody
      public Map<String, Object> listType() {
          /** 插入两个列表,注意他们在链表的顺序
           * 链表从左到右顺序分别为: v10、v8、v6、v4、v2 */
          stringRedisTemplate.opsForList().leftPushAll("list_1", "v2",
                   "v4", "v6", "v8", "v10");
          /** 链表从左到右顺序分别为: v1、v2、v3、v4、v5、v6 */
          /** 绑定list_2 链表操作 */
          BoundListOperations listOperations =
                  stringRedisTemplate.boundListOps("list_2");
          /** 从右边弹出一个成员 */
          Object result1 = listOperations.rightPop();
          /** 获取定位元素, Redis 从 0开始己算,这里值为 v2 */
          Object result2= listOperations.index(1);
          /** 从左边插入链表 */
          listOperations.leftPush("v0");
          /** 求链表长度 */
          Long size = listOperations.size();
          /** 求链表下标区间成员,整个链表下标范围为0 到size-1,这里不取最后一个元素 */
          List elements = listOperations.range(0, size-2);
          Map<String, Object> map = new HashMap<String, Object>();
          map.put("success", true);
          return map;
      }
}

6.3 集合(Set)

集合(Set):
       Redis 的 Set 是 String 类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 232 - 1(4294967295, 每个集合可存储40多亿个成员)。
 
sadd 命令:
       命令 :sadd key member 。添加一个 string 元素到 key 对应的 set 集合中,成功返回1,如果元素已经在集合中返回 0,如果 key 对应的 set 不存在则返回错误。如添加了两次,但根据集合内元素的唯一性,第二次插入的元素将被忽略。

代码示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @ClassName: RedisDataTypeController
 * @Description:
 * @Date:2019/4/26 17:43
 **/
@Controller
@RequestMapping("/redis")
public class RedisDataTypeController {

      @Autowired
      private StringRedisTemplate stringRedisTemplate;
      @Autowired
      private RedisTemplate redisTemplate;

    /**
     * 集合 Set
     * @return
     */
    @RequestMapping("/set")
      @ResponseBody
      public Map<String, Object> setType() {
          /** 这里v1重复两次,因为集合不允许重复,所以只插入5个成员到 set 中 */
          stringRedisTemplate.opsForSet().add("set_1", "v1", "v1",
                  "v2", "v3", "v4", "v5");
          stringRedisTemplate.opsForSet().add("set_2", "v2", "v4",
                  "v6", "v8", "v10");
          /** 绑定set_1 集合操作 */
          BoundSetOperations setOperations =
                  stringRedisTemplate.boundSetOps("set_1");
          /** 增加两个元素 */
          setOperations.add("v6", "v7");
          /** 删除两个元素 */
          setOperations.remove("v1", "v7");
          /** 返回所有元素 */
          Set set_1 = setOperations.members();
          /** 求成员数 */
          Long size = setOperations.size();
          /** 求交集 */
          Set inter = setOperations.intersect("set_2");
          /** 求交集,并且用新集合 inter 保存 */
          setOperations.intersectAndStore("set_2", "inter");
          /** 求差集 */
          Set diff = setOperations.diff("set_2");
          /** 求差集,并且用新集合 diff 保存 */
          setOperations.diffAndStore("set_2", "diff");
          /** 求并集 */
          Set union = setOperations.union("set_2");
          /** 求并集,并且用新集合 union 保存 */
          setOperations.unionAndStore("set_2", "union");
          Map<String, Object> map = new HashMap<String, Object>();
          map.put("success", true);
          return map;
      }
}

6.4 有序集合(Zset)

有序集合(Zset):
       Redis 的 zset 和 set 一样也是 String 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
 
zadd 命令:
       命令:zadd key score member 。添加元素到集合,元素在集合中存在则更新对应score

代码示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;

import java.util.*;

/**
 * @ClassName: RedisDataTypeController
 * @Description:
 * @Date:2019/4/26 17:43
 **/
@Controller
@RequestMapping("/redis")
public class RedisDataTypeController {

      @Autowired
      private StringRedisTemplate stringRedisTemplate;
      @Autowired
      private RedisTemplate redisTemplate;

     /**
      * 有序集合 Zset
      * @return
      */
      @RequestMapping("/zset")
      @ResponseBody
      public Map<String, Object> zsetType() {
          Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>();
          for (int i=1; i<=9; i++) {
              // 分数
              double score = i*0.1;
              // 创建一个TypedTuple对象,存入值和分数
              ZSetOperations.TypedTuple<String> typedTuple =
                      new DefaultTypedTuple<String>("value"+i, score);
              typedTupleSet.add(typedTuple);
          }
          // 往有序集合插入元素
          stringRedisTemplate.opsForZSet().add("zset_1", typedTupleSet);
          // 绑定 zset_1 有序集合操作
          BoundZSetOperations<String, String> zSetOperations =
                  stringRedisTemplate.boundZSetOps("set_1");
          // 增加一个元素
          zSetOperations.add("value10", 0.27);
          Set<String> setRange = zSetOperations.range(1, 6);
          // 按分数排序获取有序集合
          Set<String> setScore = zSetOperations.rangeByScore(0.2, 0.6);
          // 定义值范围
          RedisZSetCommands.Range range = new RedisZSetCommands.Range();
          range.gt("value3");      // 大于 value3
          // range.gte("value3");  // 大于等于value3
          // range.lt("value8");   // 小于value8
          range.lte("value8");     // 小于等于value8
          // 按值排序,注意这个排序是按字符串排序
          Set<String> setLex = zSetOperations.rangeByLex(range);
          // 删除元素
          zSetOperations.remove("value9", "value2");
          // 求分数
          Double score = zSetOperations.score("value8");
          // 在下标区间下,按分数排序,同时返回value 和 score
          Set<ZSetOperations.TypedTuple<String>> scoreSet =
                  zSetOperations.rangeByScoreWithScores(1, 6);
          // 按从大到小排序
          Set<String> reverseSet = zSetOperations.reverseRange(2, 8);
          Map<String, Object> map = new HashMap<String, Object>();
          map.put("success", true);
          return map;
      }
}

 

7、关于Redis的事务问题

       Redis虽是NoSql型数据库,但是也支持事务。前面说过,在一个Redis连接中执行多个命令,一般考虑使用sessionBackcall来达到目的。同时,在Redis中使用事务,通常的命令组合是watch…multi…exec.下面仔细讲解一下watch…multi…exec.

 

       watch 命令 : 可以监控Redis的一些键(key值);

 

       multi 命令 : 是开始事务,这里开始事物后客户端的命令并不会马上执行,而是被存放在一个队列中。这里需要在注意,这种情况下我们执行一些返回数据的命令,Redis也是不会马上执行,而是将命令放在一个队列中,所以此时调用Redis的命令,结果都是返回null.

 

       exec 命令 : 是执行事务。但是需要注意的是,它在队列命令执行前会判断被watch监控的Redis的键的数据是否发生过变化,即便是赋予与之前相同的值也会被判定为是改变过。如果它判定发生了改变,那Redis会取消事务,否则就会执行事务。Redis在执行事务时,只会全执行或全部不执行,而且不会被其他客户端打断,这样做的目的就是保证了Redis事物下数据的一致性。

 

       下面是Redis事物执行的流程图:

Redis作为数据库是如何使用的 用redis当数据库_redis入门_02

示例代码:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName: RedisTransactionController
 * @Description:
 * @Date:2019/4/28 9:14
 **/
@Controller
public class RedisTransactionController {

     @Autowired
     private RedisTemplate redisTemplate;

    /**
     * Redis 事务示例
     * @return
     */
     @RequestMapping("/multi")
     @ResponseBody
     public Map<String, Object> executeMulti() {
         redisTemplate.opsForValue().set("key_1", "value_1");
         List list = (List)redisTemplate.execute(new SessionCallback() {
                 @Override
                 public Object execute(RedisOperations operations)
                         throws DataAccessException {
                     /** 设置要监控的 key 值 */
                     operations.watch("key_1");
                     /** 开启事务,在exec命令执行前,全部都只是进入到队列中 */
                     operations.multi();
                     operations.opsForValue().set("key_2", "value_2");
                     // operations.opsForValue().increment("key_1", 1); // 这里是特殊说明A步
                     /** 这里获取值将为 null,因为 Redis 只是把命令放入队列 */
                     Object value2 = operations.opsForValue().get("key_2");
                     operations.opsForValue().set("key_3", "value_3");
                     operations.opsForValue().set("key_4", "value_4");
                     /** 执行 exec 命令,会先判断key_1是否在监控后被修改过,
                      * 如果是则不执行事物,否则执行事物 */
                     return operations.exec();
                 }
             }
         );
         System.out.println(list);
         Map<String, Object> map = new HashMap<String, Object>();
         map.put("success", true);
         return map;
     }
}

需要注意的是:
 
       代码示例中,特殊说明A步,如果注释取消,让代码运行,因为key_1是一个字符串,故这里的代码是对字符串加1,会运行出错抛出异常。然后我们去Redis服务器查询key_2和key_3的值,会发现他们均已经有值。
       由此可以看出Redis事务是先让命令进入队列,所以一开始它并没有检测到运算命令是否能成功,只有在exec命令执行时,才能发现错误。对于错误的命令Redis只会报出错误,而错误后面的命令依然会被执行,所以key_2和key_3均存在数据。该点务必要注意,是Redis和数据库事物不一样的地方。

如有偏差部分,请大神指点交流