Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多变的数据结构和数据操作,为不同的大象构建不同的冰箱。
Redis常用数据类型
Redis最为常用的数据类型:
1.String 2.Hash 3.List 4.Set 5.Sorted set
1、String
常用命令: set,get,decr,incr,mget 等。
应用场景:String类型是最简单也是最常用的redis数据结构,key/value格式完全可以取代Memcached作为缓存服务器,单机的测试效果显示redis的效果更好。
set、get:最简单的数据缓存
mset、mget: 批量操作,把数据统一传回客户端,节省网络io时间
decr、incr:计数器
append命令:可以作为时间序列,配合getrange、setrange,对字符串进行操作,目前redis还木有修剪操作
setbit、getbit: 省内存的好命令,可以作为简单的布尔过滤器来判断用户是否执行过某些操作
2、List
常用命令:lpush,rpush,lpop,rpop,lrange等。
应用场景:List的应用场景很多,应用也相当广泛
lpush、lpop:天然的队列操作,轻松实现队列任务,Celery的存储容器我们选的就是redis
lpush、ltrim: 显示最新的数据,很好用的!比如:游戏上方的跑马灯,就可以用这两个命令来存储最新的50条记录
还有一些其他操作:堵塞式的blpop, lrange(O(n)), lindex(O(n)),linsert(O(n)), llen(O(1)),lrem(O(n)),lset(O(n))
3、Hash
常用命令:hget,hset,hgetall 等。
应用场景:以前在memcached中如果保存一个大的数据,经常用序列化之后保存,取出来反序列化后使用,即不经济实惠,在高并发下还存在原子性问题,在redis中, 用哈希实现,so easy啦!
hget、hset: 实现一个key对应一个数据集集合,数据集集合里包含多个单独的key/value键值对,操作依然是原子性的
hmget、hmset、hgetall: 批量操作,节省网络io时间哦
hincrby: 对哈希里域值,进行原子性的加1
其他操作: hdel(O(n))、 hkeys(O(n))、hexits(O(1))、hvals(O(n))、hscan(O(n))
4、Set
常用命令:
sadd,spop,smembers,sunion 等。
应用场景:set与list类似,只是set是经过去重的集合,需要一个不重复的数据结构,就要考虑考虑set
sadd: 存储一个不重复数据的数据集合
sunion、sdiff、sinter: 进行集合处理,例如微博中,将一个用户关注的所有人放入set集合中,通过并集、交集、差集操作,实现`共同关注`、`共同喜好`、`二度好友`等功能
其他操作:srem、spop、scard、sismember、smove、srandmember
5、Sorted Set
常用命令:
zadd,zrange,zrem,zcard等
应用场景:set是无序的,而Sorted set 顾名思义,它是有序的,由key、member和score组成,需要一个有序而且不重复的数据结构,就要考虑考虑sorted set
zadd:存储一个按照score排序的数据集合,添加时自动排序,例如:优先队列,普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
zrange、zrangebyscore等等:按照score顺序获取数据集,例如:微博的时间流信息,把发布时间作为score。还可以用来处理过期数据。后台任务使用ZRANGE…SCORES查询排序集合,取出最新的10个项目。如果发现unix时间已经过期,则在数据库中删除条目。
zrank: 排行榜功能,score作为投票结果
其他操作:zcard、zcount、zincrby、zrem、zscore,以及set的集合操作
Redis 的 5 个常见使用场景
1、会话缓存(Session Cache)
最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?
幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。
2、全页缓存(FPC)
除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。
再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。
此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
3、队列
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。
排行榜/计数器
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。
Redis实际应用场景
Redis在很多方面与其他数据库解决方案不同:它使用内存提供主存储支持,而仅使用硬盘做持久性的存储;它的数据模型非常独特,用的是单线程。另一个大区别在于,你可以在开发环境中使用Redis的功能,但却不需要转到Redis。
转向Redis当然也是可取的,许多开发者从一开始就把Redis作为首选数据库;但设想如果你的开发环境已经搭建好,应用已经在上面运行了,那么更换数据库框架显然不那么容易。另外在一些需要大容量数据集的应用,Redis也并不适合,因为它的数据集不会超过系统可用的内存。所以如果你有大数据应用,而且主要是读取访问模式,那么Redis并不是正确的选择。
然而我喜欢Redis的一点就是你可以把它融入到你的系统中来,这就能够解决很多问题,比如那些你现有的数据库处理起来感到缓慢的任务。这些你就可以通过Redis来进行优化,或者为应用创建些新的功能。在本文中,我就想探讨一些怎样将Redis加入到现有的环境中,并利用它的原语命令等功能来解决 传统环境中碰到的一些常见问题。在这些例子中,Redis都不是作为首选数据库。
Redis使用经验
一、Redis部署
本司由于平时的数据量不大,一直使用的三台redis实例,一主两从,三个哨兵sentinel分别监视三个redis实例作为高可用性的保障,这次活动的预估参与人数以及请求并发量很大,所以为了保证高可用性,在部署时做了一下变更方案:
更换SSD硬盘,并增加内存至128G (简单粗暴,效果显著)
双机房两组Redis实例,一组对外服务,另一组作为热备份,不提供服务并定时备份,服务中的实例出现故障,立即切换备份实例为服务实例
OS参数:vm.over_commit_memory配置 默认为0,改为1
Redis的快照、AOF文件重写、主备同步等功 能都依赖于fork系统调用,以快照(bgsave/save)为例,Redis会fork一个子进程出来,由子进程来将当前的数据存储为一个RDB文件。
vm.over_commit_memory会影响到内存分配,其值可以是:
0: 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
1: 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
2: 表示内核允许分配超过所有物理内存和交换空间总和的内存。
vm.over_commit_memory默认值为0,在该配置下,当Redis执行fork时,服务器可用内存必须大于Redis当前使用内存2倍时,fork才能成功。
而实际上Linux在fork时,使用COW(copy-on-write)技术,子进程共享父进程的地址空间,只有共享地址空间发生改变时,才需要复制改变部分的内存;大部分情况下,fork子进程并不会导致内存使用翻倍,为fork预留一倍的内存是完全没有必要的,Redis启动时也会建议使用者将vm.over_commit_memory设置为1。
OS参数:vm.swapiness设置为0
Redis是全内存的KV数据库,当服务器内存不足时,OS的swap机制可能会把Redis的部分数据换出到磁盘,访问Redis时,如果被访问的数据刚好不在内存里,则会产生缺页中断,从磁盘读取数据,这种行为会极大的影响Redis服务的性能。
为保证Redis服务的性能,应该尽量避免发生swap,将vm.swapiness设置为0(该参数可在0-100之间取值,默认为60,越高使用swap空间的可能性越大)。
OS参数:transparent hugepage
Linux-2.6.38内核引入透明大内存页的支持,这个参数对Redis有利有弊。
好处在于:大内存页意味着更小的页表,fork的开销会降低不少
坏处在于:大内存页,意味着页被修改的几率更大,COW时拷贝成本更高
二、压测过程中遇到的问题及解决方案
保证服务可靠性,对主要单接口压测,以及复合场景下的多接口压测,观察接口的响应时间以及服务的吞吐量,还有对突发情况下的Redis机器故障转移,如机房断电,主redis挂掉等情况。
问题:
业务逻辑复杂,频繁访问redis,接口响应时间有提升空间
线上要清空一个db的数据,执行flushdb后,数据依然存在(原因:该db内数据量大,清空操作超过3秒,sentinel发现3秒内,master无正确响应,就将slave拉起作为master,从进行同步,陷入循环。。。)
Redis目前的主从同步机制,主从连接断开后,如果从落后的数据超过1M(可配置,越大内存开销越大),则需要重新全量同步一次,一次全量同步会产生极大的内存、磁盘、CPU及网络开销。主从全量同步过程中,如果写入比较多,主从同步缓冲区就会不断累积写入的数据,当累积的数据超出限制时,主从连接就会断开,此时从又必须重新向主请求全量同步,如此往复... 导致同步一直不能成功
数据量很大时,redis的bgsave操作,虽然是子进程操作,但是也会阻塞redis,导致超时
方案:
mysql读写分离,并分库,分表
redis数据分片,把重要、不重要的数据分开到两个redis实例分别存放
代码中减少redis网络IO请求,多用mget、hmget、pipeline等一次性读多条数据命令
代码中考虑用更合适的redis数据结构以及时间复杂度少的命令。 Instagram公司优化Redis使用内存案例
设计数据结构时,尽量减小单个key的value大小,如果单例redis中数据量过大,故障恢复或者同步过程会很慢。单例redis最大使用内存一定要小于机器最大内存的一半。
清空一个数据量很大的db前,先调大sentinel的自动故障转移时间,清空后再设置回来。
适当扩大client-output-buffer-limit slave 256mb 64mb 60 的配置值
一定要设置限流,运维层限制一道,代码层限制一道,保证整体服务器不宕机。
备选方案:
复杂的redis操作,可以考虑用短小的lua脚本执行,用eval和evalsha命令执行(优点:充分利用CPU,减少网络IO时间。缺点:不方便维护,要保证脚本不阻塞)
传统的一主多从A--B&C 当把B拉起为master时,C仍然会清空自身数据来同步B,psync的断点续传对此无作用,所以考虑改为变为A--B--C同步结构
redis集群(时间紧,考虑但是暂时不会采用)
采用主不开持久化,从开持久化,不会出现数据量大,bgsave导致的连接超时情况(缺点:A--B&C模式下,A故障并自动拉起或者由sentinel把B被拉起为master,中间的心跳检测过程中,还是会有一个间隔,导致从同步主,发现A没有数据,会清空自身的数据,很危险!)