每天十道面试题-20200406


每天十道面试题-20200406

        • 题目
        • 解答
            • 题目一
            • 题目二
            • 题目三
            • 题目四
            • 题目五
            • 题目六
            • 题目七
            • 题目八
            • 题目九
            • 题目十

题目

  • 1、Jedis 与 Redisson 对比有什么优缺点?
  • 2、Redis分布式锁?
  • 3、MySQL 和 MongoDB 的区别?
  • 4、负载均衡的实现,有没有使用过nginx?
  • 5、为什么数据库使用索引查询速度会那么快,是怎样实现的?
  • 6、除了mysql这种关系型数据库外,还有哪些数据库?
  • 7、TCP和UDP的区别?
  • 8、一次HTTP的完整请求过程(从网络协议的层面来阐述)?
  • 9、面向对象语言的特点?
  • 10、hashmap和hashtable的区别?

解答

题目一
  • 题干:Jedis 与 Redisson 对比有什么优缺点?
  • 分析:
  • Redisson
    优点:
    实现了分布式特性和可扩展的 Java 数据结构,适合分布式开发
    API 线程安全
    基于 Netty 框架的事件驱动的通信,可异步调用
    缺点:
    API 更抽象,学习使用成本高
    Jedis
    优点:
    提供了比较全面的 Redis 操作特性的 API
    API 基本与 Redis 的指令一一对应,使用简单易理解
    缺点:
    同步阻塞 IO
    不支持异步
    线程不安全

  • 回答:
  • 见分析。

