第三十章:Redis

一:Redis

官网:https://redis.io/
中文网:https://www.redis.net.cn/tutorial/3501.html

1.Redis介绍

# 介绍: cs 架构的软件
	redis:
  	非关系型数据库【存数据的地方】nosql 数据库
    内存存储,速度非常快,可以持久化【数据从内存同步到硬盘】
    数据类型丰富【5大数据类型:字符串,列表,哈希(字典),集合,有序集合】
    key-value 形式存储【根本没有表的结构,相当于咱们的字典】
    
    -nosql:指非关系型数据库:1 不限于SQL  2 没有sql
    
# redis 为什么这么快
	-1 高性能的网络模型:IO多路复用的 epoll 模型,承载住非常高的并发量
  -2 纯内存操作,避免了很多 io
  -3 单线程架构,避免了线程间切换的消耗
    -6.x 之前:单线程,单进程
    -6.x 以后,多线程架构,数据操作还是使用单线程,别的线程做数据持久化,其他操作

2.Reids特性

# 特性 Redis 特性(8个)
# 速度快:10w ops(每秒 10w 读写),数据存在内存中,c 语言实现,单线程模型
# 持久化:rdb 和 aof
# 多种数据结构:
	5大数据结构
    BitMaps位图:布隆过滤器 本质是 字符串
    HyperLogLog:超小内存唯一值计数,12kb HyperLogLog 本质是 字符串
    GEO:地理信息定位 本质是有序集合
# 支持多种编程语言:基于 tcp 通信协议,各大编程语言都支持
# 功能丰富:发布订阅(消息) Lua 脚本,事务(pipeline)
# 简单:源代码几万行,不依赖外部库
# 主从复制:主服务器和从服务器,主服务器可以同步到从服务器中
# 高可用和分布式:
    2.8版本以后使用redis-sentinel支持高可用
    3.0版本以后支持分布式

3.应用场景

# 缓存系统:使用最广泛的就是缓存
# redis 应用场景(了解)
	1 当缓存数据库使用,接口缓存,提高接口响应速度
    -请求进到视图---》去数据查询[多表查询,去硬盘取数据:速度慢]----》转成json格式字符串---》返回给前端
    -请求进到视图---》去redis[内存]----》取json格式字符串---》返回给前端
  2 做计数器:单线程,不存在并发安全问题
    - 统计网站访问量
    - 个人站点浏览量
    - 文章阅读量
  3 去重操作:集合
  4 排行榜:有序集合
    - 阅读排行榜
    - 游戏金币排行榜
    - 推荐(销量高的,推荐)
  5 布隆过滤器
  6 抽奖
  7 消息队列:发布订阅,阻塞队列实现(简单的分布式,blpop:阻塞队列,生产者消费者) celery的消息队列
  8 地理位置信息:附近的人
  9 实时系统:垃圾邮件处理系统,黑白名单系统

4.Redis安装

# 官网:https://redis.io/
	-下载完是源代码:c语言源码:
    	https://redis.io/download/#redis-stack-downloads
  -公司里 5.x 比较多
  -最稳定 6.x
  -最新 7.x

# 中文网:http://redis.cn/download.html
	-上面最新只到5.x

# 默认端口
-h 127.0.0.1 
-p 6379
# 安装
$ wget http://download.redis.io/releases/redis-2.8.17.tar.gz
$ tar xzf redis-2.8.17.tar.gz
$ cd redis-2.8.17
$ make
# make 完后 redis-2.8.17目录下会出现编译后的redis服务程序redis-server,还有用于测试的客户端程序redis-cli
$ ./redis-server
# 注意这种方式启动redis 使用的是默认配置。也可以通过启动参数告诉redis使用指定配置文件使用下面命令启动。
$ ./redis-server redis.conf
# redis.conf是一个默认的配置文件。我们可以根据需要使用自己的配置文件。

# 启动redis服务进程后,就可以使用测试客户端程序redis-cli和redis服务交互了。 比如:
$ ./redis-cli
redis> set foo bar
OK
redis> get foo
"bar"

5.数据类型

1.String(字符串)

string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。

string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。

string类型是Redis最基本的数据类型,一个键最大能存储512MB。

在以上实例中我们使用了 Redis 的 SETGET 命令。键为 name,对应的值为redis.net.cn。

注意:一个键最大能存储512MB。

2.Hash(哈希)

Redis hash 是一个键值对集合。

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

redis 127.0.0.1:6379> HMSET user:1 username redis.net.cn password redis.net.cn points 200
OK
redis 127.0.0.1:6379> HGETALL user:1
1) "username"
2) "redis.net.cn"
3) "password"
4) "redis.net.cn"
5) "points"
6) "200"
redis 127.0.0.1:6379>

实例中我们使用了 Redis HMSET, HEGTALL 命令,user:1 为键值。

每个 hash 可以存储 232 - 1 键值对(40多亿)。

3.List(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。

列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。

4.Set(集合)

Redis的Set是string类型的无序集合。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

添加一个string元素到,key对应的set集合中,成功返回1,如果元素以及在集合中返回0,key对应的set不存在返回错误。

sadd key member
redis 127.0.0.1:6379> sadd redis.net.cn redis
(integer) 1
redis 127.0.0.1:6379> sadd redis.net.cn mongodb
(integer) 1
redis 127.0.0.1:6379> sadd redis.net.cn rabitmq
(integer) 1
redis 127.0.0.1:6379> sadd redis.net.cn rabitmq
(integer) 0
redis 127.0.0.1:6379> smembers redis.net.cn
 
1) "rabitmq"
2) "mongodb"
3) "redis"

注意:以上实例中 rabitmq 添加了两次,但根据集合内元素的唯一性,第二次插入的元素将被忽略。

集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

5.zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

zset的成员是唯一的,但分数(score)却可以重复。

添加元素到集合,元素在集合中存在则更新对应score

zadd key score member 
redis 127.0.0.1:6379> zadd redis.net.cn 0 redis
(integer) 1
redis 127.0.0.1:6379> zadd redis.net.cn 0 mongodb
(integer) 1
redis 127.0.0.1:6379> zadd redis.net.cn 0 rabitmq
(integer) 1
redis 127.0.0.1:6379> zadd redis.net.cn 0 rabitmq
(integer) 0
redis 127.0.0.1:6379> ZRANGEBYSCORE redis.net.cn 0 1000
 
1) "redis"
2) "mongodb"
3) "rabitmq"

二:redis 操作

# redis 是 key-value 形式存储
# redis 数据放在内存中,如果断电,数据丢失 ---> 需要有持久化的方案

