1. 程式人生 > 實用技巧 >Redis呼叫Lua指令碼並測試

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 );
    }
}