旅悦集团

2019-08-25  本文已影响0人  今兮何惜

        相信很多同学和我一样,很难把这么一家公司和携程联系起来,但事实就是事实,不会以人的意志为转移。2016年成立的旅悦集团在2019年8月的时候对外公布的资料显示已经1500+人,这在互联网行业的扩张速度简直惊人,公司时互联网+自主民宿品牌,通过建立会员社区互动来增加用户的粘性,下载了花猪简单点了点,无论是内容还是产品本身的流畅度,都是很不错的,公司在东升科技园,进入办公区后整个装修的色调和布局非常nice,整体感觉不错,是很值得加入的一家公司,如下是面试记录:


1、什么是分布式锁,如果让你设计,你准备怎么做?谈谈乐观锁和悲观锁

        在分布式环境下,数据只有一份,此时需要利用锁的技术控制任意一个时刻只有一个进程中的一个线程访问该数据,也就是分布式锁。

        可以利用Redis实现分布式锁,具体步骤:

①setnx(lockKey,当前时间+过期时间),如果返回1,获取锁成功,如果返回0进入②

②oldExpireTime=get(lockKey),并将值与当前系统时间比较,如果该锁超时,可以允许别的请求获取,转向③

③newExpireTIme=当前时间+过期时间,然后currentExpireTime=getset(lockKey,newExpireTIme)

④如果currentExpireTime=oldExpireTime,说明getset命令成功,获取所成功,否则,获取锁失败

⑤在成功获取到锁后,可以处理业务逻辑,为了提高效率,业务处理完成后可以判断该Key是否过期,如果是,直接删除之。

       悲观锁总是假设最坏的情况,每次访问数据的时候都认为别的线程会修改数据,所以每次在操作前都会加上锁,直到用完释放掉后别的线程才能访问该数据。如Java中的synchronize和ReentrantLock等独占锁就是一种悲观锁的体现。

        乐观锁总是假设最好的情况,每次访问数据的时候都认为别的线程不会修改数据,所以不会上锁,但是在更新的时候会判断一下    在次期间有没有别的线程更新了数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样提高吞吐量。Java中的java.util.concurrent.atomic包下面的原子变量类就是乐观锁的体现。


2、Kafka为什么能做到很大的吞吐量,其底层的实现逻辑是什么?

        Kafka采用了顺序读写、Page Cache、零拷贝以及分区分段、批量读写这些设计,从而实现很大的吞吐量。

①Kafka是将消息记录持久化到磁盘上的,而磁盘的读写速度和寻址方式有很大的关系,基于磁盘的随机读写方式确实很慢,但是顺序读写至少高出前者3个数量级。Kafka的消息是顺序追加到本地磁盘文件末尾的,而不是随机的写入,这个设计使得吞吐量得到显著的提升。

②Kafka还利用了操作系统本身的内存Page Cache而不是JVM内存,这样做不但可以避免Java对象对内存的消耗,还可以避免GC问题对性能的影响。操作系统本身的具有缓存设计且利用率很高,可以增加读写的速度,其次系统缓存不会随着服务进程的重启而消失,从而避免了重构缓存过程对性能的影响

③在消费端,Kafka利用了linux系统的“零拷贝”机制,避免了数据在内核空间和用户空间的多次拷贝,从而提高了消费端的性能

④Kafka的消息是按照topic分类存储的,topic又按照partition分区存放在不同的broker节点,每个partition对应操作系统上的一个文件夹,partition实际还按照segment分段存储的,所以对Kafka的每次操作其实是直接对segment的操作,威力进一步优化,Kafka还对分段后的文件建立索引,这种分区分段+索引的设计,大大提高了数据读取的速度。


3、在以前的经历中对MySQL做过哪些优化,索引原理是什么,用过那种类型的索引,存储引擎?

参考:https://www.nowcoder.com/discuss/150059?type=0&order=0&pos=8&page=0):

①存储引擎的选择:Innodb支持事务,行级锁、外键,并且使用B+Tree(搜索多叉树)维护数据顺序,牺牲了一定的写入效率,但查询效率大大提升,适用于频繁查询的场景;Myisam支持全文索引,但删除数据会产生磁盘碎片,需要定期手动优化,适用于写入多的情况

②索引优化:针对查询频率高的索引建立合理的索引,同时尽量在满足查询需求的情况下减少索引的数量,比如使用联合索引;在建好索引后,对查询的SQL要进一步优化,使其可以有效利用索引,避免全表扫描,如保证索引项在语句中单独出现、正确使用模糊查询like,or两边字段或者join语句的连接字段均建立索引等

③查询缓存优化:将select语句的查询结果缓存起来二次使用,比如使用mybatis可以开启二次缓存以提高查询的效率。

