Redis入门

一、Redis介绍

1、介绍

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

redis是一个高性能的key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、
set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。

与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。
这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。
由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。


Redis
{
  key: value
}
支持5大数据类型
{
  key1:"",
  key2: [],
  key3: {field: ""},
  key4: 集合
  key5: 有序集合
}

 

2、特点

Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
Redis支持数据的备份,即master-slave模式的数据备份

 

3、优势

性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。
多个操作也支持事务,整个事务不是原子性,事务里面的操作是原子性的,失败就失败,成功就成功,。
丰富的特性 – Redis还支持 publish/subscribe(发布/订阅), 通知, key 过期等等特性。

 

4、安装及使用推荐网站

redis默认端口:6379
mysql默认端口:3306

菜鸟教程: http://www.runoob.com/redis/redis-install.html

 

5、使用redis

1.启动redis服务
打开一个 cmd 窗口 使用 cd 命令切换目录到redis的目录 C:\redis 运行:
redis-server.exe redis.windows.conf

如果想方便的话,可以把 redis 的路径加到系统的环境变量里,这样就省得再输路径了,
后面的那个 redis.windows.conf 可以省略,如果省略,会启用默认的。


2. 操作redis数据库
这时候另启一个 cmd 窗口,原来的不要关闭,不然就无法访问服务端了。
切换到 redis 目录下运行:
redis-cli.exe -h 127.0.0.1 -p 6379

设置键值对:
set myKey abc

取出键值对:
get myKey

 

Redis的持久化、主从复制 

 

6.不同系统的数据库启动命令

1、windows下启动mysql
1.启动server端  - 可以在service服务中操作,也可以打开cmd窗口
> net start mysql


2.停止服务 - 可以在service服务中操作,也可以打开cmd窗口
> net stop mysql


3.登录用户
> mysql -u root -p -h 127.0.0.1  # mysql5.6默认是没有密码的,遇到password直接按回车键


2、linux下启动mysql
1.启动mysql
systemctl start mysql 

2.linux客户端连接自己 
mysql -u root -p -h 127.0.0.1
    -u 用户
    -p 密码验证
    -h 连接的主机地址


3、window下启动redis
1.启动redis服务
打开一个 cmd 窗口 使用 cd 命令切换目录到redis的目录 C:\redis 运行:
redis-server.exe redis.windows.conf


2. 进入redis数据库
这时候另启一个 cmd 窗口,原来的不要关闭,不然就无法访问服务端了。
切换到 redis 目录下运行:
redis-cli.exe -h 127.0.0.1 -p 6379


4、linux下启动redis
1.启动redis服务端
redis-server /opt/redis-4.0.10/redis.conf  # 指定配置文件启动redis


2.登录redis客户端
redis-cli -p 6380
如果我们redis是设置了密码的,这种方法进入redis,还需要验证密码,否则操作不了
auth redis123  # redis123是你们自定义的密码

 

二、一些常用命令

1、关于key的命令
1. DEL key
该命令用于在 key 存在时删除 key。

2. DUMP key 
序列化给定 key ,并返回被序列化的值。

3. EXISTS key 
检查给定 key 是否存在。

4. EXPIRE key seconds
为给定 key 设置过期时间,以秒计。

5. EXPIREAT key timestamp 
EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。

6. PEXPIRE key milliseconds 
设置 key 的过期时间以毫秒计。

7. PEXPIREAT key milliseconds-timestamp 
设置 key 过期时间的时间戳(unix timestamp) 以毫秒计

8.TTL key
以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)
返回值:
当 key 不存在时,返回 -2
当 key 存在但没有设置剩余生存时间时,返回 -1
否则,以秒为单位,返回 key 的剩余生存时间

 

三、redis基础

redis是一种高级的key:value存储系统,其中value支持五种数据类型
String: 字符串
List: 列表
Set: 集合
Sorted Set: 有序集合
Hash: 散列

 

0、查看redis中有哪些key

查看redis中有哪些key: KEYS pattern

# 例如,列出所有的key
keys *

# 列出匹配的key
keys my*
1) myname
2) myage


查看key的类型:type your-key
type myname

 

1、strings类型

操作

