Redis为什么这么快
答案:
- redis是纯内存操作:数据存放在内存中,内存的响应时间大约是100纳秒,这是Redis每秒万亿级别访问的重要基础。
- 非阻塞I/O:Redis采用epoll做为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间。
- 单线程避免了线程切换和竞态产生的消耗。
Redis采用单线程模型,每条命令执行如果占用大量时间,会造成其他线程阻塞,对于Redis这种高性能服务是致命的,所以Redis是面向高速执行的数据库
Redis的主要缺点是数据库容量受到物理内存的限制,不能作海量数据的高性能读写,因此Redis适合的场景主要局限在较小的数据量的高性能操作和运算。
Redis系列之 缓存穿透,缓存击穿,缓存雪崩
-
缓存穿透:
是指缓存和数据库中没有数据,而用户不断发起请求,过多的请求会导致数据库压力过大而宕机。
- 解决方法:Cache null策略,布隆过滤器
-
缓存击穿
是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
- 解决办法:设置热点数据不过期,分布式锁
-
缓存雪崩
是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
- 解决办法:数据预热,缓存过期时间随机,Redis集群
Redis有哪几种数据结构
Redis
支持5种数据类型:String(字符串)
、hash(哈希)
、list(列表)
、set(集合)
、zset(有序集合)、
Bitmap
、HyperLogLog
、 Geospatial
-
String
一个
key
对应一个value
String
类型是二进制安全的,因此redis
的string
可以包含任何数据,比如jpg
图片或者序列化对象String
类型的值最大能存储512MB常用命令:
get
、set
、decr
、incr
、mget
等 -
hash
hash
是一个键值对集合:是一个String
类型的field
和value
的映射表hash
特别适合用于存储对象每个
hash
可存储2^(32-1)
键值对常用命令:
hget
、hset
、hgetall
等 -
list
list
是一个简单的字符串列表、按照插入顺序排序,你可以添加一个元素到列表的头部或者尾部list
类型经常会被用于消息队列的服务、以完成多程序之间的消息交换列表最多可存储
2^(32-1)
个元素常用命令:
lpush
、rpush
、lpop
、rpop
、lrange
等 -
set
set
也是一个字符串列表,和列表不同的是,在插入和删除时会判断是否存在了该元素。集合的最大的优势在于可以进行交集并集差集操作。集合是通过hash
表实现的,因此,添加,删除,查找的复杂度都是o(1)
应用场景:
- 利用交集求共同好友。
- 利用唯一性,可统计访问网站的所有独立
IP
。 - 好友推荐的时候根据
tag
求交集,大于某个thresold
就可以推荐
集合最多可存储
2^(32-1)
个元素常用命令:
sadd
、spop
、smembers
、sunion
等 -
zset
和
set
一样是String
类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double
类型的分数。redis
正是通过分数来为集合中的成员进行从小到大的排序。zset
的成员是唯一的,但分数却可以重复。应用场景:
- 例如存储全班同学的成绩,其集合
value
可以是同学的学号、而score
就可以是成绩 - 排行榜应用,根据得分列出
topN
的用户等。
常用命令:
zadd
、zrange
、zrem
、zcard
等 - 例如存储全班同学的成绩,其集合
-
Bitmap
通过操作二进制位记录数据。
-
HyperLogLog
被用于估计一个set中元素数量的概率性数据结构
-
Geospatial
被用于地理空间关系计算
Redis的淘汰策略
六种淘汰策略
volatile-lru:从设置了过期时间的数据集中,选择最近最久未使用的数据释放
allkeys-lru:从数据集中(包括设置过期时间以及未设置过期时间的数据集中释放),选择最近最久未被使用的数据释放
volatile-random:从设置了过期时间的数据集中,随机选择一个数据进行释放
allkeys-random:从数据集中(包括了设置过期时间以及未设置过期时间)随机选择一个数据进行入释放
volatile-ttl:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作;
noeviction:不删除任意数据(但redis还会根据引用计数器进行释放),这时如果内存不够时,会直接返回错误。
默认的内存策略是noeviction,在Redis中LRU算法是一个近似算法,默认情况下,Redis随机挑选5个键,并且从中选取一个最近最久未使用的key进行淘汰,在配置文件中可以通过maxmemory-samples的值来设置redis需要检查key的个数,但是检查的越多,耗费的时间也就越久,但是结构越精确(也就是Redis从内存中淘汰的对象未使用的时间也就越久~),设置多少,综合权衡。
**一般来说,推荐使用的策略是volatile-lru,并辨识Redis中保存的数据的重要性。**对于那些重要的,绝对不能丢弃的数据(如配置类数据等),应不设置有效期,这样Redis就永远不会淘汰这些数据。对于那些相对不是那么重要的,并且能够热加载的数据(比如缓存最近登录的用户信息,当在Redis中找不到时,程序会去DB中读取),可以设置上有效期,这样在内存不够时Redis就会淘汰这部分数据。
Redis的持久化
Redis
的持久化策略有两种:
-
RDB
:快照形式是直接把内存中的数据保存到一个dump
文件中,定时保存。 -
AOF
:把所有的对Redis
的服务器进行修改的命令都存在一个文件里,命令的集合 -
RDB的优缺点:
-
优点:
- 对性能的影响最小,
Redis
在保存RDB
快照时会fork
出子进程进行,几乎不影响Redis
处理客服端请求的效率。 - 每次快照都会生成一个完整的数据快照文件,所以**可以辅助其他手段保存多个时间点的快照(**例如把每天0点的快照备份至其他存储媒介中)
- 作为非常可靠的灾难恢复手段。使用
RDB
文件进行数据恢复要比使用AOF
要快很多。
- 对性能的影响最小,
-
缺点:
- 快照是定期生成的,所以在
Redis crash
时或多或少会丢失一部分数据 - 如果数据集非常大且
cpu
不够强(比如单核cpu
),Redis
在fork
子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的性能。
- 快照是定期生成的,所以在
-
AOF的原理
AOF提供了三种
fsync
配置,always/everysec/no
,通过配置项[appendfsync]
指定:appendfsync no
:不进行fsync
,将flush
文件的时机交给OS
决定,速度最快appendfsync always
:每写入一条日志就进行一次fsync
操作,数据安全性最高,但速度最慢appendfsync everysec
:折中的做法,交由后台线程每秒fsync
一次随着
AOF
不断地记录写操作日志,因为所有的操作都会记录,所以必定会出现一些无用的日志。大量无用的日志会让AOF
文件过大,也会让数据恢复的时间过长。不过Redis
提供了AOF rewrite
功能,可以重写AOF
文件,只保留能够把数据恢复到最新状态的最小写操作集。AOF rewrite
可以通过BGREWRITEAOF
命令触发,也可以配置Redis
定期自动进行:auto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb
上面两行配置的含义是,
Redis
在每次AOF rewrite
时,会记录完成rewrite
后的AOF
日志大小,当AOF
日志大小在该基础上增长了100%
后,自动进行AOF rewrite
。同时如果增长的大小没有达到64mb
,则不会进行注意:
Rewrite原理 : AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename)。遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
-
AOF的优缺点
-
优点:
- 最安全,在启用
appendfsync always
时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec
也至多只会丢失1秒的数据。AOF
文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof
工具轻松修复。AOF
文件易读,可修改,在进行了某些错误的数据清除操作后,只要AOF
文件没有rewrite
,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。
- 最安全,在启用
-
缺点:
-
AOF
文件通常比RDB
文件更大性能消耗比RDB
高数据恢复速度比RDB慢
Redis
的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:AOF + fsync always
的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync
,会对Redis
的性能有比较明显的影响AOF + fsync every second
是比较好的折中方案不过大多数应用场景下,建议至少开启
RDB
方式的数据持久化。Redis
对于数据备份是非常友好的, 因为你可以在服务器运行的时候对RDB
文件进行复制:RDB
文件一旦被创建, 就不会进行任何修改。 当服务器要创建一个新的RDB
文件时, 它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时, 程序才使用rename(2)
原子地用临时文件替换原来的RDB
文件。
-
Redis的过期策略
1.设置过期时间
- expire key time—这是最常用的方式
- setex(String key,int seconds,String value) —字符串独有的方式
注意:
- 除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置时间
- 如果没有设置时间,那缓存就是永不过期
- 如果设置了过期时间,之后又想让缓存永不过期,使用persist key
2.三种过期策略
-
定时删除
- 含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
- 优点:保证内存被尽快释放
- 缺点:
- 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
- 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
- 没人用
-
惰性删除
- 含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
- 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
- 缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
-
定期删除
- 含义:每隔一段时间执行一次删除过期key操作
- 优点:
- 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用–处理"定时删除"的缺点
- 定期删除过期key–处理"惰性删除"的缺点
- 缺点
- 在内存友好方面,不如"定时删除"
- 在CPU时间友好方面,不如"惰性删除"
- 难点
- 合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)
3.Redis采用的过期策略
惰性删除+定期删除
- 惰性删除流程
- 在进行get或setnx(当key不存在时设置value)等操作时,先检查key是否过期
- 若过期,删除key,然后执行相应操作
- 若没过期,则直接执行相应操作
- 定期删除流程(简而言之,就是对指定个数的数据库的每一个库随机删除小于等于指定个数的过期key)
- 遍历每个数据库(redis.conf中配置的
database
数量,默认为16)- 检查当前库中的指定个数个key(默认每个库检查20个key,相当于循环下面的操作20次)
- 如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历
- 随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key
- 判断定期删除操作是否已经到达时长,若已经到达,直接退出定期删除。
- 检查当前库中的指定个数个key(默认每个库检查20个key,相当于循环下面的操作20次)
- 遍历每个数据库(redis.conf中配置的
- 注意:
- 对于定期删除,在程序中有一个全局变量current_db来记录下一个将要遍历的库,假设有16个库,我们这一次定期删除遍历了10个,那此时的current_db就是11,下一次定期删除就从第11个库开始遍历,假设current_db等于15了,那么之后遍历就再从0号库开始(此时current_db==0)
4.RDB对过期key的处理
过期key对RDB没有任何影响
- 从内存数据库持久化数据到RDB文件
- 持久化key之前,会检查是否过期,过期的key不进入RDB文件
- 从RDB文件恢复数据到内存数据库
- 数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)
5.AOF对过期key的处理
过期key对AOF没有任何影响
- 从内存数据库持久化数据到AOF文件:
- 当key过期后,还没有被删除,此时进行执行持久化操作(该key是不会进入aof文件的,因为没有发生修改命令)
- 当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉)
- AOF重写
- 重写时,会先判断key是否过期,已过期的key不会重写到aof文件
Redis管道
Redis是基于请求/响应协议的TCP服务,在客服端向服务器发送一个查询请求时,需要监听socket的返回,该监听过程一直阻塞,直到服务器有结果返回。由于Redis集群通常部署在不同服务器上,所以每次查询都会存在一定的网络延迟,多次请求的话可能会使延迟累加,使得Redis性能大大下降,因此Redis提出了管道技术
Redis管道技术,允许在服务器未响应的时候,连续多次发送多个请求,并最终一次性读取所有服务器的响应。这样能显著提升Redis的性能。
Redis事务
Redis支持分布式环境下的事务操作,其事务可以一次执行多个命令,事务中的所有命令都会序列化地顺序执行。
Redis的事务执行流程如下:
- 事务开启:客服端执行Multi命令开启事务
- 提交请求:客服端提交命令到事务
- 任务入队列:Redis将客服端请求放入事务队列中等待执行
- 入队状态反馈:服务器返回QURUD,表示命令已被放入事务队列
- 执行命令:客服端通过Exec执行事务
- 事务执行错误:在Redis事务中如果某条命令执行错误,则其他命令会继续执行,不会回滚。可以通过watch监控事务的执行状态并处理命令执行错误的异常情况。
- 执行结果反馈:服务器向客服端返回事务执行的结果。
命令 | 说明 |
---|---|
Multi | 标记一个事务块的开始 |
Exec | 执行所有事务块内的命令 |
Discard | 取消事务,放弃执行事务块内的所有命令 |
Watch | 监视一个key,在事务执行之前如果这个key被其他命令改动,那么事务将被打断 |
UnWatch | 取消watch命令对所有key的监视 |
Redis发布 订阅
Redis发布/订阅是一种消息通信模式,发送者(pub)向频道(Channel)发送消息,订阅者(Sub)接收频道上的消息。
Redis的集群模式及工作原理
Redis有三种集群模式:主从模式、哨兵模式、和集群模式。
(1)主从模式:
所有的写请求都被发送到主数据库上,再由主数据库将数据同步到从数据库上,主数据库主要用于执行写操作和数据同步,从数据库主要用于执行读操作,缓解系统的压力。
(2)哨兵模式
在主从模式上添加一个哨兵的角色来监控集群的运行状态。哨兵通过发送命令让Redis服务器返回其运行状态。哨兵是一个独立运行的进程,在监测到Master宕机时会自动将Slave切换成Master,然后通过发布/订阅模式通知其他从服务器修改其配置文件。完成主备热切。
(3)集群模式
Redis集群实现了在多个Redis节点之间进行数据分片和数据复制。
基于Redis集群的数据自动分片能力,我们能够方便的对Redis集群进行横向扩展,以提高Redis集群的吞吐量。
基于Redis集群的数据复制能力,在集群中的一部分节点失效或者无法进行通信时,Redis任然可以基于副本数据对外提供服务,这提高了集群的可用性。
Redis 集群数据复制的原理
1)若启动一个Slave机器进程,则它会向Master机器发送一个“sync command”命令,请求同步连接。
2)无论是第一次连接还是重新连接,Master机器都会启动一个后台进程,将数据快照保存到数据文件中(执行rdb操作),同时Master还会记录修改数据的所有命令并缓存在数据文件中。
3)后台进程完成缓存操作之后,Maste机器就会向Slave机器发送数据文件,Slave端机器将数据文件保存到硬盘上,然后将其加载到内存中,接着Master机器就会将修改数据的所有操作一并发送给Slave端机器。若Slave出现故障导致宕机,则恢复正常后会自动重新连接。
4)Master机器收到Slave端机器的连接后,将其完整的数据文件发送给Slave端机器,如果Mater同时收到多个Slave发来的同步请求,则Master会在后台启动一个进程以保存数据文件,然后将其发送给所有的Slave端机器,确保所有的Slave端机器都正常。
如果在主从复制过程中遭遇连接断开,则重新连接之后可以从中断处继续进行复制,而不必重新同步。
断点续传的工作原理具体如下。
主服务器端为复制流维护一个内存缓冲区(in-memory backlog)。主从服务器都维护一个复制偏移量(replication offset)和master run id。当连接断开时,从服务器会重新连接上主服务器,然后请求继续复制,假如主从服务器的两个master run id相同,并且指定的偏移量在内存缓冲区中还有效,则复制就会从上次中断的点开始继续。如果其中一个条件不满足,就会进行完全重新同步(在2.8版本之前就是直接进行完全重新同步)。
Redis 集群方案
1.codis
目前使用最多的集群方案,基本和twemproxy一致的效果,但它支持在结点数量改变的情况下,旧结点数据可恢复到新hash节点。
Codis分片机制:
Codis默认所有的key划分为1024个slot,对客户端传入的key做crc32运算计算hash值,再将hash后的整数值对1024取模获取key的slot。codis会在内存中维护slot与redis实例的对应关系。根据映射关系将数据转发到对应的实例。
codis集群通过对zk与etcd的支持来保证数据的一致性,如果是依赖zk,那么codisProxy的slot关系信息会存储在zk节点上,通过zk的监听机制来共享slot信息。
如何扩容:
进行扩容,意味着集群中增加新的redis实例,这时slot与实例的映射关系需要调整,意味着一部分数据需要进行迁移。
首先第一个问题是,我们需要找到槽位对应的所有key。codis增加了slotsscan命令,可以遍历指定slot下所有的key。然后挨个将每个key迁移到新的redis节点。迁移过程中,codis收到新请求,如果是查询key,那么会强制先完成迁移工作,然后再提供对外服务。
最后一点,迁移操作是一个move操作,即迁移完成后,旧实例中就不存在key了。
自动均衡机制:
redis新增实例,手动均衡slot比较麻烦,所以codis提供了自动均衡机制。自动均衡机制会在系统空闲时观察每个实例对应的slot数量,不平衡会自动进行迁移。
Codis的劣势
1.使用codis扩容的机器,redis不再支持事务。
2.rename这种危险的命令也不支持。官方文档中提供了不支持的命令列表。
3.为了支持迁移,单个key对应的value不宜过大。过大会导致迁移卡顿,官方建议小于1M,所以不适合存放社交关系数据等等。
4.网络开销比单个实例要大,性能略微下降。可以通过增加代理数量来弥补性能不足。
5.如果依赖zk,那么会增加zk运维成本。
Codis的优势
1.设计上比官方的redis cluster方案要简单。
2.托管给zk或者etcd,省去了分布式一致性逻辑。
问题:mget查询多个key的场景,codis会将key按照映射关系分组,然后对涉及的redis执行mget,最后由codis汇总返回。
分布式缓存设计的核心问题
缓存预热
指用户在请求数据前,先将数据加载到缓存系统中,用户查询事先被预热的缓存数据,以提高系统的查询效率。
缓存更新
缓存更新是指在数据变化后及时将变化后的数据更新到缓存中。常见的缓存更新策略有以下4种:
- 定时更新:定时将底层数据库的内容更新到缓存系统中
- 过期更新:将缓存种过期的数据更新为最新数据并更新缓存的过期时间
- 写请求更新:在用户有写请求时先写数据库同时更新缓存
- 读请求更新:有读请求时,如果缓存数据不存在或者过期,将底层数据库的查询结果更新到缓存中。
缓存淘汰策略
- FIFO(先进先出):判断被存储的时间,离目前最远的数据优先被淘汰
- LRU(最近被使用):判断缓存最近被使用的时间,距离当前时间最远的数据优先被淘汰。
- LFU(最不经常使用):在一段时间内,被使用次数最少的缓存优先被淘汰。
缓存雪崩
大量的缓存同一时间失效,造成数据库的压力过大进而可能发生宕机的现象。
解决办法:
- 请求加锁
- 失效更新
- 设置不同的失效时间
缓存穿透
缓存穿透是指由于缓存系统故障或者用户频繁查询系统中不存在的数据,导致请求穿过缓存系统不断发送到数据库,造成数据库过载的现象。
解决方法:
- 布隆过滤器:会过滤掉不存在数据请求的请求
- Cache null策略:在缓存中也缓存null这个数据。
缓存降级
是指由于访问量剧增,为保证核心业务的正常运行,减少或关闭非核心业务对资源的使用,常见的策略如下:
- 写降级:写请求增大,可以先只对Cache更新,事后在异步更新到数据库中。
- 读降级:只对Cache进行读取,并将结果返回给用户,适用于对数据实时性要求不高的场景。
Redis系列之事务及乐观锁
在redis中,是有事务的。但是redis的事务是弱事务。事务没有隔离级别,事务中的多条命令也不是原子性的
redis的事物使用有三步:
- 开启事物 (multi)
- 命令入队 (需要执行的命令写入队列,先进先出,队列中是一组命令。)
- 执行事物 (exec)
事物开启后,也可以取消事物(discard):
编译时报错,是因为队列中的命令本身有问题,导致在命令入队的时候就报错;有编译错误的时候,执行exec会提示失败,所有的命令都不能执行。
运行时错误,是入栈的命令本身没有错误,但是在出队执行的时候报错,比如对String做自增操作。
运行时报错了,但是事物不会回滚,而且,出错后不会影响后续的命令执行,只会有出错的那一条命令执行失败。所以,对于队列中的命令,是不存在原子性的。
redis的事物没有隔离性,
Redis使用watch
实现了一个乐观锁。
Redis系列之分布式锁
什么是分布式锁
先说一个场景,消费者在购物网站上下单或收银员在POS机上下单,由于网络等问题,在连续点击了两下,后端网站如何处理,如何响应?对于这个问题,前端需要处理,后端也需要处理。这里主要说后端,后端不光要处理重复订单问题,还有处理幂等问题。幂等问题简单来说就是相同的请求,要有相同的响应结果,这里就不展开了。重复订单该如何处理?
对于一个小的访问量不大的网站,部署了一个tomcat,这个问题可以简单的通过JVM提供的同步锁synchronized实现。但是当网站访问量越来越大时,需要扩展机器,synchronized就不能起作用了。相同的下单参数连续两次请求后端服务器,可能会被分发到两个tomcat上,就会出现synchronized失效问题。
幂等操作:
在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“getUsername()和setTrue()”函数就是一个幂等函数。用通俗的话讲:就是针对一个操作,不管做多少次,产生效果或返回的结果都是一样的
分布式锁要解决的就是多机器部署时,相同请求并发访问时资源竞争问题。请求到达每个tomcat时,首先要去redis中注册锁,注册成功返回true则说明获得了锁,可以继续处理相关的业务,处理完成后释放锁。同一时刻只能有一个tomcat能获得锁,其他没获得锁的tomcat则多次尝试继续获得锁,没有获得锁不能处理业务。获得锁的tomcat释放锁后,其他的tomcat才能有一个获得锁。
解决重复订单问题:
在请求进入方法前,加锁,往后的同一个请求(requestId相同)无法获取锁,就被判定为重复请求,抛出异常,等第一个请求调用完毕后再释放锁。
这里是使用redis做外部存储介质存储锁的,使用zookeeper也是类似的。万变不离其宗,原理都一样,只是技术选型有差别。
Redis系列之数据持久化(RDB和AOF)
Redis数据类型之bitmaps
redis系列之——数据类型bitmaps:今天你签到了吗?
什么是bitmaps
来看看官方对Bitmaps的说明:
- Bitmaps 并不是实际的数据类型,而是定义在String类型上的一个面向字节操作的集合。因为字符串是二进制安全的块,他们的最大长度是512M,最适合设置成2^32个不同字节。
- bitmaps的位操作分成两类:1.固定时间的单个位操作,比如把String的某个位设置为1或者0,或者获取某个位上的值 2.对于一组位的操作,对给定的bit范围内,统计设定值为1的数目(比如人口统计)。
- bitmaps最大的优势是在存储数据时可以极大的节省空间,比如在一个项目中采用自增长的id来标识用户,就可以仅用512M的内存来记录40亿用户的信息(比如用户是否希望收到新的通知,用1和0标识)
简单来说bitmaps就是一个长度可变的bit数组。每个位只能存储0或1。我们先来看看bitmap的具体表示,当我们使用命令 setbit key (0,2,4,6) 1后,这个bit数组的具体表示为:
bit0bit1bit2bit3bit4bit5bit6bit710101010
一天的1亿人的登录情况(登录、未登录)就可以使用一个长度为1亿的bit数组存储,数组的索引就是用户的userId(假设userId是自增的)。
使用场景
由于bit数组的每个位置只能存储0或者1这两个状态;所以对于实际生活中,处理两个状态的业务场景就可以考虑使用bitmaps。如用户登陆/未登录,签到/未签到,关注/未关注,打卡/未打卡等。同时bitmap还通过了相关的统计方法进行快速统计。
由于bit数组的每个位置只能存储0或者1这两个状态;所以对于实际生活中,处理两个状态的业务场景就可以考虑使用bitmaps。如用户登陆/未登录,签到/未签到,关注/未关注,打卡/未打卡等。同时bitmap还通过了相关的统计方法进行快速统计。
内存占用比较
假如一个平台有8亿用户,平均日活跃用户有1亿,,分别使用List和Bitmap存储平台某一天是否活跃(登陆)用户时内存占用情况(1KB=1000bit):
数据类型每个userId占用空间需要存储的用户量内存使用总量List4 * 8bit=32bit(假设userId用的是int存储)100,000,00032bit * 100,000,000= 400MBBitmaps1bit800,000,0001bit * 800,000,000=100MB
假如一个平台有8亿用户,平均日活跃用户有100万,,分别使用List和Bitmap存储平台某一天是否活跃(登陆)用户时内存占用情况(1KB=1000bit):
数据类型每个userId占用空间需要存储的用户量内存使用总量List4 * 8bit=32bit(假设userId用的是int存储)1,000,00032bit * 1,000,000= 4MBBitmaps1bit800,000,0001bit * 800,000,000=100MB
所以并不是在所有的情况下,使用bitmap都是最好的选择。平台虽然有8亿用户,但是活跃的用户很少,这是使用Bitmaps,如果只有一个用户登录(加入是userId=800,000,000-1这个用户登录),也需要分配100MB的空间。
注意
bitmaps类型(string)最大长度为512M。 setbit时的偏移量很大时,可能会有较大耗时。 bitmaps不是绝对的好,有时可能更浪费空间。
Bloot Filter该怎么做,你是不是已经知道了?
完成,收工!!
Redis系列之数据类型geospatial
如何实现定位功能
说到定位,很多人第一反应应该是,实时上报经纬度,数据库中提前存储好所有的经纬度,然后用上报的经纬度和数据库中的经纬度进行比较,计算出附近的人或共享单车。这种做法需要循环遍历,数据库中的数据量大,查询慢,效率低。
那么,这些app是如何做到既能够精确定位,又能够实时查询的呢?答案就是使用geohash。redis的"数据类型"geospatial就能计算出geohash。redis使用geohash技术将实时上报的精度和纬度,通过一定的算法转化成最长12个字符的字符串,两个位置的经纬度计算的字符串的前缀越相同,则两个位置离得越近。这样一来就可以通过数据库的like加上geohash的前几位模糊查询数据库的数据了。比如ofo共享单车,数据库中用一张表t_bike专门存储ofo的每一辆车的编号no、经度longitude 、纬度latitude、geohash等字段,当每一辆车上报自己的经纬度时,同时计算一个geohash存到表中;当用户要用车时,上报用户的实时位置的经纬度,并计算一个hash值,比如hash=efgrtv98fjng,那么可以使用:
select * from t_bike where geohash like 'efgrtv98%'
就可以找到附近有多少车了。like后面使用的hash位数越多,查找的范围越准确。
查询的前提是开启实时定位功能。
Geohash技术
geohash技术就是将经纬度转换成最长12个字符的字符串,同时两个位置越近,生成的字符串的前缀越一致。这是如何实现的呢?
例如,东方明珠的经纬度,东经121.506377,北纬31.245105。
下面就以东方明珠为例,简单说一下如何将这两个经纬度计算成一个hash字符串的。
geohash的计算
1.使用二分法生成二进制
将纬度(-90,90)分成两个区间,(-90,0)和(0,90),如果目标纬度落在左边区间则记为0,否则记为1;再将目标纬度所在的那个区间在通过二分法分成两个相等的区间,如果目标纬度落在左边区间则记为0,否则记为1,以此类推。
同样的,将经度(-180,180)也通过这种方式计算。
最终,经度和纬度计算后,分别得到一个由0和1组成的二进制。
假如,东方明珠的经纬度计算后,得到两个二进制位:
经度:110101100101001110111100011010
纬度:101011000101010000110101100101
2.合并二进制
将上面的两个二进制按照“偶数位放经度,奇数位放纬度”的原则,从0位开始数起,合并成一个二进制。
可以理解成将纬度向后移动一位,然后将两行压成一行。
结果: 111001100111100000110011000110101000111110110001011010011001
3.二进制转换成十进制
把上面合并后的60位二进制,按照从左往右,每5位划分成1个组,如果最后一组如果不足5位就用0补齐到5位。分组后所示:
分组结果: 11100 11001 11100 00011 00110 00110 10100 01111 10110 00101 10100 11001
将上面的每组二进制分别转成十进制:
十进制结果: 28 25 28 3 6 6 20 15 22 5 20 25
4.十进制转base32字符串
使用base32编码表,将每个十进制数替换成编码表中的字符,获得一个字符串。
base32编码表如下:
转化后的字符串:
base32字符串:4Z4CGGUPWFUZ
这就是模拟东方明珠的经纬度生产的geohash的值(不是真实值)。
geohash的精度
geohash这个字符串在地图上表示一个矩形的块。
hash的字符串长1位-12位,对应精度的级别1-12级。字符串越长,位置越精确。
上面模拟的东方明珠的hash有12位字符串,精度在37mm以内。上面可以看出,6位hash的精度在1.2km以内。所以当两个hash的前6位相同时,就可以将范围缩小到1.2km以内了。在实际的应用中,我们就可以通过调整精度级别控制搜索的范围。
geohash的区块中,同一个区块内部的点被认为是最近的。如下图,如果你在东方明珠圆圈的中心,搜索最近的便利店,你会搜索到A点,而搜索不到B点,虽然B点是最近的。这就是geohash的边界问题。这个该如何解决呢?
其实,就是将该区块上下左右以及四个对角的8个区块的hash都计算一遍,分别计算这些便利店和自己之间的距离,找到最近的一家。因为这是的数据量已经非常小了,计算周边的8个块也很快。
redis> GEOADD china:city 121.47 31.23 shanghai #添加上海的经纬度
(integer) 1
redis> GEOADD china:city 116.40 39.90 beijing #添加北京的经纬度
(integer) 1
redis> GEODIST china:city shanghai beijing km #计算上海和北京之间的直线距离
"1067.3788"
redis> GEORADIUS china:city 116 39 1500 km #找到离经纬度为116,39的位置1500km以内的地方有哪些 ,因为redis中只有两个城市,所以只能显示两个
1) "beijing"
2) "shanghai"
redis> GEOHASH china:city beijing #获得北京的geohash
1) "wx4fbxxfke0"
Redis系列之一致性hash算法
Redis系列之高可用(主从、哨兵、集群)
一,主从模式
一般,系统的高可用都是通过部署多台机器实现的。redis为了避免单点故障,也需要部署多台机器。
因为部署了多台机器,所以就会涉及到不同机器的的数据同步问题。
为此,redis提供了Redis提供了复制(replication)功能,当一台redis数据库中的数据发生了变化,这个变化会被自动的同步到其他的redis机器上去。
redis多机器部署时,这些机器节点会被分成两类,一类是主节点(master节点),一类是从节点(slave节点)。一般主节点可以进行读、写操作,而从节点只能进行读操作。同时由于主节点可以写,数据会发生变化,当主节点的数据发生变化时,会将变化的数据同步给从节点,这样从节点的数据就可以和主节点的数据保持一致了。一个主节点可以有多个从节点,但是一个从节点会只会有一个主节点,也就是所谓的一主多从结构。
二, 哨兵模式
主从模式下,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这种方式并不推荐,实际生产中,我们优先考虑哨兵模式。这种模式下,master宕机,哨兵会自动选举master并将其他的slave指向新的master。
在主从模式下,redis同时提供了哨兵命令redis-sentinel
,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵进程向所有的redis机器发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵可以有多个,一般为了便于决策选举,使用奇数个哨兵。哨兵可以和redis机器部署在一起,也可以部署在其他的机器上。多个哨兵构成一个哨兵集群,哨兵直接也会相互通信,检查哨兵是否正常运行,同时发现master宕机哨兵之间会进行决策选举新的master
哨兵模式的作用:
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器;
- 当哨兵监测到master宕机,会自动将slave切换到master,然后通过发布订阅模式通过其他的从服务器,修改配置文件,让它们切换主机;
- 然而一个哨兵进程对Redis服务器进行监控,也可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
哨兵很像kafka集群中的zookeeper的功能
三,集群模式
先说一个误区:Redis的集群模式本身没有使用一致性hash算法,而是使用slots插槽。这是很多人的一个误区。这里先留个坑,后面我会出一期《 redis系列之——一致性hash算法》。
Redis 的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,对数据进行分片,也就是说每台 Redis 节点上存储不同的内容;
这里的6台redis两两之间并不是独立的,每个节点都会通过集群总线(cluster bus),与其他的节点进行通信。通讯时使用特殊的端口号,即对外服务端口号加10000。例如如果某个node的端口号是6379,那么它与其它nodes通信的端口号是16379。nodes之间的通信采用特殊的二进制协议。
对客户端来说,整个cluster被看做是一个整体,客户端可以连接任意一个node进行操作,就像操作单一Redis实例一样,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node,这有点儿像浏览器页面的302 redirect跳转。
根据官方推荐,集群部署至少要 3 台以上的master节点,最好使用 3 主 3 从六个节点的模式。测试时,也可以在一台机器上部署这六个实例,通过端口区分出来。
3.4.运行机制
在 Redis 的每一个节点上,都有这么两个东西,一个是插槽(slot),它的的取值范围是:0-16383,可以从上面redis-trib.rb
执行的结果看到这16383个slot在三个master上的分布。还有一个就是cluster,可以理解为是一个集群管理的插件,类似的哨兵。
当我们的存取的 Key到达的时候,Redis 会根据 crc16的算法对计算后得出一个结果,然后把结果和16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。
当数据写入到对应的master节点后,这个数据会同步给这个master对应的所有slave节点。
为了保证高可用,redis-cluster集群引入了主从模式,一个主节点对应一个或者多个从节点。当其它主节点ping主节点master 1时,如果半数以上的主节点与master 1通信超时,那么认为master 1宕机了,就会启用master 1的从节点slave 1,将slave 1变成主节点继续提供服务。
如果master 1和它的从节点slave 1都宕机了,整个集群就会进入fail状态,因为集群的slot映射不完整。如果集群超过半数以上的master挂掉,无论是否有slave,集群都会进入fail状态。
redis-cluster采用去中心化的思想,没有中心节点的说法,客户端与Redis节点直连,不需要中间代理层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
注:
对redis集群的扩容就是向集群中添加机器,缩容就是从集群中删除机器,并重新将16383个slots分配到集群中的节点上(数据迁移)。
3.8.集群模式的优缺点
优点
采用去中心化思想,数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布;
可扩展性:可线性扩展到 1000 多个节点,节点可动态添加或删除;
高可用性:部分节点不可用时,集群仍可用。通过增加 Slave 做 standby 数据副本,能够实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升;
降低运维成本,提高系统的扩展性和可用性。
缺点
1.Redis Cluster是无中心节点的集群架构,依靠Goss协议(谣言传播)协同自动化修复集群的状态
但 GosSIp有消息延时和消息冗余的问题,在集群节点数量过多的时候,节点之间需要不断进行 PING/PANG通讯,不必须要的流量占用了大量的网络资源。虽然Reds4.0对此进行了优化,但这个问题仍然存在。
2.数据迁移问题
Redis Cluster可以进行节点的动态扩容缩容,这一过程,在目前实现中,还处于半自动状态,需要人工介入。在扩缩容的时候,需要进行数据迁移。
而 Redis为了保证迁移的一致性,迁移所有操作都是同步操作,执行迁移时,两端的 Redis均会进入时长不等的阻塞状态,对于小Key,该时间可以忽略不计,但如果一旦Key的内存使用过大,严重的时候会接触发集群内的故障转移,造成不必要的切换。
四、总结
主从模式:master节点挂掉后,需要手动指定新的master,可用性不高,基本不用。
哨兵模式:master节点挂掉后,哨兵进程会主动选举新的master,可用性高,但是每个节点存储的数据是一样的,浪费内存空间。数据量不是很多,集群规模不是很大,需要自动容错容灾的时候使用。
集群模式:数据量比较大,QPS要求较高的时候使用。 Redis Cluster是Redis 3.0以后才正式推出,时间较晚,目前能证明在大规模生产环境下成功的案例还不是很多,需要时间检验。
Redis怎么实现的点赞(项目)
step1: 参数校验:对传入的参数进行null值判断
step2:逻辑校验:对于用户点赞,用户不能重复点赞相同的文章
对于取消点赞,用户不能取消未点赞的文章
step3:存入Redis:
存入的数据主要有所有文章的点赞数,某篇文章的点赞数,用户点赞的文章
step4:定时任务
通过定时任务(1小时执行一次),从Redis读取数据持久化到MySQL中
Redis的应用
- 缓存,速度快,提升服务器性能
- 排行榜,在使用传统的关系型数据库来做这个事情,非常的麻烦,利用Redis的SortSet(有序集合)数据结构可以简单搞定
- 计数器:利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力
- 好友关系:利用集合的一些命令,比如求交集,并集,差集等。可以方便搞定一些共同好友,共同爱好值之类的功能。
- 简单消息队列:除了Redis自身的发布/订阅模式,我们也可以利用List来实现一个队列机制,比如:到货通知,邮件发送之类的需求。
- Session共享:以PHP为例,默认Session是保存在服务器的文件中,如果是集群服务,同一个用户过来可能落在不同机器上,这就会导致用户频繁登陆;采用Redis保存Session后,无论用户落在那台机器上都能够获取到对应的Session信息。
- 一些频繁被访问的数据,经常被访问的数据如果放在关系型数据库,每次查询的开销都会很大,而放在redis中,因为redis 是放在内存中的可以很高效的访问
Redis的String应用场景:
-
计数器
INCR article:readcount:{文章id}
GET article:readcount:{文章id}
-
web集群session共享(不同的访问请求映射到不同服务器上的问题)
spring session +redis实现session共享
-
分布式系统全局序列号
INCRBY orderid 1000
Redis的一些问题
(https://blog.csdn.net/qq_28827039/article/details/81183888)