牛客面经分析(二)

1、你实习都遇到了什么问题(交流、技术)?你是怎么解决的?
2、java内存模型?
3、如何解决缓存不一致问题?
4、mvcc原理知道吗?
5、mvcc是如何实现的?
6、undolog都有那些参数?
7、你都用过java里的那些容器?
8、hashmap的set和get方法是如何执行的?
9、 红黑树 的插入和查询的时间复杂度?
10、在人为可以控制的情况下, 链表 不会很长,那为什么还要用 红黑树 呢?
11、你是如何构建索引的?
12、索引为什么不可以很多?
13、怎么解决hash冲突?(我说了再次hash和开放定址法)布隆过滤器知道吗?(然后给我举了个例子,用布隆过滤器来解决hash冲突)

1.你写项目都遇到了什么问题(交流、技术)?你是怎么解决的?

开放题,那就在这里拓展一下我自己的面试项目可能会遇到的问题吧!
大家不用看 我自己瞎写的,直接看第二题吧!

  1. 简单介绍一下你的项目吧?
    我写的项目是Mall商城系统,首先关于项目的来源,由于没有项目经验,只写过很多的WebDome和自己搭建个人网站的小项目,所以在GitHub上找到一个商城项目,该项目实现了一个商城网站的基础框架,包括后台管理员系统,前台用户系统,然后本来是向根据它的push记录来学习,但是它的push记录多达800条,根本翻不到最后,后来就直接看最新版的代码了,代码难度不大,主要是学习到了项目的整体流程,自己实现了一下,然后我开始从项目搭建开始介绍,首先不管是前台系统还是后台系统,都需要用户认证,我的项目使用的是,在该项目之前,我是使用过SpringSecurity的springboot启动器,它的简单实现方法是直接导入SpringSecurity的依赖后就会拦截所有请求,它底层是一串过滤器链进行授权和认证,需要自己实现一个WebSecurityConfigurerAdpater来配置具体的规则,比如对不同url的不同处理,关闭csrf(跨站请求伪造,例如浏览器登录了正常网站A,验证通过了,返回了该网站的cookie,之后访问危险网站B时,B通过用户向A网站发送请求,让A网站误认为是已经认证过的用户),当然还得注入一个Encoder的Bean用于设置密码的加密方式,然后刚开始我写这个项目参考项目时,发现它的单独写了一个模块,该模块实现了这些基本的配置,例如权限不足处理器,未登录处理器,然后设置白名单,该模块使用的是把名单放入application.yaml文件当中(然后这个东西在子类才会去实现)使用@ConfigurationProperties(prefix=“xxx”)注解编写一个类,表示配置文件中的名称是什么,然后将白名单设置为直接通过,然后写了一个接口叫做DynamicSecurityService,用来实现获取权限和访问url的对应关系,然后该接口也是由继承的子类来实现,这就实现了动态权限管理,使用@AutoWire(require = false)来表示如果子类没有实现,就不进行动态权限配置,如果子类实现了,就添加一个自己实现的dynamicSecurityFilter的过滤器来进行动态权限过滤功能。这里是SpringSecurity的大概配置,它主要是实现访问路径和当前权限进行匹配这样一个工作,然后是JWT来当作token来实现用户认证,它的应用是先写一个util类来实现JWT各种基础功能,然后写一个JWTFilter来拦截请求,他会拦截Header中带token的请求,来解析用户信息,存入SecurityContext中(它就是使用threadLocal来实现的,这也说明我这个项目只适合单机开发,不适合分布式)将其加入SpringSecurity的过滤器链中,JWT的话 他就是一个加密字符串,用户认证的流程就是,当用户登录后,会生成一个Token,一边存入redis,一边返回前端,前端将其加入到Header中,之后用户登录就携带了header,进入JWT过滤器时,就会截获该header,然后解析内容,然后使用UserService进行登录,返回的UserDetails对象存储Local域中,后续权限认证的时候使用,JWT分为header,payload,signture,它的主要区别用户是使用signture来区分的,因为它不能被解密,而header中存储使用的加密方式,token类型(jwt),payload中存储的是(用户名,创建时间)用于过期校验,而singture使用无法解密的加密方法对前边两部分加密并加了盐,所以起到认证作用。这就是用户登录全过程,然后就是如何使其生效,就是在需要认证权限框架的项目中导入该模块,在applicaiton.yaml中配置白名单,继承该模块的配置类并实现其中的动态权限服务接口,实现方法可以是使用数据库查数据之类的,当然还需要使用使用用户名登录的方法。这就是动态权限认证框架的基本内容,然后我了解到现在是出现Auto2,这个是使用了第三方来登录,例如微信登录等,这个类似于https协议的stl的第三方认证机构CA的作用,来保证信息交互安全。