# 5 种数据类型,value 类型
	-字符串:用的最多,做缓存;做计数器
  -列表:简单的消息队列
  -字典(hash):缓存
  -集合:去重
  -有序集合:排行榜
import redis

conn = redis.Redis()
conn.close()

1.字符串操作

# redis有5大数据类型 ---》字符串,hash,列表

# 主要的api:操作字符串的方法
'''
1 set(name, value, ex=None, px=None, nx=False, xx=False)
  setnx(name, value)
  psetex(name, time_ms, value)
2 mset(*args, **kwargs)
3 get(name)
5 mget(keys, *args)
6 getset(name, value)
7 getrange(key, start, end)
8 setrange(name, offset, value)
9 setbit(name, offset, value)
10 getbit(name, offset)
11 bitcount(key, start=None, end=None)
12 strlen(name)
13 incr(self, name, amount=1)
14 incrbyfloat(self, name, amount=1.0)
15 decr(self, name, amount=1)
16 append(key, value)
'''

# 你需要记住的
set、get、strlen、incr

1.1 set() 【常用】

# 在 Redis 中设置值,过期时间为 3s
# set(name, value, ex=None, px=None, nx=False, xx=False)
conn.set('hobby', '射箭', ex=3)
r.set('name', 'zhangsan')
'''
参数:
     set(name, value, ex=None, px=None, nx=False, xx=False)
     ex,过期时间(秒)
     px,过期时间(毫秒)
     nx,如果设置为True,则只有name不存在时,当前set操作才执行,同setnx(name, value)
     xx,如果设置为True,则只有name存在时,当前set操作才执行
'''
setex(name, value, time)
#设置过期时间(秒)

psetex(name, time_ms, value)
#设置过期时间(豪秒)
参数 说明 使用
ex 过期时间(秒) conn.set('hobby','篮球',ex=3)
px 过期时间(毫秒) conn.set('hobby','篮球',px=3)
nx 如果设置为True,则只有name不存在时,当前set操作才执行, 值存在,就修改不了,执行没效果 conn.set('name','lqz',nx=True)
conn.set('name','lqz',nx=False)
xx 如果设置为True,则只有name存在时,当前set操作才执行,值存在才能修改,值不存在,不会设置新值 conn.set('hobby','篮球',xx=True)
conn.set('hobby','篮球',xx=False)

1.2 mset()

mset(*args, **kwargs)
#批量设置值
r.mset(name1='zhangsan', name2='lisi')
#或
r.mget({"name1":'zhangsan', "name2":'lisi'})

1.3 get()/mget() 【常用】

get(name)
mget(keys, *args)
#批量获取
print(r.mget("name1","name2"))
#或
li=["name1","name2"]
print(r.mget(li))

1.4 getset()

getset(name, value)
# 设置新值,打印原值
print(r.getset("name1","wangwu")) #输出:zhangsan
print(r.get("name1")) #输出:wangwu

1.5 getrange()

getrange(key, start, end)
# 根据字节获取子序列
r.set("name","zhangsan")
print(r.getrange("name",0,3))#输出:zhan

1.6 setrange()

setrange(name, offset, value)
# 修改字符串内容,从指定字符串索引开始向后替换,如果新值太长时,则向后添加
r.set("name","zhangsan")
r.setrange("name",1,"z")
print(r.get("name")) #输出:zzangsan
r.setrange("name",6,"zzzzzzz")
print(r.get("name")) #输出:zzangszzzzzzz

1.7 setbit()

setbit(name, offset, value)
# 对二进制表示位进行操作
''' name:redis的name
    offset,位的索引(将值对应的ASCII码变换成二进制后再进行索引)
    value,值只能是 1 或 0 '''

str="345"
r.set("name",str)
for i in str:
    print(i,ord(i),bin(ord(i)))#输出 值、ASCII码中对应的值、对应值转换的二进制
'''
输出:
    3 51 0b110011
    4 52 0b110100
    5 53 0b110101'''

r.setbit("name",6,0)#把第7位改为0,也就是3对应的变成了0b110001
print(r.get("name"))#输出:145

1.8 getbit()

getbit(name, offset)
# 获取 name 对应值的二进制中某位的值(0或1)
r.set("name","3") # 对应的二进制0b110011
print(r.getbit("name",5))   #输出:0
print(r.getbit("name",6))   #输出:1

1.9 bitcount()

bitcount(key, start=None, end=None)
# 获取对应二进制中 1 的个数
r.set("name","345")#0b110011 0b110100 0b110101
print(r.bitcount("name",start=0,end=1)) #输出:7
''' key:Redis的name
    start:字节起始位置
    end:字节结束位置
'''

1.10 strlen()【常用】

strlen(name)
# 返回 name 对应值的字节长度(utf-8 汉字一般 3 个字节)
r.set("name","zhangsan")
print(r.strlen("name")) # 输出:8 字节

1.11 incr()【常用】

incr(self, name, amount=1)
# 自增 mount 对应的值,当 mount 不存在时,则创建 mount=amount,否则,则自增,amount 为自增数(整数)
print(r.incr("mount",amount=2)) # 输出:2
print(r.incr("mount")) # 输出:3
print(r.incr("mount",amount=3)) # 输出:6
print(r.incr("mount",amount=6)) # 输出:12
print(r.get("mount")) # 输出:12

1.12 incrbyfloat()

incrbyfloat(self, name, amount=1.0)
# 类似 incr() 自增,amount为自增数(浮点数)

1.13 decr()

decr(self, name, amount=1)
# 自减 name 对应的值,当 name 不存在时,则创建 name=amount,否则,则自减,amount为自增数(整数)

1.14 append()

append(key, value)
# 在 name 对应的值后面追加内容
r.set("name","zhangsan")
print(r.get("name"))    # 输出:zhangsan
r.append("name","lisi")
print(r.get("name"))    # 输出:zhangsanlisi

2.hash 操作

redis中的Hash 在内存中类似于一个name对应一个dic来存储

'''
1 hset(name, key, value)
2 hmset(name, mapping)
3 hget(name,key)
4 hmget(name, keys, *args)
5 hgetall(name)
6 hlen(name)
7 hkeys(name)
8 hvals(name)
9 hexists(name, key)
10 hdel(name,*keys)
11 hincrby(name, key, amount=1)
12 hincrbyfloat(name, key, amount=1.0)
13 hscan(name, cursor=0, match=None, count=None)
14 hscan_iter(name, match=None, count=None)
'''

记住:hset、hget、hexists、hincrby、hlen

2.1 hset()【常用】

hset(name, key, value)
#name对应的hash中设置一个键值对(不存在,则创建,否则,修改)
r.hset("dic_name","a1","aa")