④架构设计方面的优化:分区、分库分表(水平、垂直)、读写分离

⑤借助于explain、profile、和开启慢查询日志的方式有针对性的优化


4、谈谈Redis的缓存驱逐策略和淘汰算法?

        Redis的淘汰算法有FIFO、LRU、LFU;驱逐策略有6中,分别是no-eviction(不驱逐)、allkeys-lru、volatile-lru、allkeys-random、volatile-random、volatile-ttl(淘汰设置过期时间的keys中将要过期的),常用的是volatile-lru


5、JVM调优做过哪些?

        JVM的调优目的是优化JVM进程的响应速度,从而使程序性能达到一个较好的状态,涉及方面主要有选择合适的GC回收器,比如并发回收器或者CMS回收器应对多线程情况,针对垃圾回收算法优化的XX:CMSFullGCsBeforeCompaction等回收器,在启动JVM进程层之前合理分配年轻代、年老代和持久代的堆内存大小,达到减少GC对程序性能的影响(full gc或者miner gc),常见的设置参数有-Xms、-Xmx、-XX:+UseCMSCompactAtFullCollection、-Xmn768M、-XX:SurvivorRatio=3等,在具体的问题上,使用jstack、jmap,jstat等Java命令结合linux命令如top、ps以及内存堆栈分析工具如mat等排查,目的还是为了优化程序对堆内存的不合理使用。

常见的内存错误:https://segmentfault.com/a/1190000009056113

理论片:https://www.cnblogs.com/beyondcj/p/6273487.html


6、Java Concurrent并发包有哪些?

         java.util.concurrent包下面主要提供4类工具类的实现:

①线程安全的容器

参考:https://www.cnblogs.com/ygj0930/p/6543901.html

         ConcurrentHashMap:采用“分段加锁”的设计细化锁的粒度:把整个map划分成一系列的segment(分段)的组成单元,每个segment相当于一个小的HashTable,这样加锁的对象就是segment。ConcurrentHashMap中的map对读是不加锁,只有在remove、put等改变数据的操作时才在对应的segment上加锁,不会影响其他的segment的读写,所以极大地提高了性能。ConcurrentHashMap不维护顺序。

        ConcurrentSkipListMap:基于跳表实现的支持多层索引的设计的数据结构,也就是多层设计的链表实现,在修改数据时利用CAS原理,也就是更新数据是和老的数据比较达到无锁话编程支持线程安全的目的。

         CopyOnWriteArrayList、CopyOnWriteArraySet:CopyOnWrite是一种常见的写时复制并发容器,基本思想是读写分离,即在读时不加锁,但在写的时候需要加锁,同时将旧元素Copy到新的数组,并将新的元素添加其中,最后将原数组引用指向新的数组。

②并发队列的实现:

参考:https://www.cnblogs.com/losemyfuture/p/9375333.html

          ConcurrentLinkedQueue:基于链表无锁方式实现的线程无界安全队列,遵循FIFO,不允许null

         ArrayBlockingQueue:基于数组实现的有界阻塞队列,线程安全

         LinkedBlockingQueue:基于链表实现的无界阻塞队列,线程安全

         PriorityBlockingQueue:无界的优先级队列,线程安全

         SynchronousQueue:同时仅容纳一个元素的线程安全队列。

③线程池相关工具:创建不同的线程池、调度任务

参考:https://yq.aliyun.com/articles/708294?spm=a2c4e.11155435.0.0.4cce540esKhARm

核心线程池、最大线程池、任务队列容量大小的关系;

创建线程池的2种方式:

           使用Executors(newCachedThreadPool、newSingleThreadScheduledExecutor、newScheduledThreadPool、newFixedThreadPool)

            ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,  TimeUnit unit, BlockingQueue<Runnable> workQueue)

④同步结构:

参考:https://www.cnblogs.com/dolphin0520/p/3920397.html

        倒计数器CountDownLatch:实现计数器的功能,比如可以实现如某一线程A等待其他4个线程执行完毕后才执行。主线程await方法挂起,每个线程处理完成后调用countDown方法既可以。

        环栅栏CyclicBarrier:可以利用await方法挂起所有的线程达到让一组线程等待至某一状态后再全部执行。CyclicBarrier可以重用

        信号量Semphore:可以控制同时被访问的线程数,acquire(int n)阻塞获取n个许可,release(int n)释放n个许可


7、1个主线程有4个子线程,要求实现子线程完成后主线程才能进如下一个操作,如何实现?

       利用CountDownLatch的await方法只有在计数器的值为0的时候才执行的特性


8、Spring Boot、Spring Cloud

Boot的基础面试:https://www.songma.com/news/txtlist_i24822v.html

推荐一个系统的Java知识系列文章:http://www.ityouknow.com


上一篇下一篇

猜你喜欢

热点阅读