# 增
set    设置key
get      获取key
mset     设置多个键值对
mget     获取多个键值对
append   追加string

# 删
del      删除key

# 改
set key NewValue  # 覆盖修改
rename key newkey   # 改名

incr     递增+1
decr     递减-1

 

示例

127.0.0.1:6379> set name 'dog'  # 设置key
OK
127.0.0.1:6379> get name  # 获取value
"dog"
127.0.0.1:6379> set name 'BigDog'  # 覆盖修改key
OK
127.0.0.1:6379> get name  # 获取value
"BigDog"
127.0.0.1:6379> append name ' is you'  # 向name这个key追加
(integer) 13
127.0.0.1:6379> get name   # 获取value
"BigDog is you"
127.0.0.1:6379> rename name dog_name  # 修改key名
OK
127.0.0.1:6379> keys *
1) "dog_name"
127.0.0.1:6379> mset age 18 hight 180 long 18.8  # 一次性设置多个键值对
OK
127.0.0.1:6379> mget age hight long  # 一次性获取多个键的值
1) "18"
2) "180"
3) "18.8"
127.0.0.1:6379> get age  # 获取value
"18"
127.0.0.1:6379> keys *  # 找到所有key
1) "long"
2) "hight"
3) "age"
4) "dog_name"
127.0.0.1:6379> del dog_name  # 删除key
(integer) 1
127.0.0.1:6379> get dog_name  # 获取不存在的value,为nil
(nil)
127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> get num
"10"
127.0.0.1:6379> incr num  # 递增+1
(integer) 11
127.0.0.1:6379> get num
"11"
127.0.0.1:6379> decr num  # 递减-1
(integer) 10
127.0.0.1:6379> get num
"10"

 

2、list类型

操作

# 这里的 l 、 r 代表left right
lpush 从列表左边插(一次可以添加多个value) rpush 从列表右边插(一次可以添加多个value) lpushx/rpushx key存在则添加值,不存在不处理(一次只能添加一个value) lpop 删除最左边一个元素 rpop 删除最右边一个元素

# 这里的 l 代表 list lrange 查看指定范围(长度)内的元素 lrange key start stop ltrim 截取一定长度列表,其余的value删除
llen 查看list的长度

 

示例

127.0.0.1:6379> lpush list1 'a' 'b' 'c' 'd'  # 新建一个list1,从左边放入四个元素
(integer) 4
127.0.0.1:6379> llen list1  # 查看list1的长度
(integer) 4
127.0.0.1:6379> lrange list1 0 -1  # 查看list所有元素
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> rpush list1 'haha'  # 从右边插入haha
(integer) 5
127.0.0.1:6379> lrange list1 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
5) "haha"
127.0.0.1:6379> lpushx list3 'xixi'  # list3存在则添加元素,不存在则不作处理
(integer) 0
127.0.0.1:6379> ltrim list1 0 3  # 截取队列的值,从索引0取到3,删除其余的元素
OK
127.0.0.1:6379> lrange list1 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> lpop list1  # 删除左边的第一个
"d"
127.0.0.1:6379> lrange list1 0 -1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> rpop list1  # 删除右边的第一个
"a"
127.0.0.1:6379> lrange list1 0 -1
1) "c"
2) "b"

 

3、set集合类型

redis的集合,是一种无序的集合,集合中的元素没有先后顺序,且集合成员是唯一的。

操作

sadd/srem   添加/删除 元素
sismember   判断是否为set的一个元素
smembers    返回集合所有的成员
scard 获取集合的成员数,即集合大小(长度) sdiff 返回一个集合和其他集合的差异 sinter 返回几个集合的交集 sunion 返回几个集合的并集

 

示例

