Redis命令行遍历所有key方法

最常见的指令是:

keys 前缀*

后面的参数跟通配符来列出所有符合的key。

由于KEYS命令一次性返回所有匹配的key,所以,当redis中的key非常多时,对于内存的消耗和redis服务器都是一个隐患,
对于Redis 2.8以上版本给我们提供了一个更好的遍历key的命令 SCAN 该命令的基本格式:

SCAN cursor [MATCH pattern] [COUNT count]  

SCAN 每次执行都只会返回少量元素,所以可以用于生产环境,而不会出现像 KEYS 或者 SMEMBERS 命令带来的可能会阻塞服务器的问题。

SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程

当SCAN命令的游标参数(即cursor)被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。

简单的迭代演示:

redis 127.0.0.1:6379> scan 0
1) "17"
2)  1) "key:12"
    2) "key:8"
    3) "key:4"
    4) "key:14"
    5) "key:16"
    6) "key:17"
    7) "key:15"
    8) "key:10"
    9) "key:3"
   10) "key:7"
   11) "key:1"
redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"
   2) "key:18"
   3) "key:0"
   4) "key:2"
   5) "key:19"
   6) "key:13"
   7) "key:6"
   8) "key:9"
   9) "key:11"

在上面这个例子中, 第一次迭代使用 0 作为游标, 表示开始一次新的迭代。第二次迭代使用的是第一次迭代时返回的游标 17 ,作为新的迭代参数 。

显而易见,SCAN命令的返回值 是一个包含两个元素的数组, 第一个数组元素是用于进行下一次迭代的新游标, 而第二个数组元素则又是一个数组, 这个数组中包含了所有被迭代的元素。

注意:返回的游标不一定是递增的,可能后一次返回的游标比前一次的小。

在第二次调用 SCAN 命令时, 命令返回了游标 0 , 这表示迭代已经结束, 整个数据集已经被完整遍历过了。

full iteration :以 0 作为游标开始一次新的迭代, 一直调用 SCAN 命令, 直到命令返回游标 0 , 我们称这个过程为一次完整遍历。

SCAN增量式迭代命令并不保证每次执行都返回某个给定数量的元素,甚至可能会返回零个元素, 但只要命令返回的游标不是 0 , 应用程序就不应该将迭代视作结束。

不过命令返回的元素数量总是符合一定规则的, 对于一个大数据集来说, 增量式迭代命令每次最多可能会返回数十个元素;而对于一个足够小的数据集来说,可能会一次迭代返回所有的key。

缓存重构 – 减少Redis Key的数量

未分类

前不久重构系统的时候,发现redis的key已经超过5000万个了,已经没法用keys做遍历了,即使用迭代器*scan做遍历,开销也大到无法接受了。对业务我是相当熟悉的,我很确定我们不需要这么多的key,于是着手开始清理。

首先我跑了个脚本,统计出最常见的key的前缀,发现有两类最多,都超过1000万,分别是

  • carbrand_udid_,缓存的是每个用户的绑定了车牌的车型

  • 1100_341_,缓存的是某个URL对应的图片是否允许被展示

这些key都没有过期时间,也没有清理机制。而业务本身是要求carbrand_udid_永不过期的,1100_341_的时效则比较短,通常在半个月以内,最长也不会超过三个月。

针对第一种情况,原先一个carbrand_udid_$userId对应一个车型列表,通常这个列表长度不超过10,毕竟拥有10个以上车牌且在我们平台上绑定过的用户是极其稀有的。而且每次对这个列表都是整体存取的,不需要单独访问其中一个车型。这种情况其实很好改造,我们放弃每个用户一个key的做法,改用hashtable,table.carbrand,用$userId做hashtable的field,然后把JSON序列化后的车型ID作为value,Redis的hashtable可以存储40亿个键值对,我们用户数才3亿,绑定了牌照的用户不过几千万,在可以预见的未来,不存在超过40亿的可能性。第一种情况的改造,把数千万个key塞到了hashtable内,变成了一个key加这个key内部的数千万个field。

本质上这种改造,是把二维的数据压缩成一维的,data[][]变成data[],消失的那个维度是通过JSON编码成字符串完成的。

