Redis事务
事务相关命令
- DISCARD
- 取消事务,放弃执行事务块内的使用指令。
- MULTI
- 标记一个事务块的开始。
- EXEC
- 执行索引事务块内的命令。
- WATCH
- 监视一个(或多个)key,如果事务执行之前这些key被其他命令所改动,那么事务将被打断。
- UNWATCH
- 取消WATCH命令对所有key的监视。
UNWATCH命令可以在WATCH命令执行之后,MULTI命令执行之前对连接进行重置,同样的DISCARD命令可以在MULTI命令执行之后,EXEC执行之前取消WATCH命令和清空已入队命令。
延迟执行事务有助于提升性能:因为Redis在执行事务的过程中,会延迟执行已入队的命令,直到客户端发送EXEC命令为止。因此绝大部分Redis客户端会等事务包含的所有命令出现了之后,才会一次性将MULTI命令与EXEC命令之间全部发送给Redis。这种“一次性发送多个命令,然后等待所有回复的出现的做法通常被称为流水线(pipelining),它可以通过减少客户端与Redis服务器之间的网络通信次数来提升Redis在执行多个命令时的性能。
Redis为什么没有实现加锁功能
因为Redis为了尽可能的减少客户端的等待时间,所以并不会在执行WATCH命令时,对数据进行加锁。相反的,Redis会在WATCH监控的数据变动时通知客户端,这种做法被称为乐观锁,而关系型数据库实际执行的加锁操作则为悲观锁,虽然实际中很有效果,但是它的缺点在于持所的客户端运行越慢,等待获取锁的客户端阻塞时间越长。所以说Redis只需在自己事务执行失败的时候进行重试就好了。
非事务型流水线
对于一次执行多个Redis命令,可以使用MULTI和EXEC包裹命令来使用流水线执行,降低通信次数和延迟,但是事务操作也会消耗资源,并可能导致其他重要命令被延迟执行。这时候可以使用非事务型流水线pipeline()操作,将多个命令包裹起来,统一发送给Redis执行,能减少客户端与Redis之间的通信往返次数,从而提高Redis的执行性能。
同样的操作,使用非事务型流水线和普通执行之间对比:
// 单条执行Redis命令
public void updateToken(Jedis conn, String token, String user, String item) {
long timestamp = System.currentTimeMillis() / 1000;
conn.hset("login:", token, user);
conn.zadd("recent:", timestamp, token);
if (item != null) {
conn.zadd("viewed:" + token, timestamp, item);
conn.zremrangeByRank("viewed:" + token, 0, -26);
conn.zincrby("viewed:", -1, item);
}
}
// 使用流水线包裹命令执行
public void updateTokenPipeline(Jedis conn, String token, String user, String item) {
long timestamp = System.currentTimeMillis() / 1000;
// 打包
Pipeline pipe = conn.pipelined();
pipe.multi();
pipe.hset("login:", token, user);
pipe.zadd("recent:", timestamp, token);
if (item != null){
pipe.zadd("viewed:" + token, timestamp, item);
pipe.zremrangeByRank("viewed:" + token, 0, -26);
pipe.zincrby("viewed:", -1, item);
}
// 执行
pipe.exec();
}
明显使用流水线比不使用的速度快几倍。
性能方面的注意事项
可以通过Redis附带的性能测试程序redis-benchmark
来了解Redis在自己服务器上的各种性能特征。