Redis+Lua——他叫了外援

Stella981
• 阅读 570

    Redis从2.6版本开始引入对Lua脚本的支持,通过在Redis服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务端原子的执行多个Redis命令。

Lua

    Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

为什么用

刚需

  • 原子操作:Redis 使用单个 Lua 解释器去运行所有脚本,并且,Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。

优势

  • 高效且灵活:Redis的事务实现是基于观察者模式的check-and-set(乐观锁)。相对而言,引入Lua脚本的方式,可以轻松实现在事务方式中难以(无法)实现的业务。
  • 减少网络开销:合并多次执行命令的网络请求,用一个请求完成,减少了网络资源开销和响应时间。
  • 复用性:客户端发送的脚本会永久存储在Redis中,这意味着其他客户端可以复用这一脚本而不需要使用代码完成同样的逻辑。此特性基于 Script Load 命令。

应用

    比如有这样一个需求:每当有新的用户注册到我的平台,为其分配一个客户经理,要求每分配30次,切换一个客户经理,我们只有两个客户经理可用。这个注册服务部署了N个实例。你可以考虑一下如何使用Redis的事务实现。

JAVA实现

    /** 通过计数器获取客户经理姓名的lua脚本 */
    /** 客户经理资源被抽象成node(环形/单向) */
    static final String ASSIGN_ACCOUNT_MANAGER_LUA_SCRIPT = "local node1 = {next = nil, value = '王尼玛经理'}\n" +
            "local node2 = {next = node1, value = '王速卡经理'}\n" +
            "node1.next = node2\n" +
            "local currentNode = node1\n" +
            "\n" +
            "local currentAccountManagerkey = KEYS[1]\n" +
            "local currentAccountManager = redis.call('get', currentAccountManagerkey)\n" +
            "\n" +
            "if currentAccountManager then\n" +
            "    if currentNode.value ~= currentAccountManager then\n" +
            "        currentNode = currentNode.next\n" +
            "    end\n" +
            "else\n" +
            "    currentAccountManager = node1.value\n" +
            "end\n" +
            "\n" +
            "local counterKey = KEYS[2]\n" +
            "local counter = redis.call('incr', counterKey)\n" +
            "\n" +
            "if(tonumber(counter) == 30) then\n" +
            "    redis.call('set', counterKey, 0)\n" +
            "    redis.call('set', currentAccountManagerkey, currentNode.next.value)\n" +
            "end\n" +
            "\n" +
            "return currentAccountManager";
    /** 客户经理姓名key*/
    static final String CURRENT_ACCOUNT_MANAGER_KEY = "assignAccountManager:account_manager";
    /** 客户经理分配计数器key*/
    static final String COUNTER_KEY = "assignAccountManager:assign_counter";

jedis方式

@Component
public class JedisUtil {

    private JedisUtil() {
        
    }

    @Autowired
    private JedisPool jedisPool;    

     /**
     * 注册脚本。将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本
     * 如果给定的脚本已经在缓存里面了,那么不执行任何操作。
     * 在脚本被加入到缓存之后,通过 EVALSHA 命令,可以使用脚本的 SHA1 校验和来调用这个脚本。
     * 脚本可以在缓存中保留无限长的时间,直到执行 SCRIPT FLUSH 为止。
     * @param script 脚本字符串
     * @return 脚本 sha id
     */
    public String scriptLoad(String script) {
        Assert.hasLength(script, "parameter [script] must not be null");
        Jedis jedis = null;
        String  luaLoad = null;
        try {
            jedis = jedisPool.getResource();
            luaLoad  = jedis.scriptLoad(script);
            return luaLoad;
        } catch (Exception e) {

        } finally {
            if(jedis != null){
                jedis.close();
            }
        }
        return null;
    }

