一文搞懂Redis

要知道Redis的应用场景就得首先了解什么是redis。

1、什么是Redis

Redis是一款内存高速缓存数据库。Redis全称为:Remote     Dictionary    Server (远程数据服务),使用C语言编写,Redis是一个key-value存储系统(键值存储系统),支持丰富的数据类型,如:String、list、set、zset、hash。 Redis是一种支持key-value等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。使用C语言编写,支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化.

2、redis的特点

Redis以内存作为数据存储介质,读写数据的效率极高。

Redis跟memcache不同的是,储存在Redis中的数据是持久化的,断电或重启,数据也不会丢失。

Redis的存储分为内存存储、磁盘存储和log文件。

Redis可以从磁盘重新将数据加载到内存中,也可以通过配置文件对其进行配置,因此,redis才能实现持久化。

Redis支持主从模式,可以配置集群,更利于支撑大型的项目。

3、redis的应用场景,能做什么?

众多语言都支持Redis,因为Redis交换数据快,在服务器中常用来存储一些需要频繁调取的数据,节省内存开销,也极大的提升了速度。将一些热点数据存储到Redis中,要用的时候,直接从内存取,极大的提高了速度和节约了服务器的开销。

        1、会话缓存(最常用) 

        2、消息队列(支付)

        3、活动排行榜或计数

        4、发布,订阅消息(消息通知)

        5、商品列表,评论列表

4、redis数据类型

 Redis一共支持五种数据类型:String(字符串)、hash(哈希)、list(列表)、set(集合)和zset(sorted  set有序集合)

        String(字符串):Redis最基本的数据类型,一个键对应一个值,一个键值最大存储512MB

        Hash(哈希):hash是一个键值对的集合,是一个String类型的field和value的映射表,适合用于存储对象

        List(列表):是redis的简单的字符串列表,按插入顺序排序

        Set(集合):是String字符串类型的无序集合,也不可重复

        ZSet(sorted  set  有序集合)是String类型的有序集合,也不可重复。有序集合中的每个元素都需要指定一个分数,根据分数对元素进行升序排序。

5、redis的相关命令

5.1 redis连接

redis提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。

redis连接实例是线程安全的,可以直接将redis连接实例设置为一个全局变量,直接使用。如果需要另一个Redis实例(or Redis数据库)时,就需要重新创建redis连接实例来获取一个新的连接。同理,python的redis没有实现select命令。

安装python里的redis插件(注意这里不是安装redis本身,安装的是操作redis的python插件,安装redis本身,出门左转百度)

pip install redis

连接redis,加上decode_responses=True,写入的键值对中的value为str类型,不加这个参数写入的则为字节类型。

import redis   # 导入redis模块,通过python操作redis 也可以直接在redis主机的服务端操作缓存数据库

r = redis.Redis(host='localhost', port=6379, decode_responses=True)   # host是redis主机,需要redis服务端和客户端都启动 redis默认端口是6379
r.set('name', 'junxi')  # key是"foo" value是"bar" 将键值对存入redis缓存
print(r['name'])
print(r.get('name'))  # 取出键name对应的值
print(type(r.get('name')))

5.2连接池连接

redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池

import redis    # 导入redis模块,通过python操作redis 也可以直接在redis主机的服务端操作缓存数据库

pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)   # host是redis主机,需要redis服务端和客户端都起着 redis默认端口是6379
r = redis.Redis(connection_pool=pool)
r.set('gender', 'male')     # key是"gender" value是"male" 将键值对存入redis缓存
print(r.get('gender'))      # gender 取出键male对应的值

5.3 redis的基本命令String

'''在Redis中设置值,默认,不存在则创建,存在则修改
参数:
ex,过期时间(秒)
px,过期时间(毫秒)
nx,如果设置为True,则只有name不存在时,当前set操作才执行。如果键key不存在,那么输出是True;如果键key已经存在,输出是None
xx,如果设置为True,则只有name存在时,当前set操作才执行。如果键key已经存在,那么输出是True;如果键key不存在,输出是None.'''
set(name, value, ex=None, px=None, nx=False, xx=False)
'''设置值,只有name不存在时,执行设置操作(添加)'''
print(r.setnx('fruit1', 'banana'))  # fruit1不存在,输出为True
'''设置值
参数:time,过期时间(数字秒 或 timedelta对象)'''
setex(name, time, value)
'''设置值
参数 time_ms,过期时间(数字毫秒 或 timedelta对象)'''
r.psetex("fruit3", 5000, "apple")
time.sleep(5)
print(r.get('fruit3'))  # 5000毫秒后,取值就从apple变成None
'''批量设置值mset(*args, **kwargs),如:'''
r.mset({'k1': 'v1', 'k2': 'v2'})
r.mset(k1="v1", k2="v2") # 这里k1 和k2 不能带引号 一次设置对个键值对
print(r.mget("k1", "k2"))   # 一次取出多个键对应的值
print(r.mget("k1"))
'''批量获取 mget(keys,*args)'''
print(r.mget('k1', 'k2'))
print(r.mget(['k1', 'k2']))
print(r.mget("fruit", "fruit1", "fruit2", "k1", "k2"))  # 将目前redis缓存中的键对应的值批量取出来
'''设置新值并获取原来的值 getset(name,value)'''
print(r.getset("food", "barbecue"))  # 设置的新值是barbecue 设置前的值是beef
'''获取子序列(根据字节获取,非字符) getrange(key,start,end)
参数:
name,Redis 的 name
start,起始位置(字节)
end,结束位置(字节)
如: "君惜大大" ,0-3表示 "君" '''
r.set("cn_name", "君惜大大") # 汉字
print(r.getrange("cn_name", 0, 2))   # 取索引号是0-2 前3位的字节 君 切片操作 (一个汉字3个字节 1个字母一个字节 每个字节8bit)
print(r.getrange("cn_name", 0, -1))  # 取所有的字节 君惜大大 切片操作
r.set("en_name","junxi") # 字母
print(r.getrange("en_name", 0, 2))  # 取索引号是0-2 前3位的字节 jun 切片操作 (一个汉字3个字节 1个字母一个字节 每个字节8bit)
print(r.getrange("en_name", 0, -1)) # 取所有的字节 junxi 切片操作
'''setrange(name,offset,value) 修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加)
参数:
offset,字符串的索引,字节(一个汉字三个字节)
value,要设置的值'''
r.setrange("en_name", 1, "ccc")
print(r.get("en_name"))    # jccci 原始值是junxi 从索引号是1开始替换成ccc 变成 jccci
'''setbit(name, offset, value) 对name对应值的二进制表示的位进行操作
参数:
name,redis的name
offset,位的索引(将值变换成二进制后再进行索引)
value,值只能是 1 或 0 
注:如果在Redis中有一个对应: n1 = "foo",
那么字符串foo的二进制表示为:01100110 01101111 01101111
所以,如果执行 setbit('n1', 7, 1),则就会将第7位设置为1,
那么最终二进制则变成 01100111 01101111 01101111,即:"goo" '''