2.2 hmset()

hmset(name, mapping)
#在name对应的hash中批量设置键值对,mapping:字典
dic={"a1":"aa","b1":"bb"}
r.hmset("dic_name",dic)
print(r.hget("dic_name","b1"))#输出:bb

2.3 hget()【常用】

r.hset("dic_name","a1","aa")
#在name对应的hash中根据key获取value
print(r.hget("dic_name","a1"))#输出:aa

2.4 hmget()

hmget(name, keys, *args)
# 在 name 对应的 hash 中获取多个 key 的值
li=["a1","b1"]
print(r.hmget("dic_name",li))
print(r.hmget("dic_name","a1","b1"))

2.5 hgetall(name)

res=conn.hgetall('hash_test')   # 可以,但是不好,一次性拿出,可能占很大内存
print(res)

2.6 hlen(name)【常用】

dic={"a1":"aa","b1":"bb"}
r.hmset("dic_name",dic)

#hlen(name) 获取hash中键值对的个数
print(r.hlen("dic_name"))

2.7 hkeys()/hvals()

# hkeys(name)
# hvals(name)

dic={"a1":"aa","b1":"bb"}
r.hmset("dic_name",dic)

# hkeys(name) 获取 hash 中所有的 key 的值
print(r.hkeys("dic_name"))

# hvals(name) 获取 hash 中所有的 value 的值
print(r.hvals("dic_name"))

2.8 hexists()【常用】

# hexists(name, key)
# 检查 name 对应的 hash 是否存在当前传入的 key
print(r.hexists("dic_name","a1")) # 输出:True

2.9 hdel()

# hdel(name,*keys)
# 删除指定 name 对应的 key 所在的键值对
r.hdel("dic_name","a1")

2.10 hincrby()【常用】

# hincrby(name, key, amount=1)
# 自增 hash 中 key 对应的值,不存在则创建 key=amount(amount 为整数)
print(r.hincrby("demo","a",amount=2))

2.11 hincrbyfloat()

# hincrbyfloat(name, key, amount=1.0)
# 与 hincrby() 类似。自增 hash 中 key 对应的值,不存在则创建 key=amount(amount 为浮点数)

2.12 hscan()

# hscan(name, cursor=0, match=None, count=None)
# cursor - 游标。
# pattern - 匹配的模式。
# count - 指定从数据集里返回多少元素,默认值为 10

res = conn.hscan('hash_test', cursor=0, count=5)
print(len(res[1])) #(数字,拿出来的10条数据)   数字是下一个游标位置

2.13 hscan_iter()

# hscan_iter(name, match=None, count=None)
res=conn.hscan_iter('hash_test',count=10)
print(res)  # generator 只要函数中有 yield 关键字,这个函数执行的结果就是生成器 ,生成器就是迭代器,可以被 for 循环

3.列表操作

redis 中的 List 在在内存中按照一个 name 对应一个List来存储

'''
1 lpush(name,values)
2 lpushx(name,value)
3 rpushx(name, value) 表示从右向左操作
4 llen(name)
5 linsert(name, where, refvalue, value))
6 lset(name, index, value)
7 lrem(name, value, num)
8 lpop(name)
9 lindex(name, index)
10 lrange(name, start, end)
11 ltrim(name, start, end)
12 rpoplpush(src, dst)
13 blpop(keys, timeout)
14 brpoplpush(src, dst, timeout=0)
15 自定义增量迭代
'''
记住:lpush、lpushx、rpushx、llen、lpop

3.1 lpush()【常用】

lpush(name, values)
# 在 name 对应的 list 中添加元素,每个新的元素都添加到列表的最左边
r.lpush("list_name",2)
r.lpush("list_name",3,4,5) # 保存在列表中的顺序为 5,4,3,2

3.2 rpush()

# 同 lpush,但每个新的元素都添加到列表的最右边

3.3 lpushx()

# 在 name 对应的 list 中添加元素,只有 name 已经存在时,值添加到列表的最左边

3.4 rpushx()

# 在 name 对应的 list 中添加元素,只有 name 已经存在时,值添加到列表的最右边

3.5 llen(name)【常用】

# name 对应的 list 元素的个数
print(r.llen("list_name"))

3.6 linsert()

# linsert(name, where, refvalue, value)
# 在 name 对应的列表的某一个值前或后插入一个新值
r.linsert("list_name", "BEFORE", "2", "SS")#在列表内找到第一个元素 2,在它前面插入SS
参数 说明
name redis的name
where BEFORE(前)或AFTER(后)
refvalue 列表内的值
value 要插入的数据

3.7 r.lset()

# r.lset(name, index, value)
# 对 list 中的某一个索引位置重新赋值
r.lset("list_name", 0, "bbb")

3.8 r.lrem()

# r.lrem(name, value, num)
#删除name对应的list中的指定值
r.lrem("list_name", "SS", num=0)
参数 说明
name redis的name
value 要删除的值
num num=0 删除列表中所有的指定值
num=2 从前到后,删除2个
num=-2 从后向前,删除2个

3.9 lpop(name)【常用】

# lpop(name)
# 移除列表的左侧第一个元素,返回值则是第一个元素
print(r.lpop("list_name"))

3.10 rpop(name)

# 通 lpop,rpop(name) 表示从右向左操作

3.11 lindex()

# lindex(name, index)
# 根据索引获取列表内元素
print(r.lindex("list_name", 1))

3.12 lrange()【常用】

# lrange(name, start, end)
# 分片获取元素
print(r.lrange("list_name", 0, -1))

3.13 ltrim()

# ltrim(name, start, end)
# 移除列表内没有在该索引之内的值
r.ltrim("list_name", 0, 2)

3.14 rpoplpush()

# rpoplpush(src, dst)
# 从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
# src 要取数据的列表
# dst 要添加数据的列表

3.15 blpop()

# blpop(keys, timeout)
# 记住 ,可以做消息队列使用  阻塞式弹出,如果没有,就阻塞
res=conn.blpop('boys')
print(res)

3.16 r.brpop()

# r.brpop(keys, timeout),从右向左获取数据
while True:
    print(r.blpop(["list_name","list_name1"],timeout=0))
    print(r.lrange("list_name",0,-1),r.lrange("list_name1",0,-1))

'''keys: redis的name的集合
   timeout: 超时时间,获取完所有列表的元素之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞'''

3.17 brpoplpush()

# brpoplpush(src, dst, timeout=0)

5.Set 有序集合

Set 集合就是不允许重复的列表

有序集合:

  在集合的基础上,为每元素排序,元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序。

1.zrange() 参数

