前言大多数数据库,因为常常和磁盘打交道,在高并发场景下,响应会很是的慢。为了解决这种速度差别,大多数系统都习惯性的加入一个缓存层,来加速数据的读取。redis因为它优秀的处理能力和丰富的数据结构,已经成为了事实上的分布式缓存标准。java
可是,若是你觉得redis只能作缓存的话,那就过小看它了。mysql
redis丰富的数据结构,使得它的业务使用场景很是普遍,加上rdb的持久化特性,它甚至可以被看成落地的数据库使用。在这种状况下,redis可以撑起大多数互联网公司,尤为是社交、游戏、直播类公司的半壁江山。redis
Redis可以胜任存储工做redis提供了很是丰富的集群模式:主从
、哨兵
、cluster
,知足服务高可用的需求。同时,redis提供了两种持久化方式:aof
和rdb
,经常使用的是rdb。sql
经过bgsave
指令,主进程会fork出新的进程,回写磁盘。bgsave至关于作了一个快照,因为它并无WAL日志和checkpoint机制,是没法作到实时备份的。若是机器忽然断电,那就很容易丢失数据。数据库
幸运的是,redis是内存型的数据库,主丛同步的速度是很是快的。若是你的集群维护的好,内存分配的合理,那么除非机房断电,不然redis的SLA,会一直保持在很是高的水平。编程
听起来不是绝对可靠啊,有丢失数据的可能!这在通常CRUD的业务中,是没法忍受的。但为何redis可以知足大多数互联网公司的需求?这也是由业务属性所决定的。api
在决定最大限度拥抱redis以前,你须要确认你的业务是否有如下特色:数组
除了核心业务,是否大多数业务对于数据的可靠性要求较低,丢失一两条数据是能够忍受的?缓存
很幸运的是,这类业务需求特别的多。好比常见的社交,游戏、直播、运营类业务,都是能够彻底依赖Redis的。安全
Reids应用场景Redis具备松散的文档结构,丰富的数据类型,可以适应变幻无穷的scheme变动需求,接下来我将介绍Redis除缓存外的大量的应用场景。
在传统的数据库设计中,用户表是很是难以设计的,变动的时候会伤筋动骨。使用Redis的hash
结构,能够实现松散的数据模型设计。某些不固定,验证型的功能属性,能够以JSON接口直接存储在hash的value中。使用hash结构,能够采用HGET和HMGET等指令,只获取本身所须要的数据,在使用上也是很是便捷的。
>HSET user:199929 sex m >HSET user:199929 age 22 >HGETALL user:199929 1) "sex" 2) "m" 3) "age" 4) "22"
这种非统计型的、读多写少的场景,是很是适合使用KV结构进行存储的。Redis的hash结构提供了很是丰富的指令,某个属性也可使用HINCRBY
进行递增递减,很是的方便。
上面稍微提了一下HINCRBY指令,而对于Redis的Key自己来讲,也有INCRBY
指令,实现某个值
的递增递减。
好比如下场景:统计某个帖子的点赞数;存放某个话题的关注数;存放某个标签的粉丝数;存储一个大致的评论数;某个帖子热度;红点消息数;点赞、喜欢、收藏数等。
> INCRBY feed:e3kk38j4kl:like 1 > INCRBY feed:e3kk38j4kl:like 1 > GET feed:e3kk38j4kl:like "2"
像微博这样容易出现热点的业务,传统的数据库,确定是撑不住的,就要借助于内存数据库。因为Redis的速度很是快,就不用再采用传统DB很是慢的count
操做,全部这种递增操做都是毫秒级别的,并且效果都是实时的。
排行榜能提升参与者的积极性,因此这项业务很是常见,它本质上是一个topn的问题。
Redis中有一个叫作zset的数据结构,使用跳表实现的有序列表,能够很容易实现排行榜一类的问题。当存入zset中的数据,达到千万甚至是亿的级别,依然可以保持很是高的并发读写,且拥有很是棒的平均响应时间(5ms之内)。
使用zadd
能够添加新的记录,咱们会使用排行相关的分数,做为记录的score值,而后使用zrevrange
指令便可获取实时的排行榜数据,而zrevrank
则能够很是容易的获取用户的实时排名。
>ZADD sorted:xjjdog:2021-07 55 dog0 >ZADD sorted:xjjdog:2021-07 89 dog1 >ZADD sorted:xjjdog:2021-07 32 dog2 >ZCARD sorted:xjjdog:2021-07 >3 > ZREVRANGE sorted:xjjdog:2021-07 0 -10 WITHSCORES # top10排行榜 1) "dog1" 2) "89" 3) "dog0" 4) "55" 5) "dog2" 6) "32"
set
结构,是一个没有重复数据的集合,你能够将某个用户的关注列表、粉丝列表、双向关注列表、黑名单、点赞列表等,使用独立的zset进行存储。
使用ZADD
、ZRANK
等,将用户的黑名单使用ZADD添加,ZRANK使用返回的sorce值判断是否存在黑名单中。使用sinter
指令,能够获取A和B的共同好友。
除了好友关系,有着明确黑名单、白名单业务场景的数据,均可以使用set结构进行存储。这种业务场景还有不少,好比某个用户上传的通信录,计算通信录的好友关系等等。
在实际使用中,使用zset存储这类关系的更多一些。zset同set同样,都不容许有重复值,但zset多了一个score字段,咱们能够存储一个时间戳,用来标明关系创建所发生的时间,有更明确的业务含义。
相似统计天天的活跃用户、用户签到、用户在线状态,这种零散的需求,实在是太多了。若是为每个用户存储一个bool变量,那占用的空间就太多了。这种状况下,咱们可使用bitmap
结构,来节省大量的存储空间。
>SETBIT online:2021-07-23 3876520333 1 >SETBIT online:2021-07-24 3876520333 1 >GETBIT online:2021-07-23 3876520333 1 >BITOP AND active online:2021-07-23 online:2021-07-24 >GETBIT active 3876520333 1 >DEBUG OBJECT online:2021-07-23 Value at:0x7fdfde438bf0 refcount:1 encoding:raw serializedlength:5506446 lru:16410558 lru_seconds_idle:5 (0.96s)
注意,若是你的id很大,你须要先进行一次预处理,不然它会占用很是多的内存。
bitmap包含一串连续的2进制数字,使用1bit来表示真假问题。在bitmap上,可使用and、or、xor等位操做(bitop
)。
Redis的分布式锁,是一种轻量级的解决方案。虽然它的可靠性比不上Zookeeper之类的系统,但Redis分布式锁有着极高的吞吐量。
一个最简陋的加锁动做,可使用redis带nx和px参数的set指令去完成。下面是一小段简单的分布式样例代码。
public String lock(String key, int timeOutSecond) { for (; ; ) { String stamp = String.valueOf(System.nanoTime()); boolean exist = redisTemplate.opsForValue().setIfAbsent(key, stamp, timeOutSecond, TimeUnit.SECONDS); if (exist) { return stamp; } } } public void unlock(String key, String stamp) { redisTemplate.execute(script, Arrays.asList(key), stamp); }
删除操做的lua为。
local stamp = ARGV[1] local key = KEYS[1] local current = redis.call("GET",key) if stamp == current then redis.call("DEL",key) return "OK" end
redisson的RedLock,是使用最广泛的分布式锁解决方案,有读写锁的差异,并处理了多redis实例状况下的异常问题。
使用计数器去实现简单的限流,在Redis中是很是方便的,只须要使用incr配合expire指令便可。
incr key expire key 1
这种简单的实现,一般来讲不会有问题,但在流量比较大的状况下,在时间跨度上会有流量忽然飙升的风险。根本缘由,就是这种时间切分方式太固定了,没有相似滑动窗口这种平滑的过分方案。
一样是redisson的RRateLimiter,实现了与guava
中相似的分布式限流工具类,使用很是便捷。下面是一个简短的例子:
RRateLimiter limiter = redisson.getRateLimiter("xjjdogLimiter"); // 只须要初始化一次 // 每2秒钟5个许可 limiter.trySetRate(RateType.OVERALL, 5, 2, RateIntervalUnit.SECONDS); // 没有可用的许可,将一直阻塞 limiter.acquire(3);
redis能够实现简单的队列。在生产者端,使用LPUSH加入到某个列表中;在消费端,不断的使用RPOP指令取出这些数据,或者使用阻塞的BRPOP指令获取数据,适合小规模的抢购需求。
Redis还有PUB/SUB模式,不过pubsub更适合作消息广播之类的业务。
在Redis5.0中,增长了stream类型的数据结构。它比较相似于Kafka,有主题和消费组的概念,能够实现多播以及持久化,已经能知足大多数业务需求了。
早早在Redis3.2版本,就推出了GEO功能。经过GEOADD
指令追加lat、lng经纬数据,能够实现坐标之间的距离计算、包含关系计算、附近的人等功能。
关于GEO功能,最强大的开源方案是基于PostgreSQL的PostGIS,但对于通常规模的GEO服务,redis已经足够用了。
要看redis能干什么,就不得不提如下java的客户端类库redisson。redisson包含丰富的分布式数据结构,所有是基于redis进行设计的。
redisson提供了好比Set、 SetMultimap、 ScoredSortedSet、 SortedSet, Map、 ConcurrentMap、 List、 ListMultimap、 Queue、BlockingQueue等很是多的数据结构,使得基于redis的编程更加的方便。
对于某个语言来讲,基本的数组、链表、集合等api,配合起来可以完成大部分业务的开发。Redis也不例外,它拥有这些基本的api操做能力,一样可以组合成分布式的、线程安全的高并发应用。
因为Redis是基于内存的,因此它的速度很是快,咱们也会把它看成一个中间数据的存储地去使用。好比一些公用的配置,放到redis中进行分享,它就充当了一个配置中心的做用;好比把JWT的令牌存放到Redis中,就能够突破JWT的一些限制,作到安全登出。
一站式Redis面临的挑战
redis的数据结构丰富,通常不会在功能性上形成困扰。但随着请求量的增长,SLA要求的提升,咱们势必会对Redis进行一些改造和定制性开发。
redis提供了主从、哨兵、cluster等三种集群模式,其中cluster模式为目前大多数公司所采用的方式。
可是,redis的cluster模式,有很多的硬伤。redis cluster采用虚拟槽的概念,把全部的key映射到 0~16383个整数槽内,属于无中心化的架构。但它的维护成本较高,slave也不可以参与读取操做。
它的主要问题,在于一些批量操做的限制。因为key被hash到多台机器上,因此mget、hmset、sunion等操做就很是的不友好,常常发生性能问题。
redis的主从模式是最简单的模式,但没法作到自动failover,一般在主从切换后,还须要修改业务代码,这是不能忍受的。即便加上haproxy这样的负载均衡组件,复杂性也是很是高的。
哨兵模式在主从数量比较多的时候,可以显著的体现它的价值。一个哨兵集群,可以监控成百上千个集群,可是哨兵集群自己的维护是比较困难的。幸运的是,redis的文本协议很是简单,在netty中,甚至直接提供了redis的codec。自研一套哨兵系统,增强它的功能,是可行的。
redis的特色是,无论什么数据,都一股脑地搞到内存里作计算,这对于有时间序列概念,有冷热数据之分的业务,形成了很是大的成本考验。为何大多数开发者喜欢把数据存放在MySQL中,而不是Redis中?除了事务性要求之外,很大缘由是历史数据的问题。
一般,这种冷热数据的切换,是由中间件完成的。咱们上面也谈到了,Redis是一个文本协议,很是简单。作一个中间件,或者作一个协议兼容的Redis模拟存储,是比较容易的。
好比咱们Redis中,只保留最近一年的活跃用户。一个好几年不活跃的用户,忽然间访问了系统,这时候咱们获取数据的时候,就须要中间件进行转换,从容量更大,速度更慢的存储中查找。
这个时候,Redis的做用,更像是一个热库,更像是一个传统cache层作的事情,发生在业务已经上规模的时候。可是注意,直到此时,咱们的业务层代码,一直都是操做的redis的api。它们使用这众多的函数指令,并不关心数据究竟是真正存储在redis中,仍是在ssdb中。
redis还能玩不少花样。举个例子,全文搜索。不少人都会首选es,但redis生态就提供了一个模块:RediSearch,能够作查询,能够作filter。
但咱们一般还会有更多的需求,好比统计类、搜索类、运营效果分析等。这类需求与大数据相关,即便是传统的DB也不能胜任。这时候,咱们固然要把redis中的数据,导入到其余平台进行计算啦。
若是你选择的是redis数据库,那么dba打交道的,就是rdb,而不是binlog。有不少的rdb解析工具(好比redis-rdb-tools),可以按期把rdb解析成记录,导入到hadoop等其余平台。
此时,rdb成为全部团队的中枢,成为基本的数据交换格式。导入到其余db后的业务,该怎么玩怎么玩,彻底不会由于业务系统选用了redis就没法运转。
大多数业务系统,跑在redis上,这是不少一直使用MySQL作业务系统的同窗所不能想象的。看完了上面的介绍,相信你可以对redis可以实现的存储功能有个大致的了解。打开你的社交app、游戏app、视频app,看一下它们的功能,可以涵盖多少呢?
我这里要强调的是,某些数据,并非必定要落地到RDBMS才算安全,它们并非一个强需求。
那既然redis这么厉害,为何还要有mysql、tidb这样的存储呢?关键还在于业务属性上。
若是一个业务系统,每次交互的数据,都是一个很是大的结果集,并涉及到很是复杂的统计、过滤工做,那么RDBMS是必须的;但若是一个系统,可以经过某个标识,快速定位到一类数据,这一类数据在能够预见的将来,是有限的,那就很是适合Redis存储
。
一个电商系统,选用redis作存储就是做死,但一个社交系统就快活的多。在合适的场景选用合适的工具,才是咱们应该作的。
可是一个系统,可否在产品验证期,就能快速的响应变化,快速开发上线,才是成功的关键。这也是使用redis作数据库,所可以带来的最大好处。千万别被那几率极低的丢数据场景,给吓怕了。比起产品成功,你的系统即便是牢如钢铁,也一文不值。