redis学习记录(安装/使用)

1、redis的安装

1.1、下载源码安装

wget http://download.redis.io/redis-stable.tar.gz 

tar -xzvf redis-stable.tar.gz

cd redis-stable

make

cp src/redis-cli /usr/local/bin/

1.2、docker安装

这个就比较简单了,参见docker相关文章。 

1.3、测试
redis-cli -h host -p port -a password
 

1.4、不用redis-cli连接redis数据库

连接redis数据库可以不用安装redis-cli,使用telnet就是可以的。如下:

#已知某redis的ip/port为 10.10.11.159 6321,密码为wx@123
我们在一台能访问这个reidsip的机器上执行如下语句:

telnet 10.10.11.159 6321 
auth "wx@123"

然后就可以执行redis指令了。


2、redis支持数据结构及相关指令

Redis 数据类型_redis教程

2.1、字符串string

2.2、字典Hash

2.3、列表List

2.4、集合Set

2.5、有序集合Sorted Set

3、redis原理相关

3.1、redis数据持久化方式

3.1.1、RDB持久化

3.1.2、AOF持久化

 4、redis的集群模式

redis总共有三种集群模式,分别是:①主从模式  ②哨兵模式 ③cluster模式。

4.1、redis主从模式

执行slaveof让一个服务器去复制另一个服务器。

0、redis的复制功能分为同步(sync)和传播(propagate)。
redis主从复制通过定义master和slave实现数据的备份,此外它也是redis集群的实现基础。主要包括如下特点:
(1)单向数据流。从master流向slave,确保数据同步的一致性。
(2)slave的只读特性。slave通常被配置为只读模式,可以分散读取压力
(3)master故障应对机制。简单的主从模式并不能完成故障自动恢复,需要借助哨兵或cluster。

4.2、哨兵模式 —— 自动主动切换

        哨兵(Sentinel)是redis官方推荐的高可用(HA)解决方案。举个例子。通常主动切换主从的方法是:当主服务器宕机后,手动的把一台从服务器切换为主服务器。这需要人工干预,费时费力还会造成一段时间内服务不可用。显然这不是最理想的方式,而redis对于主从切换则提供了“哨兵“。具体来说由一个或者多个Sentinel实例组成Sentinel系统来监控任意多个主服务器以及主服务器树下的所有从服务器,并在被监控的主服务器进入下线状态时,自动将下线主服务器下的某个从服务器升级为新的主服务器。
        哨兵是一个独立的进程,其本质是一个运行在特殊模式下的redis服务器,你可以在启动一个普通redis服务器时通过给定“--sentinel“选项来启动哨兵。其原理是哨兵通过发送命令、等待redis服务器响应,从而达到监控多个Redis实例(redis主/从服务器)的功能。另外,哨兵的涉及思路和zookeeper非常类似。

4.3、集群模式 —— 重点就是这个了

        所谓集群就是通过添加服务器的数量在提供相同服务的前提下,让服务整体更加稳定和高效。集群中的机器被称为节点,分为主节点(master node)和从节点(slave node)。其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时代理下线主节点继续处理命令请求。
        redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384个槽(slot),数据库中的每个键都属于这16384个槽中的一个,集群中的每个节点可以处理0或最多16384个槽。当所有的槽都有节点在处理时,集群处于上线状态(ok);相反集群处于下线状态(fail)。

 5、redis的发布与订阅 —— 提供一种消息队列实现方式

看看就好,一般不这么用。

        redis的发布/订阅(pub/sub)是一种消息通信模式。当一个客户端通过publish命令向订阅者发布消息的时候我们称这个客户端为发布者(publisher);当一个客户端使用subscribe或者psubscribe命令接受消息的时候我们称该客户端为订阅者(subscriber);而连接发布者和订阅者的媒介就是频道(channel)。

6、redis lua脚本

https://github.com/changsongl/lua-redis

6.1、lua脚本解决什么问题?

(1)与多次发送redis指令相比,lua脚本可以减少网络开销。合并指令。

(2)原子操作。在Lua脚本中会将整个脚本做一个一个整体执行,不会被打断。

(3)复用和封装。对于一些通用的功能,通过lua脚本封装能很好的复用。

6.2、lua脚本与事务

        Lua脚本本身也可以看作为一种事务,而且使用脚本起来更简单,并且可控制的流程更灵活

6.3、lua脚本使用举例

6.3.1、在hash和zset两个key中删除指定条数的数据项

zset_key:删除从小到大排序的前count条数据;

hash_key:删除count个字段,字段名由前面zset获取。

对应lua脚本及解释如下: 

