Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
Redis与其他key-value存储有什么不同?
1. 整个Redis数据库将只包含一个文件,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
2. 性能最大化,它仅需要fork出子进程,由子进程完成持久化工作,极大的避免服务进程执行IO操作了。
3. 相比于AOF机制,如果数据集很大,RDB的启动效率会更高
1. RDB容易丢数据,因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失
2. RDB通过fork子进程来完成持久化的如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
AOF优缺点介绍(镜像)
在 /etc/redis.conf 中配置使用RDP
# save 900 1 // 900内,有1条写入,则产生快照
# save 300 1000 // 如果300秒内有1000次写入,则产生快照
# save 60 10000 // 如果60秒内有10000次写入,则产生快照
# stop-writes-on-bgsave-error yes // 后台备份进程出错时,主进程停不停止写入? 主进程不停止 容易造成数据不一致
# rdbcompression yes // 导出的rdb文件是否压缩 如果rdb的大小很大的话建议这么做
# Rdbchecksum yes // 导入rbd恢复时数据时,要不要检验rdb的完整性 验证版本是不是一致
# dbfilename dump.rdb //导出来的rdb文件名
# dir ./ //rdb的放置路径
在 /etc/redis.conf 中配置使用AOF
# appendonly no // 是否打开aof日志功能 aof跟 rdb都打开的情况下
# appendfsync always // 每1个命令,都立即同步到aof. 安全,速度慢
# appendfsync everysec // 折衷方案,每秒写1次
# appendfsync no // 写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到aof. 同步频率低,速度快,
# no-appendfsync-on-rewrite yes: // 正在导出rdb快照的过程中,要不要停止同步aof
# auto-aof-rewrite-percentage 100 //aof文件大小比起上次重写时的大小,增长率100%时,重写 缺点 刚开始的时候重复重写多次
# auto-aof-rewrite-min-size 64mb //aof文件,至少超过64M时,重写
测试使用: redis-benchmark -n 10000 表示 执行请求10000次,执行ls 发现出现 rdb 跟 aof文件。appendonly.aof dump.rdb
# 注: 在dump rdb过程中,aof如果停止同步,会不会丢失?
答: 不会,所有的操作缓存在内存的队列里, dump完成后,统一操作.
# 注: aof重写是指什么?
答: aof重写是指把内存中的数据,逆化成命令,写入到.aof日志里,以解决aof日志过大的问题.
# 问: 如果rdb文件,和aof文件都存在,优先用谁来恢复数据?
答: aof
# 问: 2种是否可以同时用?
答: 可以,而且推荐这么做
# 问: 恢复时rdb和aof哪个恢复的快
答: rdb快,因为其是数据的内存映射,直接载入到内存,而aof是命令,需要逐条执行
字符串(string)
list(列表)
hash(字典)
set(集合)
zset(有序集合)
Redis 对String操作
import redis
r = redis.Redis(host='1.1.1.3', port=6379)
#1、打印这个Redis缓存所有key以列表形式返回:[b'name222', b'foo']
print( r.keys() ) # keys *
#2、清空redis
r.flushall()
#3、设置存在时间: ex=1指这个变量只会存在1秒,1秒后就不存在了
r.set('name', 'Alex') # ssetex name Alex
r.set('name', 'Alex',ex=1) # ssetex name 1 Alex
#4、获取对应key的value
print(r.get('name')) # get name
#5、删除指定的key
r.delete('name') # del 'name'
#6、避免覆盖已有的值: nx=True指只有当字典中没有name这个key才会执行
r.set('name', 'Tom',nx=True) # setnx name alex
#7、重新赋值: xx=True只有当字典中已经有key值name才会执行
r.set('name', 'Fly',xx=True) # set name alex xx
#8、psetex(name, time_ms, value) time_ms,过期时间(数字毫秒 或 timedelta对象)
r.psetex('name',10,'Tom') # psetex name 10000 alex
#10、mset 批量设置值; mget 批量获取
r.mset(key1='value1', key2='value2') # mset k1 v1 k2 v2 k3 v3
print(r.mget({'key1', 'key2'})) # mget k1 k2 k3
#11、getset(name, value) 设置新值并获取原来的值
print(r.getset('age','100')) # getset name tom
#12、getrange(key, start, end) 下面例子就是获取name值abcdef切片的0-2间的字符(b'abc')
r.set('name','abcdef')
print(r.getrange('name',0,2))
#13、setbit(name, offset, value) #对name对应值的二进制表示的位进行操作
r.set('name','abcdef')
r.setbit('name',6,1) #将a(1100001)的第二位值改成1,就变成了c(1100011)
print(r.get('name')) #最后打印结果:b'cbcdef'
#14、bitcount(key, start=None, end=None) 获取name对应的值的二进制表示中 1 的个数
#15、incr(self,name,amount=1) 自增 name对应的值,当name不存在时,则创建name=amount,否则自增
#16、derc 自动减1:利用incr和derc可以简单统计用户在线数量
#如果以前有count就在以前基础加1,没有就第一次就是1,以后每运行一次就自动加1
num = r.incr('count')
#17、num = r.decr('count') #每运行一次就自动减1
#每运行一次incr('count')num值加1每运行一次decr后num值减1
print(num)
#18、append(key, value) 在redis name对应的值后面追加内容
r.set('name','aaaa')
r.append('name','bbbb')
print(r.get('name')) #运行结果: b'aaaabbbb'
import redis
r = redis.Redis(host='10.1.0.51', port=6379)
r.setbit('n',10,1) #设置n的第十位是二进制的1
print(r.getbit('n',10)) #获取n的第十位是1还是0(id=10用户是否在线)
print(r.bitcount('n')) #统计那种共有多上个1(用户在线数量)
Redis 对 Hash操作
import redis
pool = redis.ConnectionPool(host='1.1.1.3', port=6379)
r = redis.Redis(connection_pool=pool)
#1 hset(name, key, value) name=字典名字,key=字典key,value=对应key的值
r.hset('info','name','tom') # hset info name tom
r.hset('info','age','100')
print(r.hgetall('info')) # hgetall info {b'name': b'tom', b'age': b'100'}
print(r.hget('info','name')) # hget info name b'tom'
print(r.hkeys('info')) #打印出”info”对应的字典中的所有key [b'name', b'age']
print(r.hvals('info')) #打印出”info”对应的字典中的所有value [b'tom', b'100']
#2 hmset(name, mapping) 在name对应的hash中批量设置键值对
r.hmset('info2', {'k1':'v1', 'k2': 'v2','k3':'v3'}) #一次性设置多个值
print(r.hgetall('info2')) #hgetall() 一次性打印出字典中所有内容
print(r.hget('info2','k1')) #打印出‘info2’对应字典中k1对应的value
print(r.hlen('info2')) # 获取name对应的hash中键值对的个数
print(r.hexists('info2','k1')) # 检查name对应的hash是否存在当前传入的key
r.hdel('info2','k1') # 将name对应的hash中指定key的键值对删除
print(r.hgetall('info2'))
#3 hincrby(name, key, amount=1)自增name对应的hash中的指定key的值,不存在则创建key=amount
r.hincrby('info2','k1',amount=10) #第一次赋值k1=10以后每执行一次值都会自动增加10
print(r.hget('info2','k1'))
#4 hscan(name, cursor=0, match=None, count=None)对于数据大的数据非常有用,hscan可以实现分片的获取数据
# name,redis的name
# cursor,游标(基于游标分批取获取数据)
# match,匹配指定key,默认None 表示所有的key
# count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
print(r.hscan('info2',cursor=0,match='k*')) #打印出所有key中以k开头的
print(r.hscan('info2',cursor=0,match='*2*')) #打印出所有key中包含2的
#5 hscan_iter(name, match=None, count=None)
# match,匹配指定key,默认None 表示所有的key
# count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
for item in r.hscan_iter('info2'):
print(item)
Redis 对List操作
import redis
pool = redis.ConnectionPool(host='10.1.0.51', port=6379)
r = redis.Redis(connection_pool=pool)
#1 lpush:反向存放 rpush正向存放数据
r.lpush('names','alex','tom','jack') # 从右向左放数据比如:3,2,1(反着放)
print(r.lrange('names',0,-1)) # 结果:[b'jack', b'tom']
r.rpush('names','zhangsan','lisi') #从左向右放数据如:1,2,3(正着放)
print(r.lrange('names',0,-1)) #结果:b'zhangsan', b'lisi']
#2.1 lpushx(name,value) 在name对应的list中添加元素,只有name已经存在时,值添加到列表最左边
#2.2 rpushx(name, value) 表示从右向左操作
#3 llen(name) name对应的list元素的个数
print(r.llen('names'))
#4 linsert(name, where, refvalue, value)) 在name对应的列表的某一个值前或后插入一个新值
# name,redis的name
# where,BEFORE或AFTER
# refvalue,标杆值,即:在它前后插入数据
# value,要插入的数据
r.rpush('name2','zhangsan','lisi') #先创建列表[zhangsan,lisi]
print(r.lrange('name2',0,-1))
r.linsert('name2','before','zhangsan','wangwu') #在张三前插入值wangwu
r.linsert('name2','after','zhangsan','zhaoliu') #在张三前插入值zhaoliu
print(r.lrange('name2',0,-1))
#5 r.lset(name, index, value) 对name对应的list中的某一个索引位置重新赋值
r.rpush('name3','zhangsan','lisi') #先创建列表[zhangsan,lisi]
r.lset('name3',0,'ZHANGSAN') #将索引为0的位置值改成'ZHANGSAN'
print(r.lrange('name3',0,-1)) #最后结果:[b'ZHANGSAN', b'lisi']
#6 r.lrem(name, value, num) 在name对应的list中删除指定的值
# name,redis的name
# value,要删除的值
# num, num=0,删除列表中所有的指定值;
# num=2,从前到后,删除2个;
# num=-2,从后向前,删除2个
r.rpush('name4','zhangsan','zhangsan','zhangsan','lisi')
r.lrem('name4','zhangsan',1)
print(r.lrange('name4',0,-1))
#7 lpop(name) 在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素
r.rpush('name5','zhangsan','lisi')
r.rpop('name5')
print(r.lrange('name5',0,-1))
#8 lindex(name, index) 在name对应的列表中根据索引获取列表元素
r.rpush('name6','zhangsan','lisi')
print(r.lindex('name6',1))
#9 lrange(name, start, end) 在name对应的列表分片获取数据
r.rpush('num',0,1,2,3,4,5,6)
print(r.lrange('num',1,3))
#10 ltrim(name, start, end) 在name对应的列表中移除没有在start-end索引之间的值
r.rpush('num1',0,1,2,3,4,5,6)
r.ltrim('num1',1,2)
print(r.lrange('num1',0,-1))
#11 rpoplpush(src, dst) 从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
r.rpush('num2',0,1,2,3)
r.rpush('num3',100)
r.rpoplpush('num2','num3')
print(r.lrange('num3',0,-1)) #运行结果:[b'3', b'100']
#12 blpop(keys, timeout) 将多个列表排列,按照从左到右去pop对应列表的元素
#timeout,超时时间,当元素所有列表的元素获取完之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞
r.rpush('num4',0,1,2,3)
r.blpop('num4',10)
print(r.lrange('num4',0,-1))
redis对Set集合操作,Set集合就是不允许重复的列表
import redis
r = redis.Redis(host='10.1.0.51', port=6379)
#1 sadd(name,values) name对应的集合中添加元素
#2 scard(name) 获取name对应的集合中元素个数
r.sadd('name0','alex','tom','jack')
print(r.scard('name0'))
#3 sdiff(keys, *args) 在第一个name对应的集合中且不在其他name对应的集合的元素集合
r.sadd('num6',1,2,3,4)
r.sadd('num7',3,4,5,6) #在num6中有且在num7中没有的元素
print(r.sdiff('num6','num7')) #运行结果:{b'1', b'2'}
#4 sdiffstore(dest, keys, *args)
#获取第一个name对应的集合中且不在其他name对应的集合,再将其新加入到dest对应的集合中
# 将在num7中不在num8中的元素添加到num9
r.sadd('num7',1,2,3,4)
r.sadd('num8',3,4,5,6)
r.sdiffstore('num9','num7','num8')
print(r.smembers('num9')) #运行结果: {b'1', b'2'}
#5 sinter(keys, *args) 获取多一个name对应集合的交集
r.sadd('num10',4,5,6,7,8)
r.sadd('num11',1,2,3,4,5,6)
print(r.sinter('num10','num11')) #运行结果: {b'4', b'6', b'5'}
#6 sinterstore(dest, keys, *args) 获取多一个name对应集合的并集,再讲其加入到dest对应的集合中
r.sadd('num12',1,2,3,4)
r.sadd('num13',3,4,5,6)
r.sdiffstore('num14','num12','num13')
print(r.smembers('num14')) #运行结果: {b'1', b'2'}
#7 sismember(name, value) 检查value是否是name对应的集合的成员
r.sadd('name22','tom','jack')
print(r.sismember('name22','tom'))
#8 smove(src, dst, value) 将某个成员从一个集合中移动到另外一个集合
r.sadd('num15',1,2,3,4)
r.sadd('num16',5,6)
r.smove('num15','num16',1)
print(r.smembers('num16')) #运行结果: {b'1', b'5', b'6'}
#9 spop(name) 从集合的右侧(尾部)移除一个成员,并将其返回
r.sadd('num17',4,5,6)
print(r.spop('num17'))
#10 srandmember(name, numbers) 从name对应的集合中随机获取 numbers 个元素
r.sadd('num18',4,5,6)
print(r.srandmember('num18',2))
#11 srem(name, values) 在name对应的集合中删除某些值
r.sadd('num19',4,5,6)
r.srem('num19',4)
print(r.smembers('num19')) #运行结果: {b'5', b'6'}
#12 sunion(keys, *args) 获取多一个name对应的集合的并集
r.sadd('num20',3,4,5,6)
r.sadd('num21',5,6,7,8)
print(r.sunion('num20','num21')) #运行结果: {b'4', b'5', b'7', b'6', b'8', b'3'}
#13 sunionstore(dest,keys, *args)
# 获取多个name对应的集合的并集,并将结果保存到dest对应的集合中
r.sunionstore('num22','num20','num21')
print(r.smembers('num22')) #运行结果: {b'5', b'7', b'3', b'8', b'6', b'4'}
#14 sscan(name, cursor=0, match=None, count=None)
# sscan_iter(name, match=None, count=None)
#同字符串的操作,用于增量迭代分批获取元素,避免内存消耗太大
redis对有序集合操作
import redis
pool = redis.ConnectionPool(host='10.1.0.51', port=6379)
r = redis.Redis(connection_pool=pool)
#1 zadd(name, *args, **kwargs) 在name对应的有序集合中添加元素
r.zadd('zz', n1=11, n2=22,n3=15)
print(r.zrange('zz',0,-1)) #[b'n1', b'n3', b'n2']
print(r.zrange('zz',0,-1,withscores=True)) #[(b'n1', 11.0), (b'n3', 15.0), (b'n2', 22.0)]
#2 zcard(name) 获取name对应的有序集合元素的数量
#3 zcount(name, min, max) 获取name对应的有序集合中分数 在 [min,max] 之间的个数
r.zadd('name01', tom=11,jack=22,fly=15)
print(r.zcount('name01',1,20))
#4 zincrby(name, value, amount) 自增name对应的有序集合的 name 对应的分数
#5 zrank(name, value) 获取某个值在 name对应的有序集合中的排行(从 0 开始)
r.zadd('name02', tom=11,jack=22,fly=15)
print(r.zrank('name02','fly'))
#6 zrem(name, values) 删除name对应的有序集合中值是values的成员
r.zadd('name03', tom=11,jack=22,fly=15)
r.zrem('name03','fly')
print(r.zrange('name03',0,-1)) # [b'tom', b'jack']
#7 zremrangebyrank(name, min, max)根据排行范围删除
r.zadd('name04', tom=11,jack=22,fly=15)
r.zremrangebyrank('name04',1,2)
print(r.zrange('name04',0,-1)) # [b'tom']
#8 zremrangebyscore(name, min, max) 根据分数范围删除
r.zadd('name05', tom=11,jack=22,fly=15)
r.zremrangebyscore('name05',1,20)
print(r.zrange('name05',0,-1))
#9 zremrangebylex(name, min, max) 根据值返回删除
#10 zscore(name, value) 获取name对应有序集合中 value 对应的分数
#11 zinterstore(dest, keys, aggregate=None) #11测试过代码报错,未解决
#获取两个有序集合的交集,如果遇到相同值不同分数,则按照aggregate进行操作
# aggregate的值为: SUM MIN MAX
r.zadd('name09', tom=11,jack=22,fly=15)
r.zadd('name10', tom=12,jack=23,fly=15)
r.zinterstore('name11',2,'name09','name10')
print(r.zrange('name11',0,-1))
# 127.0.0.1:6379> zadd name222 11 zhangsan 12 lisi
(integer) 2
# 127.0.0.1:6379> zrange name222 0 -1
1) "zhangsan"
2) "lisi"
# 127.0.0.1:6379> zadd name333 11 zhangsan 12 lisi
(integer) 2
# 127.0.0.1:6379> zrange name333 0 -1
1) "zhangsan"
2) "lisi"
# 127.0.0.1:6379> zinterstore name444 2 name222 name333
(integer) 2
# 127.0.0.1:6379> zrange name444 0 -1 withscores
1) "zhangsan"
2) "22"
3) "lisi"
4) "24"
redis其他常用操作
import redis
pool = redis.ConnectionPool(host='1.1.1.3', port=6379)
r = redis.Redis(connection_pool=pool)
#1 查看当前Redis所有key
print(r.keys('*'))
#2 delete(*names) 删除Redis对应的key的值
r.delete('num16')
#3 exists(name) 检测redis的name是否存在
print(r.exists('name09'))
#4 keys(pattern='*') 根据模型获取redis的name
# KEYS * 匹配数据库中所有 key 。
# KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
# KEYS h*llo 匹配 hllo 和 heeeeello 等。
# KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo
print(r.keys(pattern='name*')) #打印出Redis中所有以name开通的key
#5 expire(name ,time) 为某个redis的某个name设置超时时间
r.expire('name09',1) # 1秒后就会删除这个key值name09
#6 rename(src, dst) 对redis的name重命名为
r.rename('num13','num13new')
# redis 127.0.0.1:6379> SET db_number 0 # 默认使用 0 号数据库
# redis 127.0.0.1:6379> SELECT 1 # 使用 1 号数据库
# redis 127.0.0.1:6379[1]> GET db_number # 已经切换到 1 号数据库,注意 Redis 现在的命令提符多了个 [1]
# redis 127.0.0.1:6379[1]> SET db_number 1 # 设置默认使用 1 号数据库
# redis 127.0.0.1:6379[1]> GET db_number # 获取当前默认使用的数据库号
#1 move(name, db)) 将redis的某个值移动到指定的db下(对方库中有就不移动)
127.0.0.1:6379> move name0 4
#2 type(name) 获取name对应值的类型
127.0.0.1:6379[4]> type name0
redis的管道使用(通过管道向指定db传送数据)
import redis,time
pool = redis.ConnectionPool(host='10.1.0.51', port=6379,db=5)
r = redis.Redis(connection_pool=pool)
# pipe = r.pipeline(transaction=False)
pipe = r.pipeline(transaction=True)
pipe.set('name', 'alex')
time.sleep(4)
pipe.set('role', 'sb')
pipe.execute() #只有执行这里上面两条才会一起执行,才能到db5中看到这两个值
# 127.0.0.1:6379[5]> select 5
# OK
# 127.0.0.1:6379[5]> keys *
# 1) "name"
# 2) "role"
import redis
class RedisHelper:
def __init__(self):
self.__conn = redis.Redis(host='10.1.0.51') #连接Redis服务器
self.chan_sub = 'fm104.5' #发布频道'fm104.5'
self.chan_pub = 'fm104.5' #接收频道也是'fm104.5'
#发消息
def public(self, msg):
self.__conn.publish(self.chan_pub, msg) #直接调用Redis的chan_pub方法发消息
print('pub')
return True
#收消息
def subscribe(self):
print('sub')
pub = self.__conn.pubsub() #开始订阅,仅仅相当于打开收音机
pub.subscribe(self.chan_sub) #调频道
pub.parse_response() #准备接收
return pub #再调用一次pub.parse_response()才会接收
from redisHelper import RedisHelper
#这里的RedisHelper()就是redisHelper中定义的类
obj = RedisHelper() #实例化一个对象RedisHelper
redis_sub = obj.subscribe()
while True:
msg= redis_sub.parse_response() #如果Public发送有数据就打印,没有就卡住
print(msg)
from redisHelper import RedisHelper
obj = RedisHelper()
obj.public('hello')
# sentinel.conf 配置说明
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
'''1、sentinel monitor mymaster 127.0.0.1 6379 2'''
#1)sentinel监控的master的名字叫做mymaster,地址为127.0.0.1:6379
#2)当集群中有2个sentinel认为master死了时,才能真正认为该master已经不可用了
'''2、sentinel down-after-milliseconds mymaster 60000'''
#1)sentinel会向master发送心跳PING来确认master是否存活,如果master在60000毫秒内不回应PONG
#2)那么这个sentinel会单方面地认为这个master已经不可用了
'''3、sentinel failover-timeout mymaster 180000'''
#1)如果sentinel A推荐sentinel B去执行failover,B会等待一段时间后,自行再次去对同一个master执行failover,
#2)这个等待的时间是通过failover-timeout配置项去配置的。
#3)从这个规则可以看出,sentinel集群中的sentinel不会再同一时刻并发去failover同一个master,
#4)第一个进行failover的sentinel如果失败了,另外一个将会在一定时间内进行重新进行failover,以此类推。
'''4、sentinel parallel-syncs mymaster 1'''
#1)在发生failover主备切换时,这个选项指定了最多可以有多少个slave同时对新的master进行同步
#2)如果这个数字越大,就意味着越多的slave因为replication而不可用,这个数字越小,完成failover所需的时间就越长。
#3)可以通过将这个值设为 1 来保证每次只有一个slave处于不能处理命令请求的状态。
SLAVE OF NO ONE
命令,然后能够通过INFO
命令看到新master的配置信息。SLAVE OF NO ONE
`后,即使其它的slave还没针对新master重新配置自己,failover也被认为是成功了的。# redis-6379.conf主要修改参数
port 6379
daemonize yes
logfile "6379.log"
dbfilename "dump-6379.rdb"
# ./redis-server redis-6379.conf
#方式1:
# [root@localhost bin]# ./redis-cli -h 127.0.0.1 -p 6379 ping
PONG
# 方式2:
# [root@localhost bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
(empty list or set)
# 从节点1 redis-6380.conf 主要修改参数
port 6380
daemonize yes
logfile "6380.log"
dbfilename "dump-6380.rdb"
slaveof 127.0.0.1 6379
# 从节点2 redis-6381.conf 主要修改参数
port 6381
daemonize yes
logfile "6381.log"
dbfilename "dump-6381.rdb"
slaveof 127.0.0.1 6379
./redis-server redis-6380.conf
# ./redis-server redis-6381.conf
# [root@localhost bin]# ./redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master #当前节点角色
connected_slaves:2 #从节点连接个数
slave0:ip=127.0.0.1,port=6380,state=online,offset=392,lag=1 #从节点连接信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=392,lag=2 #从节点连接信息
master_replid:6bc06103642acba6430e01ec78ef18ada4736649
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:392
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:392
# port 26379
# sentinel monitor mymaster 127.0.0.1 6379 1
# ./redis-sentinel sentinel-26379.conf
# 方法二, 使用redis-server命令加–sentinel参数:
redis-server sentinel-26379.conf --sentinel
# [root@localhost bin]# redis-cli -h 127.0.0.1 -p 26379 info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=1
# [root@localhost bin]# ./redis-cli shutdown
# 14549:X 24 Jul 15:44:44.568 # +vote-for-leader e31085285266ff86372eeeb4970c9a8de0471025 1
# 14549:X 24 Jul 15:44:44.604 # +sdown master mymaster 127.0.0.1 6379
# 14549:X 24 Jul 15:44:44.604 # +odown master mymaster 127.0.0.1 6379 #quorum 1/1
# 14549:X 24 Jul 15:44:44.604 # Next failover delay: I will not start a failover before Wed Jul 24 15:50:45 2019
# 14549:X 24 Jul 15:44:45.093 # +config-update-from sentinel e31085285266ff86372eeeb4970c9a8de0471025 127.0.0.1
26381 @ mymaster 127.0.0.1 6379
# 14549:X 24 Jul 15:44:45.093 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6381
# 14549:X 24 Jul 15:44:45.093 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
# 14549:X 24 Jul 15:44:45.093 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
# 14549:X 24 Jul 15:45:15.127 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
127.0.0.1:6381> info replication
# Replication
role:slave ###6379节点正常是,6381为从节点
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:166451
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:6bc06103642acba6430e01ec78ef18ada4736649
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:166451
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:253
repl_backlog_histlen:166199
127.0.0.1:6381> info replication
# Replication
role:master #执行shutdwon后成为新的master节点
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=217098,lag=1
master_replid:e39de2323e3ab0ff0eff1347ad1c65e2bd3fd917
master_replid2:6bc06103642acba6430e01ec78ef18ada4736649
master_repl_offset:217098
second_repl_offset:172878
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:253
repl_backlog_histlen:216846
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
一个是事件的类型,
一个是事件的描述。
如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
添加:值到布隆过滤器
1)向布隆过滤器添加key,会使用 f、g、h hash函数对key算出一个整数索引,然后对长度取余
2)每个hash函数都会算出一个不同的位置,把算出的位置都设置成1就完成了布隆过滤器添加过程
查询:布隆过滤器值
1)当查询某个key时,先用hash函数算出一个整数索引,然后对长度取余
2)当你有一个不为1时肯定不存在这个key,当全部都为1时可能有这个key
3)这样内存中的布隆过滤器过滤掉大量不存在的row请求,然后去再磁盘进行查询,减少IO操作
删除:不支持
1)目前我们知道布隆过滤器可以支持 add 和 isExist 操作
2)如何解决这个问题,答案是计数删除,但是计数删除需要存储一个数值,而不是原先的 bit 位,会增大占用的内存大小。
3)增加一个值就是将对应索引槽上存储的值加一,删除则是减一,判断是否存在则是看值是否大于0。
# Multi 命令用于标记一个事务块的开始事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性( atomic )地执行
> multi(开始一个redis事物)
incr books
incr books
> exec (执行事物)
> discard (丢弃事物)
[root@redis ~]# redis-cli
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set test 123
QUEUED
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get test
"123"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set test 456
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get test
"123"
127.0.0.1:6379>
#定义ip
host = 'localhost'
#建立服务连接
r = redis.Redis(host=host)
pipe = r.pipeline()
#开启事务
pipe.multi()
#存储子命令
pipe.set('key2', 4)
#执行事务
pipe.execute()
print(r.get('key2'))
Redis事务的三个阶段:
# > setnx lock:codehole true
# .... do something critical ....
# > del lock:codehole
# > setnx lock:codehole true
# > expire lock:codehole 5
# .... do something critical ....
# > del lock:codehole
# > set lock:codehole true ex 5 nx
# ''' do something '''
# > del lock:codehole
public String getByKey(String keyA,String keyB) {
String value = redisService.get(keyA);
if (StringUtil.isEmpty(value)) {
value = redisService.get(keyB);
String newValue = getFromDbById();
redisService.set(keyA,newValue,31, TimeUnit.DAYS);
redisService.set(keyB,newValue);
}
return value;
}
public String getWithLock(String key, Jedis jedis, String lockKey, String uniqueId, long expireTime) {
// 通过key获取value
String value = redisService.get(key);
if (StringUtil.isEmpty(value)) {
// 分布式锁,详细可以参考https://blog.csdn.net/fanrenxiang/article/details/79803037
//封装的tryDistributedLock包括setnx和expire两个功能,在低版本的redis中不支持
try {
boolean locked = redisService.tryDistributedLock(jedis, lockKey, uniqueId, expireTime);
if (locked) {
value = userService.getById(key);
redisService.set(key, value);
redisService.del(lockKey);
return value;
} else {
// 其它线程进来了没获取到锁便等待50ms后重试
Thread.sleep(50);
getWithLock(key, jedis, lockKey, uniqueId, expireTime);
}
} catch (Exception e) {
log.error("getWithLock exception=" + e);
return value;
} finally {
redisService.releaseDistributedLock(jedis, lockKey, uniqueId);
}
}
return value;
}
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
</dependencies>
public class BloomFilterTest {
private static final int capacity = 1000000;
private static final int key = 999998;
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), capacity);
static {
for (int i = 0; i < capacity; i++) {
bloomFilter.put(i);
}
}
public static void main(String[] args) {
/*返回计算机最精确的时间,单位微妙*/
long start = System.nanoTime();
if (bloomFilter.mightContain(key)) {
System.out.println("成功过滤到" + key);
}
long end = System.nanoTime();
System.out.println("布隆过滤器消耗时间:" + (end - start));
int sum = 0;
for (int i = capacity + 20000; i < capacity + 30000; i++) {
if (bloomFilter.mightContain(i)) {
sum = sum + 1;
}
}
System.out.println("错判率为:" + sum);
}
}
# 成功过滤到999998
# 布隆过滤器消耗时间:215518
# 错判率为:318
public static <T> BloomFilter<T> create(Funnel<T> funnel, int expectedInsertions /* n */) {
return create(funnel, expectedInsertions, 0.03); // FYI, for 3%, we always get 5 hash functions
}
# https://blog.csdn.net/fanrenxiang/article/details/80542580:详细操作
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 连接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大连接数 -->
<property name="maxTotal" value="30" />
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="10" />
<!-- 每次释放连接的最大数目 -->
<property name="numTestsPerEvictionRun" value="1024" />
<!-- 释放连接的扫描间隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<!-- 连接最小空闲时间 -->
<property name="minEvictableIdleTimeMillis" value="1800000" />
<!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
<property name="softMinEvictableIdleTimeMillis" value="10000" />
<!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
<property name="maxWaitMillis" value="1500" />
<!-- 在获取连接的时候检查有效性, 默认false -->
<property name="testOnBorrow" value="true" />
<!-- 在空闲时检查有效性, 默认false -->
<property name="testWhileIdle" value="true" />
<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
<property name="blockWhenExhausted" value="false" />
</bean>
<!-- jedis客户端单机版 -->
<bean id="redisClient" class="redis.clients.jedis.JedisPool">
<constructor-arg name="host" value="192.168.146.131"></constructor-arg>
<constructor-arg name="port" value="6379"></constructor-arg>
<constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg>
</bean>
<bean id="jedisClient" class="com.taotao.rest.dao.impl.JedisClientSingle"/>
<!-- jedis集群版配置 -->
<!-- <bean id="redisClient" class="redis.clients.jedis.JedisCluster">
<constructor-arg name="nodes">
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7001"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7002"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7003"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7004"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7005"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7006"></constructor-arg>
</bean>
</set>
</constructor-arg>
<constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg>
</bean>
<bean id="jedisClientCluster" class="com.taotao.rest.dao.impl.JedisClientCluster"></bean> -->
</beans>
package com.taotao.rest.dao.impl;
import org.springframework.beans.factory.annotation.Autowired;
import com.taotao.rest.dao.JedisClient;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class JedisClientSingle implements JedisClient{
@Autowired
private JedisPool jedisPool;
@Override
public String get(String key) {
Jedis jedis = jedisPool.getResource();
String string = jedis.get(key);
jedis.close();
return string;
}
@Override
public String set(String key, String value) {
Jedis jedis = jedisPool.getResource();
String string = jedis.set(key, value);
jedis.close();
return string;
}
@Override
public String hget(String hkey, String key) {
Jedis jedis = jedisPool.getResource();
String string = jedis.hget(hkey, key);
jedis.close();
return string;
}
@Override
public long hset(String hkey, String key, String value) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.hset(hkey, key, value);
jedis.close();
return result;
}
@Override
public long incr(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.incr(key);
jedis.close();
return result;
}
@Override
public long expire(String key, int second) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.expire(key, second);
jedis.close();
return result;
}
@Override
public long ttl(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.ttl(key);
jedis.close();
return result;
}
@Override
public long del(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.del(key);
jedis.close();
return result;
}
@Override
public long hdel(String hkey, String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.hdel(hkey, key);
jedis.close();
return result;
}
}
package com.taotao.rest.dao.impl;
import org.springframework.beans.factory.annotation.Autowired;
import com.taotao.rest.dao.JedisClient;
import redis.clients.jedis.JedisCluster;
public class JedisClientCluster implements JedisClient {
@Autowired
private JedisCluster jedisCluster;
@Override
public String get(String key) {
return jedisCluster.get(key);
}
@Override
public String set(String key, String value) {
return jedisCluster.set(key, value);
}
@Override
public String hget(String hkey, String key) {
return jedisCluster.hget(hkey, key);
}
@Override
public long hset(String hkey, String key, String value) {
return jedisCluster.hset(hkey, key, value);
}
@Override
public long incr(String key) {
return jedisCluster.incr(key);
}
@Override
public long expire(String key, int second) {
return jedisCluster.expire(key, second);
}
@Override
public long ttl(String key) {
return jedisCluster.ttl(key);
}
@Override
public long del(String key) {
return jedisCluster.del(key);
}
@Override
public long hdel(String hkey, String key) {
return jedisCluster.hdel(hkey, key);
}
}
package com.taotao.rest.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.taotao.commonEntity.JsonUtils;
import com.taotao.commonEntity.TaotaoResult;
import com.taotao.mapper.TbContentMapper;
import com.taotao.pojo.TbContent;
import com.taotao.pojo.TbContentExample;
import com.taotao.pojo.TbContentExample.Criteria;
import com.taotao.rest.dao.JedisClient;
import com.taotao.rest.service.ContentService;
import redis.clients.jedis.Jedis;
//首页大广告位的获取服务层信息
@Service
public class ContentServiceImpl implements ContentService {
@Value("${CONTENTCATEGORYID}")
private String CONTENTCATEGORYID;
@Autowired
private TbContentMapper contentMapper;
@Autowired
private JedisClient jedisClient;
@Override
public List<TbContent> getContentList(Long categoryId) {
/*一般第一次访问的时候先从数据库读取数据,然后将数据写入到缓存,再次访问同一内容的时候就从缓存中读取,如果缓存中没有则从数据库中读取
所以我们添加缓存逻辑的时候,从数据库中将内容读取出来之后,先set入缓存,然后再从缓存中添加读取行为,如果缓存为空则从数据库中进行读取
*/
//从缓存中获取值
String getData = jedisClient.hget(CONTENTCATEGORYID, categoryId+"");
if (!StringUtils.isBlank(getData)) {
List<TbContent> resultList= JsonUtils.jsonToList(getData, TbContent.class);
return resultList;
}
TbContentExample example=new TbContentExample();
Criteria criteria = example.createCriteria();
criteria.andCategoryIdEqualTo(categoryId);
List<TbContent> list = contentMapper.selectByExample(example);
//向缓存中放入值
String jsonData = JsonUtils.objectToJson(list);
jedisClient.hset(CONTENTCATEGORYID, categoryId+"",jsonData);
return list;
}
}