Redis调用Lua脚本并测试

一、为什么使用Lua脚本

为了一次通信执行多个Redis命令,我们可以用pipline ,但是多个命令间没有逻辑联系 。

Lua脚本可以一次通信执行多个Redis命令,而且内部可以写自己的逻辑,整个脚本执行是原子性的。

二、命令行调用Lua脚本

EVAL script numkeys key [key ...] arg [arg ...]

redis 127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

三、Lua脚本文件执行

1)保存脚本文件为ip_control.lua,用作IP控制的脚本

local times = redis.call('incr',KEYS[1])
if times == 1 then
    redis.call('expire',KEYS[1], ARGV[1])
end
if times > tonumber(ARGV[2]) then
    return 0
end
return 1

2)redis客户端执行脚本

多次执行此脚本则会触发限流,返回值为0
redis-cli -h localhost -p 6381 --eval /root/ip_control.lua rate_limit:127.0.0.1 ,  60  3

-h 服务器IP 
-p 服务器端口
执行脚本: /root/ip_control.lua

保存的Key: rate_limit:127.0.0.1
数据存放时间60s则超时 , 限流的数量为3

四、Java调用Lua脚本进行数据量控制-我用的是spring中的 RedisTemplate,所以里面引入了spring相关的包

1)maven中的jar引入

<?xml version="1.0" encoding="UTF-8"?>


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>


  <groupId>com.test</groupId>
  <artifactId>test111</artifactId>
  <version>1.0-SNAPSHOT</version>


  <name>test111</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>


  </properties>


  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.2.RELEASE</version>
  </parent>


  <dependencies>


    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>


    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>


    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>com.squareup.okhttp3</groupId>
      <artifactId>okhttp</artifactId>
      <version>4.2.2</version>
    </dependency>


    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>27.0.1-jre</version>
    </dependency>
    <dependency>
      <groupId>joda-time</groupId>
      <artifactId>joda-time</artifactId>
      <version>2.10.5</version>
    </dependency>


    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>6.10</version>
      <scope>test</scope>
    </dependency>


    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.4</version>
    </dependency>


    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
      <exclusions>
        <exclusion>
          <groupId>io.lettuce</groupId>
          <artifactId>lettuce-core</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
      <version>2.4.3</version>
    </dependency>


    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.7</version>
    </dependency>

  </dependencies>
  <profiles>
    <profile>
      <id>dev</id>
      <properties>
        <profiles.active>dev</profiles.active><!--开发环境 -->
      </properties>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
    </profile>


    <profile>
      <id>test</id>
      <properties>
        <profiles.active>test</profiles.active><!--测试环境 -->
      </properties>
    </profile>


    <profile>
      <id>prod</id>
      <properties>
        <profiles.active>prod</profiles.active><!--生产环境 -->
      </properties>
    </profile>
  </profiles>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <mainClass>test.Server</mainClass>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>${project.build.sourceEncoding}</encoding>
          <showWarnings>true</showWarnings>
          <showDeprecation>true</showDeprecation>


        </configuration>
      </plugin>
    </plugins>
    <resources>
      <resource>
        <directory>src/main/properties</directory>
        <filtering>true</filtering>
        <excludes>
          <exclude>application.properties</exclude>
          <!--          <exclude>application-dev.properties</exclude>
                    <exclude>application-test.properties</exclude>
                    <exclude>application-product.properties</exclude>-->
        </excludes>
      </resource>

      <resource>
        <directory>src/main/properties</directory>
        <filtering>true</filtering>
        <includes>
          <include>application.properties</include>
          <include>ip_control.lua</include>
          <include>application-${profiles.active}.properties</include>
        </includes>
      </resource>
    </resources>

  </build>

</project>

2)spring的配置文件-application.properties

这里是哨兵模式引入的

####redis的配置信息###
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=192.168.112.131:26379,192.168.112.131:26380,192.168.112.131:26381
spring.redis.password=
#采用哪个数据库
spring.redis.database=0
# 连接池最大连接数,默认8个,(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0

3)使用redisTemplate调用脚本

@SpringBootTest
@RunWith(SpringRunner.class) 
public class RedisTest
{

    @Resource
    private RedisTemplate redisTemplate;


    @Test
    public void test2(){
        String luaIpControl = null;
        try {
            //文件路径也可以使用相对路径
            luaIpControl = FileUtils.readFileToString(new File("E:\\github\\test2\\target\\classes\\ip_control.lua"),"utf-8");
            //RedisTest.class.getClassLoader().getResourceAsStream("ip_control.lua");
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println( luaIpControl );

        String key = "rate_limit:127.0.0.1";
        Integer expire = 30;
        Integer maxVisit = 10 ;
        Long result = null;
        for (int i = 0; i < 11; i++) {
            result = invokScript( key , expire ,maxVisit , luaIpControl);
        }

        System.out.println( result );
    }


    public   Long invokScript(String key, int expire ,int maxVisit, String script) {
        // 脚本里的KEYS参数
        List<String> keys = new ArrayList<>();
        keys.add(key);
        // 脚本里的ARGV参数
        List<String> args = new ArrayList<>();
        args.add(Integer.toString(expire));
        args.add(Integer.toString(maxVisit));


        Long result = (long) redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                // 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    return (Long) ((JedisCluster) nativeConnection).eval(script, keys, args);
                }


                // 单机模式
                else if (nativeConnection instanceof Jedis) {
                    return (Long) ((Jedis) nativeConnection).eval(script, keys, args);
                }
                /*else if (nativeConnection instanceof Redisson) {
                    Redisson redisson = (Redisson)nativeConnection;
                    return redisson.getScript().eval(RScript.Mode.READ_WRITE,STOCK_LUA,RScript.ReturnType.INTEGER, Collections.singletonList(keys), new List[]{args});
                }*/
                return null;
            }
        });
        return result;
    }


    @Test
    public void test1(){
        redisTemplate.opsForValue().set("name","wang" ,100 ,TimeUnit.SECONDS);

        String str = (String) redisTemplate.opsForValue().get("name");
        System.out.println( str );
    }
}