欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 会展 > Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本

Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本

2024/12/22 5:23:20 来源:https://blog.csdn.net/qq_25308331/article/details/144542356  浏览:    关键词:Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本

键自动过期功能,可以让特定的键在指定时间之后自动被移除。

流水线功能允许客户端把任意多条Redis命令请求打包在一起,然后一次性将它们全部发送给服务器,服务器将这些命令都处理完毕后,会一次性地将它们的执行结果返回给客户端。

事务可以将多个命令打包成一个命令来执行,打包发送,打包执行。当事务成功执行时,事务中包含的所有命令都被执行,否则,所有命令都不执行。

Lua脚本是一个小巧的脚本语言,由C语言编写,目标是成为一个很容易嵌入其他语言中使用的语言。

1 自动过期

设置生存时间

EXPIRE key seconds

PEXPIRE key milliseconds

设置过期时间

EXPIREAT key timestamp

PEXPIREAT key milliseconds-timestamp

SET命令设置

SET key value [EX seconds | PX milliseconds | EXAT timestamp | PXAT milliseconds-timestamp]

获取剩余生存时间

(Time To Live,TTL)

TTL key

PTTL key

表 自动过期相关命令

1.1 示例

自动过期的登录会话:通过给会话令牌设置过期时间来让它在指定时间之后被移除,程序只需检查会话令牌是否存在,就能知道是否应该让用户重新登录了。

public class LoginSession {private final static Jedis jedis = RedisPool.getRedis();private final static String SESSION_KEY = "session_token_string::%s";private final static long DEFAULT_TIMEOUT = 2L;private static String generateToken() {return UUID.randomUUID().toString();}public static String saveToken(String userId) {String token = generateToken();jedis.setex(String.format(SESSION_KEY,userId),DEFAULT_TIMEOUT,token);return token;}public static void check(String userId,String token) {if(userId == null || token == null) throw new RuntimeException("用户id或token不能为空");String s = jedis.get(String.format(SESSION_KEY, userId));if (!token.equals(s)) throw new RuntimeException("token已失效");System.out.println("确认成功");}public static void main(String[] args) throws InterruptedException {String token = saveToken("123");Thread.sleep(1000);check("123",token);Thread.sleep(1500);check("123",token);}
}

2 流水线与事务

流水线可以显著降低网络通信次数,大幅度减少程序在网络通信方面耗费的时间,使得程序的执行效率得到显著的提示。

但是流水线不能保证命令被全部执行,而事务可以,事务打包发送,打包执行。

2.1 流水线

Redis不会限制客户端在流水线中包含的命令数量,却会为客户端的输入缓存区设置默认值为1GB的上限。当客户端发送的数据量超出这一限制时,Redis将强制关闭该客户端。

2.2 事务

开启事务

MULTI

执行事务

EXEC

放弃事务

DISCARD

表 事务相关命令

2.2.1 乐观锁

WATCH key [key ...]

对一个或多个键进行监控,如果客户端在尝试执行事务之前(MULTI之后,EXEC之前),这些键值发送了变化,那服务器将拒绝执行客户端发送的事务,并向它返回一个空值。

当事务执行完成后,会自动取消对这些键的监控。

UNWATCH [key ...]

取消对键值的监控,当没有指定键时,会取消对所有键的监控。

2.3 示例

带身份验证功能的锁:在获取锁时,直到删除这个锁的这段时间,锁键的值可能会发生变化,使用乐观锁区保证DEL命令只会在锁键的值没有发生任何变化的情况下执行。

public class IdentityLock {private final static String LOCK_KEY = "identity_lock_string";private final static Long DEFAULT_TIMEOUT = 5L;private final static Date START_TIME = new Date();public static boolean acquire(String user) {Jedis jedis = RedisPool.getRedis();SetParams setParams = new SetParams();setParams.ex(DEFAULT_TIMEOUT);setParams.nx();String set = jedis.set(LOCK_KEY, user, setParams);RedisPool.getRedisPool().returnResource(jedis);return "OK".equals(set);}public static void release(String user) {Jedis jedis = RedisPool.getRedis();jedis.watch(LOCK_KEY);String lock = jedis.get(LOCK_KEY);Transaction transaction = jedis.multi();try {if (user.equals(lock)) {transaction.del(LOCK_KEY);System.out.println(user + "释放锁");transaction.exec();} else {transaction.discard();}} catch (Exception e) {transaction.discard();} finally {transaction.close();jedis.unwatch();RedisPool.getRedisPool().returnResource(jedis);}}private static class UserThread extends Thread {private final String userId;private final int timeout;private UserThread(String userId, int timeout) {this.userId = userId;this.timeout = timeout;}@Overridepublic void run() {System.out.println(userId + "尝试获取锁:" + timeout);while (true) {if (acquire(userId)) {Date date = new Date();long second = (date.getTime() - START_TIME.getTime()) / 1000;System.out.println(userId + "获取锁成功:" + second + "s");break;}}try {Thread.sleep(timeout * 1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}release(userId);}}public static void main(String[] args) {RedisPool.getRedis().del(LOCK_KEY);Random random = new Random();for (int i = 0; i < 10; i++) {new UserThread(i+ "",random.nextInt(7) + 1).start();}}
}

3 Lua 脚本

Lua脚本从Redis2.6开始引入,它带来了两大变化:

  1. 使得用户可以按需对Redis服务器增加新功能,而不必修改Redis的源码。
  2. Redis服务器以原子方式执行Lua脚本。

3.1 执行脚本

EVAL script num_keys key [key ...] arg [arg ...]

script: 是脚本本身

num_keys: 脚本需要处理的键数量,后面跟的key 为键名,数量与num_keys一致。

arg: 指定传递给脚本的附加参数。

Lua 的数组起始索引是1.

图 EVAL 使用示例

3.1.1 使用脚本执行Redis命令

在脚本中调用redis.call()函数或redis.pcall()函数。这两个函数的参数相同(参数如上面示例所示),不同点在于处理错误的方式。前者在执行命令出错时会引发一个Lua错误,迫使EVAL命令向调用者返回一个错误;而后者会将错误包裹起来,并返回一个表示错误的Lua表格。

图 call与pcall处理错误的方式

3.1.2 值转换

带有小数部分的Lua数字将被转换为Redis整数回复,要想向Redis返回小数,着需要在Lua中,将这个小数转换为字符串。

图 Lua向Redis输出小数

3.2 缓存并执行脚本

在定义脚本后,程序通常会重复执行脚本,客户端每次执行脚本都需要将相同的脚本重新发送一次,会很浪费网络带宽。

SCRIPT LOAD script

将给定的脚本缓存在服务器中,并返回对应的SHA1校验和

EVALSHA sha1 num_keys key [key ...] arg [arg ...]

执行已被缓存的脚本。

图 缓存并执行脚本示例

3.2.1 脚本管理

检查脚本是否已被缓存

SCRIPT EXISTS sha1 [sha1 ...]

移除所有已缓存脚本

SCRIPT FLUSH [ASYNC | SYNC]

强制停止正在运行的脚本

SCRIPT KILL

表 脚本管理相关命令

使用KILL时,服务器由两种反应:

  1. 正在运行的Lua脚本尚未执行过任何写命令,那么服务器将终止该脚本,然后回到正常状态,继续处理客户端的命令请求。
  2. 如果正在运行的Lua脚本已经执行过写命令,为了防止这些脏数据被保存到数据库中,服务器不会直接终止脚本并回到正常状态,用户只能使用SHUTDOWN nosave命令,手动重启服务器。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com