#扩展,转换二进制表示:
source = "陈思维"
source = "foo"
for i in source:
num = ord(i)
print bin(num).replace('b','')
'''特别的,如果source是汉字 "陈思维"怎么办?
答:对于utf-8,每一个汉字占 3 个字节,那么 "陈思维" 则有 9个字节
对于汉字,for循环时候会按照 字节 迭代,那么在迭代时,将每一个字节转换 十进制数,然后再将十进制数转换成二进制
11100110 10101101 10100110 11100110 10110010 10011011 11101001 10111101 10010000'''
'''getbit(name, offset)  获取name对应的值的二进制表示中的某位的值 (0或1)'''
print(r.getbit("foo1", 0)) # 0 foo1 对应的二进制 4个字节 32位 第0位是0还是1
'''bitcount(key, start=None, end=None)  获取name对应的值的二进制表示中 1 的个数
参数:
key,Redis的name
start 字节起始位置
end,字节结束位置'''
print(r.get("foo"))  # goo1 01100111
print(r.bitcount("foo",0,1))  # 11 表示前2个字节中,1出现的个数
'''bitop(operation, dest, *keys)  获取多个值,并将值做位运算,将最后的结果保存至新的name对应的值
参数:
operation,AND(并) 、 OR(或) 、 NOT(非) 、 XOR(异或)
dest, 新的Redis的name
*keys,要查找的Redis的name'''
bitop("AND", 'new_name', 'n1', 'n2', 'n3')
#获取Redis中n1,n2,n3对应的值,然后讲所有的值做位运算(求并集),然后将结果保存 new_name 对应的值中
r.set("foo","1")  # 0110001
r.set("foo1","2")  # 0110010
print(r.mget("foo","foo1"))  # ['goo1', 'baaanew']
print(r.bitop("AND","new","foo","foo1"))  # "new" 0 0110000
print(r.mget("foo","foo1","new"))

source = "12"
for i in source:
num = ord(i)
print(num)  # 打印每个字母字符或者汉字字符对应的ascii码值 f-102-0b100111-01100111
print(bin(num))  # 打印每个10进制ascii码值转换成二进制的值 0b1100110(0b表示二进制)
print bin(num).replace('b','')  # 将二进制0b1100110替换成01100110
'''strlen(name)   返回name对应值的字节长度(一个汉字3个字节)'''
print(r.strlen("foo"))  # 4 'goo1'的长度是4
'''incr(self, name, amount=1)  自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。
参数:
name,Redis的name
amount,自增数(必须是整数)
注:同incrby'''

r.set("foo", 123)
print(r.mget("foo", "foo1", "foo2", "k1", "k2"))
r.incr("foo", amount=1)
print(r.mget("foo", "foo1", "foo2", "k1", "k2"))

应用场景 – 页面点击数
假定我们对一系列页面需要记录点击次数。例如论坛的每个帖子都要记录点击次数,而点击次数比回帖的次数的多得多。如果使用关系数据库来存储点击,可能存在大量的行级锁争用。所以,点击数的增加使用redis的INCR命令最好不过了。
当redis服务器启动时,可以从关系数据库读入点击数的初始值(12306这个页面被访问了34634次)

r.set("visit:12306:totals", 34634)
print(r.get("visit:12306:totals"))

每当有一个页面点击,则使用INCR增加点击数即可。

r.incr("visit:12306:totals")
r.incr("visit:12306:totals")

页面载入的时候则可直接获取这个值

print(r.get("visit:12306:totals"))
'''incrbyfloat(self, name, amount=1.0) 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。
参数:
name,Redis的name
amount,自增数(浮点型)
'''
r.set("foo1", "123.0")
r.set("foo2", "221.0")
print(r.mget("foo1", "foo2"))
r.incrbyfloat("foo1", amount=2.0)
r.incrbyfloat("foo2", amount=3.0)
print(r.mget("foo1", "foo2"))
'''decr(self, name, amount=1)
自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减。
参数:
name,Redis的name
amount,自减数(整数)'''

r.decr("foo4", amount=3) # 递减3
r.decr("foo1", amount=1) # 递减1
print(r.mget("foo1", "foo4"))
'''append(key, value)
在redis name对应的值后面追加内容
参数:
key, redis的name
value, 要追加的字符串'''
r.append("name", "haha")    # 在name对应的值junxi后面追加字符串haha
print(r.mget("name"))

5.4 redis基本命令hash
 

'''单个增加--修改(单个取出)--没有就新增,有的话就修改 hset(name, key, value)
name对应的hash中设置一个键值对(不存在,则创建;否则,修改)
参数:
name,redis的name
key,name对应的hash中的key
value,name对应的hash中的value'''

#hsetnx(name, key, value) #当name对应的hash中不存在当前key时则创建(相当于添加)

import redis
import time
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
r = redis.Redis(connection_pool=pool)
r.hset("hash1", "k1", "v1")
r.hset("hash1", "k2", "v2")
print(r.hkeys("hash1")) # 取hash中所有的key
print(r.hget("hash1", "k1"))    # 单个取hash的key对应的值
print(r.hmget("hash1", "k1", "k2")) # 多个取hash的key对应的值
r.hsetnx("hash1", "k2", "v3") # 只能新建
print(r.hget("hash1", "k2"))
'''批量增加(取出) hmset(name, mapping)
在name对应的hash中批量设置键值对
参数:
name,redis的name
mapping,字典,如:{'k1':'v1', 'k2': 'v2'}'''

r.hmset("hash2", {"k2": "v2", "k3": "v3"})
'''hget(name,key) 在name对应的hash中获取根据key获取value
hmget(name, keys, *args) 在name对应的hash中获取多个key的值
参数:
name,reids对应的name
keys,要获取key集合,如:['k1', 'k2', 'k3']
*args,要获取的key,如:k1,k2,k3'''

print(r.hget("hash2", "k2"))  # 单个取出"hash2"的key-k2对应的value
print(r.hmget("hash2", "k2", "k3"))  # 批量取出"hash2"的key-k2 k3对应的value --方式1
print(r.hmget("hash2", ["k2", "k3"]))  # 批量取出"hash2"的key-k2 k3对应的value --方式2
'''取出所有的键值对 hgetall(name)
获取name对应hash的所有键值'''
print(r.hgetall("hash1"))
'''得到所有键值对的格式 hash长度 hlen(name)
获取name对应的hash中键值对的个数'''
print(r.hlen("hash1"))
'''得到所有的keys(类似字典的取所有keys) hkeys(name)
获取name对应的hash中所有的key的值'''
print(r.hkeys("hash1"))
'''得到所有的value(类似字典的取所有value) hvals(name)
获取name对应的hash中所有的value的值'''
print(r.hvals("hash1"))
'''判断成员是否存在(类似字典的in) hexists(name, key)
检查name对应的hash是否存在当前传入的key'''
print(r.hexists("hash1", "k4"))  # False 不存在
print(r.hexists("hash1", "k1"))  # True 存在
'''删除键值对 hdel(name,*keys)
将name对应的hash中指定key的键值对删除'''
print(r.hgetall("hash1"))
r.hset("hash1", "k2", "v222")   # 修改已有的key k2
r.hset("hash1", "k11", "v1")   # 新增键值对 k11
r.hdel("hash1", "k1")    # 删除一个键值对
print(r.hgetall("hash1"))
'''自增自减整数(将key对应的value--整数 自增1或者2,或者别的整数 负数就是自减) hincrby(name, key, amount=1)
自增name对应的hash中的指定key的值,不存在则创建key=amount
参数:
name,redis中的name
key, hash对应的key
amount,自增数(整数)'''

