[原]Redis使用场景及使用经验

   Redis is an open source (BSD licensed), in-memory data structure store!

  欢迎转载,转载请注明出处

  刚刚结束一个游戏类的活动项目,由于预估的参与人数较多,产生的数据量会很大,为了达到更好的游戏效果,所以决定抛弃之前Mysql为主Redis为辅的存储方式,而是Redis为主,Mysql为辅(负责落地一些重要的用户数据),期间收货了很多。

  一、Redis中的数据结构以及常见的使用场景

  常用的Redis数据结构:

  • String  # 键值对
  • List      # 列表
  • Hash    # 哈希
  • Set      # 集合
  • 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命令参考中文版  英文版

 

二、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的配置文件, 内容如下:(配置文件详细介绍请点这里

daemonize yes
pidfile "./redis.pid"
port 6379
timeout 0
tcp-keepalive 60
loglevel notice
logfile "./redis.log"
databases 16
save 900 1
save 300 10
save 60 10000
tcp-backlog 511
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename "redis.rdb"
dir "./redis"
slave-serve-stale-data yes
slave-read-only yes
repl-disable-tcp-nodelay no
slave-priority 100
maxclients 20000
maxmemory 60gb
maxmemory-policy noeviction
appendonly no  # 可以忍受一小段时间内的数据丢失,所以关闭了aof持久化
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
hash-max-ziplist-entries 512
hash-max-ziplist-value 512
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
# Generated by CONFIG REWRITE  # 由sentinel自动故障转移生成的
#slaveof 10.181.60.113 6382
slaveof 10.181.254.157 6379

 

 

二、压测过程中遇到的问题及解决方案

   保证服务可靠性,对主要单接口压测,以及复合场景下的多接口压测,观察接口的响应时间以及服务的吞吐量,还有对突发情况下的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没有数据,会清空自身的数据,很危险!)

 

posted @ 2015-10-08 16:34  titanjf  阅读(3450)  评论(1编辑  收藏  举报