Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis详细功能用法介绍,可以看redis中文网:http://www.redis.cn/
可以看到,介绍的很详细。
常用命令【很全】:http://redisdoc.com/index.html
这里包括了所有的redis命令。
Redis内存淘汰指的是用户存储的一些键被可以被Redis主动地从实例中删除,从而产生读miss的情况。
Redis提供了下面几种淘汰策略供用户选择,其中默认的策略为noeviction策略:
我们了解了Redis大概提供了这么几种淘汰策略,那么如何选择呢?淘汰策略的选择可以通过下面的配置指定:
几种策略的适用场景:
另外,volatile-lru策略和volatile-random策略适合我们将一个Redis实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个Redis实例来达到相同的效果,值得一提的是将key设置过期时间实际上会消耗更多的内存,因此我们建议使用allkeys-lru策略从而更有效率的使用内存。
当内存不足时,redis会启用内存释放,一般采用lru最近最久未使用。redis中可以使用expire命令设置一个键的生存时间,到时间后redis会自动删除它。
过期时间可以设置为秒或者毫秒精度。
过期时间分辨率总是 1 毫秒。
过期信息被复制和持久化到磁盘,当 Redis 停止时时间仍然在计算 (也就是说 Redis 保存了过期时间)。
expire 设置生存时间(单位/秒)
expire key seconds(秒)
ttl 查看键的剩余生存时间
ttl key
persist 取消生存时间
persist key
expireat指定时刻过期
expireat key unix时间戳
expireat key 1551858600
一组redis命令要么都执行,要么都不执行。
原理:先将属于一个事务的命令发送给redis进行缓存,最后再让redis依次执行这些命令。
应用场景:
一组命令必须同时都执行,或者都不执行。
我们想要保证一组命令在执行的过程之中不被其它命令插入。
命令:
multi //事务开始
.....
exec //事务结束,开始执行事务中的命令
discard //放弃事务
错误处理
Redis 不支持回滚(roll back)
正因为redis不支持回滚功能,才使得redis在事务上可以保持简洁和快速。
redis持久化指把数据持久化到磁盘,便于故障恢复,redis支持两种方式的持久化,可以单独使用或者结合起来使用。
rdb方式的持久化是通过快照完成的,当符合一定条件时redis会自动将内存中的所有数据执行快照操作并存储到硬盘上。默认存储在dump.rdb文件中。(文件名在配置文件中dbfilename)
redis进行快照的时机(在配置文件redis.conf中)
save 900 1 //表示900秒内至少一个键被更改则进行快照。
save 300 10 //表示300秒内10条被更改则快照
save 60 10000 //60秒内10000条
Redis自动实现快照的过程
优点:由于存储的有数据快照文件,恢复数据很方便。
缺点:会丢失最后一次快照以后更改的所有数据。
把写操作指令,持续的写到一个类似日志文件里。
由于写操作指令保存在日志文件里,异常恢复时把文件里面所有指令执行一遍即可。如果你执行了flushall命令,清空了redis,而你采用的aof持久化方式,那么,就可以找到这个文件,将最后一行flushall删掉,执行恢复命令,将命令全部执行一遍,这样数据就恢复了。
AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)。
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。
一般来说,如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快, 除此之外, 使用 RDB 还可以避免之前提到的 AOF 程序的 bug 。因为以上提到的种种原因, 未来我们可能会将 AOF 和 RDB 整合成单个持久化模型。 (这是一个长期计划。)
当然了,也可以不开启持久化,这样redis崩溃后就不能恢复了!有人说redis好好的,怎么会崩溃呢,呵呵,反正我见到96G内存的redis单机版都崩溃过的!
篇幅不便太长,两种方式详细持久化方式可以看:http://note.youdao.com/noteshare?id=dab5739e67d2ba22ab9e79f7a1828175&sub=E1AD8DE0FD1C4205BBFA729FB4BBC29A
Redis 的 set 用于保存唯一的数据集合,通过它可以快速判断某一个元素是否存在于集合中,也可以快速计算某一个集合的元素个数,另外和可以合并集合到一个新的集合中。涉及的命令如下:
复制代码 代码如下:
SISMEMBER key member # 判断 member 是否存在
SADD key member # 往集合中加入 member
SCARD key # 获取集合元素个数
基于 set 的方法简单有效,计数精确,适用面广,易于理解,它的缺点是消耗资源比较大(当然比起关系数据库是少很多的),如果元素个数很大(比如上亿的计数),消耗内存很恐怖。
Redis 的 bit 可以用于实现比 set 内存高度压缩的计数,它通过一个 bit 1 或 0 来存储某个元素是否存在信息。例如网站唯一访客计数,可以把 user_id 作为 bit 的偏移量 offset,设置为 1 表示有访问,使用 1 MB的空间就可以存放 800 多万用户的一天访问计数情况。涉及的命令如下: 复制代码 代码如下:
SETBIT key offset value # 设置位信息
GETBIT key offset # 获取位信息
BITCOUNT key [start end] # 计数
BITOP operation destkey key [key ...] # 位图合并
基于 bit 的方法比起 set 空间消耗小得多,但是它要求元素能否简单映射为位偏移,适用面窄了不少,另外它消耗的空间取决于最大偏移量,和计数值无关,如果最大偏移量很大,消耗内存也相当可观。
实现超大数据量精确的唯一计数都是比较困难的,但是如果只是近似的话,计算科学里有很多高效的算法,其中 HyperLogLog Counting 就是其中非常著名的算法,它可以仅仅使用 12 k左右的内存,实现上亿的唯一计数,而且误差控制在百分之一左右。涉及的命令如下: 复制代码 代码如下:
PFADD key element [element ...] # 加入元素
PFCOUNT key [key ...] # 计数
这种计数方法真的很神奇,我也没有彻底弄明白,有兴趣可以深入研究相关文章。
redis 提供的这三种唯一计数方式各有优劣,可以充分满足不同情况下的计数要求。
Redis-Sentinel是官方推荐的高可用解决方案,当redis在做master-slave的高可用方案时,假如master宕机了,redis本身(以及其很多客户端)都没有实现自动进行主备切换,而redis-sentinel本身也是独立运行的进程,可以部署在其他与redis集群可通讯的机器中监控redis集群。
很显然,单个哨兵会存在自己挂掉而无法监控整个集群的问题,所以哨兵也是支持集群的,我们通常用三台哨兵机器来监控一组redis集群。
Redis是一种NoSQL的文档数据库,通过key-value的结构存储在内存中,Redis读的速度是110000次/s,写的速度是81000次/s,性能很高,使用范围也很广。Redis是一个key-value存储系统。和Memcached类似,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++(hiredis),C#,PHP,JavaScript,Perl,Object-C,Python,Ruby等客户端,使用很方便。
在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库[1] (slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
说明从服务器只能读数据,而不能写入数据。
数据在【从服务器】里【读】,在【主服务器】里【写】。
这样就实现了redis数据库的读写分离功能。
即使使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用集群,就是分布式存储。即每台redis存储不同的内容, Redis集群提供一种方式自动将数据分布在多个Redis节点上。
架构细节:
a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成进群的slot映射[0-16383]不完成时进入fail状态.
b:如果进群超过半数以上master挂掉,无论是否有slave集群进入fail状态.
每个Redis集群中的节点都需要打开两个TCP连接。一个连接用于正常的给Client提供服务,比如6379,还有一个额外的端口(通过在这个端口号上加10000)作为数据端口,比如16379。第二个端口(本例中就是16379)用于集群总线,这是一个用二进制协议的点对点通信信道。这个集群总线(Cluster bus)用于节点的失败侦测、配置更新、故障转移授权,等等。客户端从来都不应该尝试和这些集群总线端口通信,它们只应该和正常的Redis命令端口进行通信。注意,确保在你的防火墙中开放着两个端口,否则,Redis集群节点之间将无法通信。
命令端口和集群总线端口的偏移量总是10000。
注意,如果想要集群按照你想的那样工作,那么集群中的每个节点应该:
正常的客户端通信端口(通常是6379)用于和所有可到达集群的所有客户端通信 集群总线端口(the client port + 10000)必须对所有的其它节点是可到达的 也就是,要想集群正常工作,集群中的每个节点需要做到以下两点:
正常的客户端通信端口(通常是6379)必须对所有的客户端都开放,换言之,所有的客户端都可以访问 集群总线端口(客户端通信端口 + 10000)必须对集群中的其它节点开放,换言之,其它任意节点都可以访问 如果你没有开放TCP端口,你的集群可能不会像你期望的那样工作。集群总线用一个不同的二进制协议通信,用于节点之间的数据交换
Redis集群不同一致性哈希,它用一种不同的分片形式,在这种形式中,每个key都是一个概念性(hash slot)的一部分。
Redis集群中有16384个hash slots,为了计算给定的key应该在哪个hash slot上,我们简单地用这个key的CRC16值来对16384取模。(即:key的CRC16 % 16384)
Redis集群中的每个节点负责一部分hash slots,假设你的集群有3个节点,那么:
Node A contains hash slots from 0 to 5500 Node B contains hash slots from 5501 to 11000 Node C contains hash slots from 11001 to 16383 允许添加和删除集群节点。比如,如果你想增加一个新的节点D,那么久需要从A、B、C节点上删除一些hash slot给到D。同样地,如果你想从集群中删除节点A,那么会将A上面的hash slots移动到B和C,当节点A上是空的时候就可以将其从集群中完全删除。
因为将hash slots从一个节点移动到另一个节点并不需要停止其它的操作,添加、删除节点以及更改节点所维护的hash slots的百分比都不需要任何停机时间。也就是说,移动hash slots是并行的,移动hash slots不会影响其它操作。
Redis支持多个key操作,只要这些key在一个单个命令中执行(或者一个事务,或者Lua脚本执行),那么它们就属于相同的hash slot。你也可以用hash tags俩强制多个key都在相同的hash slot中。
当部分master节点失败了,或者不能够和大多数节点通信的时候,为了保持可用,Redis集群用一个master-slave模式,这样的话每个hash slot就有1到N个副本。
在我们的例子中,集群有A、B、C三个节点,如果节点B失败了,那么5501-11000之间的hash slot将无法提供服务。然而,当我们给每个master节点添加一个slave节点以后,我们的集群最终会变成由A、B、C三个master节点和A1、B1、C1三个slave节点组成,这个时候如果B失败了,系统仍然可用。节点B1是B的副本,如果B失败了,集群会将B1提升为新的master,从而继续提供服务。然而,如果B和B1同时失败了,那么整个集群将不可用。
Redis集群不能保证强一致性。换句话说,Redis集群可能会丢失一些写操作。The first reason why Redis Cluster can lose writes is because it uses asynchronous replication.
Redis集群可能丢失写的第一个原因是因为它用异步复制。
客户端写到master B master B回复客户端OK master B将这个写操作广播给它的slaves B1、B2、B3 正如你看到的那样,B没有等到B1、B2、B3确认就回复客户端了,也就是说,B在回复客户端之前没有等待B1、B2、B3的确认,这对应Redis来说是一个潜在的风险。所以,如果客户端写了一些东西,B也确认了这个写操作,但是在它将这个写操作发给它的slaves之前它宕机了,随后其中一个slave(没有收到这个写命令)可能被提升为新的master,于是这个写操作就永远丢失了。
这和大多数配置为每秒刷新一次数据到磁盘的情况是一样的。你可以通过强制数据库在回复客户端以前刷新数据,但是这样做的结果会导致性能很低,这就相当于同步复制了。
基本上,需要在性能和一致性之间做一个权衡。
如果绝对需要的话,Redis集群也是支持同步写的,这是通过WAIT命令实现的,这使得丢失写的可能性大大降低。然而,需要注意的是,Redis集群没有实现强一致性,即使用同步复制,因为总是有更复杂的失败场景使得一个没有接受到这个写操作的slave当选为新的master。
另一个值得注意的场景,即Redis集群将会丢失写操作,这发生在一个网络分区中,在这个分区中,客户端与少数实例(包括至少一个主机)隔离。
假设这样一个例子,有一个集群有6个节点,分别由A、B、C、A1、B1、C1组成,三个masters三个slaves,有一个客户端我们叫Z1。在分区发生以后,可能分区的一边是A、C、A1、B1、C1,另一边有B和Z1。此时,Z1仍然可用写数据到B,如果网络分区的时间很短,那么集群可能继续正常工作,而如果分区的时间足够长以至于B1在多的那一边被提升为master,那么这个时候Z1写到B上的数据就会丢失。
什么意思呢?简单的来说就是,本来三主三从在一个网络分区中,突然网络分区发生,于是一边是A、C、A1、B1、C1,另一边是B和Z1,这时候Z1往B中写数据,于此同时另一边(即A、C、A1、B1、C1)认为B已经挂了,于是将B1提升为master,当分区回复的时候,由于B1变成了master,所以B就成了slave,于是B就要丢弃它自己原有的数据而从B1那里同步数据,于是乎先去Z1写到B的数据就丢失了。
注意,有一个最大窗口,这是Z1能够向B写的最大数量:如果时间足够的话,分区的多数的那一边已经选举完成,选择一个slave成为master,此时,所有在少数的那一边的master节点将停止接受写。
也就说说,有一个最大窗口的设置项,它决定了Z1在那种情况下能够向B发送多数写操作:如果分隔的时间足够长,多数的那边已经选举slave成为新的master,此后少数那边的所有master节点将不再接受写操作。
在Redis集群中,这个时间数量是一个非常重要的配置指令,它被称为node timeout。在超过node timeout以后,一个master节点被认为已经失败了,并且选择它的一个副本接替master。类似地,如果在过了node timeout时间以后,没有一个master能够和其它大多数的master通信,那么整个集群都将停止接受写操作。
Codis 是豌豆荚公司开发的一个分布式 Redis 解决方案,用Go语言开发的。对于上层的应用来说,连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别 (不支持的命令列表),Codis 底层会处理请求的转发,不停机的数据迁移等工作。所有后边的一切事情,对于前面的客户端来说是透明的,可以简单的认为后边连接的是一个内存无限大的 Redis 服务。
Codis Proxy (codis-proxy)
,处理客户端请求,支持Redis协议,因此客户端访问Codis Proxy跟访问原生Redis没有什么区别;Codis Dashboard (codis-config)
,Codis 的管理工具,支持添加/删除 Redis 节点、添加/删除 Proxy 节点,发起数据迁移等操作。codis-config 本身还自带了一个 http server,会启动一个 dashboard,用户可以直接在浏览器上观察 Codis 集群的运行状态;Codis Redis (codis-server)
,Codis 项目维护的一个 Redis 分支,基于 2.8.21 开发,加入了 slot 的支持和原子的数据迁移指令;ZooKeeper/Etcd
,Codis 依赖 ZooKeeper 来存放数据路由表和 codis-proxy 节点的元信息,codis-config 发起的命令都会通过ZooKeeper 同步到各个存活的 codis-proxy;codis和twemproxy最大的区别有两个:
codis支持动态水平扩展,对client完全透明不影响服务的情况下可以完成增减redis实例的操作;
codis是用go语言写的并支持多线程,twemproxy用C并只用单线程。 后者又意味着:codis在多核机器上的性能会好于twemproxy;codis的最坏响应时间可能会因为GC的STW而变大,不过go1.5发布后会显著降低STW的时间;如果只用一个CPU的话go语言的性能不如C,因此在一些短连接而非长连接的场景中,整个系统的瓶颈可能变成accept新tcp连接的速度,这时codis的性能可能会差于twemproxy。
redis cluster基于smart
client和无中心的设计,client必须按key的哈希将请求直接发送到对应的节点。这意味着:使用官方cluster必须要等对应语言的redis driver对cluster支持的开发和不断成熟;client不能直接像单机一样使用pipeline来提高效率,想同时执行多个请求来提速必须在client端自行实现异步逻辑。 而codis因其有中心节点、基于proxy的设计,对client来说可以像对单机redis一样去操作proxy(除了一些命令不支持),还可以继续使用pipeline并且如果后台redis有多个的话速度会显著快于单redis的pipeline。同时codis使用zookeeper来作为辅助,这意味着单纯对于redis集群来说需要额外的机器搭zk,不过对于很多已经在其他服务上用了zk的公司来说这不是问题:)
因为codis的proxy是无状态的,可以比较容易的搭多个proxy来实现高可用性并横向扩容。
对Java用户来说,可以使用经过我们修改过的Jedis,Jodis ,来实现proxy层的HA。它会通过监控zk上的注册信息来实时获得当前可用的proxy列表,既可以保证高可用性,也可以通过轮流请求所有的proxy实现负载均衡。如果需要异步请求,可以使用我们基于Netty开发的Nedis。
对下层的redis实例来说,当一个group的master挂掉的时候,应该让管理员清楚,并手动的操作,因为这涉及到了数据一致性等问题(redis的主从同步是最终一致性的)。因此codis不会自动的将某个slave升级成master。 不过我们也提供一种解决方案:codis-ha。这是一个通过codis开放的api实现自动切换主从的工具。该工具会在检测到master挂掉的时候将其下线并选择其中一个slave提升为master继续提供服务。
需要注意,codis将其中一个slave升级为master时,该组内其他slave实例是不会自动改变状态的,这些slave仍将试图从旧的master上同步数据,因而会导致组内新的master和其他slave之间的数据不一致。因为redis的slave of命令切换master时会丢弃slave上的全部数据,从新master完整同步,
因为 codis-proxy
是无状态的,可以比较容易的搭多个实例,达到高可用性和横向扩展。
对 Java 用户来说,可以使用基于 Jedis 的实现 Jodis ,来实现 proxy 层的 HA:
它会通过监控 zookeeper 上的注册信息来实时获得当前可用的 proxy 列表,既可以保证高可用性;
也可以通过轮流请求所有的proxy实现负载均衡。
如果需要异步请求,可以使用我们基于Netty开发的 Nedis。
<dependency>
<groupId>com.wandoulabs.jodisgroupId>
<artifactId>jodisartifactId>
<version>0.2.2version>
dependency>
注意:不是下面这个依赖,被坑死了
<dependency>
<groupId>io.codis.jodisgroupId>
<artifactId>jodisartifactId>
<version>0.5.1version>
dependency>
java 代码:
public static void main(String[] args) {
// codis 连接池配置
JedisPoolConfig jps = new JedisPoolConfig();
jps.setMaxIdle(5);
jps.setMinIdle(10);
jps.setMaxTotal(16);
JedisResourcePool jedisPool = RoundRobinJedisPool
.create()
.poolConfig(jps)
.curatorClient("0.0.0.0:2181,1.1.1.1:2181", 30000)
.zkProxyDir("/jodis/codis-bigdata")
.password("123456")
.build();
try (Jedis jedis = jedisPool.getResource()) {
String values = jedis.get("{37305908-8007-4535-B243-F3AC1125C45E}_wf");
System.out.println(values);
}
}
对下层的 redis 实例来说,当一个 group 的 master 挂掉的时候,应该让管理员清楚,并手动的操作,因为这涉及到了数据一致性等问题(redis的主从同步是最终一致性的)。因此 codis 不会自动的将某个 slave 升级成 master。关于外部 codis-ha 工具(具体可以参考之前的章节),这是一个通过 codis-dashboard 开放的 RESTful API 实现自动切换主从的工具。该工具会在检测到 master 挂掉的时候主动应用主从切换策略,提升单个 slave 成为新的 master。
需要注意,codis 将其中一个 slave 升级为 master 时,该组内其他 slave 实例是不会自动改变状态的,这些 slave 仍将试图从旧的 master 上同步数据,因而会导致组内新的 master 和其他 slave 之间的数据不一致。因此当出现主从切换时,需要管理员手动创建新的 sync action 来完成新 master 与 slave 之间的数据同步(codis-ha 不提供自动操作的工具,因为这样太不安全
热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存,毕竟强大到冒泡的QPS和极强的稳定性不是所有类似工具都有的,而且相比于memcached还提供了丰富的数据类型可以使用,另外,内存中的数据也提供了AOF和RDB等持久化机制可以选择,要冷、热的还是忽冷忽热的都可选。
诸如统计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且100%毫秒级性能!爽。
命令:INCRBY
当然爽完了,别忘记持久化,毕竟是redis只是存了内存!
相当于消息系统,ActiveMQ,RocketMQ等工具类似,但是个人觉得简单用一下还行,如果对于数据一致性要求高的话还是用RocketMQ等专业系统。
由于redis把数据添加到队列是返回添加元素在队列的第几位,所以可以做判断用户是第几个访问这种业务
队列不仅可以把并发请求变成串行,并且还可以做队列或者栈使用
用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。
想想一下腾讯10亿用户,要几个毫秒内查询到某个用户是否在线,你能怎么做?千万别说给每个用户建立一个key,然后挨个记(你可以算一下需要的内存会很恐怖,而且这种类似的需求很多,腾讯光这个得多花多少钱。。)好吧。这里要用到位操作——使用setbit、getbit、bitcount
命令。
原理是:
redis内构建一个足够长的数组,每个数组元素只能是0和1两个值,然后这个数组的下标index用来表示我们上面例子里面的用户id(必须是数字哈),那么很显然,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统,上面我说的几个场景也就能够实现。用到的命令是:setbit、getbit、bitcount
验证前端的重复请求(可以自由扩展类似情况),可以通过redis进行过滤:每次请求将request Ip、参数、接口等hash作为key存储redis(幂等性请求),设置多长时间有效期,然后下次请求过来的时候先在redis中检索有没有这个key,进而验证是不是一定时间内过来的重复提交
秒杀系统,基于redis是单线程特征,防止出现数据库“爆破”
全局增量ID生成,类似“秒杀”
例如新闻列表页面最新的新闻列表,如果总数量很大的情况下,尽量不要使用select a from A limit 10这种low货,尝试redis的 LPUSH命令构建List,一个个顺序都塞进去就可以啦。不过万一内存清掉了咋办?也简单,查询不到存储key的话,用mysql查询并且初始化一个List到redis中就好了。
谁得分高谁排名往上。命令:ZADD
(有续集,sorted set
)