127.0.0.1:6379> sadd school class1 class2  # 添加一个名叫school的集合,有2个元素,不加引号就当做字符串处理
(integer) 2
127.0.0.1:6379> smembers school  # 查看集合school成员
1) "class1"
2) "class2"
127.0.0.1:6379> scard school  # 获取集合成员数
(integer) 2
127.0.0.1:6379> srem school class1  # 删除school的class1成员
(integer) 1
127.0.0.1:6379> smembers school
1) "class2"
127.0.0.1:6379> sismember school class1  # 是否是school的成员,不是返回0,是返回1
(integer) 0
127.0.0.1:6379> sadd school class3  # 给school添加一个新成员
(integer) 1
127.0.0.1:6379> smembers school
1) "class3"
2) "class2"
127.0.0.1:6379> sadd school2 class3 class4  # 添加一个名叫school2的集合,有2个元素
(integer) 1
127.0.0.1:6379> smembers school2
1) "class3"
2) "class4"
127.0.0.1:6379> sdiff school school2  # 找出集合school中有的,而school2中没有的元素
1) "class2"
127.0.0.1:6379> sdiff school2 school  # 找出集合school2中有的,而school中没有的元素
1) "class4"
127.0.0.1:6379> sinter school school2  # 找出school和school2的交集
1) "class3"
127.0.0.1:6379> sunion school school2  # 找出school和school2的并集(自动去重)
1) "class3"
2) "class4"
3) "class2"

 

4、sorted set有序集合类型(zset)

zset是set的一个升级版本,他在set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,zset会自动重新按新的值调整顺序。 可以对指定键的值进行排序权重的设定,它应用排名模块比较多。

比如一个存储全班同学成绩的 Sorted Sets,其集合 value 可以是同学的学号,而 score 就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。另外还可以用 Sorted Sets 来做带权重的队列,比如普通消息的 score 为1,重要消息的 score 为2,然后工作线程可以选择按 score 的倒序来获取工作任务,让重要的任务优先执行。

zset集合可以完成有序执行、按照优先级执行的情况;

zadd/zrem   添加/删除 元素
zrange      返回指定范围的元素 顺序
zrevrange   返回指定范围的元素 倒序
zscore      查询元素的分数
zcard       获取集合的成员数,即集合大小(长度)
zcount      统计集合某个区间的元素个数

 

示例

#               zadd 集合名 分数1 元素1 分数2 元素2
127.0.0.1:6379> zadd school 1  class1  2  class2
(integer) 2
127.0.0.1:6379> zrange school 0 -1  # 按分数排序的
1) "class1"
2) "class2"
127.0.0.1:6379> zrevrange school 0 -1
1) "class2"
2) "class1"
127.0.0.1:6379> zadd school 3 class3
(integer) 1
127.0.0.1:6379> zscore school class1  # 查看某个元素的分数
"1"
127.0.0.1:6379> zcard school  # 查询集合的成员数
(integer) 3
127.0.0.1:6379> zcount school 0 1  # 查询集合的0到1分数的元素个数
(integer) 1
127.0.0.1:6379> zrem school class2
(integer) 1
127.0.0.1:6379> zrange school 0 -1
1) "class1"
2) "class3"
127.0.0.1:6379> zadd school 0 class0
(integer) 1
127.0.0.1:6379> zscore school class0
"0"
127.0.0.1:6379> zrange school 0 -1  # 分数0最小,重新排序到最前面
1) "class0"
2) "class1"
3) "class3"
127.0.0.1:6379>

 

 

5、哈希数据结构

hashes即哈希。哈希是从redis-2.0.0版本之后才有的数据结构。

hashes存的是字符串和字符串值之间的映射,比如一个用户要存储其全名、姓氏、年龄等等,就很适合使用哈希。

操作

hset    设置散列值
hget    获取散列值
hmset   设置多对散列值
hmget   获取多对散列值
hgetall 获取这个key的所有field和value hsetnx 如果散列已经存在,则不设置(防止覆盖key) hkeys 返回所有keys hvals 返回所有values hlen 返回散列包含域(field)的数量 hdel 删除散列指定的域(field) hexists 判断是否存在

 

注意

Hash不支持多次嵌套,即
"key": {'field': '不能再对应字典'}

"key": {'field': {...}}   --> 错误 
若想嵌套字典,可以json.dumps后存入,取出数据的时候可以json.loads

 

语法

hset key field value
结构如下
    key: {
        field1: value1,
        field2: value2,
    }

 

示例