r.hset("hash1", "k3", 123)
r.hincrby("hash1", "k3", amount=-1)
print(r.hgetall("hash1"))
r.hincrby("hash1", "k4", amount=1)  # 不存在的话,value默认就是1
print(r.hgetall("hash1"))
'''自增自减浮点数(将key对应的value--浮点数 自增1.0或者2.0,或者别的浮点数 负数就是自减) hincrbyfloat(name, key, amount=1.0)
自增name对应的hash中的指定key的值,不存在则创建key=amount
参数:
name,redis中的name
key, hash对应的key
amount,自增数(浮点数)
自增name对应的hash中的指定key的值,不存在则创建key=amount'''
r.hset("hash1", "k5", "1.0")
r.hincrbyfloat("hash1", "k5", amount=-1.0)    # 已经存在,递减-1.0
print(r.hgetall("hash1"))
r.hincrbyfloat("hash1", "k6", amount=-1.0)    # 不存在,value初始值是-1.0 每次递减1.0
print(r.hgetall("hash1"))
'''取值查看--分片读取 hscan(name, cursor=0, match=None, count=None)
增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,并非一次性将数据全部获取完,从而放置内存被撑爆
参数:
name,redis的name
cursor,游标(基于游标分批取获取数据)
match,匹配指定key,默认None 表示所有的key
count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
如:
第一次:cursor1, data1 = r.hscan('xx', cursor=0, match=None, count=None)
第二次:cursor2, data1 = r.hscan('xx', cursor=cursor1, match=None, count=None)
...
直到返回值cursor的值为0时,表示数据已经通过分片获取完毕'''
print(r.hscan("hash1"))
'''hscan_iter(name, match=None, count=None) 利用yield封装hscan创建生成器,实现分批去redis中获取数据
参数:
match,匹配指定key,默认None 表示所有的key
count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数'''
for item in r.hscan_iter('hash1'):
    print(item)
print(r.hscan_iter("hash1"))    # 生成器内存地址

5.5redis基本命令list

'''增加(类似于list的append,只是这里是从左边新增加)--没有就新建 lpush(name,values)
在name对应的list中添加元素,每个新的元素都添加到列表的最左边'''
import redis
import time

pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
r = redis.Redis(connection_pool=pool)

r.lpush("list1", 11, 22, 33)
print(r.lrange('list1', 0, -1))

#保存顺序为: 33,22,11
###扩展
r.rpush("list2", 11, 22, 33)  # 表示从右向左操作
print(r.llen("list2"))  # 列表长度
print(r.lrange("list2", 0, 3))  # 切片取出值,范围是索引号0-3
'''增加(从右边增加)--没有就新建'''
r.rpush("list2", 44, 55, 66)    # 在列表的右边,依次添加44,55,66
print(r.llen("list2"))  # 列表长度
print(r.lrange("list2", 0, -1)) # 切片取出值,范围是索引号0到-1(最后一个元素)
'''往已经有的name的列表的左边添加元素,没有的话无法创建  lpushx(name,value)
在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边'''
r.lpushx("list10", 10)   # 这里list10不存在
print(r.llen("list10"))  # 0
print(r.lrange("list10", 0, -1))  # []

r.lpushx("list2", 77)   # 这里"list2"之前已经存在,往列表最左边添加一个元素,一次只能添加一个
print(r.llen("list2"))  # 列表长度
print(r.lrange("list2", 0, -1)) # 切片取出值,范围是索引号0到-1(最后一个元素
'''往已经有的name的列表的右边添加元素,没有的话无法创建'''
r.rpushx("list2", 99)   # 这里"foo_list1"之前已经存在,往列表最右边添加一个元素,一次只能添加一个
print(r.llen("list2"))  # 列表长度
print(r.lrange("list2", 0, -1)) # 切片取出值,范围是索引号0到-1(最后一个元素)

'''新增(固定索引号位置插入元素)
linsert(name, where, refvalue, value))
在name对应的列表的某一个值前或后插入一个新值
参数:
name,redis的name
where,BEFORE或AFTER
refvalue,标杆值,即:在它前后插入数据
value,要插入的数据'''
r.linsert("list2", "before", "11", "00")   # 往列表中左边第一个出现的元素"11"前插入元素"00"
print(r.lrange("list2", 0, -1))   # 切片取出值,范围是索引号0-最后一个元素
'''修改(指定索引号进行修改) r.lset(name, index, value)
对name对应的list中的某一个索引位置重新赋值
参数:
name,redis的name
index,list的索引位置
value,要设置的值'''
r.lset("list2", 0, -11)    # 把索引号是0的元素修改成-11
print(r.lrange("list2", 0, -1))
'''删除(指定值进行删除) r.lrem(name, value, num)
在name对应的list中删除指定的值
参数:
name,redis的name
value,要删除的值
num, num=0,删除列表中所有的指定值;
num=2,从前到后,删除2个; num=1,从前到后,删除左边第1个
num=-2,从后向前,删除2个'''
r.lrem("list2", "11", 1)    # 将列表中左边第一次出现的"11"删除
print(r.lrange("list2", 0, -1))
r.lrem("list2", "99", -1)    # 将列表中右边第一次出现的"99"删除
print(r.lrange("list2", 0, -1))
r.lrem("list2", "22", 0)    # 将列表中所有的"22"删除
print(r.lrange("list2", 0, -1))
'''删除并返回  lpop(name)
在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素
更多:
rpop(name) 表示从右向左操作'''
r.lpop("list2")    # 删除列表最左边的元素,并且返回删除的元素
print(r.lrange("list2", 0, -1))
r.rpop("list2")    # 删除列表最右边的元素,并且返回删除的元素
print(r.lrange("list2", 0, -1))
'''删除索引之外的值  ltrim(name, start, end)
在name对应的列表中移除没有在start-end索引之间的值
参数:
name,redis的name
start,索引的起始位置
end,索引结束位置'''
r.ltrim("list2", 0, 2)    # 删除索引号是0-2之外的元素,值保留索引号是0-2的元素
print(r.lrange("list2", 0, -1))
'''取值(根据索引号取值) lindex(name, index)
在name对应的列表中根据索引获取列表元素'''
print(r.lindex("list2", 0))  # 取出索引号是0的值
'''移动 元素从一个列表移动到另外一个列表  rpoplpush(src, dst)
从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
参数:
src,要取数据的列表的name
dst,要添加数据的列表的name'''
r.rpoplpush("list1", "list2")
print(r.lrange("list2", 0, -1))
'''移动 元素从一个列表移动到另外一个列表 可以设置超时 brpoplpush(src, dst, timeout=0)
从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
参数:
src,取出并要移除元素的列表对应的name
dst,要插入元素的列表对应的name
timeout,当src对应的列表中没有数据时,阻塞等待其有数据的超时时间(秒),0 表示永远阻塞'''
r.brpoplpush("list1", "list2", timeout=2)
print(r.lrange("list2", 0, -1))
'''一次移除多个列表  blpop(keys, timeout)
将多个列表排列,按照从左到右去pop对应列表的元素
参数:
keys,redis的name的集合
timeout,超时时间,当元素所有列表的元素获取完之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞
更多:
r.brpop(keys, timeout) 同blpop,将多个列表排列,按照从右像左去移除各个列表内的元素'''
r.lpush("list10", 3, 4, 5)
r.lpush("list11", 3, 4, 5)
while True:
    r.blpop(["list10", "list11"], timeout=2)
    print(r.lrange("list10", 0, -1), r.lrange("list11", 0, -1))
