redis
redis介绍
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
redis常用的数据类型
1. String
- 字符串
- 数值
- 1.常用命令
INCR 命令将字符串值解析成整型,将其加一,最后将结果保存为新的字符串值
INCRBY n 对一个k 加 n
DECR 减一
DECRBY n 减n
GETSET命令,行如其名:它为key设置新值并且返回原值 - 2.命令说明
INCR是原子操作意味着什么呢?就是说即使多个客户端对同一个key发出INCR命令,也决不会导致竞争的情况。例如如下情况永远不可能发生:『客户端1和客户端2同时读出“10”,他们俩都对其加到11,然后将新值设置为11』。最终的值一定是12,read-increment-set操作完成时,其他客户端不会在同一时间执行任何命令。
对字符串,另一个的令人感兴趣的操作是GETSET命令,行如其名:他为key设置新值并且返回原值。这有什么用处呢?例如:你的系统每当有新用户访问时就用INCR命令操作一个Redis key。你希望每小时对这个信息收集一次。你就可以GETSET这个key并给其赋值0并读取原值。
为减少等待时间,也可以一次存储或获取多个key对应的值,使用MSET和MGET命令:
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。 - 3.使用场景
抢购,秒杀,详情页,点赞,评论 规避并发下, 对数据库的事务操作 完全由redis内存操作代替
- 1.常用命令
- bitmap
- 1.常用命令
setbit
bitcount
bitpos
bitop - 2.使用场景1
1,有用户系统,统计用户登录天数,
setbit sean 1 1
setbit sean 7 1
setbit sean 364 1
STRLEN sean
BITCOUNT sean -2 -1
01 02 03 04
sean 0 1 0 1 010101
json 0 1 0 1 011111
每用户46B * 用户数 10000000 =460 000 000 - 3.使用场景2
618做活动:送礼物
大库备货多少礼物
假设京东有2E用户
僵尸用户
冷热用户/忠诚用户
活跃用户统计!1号3号 都有哪些用户进行了登录3号 连续登录要 去重
比如说 1号
setbit 20190101 1 1
setbit 20190102 1 1
setbit 20190102 7 1
bitop or destkey 20190101 20190102
BITCOUNT destkey 0 -1
- 1.常用命令
2. set
2.set介绍
Redis Set 是 String 的无序排列。SADD 指令把新的元素添加到 set 中。对 set 也可做一些其他的操作,比如测试一个给定的元素是否存在,对不同 set 取交集,并集或差,等等。-
SRANDMEMBER key count
3.使用场景
Sets 适合用于表示对象间的关系。 例如,我们可以轻易使用 set 来表示标记。
一个简单的建模方式是,对每一个希望标记的对象使用 set。这个 set 包含和对象相关联的标签的 ID。
假设我们想要给新闻打上标签。 假设新闻 ID 1000 被打上了 1,2,5 和 77 四个标签,我们可以使用一个 set 把 tag ID 和新闻条目关联起来:
3. hash
hash介绍
Hash 便于表示 objects,实际上,你可以放入一个 hash 的域数量实际上没有限制(除了可用内存以外)。所以,你可以在你的应用中以不同的方式使用 hash。
HMSET 指令设置 hash 中的多个域,而 HGET 取回单个域。HMGET 和 HGET 类似,但返回一系列值:使用场景
场景:点赞,收藏,详情页
4.sorted_set
- 1.常用命令
- sorted_set介绍
Redis排序集—
排序集是一种数据类型,类似于集合和哈希之间的混合。像集合一样,排序集合由唯一的,非重复的字符串元素组成,因此从某种意义上说,排序集合也是一个集合。
但是,虽然不对集合内的元素进行排序,但是排序后的集合中的每个元素都与一个称为得分的浮点值相关联 (这就是为什么该类型也类似于哈希的原因,因为每个元素都映射到一个值)。
此外,已排序集合中的元素是按顺序进行的(因此,它们不是应请求而排序的,顺序是用于表示已排序集合的数据结构的特殊性)。它们按照以下规则排序:
如果A和B是两个具有不同分数的元素,那么如果A.score是> B.score,则A>B。
如果A和B的得分完全相同,那么如果A字符串在字典上大于B字符串,则A>B。A和B字符串不能相等,因为排序集仅具有唯一元素。
它的底层核心实现的机制是跳跃表 - 功能
集合操作
并集,交集
权重/聚合指令
5.list
- 1.常用命令
- list介绍
Lists: 按插入顺序排序的字符串元素的集合。他们基本上就是链表(linked lists)
Redis lists基于Linked Lists实现。这意味着即使在一个list中有数百万个元素,在头部或尾部添加一个元素的操作,其时间复杂度也是常数级别的。用LPUSH 命令在十个元素的list头部添加新元素,和在千万元素list头部添加新元素的速度相同。
那么,坏消息是什么?在数组实现的list中利用索引访问元素的速度极快,而同样的操作在linked list实现的list上没有那么快。
Redis Lists用linked list实现的原因是:对于数据库系统来说,至关重要的特性是:能非常快的在很大的列表上添加元素。另一个重要因素是,正如你将要看到的:Redis lists能在常数时间取得常数长度。 - 功能
栈 同向命令
队列 反向命令
数组
阻塞,单播队列 FIFO
6.streams
- streams介绍
Stream是Redis 5.0版本引入的一个新的数据类型,它以更抽象的方式模拟日志数据结构,但日志仍然是完整的:就像一个日志文件,通常实现为以只附加模式打开的文件,Redis流主要是一个仅附加数据结构。至少从概念上来讲,因为Redis流是一种在内存表示的抽象数据类型,他们实现了更加强大的操作,以此来克服日志文件本身的限制。
Stream是Redis的数据类型中最复杂的,尽管数据类型本身非常简单,它实现了额外的非强制性的特性:提供了一组允许消费者以阻塞的方式等待生产者向Stream中发送的新消息,此外还有一个名为消费者组的概念。
消费者组最早是由名为Kafka(TM)的流行消息系统引入的。Redis用完全不同的术语重新实现了一个相似的概念,但目标是相同的:允许一组客户端相互配合来消费同一个Stream的不同部分的消息。
redis的发布/订阅
Pub/Sub
订阅,取消订阅和发布实现了发布/订阅消息范式(引自wikipedia),发送者(发布者)不是计划发送消息给特定的接收者(订阅者)。而是发布的消息分到不同的频道,不需要知道什么样的订阅者订阅。订阅者对一个或多个频道感兴趣,只需接收感兴趣的消息,不需要知道什么样的发布者发布的。这种发布者和订阅者的解耦合可以带来更大的扩展性和更加动态的网络拓扑。
为了订阅foo和bar,客户端发出一个订阅的频道名称:
SUBSCRIBE foo bar
其他客户端发到这些频道的消息将会被推送到所有订阅的客户端。
客户端订阅到一个或多个频道不必发出命令,尽管他能订阅和取消订阅其他频道。订阅和取消订阅的响应被封装在发送的消息中,以便客户端只需要读一个连续的消息流,其中第一个元素表示消息类型。PUBLISH foo Hello
redis的内存淘汰策略
LRU是Redis唯一支持的回收方法。本页面包括一些常规话题,Redis的maxmemory
指令用于将可用内存限制成一个固定大小,还包括了Redis使用的LRU算法,这个实际上只是近似的LRU。
- 回收策略
当maxmemory限制达到的时候Redis会使用的行为由 Redis的maxmemory-policy配置指令来进行配置。
以下的策略是可用的:- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错(大部分的写入指令,但DEL和几个例外)
- allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
- volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
- allkeys-random: 回收随机的键使得新添加的数据有空间存放。
- volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
- volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过RedisINFO命令输出以便调优。
一般的经验规则:
- 使用allkeys-lru策略:当你希望你的请求符合一个幂定律分布,也就是说,你希望部分的子集元素将比其它其它元素被访问的更多。如果你不确定选择什么,这是个很好的选择。.
- 使用allkeys-random:如果你是循环访问,所有的键被连续的扫描,或者你希望请求分布正常(所有元素被访问的概率都差不多)。
- 使用volatile-ttl:如果你想要通过创建缓存对象时设置TTL值,来决定哪些对象应该被过期。
allkeys-lru 和 volatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。
为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为没有必要为键取设置过期时间当内存有压力时。
redis的过期策略
过期策略通常有以下三种:
定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:定期删除指的是Redis默认每隔100ms就随机抽取一些设置了过期时间的key,检测这些key是否过期,如果过期了就将其删掉。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
Redis中同时使用了惰性过期和定期过期两种过期策略
持久化如何处理过期
RDB
从内存数据库持久化数据到RDB文件:持久化key之前,会检查是否过期,过期的key不进入RDB文件 从RDB文件恢复数据到内存数据库:数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)。AOF
从内存数据库持久化数据到AOF文件:当key过期后,还没有被删除,此时进行执行持久化操作(该key是不会进入aof文件的,因为没有发生修改命令)
当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉)
AOF重写:重写时,会先判断key是否过期,已过期的key不会重写到aof文件
redis的事务
MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
EXEC 命令负责触发并执行事务中的所有命令:
- 如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
- 另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。
当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。
然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。
如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。
使用redis-check-aof
程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。
从 2.2 版本开始,Redis 还可以通过乐观锁(optimistic lock)实现 CAS (check-and-set)操作。
redis的缓存穿透/缓存雪崩/缓存穿透 和 缓存预热,缓存降级
- 缓存穿透
1.什么是缓存穿透
缓存穿透是指用户请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致 用户每次请求该数据都要去数据库中查询一遍。如果有恶意攻击者不断请求系统中不存在的数 据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至导致数据库承受不住而宕 机崩溃。
2.问题分析
缓存穿透的关键在于在Redis中查不到key值,它和缓存击穿的根本区别在于传进来的key 在Redis中是不存在的。假如有黑客传进大量的不存在的key,那么大量的请求打在数据库上是 很致命的问题,所以在日常开发中要对参数做好校验,一些非法的参数,不可能存在的key就直 接返回错误提示。
3.解决办法
(1)将无效的key存放进Redis中,当出现Redis查不到数据,数据库也查不到数据的情况,我们就把这个key保存到Redis中,设置value=”null”,并设置其过期时间极短,后面再出现查询这个key的请求的时候,直接返回null,就不需要再查询数据库了。但这种处理方式是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。
(2)使用布隆过滤器,如果布隆过滤器判定某个 key 不存在布隆过滤器中,那么就一定不存在,如果判定某个key存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一个布隆过滤器,将数据库中的所有key都存储在布隆过滤器中,在查询Redis前先去布隆过滤器查询 key 是否存在,如果不存在就直接返回,不让其访问数据库,从而避免了对底层存储系统的查询压力。
4.注
针对一些恶意攻击,攻击带过来的大量key是随机,那么我们采用第一种方案就会缓存大量 不存在key的数据。那么这种方案就不合适了,我们可以先对使用布隆过滤器方案进行过滤掉这 些key。所以,针对这种key异常多、请求重复率比较低的数据,优先使用第二种方案直接过滤 掉。而对于空数据的key有限的,重复率比较高的,则可优先采用第一种方式进行缓存。 - 缓存击穿
1.什么是缓存击穿
缓存击穿跟缓存雪崩有点类似,缓存雪崩是大规模的key失效,而缓存击穿是某个热点的 key失效,大并发集中对其进行请求,就会造成大量请求读缓存没读到数据,从而导致高并发 访问数据库,引起数据库压力剧增。这种现象就叫做缓存击穿。
2.问题分析
关键在于某个热点的key失效了,导致大并发集中打在数据库上。所以要从两个方面解 决,第一是否可以考虑热点key不设置过期时间,第二是否可以考虑降低打在数据库上的请求量。
3.解决方案
(1)热点数据缓存永远不过期
(2)把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行 缓存的构建
(1)在缓存失效后,通过互斥锁或者队列来控制读数据写缓存的线程数量,比如某个key只允 许一个线程查询数据和写缓存,其他线程等待。这种方式会阻塞其他的线程,此时系统的 吞吐量会下降 - 缓存雪崩
1.什么是缓存雪崩
如果缓在某一个时刻出现大规模的key失效,那么就会导致大量的请求打在了数据库上面,导 致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果运维马 上又重启数据库,马上又会有新的流量把数据库打死。这就是缓存雪崩。
2.问题分析
造成缓存雪崩的关键在于同一时间的大规模的key失效,为什么会出现这个问题,主要有两种 可能:第一种是Redis宕机,第二种可能就是采用了相同的过期时间。搞清楚原因之后,那么有 什么解决方案呢?
3.解决方案
(1)事前:
① 均匀过期:设置不同的过期时间,让缓存失效的时间尽量均匀,避免相同的过期时间导致 缓存雪崩,造成大量数据库的访问。
② 分级缓存:第一级缓存失效的基础上,访问二级缓存,每一级缓存的失效时间都不同。
③ 热点数据缓存永远不过期。
(2)事中
① 互斥锁:在缓存失效后,通过互斥锁或者队列来控制读数据写缓存的线程数量,比如某个 key只允许一个线程查询数据和写缓存,其他线程等待。这种方式会阻塞其他的线程,此时 系统的吞吐量会下降
② 使用熔断机制,限流降级。当流量达到一定的阈值,直接返回“系统拥挤”之类的提示, 防止过多的请求打在数据库上将数据库击垮,至少能保证一部分用户是可以正常使用,其他 用户多刷新几次也能得到结果。
(3)事后
① 开启Redis持久化机制,尽快恢复缓存数据,一旦重启,就能从磁盘上自动加载数据恢复 内存中的数据。 - 缓存预热
1.什么是缓存预热
缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时 候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。
如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会 访问到数据库中, 对数据库造成流量的压力。
2.缓存预热解决方案
(1)数据量不大的时候,工程启动的时候进行加载缓存动作;
(2)数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
(3)数据量太大的时候,优先保证热点数据进行提前加载到缓存。 - 缓存降级
缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。
在项目实战中通常会将部分热点数据缓存到服务的内存中,这样一旦缓存出现异常,可以直接使用服务的内存数据,从而避免数据库遭受巨大压力。
redis中的持久化
- rdb
- aof
redis的集群
graph TD A[Mater] --> B[Slave] A --> C[Slave] A --> D[Slave] D --> E[Slave] D --> F[Slave]
1.全量同步
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都 复制一份。具体步骤如下:
-从服务器连接主服务器,发送SYNC命令;
-主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
-主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
-从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
-主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
-从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令
2.增量同步
-Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
-增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
3.Redis主从同步策略
-主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量 同步,如不成功,要求从机进行全量同步。
4.详细文档
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
-监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
-提醒(Notification): 当被监控的某个 Redis 服务器出现问题时,Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
-自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
虽然 Redis Sentinel 释出为一个单独的可执行文件 redis-sentinel , 但实际上它只是一个运行在特殊模式下的 Redis 服务器, 你可以在启动一个普通 Redis 服务器时通过给定 –sentinel 选项来启动 Redis Sentinel 。
- 分区
有许多分区标准。假如我们有4个Redis实例R0, R1, R2, R3,有一批用户数据user:1
,user:2
, … ,那么有很多存储方案可以选择。从另一方面说,有很多different systems to map方案可以决定用户映射到哪个Redis实例。
一种最简单的方法就是范围分区,就是将不同范围的对象映射到不同Redis实例。比如说,用户ID从0到10000的都被存储到R0,用户ID从10001到20000被存储到R1,依此类推。
这是一种可行方案并且很多人已经在使用。但是这种方案也有缺点,你需要建一张表存储数据到redis实例的映射关系。这张表需要非常谨慎地维护并且需要为每一类对象建立映射关系,所以redis范围分区通常并不像你想象的那样运行,比另外一种分区方案效率要低很多。
另一种可选的范围分区方案是散列分区,这种方案要求更低,不需要key必须是object_name:<id>
的形式,如此简单:
-使用散列函数 (如crc32
)将键名称转换为一个数字。例:键foobar
, 使用crc32(foobar)
函数将产生散列值93024922
。
-对转换后的散列值进行取模,以产生一个0到3的数字,以便可以使这个key映射到4个Redis实例当中的一个。93024922 % 4
等于2
, 所以foobar
会被存储到第2个Redis实例。 R2 注意: 对一个数字进行取模,在大多数编程语言中是使用运算符%
还有很多分区方法,上面只是给出了两个简单示例。有一种比较高级的散列分区方法叫一致性哈希,并且有一些客户端和代理(proxies)已经实现.
分区的优势
1.通过利用多台计算机内存的和值,允许我们构造更大的数据库。
2.通过多核和多台计算机,允许我们扩展计算能力;通过多台计算机和网络适配器,允许我们扩展网络带宽。
分区的不足
1.涉及多个key的操作通常是不被支持的。举例来说,当两个set映射到不同的redis实例上
时,你就不能对这两个set执行交集操作。
2.涉及多个key的redis事务不能使用。
3.当使用分区时,数据处理较为复杂,比如你需要处理多个rdb/aof文件,并且从多个实例
和主机备份持久化文件。
4.增加或删除容量也比较复杂。redis集群大多数支持在运行时增加、删除节点的透明数据
平衡的能力,但是类似于客户端分区、代理等其他系统则不支持这项特性。然而,一种叫做presharding的技术对此是有帮助的。
支持redis自动分区的工具
-predix
-twemprox
predix和twemprox性能对比记录
综合以上推荐使用predixy,支持的redis的比较全面。