127.0.0.1:6379> hset school name 'ChinaSchool'  # 创建一个key为school的哈希数据
(integer) 1
127.0.0.1:6379> hget school name  # 获取school的name的值
"ChinaSchool"
127.0.0.1:6379> hmset school age 100 area 2000  # 给school批量设置键值对
OK
127.0.0.1:6379> hmget school name age area  # 批量获取school的键对应的值
1) "ChinaSchool"
2) "100"
3) "2000"
127.0.0.1:6379> hgetall school  # 获取school所有的键值对
1) "name"
2) "ChinaSchool"
3) "age"
4) "100"
5) "area"
6) "2000"
127.0.0.1:6379> hkeys school  # 获取school的所有key
1) "name"
2) "age"
3) "area"
127.0.0.1:6379> hvals school  # 获取school的所有值
1) "ChinaSchool"
2) "100"
3) "2000"
127.0.0.1:6379> hlen school  # 获取school的长度
(integer) 3
127.0.0.1:6379> hexists school name  # 判断school中是否有name这个键,有就返回1,没有就返回0
(integer) 1
127.0.0.1:6379> hdel shcool area  # 删除school中的area键值对
(integer) 1

 

6、redis如何切换db

redis中切换db意思就是跟MySQL类似,将数据存在不同的数据库中。

redis配置文件中,有个配置项 databases 16 
代表的是redis默认配置了16个库(0~15号). 且默认使用的是0号库.

如果要切换库则使用:
select # //#表示几号库.

比如:
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 2  # 切换到 db 1后,这里会显示 ...[1]
OK
127.0.0.1:6379[2]> select 0  # 切换到 db 2后,这里会显示 ...[2]
OK
127.0.0.1:6379>  # 默认的db 不展示数字

 

7、额外的危险操作(慎用)

获取redis数据库中所有的键(这个不危险)
keys *

删除所有Key,可以使用Redis的flushdb和flushall命令(危险慎用)
# 删除当前数据库中的所有Key
flushdb

# 删除所有数据库中的key
flushall

 

8、redis过期删除策略和内存淘汰策略

比如这样的应用场景:Redis只能存5G数据,可你写了10G,如何删除多余的5G数据?

答案就是:使用redis过期删除策略和内存淘汰策略。

Redis过期删除策略采用的是 -- 定期删除+惰性删除策略

  • 定期删除,redis每过一段时间(默认100ms)进行随机抽取检查,有过期key则删除;
  • 惰性删除,放任过期键不管,在每次获取键时检查是否过期,过期则删除。

定期删除函数的运行频率,在Redis2.6版本中,规定每秒运行10次,大概100ms运行一次。

在Redis2.8版本后,可以通过修改配置文件redis.conf 的 hz 选项来调整这个次数。

 

采用定期删除+惰性删除策略其实也会出现其他问题:

    如果定期删除没删除掉key,也没有及时去获取key(及惰性删除也没生效),那么该策略就不灵了。

redis还有一种数据删除策略:定时删除
原理:创建一个定时器来监控key,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作 
优点:节约内存,到时就删除,快速释放掉不必要的内存占用 
缺点:CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量 
总结:用处理器性能换取存储空间 (拿时间换空间)

因此如果有人问:为什么不使用定时删除策略?
答:定时删除,用一个定时器来监控key,过期则删除。虽然内存能够及时释放,但是十分消耗CPU资源,在大并发请求下,CPU要将时间应用在处理请求上,而不是删除key,因此没有采用此策略。

 

补救措施是采用内存淘汰策略

在配置文件redis.conf 中,可以通过参数 maxmemory <bytes> 来设定最大内存

当现有内存大于 maxmemory 时,便会触发redis主动淘汰内存方式,通过设置 maxmemory-policy

有如下几种淘汰方式:

  1. volatile-lru   利用LRU算法移除设置了过期时间的最近最少使用的key (LRU:最近使用 Least Recently Used ) 。
  2. allkeys-lru   利用LRU算法移除任何key (和上一个相比,删除的key包括设置过期时间和不设置过期时间的)。通常使用该方式。
  3. volatile-random 移除设置了过期时间的随机key 。
  4. allkeys-random  无差别的随机移除。
  5. volatile-ttl   移除即将过期的key(minor TTL) 
  6. noeviction 不移除任何key,只是返回一个写错误 ,默认选项,一般不会选用。

 

四、在python中使用redis

1. pip install redis

2. 导入建立连接
import redis