参数 描述
name redis的name
start 有序集合索引起始位置
end 有序集合索引结束位置
desc 排序规则,默认按照分数从小到大排序
withscores 是否获取元素的分数,默认只获取元素的值
score_cast_func 对分数进行数据转换的函数

2.zinterstore() 参数

r.zadd("zset_name", "a1", 6, "a2", 2,"a3",5)
r.zadd('zset_name1', a1=7,b1=10, b2=5)

# 获取两个有序集合的交集并放入dest集合,如果遇到相同值不同分数,则按照aggregate进行操作
# aggregate的值为: SUM  MIN  MAX
r.zinterstore("zset_name2",("zset_name1","zset_name"),aggregate="MAX")
print(r.zscan("zset_name2"))

6.通用操作

通用操作,不指定类型,所有类型都支持

'''
1 delete(*names)
2 exists(name)
3 keys(pattern='*')
4 expire(name ,time)
5 rename(src, dst)
6 move(name, db))
7 randomkey()
8 type(name)
'''
方法 使用 说明
delete(*names) 根据name删除redis中的任意数据类型
exists(name) 检测redis的name是否存在
keys(pattern='*') 根据* ?等通配符匹配获取redis的name
expire(name ,time) 为某个name设置超时时间
rename(src, dst) 重命名
move(name, db)) 将redis的某个值移动到指定的db下
randomkey() 随机获取一个redis的name(不删除)
type(name) 获取name对应值的类型
# 1 delete(*names)
# conn.delete('name', 'userinfo2')
# conn.delete(['name', 'userinfo2'])  # 不能用它
# conn.delete(*['name', 'userinfo2'])  # 可以用它


# 2 exists(name)
# res=conn.exists('userinfo')
# print(res)


# 3 keys(pattern='*')
# res=conn.keys('w?e')  #  ?表示一个字符,   * 表示多个字符
# print(res)


# 4 expire(name ,time)
# conn.expire('userinfo',3)

# 5 rename(src, dst)
# conn.rename('hobby','hobby111')

# 6 move(name, db))
# conn.move('hobby111',8)
# 7 randomkey()
# res=conn.randomkey()
# print(res)
# 8 type(name)
# print(conn.type('girls'))
print(conn.type('age'))
conn.close()

7.redis 管道

# mysql事务:四大特性   事务的隔离级别  mysql5.7 默认的隔离级别是什么
# 事务---》四大特性:
	-原子性
    -一致性
    -隔离性
    -持久性
    
# redis:redis数据库,是否支持事务?
	-支持
    -不支持
    
# redis事务机制可以保证一致性和隔离性,无法保证持久性,但是对于redis而言,本身是内存数据库,所以持久化不是必须属性。原子性需要自己进行检查,尽可能保证


# redis 不像mysql一样,支持强事务,事务的四大特性不能全部满足,但是能满足一部分,通过redis的管道实现的
# redis本身不支持事务,但是可以通过管道,实现部分事务

 
# redis 通过管道,来保证命令要么都成功,要么都失败,完成事务的一致性,但是管道只能用在单实例,集群环境中,不支持 pipline
import redis

conn = redis.Redis()
# 输入值
# conn.set('zhangsan', 500)
# conn.set('lisi', 500)

# 建立 redis 管道
p = conn.pipeline()
# 开启管道
p.multi()

# 张三减 125,李四加 125
p.decr('zhangsan', 125)
# raise Exception('抛出异常')
p.incr('lisi', 125)

# 关闭管道,关闭 Redis()
p.execute()
conn.close()

参考链接:
https://www.cnblogs.com/melonjiang/p/5342505.html

https://www.cnblogs.com/liuqingzheng/articles/9833534.html

三:python操作

安装redis 模块:pip install redis

1.快速使用

连接取值

# 1 导入模块的 Redis 类
from redis import Redis

# 2 实例化得到对象
conn = Redis(host='127.0.0.1', port=6379)

# 3 使用 conn,操作 redis
# 获取 name 的值
# res = conn.get('name')  # 返回数据是 bytes 格式

# 4 设置值
conn.set('age',19)


conn.close()

使用线程池

# one.py  单例模式文件

import redis

POOL = redis.ConnectionPool(max_connections=100, host='127.0.0.1', port=6379)
# redis_test.py		执行文件

import redis
from threading import Thread
from one import POOL


def get_name_from_redis():
    # 做成模块后,导入,无论导入多少次,导入的都那一个POOL对象
    # 设置的 max_connections 太大,会导致报错,原因是:拿连接,池里不够了,没有等待,线程报错。处理:设置等待,参数
    conn = redis.Redis(connection_pool=POOL)
    res = conn.get('name')
    print(res)
    conn.close()
    
for i in range(20):
    t = Thread(target=get_name_from_redis)
    t.start()

2.django 中集成 redis

2.1 直接使用

# 方式一:直接使用
from user.POOL import pool
import redis
def index(request):
    conn = redis.Redis(connection_pool=pool)
    conn.incr('page_view')
    res = conn.get('page_view')
    return HttpResponse('被你看了%s次' % res)

2.2 django 方案【推荐】

方式二:django 的缓存使用 redis 【推荐使用】,存储的内容会放到一个文件夹内。

  • 导入 django 缓存:from django.core.cache import cache
  • 在使用 redis 的地方:cache.set('count', res + 1)
# settings.py 中配置
CACHES = {
	"default": {
		"BACKEND": "django_redis.cache.RedisCache",
		"LOCATION": "redis://127.0.0.1:6379",
		"OPTIONS": {
			"CLIENT_CLASS": "django_redis.client.DefaultClient",
			"CONNECTION_POOL_KWARGS": {"max_connections": 100}
			# "PASSWORD": "123",
		}
	}
}
# urls.py 
from django.urls import path
from testapp.views import RedisView
urlpatterns = [
    path('redis/', RedisView.as_view())
]
# views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from django.core.cache import cache


class RedisView(APIView):
    def get(self, request):
        cache.set('Django', 'this is Django!!!', 15)

        name = cache.get('Django')
        return Response({'name': name})

2.3 django-redis

使用第三方 django-redis 模块,存储的内容会直接存在库下。

# urls.py 
from django.urls import path
from testapp.views import RedisView
urlpatterns = [
    path('redis/', RedisView.as_view())
]
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django_redis import get_redis_connection


class RedisView(APIView):
    def get(self, request):
        conn = get_redis_connection()
        conn.set('price', 56.23)
        price = conn.get('price')
        return Response({'price': price})

四:linux操作

1.安装

# 1 下载(源代码,c)
wget http://download.redis.io/releases/redis-6.2.9.tar.gz

# 2 解压
tar -xzf redis-6.2.9.tar.gz