'''
自定义增量迭代
由于redis类库中没有提供对列表元素的增量迭代,如果想要循环name对应的列表的所有元素,那么就需要:

获取name对应的所有列表
循环列表
但是,如果列表非常大,那么就有可能在第一步时就将程序的内容撑爆,所有有必要自定义一个增量迭代的功能:
'''
def list_iter(name):
    """
    自定义redis列表增量迭代
    :param name: redis中的name,即:迭代name对应的列表
    :return: yield 返回 列表元素
    """
    list_count = r.llen(name)
    for index in range(list_count):
        yield r.lindex(name, index)

# 使用
for item in list_iter('list2'): # 遍历这个列表
    print(item)

5.6 redis基本命令 set

'''新增  sadd(name,values)
name对应的集合中添加元素'''
r.sadd("set1", 33, 44, 55, 66)  # 往集合中添加元素
print(r.scard("set1"))  # 集合的长度是4
print(r.smembers("set1"))   # 获取集合中所有的成员
'''获取元素个数 类似于len scard(name)
获取name对应的集合中元素个数'''
print(r.scard("set1"))  # 集合的长度是4
'''获取集合中所有的成员 smembers(name)
获取name对应的集合的所有成员'''
print(r.smembers("set1"))   # 获取集合中所有的成员
'''获取集合中所有的成员--元组形式  sscan(name, cursor=0, match=None, count=None)'''
print(r.sscan("set1"))
'''获取集合中所有的成员--迭代器的方式  sscan_iter(name, match=None, count=None)
同字符串的操作,用于增量迭代分批获取元素,避免内存消耗太大'''
for i in r.sscan_iter("set1"):
    print(i)
'''差集  sdiff(keys, *args)
在第一个name对应的集合中且不在其他name对应的集合的元素集合'''
r.sadd("set2", 11, 22, 33)
print(r.smembers("set1"))   # 获取集合中所有的成员
print(r.smembers("set2"))
print(r.sdiff("set1", "set2"))   # 在集合set1但是不在集合set2中
print(r.sdiff("set2", "set1"))   # 在集合set2但是不在集合set1中
'''差集--差集存在一个新的集合中  sdiffstore(dest, keys, *args)
获取第一个name对应的集合中且不在其他name对应的集合,再将其新加入到dest对应的集合中'''
r.sdiffstore("set3", "set1", "set2")    # 在集合set1但是不在集合set2中
print(r.smembers("set3"))   # 获取集合3中所有的成员
'''交集  sinter(keys, *args)
获取多一个name对应集合的交集'''
print(r.sinter("set1", "set2")) # 取2个集合的交集
'''交集--交集存在一个新的集合中  sinterstore(dest, keys, *args)
获取多一个name对应集合的并集,再将其加入到dest对应的集合中'''
print(r.sinterstore("set3", "set1", "set2")) # 取2个集合的交集
print(r.smembers("set3"))
'''并集  sunion(keys, *args)
获取多个name对应的集合的并集'''
print(r.sunion("set1", "set2")) # 取2个集合的并集
'''并集--并集存在一个新的集合  sunionstore(dest,keys, *args)
获取多一个name对应的集合的并集,并将结果保存到dest对应的集合中'''
print(r.sunionstore("set3", "set1", "set2")) # 取2个集合的并集
print(r.smembers("set3"))
'''判断是否是集合的成员 类似in  sismember(name, value)
检查value是否是name对应的集合的成员,结果为True和False'''

print(r.sismember("set1", 33))  # 33是集合的成员
print(r.sismember("set1", 23))  # 23不是集合的成员
'''移动  smove(src, dst, value)
将某个成员从一个集合中移动到另外一个集合'''
r.smove("set1", "set2", 44)
print(r.smembers("set1"))
print(r.smembers("set2"))
'''删除--随机删除并且返回被删除值 spop(name)
从集合移除一个成员,并将其返回,说明一下,集合是无序的,所有是随机删除的'''
print(r.spop("set2"))   # 这个删除的值是随机删除的,集合是无序的
print(r.smembers("set2"))
'''删除--指定值删除  srem(name, values)
在name对应的集合中删除某些值'''

print(r.srem("set2", 11))   # 从集合中删除指定值 11
print(r.smembers("set2"))

5.7 redis基本命令 有序set

Set操作,Set集合就是不允许重复的列表,本身是无序的

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

'''新增 zadd(name, *args, **kwargs)
在name对应的有序集合中添加元素'''
import redis
import time

pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
r = redis.Redis(connection_pool=pool)

r.zadd("zset1", n1=11, n2=22)
r.zadd("zset2", 'm1', 22, 'm2', 44)
print(r.zcard("zset1")) # 集合长度
print(r.zcard("zset2")) # 集合长度
print(r.zrange("zset1", 0, -1))   # 获取有序集合中所有元素
print(r.zrange("zset2", 0, -1, withscores=True))   # 获取有序集合中所有元素和分数
'''获取有序集合元素个数 类似于len  zcard(name)
获取name对应的有序集合元素的数量'''
print(r.zcard("zset1")) # 集合长度
'''获取有序集合的所有元素 r.zrange( name, start, end, desc=False, withscores=False, score_cast_func=float)
按照索引范围获取name对应的有序集合的元素
参数:
name,redis的name
start,有序集合索引起始位置(非分数)
end,有序集合索引结束位置(非分数)
desc,排序规则,默认按照分数从小到大排序
withscores,是否获取元素的分数,默认只获取元素的值
score_cast_func,对分数进行数据转换的函数'''

#3-1 从大到小排序(同zrange,集合是从大到小排序的)
#zrevrange(name, start, end, withscores=False, score_cast_func=float)

print(r.zrevrange("zset1", 0, -1))    # 只获取元素,不显示分数
print(r.zrevrange("zset1", 0, -1, withscores=True)) # 获取有序集合中所有元素和分数,分数倒序

#3-2 按照分数范围获取name对应的有序集合的元素
#zrangebyscore(name, min, max, start=None, num=None, withscores=False, score_cast_func=float)
for i in range(1, 30):
   element = 'n' + str(i)
   r.zadd("zset3", element, i)
print(r.zrangebyscore("zset3", 15, 25)) # # 在分数是15-25之间,取出符合条件的元素
print(r.zrangebyscore("zset3", 12, 22, withscores=True))    # 在分数是12-22之间,取出符合条件的元素(带分数)

#3-3按照分数范围获取有序集合的元素并排序(默认从大到小排序)
#zrevrangebyscore(name, max, min, start=None, num=None, withscores=False, score_cast_func=float)
print(r.zrevrangebyscore("zset3", 22, 11, withscores=True)) # 在分数是22-11之间,取出符合条件的元素 按照分数倒序

#3-4 获取所有元素--默认按照分数顺序排序
#zscan(name, cursor=0, match=None, count=None, score_cast_func=float)
print(r.zscan("zset3"))

#3-5 获取所有元素--迭代器
#zscan_iter(name, match=None, count=None,score_cast_func=float)
for i in r.zscan_iter("zset3"): # 遍历迭代器
    print(i)