# 建立连接
# 默认的值set后都会设置成bytes类型,decode_responses=True可以把类型转换成你原本的值的类型
conn = redis.Redis(host='localhost', port=6379, decode_responses=True)

# 字符串的操作
conn.set('name', 'xiaoming')
print(conn.get('name'))

conn.mset({'key1': 'v1', 'key2': 'v2'})
print(conn.mget(['key1', 'key2']))
print(conn.get('key1'))


# key操作
print(conn.exists('name'))
print(conn.exists('age'))

conn.expire('name', 5)

# Hash操作
conn.hset('xiaoming', 'hobby', '')
print(conn.hget('xiaoming', 'hobby'))

conn.hmset('heihei', {'f1': 'v1', 'f2': 'v2'})
print(conn.hmget('heihei', ['f1', 'f2']))
print(conn.hgetall('heihei'))

import json
my_dict = {
    'hobby1': '',
    'hobby2': ''
}
ret = json.dumps(my_dict, ensure_ascii=False)
conn.hset('xiaohong', 'hobby', ret)
print(conn.hget('xiaohong', 'hobby'))
print(conn.hgetall('xiaohong'))

# 额外操作 嘿嘿!
# 给redis中的数据设置过期时间:ex=seconds
conn.set('heiheihei', '嘿嘿嘿', ex=7)  # 7秒后过期


# scan_iter模糊匹配,匹配成功的对象放入一个生成器
conn.hset('user_1', 'name', '1')
conn.hset('user_2', 'name', '2')
user_key = 'user_*'
all_user_keys = conn.scan_iter(user_key)  # 所有以user_开头的key都会匹配成功,存到all_user_keys这个生成器里面
for key in all_user_keys:
    print(conn.hgetall(key))


# 判断这个key是否存在
conn.exists('heiheihei')
conn.exists('user_1')

# 这个key的某个键是否存在
conn.hexists('user_1', 'name')

# 删除这个key的数据
conn.delete('xiaohong')


# list操作
conn.lpush("list1", "a", 2, 3)  # 新建一个list1,从左边放入3个元素
conn.rpush("list1", "b", "d")  # 在右边加入2个元素
print(conn.lrange("list1", 0, -1))   # 查看list1所有元素

conn.lpop("list1")  # 删除左边第一个元素
print(conn.lrange("list1", 0, -1))


# set操作
conn.sadd("school", "class1", "class2")  # 添加一个名叫school的集合,有2个元素
print(conn.smembers("school"))  # 查看集合school成员

conn.srem("school", "class1")  # 删除school的class1成员
print(conn.smembers("school"))

print(conn.sismember("school", "class1"))  # 是否是school的成员

conn.sadd("school", "class3")  # 给school添加一个新成员

conn.sadd("school2", "class3", "class4")  # 添加一个名叫school2的集合,有2个元素
print(conn.smembers("school2"))

print(conn.sdiff("school", "school2"))  # 找出集合school中有的,而school2中没有的元素
print(conn.sdiff("school2", "school"))  # 找出集合school2中有的,而school中没有的元素
print(conn.sinter("school", "school2"))  # 找出school和school2的交集
print(conn.sunion("school", "school2"))  # 找出school和school2的并集(自动去重)

 

五、Redis文件及配置概述

1、文件作用

redis-benchmark : Redis 性能测试工具
redis-check-aof : 检查 aof 日志的工具
redis-check-dump : 检查 rdb 日志的工具
redis-cli : 连接用的客户端
redis-server : Redis 服务进程

 

2、redis.windows.conf 文件的配置(Linux中的名字叫做redis.conf)

1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

    daemonize no

2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

    pidfile /var/run/redis.pid

3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

    port 6379

4. 绑定的主机地址

    bind 127.0.0.1

5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

    timeout 300

6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

    loglevel verbose

7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null

    logfile stdout

8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

    databases 16

9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

    save <seconds> <changes>

    Redis默认配置文件中提供了三个条件:

    save 900 1

    save 300 10

    save 60 10000

    分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

 

10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

    rdbcompression yes

11. 指定本地数据库文件名,默认值为dump.rdb

    dbfilename dump.rdb

12. 指定本地数据库存放目录

    dir ./