# 3 建立软连接
ln -s redis-6.2.9 redis
cd redis
make&&make install

# 4 在 src 目录下可以看到
    # redis-server--->redis 服务器
    # redis-cli---》redis 命令行客户端
    # redis-benchmark---》redis 性能测试工具
    # redis-check-aof--->aof 文件修复工具
    # redis-check-rdb---》rdb 文件检查工具
    # redis-sentinel---》sentinel 服务器,哨兵
    # redis 作者对 windows 维护不好,window 自己有安装包
    
    
##### 卸载 redis
# 1、查看 redis 进程;
ps -aux|grep redis
# 2、kill 掉进程;
kill 进程id
# 3、进入到 redis 目录
cd /usr/local/
# 4、删除 redis 对应的文件
rm -f /usr/local/redis/bin/redis*
rm -f /usr/local/bin/redis*
# 5、删除对应的文件
rm -rf redis

2.启动方式

2.1最简启动

# 最简启动
redis-server
ps -ef|grep redis  				# 查看进程
#yum install net-tools -y
netstat -antpl|grep redis 		# 查看端口
redis-cli -h ip -p port ping 	# 命令查看

2.2 动态参数启动

# 动态参数启动
redis-server --port 6380	# 启动,监听 6380 端口

2.3 配置文件启动

# 默认配置文件:redis.conf

# 配置文件启动(6379对应手机按键MERZ,意大利女歌手Alessia Merz的名字)

##### 通过 redis-cli 连接,输入 config get * 可以获得默认配置
# 在 redis 目录下创建 config 目录,copy 一个 redis.conf 文件
# daemonize ---》是否是守护进程启动(no|yes)
# port ---》端口号
# logfile ---》redis 系统日志
# dir ---》redis 工作目录


daemonize yes
pidfile /var/run/redis.pid
port 6379
dir "/root/redis/data"
logfile 6379.log
# 创建data文件夹
mkdir data
# 启动
redis-server redis.conf

3.客户端连接命令

redis-cli -h 地址 -p 端口
redis-cli -h 127.0.0.1 -p 6379 info  # 直接返回redis的运行信息---》redis监控平台

CONFIG GET *  		# 获取 redis 的配置信息
# 296 对配置文件,只需要关注咱们讲过的即可 dir,port,daemonize
# requirepass 设置的密码是什么,如果是空,没有密码
# 直接修改 redis 的配置信息,修改后同步到硬盘,永久生效
CONFIG SET requirepass 123456  		# 密码设置上了
CONFIG REWRITE 		# 写到硬盘上了

# 下次再连接,必须使用如下两种方式
	-方式一:直接连接,指定密码:redis-cli -a 123456
  -方式二:先连接进去redis-cli,再执行r  auth 密码
    

# 注意的 redis 配置文件:
# bind    			127.0.0.1  0.0.0.0
# port   				端口
# requirepass  	密码
# dir  					工作目录
# logfile 			日志文件
# daemonize 		是否以守护进程运行
# pidfile 			放的是进程 id 号
# maxmemory 		可以使用的最大内存
# databases  		有多少个库
# dbfilename  	rdb 的持久化方案的存储文件 dump.rdb

4. redis通用命令

# 算法性能衡量标准: 大O表示法
	时间:运行算法的实际     o(1)  o(log n)   o(n)   o(n方)
  空间:运行算法占用的内存  o(1)  o(log n)   o(n)   o(n方)

####1-keys 
#打印出所有 key
keys * 
# 打印出所有以 he 开头的 key
keys he*
# 打印出所有以 he 开头,第三个字母是 h 到 l 的范围
keys he[h-l]
# 三位长度,以he开头,?表示任意一位
keys he?
# keys命令一般不在生产环境中使用,生产环境 key 很多,时间复杂度为o(n),用 scan 命令

####2-dbsize   计算 key 的总数
dbsize 	# redis 内置了计数器,插入删除值该计数器会更改,所以可以在生产环境使用,时间复杂度是o(1)

###3-exists key 时间复杂度o(1)
# 设置a
set a b
# 查询key:a
get a
#查看a是否存在
exists a
(integer) 1
#存在返回1 不存在返回0

###4-del key  时间复杂度o(1)
删除成功返回1,key不存在返回0

###5-expire key seconds  时间复杂度o(1)
expire name 3 	# 3s 过期
ttl name  		# 查看 name 还有多长时间过期
persist name 	# 去掉 name 的过期时间

###6-type key  时间复杂度o(1)
type name	# 查看 name 类型,返回 string

### 7 其他
info 命令:内存,cpu,主从相关
client list  	正在连接的会话
client kill 	ip:端口
dbsize  			总共有多少个 key
flushall  		清空所有
flushdb  			只清空当前库
select 数字  	 选择某个库  总共 16 个库
monitor  			记录操作日志,夯住

5.数据结构和内部编码

# redis 的跳跃表吗?
	-他是 reids 的有序集合类型的底层实现
    
 #单线程为什么这么快
    1 纯内存
    2 非阻塞IO (epoll),自身实现了事件处理,不在网络io上浪费过多时间
    3 避免线程间切换和竞态消耗

6.redis类型

6.1 redis字符串类型

# 字符串 key value 不能大于 512m,一般建议 100k 以内

###1---基本使用get,set,del
get name       #时间复杂度 o(1)
set name lqz   #时间复杂度 o(1)
del name       #时间复杂度 o(1)

###2---其他使用incr,decr,incrby,decrby
incr age  # 对 age 这个 key 的 value 值自增1
decr age  # 对 age 这个 key 的 value 值自减1
incrby age 10  # 对 age 这个 key 的 value 值增加 10
decrby age 10  # 对 age 这个 key 的 value 值减 10
# 统计网站访问量(单线程无竞争,天然适合做计数器)
# 缓存 mysql 的信息(json 格式)
# 分布式 id 生成(多个机器同时并发着生成,不会重复)

###3---set,setnx,setxx
set name lqz  		# 不管 key 是否存在,都设置 
setnx name lqz 		# key 不存在时才设置(新增操作)
set name lqz nx 	# 同上
set name lqz xx 	# key 存在,才设置(更新操作)

###4---mget mset
mget key1 key2 key3     # 批量获取key1,key2.。。时间复杂度o(n)
mset key1 value1 key2 value2 key3 value3    # 批量设置时间复杂度o(n)
# n 次 get 和 mget 的区别
# n 次 get 时间=n次命令时间 +n 次网络时间
# mget 时间=1 次网络时间 +n 次命令时间