#zcount(name, min, max) 获取name对应的有序集合中分数 在 [min,max] 之间的个数
print(r.zrange("zset3", 0, -1, withscores=True))
print(r.zcount("zset3", 11, 22))
'''自增 zincrby(name, value, amount)
自增name对应的有序集合的 name 对应的分数'''
r.zincrby("zset3", "n2", amount=2)    # 每次将n2的分数自增2
print(r.zrange("zset3", 0, -1, withscores=True))
'''获取值的索引号  zrank(name, value)
获取某个值在 name对应的有序集合中的索引(从 0 开始)
更多:
zrevrank(name, value),从大到小排序'''
print(r.zrank("zset3", "n1"))   # n1的索引号是0 这里按照分数顺序(从小到大)
print(r.zrank("zset3", "n6"))   # n6的索引号是1
print(r.zrevrank("zset3", "n1"))    # n1的索引号是29 这里安照分数倒序(从大到小)
'''删除--指定值删除  zrem(name, values)
删除name对应的有序集合中值是values的成员'''
r.zrem("zset3", "n3")   # 删除有序集合中的元素n3 删除单个
print(r.zrange("zset3", 0, -1))
'''删除--根据排行范围删除,按照索引号来删除 zremrangebyrank(name, min, max)
根据排行范围删除'''
r.zremrangebyrank("zset3", 0, 1)  # 删除有序集合中的索引号是0, 1的元素
print(r.zrange("zset3", 0, -1))
'''删除--根据分数范围删除 zremrangebyscore(name, min, max)
根据分数范围删除'''
r.zremrangebyscore("zset3", 11, 22)   # 删除有序集合中的分数是11-22的元素
print(r.zrange("zset3", 0, -1))
'''获取值对应的分数  zscore(name, value)
获取name对应有序集合中 value 对应的分数'''
print(r.zscore("zset3", "n27"))   # 获取元素n27对应的分数27

5.8 其他常用操作

'''删除  delete(*names)
根据删除redis中的任意数据类型(string、hash、list、set、有序set)'''
r.delete("gender")  # 删除key为gender的键值对
'''检查名字是否存在  exists(name)
检测redis的name是否存在,存在就是True,False 不存在'''
print(r.exists("zset1"))
'''模糊匹配
keys(pattern='')
根据模型获取redis的name
更多:
KEYS * 匹配数据库中所有 key 。
KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
KEYS hllo 匹配 hllo 和 heeeeello 等。
KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo'''
print(r.keys("foo*"))
'''设置超时时间 expire(name ,time)
为某个redis的某个name设置超时时间'''
r.lpush("list5", 11, 22)
r.expire("list5", time=3)
print(r.lrange("list5", 0, -1))
time.sleep(3)
print(r.lrange("list5", 0, -1))
'''重命名 rename(src, dst)
对redis的name重命名'''
r.lpush("list5", 11, 22)
r.rename("list5", "list5-1")
'''随机获取name randomkey()
随机获取一个redis的name(不删除)'''
print(r.randomkey())
'''获取类型  type(name)
获取name对应值的类型'''
print(r.type("set1"))
print(r.type("hash2"))
'''查看所有元素 scan(cursor=0, match=None, count=None)'''
print(r.hscan("hash2"))
print(r.sscan("set3"))
print(r.zscan("zset2"))
print(r.getrange("foo1", 0, -1))
print(r.lrange("list2", 0, -1))
print(r.smembers("set3"))
print(r.zrange("zset3", 0, -1))
print(r.hgetall("hash1"))
'''查看所有元素--迭代器  scan_iter(match=None, count=None)'''
for i in r.hscan_iter("hash1"):
    print(i)

for i in r.sscan_iter("set3"):
    print(i)

for i in r.zscan_iter("zset3"):
    print(i)

other 方法

print(r.get('name'))    # 查询key为name的值
r.delete("gender")  # 删除key为gender的键值对
print(r.keys()) # 查询所有的Key
print(r.dbsize())   # 当前redis包含多少条数据
r.save()    # 执行"检查点"操作,将数据写回磁盘。保存时阻塞
# r.flushdb()        # 清空r中的所有数据

管道(pipeline)

redis默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,
如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作。

管道(pipeline)是redis在提供单个请求中缓冲多条服务器命令的基类的子类。它通过减少服务器-客户端之间反复的TCP数据库包,从而大大提高了执行批量命令的功能。

import redis
import time

pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
r = redis.Redis(connection_pool=pool)

# pipe = r.pipeline(transaction=False)    # 默认的情况下,管道里执行的命令可以保证执行的原子性,执行pipe = r.pipeline(transaction=False)可以禁用这一特性。
# pipe = r.pipeline(transaction=True)
pipe = r.pipeline() # 创建一个管道

pipe.set('name', 'jack')
pipe.set('role', 'sb')
pipe.sadd('faz', 'baz')
pipe.incr('num')    # 如果num不存在则vaule为1,如果存在,则value自增1
pipe.execute()

print(r.get("name"))
print(r.get("role"))
print(r.get("num"))

管道的命令可以写在一起,如:

pipe.set('hello', 'redis').sadd('faz', 'baz').incr('num').execute()
print(r.get("name"))
print(r.get("role"))
print(r.get("num"))

关于redis的线程安全和快速执行的原因

总结起来可以概括为以下几点:

1、单线程,线程安全

单线程指的是网络请求模块使用了一个线程(所以不用考虑并发安全性),即一个线程处理所有的网络请求,其他模块仍用多个线程。

redis实际上是采用了线程封闭的概念,把任务封闭在一个线程,自然避免了线程安全问题,不过对于依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。

2、能够快速执行的原因主要概括为以下:

(1)绝大部分请求是纯粹的内存操作(非常快速)

(2)采用单线程,避免了不必要的上下文切换和竞争条件

(3)非阻塞IO(IO多路复用)

redis在实现IO多路复用时,内部采用epoll,采用了epoll+自己实现的简单的事件框架。epolll中的读、写、关闭、连接都转换成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。上面的三个条件不是相互独立的,特别是(1),如果请求都是耗时的,采用单线程的吞吐量及性能可想而知了。

关于阻塞IO和非阻塞IO:举个例子:

阻塞IO:老李去火车站买票,排队三天买到一张退票。耗费:在车站吃喝拉撒睡 3天,其他事一件没干。

非阻塞IO:老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。耗费:往返车站6次,路上6小时,其他时间做了好多事。

linux下非阻塞IO方式有select、poll、epoll等等。select只能监听1024个连接;poll拷贝参数,导致开销过大,线程不安全,所以用epoll

一个具体的例子:

由于进程的执行过程是线性的(也就是顺序执行),当我们调用低速系统I/O(read,write,accept等等),进程可能阻塞,此时进程就阻塞在这个调用上,不能执行其他操作.阻塞很正常。假设一个服务器进程和一个客户端进程通信,服务器端read(sockfd1,bud,bufsize),此时客户端进程没有发送数据,那么read(阻塞调用)将阻塞,直到客户端调用write(sockfd,but,size)发来数据.在一个客户和服务器通信时这没什么问题;当多个客户与服务器通信时当多个客户与服务器通信时,若服务器阻塞于其中一个客户sockfd1,当另一个客户的数据到达套接字sockfd2时,服务器不能处理,仍然阻塞在read(sockfd1,...)上;此时问题就出现了,不能及时处理另一个客户的服务,咋么办?

IO多路复用。继续上面的问题,有多个客户连接,sockfd1,sockfd2,sockfd3..sockfdn同时监听这n个客户,当其中有一个发来消息时就从select的阻塞中返回,然后就调用read读取收到消息的sockfd,然后又循环回select阻塞;这样就不会因为阻塞在其中一个上而不能处理另一个客户的消息。

问题是:在读取socket1的数据时,如果其它socket有数据来,那么也要等到socket1读取完了才能继续读取其它socket的数据吧。那不是也阻塞住了吗?

1.CPU本来就是线性的不论什么都需要顺序处理,并行只能是多核CPU;