题目二
  • 题干:Redis分布式锁?
  • 分析:
  • 分布式锁三种实现方式
    1.基于数据库实现分布式锁;
    2.基于缓存(Redis等)实现分布式锁;
    3.基于Zookeeper实现分布式锁;
    数据库实现
    1.悲观锁
    利用select … where … for update 排他锁
    注意: 其他附加功能与实现一基本一致,这里需要注意的是“where name=lock ”,name字段必须要走索引,否则会锁表。有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题。
    2.乐观锁
    所谓乐观锁与前边最大区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update version失败后才能觉察到。我们的抢购、秒杀就是用了这种实现以防止超卖。
    通过增加递增的版本号字段实现乐观锁
    缓存Redis
    1.使用命令介绍:
    (1)SETNX
    SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
    (2)expire
    expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
    (3)delete
    delete key:删除key
    在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。
    2.实现思想:
    (1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
    (2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
    (3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
    Zookeeper
    ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
    (1)创建一个目录mylock;
    (2)线程A想获取锁就在mylock目录下创建临时顺序节点;
    (3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
    (4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
    (5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
    这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。
    优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。
    缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。
    数据库分布式锁实现
    缺点:
    1.db操作性能较差,并且有锁表的风险
    2.非阻塞操作失败后,需要轮询,占用cpu资源;
    3.长时间不commit或者长时间轮询,可能会占用较多连接资源
    Redis(缓存)分布式锁实现
    缺点:
    1.锁删除失败 过期时间不好控制
    2.非阻塞,操作失败后,需要轮询,占用cpu资源;
    ZK分布式锁实现
    缺点:性能不如redis实现,主要原因是写操作(获取锁释放锁)都需要在Leader上执行,然后同步到follower。
    总之:ZooKeeper有较好的性能和可靠性。
    从理解的难易程度角度(从低到高)数据库 > 缓存 > Zookeeper
    从实现的复杂性角度(从低到高)Zookeeper >= 缓存 > 数据库
    从性能角度(从高到低)缓存 > Zookeeper >= 数据库
    从可靠性角度(从高到低)Zookeeper > 缓存 > 数据库

  • 回答:
  • 见分析。

题目三
  • 题干:MySQL 和 MongoDB 的区别?
  • 分析:
  • 一、MySQL是一种关系型数据库存储模式取决于存储引擎【使用InnoDB存储引擎的居多一般持久化到磁盘】使用SQL查询,架构特点是单点集群、MHA,开源。
    优势:
    在不同的引擎上有不同 的存储方式。
    查询语句是使用传统的sql语句,拥有较为成熟的体系,成熟度很高。
    开源数据库的份额在不断增加,mysql的份额页在持续增长。
    缺点:
    在海量数据处理的时候效率会显著变慢。
    二、MongoDB
    非关系型数据库,存储模式【虚拟内存+持久化】,自有的查询模式,通过副本集和分片。基于内存,将热数据存在物理内存中,
    优点:
    快速!在适量级的内存的Mongodb的性能是非常迅速的,它将热数据存储在物理内存中,使得热数据的读写变得十分快。高扩展性,存储的数据格式是json格式!
    缺点:
    不支持事务,而且开发文档不是很完全,完善。
    Mysql和Mongodb主要应用场景

  • 回答:
  • 见分析。

题目四
  • 题干:负载均衡的实现,有没有使用过nginx?
  • 分析:
  • 一般用nginx作为反向代理,nginx 主进程监听一个端口,当请求过来的时候,用worker进程去争夺锁,然后进行请求的解析处理相应,nginx在启动后以daemon的方式在后台运行,会有一个master进程和多个worker进程。
    master进程:主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。
    worker进程:处理基本的网络事件了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,或者直接设置参数worker_processes auto;
    Nginx是由一个俄罗斯人专门为解决高并发而开发的
    nginx 采用的是多进程+epoll,能实现高并发,其可以支持的并发上限大概是同时支持5W个连接
    1 多进程
    nginx 在启动后,会有一个 master 进程和多个相互独立的 worker 进程,master进程接收来自外界的连接,并向各worker进程发送信号,每个进程都有可能来处理这个连 接,master进程能监控worker进程的运行状态,当 worker 进程退出后(异常情况下),会自动启动新的worker进程
    【惊群现象】
    master进程首先通过 socket() 来创建一个socket文件描述符用来监听,然后fork生成子进程(workers 进程),那么当连接进来时,所有子进程都将收到master进程的 通知并“争着”与它建立连接,这就叫“惊群现象”。大量的进程被激活又挂起,只有一个进程可以accept() 到这个连接,这当然会消耗系统资源
    【nginx对惊群现象的处理】
    nginx 提供了一个 accept_mutex 这个东西,即每个 worker 进程在执行accept之前都需要先获取锁,获取不到就放弃执行accept()。有了这把锁之后,同一时刻,就只会 有一个进程去accpet(),这样就不会有惊群问题了
    2 IO多路复用
    IO多路复用 :每个线程或者进程同时处理多个连接
    IO多路复用的三个阶段
    第1阶段 selector方式,使用fd_set结构体告诉内核去监控哪些文件句柄,采用遍历的方式检查是否有文件句柄就绪,然后再通知应用程序。他的缺点是文件句柄有上 限限制,并且效率不高,采用的是遍历方式
    第2阶段 poll方式,采用新的数据结构消取了文件句柄上限,但是还是采用遍历的方式检查文件句柄是否就绪
    第3阶段 epoll方式,通过epoll_ctl注册文件句柄,一旦该文件句柄就绪,epoll_wait便可以收到通知, 并通知应用程序进行处理,不用主动去遍历检查文件句柄是否就绪

  • 回答:
  • 见分析。

题目五
  • 题干:为什么数据库使用索引查询速度会那么快,是怎样实现的?
  • 分析:
  • 首先要这么说,索引的确会提升查询速度,具体实现取决于数据库底层的引擎,我们这里只说使用InnoDB引擎的Mysql数据库,对于InnoDB引擎底层索引的实现是使用的B+ tree,是平衡N路查找树B-Tree的一种变种,最多只需要四层的B+tree就能够满足存储上亿的数据,一般表的索引是超不过四层的,这样就节省了搜索的路径树的深度最大为4且都是4,而且B+ tree的结构对于叶子结点是存放真实数据的地方,使用双向链表按照主键递增顺序存储,而在单个页内使用单向链表按照主键自增存储。InnoDB做到了索引即数据,数据即索引,叶子结点存放真实数据,非叶子节点包括根节点存放目录页,每个页的大小都是16KB 从磁盘加载到名叫Buffer Pool 的 内存中,整体结构便于定位到目标数据所在的页,而在页内根据页的结构,使用二分查找根据槽定位到数据所在的组,然后组内遍历找到具体的目标数据。

  • 回答:
  • 首先要这么说,索引会提升查询速度,具体实现取决于数据库底层的引擎,我们这里只说使用InnoDB引擎的Mysql数据库,对于InnoDB引擎底层索引的实现是使用的B+ tree,是平衡N路查找树B-Tree的一种变种,最多只需要四层的B+tree就能够满足存储上亿的数据,一般表的索引是超不过四层的,这样就节省了搜索的路径树的深度最大为4,而且B+ tree的结构对于叶子结点是存放真实数据的地方,使用双向链表按照主键递增顺序存储,而在单个页内使用单向链表按照主键自增存储。

题目六
  • 题干:除了mysql这种关系型数据库外,还有哪些数据库?
  • 分析:
  • 还有SQLSserver Oracle,NOSQL数据库

  • 回答:
  • 还有SQLSserver Oracle,NOSQL数据库

题目七
  • 题干:TCP和UDP的区别?
  • 分析:
  • 1、基于连接与无连接;
    2、对系统资源的要求(TCP较多,UDP少);
    3、UDP程序结构较简单;
    4、流模式与数据报模式 ;
    5、TCP保证数据正确性,UDP可能丢包;
    6、TCP保证数据顺序,UDP不保证。

  • 回答:
  • 1、基于连接与无连接;
    2、对系统资源的要求(TCP较多,UDP少);
    3、UDP程序结构较简单;
    4、流模式与数据报模式 ;
    5、TCP保证数据正确性,UDP可能丢包;
    6、TCP保证数据顺序,UDP不保证。

题目八
  • 题干:一次HTTP的完整请求过程(从网络协议的层面来阐述)?
  • 分析:
  • 参考第九题

  • 回答:
  • 参考第九题

题目九
  • 题干:面向对象语言的特点?
  • 分析:
  • 封装、继承、多态。

  • 回答:
  • 封装、继承、多态。其余的自己展开

题目十
  • 题干:hashmap和hashtable的区别?
  • 分析:
  • Hashtable是java一开始发布时就提供的键值映射的数据结构,而HashMap产生于JDK1.2。虽然Hashtable比HashMap出现的早一些,但是现在Hashtable基本上已经被弃用了。而HashMap已经成为应用最为广泛的一种数据类型了。造成这样的原因一方面是因为Hashtable是线程安全的,效率比较低。HashMap和Hashtable不仅作者不同,而且连父类也是不一样的。HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口Dictionary类是一个已经被废弃的类(见其源码中的注释)。父类都被废弃,自然而然也没人用它的子类Hashtable了。*NOTE: This class is obsolete. New implementations should
    *implement the Map interface, rather than extending this
    class.

    Hashtable比HashMap多提供了elments() 和contains() 两个方法。
    elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。
    contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法。
    Hashtable既不支持Null key也不支持Null value。Hashtable的put()方法的注释中有说明。当key为Null时,调用put() 方法,运行到下面这一步就会抛出空指针异常。因为拿一个Null值去调用方法了。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。Hashtable是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步
    HashMap不是线程安全的,在多线程并发的环境下,可能会产生死锁等问题。具体的原因在下一篇文章中会详细进行分析。使用HashMap时就必须要自己增加同步处理,
    虽然HashMap不是线程安全的,但是它的效率会比Hashtable要好很多。这样设计是合理的。在我们的日常使用当中,大部分时间是单线程操作的。HashMap把这部分操作解放出来了。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。
    Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
    HashMap的Iterator是fail-fast迭代器。当有其它线程改变了HashMap的结构(增加,删除,修改元素),将会抛出ConcurrentModificationException。不过,通过Iterator的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。
    JDK8之前的版本中,Hashtable是没有fast-fail机制的。在JDK8及以后的版本中 ,HashTable也是使用fast-fail的
    Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
    创建时,如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说Hashtable会尽量使用素数、奇数。而HashMap则总是使用2的幂作为哈希表的大小。
    之所以会有这样的不同,是因为Hashtable和HashMap设计时的侧重点不同。Hashtable的侧重点是哈希的结果更加均匀,使得哈希冲突减少。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。而HashMap则更加关注hash的计算效率问题。在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。HashMap为了加快hash的速度,将哈希表的大小固定为了2的幂。当然这引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改动。这从而导致了Hashtable和HashMap的计算hash值的方法不同
    为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。
    Hashtable直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得最终的位置。

  • 回答:
  • 见分析