###5---其他:getset,append,strlen
getset name lqznb # 设置新值并返回旧值 时间复杂度o(1)
append name 666 # 将 value 追加到旧的 value 时间复杂度o(1)
strlen name  # 计算字符串长度(注意中文)  时间复杂度o(1)

###6---其他:incrybyfloat,getrange,setrange
increbyfloat age 3.5  		# 为 age 自增3.5,传负值表示自减 时间复杂度o(1)
getrange key start end 		# 获取字符串制定下标所有的值  时间复杂度o(1)
setrange key index value 	# 从指定 index 开始设置 value 值  时间复杂度o(1)

6.2 哈希类型

###1---hget,hset,hdel
hget key field  #获取hash key对应的field的value 时间复杂度为 o(1)
hset key field value #设置hash key对应的field的value值 时间复杂度为 o(1)
hdel key field #删除hash key对应的field的值 时间复杂度为 o(1)
#测试
hset user:1:info age 23
hget user:1:info ag
hset user:1:info name lqz
hgetall user:1:info
hdel user:1:info age
###2---hexists,hlen
hexists key field  #判断hash key 是否存在field 时间复杂度为 o(1)
hlen key   #获取hash key field的数量  时间复杂度为 o(1)
hexists user:1:info name
hlen user:1:info  #返回数量
        
###3---hmget,hmset
hmget key field1 field2 ...fieldN  #批量获取hash key 的一批field对应的值  时间复杂度是o(n)
hmset key field1 value1 field2 value2  #批量设置hash key的一批field value 时间复杂度是o(n)

###4--hgetall,hvals,hkeys
hgetall key  #返回hash key 对应的所有field和value  时间复杂度是o(n)
hvals key   #返回hash key 对应的所有field的value  时间复杂度是o(n)
hkeys key   #返回hash key对应的所有field  时间复杂度是o(n)
###小心使用hgetall
##1 计算网站每个用户主页的访问量
hincrby user:1:info pageview count
##2 缓存mysql的信息,直接设置hash格式



##其他操作 hsetnx,hincrby,hincrbyfloat
hsetnx key field value #设置hash key对应field的value(如果field已存在,则失败),时间复杂度o(1)
hincrby key field intCounter #hash key 对英的field的value自增intCounter 时间复杂度o(1)
hincrbyfloat key field floatCounter #hincrby 浮点数 时间复杂度o(1)

6.3 列表类型

# 插入操作

#rpush 从右侧插入
rpush key value1 value2 ...valueN  #时间复杂度为o(1~n)
#lpush 从左侧插入

#linsert
linsert key before|after value newValue   #从元素value的前或后插入newValue 时间复杂度o(n) ,需要遍历列表
linsert listkey before b java
linsert listkey after b php

# 删除操作

lpop key #从列表左侧弹出一个item 时间复杂度o(1)

rpop key #从列表右侧弹出一个item 时间复杂度o(1)

lrem key count value
#根据count值,从列表中删除所有value相同的项 时间复杂度o(n)
1 count>0 从左到右,删除最多count个value相等的项
2 count<0 从右向左,删除最多 Math.abs(count)个value相等的项
3 count=0 删除所有value相等的项
lrem listkey 0 a #删除列表中所有值a
lrem listkey -1 c #从右侧删除1个c

ltrim key start end #按照索引范围修剪列表 o(n)
ltrim listkey 1 4 #只保留下表1--4的元素

# 查询操作
lrange key start end #包含end获取列表指定索引范围所有item  o(n)
lrange listkey 0 2
lrange listkey 1 -1 #获取第一个位置到倒数第一个位置的元素

lindex key index #获取列表指定索引的item  o(n)
lindex listkey 0
lindex listkey -1

llen key #获取列表长度

# 修改操作

lset key index newValue #设置列表指定索引值为newValue o(n)
lset listkey 2 ppp #把第二个位置设为ppp

# 实战

实现timeLine功能,时间轴,微博关注的人,按时间轴排列,在列表中放入关注人的微博的即可


# 其他操作

blpop key timeout #lpop的阻塞版,timeout是阻塞超时时间,timeout=0为拥有不阻塞 o(1)
brpop key timeout #rpop的阻塞版,timeout是阻塞超时时间,timeout=0为拥有不阻塞 o(1)

#要实现栈的功能
lpush+lpop
#实现队列功能
lpush+rpop
#固定大小的列表
lpush+ltrim
#消息队列
lpush+brpop

6.4 集合类型

sadd key element #向集合key添加element(如果element存在,添加失败) o(1)
srem key element #从集合中的element移除掉 o(1)
scard key #计算集合大小
sismember key element #判断element是否在集合中
srandmember key count #从集合中随机取出count个元素,不会破坏集合中的元素
spop key #从集合中随机弹出一个元素
smembers key #获取集合中所有元素 ,无序,小心使用,会阻塞住 


sdiff user:1:follow user:2:follow  #计算user:1:follow和user:2:follow的差集
sinter user:1:follow user:2:follow  #计算user:1:follow和user:2:follow的交集     
sunion user:1:follow user:2:follow  #计算user:1:follow和user:2:follow的并集
                
   
sdiff|sinter|suion + store destkey... #将差集,交集,并集结果保存在destkey集合中


# 应用场景
去重场景
抽奖系统 :通过spop来弹出用户的id,活动取消,直接删除
点赞,点踩,喜欢等,用户如果点了赞,就把用户id放到该条记录的集合中
标签:给用户/文章等添加标签,sadd user:1:tags 标签1 标签2 标签3
给标签添加用户,关注该标签的人有哪些
共同好友:集合间的操作

6.5 有序集合(zset)

# 特点:不能重复 有一个分值字段,来保证顺序
key                  score                value
user:ranking           1                   lqz
user:ranking           99                  lqz2
user:ranking           88                  lqz3
#集合有序集合
集合:无重复元素,无序,element
有序集合:无重复元素,有序,element+score
#列表和有序集合
列表:可以重复,有序,element
有序集合:无重复元素,有序,element+score

#  API使用
zadd key score element #score可以重复,可以多个同时添加,element不能重复 o(logN) 
zrem key element #删除元素,可以多个同时删除 o(1)
zscore key element #获取元素的分数 o(1)
zincrby key increScore element #增加或减少元素的分数  o(1)
zcard key #返回元素总个数 o(1)
zrank key element #返回element元素的排名(从小到大排)

zrange key 0 -1 #返回排名,不带分数  o(log(n)+m) n是元素个数,m是要获取的值
zrange player:rank 0 -1 withscores #返回排名,带分数

zrangebyscore key minScore maxScore #返回指定分数范围内的升序元素 o(log(n)+m) n是元素个数,m是要获取的值
zrangebyscore user:1:ranking 90 210 withscores #获取90分到210分的元素

