zoukankan      html  css  js  c++  java
  • Jedis与Lua脚本结合

    使用Lua脚本的好处
       1、减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延和请求次数。

       2、原子性的操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。

       3、代码复用:客户端发送的脚步会永久存在redis中,这样,其他客户端可以复用这一脚本来完成相同的逻辑。

       4、速度快:见 与其它语言的性能比较, 还有一个 JIT编译器可以显著地提高多数任务的性能; 对于那些仍然对性能不满意的人, 可以把关键部分使用C实现, 然后与其集成, 这样还可以享受其它方面的好处。

        5、可以移植:只要是有ANSI C 编译器的平台都可以编译,你可以看到它可以在几乎所有的平台上运行:从 Windows 到Linux,同样Mac平台也没问题, 再到移动平台、游戏主机,甚至浏览器也可以完美使用 (翻译成JavaScript).

        6、源码小巧:20000行C代码,可以编译进182K的可执行文件,加载快,运行快。
     

    eval 命令
    (1)首先需要了解Redis eval 命令:
    eval 命令也会将脚本添加到脚本缓存中,但是它会立即对输入的脚本进行求值。
     
    eg
    EVAL script numkeys key [key ...] arg [arg ...]  
    script参数是一段Lua脚本程序,它会被运行在Redis服务器上下文中,这段脚本不必(也不应该)定义为一个Lua函数。
    numkeys参数用于指定键名参数的个数。
    键名参数 key [key ...] 从EVAL的第三个参数开始算起,表示在脚本中所用到的那些Redis键(key),这些键名参数可以在 Lua中通过全局变量KEYS数组,用1为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
    在命令的最后,那些不是键名参数的附加参数 arg [arg ...] ,可以在Lua中通过全局变量ARGV数组访问,访问的形式和KEYS变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)
     
    如:
    eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 username age jack 20

    一般java运行lua脚本,采用的也是类似上述表达式,后面描述

    (2)对于一段长的lua脚本,可以将脚本放在一个文件中,通过如下命令执行lua脚本

    $ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...

                  --eval,告诉redis-cli读取并运行后面的lua脚本
                   path/to/redis.lua,是lua脚本的位置
                   KEYS[1] KEYS[2],是要操作的键,可以指定多个,在lua脚本中通过KEYS[1], KEYS[2]获取
                   ARGV[1] ARGV[2],参数,在lua脚本中通过ARGV[1], ARGV[2]获取。

    注意: KEYS和ARGV中间的 ',' 两边的空格,不能省略。

    看下面例子:

    在如下文件夹中以一个lua脚本    jedisCallLuaTest.lua

    local key=KEYS[1local args=ARGV 
    //说明:设置一个key = userName,value=Jack,20s过期时间
    return redis.call("setex",key,unpack(args))

    去客户端获取:

     

    过期了。。。。


    EVALSHA命令
    将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。
    语法如下:
    redis 127.0.0.1:6379> EVALSHA sha1 numkeys key [key ...] arg [arg ...]

    参数说明:

    • sha1 : 通过 SCRIPT LOAD 生成的 sha1 校验码。
    • numkeys: 用于指定键名参数的个数。
    • key [key ...]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
    • arg [arg ...]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。

    eg:


    整合Jedis + lua

    (1)Jedis的使用
    创建Jedis对象,主要是用于单个redis
     
    public class RedisClient {
        private static JedisPool    jedisPool    = null;
        private static String        addr        = "127.0.0.1";
        private static int            port        = 6379;
        static {
            try {
                JedisPoolConfig config new JedisPoolConfig();
                // 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
                config.setBlockWhenExhausted(true);
                // 设置的逐出策略类名, 默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最 大空闲连接数)
                config.setEvictionPolicyClassName("org.apache.commons.pool2.impl.DefaultEvictionPolicy");
                // 是否启用pool的jmx管理功能, 默认true
                config.setJmxEnabled(true);
                // MBean ObjectName = new
                // ObjectName("org.apache.commons.pool2:type=GenericObjectPool,name="
                // + "pool" + i); 默认为"pool", JMX不熟,具体不知道是干啥的...默认就好.
                config.setJmxNamePrefix("pool");
                // 是否启用后进先出, 默认true
                config.setLifo(true);
                // 最大空闲连接数, 默认8个 
                config.setMaxIdle(8);
                // 最大连接数, 默认8个
                config.setMaxTotal(8);
                // 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,
                // 默认-1
                config.setMaxWaitMillis(-1);
                // 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
                config.setMinEvictableIdleTimeMillis(1800000);
                // 最小空闲连接数, 默认0
                config.setMinIdle(0);
                // 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
                config.setNumTestsPerEvictionRun(3);
                // 对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数
                // 时直接逐出,不再根据MinEvictableIdleTimeMillis判断 (默认逐出策略)
                config.setSoftMinEvictableIdleTimeMillis(1800000);
                // 在获取连接的时候检查有效性, 默认false
                config.setTestOnBorrow(false);
                // 在空闲时检查有效性, 默认false
                config.setTestWhileIdle(false);
                // 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
                config.setTimeBetweenEvictionRunsMillis(-1);
                jedisPool new JedisPool(config, addr, port, 3000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        public synchronized static Jedis getJedis() {
            try {
                if (jedisPool != null) {
                    Jedis resource = jedisPool.getResource();
                    return resource;
                } else {
                    return null;
                }
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        public static void close(final Jedis jedis) {
            if (jedis != null) {
                jedis.close();
            }
        }
        
        /*---------------------测试---------------------------*/
        public static void main(java.lang.String[] args) {
            Jedis jedis = RedisClient.getJedis();
            // do something
            RedisClient.testCallLua(jedis);
            RedisClient.close(jedis);
        }
        
        public static void testCallLua(Jedis jedis){
            String luaStr = "return {KEYS[1],KEYS[1],ARGV[1],ARGV[2]}";
            Object result = jedis.eval(luaStr, Lists.newArrayList("userName","age"), Lists.newArrayList("Jack","20"));
            System.out.println(result);
        }
    }

    运行结果:

    [userName, userName, Jack, 20]

    同理将工程中的lua文件加载成String,作为参数运行也是可行的。

    java: 该段代码图个方便,引用了guava.jar包

    /**
         * 调用lua脚本
         * @param jedis
         */
        public static void testCallLuaFile(Jedis jedis){
            String luaStr null;
            
            //带反斜杠,路径为classPath,不带反斜杠,路径为类的同一目录
            Reader r = new InputStreamReader(RedisClient.class.getResourceAsStream("/jedisCallLuaTest.lua"));
            try {
                luaStr = CharStreams.toString(r);
                Object result = jedis.eval(luaStr, Lists.newArrayList("userName"), Lists.newArrayList("20","Tom"));
                System.out.println(result);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    注意:getResourceAsStream()这可是好帮手,用到将文件内容加载成String,一定要想到他。CharStreams是guava.jar中的对象。

    Reader转String还有如下两种伎俩:(发散。。。。)

    @Test
    public void apcheIo() throws IOException{
        String luaStr=null;
        //带反斜杠,路径为classPath,不带反斜杠,路径为类的同一目录
        Reader r= new InputStreamReader(Reader2StrDemo.class.getResourceAsStream("/jedisCallLuaTest.lua"));
    
        luaStr = org.apache.commons.io.IOUtils.toString(r);
        System.out.println(luaStr);
    }
    
    @Test
    public void java8() throws IOException{
        String luaStr=null;
        String path = "F:\xxxx\ideaProjects\java8-pro\resource\jedisCallLuaTest.lua";
    
        luaStr = Files.lines(Paths.get(path),Charset.defaultCharset()).collect(Collectors.joining());
        System.out.println(luaStr);
    }

    运行结果:

  • 相关阅读:
    mouse_event,keybd_event
    构造自己的StringGrid
    BMP2JPG,JPG2BMP
    $J的用法
    一个简单的系统托盘程序
    构造自己的双向链表
    C++的iostream标准库介绍
    D3DXVec4Dot 函数
    3D游戏从头编之D3D(3)
    D3DXVec4Cross 函数
  • 原文地址:https://www.cnblogs.com/xiaozhuanfeng/p/10426501.html
Copyright © 2011-2022 走看看