面试题2020

1.分布式系统的幂等性问题?

就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品使用约支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条

2.如何设计接口才能做到幂等呢?

  • 单次支付请求,也就是直接支付了,不需要额外的数据库操作了,这个时候发起异步请求创建一个唯一的ticketId,就是门票,这张门票只能使用一次就作废

    • 异步请求获取门票
    • 调用支付,传入门票
    • 根据门票ID查询此次操作是否存在,如果存在则表示该操作已经执行过,直接返回结果;如果不存在,支付扣款,保存结果
    • 返回结果到客户端
  • 分布式环境下各个服务相互调用
    步骤:

    • 查询订单支付状态
    • 如果已经支付,直接返回结果
    • 如果未支付,则支付扣款并且保存流水
    • 返回支付结果

3.如何保证消息不被重复消费?(如何保证消息消费时的幂等性)

Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一个 offset,代表消息的序号,然后 consumer 消费了数据(写操作)之后,每隔一段时间(定时定期),会把自己消费过的消息的 offset 提交一下,表示“我已经消费过了,下次我要是重启啥的,你就让我继续从上次消费到的 offset 来继续消费吧”。

但是凡事总有意外,比如我们之前生产经常遇到的,就是你有时候重启系统,看你怎么重启了,如果碰到点着急的,直接 kill 进程了,再重启。这会导致 consumer 有些消息处理了,但是没来得及提交 offset,尴尬了。重启之后,少数消息会再次消费一次。

  1. ActiveMQ队列消息过多产生的原因
  • 若某一个消息消费端消费速度比较慢,然后生产者就认为没有发送成功,然后重新发送,这样才会产生消息的积压。
  1. ActiveMQ队列消息过多如何解决
  • 可以在配置文件中配置消息的过期时间和死信处理(消息过期丢弃)来防止消息的积压。
  • 配置多个ActiveMQ的消费者
  1. mysql和redis 数据不一致场景
    1.如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。

    2.如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。

    不一致的解决方案:

    • 读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。
    • 采用延时双删策略

    public void write(String key,Object data){
    redis.delKey(key);
    db.updateData(data);
    Thread.sleep(500);
    redis.delKey(key);
    }

  2. #{}与${}
    #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
    $将传入的数据直接显示生成在sql中

注意:#方式能够很大程度防止sql注入,$方式无法防止Sql注入。
$方式一般用于传入数据库对象,例如传入表名.

  1. 字符串转数字,数字转字符串

    • 数字转字符串
      String.valueOf(i)
      i + “”
      it.toString()
    • 字符串转数字
      Integer.parseInt(str);
  2. 查看某段时间日志的命令
    命令: grep ‘时间’ '日志文件名 ’

  3. 线程池中7个主要的参数

corePollSize:核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
maximumPoolSize:最大线程数。表明线程中最多能够创建的线程数量。
keepAliveTime:空闲的线程保留的时间。
TimeUnit:空闲线程的保留时间单位。
BlockingQueue:阻塞队列,存储等待执行的任务。参数有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue可选。
ThreadFactory:线程工厂,用来创建线程
RejectedExecutionHandler:队列已满,而且任务量大于最大线程的异常处理策略。有以下取值

  1. mysql索引种类

普通索引:仅加速查询
唯一索引:加速查询 + 列值唯一(可以有null)
主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个
组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并
全文索引:对文本的内容进行分词,进行搜索

  1. tomcat启动参数优化
    XMS : JVM初始分配的度堆内存
    XMX : JVM最大允许分配的堆内存,按需分配

  2. Nginx如何实现静态资源压缩
    配置gzip

  3. nginx 单点故障解决方案
    Keepalived + nginx 实现nginx的高可用
    通过keepalived来实现同一个虚拟IP映射到两台Nginx代理服务器,如果主服务器挂掉或者主服务器的keepalived挂掉又或者主服务器的Nginx挂掉(Nginx挂掉后会杀死keepalived的进程,在脚本中有控制)那从服务器的keepalived会检测到并会接管原先MASTER的网络功能,这种方式来实现Nginx的高可用性

  4. git 命令行命令

    • git config可以配置git的参数,可以使用git config --list查看已经配置的
      –system、–global、–local,分别表示所有用户(本系统)、当前用户(全局)、本地配置(当前目录),默认使用–local。
    • git clone https://git.oschina.net/Tocy/SampleCode.git 克隆远程仓库
    • git add 添加文件和目录
    • git diff 查看当前目录的所有修改
    • git log 查看当前目录所有提交日志
    • git branch testing 创建分支
    • git checkout testing 切换分支
    • git merge hotfix 分支合并
  5. mybatis 一级缓存和二级缓存

