1.redis持久化机制
1)实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,
再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。
2)RDB(默认)(体积小,速度快,丢数据):按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,
通过配置文件中的save参数来定义快照的周期。(快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。)
3)AOF(体积大,速度慢,数据完整):Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。
当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
4)混合持久化(4.0以后版本支持):同时结合RDB持久化以及AOF持久化混合写入AOF文件。
4)当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
2.缓存问题
1)缓存雪崩:缓存雪崩是指原本应该访问缓存的都直接访问底层数据库了,例如大量缓存数据在同一时间过期,导致大量请求直接访问底层数据库,导致底层数据库崩溃。
解决方案:
(1)缓存数据过期时间分散(过期时间加随机数)
(2)底层数据库访问加锁或者将读写请求放入消费队列
2)缓存穿透:用户查询的数据,数据库中没有,那缓存中也不会有,用户就会绕过缓存直接查询底层数据库,如果用户大量进行这种操作,比如黑客攻击,就会造成底层数据库崩溃,这种现象成为缓存穿透。
解决方案:
(1)对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
(2)对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
(3)布隆过滤器:布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。redis的布隆过滤器插件可以很好的解决这个问题,详情点击我
(4)接口层校验,不合法请求直接拒绝
3)缓存击穿:缓存击穿和和缓存雪崩相似,例如某个key的访问量非常大,突然过期了,导致大量的访问直接打到底层数据库,造成底层数据库崩溃,这种现象成为缓存击穿。
解决方案:
(1)热点数据永不过期
(2)定时脚本,定时更新热点数据
4)缓存预热:为了避免系统上线后大量直接访问底层数据库操作,事先将某些数据放入缓存,以缓解底层数据库压力
3.redis数据类型和应用场景
1)string:任何数据都可以用string缓存
2)hash:json对象
3)list:消息队列
4)set:相当于python中的set,内部元素唯一,增删改查复杂度都是O(1)操作,支持交并补操作。获取购买相同记录,唯一性时使用。
5)sorted set:有序集合,在set的基础上增加了value的权重,利用跳跃表实现(64层),排行榜使用。
4.redis数据淘汰策略
1)volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
2)volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
3)volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
4)allkeys-lru:淘汰最近最少使用的数据
5)allkeys-random:从数据集中任意选择数据淘汰
6)no-eviction:禁止驱逐数据
7)volatile-lfu:从已设置过期时间的数据集中挑选最不经常使用的数据淘汰
8)allkeys-lfu:移除最不经常使用的数据
5.redis底层数据类型实现
redis有五大数据类型,字符串对象(string)、列表对象(list)、哈希对象(hash)、集合(set)对象和有序集合对象(zset),
redis构建了一个对象系统,每个数据类型都是一个对象,由于redis是key,value的形式存储的,所以每新建一个数据,系统就会新建两个对象,一个是key对象,一个是value对象,
具体查看博客:<https://blog.csdn.net/xiaohei_xiaobai/article/details/106061333>
6.redis-HyperLogLog基数统计
redis在2.8.9 版本添加了 HyperLogLog 数据结构,用于统计非重复数据个数,这个功能利用redis的set也能实现,但是为什么还要提供HyperLogLog呢?
原因在于如果有时我们只需要计算基数个数,不要存储具体的数据时,数据量很大,如果利用set会浪费很大的内存消耗,但是HyperLogLog的优点是,
他所需要的内存空间不会随着数据量的增大而增大,当统计元素很少时,HyperLogLog占用的内存空间也很小,当统计的数据很大时,HyperLogLog最大只会占用12k的内存空间。
7.redis分布式锁方案
1)INCR:INCR命令会将key的值加一,如果key值不存在,则key值会被初始化为0,然后执行INCR操作。
import redis
import time
rdb_host = '127.0.0.1'
rdb_port = 6379
rdb_pwd = 'root'
rdb_db = 13
db = redis.ConnectionPool(
host=rdb_host,
port=rdb_port,
password=rdb_pwd,
db=rdb_db)
rdb = redis.Redis(connection_pool=db)
def redis_lock(order_id):
lock_key = 'LOCK_' + str(order_id)
incr = rdb.incr(lock_key)
if incr == 1:
rdb.expire(lock_key, 10)
print 'lock success'
rdb.delete(lock_key)
else:
print 'current key has been occupied'
time.sleep(1)
redis_lock(order_id)
if __name__ == '__main__':
redis_lock(1234)
2)SETNX(SET is Not eXists):当key不存在时可以为key设置值,返回1,否则返回0。
import redis
import time
rdb_host = '127.0.0.1'
rdb_port = 6379
rdb_pwd = 'root'
rdb_db = 13
db = redis.ConnectionPool(
host=rdb_host,
port=rdb_port,
password=rdb_pwd,
db=rdb_db)
rdb = redis.Redis(connection_pool=db)
def redis_lock_setnx(order_id):
lock_key = 'LOCK_' + str(order_id)
setnx = rdb.setnx(lock_key, 'lock')
if setnx == 1:
rdb.expire(lock_key, 10)
print 'lock success'
rdb.delete(lock_key)
else:
print 'current key has been occupied'
time.sleep(1)
redis_lock_setnx(order_id)
if __name__ == '__main__':
redis_lock_setnx(2345)
3)SET:Redis从2.6.12版本开始, SET命令的行为可以通过一系列参数来修改
(1)EX:设置键的过期时间为 second 秒。
(2)PX:设置键的过期时间为 millisecond 毫秒。
(3)NX:只在键不存在时,才对键进行设置操作。
(4)XX:只在键已经存在时,才对键进行设置操作。
import redis
import time
rdb_host = '127.0.0.1'
rdb_port = 6379
rdb_pwd = 'root'
rdb_db = 13
db = redis.ConnectionPool(
host=rdb_host,
port=rdb_port,
password=rdb_pwd,
db=rdb_db)
rdb = redis.Redis(connection_pool=db)
def redis_lock_set(order_id):
lock_key = 'LOCK_' + str(order_id)
set = rdb.set(lock_key, 'lock', ex=10, nx=True)
if set:
print 'lock success'
rdb.delete(lock_key)
else:
print 'current key has been occupied'
time.sleep(1)
redis_lock_set(order_id)
if __name__ == '__main__':
redis_lock_set(3456)
4)以上方式存在的问题
以上方式都设置了过期时间,原因在于如果程序由于某些bug以外退出了,不加过期时间的话,这个key会一直被锁定,无法更新。
但是加过期时间来处理就会有问题,如果客户端1在设置的过期时间内程序正常执行,但是没有处理完,这时过期时间失效了,然后这个key的锁被客户端2获取,
在客户端2还没处理完的时候客户端1处理完了,然后删除了客户端2的锁。
针对这个问题可以在给锁赋值的时候增加随机字符,然后删除的时候判断下是否为自己赋的值就可以了。具体实现如下:
import redis
import time
rdb_host = '127.0.0.1'
rdb_port = 6379
rdb_pwd = 'root'
rdb_db = 13
db = redis.ConnectionPool(
host=rdb_host,
port=rdb_port,
password=rdb_pwd,
db=rdb_db)
rdb = redis.Redis(connection_pool=db)
def redis_lock_set_random(order_id):
lock_key = 'LOCK_' + str(order_id)
lock_value = 'lock' + str(int(time.time()))
set = rdb.set(lock_key, lock_value, ex=10, nx=True)
if set:
print 'lock success'
if rdb.get(lock_key) == lock_value:
rdb.delete(lock_key)
else:
print 'current key has been occupied'
time.sleep(1)
redis_lock_set_random(order_id)
if __name__ == '__main__':
redis_lock_set_random(4567)
8.Memcache与Redis的区别
1)存储方式Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis有部份存在硬盘上,redis可以持久化其数据。
2)数据支持类型memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型,提供list,set,zset,hash等数据结构的存储。
3)value值大小不同:Redis最大可以达到512M;memcache只有1M。
4)redis的速度比memcached快很多。
5)Redis支持数据的备份,即master-slave模式的数据备份。
9.单线程的redis为什么这么快
1)纯内存操作
2)单线程操作,避免了频繁的上下文切换
3)采用了非阻塞I/O多路复用机制
10.高并发知识点
1)阻塞I/O:进程发起read后,如果kernel未准备好数据,进程就会被block,进入等待阶段;等待kernel将数据准备好,才会返回数据,解除阻塞;
2)非阻塞I/O:进程发起read请求,kernel未准备好数据,直接返回一个error,进程无需阻塞等待;
进程会轮询发送read请求,如此往复;一直到kernel准备好数据,才把数据拷贝并返回给进程,结束轮询;
3)I/O多路复用:进程调用select/poll/epoll,select/poll/epoll会将进程block起来;kernel会‘监视’所有select负责的socket;
当任何一个socket准备好数据,select就会返回(也就是图中的return medable);进程调用read(图中第二次system call),将数据从kernel拷贝到用户进程;
I/O多路复用主要的三种实现方式:
(1)select:不是线程安全;只返回数据,不会告知是哪个sock返回的,只能自己遍历去查找;只能监视1024个链接。
(2)poll:相对于select改进了:可监视无数个链接,缺点:仍旧不是线程安全。
(3)epoll:改进:线程安全,只有linux支持。
11.Redis事务
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
1)MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
3)通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
12.redis协议规范(RESP)
1)简单字符串 Simple Strings, 以 "+"加号 开头
eg: "+OK\r\n"
2)错误 Errors, 以"-"减号 开头
eg: "-Error unknow command 'foobar'\r\n"
3)整数型 Integer, 以 ":" 冒号开头
eg: ":1000\r\n"
4)大字符串类型 Bulk Strings, 以 "$"美元符号开头,长度限制512M
eg: "$6\r\nfoobar\r\n" 其中字符串为 foobar,而6就是foobar的字符长度
5)数组类型 Arrays,以 "*"星号开头
eg: "*2\r\n$2\r\nfoo\r\n$3\r\nbar\r\n" 数组包含2个元素,分别是字符串foo和bar