之后就就是常规的增删改查操作。

之后是Redis在项目的使用,使用的是Spring的模板,RedisTemplate将其操作封装成简单操作,这块也需要写一个Redis的config,其中实现key和value的序列化方式(主要要带redisConnectionFactory参数,不然失效),然后根据不同服务的使用将其再进行细化,例如JWT需要将token存入Redis,这里我就可以直接掉一个UserCacheService的save()方法,至于它的key设置,全部封装到了该服务中,避免了重复的操作

之后是项目中AOP的使用,使用环绕通知来实现对接口的信息统计和Redis宕机处理

项目还使用到了RabbitMQ和ES搜索引擎,这两块还没学习过,并没有用到自己的项目中

然后后端主要就是增删改查一些内容,前台的实现也大同小异,但是这里自己多实现了一下如果很多人同时买一款产品会出现的超卖现象,类似于秒杀把,出现这种现象的原因是下单前需要先查询是否还有库存,有库存才可以先去库存-1然后再产生订单信息,这里就可以类比多线程访问同一个共享变量,在这里共享变量就是库存查询结果,当两个线程同时去查询库存时,返回比如都是1,这时候就update xxx set ku = ku -1 来更新,就会发现超买了,会出现比如1000个线程去强100个商品,商品库存数最后变成了负数,我的解决办法是使用乐观锁思想,更新时,where 库存 > 0, 然后这里是考虑实际情况来判断的,其实这里还是出现超卖了,利用CAS 应该写where 库存 = 查询的库存,这里这样做是为了京可能提高并发买的成功率,虽然两个用户冲突了,但是他们都可以买下来,因为此时库存是> 0了,只要库存小于 0 了 直接不管你吵不吵买都拒绝就行。所以这里只能类似乐观锁思想,然后是一人一单问题,其实出现多线程安全问题的本质就是查询并修改这个操作没有做到原子性,这里一人一单 ,单线程保证方法肯定就是下单前看看这个用户id有没有订单记录,多线程出问题其实就是两个线程都查到没数据,然后一起创建新订单导致一人多单问题,把两步操作加锁即可实现(这里主要锁要加载controller层,在Service’层加事务注解,不然如果锁的范围小于事务的范围,等于是线程虽然并发执行了,但事务还未提交就会出问题,因为事务未提交前,其他线程进入锁仍然查到的是没有记录)。这里没使用redis来控制,还没学redis分布式锁之类的东西
-------------------------------这些是自己的项目,不用看

  1. 项目的难点,你是如何解决的?
    其实前边两大端就是难点。

java内存模型

这个题目要注意和Java 运行时数据区模型的区分,JMM和JVM

Java Memory Model:
首先可以提一下CPU的缓存和主内存,多个CPU在数据处理时,是先将数据从主内存中读出,放入CPU的寄存器中,在写回主内存中,当多个CPU拿到同一份数据同时对其进行修改再写回主内存中时,就会出现主内存数据写回覆盖问题。所以CPU有缓存一致性协议来控制多个CPU的读写