    /**
     * 根据给定的 sha1 校验码,执行缓存在服务器中的脚本
     * @param scriptShaId 脚本 sha id
     * @param keys key列表
     * @param args arg列表
     * @return 脚本执行结果
     */
    public Object evalsha(String scriptShaId, List<String> keys, List<String> args) {
        Assert.hasLength(scriptShaId, "parameter [scriptShaId] must not be null");
        Assert.notNull(keys, "parameter [keys] must not be null");
        Assert.notNull(args, "parameter [args] must not be null");
        Jedis jedis = null;
        Object  val = null;
        try {
            jedis = jedisPool.getResource();
            if (jedis.scriptExists(scriptShaId)) {
                val = jedis.evalsha(scriptShaId, keys, args);
                return val;
            }
        } catch (Exception e) {

        } finally {
            if(jedis != null){
                jedis.close();
            }
        }
        return null;
    }
}

    /**
     * 为新注册的用户分配客户经理
     * @return 客户经理姓名
     */
    public String assignAccountManagerForNewAccount() {
        List<String> keys = Arrays.asList(CURRENT_ACCOUNT_MANAGER_KEY, COUNTER_KEY);
        return (String) jedis.evalsha(jedis.scriptLoad(ASSIGN_ACCOUNT_MANAGER_LUA_SCRIPT), keys, Collections.<String>emptyList());
    }

RedisTemplate方式

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 为新注册的用户分配客户经理
     * @return 客户经理姓名
     */
    public String assignAccountManagerForNewAccount() {
        List<String> keys = Arrays.asList(CURRENT_ACCOUNT_MANAGER_KEY, COUNTER_KEY);
        // 指定 lua 脚本,并且指定返回值类型
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(ASSIGN_ACCOUNT_MANAGER_LUA_SCRIPT, String.class);
        // 参数一:redisScript,参数二:key列表
        return redisTemplate.execute(redisScript, keys);
    }    

相对于文字描述,代码和适量的注释可以直抒胸臆

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
2年前
java操作lua脚本
java操作lua脚本实例1.前言在上一篇文章Redis中使用Lua脚本来实现并发下的原子操作中我对Lua语言的一些简单的语法及其在Redis中的操作进行了介绍,但是在Java开发中我们还需要进一步的学习才能使这种技术落地。今天就结合SpringDataRedis这个我们经常使用的Redis开发组件来实际尝试一下Lua脚本。
可莉 可莉
2年前
041. 通过 Lua 扩展 Nginx
1\.ngx\_lua模块Nginx模块需要用C开发,而且必须符合一系列复杂的规则,最重要的用C开发模块必须要熟悉Nginx的源代码,使得开发者对其望而生畏。ngx\_lua模块通过将lua解释器集成进Nginx,可以采用lua脚本实现业务逻辑。该模块具有
Stella981 Stella981
2年前
Lua 与php 性能测试说明文档
Lua 与php性能测试说明文档测试环境   192.168.10.30获取同一物品信息  读取redis localhost:6379PhpnginxredisLuanginxredisngx\_lua将lua嵌入到nginx,让nginx执行lua脚本,高并
Stella981 Stella981
2年前
Redis 脚本
Redis脚本使用Lua解释器来执行脚本。Reids2.6版本通过内嵌支持Lua环境。执行脚本的常用命令为EVAL。语法Eval命令的基本语法如下:redis127.0.0.1:6379EVALscriptnumkeyskeykey...argarg...实例
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
2年前
041. 通过 Lua 扩展 Nginx
1\.ngx\_lua模块Nginx模块需要用C开发,而且必须符合一系列复杂的规则,最重要的用C开发模块必须要熟悉Nginx的源代码,使得开发者对其望而生畏。ngx\_lua模块通过将lua解释器集成进Nginx,可以采用lua脚本实现业务逻辑。该模块具有
Stella981 Stella981
2年前
Redis执行Lua脚本示例
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。2.原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。3.
Stella981 Stella981
2年前
Bypass ngx_lua_waf SQL注入防御(多姿势)
0x00前言ngx\_lua\_waf是一款基于ngx\_lua的web应用防火墙,使用简单,高性能、轻量级。默认防御规则在wafconf目录中,摘录几条核心的SQL注入防御规则:select.(from|limit)(?:(union(.?)select))(?:from\Winformation_schema\W)这边
Stella981 Stella981
2年前
Lua完全自学手册
Lua是一个小巧的脚本语言。是巴西里约热内卢天主教大学(PontificalCatholicUniversityofRiodeJaneiro)里的一个研究小组,由RobertoIerusalimschy、WaldemarCeles和LuizHenriquedeFigueiredo所组成并于1993年开发。其设计目的是为了嵌入应用
3A网络 3A网络
1年前
Lua 脚本在 Redis 事务中的应用实践
Lua脚本在Redis事务中的应用实践使用过Redis事务的应该清楚,Redis事务实现是通过打包多条命令,单独的隔离操作,事务中的所有命令都会按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务中的命令要