IO多路复用和线程的区别?

1.跟多线程相比较,线程切换需要切换到内核进行线程切换,需要消耗时间和资源。而I/O多路复用不需要切换线/进程,效率相对较高,特别是对高并发的应用nginx就是用I/O多路复用,故而性能极佳.但多线程编程逻辑和处理上比I/O多路复用简单.而I/O多路复用处理起来较为复杂。

6、redis的发布与订阅

先介绍一下redis的pub/sub功能。Pub/Sub功能(means Publish, Subscribe)即发布及订阅功能。基于事件的系统中,Pub/Sub是目前广泛使用的通信模型,它采用事件作为基本的通信机制,提供大规模系统所要求的松散耦合的交互模式:订阅者(如客户端)以事件订阅的方式表达出它有兴趣接收的一个事件或一类事件;发布者(如服务器)可将订阅者感兴趣的事件随时通知相关订阅者。

通俗来讲,就是说我sub端(订阅者)一直监听着,一旦pub端(发布者)发布了消息,那么我就接收过来。

#发布者.py
#coding:utf-8
import time
import redis

number_list = ['300033', '300032', '300031', '300030']
signal = ['1', '-1', '1', '-1']

rc = redis.StrictRedis(host='***', port='6379', db=3, password='********')
for i in range(len(number_list)):
    value_new = str(number_list[i]) + ' ' + str(signal[i])
    rc.publish("liao", value_new)  #发布消息到liao
#订阅者.py
#coding:utf-8
import time
import redis

rc = redis.StrictRedis(host='****', port='6379', db=3,     password='******')
ps = rc.pubsub()
ps.subscribe('liao')  #从liao订阅消息
for item in ps.listen():        #监听状态:有消息发布了就拿过来
    if item['type'] == 'message':
        print item['channel']
        print item['data']

关于数据结构,也就是item,是类似于:{'pattern': None, 'type': 'message', 'channel': 'liao', 'data': '300033 1'}这样的,所以可以通过channel来判断这个消息是属于哪一个队列里的。(运行程序的时候,先运行订阅者,在运行发布者程序)

总结,要点有两个:

一是连接方式。使用python连接redis有三种方式:①使用库中的Redis类(或StrictRedis类,其实差不多);②使用ConnectionPool连接池(可保持长连接);③使用Sentinel类(如果有多个redis做集群时,程序会自己选择一个合适的连接)。
二是订阅方法。这里使用的是StrictRedis类中的pubsub方法。连接好之后,可使用subscribe或psubscribe方法来订阅redis消息。其中subscribe是订阅一个频道,psubscribe可订阅多个频道(这样写的时候,作为参数的频道应该是一个列表)。之后就可以开始监听了。

7、redis持久化

Redis是一种高级key-value数据库。它跟memcached类似,不过数据可以持久化,而且支持的数据类型很丰富。有字符串,链表,集 合和有序集合。支持在服务器端计算集合的并,交和补集(difference)等,还支持多种排序功能。所以Redis也可以被看成是一个数据结构服务 器。
Redis的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上(这称为“半持久化模式”);也可以把每一次数据变化都写入到一个append only file(aof)里面(这称为“全持久化模式”)。 

由于Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。redis提供两种方式进行持久化,一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化),另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件)。那么这两种持久化方式有什么区别呢,改如何选择呢?网上看了大多数都是介绍这两种方式怎么配置,怎么使用,就是没有介绍二者的区别,在什么应用场景下使用。

7.1 二者的区别

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

7.2 二者优缺点

RDB存在哪些优势呢?

1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。

2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。

3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。

4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

RDB又存在哪些劣势呢?

1). 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。

2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF的优势有哪些呢?

1). 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。

2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。

3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。

4). AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

AOF的劣势有哪些呢?

1). 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

2). 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。

8、使用redis有哪些好处?