zcount key minScore maxScore #返回有序集合内在指定分数范围内的个数 o(log(n)+m)

zremrangebyrank key start end #删除指定排名内的升序元素 o(log(n)+m)
zremrangebyrank user:1:rangking 1 2 #删除升序排名中1到2的元素
        
zremrangebyscore key minScore maxScore #删除指定分数内的升序元素 o(log(n)+m)
zremrangebyscore user:1:ranking 90 210 #删除分数90到210之间的元素

# 实战

排行榜:音乐排行榜,销售榜,关注榜,游戏排行榜

#其他操作
zrevrank #返回某个元素从高到低排序的顺序 
#zrevrank girls dlrb  返回迪丽热巴 按分数降序排的排名
zrevrange #从高到低排序取一定范围
zrevrangebyscore #返回指定分数范围内的降序元素

zinterstore #对两个有序集合交集
zunionstore #对两个有序集合求并集

7.慢查询

单线程架构,命令一个个执行,会有长慢命令,造成整个 redis 的阻塞。redis 提供一种方式,可以记录长慢命令【放到慢查询队列中】,用于后续的排查修改工作

# 配置慢查询
	-slowlog-max-len :慢查询队列的长度
    -slowly-log-slower-than :超过多少微妙,就算慢命令,就会记录到慢查询队列中
    
# 实战
config set slowlog-log-slower-than 0
config set slowlog-max-len 100
config rewrite  	# 写了永久生效,如果不写,只是暂时生效

# 查看慢查询队列
slowlog len 	# 获取慢查询队列长度
slowlog reset 	# 清空慢查询队列
slowlog get  	# 获取慢查询队列的所有命令

五:redis高级

1.pipeline与事务

Redis 的 pipeline(管道)功能在命令行中没有,但 redis 是支持 pipeline 的,而且在各个语言版的 client 中都有相应的实现(Redis 模块)

将一批命令,批量打包,在 redis 服务端批量计算(执行),然后把结果批量返回

1 次 pipeline(n 条命令)=1次网络时间 +n 次命令时间

# python 实现 pipline
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379)
r = redis.Redis(connection_pool=pool)

# 创建 pipeline
pipe = r.pipeline(transaction=True)

# 开启事务
pipe.multi()
pipe.set('name', 'lqz')

# 其他代码,可能出异常
pipe.set('role', 'nb')
pipe.execute()

# redis 原生实现事务 实现事务 mutil
# 1 mutil 开启事务,放到管道中一次性执行
multi   # 开启事务
set name lqz
set age 18
exec



# 2 模拟实现乐观锁  watch+multi 实现乐观锁
# 在开启事务之前,先 watch
watch age
multi
decr age
exec

# 另一台机器
multi
decr age
exec  # 先执行,上面的执行就会失败(乐观锁,被 wathc 的事务不会执行成功)

2.发布订阅

# 发布订阅是 观察者模式 :只要订阅了某个东西,这个东西发送变化,我们就能收到
发布者发布了消息,所有的订阅者都可以收到,就是生产者消费者模型(后订阅了,无法获取历史消息)

# 一个客户端发送消息
publish ysg hello  # 只要有订阅者,客户端发送消息,所有订阅者都能收到

# 另外两个客户端,订阅频道,等待接收消息
subscribe ysg

# 查看某个频道有几个订阅者
pubsub numsub ysg

# 列出活跃的频道
pubsub channels

# 发布订阅和消息队列
发布订阅数全收到,消息队列有个抢的过程,只有一个抢到

3.Bitmap位图

Bitmap位图:是字符串类型,但是以二进制形式存储的,获取,设置某个二进制位的

# set hello big
# getbit hello 0/1/2  返回比特位是0或1

# setbit hello 7 1   把第7,也就是8个比特位设置为1 
# big就变成了cit
# bitcount hello  0 1  字节数  返回8

# 独立用户统计
	-假设:1亿用户,5千万活跃用户     统计今天活跃用户是多选   用户iduserid是整形,32位整型
  -int32 类型  4个字节表示一个数字---》 正负 2的31次方-1 的范围
      1    4个字节
        1001 4个字节
  -方式一:登录,把id放到集合中---》统计集合大小
  -方式二:登录,操作位图,把id对应的数字设为1 ,以后统计1的个数
        

4.HyperLogLog

redis中支持这种算法,基于HyperLogLog算法:极小的空间完成独立数量统计
很类似于布隆过滤器

pfadd key element # 向hyperloglog添加元素,可以同时添加多个
pfcount key #计算hyperloglog的独立总数

pfadd uuids "uuid1" "uuid2" "uuid3" "uuid4" #向uuids中添加4个uuid
pfcount uuids #返回4


#也可以做独立用户统计

5.GEO地理位置信息

# GEO(地理信息定位):存储经纬度,计算两地距离,范围等
	-根据经纬度---》确定具体地址的---》高德开放api---》返回具体地址
    
    
# redis 可以存储经纬度,存储后可以做运算,
	比如:两个经纬度之间距离 (直线距离)
    比如:统计某个经纬度范围内有哪些好友,餐馆
	
    
    
# 经纬度如何获取
	-跟后端没关系:只需要存
    -app,有定位功能
    -网页,集成了高德地图,定位功能
    
    
# redis存储
geoadd key  经度  纬度 名字
# 添加
geoadd cities:locations 116.28 39.55 beijing
# 查看位置信息
geopos cities:locations beijing #获取北京地理信息
    
#计算两个点距离
geodist cities:locations beijing tianjin km
    
# 计算附近的 xx
georadiusbymember cities:locations beijing 150 km
    
# 5大数据类型的 : 有序集合

6.持久化方案

# 什么是持久化
redis的所有数据保存在内存中,把内存中的数据同步到硬盘上这个过程称之为持久化

# 持久化的实现方式
    快照:某时某刻数据的一个完成备份
       -mysql的Dump
       -redis的RDB
    写日志:任何操作记录日志,要恢复数据,只要把日志重新走一遍即可
      -mysql的 Binlog
      -Redis的 AOF

1.1 RDB

# rdb 持久化配置方式
	-方式一:通过命令 ---》同步操作
    	save:生成 rdb 持久化文件
    -方式二:异步持久化 ---》不会阻塞住其他命令的执行
    	bgsave
    -方式三:配置文件配置 ---》这个条件触发,就执行 bgsave
        save   900        1
        save   300        10
        save   60         10000
        dbfilename dump.rdb
        dir "/root/redis-6.2.9/data"
        如果 60s 中改变了 1w 条数据,自动生成 rdb
        如果 300s 中改变了 10 条数据,自动生成 rdb
        如果 900s 中改变了 1 条数据,自动生成 rdb

