redis详解

1、redis中发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis 客户端可以订阅任意数量的频道。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

2、redis事务

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

(1)事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

(2)事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。Redis事务能够保证原子性。EXEC命令会触发执行事务中的所有命令

一个事务从开始到执行会经历以下三个阶段:

a. 开始事务。

b. 命令入队。

c. 执行事务。

3、redis事务在springboot中的应用

错误的用法
/** * <h2>没有开启事务支持: 事务执行会失败</h2> * */ @Test public void testMultiFailure() { stringRedisTemplate.multi(); stringRedisTemplate.opsForValue().set("name", "qinyi"); stringRedisTemplate.opsForValue().set("gender", "male"); stringRedisTemplate.opsForValue().set("age", "19"); System.out.println(stringRedisTemplate.exec()); }

执行以上测试用例,会抛出如下的异常信息:

Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTI

这里给出的错误信息显示:在执行 EXEC 命令之前,没有执行 MULTI 命令。这很奇怪,我们明明在测试方法的第一句就执行了 MULTI。通过追踪 multi、exec 等方法,我们可以看到如下的执行源码(spring-data-redis):

public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) { Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it"); Assert.notNull(action, "Callback object must not be null"); RedisConnectionFactory factory = getRequiredConnectionFactory(); RedisConnection conn = null; try { // RedisTemplate 的 enableTransactionSupport 属性标识是否开启了事务支持,默认是 false if (enableTransactionSupport) { // only bind resources in case of potential transaction synchronization conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport); } else { conn = RedisConnectionUtils.getConnection(factory); } boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);

源码中已经给出了答案:由于 enableTransactionSupport 属性的默认值是 false,导致了每一个 RedisConnection 都是重新获取的。所以,我们刚刚执行的 MULTI 和 EXEC 这两个命令不在同一个 Connection 中。

设置 enableTransactionSupport 开启事务支持

解决上述示例的问题,最简单的办法就是让 RedisTemplate 开启事务支持,即设置 enableTransactionSupport 为 true 就可以了。测试代码如下:

/** * <h2>开启事务支持: 成功执行事务</h2> * */ @Test public void testMultiSuccess() { // 开启事务支持,在同一个 Connection 中执行命令 stringRedisTemplate.setEnableTransactionSupport(true); stringRedisTemplate.multi(); stringRedisTemplate.opsForValue().set("name", "qinyi"); stringRedisTemplate.opsForValue().set("gender", "male"); stringRedisTemplate.opsForValue().set("age", "19"); System.out.println(stringRedisTemplate.exec()); // [true, true, true] }
通过 SessionCallback,保证所有的操作都在同一个 Session 中完成更常见的写法仍是采用 RedisTemplate 的默认配置,即不开启事务支持。但是,我们可以通过使用 SessionCallback,该接口保证其内部所有操作都是在同一个Session中。测试代码如下:
/** * <h2>使用 SessionCallback, 在同一个 Redis Connection 中执行事务: 成功执行事务</h2> * */ @Test @SuppressWarnings("all") public void testSessionCallback() { SessionCallback<Object> callback = new SessionCallback<Object>() { @Override public Object execute(RedisOperations operations) throws DataAccessException { operations.multi(); operations.opsForValue().set("name", "qinyi"); operations.opsForValue().set("gender", "male"); operations.opsForValue().set("age", "19"); return operations.exec(); } }; // [true, true, true] System.out.println(stringRedisTemplate.execute(callback));

总结:我们在 SpringBoot 中操作 Redis 时,使用 RedisTemplate 的默认配置已经能够满足大部分的场景了。如果要执行事务操作,使用 SessionCallback 是比较好,也是比较常用的选择。

3、Redis事务管理

Redis事务允许在一次单独的步骤中执行一组命令,并且可以保证如下两个重要事项:

(1)Redis会将一个事务中的所有命令序列化,然后按顺序执行,Redis不可能在一个Redis事务

的执行过程中插入执行另一个客户端发出的请求。这样便能保证Redis将这些命令作为一个单独的隔离操作执行

(2)在一个Redis事务中,Redis要么执行其中的所有命令,要么什么都不执行,因此,Redis事务能够保证原子性

4、几个常见的命令使用

使用MULTI命令便可以进入一个Redis事务。这个命令的返回值总是OK。此时,用户可以发出多个Redis命令。Redis会将这些命令放入队列,而不是执行这些命令。一旦调用EXEC命令,那么Redis就会执行事务中的所有命令。相反,调用DISCARD命令将会清除事务队列,然后退出事务。DISCARD命令可以用来中止事务运行。在这种情况下,不会执行事务中的任何命令,并且会将Redis连接恢复为正常状态

5、在调用EXEC命令之前,如果命令的返回值是QUEUE字符串,那么就表示已经正确地将这个命令放入队列,如果将某个命令放入队列时发生错误,那么大多数客户端将会中止事务,并且丢弃这个事务。