• java面试总结之二


    前言

      上一篇简单说了一下面试过程中关于java基础相关的知识,其实作为一个互联网行业的程序猿,必备的技能不仅仅是java,这里我们简单说说redis。

    1、redis的作用,使用场景等。

      通常我们使用redis作为缓存,包括top n等功能,支持的数据类型包括string、hash、set、list、sorted set五种类型。对于基本常用的命令,面试有可能会一些基础的,比如setex、incrby等等,这里不再累述。主要的问题是为什么使用redis而不用memcached,redis的优势是什么之类的。首先你要非常熟悉redis并且了解memcached,至少知道两者的区别(memcached使用的相对redis较少,如果两者都会自然更好)。

      1)数据类型,memcached只支持key-value这种单一的字符串类型数据,而redis支持上述的五种类型,可以更好的满足用户的数据格式需求。(说到这里不要停,要继续举例说明,让面试官觉得你有实际应用的经验)举例:对于对象的存储,如果使用memcached,必须对对象进行序列化然后再存储,获取数据时再反序列化转成对象,而redis的hash类型可以根据field的不同直接存储对象;redis的list可以实现两端的push和pop,这样就可以作为队列和栈来使用。

      2)网络IO模型,memcached采用多线程,分为监听主线程和worker子线程,监听线程监听网络连接,接受请求后,worker子线程进行读写IO。redis是单线程,单线程的好处就是不需要来回切换上下文,将线程的效率发挥到最大,同时也能保证请求按照顺序被执行。

      3)持久化支持,memcached不支持内存数据的持久化,redis的数据持久化分为两种,快照和追加文件。下面会详细说明。

      4)其他,memcached支持cas命令,可以实现多并发访问操作同一数据的一致性,redis不支持cas命令,但提供了事务的功能,可以保证一串命令的原子性。

    2、redis持久化

      1)Snapshotting(快照)
      默认的持久化方式,通过save n m配置实现在n秒内,keys至少发生m次修改则自动做快照持久化操作,将内存数据自动持久化到默认的dump.rdp文件(压缩的二进制文件)中,可以配置多条,只要满足一个即可自动执行持久化操作(通过lastsave和dirty两个属性来控制,lastsave为上一次持久化的时间戳,dirty为期间发生变化keys的个数)。除了配置之外,也可以调用save或bgsave命令执行快照持久化,save使用主进程执行持久化,会阻塞client的请求,不推荐使用,bgsave会fork子进程进行持久化。redis通过fork子进程,父进程继续接受client端的请求,子进程通过循环内存数据副本,将数据写入临时文件,当子进程写完数据之后,原子性的rename临时文件为dump.rdb文件,替换旧的rdb文件。
      copy-on-write,在复制对象的时候并不是真正的复制对象,而是在新对象上创建一个指向源对象的指针。当父进程读取数据时,通过指针直接读取,当父进程写数据时,创建新的数据副本,而不是在源对象上直接修改,如此父进程可以继续读写数据,子进程持久化的数据是快照时刻的数据。
      优点:
        a)存储所有数据数据的快照文件,恢复数据方便(可以使用redis-check-dump修复dump文件)
      缺点:
        a)快照是将全部内存数据持久化,当数据量较大时,会产生大量的IO操作
        b)快照只是持久化某一时刻的数据,当redis宕机时,会丢失上一次快照持久化之后的内存数据
      2)Append-only-file
      通过配置redis.conf里的appendonly yes来开启,将client的每条写命令通过write函数追加写入默认的appendonly.aof日志文件中,当redis重启时通过重新执行aof日志文件中的命令来恢复数据。由于os会在内核的缓存中做write操作的缓存,所以可以通过配置告诉redis强制写入磁盘的方式,redis使用fsync函数写入磁盘的配置方式有三种,
        a)appendfsync always #每次的写命令都写入磁盘,持久化效果最好,速度最慢,不推荐使用
        b)appendfsync everysec #默认的,每一秒写入磁盘一次,推荐使用
        c)appendfsync no #完全依赖os,性能最好,持久化效果没保证
      由于追加写入aof日志文件,所以aof日志文件会越来越大,可以使用redis提供的bgrewriteaof命令来重写aof日志文件,具体如下:
        a)父进程fork出一个子进程
        b)子进程根据内存中的数据,向临时文件写入重建数据库状态的命令(对一个key的多次写命令,只记录最后一次数据状态的结果,既能保证数据的准确,又能达到压缩aof日志文件的效果)
        c)父进程继续接收client端的请求,将写命令追加写入aof日志文件,并缓存写命令
        d)当子进程将内存数据以命令的方式写入临时文件之后,会通知父进程将缓存的写命令也写入临时文件
        e)父进程重命名临时文件,替换旧的aof日志文件
      优点:
        a)数据安全,持久化性能好
      缺点:
        a)性能较低
        b)重写日志文件频繁影响性能
    3、redis事务(了解最好,更多的采用分布式锁) 

      1)命令
        a)multi,开始事务,并将该命令之后的操作命令放入队列中,该命令总是返回OK
        b)exec,执行事务,将队列中的命令原子性的执行,返回每个命令的执行结果
        c)watch key1 [key2...],为多个键添加监控,在执行exec命令之前,如果监控的命令至少一个发生变化,事务就会执行失败,exec命令会返回null,该命令总是返回OK。注:如果受监控的key在执行exec命令之前失效了,不会对事务造成影响,同一个客户端对监控对象的修改不会影响事务
        d)discard,取消事务,清除队列中的命令,如果使用了watch命令,同时取消监控,该命令总是返回OK
        e)unwatch,取消键的监控,如果调用了exec或者discard命令则无需调用此命令,该命令总是返回OK
      2)使用方法

      在调用multi命令开始事务之后,所有要被执行的命令将被放入队列中,放入过程中redis会对命令进行语法校验检查,如果正确则返回字符串QUEUED,否则返回错误信息。在redis2.6.5之前,错误的命令不会影响exec命令的执行,事务会将队列中正确的命令执行,从而造成数据的错误,需要进行校验,2.6.5版本及之后,对于队列中的错误命令,redis将取消事务,exec命令返回错误信息。redis只能检查队列中命令的语法错误,如果在执行过程中出现错误,不会终止事务,只是返回该命令的错误信息,也不会对事务进行回滚,redis不支持事务回滚
      3)代码示例

    1    watch a b c//监控a、b、c三个键,如果在执行exec命令之前,三个键中的一个或多个发生变化,则事务执行失败
    2   val = get a//同一客户端对监控键操作不会影响事务
    3   val = val + 1
    4   set a val
    5   multi//开始事务
    6   set b $val + 2//将命令放入队列中
    7   set c $val * 3
    8   exec//执行事务

      4)通过cas操作实现乐观锁,redis通过使用watch命令实现事务的“检查再设置”(cas)行为。如果存在竞争状态,在watch和exec命令之间,有其他客户端修改了监控键的值,则事务运行失败。可以通过重复执行监控—执行事务的代码直到不再出现竞争,称为乐观锁。
      5)redis脚本也是事务型的,可以通过执行redis脚本来实现事务,而且redis脚本更快、更简单。

    4、redis回收key策略

      1)被动删除
      每次访问key时,判断是否过期,如果过期则删除,会减少CPU的压力,但是会增加内存的压力,造成已失效的数据依然在内存中,甚至对于一些永远不会被访问的过期key,将会造成所谓的内存泄漏。
      2)主动删除
      对于一个持续运行的服务器来说,服务器需要定期的对自身的状态和资源进行必要的检查和整理,从而让服务器保持在一个健康稳定的状态,这类操作统称为常规操作。
    在redis中,常规操作由redis.c/serverCron来实现,主要执行以下操作:
        a)更新服务器的各类统计信息,比如时间、内存占用、数据库占用情况等。
        b)清理数据库中的过期键值对。
        c)对不合理的数据库进行大小调整。
        d)关闭和清理连接失效的客户端。
        e)尝试进行 AOF 或 RDB 持久化操作。
        f)如果服务器是主节点的话,对附属节点进行定期同步。
        g)如果处于集群模式的话,对集群进行定期同步和连接测试。
      在redis2.6版本中,serverCron每秒运行10次,平均每100毫秒运行一次,redis2.8版本之后,可以设置hz属性来控制每秒serverCron运行的次数,每次运行都会有一个时间限制,在限制时间内尽可能多的删除失效的key,过长的时间会影响redis的正常运行。时间限制和hz成反比的关系,hz值越大,时间限制越小。当redis使用主从配置时,只有主节点才会执行上面两种过期删除策略,然后通过del key指令同步到从节点
      3)maxmemory
      当已用内存超过maxmemory时,将会触发主动清除策略,清除策略如下:
        a)volatile-lru,对于设置失效时间的key,采用lru算法清除(默认)
        b)volatile-ttl,对于设置失效时间的key,根据剩余生存时间清除
        c)volatile-random,对于设置失效时间的ky,随机清除
        d)allkeys-lru,对于所有的key,采用lru算法清除
        e)allkeys-random,对于所有的key,随机清除
        f)noeviction,不清除内存,返回oom异常
      当已用的内存超过maxmemory时,所有的读写请求都会触发主动清除,清理过程是阻塞的。redis并不会准确的计算最近最少使用的key,而是根据配置maxmemory-samples n来随机选择n个key,将其中最近最少使用的删除掉
    5、redis一致性hash和分布式锁
      这里你需要知道一致性hash算法以及分布式锁的机制,另外SharedJedis是支持一致性hash的,这里有两篇文章可以参考。
    6、redis数据结构
      说一下面试过程中遇到的一个问题,redis存储了两个长度不一样的string类型数据,问获取这两个字符串长度需要的时间一样么?注意不是时间复杂度!
      其实如果你了解redis的数据结构就可以告诉他,是的,时间是一样的(当然,这里不考虑其他客观因素),string类型的结构如下:
    1 struct sdshdr {
    2 
    3     int len; //len表示buf中存储的字符串的长度。
    4 
    5     int free; //free表示buf中空闲空间的长度。
    6 
    7     char buf[]; //buf用于存储字符串内容。
    8 };

       从上面的结构可以很容易的看出,redis会为每个字符串计算出长度并存储。关于redis的介绍大概就这么多了,还有后续哦,敬请期待!

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

    原文链接:http://www.cnblogs.com/1ning/p/6700352.html

  • 相关阅读:
    OpenGL红宝书例3.1 -- glBufferSubData使用
    JNI调用问题(部分机型崩溃)
    文件编码转换工具
    vs2013安装visual assist和viemu之后提示功能等无效解决
    cocos2d-lua SDK接入
    OpenGL中shader读取实现
    CURL C++网络延时或者最低网速下载设置
    Spring框架
    软件安装方式
    Web前端常见问题
  • 原文地址:https://www.cnblogs.com/1ning/p/6700352.html
Copyright © 2020-2023  润新知