针对第二种情况,原先是1100_341_$URL,值是0或1,用来存储一个广告图片是否能展示,0是不能展示,1是可以展示。直接用上面那种方法也能大大减少key的数量,但是我需要做的更好,增加一个过期的功能。由于redis只能对key设置过期时间,不能对二维数据结构内部元素做过期设置,只能想别的办法了。能不能好好利用有效期最长也不会超过三个月这个特性呢?当然可以,而且很简单。我们把日期编码进key里面就行了,比如这样

  • image.audit.white.2018.Q1

  • image.audit.black.2018.Q2

  • image.audit.white.2018.Q1

  • image.audit.black.2018.Q2

每个季度一个白名单set和黑名单set,把URL存储在set里面,再通过判断集合中是否存在某个元素决定URL是否能访问。我们每次最多需要查4个名单,可以确定URL在哪个集合,利用redis的pipeline特性,可以在一个报文里批量查询,一次查完。4条O(1)复杂度的命令执行起来是很快的。还能不能继续优化一些呢?考虑到大部分URL都很长,在空间利用上有些浪费,所以我选择用sha1把URL做个签名,只存储这个签名就行了。第二种改造,成功把数千万个key,减少到每个季度2个,然后定期删除上上季度的key,就能完成清理工作了。

Redis有丰富的数据结构,需要因地制宜好好使用。如若不然,还不如用memcached算了。

Git鉴权方式SSH key和用户名密码

Git的鉴权方式分为 ssh 和 https,ssh 需要使用 ssh 秘钥,而https需要使用用户名和密码。有时候会每次提交都需要输入用户名和密码,下面介绍相关的Windows平台Git鉴权操作。

概述

