getset原子性 redis_Redis结合Lua脚本实现高并发原子性操作

从 2.6版本 起, Redis 开始支持 Lua 脚本 让开发者自己扩展 Redis …

案例-实现访问频率限制: 实现访问者 $ip 在一定的时间 $time 内只能访问 $limit 次.

非脚本实现

private boolean accessLimit(String ip, int limit, int time, Jedis jedis) {

boolean result = true;

String key = "rate.limit:" + ip;

if (jedis.exists(key)) {

long afterValue = jedis.incr(key);

if (afterValue > limit) {

result = false;

}

} else {

Transaction transaction = jedis.multi();

transaction.incr(key);

transaction.expire(key, time);

transaction.exec();

}

return result;

}

以上代码有两点缺陷

可能会出现竞态条件: 解决方法是用 WATCH 监控 rate.limit:$IP 的变动, 但较为麻烦;

以上代码在不使用 pipeline 的情况下最多需要向Redis请求5条指令, 传输过多.

Lua脚本实现

Redis 允许将 Lua 脚本传到 Redis 服务器中执行, 脚本内可以调用大部分 Redis 命令, 且 Redis 保证脚本的原子性:

首先需要准备Lua代码: script.lua

--

-- Created by IntelliJ IDEA.

-- User: jifang

-- Date: 16/8/24

-- Time: 下午6:11

--

local key = "rate.limit:" .. KEYS[1]

local limit = tonumber(ARGV[1])

local expire_time = ARGV[2]

local is_exists = redis.call("EXISTS", key)

if is_exists == 1 then

if redis.call("INCR", key) > limit then

return 0

else

return 1

end

else

redis.call("SET", key, 1)

redis.call("EXPIRE", key, expire_time)

return 1

end

Java

private boolean accessLimit(String ip, int limit, int timeout, Jedis connection) throws IOException {

List keys = Collections.singletonList(ip);

List argv = Arrays.asList(String.valueOf(limit), String.valueOf(timeout));

return 1 == (long) connection.eval(loadScriptString("script.lua"), keys, argv);

}

// 加载Lua代码

private String loadScriptString(String fileName) throws IOException {

Reader reader = new InputStreamReader(Client.class.getClassLoader().getResourceAsStream(fileName));

return CharStreams.toString(reader);

}

Lua 嵌入 Redis 优势:

减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输;

原子操作: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务;

复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用.

Lua模型

Lua是一种 便于嵌入应用程序 的脚本语言, 具备了作为通用脚本语言的所有功能. 其高速虚拟机实现非常有名(Lua的垃圾回收很有讲究- 增量垃圾回收 ), 在很多虚拟机系性能评分中都取得了优异的成绩. Home lua.org.

以嵌入式为方针设计的Lua, 在默认状态下简洁得吓人. 除了基本的数据类型外, 其他一概没有. 标注库也就 Coroutine、String、Table、Math、 I/O、OS, 再加上Modules包加载而已. 参考: Lua 5.1 Reference Manual - Standard Libraries(中文版: Lua 5.1 参考手册).

注: 本文仅介绍 Lua 与众不同的设计模型(对比 Java/C/C++、JavaScript、Python 与 Go), 语言细节可参考文内和附录推荐的文章以及Lua之父Roberto Ierusalimschy的(中文版: )

Base

1. 数据类型

作为通用脚本语言, Lua的数据类型如下:

数值型:

全部为浮点数型, 没有整型;

只有 nil 和 false 作为布尔值的 false , 数字 0 和空串(‘’/‘\0’)都是 true;

字符串

用户自定义类型

函数(function)

表(table)

变量如果没有特殊说明为全局变量(那怕是语句块 or 函数内), 局部变量前需加local关键字.

2. 关键字

3. 操作符

Tips:

数学操作符的操作数如果是字符串会自动转换成数字;

连接 .. 自动将数值转换成字符串;

比较操作符的结果一定是布尔类型, 且会严格判断数据类型('1' != 1);

函数(function)

在 Lua 中, 函数是和字符串、数值和表并列的基本数据结构, 属于第一类对象( first-class-object /一等公民), 可以和数值等其他类型一样赋给变量、作为参数传递, 以及作为返回值接收(闭包):

使用方式类似JavaScript:

-- 全局函数: 求阶乘

function fact(n)

if n == 1 then

return 1

else

return n * fact(n - 1)

end

end

-- 1. 赋给变量

local func = fact

print("func type: " .. type(func), "fact type: " .. type(fact), "result: " .. func(4))

-- 2. 闭包

local function new_counter()

local value = 0;

return function()

value = value + 1

return value

end

end

local counter = new_counter()

print

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值