13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

    slaveof <masterip> <masterport>

14. 当master服务设置了密码保护时,slav服务连接master的密码

    masterauth <master-password>

15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭

    requirepass foobared

16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

    maxclients 128

17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区

    maxmemory <bytes>

18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

    appendonly no

19. 指定更新日志文件名,默认为appendonly.aof

     appendfilename appendonly.aof

20. 指定更新日志条件,共有3个可选值: 
    no:表示等操作系统进行数据缓存同步到磁盘(快) 
    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) 
    everysec:表示每秒同步一次(折衷,默认值)

    appendfsync everysec

 

21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)

     vm-enabled no

22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

     vm-swap-file /tmp/redis.swap

23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

     vm-max-memory 0

24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值

     vm-page-size 32

25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

     vm-pages 134217728

26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

     vm-max-threads 4

27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

    glueoutputbuf yes

28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

    hash-max-zipmap-entries 64

    hash-max-zipmap-value 512

29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)

    activerehashing yes

30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

    include /path/to/local.conf
View Code

 

六、连接池

1、连接池的好处

Redis服务器可以单独作为一个数据库服务器来独立存在。假设Redis服务器与客户端分处在异地,虽然基于内存的Redis数据库有着超高的性
能,但是底层的网络通信却占用了一次数据请求的大量时间,因为每次数据交互都需要先建立连接,假设一次数据交互总共用时30ms,超高
性能的Redis数据库处理数据所花的时间可能不到1ms,也即是说前期的连接占用了29ms,连接池则可以实现在客户端建立多个链接并且不释
放,当需要使用连接的时候通过一定的算法获取已经建立的连接,使用完了以后则还给连接池,这就免去了数据库连接所占用的时间。


这里的连接池指的是:针对同一个数据库可以建立多个连接,我们从连接池中获取的仅仅是一个连接,至于多个
连接到达单进程单线程的Redis之后怎么处理则是另外一回事(采用多路IO复用)。

使用连接池拿到连接:

import redis
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True, max_connections=5)
conn = redis.Redis(connection_pool=pool)

 

注意:
-- redis.conf下默认配置的数据库 database=16
-- 所有的数据库都是以数字标识的 从0开始
-- 默认的数据库db=0

 

七、事务

1、介绍

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

批量操作在发送 EXEC 命令前被放入队列缓存。
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:
  开始事务。
  命令入队。
  执行事务。

注意:与mysql事务的区别
mysql:开启了事务后,其他命令不能插入进来,如果其中一项命令执行失败,那么在事务里面的所有命令都不会执行成功。(具有原子性)
redis:开启了事务后,其他命令不能插入进来,如果其中一项命令执行失败,那么在事务里面的其他命令依旧会执行。(不具有原子性)

 

2、MySQL事务与Redis事务区别

原文:https://zhuanlan.zhihu.com/p/264354517

1.事务命令

MySQL:

begin/start transaction;:显式的开启一个事

commit:提交事务,将对数据库进行的所有的修改变成永久性

rollback:结束用户的事务,并撤销现在正在进行的未提交的修改

 

Redis:
multi:标记事务的开始

exec:执行事务的commands队列

discard:结束事务,并清除commands队列

 

2.默认状态

mysql:

mysql会默认开启一个事务,且缺省设置是自动提交,即每成功执行sql,一个事务就会马上commit,所以默认不能rollback。

 

redis:

redis默认不会开启事务,即command会立即执行,而不会排队,并不支持rollback。

 

3.使用方式

mysql(包含两种方式):

用begin、rollback、commit显式开启并控制一个 新的 transaction

执行命令 set autocommit=0,用来禁止当前会话自动commit,控制 默认开启的事务

 

redis:

用multi、exec、discard,显式开启并控制一个transaction。(注意:这里没有强调 “新的” ,因为默认是不会开启事务的)。

 

4.实现原理

mysql:

mysql实现事务,是基于undo/redo日志

undo记录修改前状态,rollback基于undo日志实现

redo记录修改后的状态,commit基于redo日志实现

既然是基于redo日志实现记录修改后的状态,那么大家应该也知道,redo日志是innodb专有的,所以innodb会支持事务。