使用Git之前需要安装 Git-bash 并且把它加到环境变量,git-bash下载地址(https://git-scm.com/downloads)。

如果使用的是 git-desktop,安装的时候会自带 git-bash,需要配置到环境变量,它的地址一般在:

C:Users用户名AppDataLocalGitHubPortableGit,

在GitHub文件夹下搜索 git.exe 可以快速找到位置。一般是Portable_XXX文件夹下的cmd目录下面,有 git.exe, git-gui.exe。

在 git-bash 终端,使用 cd 命令可以跳转目录,注意这儿跳转目录的格式为:

$ cd /g/Projects/sdk_plugin

其中盘符为 G ,对于交互界面,可以直接拖动文件夹到 git-bash 中,会自动转成相应的文件夹路径。

对于某个项目,git clone 之后,在当前项目会拉取源代码,注意默认隐藏的 .git 文件夹,该文件夹下面有 config 文件,里面有关于该项目的配置。

另外需要配置项目的用户名和邮箱,这会用作提交代码的用户名和邮箱记录。

$ git config --global user.name "uusama"
$ git config --global user.email "[email protected]"

关于鉴权方式

git clone 的地址有下面两种

  • https: https://github.com/用户名/GitProject.git
  • ssh: [email protected]:用户名/GitProject.git

如果是 gitlab 内网服务端,这对应的 https 开头的地址或者 git 开头的地址。

第一种方式为 http 方式,需要提供用户名和密码,第二种方式是 ssh 方式,需要配置 ssh 的 RSA 秘钥。

可以通过修改项目的git配置文件: .git/config 中的url部分,从而改变鉴权方式。

[remote "origin"]
    url = https://github.com/youyouzh/admin.git
    fetch = +refs/heads/*:refs/remotes/origin/*

生成Git SSH秘钥

对于 ssh 方式,如果没有设置秘钥,向远程分支 git push 的时候就会被拒绝。本地 git commit 不影响,照常可以使用。

打开命令行 cmd,输入下面的命令开始生产 ssh 的 rsa key:

ssh-keygen -t rsa -C "用户名"

接下来一直回车即可,使用默认设置,如果不是第一次生成,选择 overwrite。会在指定的目录下面生产秘钥文件:/c/Users/用户名/.ssh/id_rsa.pub,在步骤说明里面有详细的地址。

记事本打开这个文件,里面就是 rsa key。

将 ssh 秘钥添加到版本库中

生成 ssh 秘钥以后,需要在 github 或者 gitlab 上面添加 ssh key。

添加的位置在个人设置->SSH秘钥中。添加完之后,如果你有提交权限的话,就可以 git push了。

HTTP 方式设置保存用户名密码

对于 HTTP 方式的git项目,会在每次 git push 的时候要求你输入用户名和密码,这时我们想只输入一次就可以了,可以通过添加git配置,保存用户名和密码,这种方式用户名和密码都是明文保存,会稍微不安全。

# 设置永久保存
git config --global credential.helper store
# 设置记住密码(默认15分钟)
git config --global credential.helper cache
# 设置过期时间3600s
git config credential.helper 'cache --timeout=3600'

上面的命令相对于在 config 文件中添加配置:

[credential]
helper=store

另外还可以将直接修改配置文件中的 url 部分,将用户名和密码写到 url 中:

http://username:[email protected]/name/project.git

redis统计大key

redis的–bigkeys参数:对redis整个keyspace进行统计(数据量大时采样,调用scan命令),寻找每种数据类型较大的keys,给出数据统计

redis-cli –bigkeys -i 0.1 -h 127.0.0.1

redis批量删除key

最近生产环境的redis服务器由于key过期不及时,现在发现时key的个数已经暴增到5000多万了。然后运维同学那边就报警了,最大内存12G,已经用了9G多了,正好下面快要双11了,让我们快些解决。

redis服务器里面堆积大量的队列状态相关的key,其实这些key可以设置有效期,或者任务完成以后删除或者过期,但是由于我们使用类库的问题,这些key既没有删除也没有过期,堆积到redis里面去了,现在我们要做的就是删除这些无用key。在删除这些keys的过程中,走了不少弯路,这里说一下我最终采用的方案。

redis的del函数可以删除单个key,也可以删除多个key,del函数官方文档可以看这里。在google之后看到目前网络上很多文章的思路是使用keys匹配返回要删除的key,然后调用del函数去删除。这种方案在数据量较小时无可厚非,但如果像我这样面临的处理的数据有5千W时,keys的阻塞问题可能会给线上生产环境带来致命的问题。所以我们需要对这种方案作出一些修改。

可喜的是自从2.8.0以后redis提供scan来遍历key,而且这个过程是非阻塞,不会影响线上生产环境。最终经过修改的方案是用scan遍历要删除的key,然后调用del删除。

下面是我用python写的用来删除key的脚本。

import sys,redis

r = redis.Redis(host="127.0.0.1", port=6379,db=0)

if  len(sys.argv) <= 1:
    print("必须指明匹配key字符串")
    exit(1)
pattern = sys.argv[1]

cursor = 0
num = 1
while 1 :
    resut = r.scan(cursor, pattern, 10000)
    del_keys = []
    for i in resut[1]:
        key = i.decode()
        del_keys.append(key)
    #print("del keys len :%d" % len(result))
    if len(del_keys) == 0:
        break
    r.delete(*del_keys)
    cursor = resut[0]
    print("delete keys num : %dw" % (num))
    num +=1

print("donen")

如何利用我这个脚本删除符合某个规则的key哪,如以king开头的key?

下面的命令即可完成上面的问题。

python3 main.py "king*"

期间我看到网上利用keys+del的lua脚本的方案,花了一段时间把scan+del改成lua脚本来删除。但是可惜的是目前redis并不支持这么做,由于scan返回的结果是不确定的,所以禁止在其后直接调用del操作。

分享一个删除redis中指定key模式的数据的shell脚本

有很多场景,我们都需要删除redis中某些具有相似特征的key,即使是线上环境也是。如果key数量很小容易处理,如果这些key很多很多,必须通过scan命令循环扫描一一删除,如果直接执行keys命令会堵死redis服务。下面这个脚本就是通过循环扫码key再删除,直至结束。

redis-del-keys.sh

#!/bin/bash
##redis主机IP
host=$1
##redis端口
port=$2
##key模式
pattern=$3
##游标
cursor=0
##退出信号
signal=0

##循环获取key并删除
while [ $signal -ne 1 ]
    do
        echo "cursor:${cursor}"
        sleep 2
        ##将redis scan得到的结果赋值到变量
        re=$(redis-cli -h $host -p $p -c  scan $cursor count 1000 match $pattern)
        ##以换行作为分隔符
        IFS=$'n' 
        #echo $re
        echo 'arr=>'
        ##转成数组
        arr=($re)
        ##打印数组长度
        echo 'len:'${#arr[@]}
        ##第一个元素是游标值
        cursor=${arr[0]}
        ##游标为0表示没有key了
        if [ $cursor -eq 0 ];then
            signal=1
        fi
        ##循环数组
    for key in ${arr[@]}
        do
            echo $key
            if [ $key != $cursor ];then
                echo "key:"$key
                ##删除key
                redis-cli -h $host -p $port -c del $key >/dev/null  2>&1
            fi
    done
done
echo 'done'

使用方式:

./redis-del-keys.sh localhost 6379 user:*

表示删除本机6379端口的redis中user:开头的所以key。