Java的内存模型类似,分为主内存和线程的工作内存,线程工作时将数据写入工作内存中,处理完后,再写回主内存中,如果不加控制也会出现上述的问题,所以java提供volatile关键字来保证工作内存每次修改数据时都保证它是主内存的最新数据,保证了共享变量的可变性

如何解决缓存不一致问题?

如上题使用volatile关键字来保证

mvcc原理知道吗?

首先MVCC是干什么的,用于事务隔离级别控制,如果两个事务想互相不影响,就需要用加锁来实现,但这样做太消耗性能,InnoDB引擎就引入了MVCC机制,Mutil-Version Concurrency Control,实现了对数据的并发访问。事务隔离级别的RC和RR都是MVCC机制来实现的。
每个表都有两个隐藏字段(或三个,当不存在主键索引时,会产生ROW_ID来作为主键索引使用),tx_id,roll_pointer,表示上次更改记录的事务id(自增)和指向上一版本记录的指针(指向undo log日志),undo log日志记录对数据表修改的反向记录,当事务回滚时,根据undo log日志来回滚数据,这样就保证了事务的原子性和一致性
在RC和RR 读一个表的时候,读到它的隔离级别能读到的表信息就是使用MVCC机制,在RC级别下,只允许读到其他事务已经提交的数据,所以他在每次快照读的时候都会生成ReadVIew,其中包含事务id,活跃事务id列表,最小活跃事务id等信息,根据事务id的4条判断来读取对版本链中符合规定的行记录,RR下是第一次读产生ReadVIew后边复用该记录,所以可以保证每次读都读到之前的记录,或者自己修改的记录。
注意,网上很多博客介绍MVCC是用来解决幻读的,实际上MVCC并不能解决幻读,MVCC主要用来在一个事务的中查询数据时保证与其他的事务的隔离性,RR和RC来使用该机制,而幻读是一个事务未结束时,另一个是事务插入一条数据,上一个事务想在这个位置插入数据时,查出来没有数据,但是就是插入不了,会报错,所以使用Next-key Lock锁住记录和间隙,防止其他事务插入数据,防止幻读。

undolog都有那些参数?

不会

你都用过java里的那些容器?

java容器主要分为两类,一类是实现Collection接口的存放数据的容器,一类是实现Map接口的存储K-V键值对的容器

Collection的子接口是List ,Set,Queue。
list有 vector,ArrayList,LinkedList(也实现了Queue接口)

Set有HashSet,TreeSet,LinkedHashSet

Queue有PriorityQueue实现优先队列,ArrayQueue,当然还有LinkedList(既可以当队列使用,也可以当作栈使用)

Map接口的实现有 HashMap,HashTable,LinkedHashMap,TreeMap

hashmap的set和get方法是如何执行的?

原面经写的set 但是因该是put方法
写过太多了

红黑树 的插入和查询的时间复杂度?

O(logN)

在人为可以控制的情况下, 链表 不会很长,那为什么还要用 红黑树 呢?

这是什么问题,人为控制了,还要考虑这么多东西么

你是如何构建索引的?

首先 索引创建方法

ALTER TABLE table_name ADD INDEX index_name ( column )

创建索引的一些原则:

  • 建立索引的目的是为了提高查询效率,所以选择查询频繁且数据量较大的字段建立索引
  • 使用经常出现在WHERE字段的字段来进行建立索引
  • 使用唯一索引, 区分度越高, 使用索引的效率越高.
  • 对于字符串可以截取前几个字符建立索引
  • 注意最左前缀原则

索引为什么不可以很多?

索引提高了查询效率,但是每次更新表的时候,都会涉及每个字段的索引的更新,索引过多会导致更新效率下降

怎么解决hash冲突?(我说了再次hash和开放定址法)布隆过滤器知道吗?(然后给我举了个例子,用布隆过滤器来解决hash冲突

hash冲突和布隆过滤器面经(一)介绍过了