1.2 AOF

# 可能会数据丢失 ---》可以使用 AOF 方案

# aof 是什么:客户端每写入一条命令,都记录一条日志,放到日志文件中,如果出现宕机,可以将数据完全恢复

# AOF的三种策略
	日志不是直接写到硬盘上,而是先放在缓冲区,缓冲区根据一些策略,写到硬盘上
    always:redis–》写命令刷新的缓冲区—》每条命令fsync到硬盘—》AOF文件
    everysec(默认值):redis——》写命令刷新的缓冲区—》每秒把缓冲区fsync到硬盘–》AOF文件
    no:redis——》写命令刷新的缓冲区—》操作系统决定,缓冲区fsync到硬盘–》AOF文件
 
# AOF重写
	随着命令的逐步写入,并发量的变大, AOF文件会越来越大,通过AOF重写来解决该问题
    本质就是把过期的,无用的,重复的,可以优化的命令,来优化,这样可以减少磁盘占用量,加速恢复速度
    
# AOF重写配置参数
	auto-aof-rewrite-min-size:500m
    auto-aof-rewrite-percentage:增长率
        
        
# aof 持久化的配置
appendonly yes #将该选项设置为 yes,打开
appendfilename "appendonly.aof" #文件保存的名字
appendfsync everysec #采用第二种策略
no-appendfsync-on-rewrite yes #在 aof 重写的时候,是否要做 aof 的 append 操作,因为 aof 重写消耗性能,磁盘消耗,正常 aof 写磁盘有一定的冲突,这段期间的数据,允许丢失

1.3 混合持久化

# 可以同时开启 aof 和 rdb,他们是相互不影响的

# redis 4.x以后,出现了混合持久化,其实就是 aof+rdb,解决恢复速度问题

# 开启了混合持久化,AOF在重写时,不再是单纯将内存数据转换为 RESP 命令写入 AOF 文件,而是将重写这一刻之前的内存做 RDB 快照处理

# 配置参数:必须先开启 AOF
# 开启 aof
appendonly yes

# 开启 aof 复写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 开启 混合持久化
aof-use-rdb-preamble yes  # 这正有用的是这句话

# 关闭 rdb
save ""



# aof 重写可以使用配置文件触发,也可以手动触发:bgrewriteaof

7.主从复制原理和方案

# 为什么需要主从
可能会有以下问题:
	1 机器故障
    2 容量瓶颈
    3 QPS瓶颈
    
# 主从解决了 qps 问题,机器故障问题
# 主从实现的功能
	一主一从,一主多从
    做读写分离
    做数据副本
    提高并发量

一个 master 可以有多个 slave
一个 slave 只能有一个 master
数据流向是单向的,从 master 到 slave,从库只能读,不能写,主库既能读又能写


# redis 主从赋值流程,原理
1. 副本(从)库通过 slaveof 127.0.0.1 6379 命令,连接主库,并发送 SYNC 给主库 
2. 主库收到SYNC,会立即触发BGSAVE,后台保存RDB,发送给副本库
3. 副本库接收后会应用RDB快照,load 进内存
4. 主库会陆续将中间产生的新的操作,保存并发送给副本库
5. 到此,我们主复制集就正常工作了
6. 再此以后,主库只要发生新的操作,都会以命令传播的形式自动发送给副本库.
7. 所有复制相关信息,从 info 信息中都可以查到.即使重启任何节点,他的主从关系依然都在.
8. 如果发生主从关系断开时,从库数据没有任何损坏,在下次重连之后,从库发送PSYNC给主库
9. 主库只会将从库缺失部分的数据同步给从库应用,达到快速恢复主从的目的



# 主从同步主库是否要开启持久化?
	如果不开有可能,主库重启操作,造成所有主从数据丢失!
  
# 启动两台 redis 服务
# 主从复制配置
	-1 命令方式,在从库上执行
    	slaveof 127.0.0.1 6379 	# 异步
    	# 从库不能写了,以后只能用来读
        
        slaveof no one  # 从库:断开主从关系
    
    -2 配置文件方式,在从库加入
    	slaveof 127.0.0.1 6379 	# 配置从节点 ip 和端口
		slave-read-only yes 	# 从节点只读,因为可读可写,数据会乱
    	autpass 123456


# 辅助配置(给主库用的)
min-slaves-to-write 1
min-slaves-max-lag 3
# 那么在从服务器的数量少于1个,或者三个从服务器的延迟(lag)值都大于或等于 3 秒时,主服务器将拒绝执行写命令

8.哨兵高可用

# 服务可用性高  
# 主从复制不是高可用
# 主从存在问题
# 1 主从复制,主节点发生故障,需要做故障转移,可以手动转移:让其中一个 slave 变成 master ---》  哨兵
# 2 主从复制,只能主写数据,所以写能力和存储能力有限 ---》集群


# 哨兵:Sentinel  实现高可用

# 工作原理:
    1 多个 sentinel 发现并确认 master 有问题
    2 选举触一个 sentinel 作为领导
    3 选取一个 slave 作为新的 master
    4 通知其余 slave 成为新的 master 的 slave
    5 通知客户端主从变化
    6 等待老的 master 复活成为新 master 的 slave
   

# 高可用搭建步骤
	第一步:先搭建一主两从
    第二步:哨兵配置文件,启动哨兵(redis的进程,也要监听端口,启动进程有配置文件)
    port 26379
    daemonize yes
    dir /root/redis/data
    bind 0.0.0.0
    logfile "redis_sentinel.log"
    sentinel monitor mymaster 127.0.0.1 6379 2
    sentinel down-after-milliseconds mymaster 30000


    port 26390
    daemonize yes
    dir /root/redis/data1
    bind 0.0.0.0
    logfile "redis_sentinel.log"
    sentinel monitor mymaster 127.0.0.1 6379 2
    sentinel down-after-milliseconds mymaster 30000


    port 26381
    daemonize yes
    dir /root/redis/data2
    bind 0.0.0.0
    logfile "redis_sentinel.log"
    sentinel monitor mymaster 127.0.0.1 6379 2
    sentinel down-after-milliseconds mymaster 30000
	
    第三步:启动三个哨兵
    
	./src/redis-sentinel ./sentinal_26379.conf
    ./src/redis-sentinel ./sentinal_26380.conf
    ./src/redis-sentinel ./sentinal_26381.conf

    第四步:停止主库,发现 80 变成了主库,以后 79 启动,变成了从库
posted @ 2023-03-07 17:45  亦双弓  阅读(26)  评论(0)    收藏  举报