(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

(2) 支持丰富数据类型,支持string,list,set,sorted set,hash

(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

 9、redis和memcached相比的优势

(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型

(2) redis的速度比memcached快很多

(3) redis可以持久化其数据

(4)Redis支持数据的备份,即master-slave模式的数据备份。

(5) 使用底层模型不同,它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

(6)value大小:redis最大可以达到1GB,而memcache只有1MB

 10、redis常见性能问题和解决方案

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件;(Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照;AOF文件过大会影响Master重启的恢复速度)

(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...;这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

 11、redis的回收策略

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。

  使用策略规则:

  1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru

  2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random

12、Redis配置 

12.1 linux安装redis

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

12.2 启动redis服务

cd src
./redis-server

上面这种启动 redis使用的是默认配置,也可以通过启动参数告诉redis使用指定配置

./redis-server ../redis.conf  #后面是新配置文件的地址

12.3 redis参数配置

# /etc/redis.conf  /redis-server 
################################## INCLUDES ###################################
#这在你有标准配置模板但是每个redis服务器又需要个性设置的时候很有用。


################################ GENERAL #####################################
#是否在后台执行,yes:后台运行;no:不是后台运行(老版本默认)
daemonize yes

#3.2里的参数,是否开启保护模式,默认开启。要是配置里没有指定bind和密码。开启该参数后,redis只会本地进行访问,拒绝外部访问。要是开启了密码 和bind,可以开启。否 则最好关闭,设置为no。
protected-mode yes

#redis的进程文件
pidfile "/var/run/redis/redis_7021.pid"

#redis监听的端口号。
port 7021

#此参数确定了TCP连接中已完成队列(完成三次握手之后)的长度, 当然此值必须不大于Linux系统定义的/proc/sys/net/core/somaxconn值,默认是511,而Linux的默认参数值是128。当系统并发量大并且客户端速度缓慢的时候,可以将这二个参数一起参考设定。该内核参数默认值一般是128,对于负载很大的服务程序来说大大的不够。一般会将它修改为2048或者更大。在/etc/sysctl.conf中添加:net.core.somaxconn = 2048,然后在终端中执行sysctl -p。
tcp-backlog 511

#指定 redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求
bind 192.168.1.55

#配置unix socket来让redis支持监听本地连接。
unixsocket "/var/run/redis/redis_7021.sock"

#配置unix socket使用文件的权限
# unixsocketperm 700

#tcp keepalive参数。如果设置不为0,就使用配置tcp的SO_KEEPALIVE值,使用keepalive有两个好处:检测挂掉的对端。降低中间设备出问题而导致网络看似连接却已经与对端端口的问题。在Linux内核中,设置了keepalive,redis会定时给对端发送ack。检测到对端关闭需要两倍的设置值。
tcp-keepalive 0

#指定了服务端日志的级别。级别包括:debug(很多信息,方便开发、测试),verbose(许多有用的信息,但是没有debug级别信息多),notice(适当的日志级别,适合生产环境),warn(只有非常重要的信息)
loglevel notice
 
#指定了记录日志的文件。空字符串的话,日志会打印到标准输出设备。后台运行的redis标准输出是/dev/null。
logfile "/var/log/redis/redis_7021.log"
 
#是否打开记录syslog功能
# syslog-enabled no
 
#syslog的标识符。
# syslog-ident redis
 
#日志的来源、设备
# syslog-facility local0
 
#数据库的数量,默认使用的数据库是DB 0。可以通过”SELECT “命令选择一个db
databases 16
 
################################ SNAPSHOTTING ################################
# 快照配置
# 注释掉“save”这一行配置项就可以让保存数据库功能失效
# 设置sedis进行数据库镜像的频率。
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化)
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化)
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
#save 900 1
#save 300 10
#save 60 10000
 
#当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作,可以通过info中的rdb_last_bgsave_status了解RDB持久化是否有错误
stop-writes-on-bgsave-error yes
 
#使用压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间
rdbcompression yes
 
#是否校验rdb文件。从rdb格式的第五个版本开始,在rdb文件的末尾会带上CRC64的校验和。这跟有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗,所以如果你追求高性能,可以关闭该配置。
rdbchecksum no
 
#rdb文件的名称
dbfilename "dump.rdb"
 
#数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
dir "/var/lib/redis_7021"
 
################################# REPLICATION #################################
#复制选项,slave复制对应的master。
# slaveof <masterip> <masterport>
 
#如果master设置了requirepass,那么slave要连上master,需要有master的密码才行。masterauth就是用来配置master的密码,这样可以在连上master后进行认证。
masterauth "XYZ"
 
#当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。2) 如果slave-serve-stale-data设置为no,除去INFO和SLAVOF命令之外的任何请求都会返回一个错误”SYNC with master in progress”。
slave-serve-stale-data yes
 
#作为从服务器,默认情况下是只读的(yes),可以修改成NO,用于写(不建议)。
slave-read-only yes
 
#是否使用socket方式复制数据。目前redis复制提供两种方式,disk和socket。如果新的slave连上来或者重连的slave无法部分同步,就会执行全量同步,master会生成rdb文件。有2种方式:disk方式是master创建一个新的进程把rdb文件保存到磁盘,再把磁盘上的rdb文件传递给slave。socket是master创建一个新的进程,直接把rdb文件以socket的方式发给slave。disk方式的时候,当一个rdb保存的过程中,多个slave都能共享这个rdb文件。socket的方式就的一个个slave顺序复制。在磁盘速度缓慢,网速快的情况下推荐用socket方式。
repl-diskless-sync no
 
#diskless复制的延迟时间,防止设置为0。一旦复制开始,节点不会再接收新slave的复制请求直到下一个rdb传输。所以最好等待一段时间,等更多的slave连上来。
repl-diskless-sync-delay 5
 
#slave根据指定的时间间隔向服务器发送ping请求。时间间隔可以通过 repl_ping_slave_period 来设置,默认10秒。
repl-ping-slave-period 5
 
#复制连接超时时间。master和slave都有超时时间的设置。master检测到slave上次发送的时间超过repl-timeout,即认为slave离线,清除该slave信息。slave检测到上次和master交互的时间超过repl-timeout,则认为master离线。需要注意的是repl-timeout需要设置一个比repl-ping-slave-period更大的值,不然会经常检测到超时。
repl-timeout 60
 
#是否禁止复制tcp链接的tcp nodelay参数,可传递yes或者no。默认是no,即使用tcp nodelay。如果master设置了yes来禁止tcp nodelay设置,在把数据复制给slave的时候,会减少包的数量和更小的网络带宽。但是这也可能带来数据的延迟。默认我们推荐更小的延迟,但是在数据量传输很大的场景下,建议选择yes。
repl-disable-tcp-nodelay no
 
#复制缓冲区大小,这是一个环形复制缓冲区,用来保存最新复制的命令。这样在slave离线的时候,不需要完全复制master的数据,如果可以执行部分同步,只需要把缓冲区的部分数据复制给slave,就能恢复正常复制状态。缓冲区的大小越大,slave离线的时间可以更长,复制缓冲区只有在有slave连接的时候才分配内存。没有slave的一段时间,内存会被释放出来,默认1m。
repl-backlog-size 32mb
 
#master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。单位为秒。
repl-backlog-ttl 3600
 
#当master不可用,Sentinel会根据slave的优先级选举一个master。最低的优先级的slave,当选master。而配置成0,永远不会被选举。
slave-priority 100
 
#redis提供了可以让master停止写入的方式,如果配置了min-slaves-to-write,健康的slave的个数小于N,mater就禁止写入。master最少得有多少个健康的slave存活才能执行写命令。这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。设置为0是关闭该功能。
# min-slaves-to-write 3
 
#延迟小于min-slaves-max-lag秒的slave才认为是健康的slave。
# min-slaves-max-lag 10
 
# 设置1或另一个设置为0禁用这个特性。
# Setting one or the other to 0 disables the feature.
# By default min-slaves-to-write is set to 0 (feature disabled) and
# min-slaves-max-lag is set to 10.
 
################################## SECURITY ###################################
#requirepass配置可以让用户使用AUTH命令来认证密码,才能使用其他命令。这让redis可以使用在不受信任的网络中。为了保持向后的兼容性,可以注释该命令,因为大部分用户也不需要认证。使用requirepass的时候需要注意,因为redis太快了,每秒可以认证15w次密码,简单的密码很容易被攻破,所以最好使用一个更复杂的密码。
requirepass "XYZ"
 
#把危险的命令给修改成其他名称。比如CONFIG命令可以重命名为一个很难被猜到的命令,这样用户不能使用,而内部工具还能接着使用。
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
rename-command SHUTDOWN REDIS_SHUTDOWN
rename-command FLUSHDB REDIS_FLUSHDB
rename-command FLUSHALL REDIS_FLUSHALL
rename-command KEYS REDIS_KEYS
#rename-command CONFIG REDIS_CONFIG
#rename-command SLAVEOF REDIS_SLAVEOF
#设置成一个空的值,可以禁止一个命令
# rename-command CONFIG ""
################################### LIMITS ####################################
 
# 设置能连上redis的最大客户端连接数量。默认是10000个客户端连接。由于redis不区分连接是客户端连接还是内部打开文件或者和slave连接等,所以maxclients最小建议设置到32。如果超过了maxclients,redis会给新的连接发送’max number of clients reached’,并关闭连接。
# maxclients 10000
 
#redis配置的最大内存容量。当内存满了,需要配合maxmemory-policy策略进行处理。注意slave的输出缓冲区是不计算在maxmemory内的。所以为了防止主机内存使用完,建议设置的maxmemory需要更小一些。
maxmemory 512mb
 
#内存容量超过maxmemory后的处理策略。
#volatile-lru:利用LRU算法移除设置过过期时间的key。
#volatile-random:随机移除设置过过期时间的key。
#volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
#allkeys-lru:利用LRU算法移除任何key。
#allkeys-random:随机移除任何key。
#noeviction:不移除任何key,只是返回一个写错误。
#上面的这些驱逐策略,如果redis没有合适的key驱逐,对于写命令,还是会返回错误。redis将不再接收写请求,只接收get请求。写命令包括:set setnx setex append incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby getset mset msetnx exec sort。
maxmemory-policy allkeys-lru
 
#lru检测的样本数。使用lru或者ttl淘汰算法,从需要淘汰的列表中随机选择sample个key,选出闲置时间最长的key移除。
# maxmemory-samples 5
 
############################## APPEND ONLY MODE ###############################
#默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了。但是redis如果中途宕机,会导致可能有几分钟的数据丢失,根据save来策略进行持久化,Append Only File是另一种持久化方式,可以提供更好的持久化特性。Redis会把每次写入的数据在接收后都写入 appendonly.aof 文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。
appendonly no
 
#aof文件名
appendfilename "appendonly.aof"
 
#aof持久化策略的配置
#no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
#always表示每次写入都执行fsync,以保证数据同步到磁盘。
#everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
appendfsync everysec
 
# 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。
no-appendfsync-on-rewrite yes
 
#aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
#设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写
auto-aof-rewrite-min-size 64mb
 
#aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项(redis宕机或者异常终止不会造成尾部不完整现象。)出现这种现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。
aof-load-truncated yes
 
################################ LUA SCRIPTING ###############################
# 如果达到最大时间限制(毫秒),redis会记个log,然后返回error。当一个脚本超过了最大时限。只有SCRIPT KILL和SHUTDOWN NOSAVE可以用。第一个可以杀没有调write命令的东西。要是已经调用了write,只能用第二个命令杀。
lua-time-limit 5000
 
################################ REDIS CLUSTER ###############################
#集群开关,默认是不开启集群模式。
#cluster-enabled yes
 
#集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。这个文件并不需要手动配置,这个配置文件有Redis生成并更新,每个Redis集群节点需要一个单独的配置文件,请确保与实例运行的系统中配置文件名称不冲突
#cluster-config-file nodes-7021.conf
 
#节点互连超时的阀值。集群节点超时毫秒数
#cluster-node-timeout 30000
 
#在进行故障转移的时候,全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间了,导致数据过于陈旧,这样的slave不应该被提升为master。该参数就是用来判断slave节点与master断线的时间是否过长。判断方法是:
#比较slave断开连接的时间和(node-timeout * slave-validity-factor) + repl-ping-slave-period
#如果节点超时时间为三十秒, 并且slave-validity-factor为10,假设默认的repl-ping-slave-period是10秒,即如果超过310秒slave将不会尝试进行故障转移
#可能出现由于某主节点失联却没有从节点能顶上的情况,从而导致集群不能正常工作,在这种情况下,只有等到原来的主节点重新回归到集群,集群才恢复运作
#如果设置成0,则无论从节点与主节点失联多久,从节点都会尝试升级成主节
#cluster-slave-validity-factor 10
 
#master的slave数量大于该值,slave才能迁移到其他孤立master上,如这个参数若被设为2,那么只有当一个主节点拥有2 个可工作的从节点时,它的一个从节点会尝试迁移。
#主节点需要的最小从节点数,只有达到这个数,主节点失败时,它从节点才会进行迁移。
# cluster-migration-barrier 1
 
#默认情况下,集群全部的slot有节点分配,集群状态才为ok,才能提供服务。设置为no,可以在slot没有全部分配的时候提供服务。不建议打开该配置,这样会造成分区的时候,小分区的master一直在接受写请求,而造成很长时间数据不一致。
#在部分key所在的节点不可用时,如果此参数设置为”yes”(默认值), 则整个集群停止接受操作;如果此参数设置为”no”,则集群依然为可达节点上的key提供读操作
#cluster-require-full-coverage yes
 
################################## SLOW LOG ###################################
###slog log是用来记录redis运行中执行比较慢的命令耗时。当命令的执行超过了指定时间,就记录在slow log中,slog log保存在内存中,所以没有IO操作。
#执行时间比slowlog-log-slower-than大的请求记录到slowlog里面,单位是微秒,所以1000000就是1秒。注意,负数时间会禁用慢查询日志,而0则会强制记录所有命令。
slowlog-log-slower-than 10000
 
#慢查询日志长度。当一个新的命令被写进日志的时候,最老的那个记录会被删掉。这个长度没有限制。只要有足够的内存就行。你可以通过 SLOWLOG RESET 来释放内存。
slowlog-max-len 128
 
################################ LATENCY MONITOR ##############################
#延迟监控功能是用来监控redis中执行比较缓慢的一些操作,用LATENCY打印redis实例在跑命令时的耗时图表。只记录大于等于下边设置的值的操作。0的话,就是关闭监视。默认延迟监控功能是关闭的,如果你需要打开,也可以通过CONFIG SET命令动态设置。
latency-monitor-threshold 0
 
############################# EVENT NOTIFICATION ##############################
#键空间通知使得客户端可以通过订阅频道或模式,来接收那些以某种方式改动了 Redis 数据集的事件。因为开启键空间通知功能需要消耗一些 CPU ,所以在默认配置下,该功能处于关闭状态。
#notify-keyspace-events 的参数可以是以下字符的任意组合,它指定了服务器该发送哪些类型的通知:
##K 键空间通知,所有通知以 __keyspace@__ 为前缀
##E 键事件通知,所有通知以 __keyevent@__ 为前缀
##g DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知
##$ 字符串命令的通知
##l 列表命令的通知
##s 集合命令的通知
##h 哈希命令的通知
##z 有序集合命令的通知
##x 过期事件:每当有过期键被删除时发送
##e 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送
##A 参数 g$lshzxe 的别名
#输入的参数中至少要有一个 K 或者 E,否则的话,不管其余的参数是什么,都不会有任何 通知被分发。详细使用可以参考http://redis.io/topics/notifications
 
notify-keyspace-events "e"
 
############################### ADVANCED CONFIG ###############################
#数据量小于等于hash-max-ziplist-entries的用ziplist,大于hash-max-ziplist-entries用hash
hash-max-ziplist-entries 512
#value大小小于等于hash-max-ziplist-value的用ziplist,大于hash-max-ziplist-value用hash。
hash-max-ziplist-value 64
 
#数据量小于等于list-max-ziplist-entries用ziplist,大于list-max-ziplist-entries用list。
list-max-ziplist-entries 512
#value大小小于等于list-max-ziplist-value的用ziplist,大于list-max-ziplist-value用list。
list-max-ziplist-value 64
 
#数据量小于等于set-max-intset-entries用iniset,大于set-max-intset-entries用set。
set-max-intset-entries 512
 
#数据量小于等于zset-max-ziplist-entries用ziplist,大于zset-max-ziplist-entries用zset。
zset-max-ziplist-entries 128
#value大小小于等于zset-max-ziplist-value用ziplist,大于zset-max-ziplist-value用zset。
zset-max-ziplist-value 64
 
#value大小小于等于hll-sparse-max-bytes使用稀疏数据结构(sparse),大于hll-sparse-max-bytes使用稠密的数据结构(dense)。一个比16000大的value是几乎没用的,建议的value大概为3000。如果对CPU要求不高,对空间要求较高的,建议设置到10000左右。
hll-sparse-max-bytes 3000
 
#Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用。当你的使用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no。如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存。
activerehashing yes
 
##对客户端输出缓冲进行限制可以强迫那些不从服务器读取数据的客户端断开连接,用来强制关闭传输缓慢的客户端。
#对于normal client,第一个0表示取消hard limit,第二个0和第三个0表示取消soft limit,normal client默认取消限制,因为如果没有寻问,他们是不会接收数据的。
client-output-buffer-limit normal 0 0 0
#对于slave client和MONITER client,如果client-output-buffer一旦超过256mb,又或者超过64mb持续60秒,那么服务器就会立即断开客户端连接。
client-output-buffer-limit slave 256mb 64mb 60
#对于pubsub client,如果client-output-buffer一旦超过32mb,又或者超过8mb持续60秒,那么服务器就会立即断开客户端连接。
client-output-buffer-limit pubsub 32mb 8mb 60
 
#redis执行任务的频率为1s除以hz。
hz 10
 
#在aof重写的时候,如果打开了aof-rewrite-incremental-fsync开关,系统会每32MB执行一次fsync。这对于把文件写入磁盘是有帮助的,可以避免过大的延迟峰值。
aof-rewrite-incremental-fsync yes

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值