Redis 是彻底开源免费的,遵照BSD协议,是一个高性能的key-value数据库。html
Redis 与其余 key - value 缓存产品有如下三个特色:java
Redis与其余key-value存储有什么不一样?node
Redis有着更为复杂的数据结构而且提供对他们的原子性操做,这是一个不一样于其余数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。python
Redis运行在内存中可是能够持久化到磁盘,因此在对不一样数据集进行高速读写时须要权衡内存,由于数据量不能大于硬件内存。在内存数据库方面的另外一个优势是,相比在磁盘上相同的复杂的数据结构,在内存中操做起来很是简单,这样Redis能够作不少内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,由于他们并不须要进行随机访问。mysql
1. 整个Redis数据库将只包含一个文件,一旦系统出现灾难性故障,咱们能够很是容易的进行恢复。程序员
2. 性能最大化,它仅须要fork出子进程,由子进程完成持久化工做,极大的避免服务进程执行IO操做了。redis
3. 相比于AOF机制,若是数据集很大,RDB的启动效率会更高算法
1. RDB容易丢数据,由于系统一旦在定时持久化以前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失spring
2. RDB经过fork子进程来完成持久化的若是当数据集较大时,可能会致使整个服务器中止服务几百毫秒,甚至是1秒钟。sql
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) #一、打印这个Redis缓存全部key以列表形式返回:[b'name222', b'foo'] print( r.keys() ) # keys * #二、清空redis r.flushall() #三、设置存在时间: ex=1指这个变量只会存在1秒,1秒后就不存在了 r.set('name', 'Alex') # ssetex name Alex r.set('name', 'Alex',ex=1) # ssetex name 1 Alex #四、获取对应key的value print(r.get('name')) # get name #五、删除指定的key r.delete('name') # del 'name' #六、避免覆盖已有的值: nx=True指只有当字典中没有name这个key才会执行 r.set('name', 'Tom',nx=True) # setnx name alex #七、从新赋值: xx=True只有当字典中已经有key值name才会执行 r.set('name', 'Fly',xx=True) # set name alex xx #八、psetex(name, time_ms, value) time_ms,过时时间(数字毫秒 或 timedelta对象) r.psetex('name',10,'Tom') # psetex name 10000 alex #十、mset 批量设置值; mget 批量获取 r.mset(key1='value1', key2='value2') # mset k1 v1 k2 v2 k3 v3 print(r.mget({'key1', 'key2'})) # mget k1 k2 k3 #十一、getset(name, value) 设置新值并获取原来的值 print(r.getset('age','100')) # getset name tom #十二、getrange(key, start, end) 下面例子就是获取name值abcdef切片的0-2间的字符(b'abc') r.set('name','abcdef') print(r.getrange('name',0,2)) #1三、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' #1四、bitcount(key, start=None, end=None) 获取name对应的值的二进制表示中 1 的个数 #1五、incr(self,name,amount=1) 自增 name对应的值,当name不存在时,则建立name=amount,不然自增 #1六、derc 自动减1:利用incr和derc能够简单统计用户在线数量 #若是之前有count就在之前基础加1,没有就第一次就是1,之后每运行一次就自动加1 num = r.incr('count') #1七、num = r.decr('count') #每运行一次就自动减1 #每运行一次incr('count')num值加1每运行一次decr后num值减1 print(num) #1八、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) #同字符串的操做,用于增量迭代分批获取元素,避免内存消耗太大
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 '''一、sentinel monitor mymaster 127.0.0.1 6379 2''' #1)sentinel监控的master的名字叫作mymaster,地址为127.0.0.1:6379 #2)当集群中有2个sentinel认为master死了时,才能真正认为该master已经不可用了 '''二、sentinel down-after-milliseconds mymaster 60000''' #1)sentinel会向master发送心跳PING来确认master是否存活,若是master在60000毫秒内不回应PONG #2)那么这个sentinel会单方面地认为这个master已经不可用了 '''三、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,以此类推。 '''四、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'))
watch key1 key2 ... : 监视一或多个key,若是在事务执行以前,被监视的key被其余命令改动,则事务被打断 ( 相似乐观锁 )
multi : 标记一个事务块的开始( queued ) 事务块内的多条命令会按照前后顺序被放进一个队列当中,最后由 EXEC 命令原子性( atomic )地执行
exec : 执行全部事务块的命令 ( 一旦执行exec后,以前加的监控锁都会被取消掉 )
discard : 取消事务,放弃事务块中的全部命令
unwatch : 取消watch对全部key的监控
Redis事务的三个阶段:
开始事务
Redis事务的开始是经过执行MULTI 命令来实现,它的做用是将执行该命令的客户端从非事务状态切换至事务状态
命令入队
当一个客户端出于事务状态时, 若是客户端发送的命令是 EXEC(执行全部事务块内的命令) 、DISCARD(取消事务,放弃执行事务块内的全部命令。) 、 WATCH(监视任意数量的key ,提一下,在事务中执行这个命令会报错:ERR WATCH inside MULTI is not allowed) 、 MULTI(标记一个事务块的开始) 四个命令之外的其余命令,那么服务器并不当即执行这个命令,而是将这个命令放入一个事务队列里面, 而后向客户端返回 QUEUED 回复。
执行事务
当一个处于事务状态的客户端向服务器发送 EXEC 命令时, 这个 EXEC 命令将当即被服务器执行: 服务器会遍历这个客户端的事务队列,执行队列中保存的全部命令,最后将执行命令所得的结果所有返回给客户端。(这里须要说明的一点是,Redis在处理网络请求的是单线程的,因此队列中的命令在执行期间是不会被其余客户端命令插进来的。这一点对理解Redis事务很关键)
WATCH
用于事务开启以前对任意数量的Key进行监视,若是这个被监视的key被改动(这里提一下,这个改动,无论是删除、添加、修改,或者A -> B -> A改回原值,都会被认为发生了改动),那么相应事务就被取消,不然事务正常执行。因此咱们能够认为 WATCH 是一个乐观锁。若是想让key取消被监控,能够用 UNWATCH 命令(这里又要提一下,UNWATCH 若是在事务中执行,也是会被放到队列里的)。
# > 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
分布式锁举例
分布式锁,是一种思想,它的实现方式有不少。好比,咱们将沙滩当作分布式锁的组件,那么它看起来应该是这样的:
加锁
加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过时
在沙滩上踩一脚,留下本身的脚印,就对应了加锁操做。其余进程或者线程,看到沙滩上已经有脚印,证实锁已被别人持有,则等待。
解锁
解锁的过程就是将Key键删除。但也不能乱删
把脚印从沙滩上抹去,就是解锁的过程。
锁超时
为了不死锁,咱们能够设置一阵风,在单位时间后刮起,将脚印自动抹去。
对于分布式锁,注意的
能够保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行这把锁要是一把可重入锁(避免死锁)这把锁最好是一把阻塞锁有高可用的获取锁和释放锁功能获取锁和释放锁的性能要好
若是查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。设置一个过时时间或者当有值的时候将缓存中的值替换掉便可。能够给key设置一些格式规则,而后查询以前先过滤掉不符合规则的Key。
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 }
# http://www.javashuo.com/article/p-cmxicfey-du.html:详细操做
<?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; } }
终于结束了,但愿能帮助你们,多多支持,关注不迷路哦!!!