在mysql中无论是否开启事务,sql都会被立即执行并返回执行结果,只是事务开启后执行后的状态只是记录在redo日志,执行commit之后,数据才会被写入磁盘。

 

redis:

redis实现事务,是基于commands队列

如果没有开启事务,command将会被立即执行并返回执行结果,并且直接写入磁盘

如果事务开启,command不会被立即执行,而是排入队列,并返回排队状态(具体依赖于客户端)。

调用exec才会执行commands队列

 

boolean a = redisTemplate.opsForZSet().add("generalService",orderId,System.currentTimeMillis())boolean a = redisTemplate.opsForZSet().add("generalService",orderId,System.currentTimeMillis()

以上代码如果没有开启事务,操作被立即执行,a将会被立即赋值(true/false)

如果开启事务,操作不会被立即执行,将会返回null值,而a的类型是boolean,所以将会抛出异常:

java.lang.NullPointerException 空指针异常。

 

Redis事务不支持Rollback(重点)

事实上Redis命令在事务执行时可能会失败,但仍会继续执行剩余命令而不是Rollback(事务回滚)。如果你使用过关系数据库,这种情况可能会让你感到很奇怪。然而针对这种情况具备很好的解释:

Redis命令可能会执行失败,仅仅是由于错误的语法被调用(命令排队时检测不出来的错误),或者使用错误的数据类 型操作某个Key;

这意味着,实际上失败的命令都是编程错误造成的,都是开发中能够被检测出来的,生产环境中不应该存在。

由于不必支持Rollback,Redis内部简洁并且更加高效。

 

redis 事务中的错误

事务期间,可能会遇到两种命令错误:

1.语法错误
执行命令的语法不正确

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> setter name ming
(error) ERR unknown command 'setter'
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

此时事务中存在一个错误的语法(setter name ming),当执行exec后,会直接返回错误,正确的命令也不会执行。


2.执行错误
命令在运行过程中出现错误

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set hometown guangdong
QUEUED
127.0.0.1:6379> lpush hometown big beautiful
QUEUED
127.0.0.1:6379> get hometown
QUEUED
127.0.0.1:6379> set hometown beijing
QUEUED
127.0.0.1:6379> get hometown
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) "guangdong"
4) OK
5) "beijing"

此时事务中,语法本身是没有问题的,所以exec执行之前,是无法发现错误的,
但是在执行的时候,由于hometown设置为了string类型,然后又使用了list的语法,导致执行出现错误,
这种情况,只会是错误的命令不执行,而正确的命令仍然会执行。

 

3、原生redis命令事务示例

它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令

# 开启事务
redis 127.0.0.1:6379> MULTI

# 命令放入缓存队列
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"

# 命令放入缓存队列
redis 127.0.0.1:6379> GET book-name

# 命令放入缓存队列
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"

# 命令放入缓存队列
redis 127.0.0.1:6379> SMEMBERS tag

# 执行事务(一次性把上面所有命令执行了)
redis 127.0.0.1:6379> EXEC

 

4、在python中执行redis事务

import redis

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

# 初始化事务对象,开启事务
pipe = conn.pipeline()

try:
    # 事务要批量执行的命令
    pipe.set('name1', '小明')
    pipe.set('name2', '小红', '老婆')  # 这里会报错的
    pipe.set('name3', '小花')

    # 提交事务,执行操作
    pipe.execute()
except Exception as e:
    print(e)

print(conn.get('name1'))  # 小明
print(conn.get('name2'))  # None
print(conn.get('name3'))  # 小花

 

八、发布订阅

1、介绍

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

 

 

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

 

 

2、在python中使用发布订阅

1. 发布者
import redis


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

# 发布者发布消息就可以了
conn.publish('我的对象', '跑了')


2.订阅者
import redis


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

# 第一步 生成一个订阅者对象
pubsub = conn.pubsub()

# 第二步 订阅一个消息,实际上就是监听这个键
pubsub.subscribe('我的对象')

# 第三步 死循环一直等待监听结果
while 1:
    print('watching...')
    msg = pubsub.parse_response()
    print(msg)

 

posted @ 2018-12-20 19:32  我用python写Bug  阅读(334)  评论(0编辑  收藏  举报