前言
一般在开发中使用redis时,都会使用冒号进行key的分割,例如login_token:admin等等,最近被要求编写可视化redis管理的工具,要以树形文件夹目录展示redis中的所有key,正文内容如下。
一、redis相关
要获取redis中的所有key,一般用的都是keys *进行匹配获取,但这个命令会造成阻塞和加锁,在数据量较大的生产环境使用比较危险,因而使用scan代替,相关的具体理论请自行搜索。相关代码如下:
/**
* 通过游标遍历redis中的key
* 每10000条一次 这里不是只执行一次 游标的移动已被封装
* 真正执行查询的语句在cursor.hasNext()中 具体可看源码
*/
public Set<String> scan(String pattern){
Set<String> keys = new HashSet<>();
RedisSerializer<?> serializer = redisTemplate.getKeySerializer();
ScanOptions scanOptions = ScanOptions.scanOptions().match(pattern).count(10000).build();
try (Cursor<byte[]> cursor = redisTemplate.execute(connection -> connection.scan(scanOptions), true)) {
if (null == cursor){
return keys;
}
while (cursor.hasNext()) {
keys.add(String.valueOf(serializer.deserialize(cursor.next())));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return keys;
}
额外记录一下redis获取内存使用量的代码。
/**
* 获取key对应的内存使用量
* @param key key
* @return long
*/
public Long memoryUsage(String key){
try {
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
if (null != connectionFactory){
String usageScript = "return redis.pcall('MEMORY', 'USAGE', KEYS[1])";
Long result = connectionFactory.getConnection().eval(
usageScript.getBytes(StandardCharsets.UTF_8),
ReturnType.INTEGER,
1,
key.getBytes(StandardCharsets.UTF_8));
if (null == result){
return 0L;
}
return result;
}
return 0L;
}catch (Exception e){
e.printStackTrace();
return 0L;
}
}
二、目录展示算法
前端使用的树形组件为ztree,如果要使用其他的前端组件需要自己更改返回格式,需要的数据为list,如图,以id和pId关联子父级。![](https://img-blog.csdnimg.cn/645a6ddbead74f279b351d3fe6e9e6bb.png)
/**
* 标记key分割的结束符号
*/
private final String splitEndSign = "}]>T^T<[{";
/**
* key中的分隔符
*/
private final String splitSign = ":";
/**
* 将redis中查出的keu加载为树形结构
* @param allKey key集合
* @return 树list
*/
private List<Ztree> initZtree(Set<String> allKey){
List<Ztree> ztrees = new LinkedList<>();
Map<String, Object> treeMap = new HashMap<>();
long start = System.currentTimeMillis();
allKey.forEach(key -> {
String[] keyArray = key.split(splitSign);
List<String> list = new ArrayList<>(keyArray.length);
Collections.addAll(list, keyArray);
// 根据分隔符切割key构造基于map的树形结构
initTreeMap(treeMap, list);
});
// 转换为树结构
treeMaptoZtree(ztrees, treeMap, null, null);
log.debug("构造树结构花费:{}毫秒", (System.currentTimeMillis() - start));
log.debug("构造树结长度为:{}条", ztrees.size());
return ztrees;
}
/**
* 构造基于map的树形结构
* @param treeMap 树map
* @param list 分割后的key的list
*/
private void initTreeMap(Map<String, Object> treeMap, List<String> list) {
// 根据key获取或设置指向的map
Object obj = treeMap.computeIfAbsent(list.get(0), k -> new HashMap<>());
// 如果list的长度大于1说明还没结束 移除掉当前的值并进行递归
if (list.size() > 1){
list.remove(0);
initTreeMap((Map<String, Object>) obj, list);
}else {
// 否则加入结束的标志 表示此key已经结束
treeMap.put(list.get(0) + splitEndSign, null);
}
}
/**
* 将treeMap转换为树结构 交予前端组件展示
*/
private void treeMaptoZtree (List<Ztree> ztrees, Map<String, Object> treeMap, String pid, String parentKey){
// 防止重复key
Set<String> repeatKeyCheck = new HashSet<>();
// 遍历treeMap
treeMap.forEach((key, value) -> {
// 唯一id用于标识父子关系
String uuid = IdUtil.fastSimpleUUID();
if (null != value && ((Map<String, Object>) value).size() > 0){
Ztree ztree = new Ztree();
String wholeKey;
// 如果parentKey不为空则不是key的开头 开始构造key的完整值
if (null != parentKey){
wholeKey = parentKey + splitSign + key;
}else {
wholeKey = key;
}
ztree.setTitle(wholeKey);
ztree.setName(key);
ztree.setpId(pid);
ztree.setId(uuid);
ztrees.add(ztree);
// 还没有结束则继续递归
treeMaptoZtree(ztrees, (Map<String, Object>) value, uuid, wholeKey);
}else {
String orgiKey = key;
// 如果key以结束符结尾 则去除结束符在进行下一步操作
if (key.endsWith(splitEndSign)){
orgiKey = key.substring(0, key.length() - splitEndSign.length());
}
// 如果key没有被添加过 进行后续操作
if (!repeatKeyCheck.contains(orgiKey)){
repeatKeyCheck.add(orgiKey);
Ztree ztree = new Ztree();
if (null != parentKey){
ztree.setTitle(parentKey + splitSign + orgiKey);
}else {
ztree.setTitle(orgiKey);
}
ztree.setName(orgiKey);
ztree.setpId(pid);
ztree.setId(uuid);
ztree.setIslast(true);
ztrees.add(ztree);
}
}
});
}
结果展示
![](https://img-blog.csdnimg.cn/b453639f3a6e45b398aff4b03df33eb3.png)