一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存 HashMap),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。

二级缓存是mapper(按namespace分)级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是多个SqlSession共享的。

  1. 如何手动设置事务

    • 获取事务定义
      DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    • 设置事务隔离级别,开启新事务
      def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    • 获得事务状态
      TransactionStatus status = transactionManager.getTransaction(def);
    • 事务回滚
      transactionManager.rollback(status);
    • 事务提交
      transactionManager.commit(status);
  2. 消息队列应用场景

    • 异步处理
      用户注册后,要发送注册邮件和注册短信
      引入消息队列后,将不是必须的业务逻辑,异步处理,注册邮箱和注册短信写入消息队列后,直接返回(写入消息队列的速度很快)

    • 应用解耦
      用户下单后,订单系统需要通知库存系统
      用户下单后,订单系统完成持久化处理,返回用户订单下单成功,库存系统订阅下单信息,库存系统根据下单信息,进行库存操作

    • 流量削峰(控制活动的人数)
      秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉
      用户的请求,服务器接收后,先存到消息队列中,加入消息队列长度超过最大长度,则直接抛弃用户请求,跳转错误页面。秒杀业务根据根据消息队列中的请求信息,再做后续处理。

  3. Nginx负载均衡策略
    轮询(轮询加权)
    ip_hash(ip绑定):根据ip的hash结果分配,可以解决session问题
    最少连接(least_conn):下一个请求将被分派到活动连接数量最少的服务器
    fair:按后端服务器的响应时间来分配请求,响应时间短的优先分配。

  4. redis 性能瓶颈?
    1.机器内存大小,因为redis的数据放在内存里,所以存放数据量的多少取决于内存的多少
    2.网络带宽

  5. 首先Redis为什么这么快?

    1. 基于内存,不会受到硬盘IO速度的限制;
    2. 单线程,避免了多线程切换导致的CPU消耗,也不用考虑锁的问题,不存在加锁释放锁的操作,也不存在因死锁而导致的性能消耗;
    3. 使用多路I/O复用模型,非阻塞IO
  6. SpingMVC与SpringBoot的联系与区别:

联系:
Spring 最初利用“工厂模式”( DI )和“代理模式”( AOP )解耦应用组件。按照这种模式搞了一个 MVC 框架(一些用 Spring 解耦的组件),用开发 web 应用( SpringMVC )。后来发现每次开发都要搞很多依赖,写很多样板代码,使代码臃肿而麻烦,于是聪明的前人整理了一些懒人整合包( starter ),这套就是 Spring Boot 。
区别:
Spring MVC 是基于 Servlet 的一个 MVC框架 主要解决 WEB 开发的问题 但关于Spring 的配置比较 ;而Spring boot 的原则是:约定优于配置 ,可以极大地简化了 spring 的配置流程。

  1. Mybatis 动态代理生成Mapper代理对象

    • 创建被代理类的接口RealSubjectInterface
    • 创建被代理类RealSubject
    • 创建一个实现接口InvocationHandler的类InvocationHandlerImpl
      即调用处理器实现类
      * 我们代理的真实对象 private Object realSubject;
      * 构造函数 为真实对象赋值 this.realSubject =realSubject;
      * invoke方法负责处理动态代理类中所有方法调用 invoke(Object proxy, Method method, Object[] args){
      方法前加操作1
      Object returnValue=method.invoke(realSubject,args);
      方法后加操作2
      }

    -测试:

    1. 创建要被代理的真实对象
      RealSubjectInterface realSubject=new RealSubject();
      InvocationHandler handler =new InvocationHandlerImpl(realSubject);
    2. 类加载器
      ClassLoader loader=realSubject.getClass().getClassLoader();
    3. 一组接口
      Class []interfaces=realSubject.getClass().getInterfaces();
    4. 生成动态代理类对象
      RealSubjectInterface realSubjectProxy= (RealSubjectInterface)Proxy.newProxyInstance(loader,interfaces,handler);
    5. 用动态代理类对象调用真实对象的方法
      System.out.println(realSubjectProxy.SayHello(“Jason zhang”));
  2. sql 优化

    1、在表中建立索引,优先考虑where、group by使用到的字段。
    2、尽量避免使用select *,返回无用的字段会降低查询效率。
    3、尽量避免使用in 和not in,会导致数据库引擎放弃索引进行全表扫描。
    4、尽量避免使用or,会导致数据库引擎放弃索引进行全表扫描。
    5、尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描。
    6、尽量避免进行null值的判断,会导致数据库引擎放弃索引进行全表扫描。
    7、尽量避免在where条件中等号的左侧进行表达式、函数操作,会导致数据库引擎放弃索引进行全表扫描。
    8、当数据量大时,避免使用where 1=1的条件。通常为了方便拼装查询条件,我们会默认使用该条件,数据库引擎会放弃索引进行全表扫描。

  3. oracle 与 mysql 区别

    • 主键 Mysql主键自增长,指定主键为auto increment;Oracle主键一般使用的序列,插入记录时将序列号的下一个值付给该字段即可
    • 单引号的处理 MYSQL里可以用双引号包起字符串,ORACLE里只可以用单引号包起字符串。
    • 翻页的SQL语句的处理 MYSQL limit;ORACLE每个结果集只有一个ROWNUM字段标明它的位置, 并且只能用ROWNUM<100, 不能用ROWNUM>80
      –分页规律总结:每页显示m条数据,查询第n页数据
      select * from (select rownum r,e. * from 要分页的表 e where rownum<=mn) t where r>mn-m ;
  4. RPC(WebService,Dubbo)与Restful(HttpClient)接口

    1. REST(基于http协议),HTTP相对更规范,更标准,更通用,无论哪种语言都支持http协议。
    2. RPC(一般基于tcp协议)RPC 框架作为架构微服务化的基础组件,屏蔽跨进程调用函数(服务)的各类复杂细节。让调用方感觉就像调用本地函数一样调用远端函数、让服务提供方感觉就像实现一个本地函数一样来实现服务。
    3. REST调用及测试都很方便,RPC就显得有点繁琐,但是RPC的效率是毋庸置疑的,所以建议在多系统之间的内部调用采用RPC。对外提供的服务,Rest更加合适
  5. zookeeper 作用?

    • 节点选举(Master节点,主节点挂了之后,从节点就会接手工作
      并且,保证这个节点是唯一的,这就是首脑模式,从而保证集群的高可用)
    • 统一配置文件管理(只需要部署一台服务器
      则可以把相同的配置文件,同步更新到其他所有服务器)
    • 发布与订阅(发布者把数据存在znode节点上,订阅者会读取这个数据)
    • 提供分布式锁()
    • 集群管理(集群中保持数据的一致性)
  6. HashMap 工作原理
    HashMap内部实现是一个桶数组,每个桶中存放着一个单链表的头结点。其中每个结点存储的是一个键值对整体(Entry),HashMap采用拉链法解决哈希冲突
    在这里插入图片描述

  • put函数大致的思路为:
    1、计算数组下标(key的hash值和数组长度计算);
    2、如果没碰撞直接放到bucket里;
    3、如果碰撞了(当两个不同的key的hash值相同时),以链表的形式存在buckets后;
    4、如果碰撞导致链表过长(大于等于5、TREEIFY_THRESHOLD),就把链表转换成红黑树;
    5、如果节点已经存在就替换old value(保证key的唯一性)
    6、如果bucket满了(超过load factor*current capacity),就要resize。

  • get函数大致的思路为:
    1、桶里的第一个节点(不存在单链表),直接获取;
    2、如果有冲突,则通过key.equals(k)去查找对应的entry
    若为树,则在树中通过key.equals(k)查找,O(logn);
    若为链表,则在链表中通过key.equals(k)查找,O(n)。

  • HashMap的扩容时机

当map中包含的Entry的数量大于等于threshold = loadFactor(装载因子0.75) * capacity(桶的长度)的时候,且新建的Entry刚好落在一个非空的桶上,此刻触发扩容机制,将其容量扩大为2倍。

