redis基础篇
1.redis常见的数据结构
redis是一种以键值对存储的高性能内存数据库,有五种常用的数据类型,string,list,hash,set,zset。
2.redis的过期时间
redis中的key可以设置过期时间,方式有两种,第一种是通过expire命令来设置,expire key seconds。第二种方式是使用setex(string key,int seconds,string value)。
3.过期键的删除策略
在redis中,对于过期的键,删除策略有两种,第一种是被动删除,就是在访问设置了过期时间的key时,首先判断该key是否过期,如果该key已经过期,则删除。但是这种方式存在的问题就是没法删除长时间没访问到的key。第二种是主动删除,redis从设置了过期时间的键中选取一部分,将过期的键删除。
4.redis的持久化
在redis中支持两种持久化的方式,一种是RDB,另一种是AOF(append-only-file)。其中RDB的方式是根据指定的规则将数据库中的数据存储下来,而AOF的方式是定时的将数据库的操作命令记录下来。
RDB方式:当满足一定的条件时,redis会fork一个子进程来进行持久化,会先将数据存储在一个临时文件中,等到持久化过程结束后,替换原先的持久化文件。该方式的优点是持久化的线程是fork出来的,不会对服务的性能产生影响。但是缺点是最后一次持久化的数据可能会丢失。RDB会在下面几种条件下对数据进行快照,第一是根据配置规则进行自动快照,第二是执行save或bgsave命令,第三是执行flushall命令,第四是执行复制(replication)命令。
AOF方式:当使用redis存储非临时文件时,为了防止数据的损失,采用AOF的方式是必要的。这种方式是将redis的每一条写命令追加到硬盘文件中,这种方式对redis的性能会有影响,但是这种性能损耗在一定条件下是可以接受的,另外使用较快的硬盘可以提高redis的性能。
5.redis的内存回收策略
随着redis中存储的数据增多,当内存不足时,就需要淘汰一部分的数据,具体的淘汰规则有下面几种。
第一种:noeviction,默认的策略,当内存不足时,所有申请内存的相关操作都会报错。
第二种:allkeys-lru,从数据集(server.db[i].dict)中挑选最近最少使用的数据进行淘汰。使用场景:如果我们对缓存的访问都是热点数据,可以使用这个策略。
第三种:allkeys-random,随机移除某个key。使用场景:如果我们的应用对于缓存key的访问频率相等,可以使用这个策略。
第四种:volatile-random,从已设置过期时间的数据集(server.db[i].expires)中选取任意的数据进行淘汰。
第五种:volatile-lru,从已设置过期时间的数据集(server.db[i].expires)中选取最近最少使用的数据进行淘汰。
第六种:volatile-ttl,从已设置过期时间的数据集(server.db[i].expires)中选取将要过期的数据进行淘汰。
总结:实际上Redis实现的LRU并不是可靠的LRU,也就是名义上我们使用LRU算法淘汰内存数据,但是实际上被淘汰的键并不一定是真正的最少使用的数据,这里涉及到一个权衡的问题,如果需要在所有的数据中搜索符合条件的数据,那么一定会增加系统的开销,Redis是单线程的,所以对于耗时的操作会谨慎一些。为了在一定成本内实现相对的 LRU,早期的Redis版本是基于采样的LRU,也就是放弃了从所有数据中搜索解改为采样空间搜索优解。Redis3.0 版本之后,Redis作者对于基于采样的LRU进行了一些优化,目的是在一定的成本内让结果更靠近真实的LRU。
6.redis单线程效率高的原因
redis是单线程的,通过单线程来处理所有来自客户端的请求。redis把所有的任务封装在了一个线程之中,从而避免了线程安全问题。至于为什么要使用单线程,官网认为redis的主要瓶颈在于内存和带宽,而不是cpu。
redis是跑在单线程中的,所有的操作都是按照顺序线性执行的。但是由于读写操作等待用户的输入输出都是阻塞的,所以IO操作在一般情况下不能够直接返回,这会导致某一文件的IO阻塞,从而使整个进程无法对外提供服务,而IO的多路复用就是为了解决这个问题而出现的。
几种简单的IO模型,第一种是同步阻塞IO(Blocking IO),即传统IO模型。第二种是同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO需要设置socket为NONBLOCK。第三种是多路复用(IO Multiplexing),即经典的reactor设计模式,也称为异步阻塞IO,java中的selector和linux的epoll都是这种模型。第四种是异步IO(Asynchronous IO),即经典的Proactor设计模式,也称为异步非阻塞IO。
同步和异步指的是用户线程和内核的交互方式。阻塞和非阻塞指用户线程调用内核IO操作是阻塞还是非阻塞。
7.lua脚本在redis中的使用
7.1 1ua脚本产生的背景
在我们使用redis的时候,会面临一些问题,第一是原子性问题,redis虽然是单线程的,但是仍然会存在线程安全问题,不过这个问题不在于redis服务器的内部。redis作为数据服务器,是提供给多个客户端使用的,多个客户端相当于同一个进程内的多个线程,如果多个客户端之间没有做好数据同步的策略,就会产生数据的不一致问题。比如,多个客户端的命令没有做请求同步,导致实际的执行顺序可能会不一致,最终的结果也就没法满足原子性了。第二个是效率问题,由于redis是基于内存的数据库,所以它自身的吞吐量是非常高的。但是在实际的使用过程中有一个因素会影响吞吐量,那就是网络。当我们在使用redis做某些特定功能的时候,可能需要多个命令或者是多个数据类型的交互才能够完成,这种多次的网络请求对性能的影响是比较大的。redis为此也提供了优化的措施,比如pipeline管道操作,但是管道有一定的局限性,就是执行的多个命令和响应之间是不存在依赖关系的。在这种情况下,我们就需要一些机制能够编写一些具体的业务逻辑的命令,从而减少网络的请求。
7.2 lua脚本
redis服务器内部本身提供了对lua基本的支持,允许我们将开发好的lua脚本传到redis中执行。redis客户端可以使用lua脚本在服务器端原子性的执行多个命令。使用lua脚本的好处有,第一,减少了网路的开销,我们可以将多个命令放到一个lua脚本中去执行。第二,原子性操作,lua脚本中可以存放多个redis命令,执行过程不会被打断。第三,lua脚本是可复用的,也可以将脚本存储在redis服务端,较少网络传输的开销。
在redis中如何使用lua脚本。