• [原]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没有数据,会清空自身的数据,很危险!)
  • 相关阅读:
    Spring Cloud Feign服务之间的调用
    jooq映射原理_JOOQ事实:从JPA批注到JOOQ表映射
    ORM优缺点详解,已经JOOQ、MyBatis介绍
    JOOQ 踩坑和评价
    ORM框架简介及优缺点
    在linux中查看jdk的版本以及安装路径
    青春未逝,热望长存—OI生涯回忆录
    耻辱柱
    浅谈如何应对考场上可能出现的烦人小学生
    RP测试器
  • 原文地址:https://www.cnblogs.com/titanjf/p/redis.html
Copyright © 2020-2023  润新知