当size(map中包含的Entry的数量)大于等于threshold(resize的阈值)的时候,并不一定会触发扩容机制(比如增加的entry对应的是一个空桶,那直接加载空桶里面,如果对应的不是空桶,会将链表拉长,就会触发扩容),但是会很可能就触发扩容机制,只要有一个新建的Entry出现哈希冲突,则立刻resize。

  • hash算法(计算数组下标)
static int indexFor(int h, int length) {  
       return h & (length-1);  
}  

如何判别在集合中是否已经存在该对象了?

调用equals()方法确实可行。但是如果集合中已经存在大量的数据,采用equals方法去逐一比较,效率必然是一个问题。添加新的对象时,先调用对象的hashCode,得到hashcode值,实际上在HashMap的具体实现中会一个表保存对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了
equals()相等,hashCode()必须相等。如果两个对象equals()相等,则它们在哈希表(如HashSet、HashMap等)中只应该出现一次;如果hashCode()不相等,那么它们会被散列到哈希表的不同位置,哈希表中出现了不止一次。hashCode方法的存在是为了减少equals方法的调用次数

  1. JVM
    在这里插入图片描述
  • 程序计数器
  1. 实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置
  • Java 虚拟机栈(描叙的是Java 方法执行的内存模型)

    1. 栈帧中都拥有局部变量表(基本数据类型和对象引用)、操作数栈、动态链接、方法出口
  • 本地方法栈(同上)
    区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务

  • 堆(虚拟机所管理的内存中最大的一块)

    1. 内存区域的唯一目的就是存放对象实例
    2. 垃圾收集器管理的主要区域
    3. 分代垃圾收集算法(目前虚拟机使用的回收算法)
    4. Java堆还可以细分为:新生代和老年代:再细致一点有:Eden(伊甸区)、From Survivor、To Survivor空间
      在这里插入图片描述
      上图所示是堆中内存分配示意图,创建一个对象,首先会在eden区域分配区域,如果内存不够,就会将年龄大的转移到Survivor区,当survivor区域存储不下,则会转移老年代的。对于一些静态变量不需要使用对象,直接调用的,则会被放入永久代。一般来说长期存活的对象最终会被存放到老年代,还有一种特殊情况也会被存放到年老代,就是创建大对象时,比如数据这种需要申请连续空间的,如果空间比较大的,则会直接进入老年代。 在回收过程中,有一个参数比较重要,就是对象的年龄,如果在一次垃圾回收过程中有使用该对象的,则将对象年龄加1,否则减1,当计数为0,则进行回收,如果年龄达到一定数字则进入老年代。总的来说内存分配机制主要体现在对象创建之后是否仍在使用,已经不使用的则回收,继续使用的则对其年龄进行更新,达到一定程度,转移到老年代。
  • 方法区(虚拟机加载的类信息、常量池、静态变量)

  • 复制算法
    复制算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。

  1. Mybatis 源码执行流程
    1.将sql语句和数据库配置信息保存在配置文件
    2.在MyBatis运行时,将配置信息存储Configuration对象
    在这里插入图片描述
    3.在创建SqlSession对象提供属性
    1) Configuration对象
    2) dirty:true sql语句执行完毕后 可以事务提交
    false sql语句执行发送错误 事务进行回滚
    3) Executor执行器对象:
    创建Statement对象,在创建过程中
    依靠MapperStatement对象将赋值内容与sql占位符
    进行绑定
    4.SqlSession.commit(): 根据此时dirty属性决定提交和回滚
    5.SqlSession.close();
    在这里插入图片描述
    在这里插入图片描述

  2. mybatis 用到的设计模式

    • Builder模式:
      例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder
  3. Object都有哪些方法?
    clone,getClass,toString,finalize,equals,hashCode,wait,notify,notifyAll

  4. 线程安全的集合对象
    Vector HashTable StringBuffer

  5. 非线程安全的集合对象
    ArrayList LinkedList HashMap HashSet TreeMap TreeSet StringBuilder

  6. 将非线程安全的集合转为线程安全的

Collections.synchronizedList
List list1 = new ArrayList<>();
List list2 = Collections.synchronizedList(list1);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值