1. 程式人生 > 程式設計 >SpringBoot通過redisTemplate呼叫lua指令碼並列印除錯資訊到redis log(方法步驟詳解)

SpringBoot通過redisTemplate呼叫lua指令碼並列印除錯資訊到redis log(方法步驟詳解)

第一次寫Lua指令碼,並通過springboot的redisTemplate呼叫指令碼,進行指令碼與應用的互動。不熟悉真的折騰了好久,現在總結一下學習過程:

第一次寫完lua時,想到的就是如何在應用呼叫指令碼的時候,去除錯指令碼。在網上海搜了一把,能找到的有點相關的寥寥無幾。

有一種方法是通過執行redis命令,呼叫redis客戶端,載入lua指令碼,然後出現基於命令列除錯的互動介面,輸入除錯命令去除錯指令碼。如下:

在終端輸入命令:redis-cli.exe --ldb --eval LimitLoadTimes.lua 1 mykey,myargv

--ldb:redis-cli.exe進行命令除錯的必要引數

--eval:告訴redis客戶端去載入Lua指令碼,後面跟著的就是 lua指令碼的路徑(我是直接放在redis目錄下),

1:傳給Lua指令碼的key的數量,我測試的時候是1

--mykey:自己傳的一個key值,和前面的數量1對應

--myargv:自己傳的除key外的引數,可以是多個

注,命令中的逗號不能忽略,並且前後要有一個空格

SpringBoot通過redisTemplate呼叫lua指令碼並列印除錯資訊到redis log(方法步驟詳解)

回車,如上圖,本來以為可以進入除錯,結果等了半天,一直沒有出現互動的命令列介面,找了好久,還是沒找到辦法,結果只好先暫停(如果有大神遇到這種情況,跪求解~~)。換一種除錯方式,把除錯資訊打在redis日誌上。

下面是我自己呼叫指令碼時,列印除錯資訊的方式,如果有更好的方式,請不吝賜教。

1、選擇redisTemplate序列化方式

首先,建立一個redisTemplate,具體程式碼就不說了,這個比較簡單。要注意的是,需要設定redisTemplate的序列化方式,springBoot預設是基於java jdk的序列化。通過這種序列化後的引數傳到Lua指令碼是,是無法正常列印到redis日誌的,會出現亂碼,而且引數如果傳的是一個Map或List的話,不方便解析。並且這種序列化佔用的位元組比較大。所以改成JSON序列化,用FastJson實現。

下面貼上redis序列化程式碼:

public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
 public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
 private Class<T> clazz;
 public FastJsonRedisSerializer(Class<T> clazz){
  super();
  this.clazz = clazz;
 }
 @Override
 public byte[] serialize(T t) throws SerializationException {
  return ofNullable(t)
    .map(r -> JSON.toJSONString(r,SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET))
    .orElseGet(() -> new byte[0]);
 }
 @Override
 public T deserialize(byte[] bytes) throws SerializationException {
  return Optional.ofNullable(bytes)
    .map(t -> JSON.parseObject(new String(t,DEFAULT_CHARSET),clazz))
    .orElseGet(() -> null);
 }
}

2、應用端載入指令碼,並設定傳遞引數

在springboot中,是用 DefaultRedisScript 類來載入指令碼的,並設定相應的資料型別來接收lua指令碼返回的資料,這個泛型類在使用時設定泛型是什麼型別,指令碼返回的結果就是用什麼型別接收。注意,該類只接收4種類型的返回型別,之前沒注意,還納悶為什麼出錯,看原始碼才曉得,截圖,如下:

SpringBoot通過redisTemplate呼叫lua指令碼並列印除錯資訊到redis log(方法步驟詳解)

在lua指令碼中,有兩個全域性的變數,是用來接收redis應用端傳遞的鍵值和其它引數的,分別為KEYS、ARGV。

在應用端傳遞給KEYS時是一個數組列表,在lua指令碼中通過索引方式獲取陣列內的值。

在應用端,傳遞給ARGV的引數比較靈活,可以是多個獨立的引數,但對應到Lua指令碼中是,統一用ARGV這個陣列接收,獲取方式也是通過陣列下標獲取。

下面貼上應用端的測試程式碼:

@Service("luaScriptService")
public class LuaScriptServiceImpl implements LuaScriptService{
 @Autowired
 private RedisTemplate<String,Object> redisTemplate1;
 private DefaultRedisScript<List> getRedisScript;
 @PostConstruct
 public void init(){
  getRedisScript = new DefaultRedisScript<List>();
  getRedisScript.setResultType(List.class);
  getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("luascript/LimitLoadTimes.lua")));
 }
 @Override
 public void redisAddScriptExec(){
  /**
   * List設定lua的KEYS
   */
  List<String> keyList = new ArrayList();
  keyList.add("count");
  keyList.add("rate.limiting:127.0.0.1");
  /**
   * 用Mpa設定Lua的ARGV[1]
   */
  Map<String,Object> argvMap = new HashMap<String,Object>();
  argvMap.put("expire",10000);
  argvMap.put("times",10);
  /**
   * 呼叫指令碼並執行
   */
  List result = redisTemplate1.execute(getRedisScript,keyList,argvMap);
  System.out.println(result);
 }
}

程式碼中傳送了兩個key,還有一個map包裝的argv,傳遞到Lua指令碼中時,KEYS和ARGV接收到的是物件字串,所以得用lua的庫做相關的解碼,我們傳送的時候是用json序列化的,用Lua的庫cjson可以轉成json物件。下面貼上Lua指令碼程式碼:

--獲取KEY
local key1 = KEYS[1]
local key2 = KEYS[2]
-- 獲取ARGV[1],這裡對應到應用端是一個List<Map>.
-- 注意,這裡接收到是的字串,所以需要用csjon庫解碼成table型別
local receive_arg_json = cjson.decode(ARGV[1])
--返回的變數
local result = {}
--列印日誌到reids
--注意,這裡的列印日誌級別,需要和redis.conf配置檔案中的日誌設定級別一致才行
redis.log(redis.LOG_DEBUG,key1)
redis.log(redis.LOG_DEBUG,key2)
redis.log(redis.LOG_DEBUG,ARGV[1],#ARGV[1])
--獲取ARGV內的引數並列印
local expire = receive_arg_json.expire
local times = receive_arg_json.times
redis.log(redis.LOG_DEBUG,tostring(times))
redis.log(redis.LOG_DEBUG,tostring(expire))
--往redis設定值
redis.call("set",key1,times)
redis.call("incr",key2)
redis.call("expire",key2,expire)
--用一個臨時變數來存放json,json是要放入要返回的陣列中的
local jsonRedisTemp={}
jsonRedisTemp[key1] = redis.call("get",key1)
jsonRedisTemp[key2] = redis.call("get",key2)
jsonRedisTemp["ttl"] = redis.call("ttl",cjson.encode(jsonRedisTemp))
result[1] = cjson.encode(jsonRedisTemp) --springboot redistemplate接收的是List,如果返回的陣列內容是json物件,需要將json物件轉成字串,客戶端才能接收
result[2] = ARGV[1] --將源引數內容一起返回
redis.log(redis.LOG_DEBUG,cjson.encode(result)) --列印返回的陣列結果,這裡返回需要以字元返回
return result

3、設定日誌級別

程式碼中,redis.log()函式向運日誌中輸出資訊,這裡要注意一下,函式裡面設定的日誌級別要和redis.conf配置檔案中設定的日誌級別一樣才能正常列印到檔案,這裡我是設定成了deubg級別。這裡可設定的級別有4種,分別如下:

  • redis.LOG_DEBUG
  • redis.LOG_VERBOSE
  • redis.LOG_NOTICE
  • redis.LOG_WARNING

在應用端,我們設定接收返回的資料型別是List,所以在Lua指令碼中,返回的型別用table與之對應,並且放到table變數中的內容,得是字串,應用端才能通過反序列化,正常解析。下圖是輸出lua返回值的列印資訊:

SpringBoot通過redisTemplate呼叫lua指令碼並列印除錯資訊到redis log(方法步驟詳解)

至此,結束,希望也能對其遇到相同問題的朋友有所幫助。

總結

以上所述是小編給大家介紹的SpringBoot通過redisTemplate呼叫lua指令碼並列印除錯資訊到redis log(方法步驟詳解),希望對大家有所幫助!