进入官网复制下载地址
官网地址
进入终端使用命令下载redis
wget http://download.redis.io/releases/redis-6.0.1.tar.gz
进入文件下载的目录解压压缩包
tar -zxvf redis-6.0.1.tar.gz -C ./
# 创建一个软连接
ln -s redis-6.0.1 redis
进入解压后的目录使用make命令进行编译
make MALLOC=libc
注:如提示make命令不存在,可使用 sudo apt install make 自行安装,编译过程中如果少什么依赖根据报错提示自行安装即可。
安装redis到指定目录
cd ./src
make install
```
注:一定要先进入redis的src目录才可以执行make install 命令
配置redis
为了方便后续的管理,我们在redis的根目录中新建两个文件夹
mkdir etc
mkdir bin
将 redis/redis.conf 文件移动到新建的etc目录中
mv redis.conf etc/
进入src目录将常用的二进制可执行文件拷贝到新建的bin目录中
cd redis/src
ls -l | grep "^-rwx" | awk '{print $9}' | xargs -i cp {} ../bin/
进入刚建的 etc 目录,然后打开编辑 redis.conf 文件
cd redis/etc
vim redis.conf
然后修改两个地方即可
# 绑定的地址 (默认是127.0.0.1,如只是本地访问就不需要修改)
# ifconfig 查看自己的ip地址绑定即可
bind 192.168.96.131
# 配置启动方式为守护进程的方式启动,也就是后台启动
daemonize yes
# 配置redis持久化的文件(这个目录需要自己提前创建)
dir /home/hadoop/opt/redis/data
# 配置连接密码(可要可不要,真实生产环境肯定还是必要的,但学习时可以图方便不设)
requirepass 123456
# 配置文件中还有很多配置,可以自行按照需求对照官网的参数解释进行配置
启动后redis-server
# 使用刚才配置好的配置文件进行启动
redis-server etc/redis.conf
# 查看服务是否启动成功
ps -xu | grep redis-server
至此redis的环境配置完成
文件名 | 主要功能 |
---|---|
redis-server | Redis服务的启动 |
redis-cli | Redis的命令行客户端 |
redis-benchmark | Redis性能测试客户端 |
redis-check-aof | AOF文件修复工具 |
redis-check-rdb | RDB文件检查工具 |
redis-sentinel | Sentinel服务器的启动 |
先来介绍一下最常用的两个运维命令吧
(1)redis-server
刚在在安装时我们已经使用过这个命令了,我们先来来介绍一下它的一些常用参数;
# 就会罗列出这个命令的使用帮助
redis-server --help
# 指定端口启动(redis 默认就是监听6379端口,可以自行更改)
redis-server --port 6379
# 使用配置文件的方式启动
redis-server redis.conf
(2)redis-cli
这个命令就比较重要了,使用它可以连接到我们的redis服务器。它提供了一个可交互的命令行窗口,我们可以在命令行做一些测试以及修改配置关闭服务器等等,总之非常的强大。
# 先列举一些简单的运维命令
# 连接前面我们启动的redis服务器 (如果端口和地址都是默认其实不填参数也行,如果设有密码加 -a 参数即可)
redis-cli -h 192.168.96.131 -p 6379
# 关闭redis-server(平时建议使用此命令进行关闭服务,
# 因为暴力关闭的化可能会造成数据还没来得急持久化,
# 服务就被关闭了从而导致数据丢失)
# 客户端连接后再输入命令
> SHUTDOWN
# 查看连接是否正常
> PING
# 打印一段文本类容
> ECHO 'hello world!'
# 退出客户端(注意这里和关闭服务时有区别的,这里先当与只是断开了连接)
> quit
string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。
在需要使用命令前先提示一句(Rredis 的命令是不区分大小写的,但为了和键值进行区分还是把命令写成大写比较直观)
用法:
用法 SET [key] [value] [EX seconds | PX milliseconds]
> SET key1 "hello world!"
OK
> GET key1
"hello world!"
> SET key1 "override"
OK
# 秒为单位:
> SET key1 "hello world!" EX 1
# 等一秒之后再获取值 (这个键时间已到期就被删除了)
> GET key1
(nil)
# 毫秒为单位:
> SET key1 "hello world!" PX 1000
OK
# 也是等一秒后再获取值,键也是时间到期就被删除
> GET key1
(nil)
用法: INCR [key]
> SET key1 1
OK
> INCR key1
(integer) 2
用法: DECR [key]
> DECR
(integer) 1
用法: INCRBY [key] [increment]
> INCRBY key1 10
(integer) 11
用法: DECRBY [key] [decrement]
> DECRBY key1 10
(integer) 1
用法: INCRBYFLOAT [key] [increment]
注意: 没有减少的方法,如需减少只需要再增长数值前加负号即可
> INCRBYFLOAT key1 0.5
"1.5"
> INCRBYFLOAT key1 -0.5
"1"
用法: APPEND [key] [value]
# 返回值为追加字符串后,字符串的总长度
> APPEND key1 name
(integer) 5
> GET key1
"1name"
这里也只是笼统的介绍了一下常用的一些命令,命令还有很多就不依次列举了
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Redis 中每个 hash 可以存储 2 32 − 1 2^{32}-1 232−1个键值对(40多亿)。
用法: HMSET key field value [field value …]
# 创建一个学生'对象',姓名为Nick,年龄:18,性别:M
> HMSET student1 name "Nick" age 18 gender "M"
OK
用法: HGETALL key
> HGETALL student1
1) "name"
2) "Nick"
3) "age"
4) "18"
5) "gender"
6) "M"
用法: HMGET key field [field …]
> HMGET student1 name
1) "Nick
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 2 32 − 1 2^{32} - 1 232−1 个元素 (4294967295, 每个列表超过40亿个元素)。
用法: LPUSH key element [element …]
# 返回的整数代表,加入元素后列表的总长度
> LPUSH color red blue yellow green
(integer) 4
用法: LRANGE key start stop
# 即使超过了列表的长度,也没关系
> LRANGE color 0 10
1) "green"
2) "yellow"
3) "blue"
4) "red"
用法: RPUSH key element [element …]
> RPUSH color white black pink
(integer) 7
> LRANGE color 0 10
1) "green"
2) "yellow"
3) "blue"
4) "red"
5) "white"
6) "black"
7) "pink"
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
集合中最大的成员数为 2 32 − 1 2^{32} - 1 232−1 (4294967295, 每个集合可存储40多亿个成员)。
用法: SADD key member [member …]
# 返回的整数代表添加的个数
> SADD zoo1 tiger lion snake
(integer) 3
# 添加重复的元素看看
> SADD zoo1 snake
(integer) 0
用法: SMEMBERS key
SMEMBERS1 zoo
1) "snake"
2) "tiger"
3) "lion"
用法: SINTER key [key …]
# 先在创建一个集合了来
> SADD zoo2 tiger lion cat dog
(integer) 4
# 求两个集合的交集
SINTER zoo1 zoo2
1) "tiger"
2) "lion"
用法: SUNION key [key …]
SUNION zoo1 zoo2
1) "lion"
2) "cat"
3) "snake"
4) "tiger"
5) "dog"
用法: SDIFF key [key …]
> SDIFF zoo1 zoo2
1) "snake"
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 2 32 − 1 2^{32} - 1 232−1 (4294967295, 每个集合可存储40多亿个成员)。
用法: ZADD key score member [score member …]
# 返回的也是添加的个数
> ZADD math_grade 88 "Nick" 99 "Alice" 55 "Tom"
(integer) 3
用法: ZRANGE key start stop [WITHSCORES]
# WITHSCORES 的作用是将分数一并显示出来,可以不加
> ZRANGE math_grade 0 10 WITHSCORES
1) "Tom"
2) "55"
3) "Nick"
4) "88"
5) "Alice"
6) "99"
用法: ZSCORE key member
> ZSCORE math_grade Tom
"55"
用法: ZCOUNT key min max
> ZCOUNT math_grade 0 59
(integer) 1
用法: ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
> ZRANGEBYSCORE math_grade 0 59
1) "Tom"
用法: KEYS pattern
pattern 支持模糊匹配,遵循glob风格
通配符 | 含义 |
---|---|
? | 匹配一个任意字符 |
* | 匹配任意多个任意字符 |
[] | 匹配括号间的一个字符,可以用 ‘-’ 表示一个范围 |
\ | 用于转义使用,如果要匹配 “?” 就可以使用 '?" 进行匹配 |
# 查看当前全部已经存在的键
> KEYS *
1) "zoo1"
2) "key1"
3) "math_grade"
4) "color"
5) "key2"
6) "zoo2"
7) "student1"
用法: EXISTS key [key …]
# 键存在返回 1,不存在返回 0
> EXISTS zoo1
(integer) 1
> EXISTS zoo3
(integer) 0
用法: DEL key [key …]
# 返回值为删除成功的键的个数
> DEL zoo1
(integer) 1
用法: 秒:EXPIRE key seconds,毫秒:PEXPIRE key milliseconds
# 如果重复设置会覆盖之前的设置
# 返回值为 1 表示成功,0 为键不存在
> EXPIRE key1 60
(integer) 1
> PEXPIRE key1 1000
(integer) 1
用法: 秒: TTL key, 毫秒: PTTL key
# 秒为单位
> EXPIRE zoo2 600
(integer) 1
> TTL zoo2
(integer) 597
# 毫秒为单位
> PTTL zoo2
(integer) 590512
用法: PERSIST key
> PERSIST zoo2
(integer) 1
# 返回负数表示不存在过期时间
> PTTL zoo2
(integer) -1
用法: TYPE key
> TYPE color
list
> TYPE student1
hash
用法: RENAME key newkey
> keys *
1) "math_grade"
2) "color"
3) "key2"
4) "zoo2"
5) "student1"
> RENAME color colors
OK
> keys *
1) "colors"
2) "math_grade"
3) "key2"
4) "zoo2"
5) "student1"
安装非常简单只需要一条命令即可完成安装
pip install redis
redis 提供两个类 Redis 和 StrictRedis, StrictRedis 用于实现大部分官方的命令,Redis 是 StrictRedis 的子类,用于向后兼用旧版本。
import redis
# decode_responses 的作用是将返回值进行解码,默认是返回字节类型
r = redis.Redis(host="192.168.96.131",
port=6379,
password="123456",
decode_responses=True)
import redis
redis.StrictRedis(host="192.168.96.131",
port=6379,
password="123456",
decode_responses=True)
查看源码我们发现两个类的内部都实现了 "__enter__“和”__exit__"方法,也就意味着更推荐我们使用python的with方法来实例化连接对象,这样发生异常就会自动为我们断开连接,从不不占用连接数,示例如下:
import redis
with redis.StrictRedis(host="192.168.96.131",
port=6379,
password="123456",
decode_responses=True) as R:
pass
import redis
pool = redis.ConnectionPool(host="192.168.96.131",
port=6379,
password="123456",
decode_responses=True)
with redis.StrictRedis(connection_pool=pool) as r:
pass
python 的 redis 库操作redis很简单,几乎就和我们前面所学的命令行中的操作一致,就先讲一些基础常见的API使用方法吧,如有另外的需求可以去看官方文档;
注: 下文的 R 就代表我们的连接Reidis后的对象
set() 方法
参数对照表
参数名 | 作用 |
---|---|
name | key 的名称 |
value | key 对应的值 |
ex | 过期时间(秒) |
px | 过期时间(毫秒) |
nx | 如果设置为True,则只有name不存在时,当前set操作才执行 |
xx | 如果设置为True,则只有name存在时,当前set操作才执行 |
示例:
# 创建一个字符串类型的 key-value 3秒后过期
R.set(name="test_key1", value="test",ex=3)
exists(*names)方法
传入一个键名或多个键名,返回是否存在这些键,如果存在就会返回True,如果不存在就会返回False
示例:
# 判断给定的键名是否已被创建
R.exists("test_key")
incr(name, amount=1)方法
将对应传入的键名对应的值自增,增加的值为amount的值,如果是键不存在,就会创建这个键,然后初始值就是amount的值,最后整个函数返回自增后值
示例:
# 创建或者增增一个键,自增的值为10
R.incr("test_incr",amount=10)
delete(*name)
根据传入的一个或者多个键名,然后再redis中删除这些键对应的键值对
示例:
# 删除两个键值对
R.delete("key1","key2")
expire(name,time)
将传入的键对应的键值对设置超时时间,如果到达超时时间,那么这个键值对就会自动删除,time为超时时间,单位为秒
示例:
# 将某个键设置10秒的超时时间
R.expire("test_key",time=10)
示意图:
我们的客户端首先发生并发任务,由于客户端需要的资源再整个集群中只有一件,这个资源在一个时间段内也只允许一个客户端使用.那么这些并发任务中的客户端就会去 ‘抢’ 这个资源,我们就要设定一个规则如果它们中有一个客户但一旦抢到了,那么就要通知其他的客户端资源已被占用,这时没抢到的客户端就会进入阻塞状态知道资源被释放,然后再开始竞争资源,整个锁的实现的详细流程就如下:
(1) 每个客户端向Redis中创建代表自己的键值对.键为整个Redis数据库中唯一的,且键是自增的(也就是先到的键就要小一点),
(2) 客户端再Redis中创建键值成功后,Redis会返回自己的键名给客户端,从图中可以看到:客户端1创建的键为002,客户端2创建的键为001,客户端3创建的键为003.
(3) 然后客户端会进行’自我检查’,判断自己注册的键是否为最小的,如果是就表示自己抢夺资源成功,然后立刻就就要设置自己在Redis上注册的这个键的过期时间.
(4) 其他没有抢夺成功的客户端就会监听比自己键小 1 的键,如果这个键消失了,就表示资源已被释放,那么自己就可以占用这个资源了.就图中而言:我们的锁被客户端2占用,这时客户端1监听客户端2,客户端3监听客户端1.
(5) 一直往返执行步骤 (4),知道所有客户端都已使用资源
(6) 此次并发任务结束
import redis
import time
import threading
LOCK_ACCESS_ID = "lock:access:id"
LOCK_REGISTER_KEY = "lock:register:id:"
redis_pool = redis.ConnectionPool(host="192.168.96.131",
port=6379,
password="123456",
decode_responses=True)
class DistributedLock(object):
def __init__(self, pool):
self.redis_connect = redis.StrictRedis(connection_pool=pool)
self.my_key = None
def get_lock(self, timeout=10):
"""
desc: 获取锁
:param timeout:获取锁之后的最大占用时间
:return: None
"""
# 向 redis 获取注册的 id (使用 lua 脚本执行从而实现原子性)
register_lock_lua = """
local register_id = redis.call("incr", KEYS[1])
redis.call("set",KEYS[2]..register_id,register_id)
return register_id
"""
# 初始化注册锁id的脚本
register_lock_id_script = self.redis_connect.register_script(register_lock_lua)
# 向脚本中传入参数,并执行脚本,返回注册的 锁id
lock_id = register_lock_id_script(keys=[LOCK_ACCESS_ID, LOCK_REGISTER_KEY])
# 初始化自己的锁对应的键名
self.my_key = "".join([LOCK_REGISTER_KEY, str(lock_id)])
# 当前自己需要监听的节点
listen_key = "".join([LOCK_REGISTER_KEY, str(lock_id - 1)])
# 判断监听的锁是否存在,如果已经消失则自己获得锁
# 并将自己对应在redis上的键值对设置过期时间
listen_lua = """
local exists = redis.call("exists", KEYS[1])
if exists then
redis.call("expire",KEYS[2],ARGV[1])
end
return exists
"""
listen_key_script = self.redis_connect.register_script(listen_lua)
# 判断比自己小 1 的键是否存在,如果存在就阻塞
while listen_key_script(keys=[listen_key, self.my_key], args=[timeout]):
time.sleep(1)
# print("do something")
def release(self):
# 判断自己注册的键是否还存在
if self.redis_connect.exists(self.my_key):
# 如果存在就删除这个键,表示主动释放锁
self.redis_connect.delete(self.my_key)
else:
# 如果键已经不存在了,那么表示我们的锁已经超时,资源被自动释放了
print("超时释放")
def close(self):
self.redis_connect.close()
# 定义装饰器,是得我们的锁的使用更加灵活
def add_lock(pool):
def wrapper1(func):
def wrapper2(*args, **kwargs):
dl = DistributedLock(pool)
try:
dl.get_lock()
func(*args, **kwargs)
finally:
dl.release()
dl.close()
return wrapper2
return wrapper1
# 调用装饰器,代表被装饰的这个函数需要锁
@add_lock(redis_pool)
def task(n):
print(f"task{n}xec")
time.sleep(2)
if __name__ == '__main__':
# 使用对线程模拟分布式环境的并发
task_pool = []
for i in range(10):
threading.Thread(target=task, args=(i,)).start()
y):
# 如果存在就删除这个键,表示主动释放锁
self.redis_connect.delete(self.my_key)
else:
# 如果键已经不存在了,那么表示我们的锁已经超时,资源被自动释放了
print("超时释放")
def close(self):
self.redis_connect.close()
# 定义装饰器,使得我们的锁的使用更加灵活
def add_lock(pool):
def wrapper1(func):
def wrapper2(*args, **kwargs):
dl = DistributedLock(pool)
try:
dl.get_lock()
func(*args, **kwargs)
finally:
dl.release()
dl.close()
return wrapper2
return wrapper1
# 调用装饰器,代表被装饰的这个函数需要锁
@add_lock(redis_pool)
def task(n):
print(f"task{n}xec")
time.sleep(2)
if __name__ == '__main__':
# 使用对线程模拟分布式环境的并发
task_pool = []
for i in range(10):
threading.Thread(target=task, args=(i,)).start()