if KEYS[1] == nil then return {err='[PARAM ERR] hash KEYS[1] empty error'} end  //hash_key
if KEYS[2] == nil then return {err='[PARAM ERR] zset KEYS[2] empty error'} end  //zset_key
if ARGV[1] == nil then return {err='[PARAM ERR] hash ARGV[1] empty error'} end  //删除条数
local key_exists = tonumber(redis.call('EXISTS', KEYS[1], KEYS[2]))             //看看这个key有几个key实际存在
if key_exists ~= 2 then return {err=string.format('[NOTEXIST ERR] KEYS size(%d) is not even', #KEYS)}  //如果不是两个key,则直接报错
if ((#KEYS % 2) ~= 0) then return {err=string.format('KEYS size(%d) is not even', #KEYS)} end          //再校验下key的个数,如果不是偶数也直接返回错误提示
local not_empty = function(x) return (type(x) == "table") and (not x.err) and (#x ~= 0) end            //这里定义了一个判空的函数,函数名是 not_empty().
local need_del_count = tonumber(ARGV[1])  //要删除的条数
local need_del_ret = redis.call('zrangebyscore', KEYS[2], '-inf', '+inf', 'LIMIT', 0, need_del_count)  //获取排序(时间戳)最小的count条需要删除的数据
if not_empty(need_del_ret) then
    local del_hash_count = redis.call('hdel', KEYS[1], unpack(need_del_ret)) //删除hash
    local del_zset_count = redis.call('zrem', KEYS[2], unpack(need_del_ret)) //删除zset
end
return need_del_ret

调用代码如下:

    std::vector<std::string> keys;
    keys.push_back(str_hash_key);  
    keys.push_back(str_zset_key);

    std::vector<std::string> argv;
    argv.push_back(std::to_string(del_amount));

    //std::vector<std::string> result;
    int ret = EvalSha(g_evalsha_delete_last_list_item, keys, argv, &deleted_item, retry);
    if (ERR_REDIS_NO_SCRIPT == ret)
    {
        // 如果lua脚本未加载,则进行懒加载
        LOGSYS_WATER(_LC_WARNING_, "no script, load again, ret:%d, evalsha:%s", ret, g_evalsha_delete_last_list_item.c_str());
        ret = ScriptLoad(g_lua_delete_last_list_item, g_evalsha_delete_last_list_item, NET_HELPER_RPC_DEFAULT_RETRYTIME_TWO);
        ret |= EvalSha(g_evalsha_delete_last_list_item, keys, argv, &deleted_item,  retry);
    }
6.3.2、在hash和zset两个key中删除指定field/member的数据
在hash_key中的删除指定的field值的项; —— 删除特定{channel}_{cid}列表。

在zset_key中删除特定member值的项;—— 删除特定{channel}_{cid}列表。

if KEYS[1] == nil then return {err='[PARAM ERR] hash KEYS[1] empty error'} end   // 参数校验, 如果没有hash_key报错
if KEYS[2] == nil then return {err='[PARAM ERR] zset KEYS[2] empty error'} end   // 参数校验, 如果没有zset_key也报错
if #ARGV == 0 then return {err='[PARAM ERR] field empty error'} end             // 要删除的 {channel}_{cid};此处 #ARGV 应该就是数组size。
local key_exists = tonumber(redis.call('EXISTS', KEYS[1], KEYS[2]))             // 查询这个key,究竟存在几个key
if key_exists ~= 2 then return {err=string.format('[NOTEXIST ERR] KEYS size(%d) is not even', #KEYS)}  //如果不是两个key都存在那就报错
local bulk = {}             //table是lua易总数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。此处初始化表 bulk。
for i=1, #ARGV do table.insert(bulk, ARGV[i]) end   //遍历 {chanel}_{cid} 列表,并插入 bulk。
local del_hash_count = redis.call('hdel', KEYS[1], unpack(bulk))  //unpack接受一个数组(table)作为参数,并默认从下标1开始返回数组的所有元素。
local del_zset_count = redis.call('zrem', KEYS[2], unpack(bulk))  //lua5.1中,unpack是全局函数 可以直接使用;5.2中unpack被移动到table.unpack。
if del_hash_count ~= del_zset_count then return {err='[DATA NOT CONSISTENE ERR] field empty error'} end
return del_hash_count

调用函数如下:

int RedisMyListHelper::DelListItemByKey(const std::string& str_hash_key, const std::string& str_zset_key, const std::vector<std::string>& subkeys,  std::vector<std::string>& result, int retry)
{
    if(str_hash_key.empty() || str_zset_key.empty() || subkeys.empty())
    {
        return ERR_REDIS_INVALID_PARAM;
    }

    std::vector<std::string> keys;
    keys.push_back(str_hash_key);
    keys.push_back(str_zset_key);

    // std::vector<std::string> argv;
    // argv.push_back(std::to_string(size));
    // if(!str_last_subkey.empty())
    // {
    //     argv.push_back(str_last_subkey);
    // }

    int ret = EvalSha(g_evalsha_delete_list_item_by_key, keys, subkeys, &result, retry);
    if (ERR_REDIS_NO_SCRIPT == ret)
    {
        // 如果lua脚本未加载,则进行懒加载
        LOGSYS_WATER(_LC_WARNING_, "no script, load again, ret:%d, evalsha:%s", ret, g_evalsha_delete_last_list_item.c_str());
        ret = ScriptLoad(g_lua_del_list_item_by_key, g_evalsha_delete_list_item_by_key, NET_HELPER_RPC_DEFAULT_RETRYTIME_TWO);
        ret |= EvalSha(g_evalsha_delete_list_item_by_key, keys, subkeys, &result,  retry);
    }

    if (ERR_REDIS_KEY_NOT_EXIST == ret || ERR_REDIS_REPLY_IS_NULL == ret)
    {
        LOGSYS_WATER(_LC_ERROR_, "DelListItemByKey  not exist, hash_key:%s, zset_key:%s , ret:%d", str_hash_key.c_str(), str_zset_key.c_str(),  ret);
        return ret;
    }
    else if (0 != ret)
    {
        LOGSYS_WATER(_LC_ERROR_, "DelListItemByKey failed, hash_key:%s, zset_key:%s, ret:%d", str_hash_key.c_str(), str_zset_key.c_str(), ret);
        return ret;
    }
    LOGSYS_WATER(_LC_DEBUG_, "DelListItemByKey succeed,  hash_key:%s, zset_key:%s, ", str_hash_key.c_str(), str_zset_key.c_str());
    return 0;
}

6.4、redis插入数据指定版本号

redis本身不直接支持在插入数据时指定版本号,但是可以通过一些方法实现类似的功能。

以下提供lua脚本获取key版本号及插入数据指定版本号的例子。 

6.4.1、redis版本号匹配才更新

        使用Lua脚本来检查键key的当前版本号是否与期望的版本号expected_version相匹配。如果匹配,则更新这个键的值为value;如果不匹配或键不存在,脚本将不执行更新操作,返回false。 

import redis
 
# 连接到Redis
r = redis.Redis(host='localhost', port=6379, db=0)
 
# Lua脚本
script = """
local key = KEYS[1]
local value = ARGV[1]
local expected_version = ARGV[2]
local current_version = redis.call('GET', key)
if current_version == expected_version then
    redis.call('SET', key, value)
    return true
else
    return false
end
"""
 
# 需要插入的键和值
key = 'mykey'
value = 'myvalue'
expected_version = '1'  # 假设期望的版本号是1
 
# 使用Lua脚本
result = r.eval(script, 1, key, value, expected_version)
 
print(result)  # 如果版本号匹配,返回true;如果不匹配或出现错误,返回false。
6.4.2、redis通过lua脚本插入数据并指定版本号

        在这个脚本中,我们首先检查键是否已经存在。如果不存在,我们使用SET命令来插入数据,并且使用HSET命令来设置版本号。如果键已经存在,我们不做任何操作,并返回0。 

-- Lua script to insert data with a specific version number
-- KEYS[1] is the key to insert the data
-- ARGV[1] is the value to insert
-- ARGV[2] is the version number
 
-- Check if the key already exists
if (redis.call('exists', KEYS[1]) == 1) then
    -- Key exists, don't overwrite
    return 0
else
    -- Key does not exist, insert the data with SET command
    redis.call('set', KEYS[1], ARGV[1])
    -- Set the version number with HSET command
    redis.call('hset', KEYS[1], 'version', ARGV[2])
    -- Return 1 to indicate the insertion was successful
    return 1
end

在Redis中执行这个Lua脚本的示例代码如下:

redis-cli --eval script.lua , mykey myvalue 1
注:上述 script.lua 是抱哈上述Lua脚本的文件,mkkey是要插入的键,myuvalue是要插入的值,1是版本号。如果键已存在,脚本返回0;如果键不存在,脚本插入数据并返回1。

 

7、redis其他使用tips

7.1、redis数据重定向输出到指定文件

有时候想导出某些key的数据到文件中,是可以直接重定向导出的。

不一定非要用python写个工具啥的。

操作格式如下:

# xx.xx.xx.xx  redis的host
# 8xxx redis端口
# xxx  reids访问密码
# whitelist_redis_key 查询数据的key
# whitelist.txt #数据输出到此文件
 
echo "smembers whitelist_redis_key" | redis-cli -h xx.xx.xx.xx -p 8xxx -a xxx> whitelist.txt

举例1:将 "llen pic_download_failed_list" 的结果输出到文件

 echo "llen pic_download_failed_list" | redis-cli -h 10.xxx.xxx.73 -p 6379 -a "password"> failed.txt

举例2:将 "smembers inuse_kfuin_set_key" 的输出导出到文件

echo "smembers inuse_kfuin_set_key" | redis-cli -h 9.xx.xxx.33 -p 12002 -a "mypassword"> inuse_kfuin_set.txt

 得到文件如下:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

焱齿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值