常见面试题总结

01 怎么样封装简历

  • 写技能,至少有一两项是精通的,不要全是熟悉这样泛泛而谈
  • 想投一个公司,根据想投公司的岗位要求,把自己的简历对着改

02 hashmap底层执行原理

hashmap的存储结构

  • 数组、链表、红黑树

特点

  • 1.快速存储
  • 2.快速查找,时间复杂度是O(1)
  • 3.可伸缩,数组可以扩容,链表长度超过8以后会变成红黑树

hashmap底层执行原理–hash算法

hash算法

  • 所有的对象都有hashcode(使用key的)
  • hash值的计算
    • (hashcode)^(hashcode>>>16)
    • 为什么这么算,确保key足够分散

数组下标计算

  • 数组默认大小:16
  • 数组下标:hash&(length-1) ,这里取余是因为hash值的范围是40亿,而数组初始大小只有16,做与运算,防止越界

hashmap底层执行原理–hash冲突

hash冲突

  • 不同的对象算出来数组下标相同

单向链表

  • 用于解决hash冲突的方案,加入一个next记录下一个节点

hashmap底层执行原理–扩容

扩容

  • 数组变长2倍–0.75

触发条件

  • 数组存储比例达到75%—0.75
    • 基于时间和空间的考虑

hashmap底层执行原理–红黑树

红黑树

  • 一种二叉树,高效的检索效率

触发条件

  • 在链表长度大于8的时候,将后面的数据存在红黑树中

03 hashtable和concurrenthashmap是如何实现线程安全的

  • 多线程的线程安全问题

    • 对于最简单的i++操作执行10000次,用两个线程分别这个过程,所得结果应该是20000,但实际结果却不是,所以这个过程是线程不安全的,因此进一步查看concurrenthashmap中是怎么处理的
  • 对于hashmap,它的get方法和put方法,既没有加synchronized关键字,也没有加锁,没有做任何同步处理,是线程不安全的

  • 对于hashtable,它的get方法上就加了synchronized关键字,同样地在put方法上也加了synchronized关键字

    • 将所有涉及到操作数据的地方都加上了synchronized关键字

为什么有了hashtable还要引入concurrenthashmap

  • hashtable不管读写都是通过使用synchronized关键字来确保线程安全,但是在多线程情况下效率是很低下的

concueenthashmap怎么实现线程安全

  • hash就是哈希桶,取哈希后放入指定的桶里面
    • 假设哈希函数是取余,假设对1000取余,那么1和1001会放入1号桶中

分段锁的思想

  • jdk1.7及以前

    • 由上面hashmap的概念可知,哈希桶之间是不影响的,因此以此为基准
    • 在jdk1.7及以前,会在哈希桶的基础上再引入一个segment分段的概念,所以在其中有一个数组容量为16的segment数组,而segment继承了ReentrantLock,每次只针对segment这一段进行加锁,不同段之间是互不干扰的,而hashtable是对整个容器加锁
  • jdk1.8和以后

    • 如果有1000个桶,就有1000把锁,每个桶都有自己独立的锁,锁的粒度变得更小,提高了并发性能

04 jvm的内存布局,垃圾回收机制

  • 运行时数据区
    • 虚拟机栈
    • 本地方法栈
    • 程序计数器
      • 程序执行时记录行号
    • 方法区

java虚拟机栈

  • 每个方法在被调用时就会创建一个栈帧,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程

java堆

  • 是java虚拟机所管理的内存中最大的一块。是所有线程的共享区域,对象实例是在这里分配内存。是垃圾收集器管理的主要区域

方法区

  • 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,运行时常量池是方法区的一部分(jdk1.6)

直接内存

  • 并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域

内存区域在不同jdk版本的变化

  • jdk1.6

    • 运行时常量池是放在方法区里面
  • jdk1.7

    • 运行时常量池方法在堆里面
  • jdk1.8

    • 移除了方法区,取而代之的是元数据空间,并不在堆上,而是本地内存单独分配一块

垃圾回收算法

标记-清除算法

  • 对要回收的内存进行标记,要回收了,对标记区域进行回收
    • 缺点:垃圾回收后,产生内存碎片,不利于大对象创建

复制算法

  • 为了解决上面标记-清除算法的问题,引用复制算法
  • 把内存区域分成两部分,每次只用一半,把垃圾回收时,把存活对象放入另一半,并往一段对齐压缩,同时对回收的一半区域整块回收
    • 缺点,内存利用率低,浪费了一般的空间

标记-整理算法

  • 对于要回收的部分进行标记,把存活的对象移动到一端,然后把要回收的区域一次性回收
    • 存在存活对象在内存中的复制移动,需要消耗一定的性能

综合使用

  • 分代收集
    • 新生代统一采用复制算法,在新生代存活一定代数后,会移动到老年代,老年代有可能采用标记-清除,有可能采用标记-整理,具体取决于垃圾回收器类型
  • 垃圾回收器的线程可以是单个,也可以是多个,分为串行垃圾回收器和并行垃圾回收器,之后又引入用户线程在某些阶段可以和垃圾回收线程并发运行,出现了并发垃圾回收期cms和g1
  • 除了g1以外,其他早期的垃圾回收期都是按逻辑分为了新生代和老年代,而g1不再严格区分,而是分成一个个的小块,每一个小块极有可能是新生代,也有可能是老年代,此时新生代用复制算法,老年代用标记整理算法

05 类加载机制里的双亲委派模型

类的生命周期

  • 加载

    • 通过全限定名找到这个类,加载类信息到虚拟机中
  • 验证

    • 每一个java文件对应的class文件是否符合虚拟机规范
  • 准备

    • 为static变量分配内存,并且为基础类型变量设置初始值
  • 解析

    • 把符号引用替换为实际引用
  • 初始化

    • 真正执行类中定义的java代码,要和构造方法区分开,只会把static变量和static方法、static块执行一遍
  • 使用

    • new
  • 卸载

    • gc掉,将类的信息和实例移除
  • 元数据加载过程包括前5个过程

  • 其中第一个单独的加载过程需要用到类加载器,我们可以使用系统的类加载器,也可以使用自定义类加载器,自定义类加载器常常用来部署的热替换,类的加密解密

类加载器

系统自带的

  • 启动类加载器
    • 存在java_home\lib目录下,是虚拟机识别的类库加载到虚拟机内存中
  • 扩展类加载器
    • 存在java_home\lib\ext目录中的所有类库,开发者可以直接使用
  • 应用程序类加载器
    • 加载用户路径上指定的类库,开发者可以直接使用,一般情况下这个就是程序中默认的类加载器

双亲委派模型

  • 1.启动类加载器
  • 2.扩展类加载器
  • 3.应用程序类加载器
  • 4.自定义类加载器

双亲委派模型过程

  • 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次向上传递,如果其父类加载器就可以完成类加载任务,就成功返回,只有传递到顶层启动类加载器都无法加载后,才返回由自己去加载

双亲委派模型的好处

  • java类随着它的类加载器一起具备了带有优先级的层次关系,保证java程序稳定运行

06 阐释事务的隔离级别和传播属性

七个事务传播属性

  • propagation_required,支持方法调用传递过来的事务,如果当前没有事务,就新建一个事务,这是最常见的选择

  • propagation_supports,支持方法调用传递过来的事务,如果当前没有事务,就以非事务执行

  • propagation_mandatory,支持方法调用传递过来的事务,如果当前没有事务,就抛出异常

  • propagation_requires_new,新建事务,如果当前存在事务,把当前事务挂起

  • propagation_not_supported,以非事务方式执行操作,如果当前存在事务,就把事务挂起

  • propagation_never,以非事务方式执行,如果当前存在事务,则抛出异常

  • propagation_nested,新建事务,如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则新建一个事务

  • 两个存在调用关系的方法事务属性设置不同可能会带来问题

五种隔离级别

  • isolation_default,这是PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别,另外四个与jdbc的隔离级别相对应

  • isolation_read_uncommiteed,这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据,这种隔离级别会产生脏读,不可重复读和幻读

  • isolation_read_committed,保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读发生,但是可能会出现不可重复读和幻读

  • isolation_repeatable_read,这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读,这种隔离级别可以避免脏读和不可重复读

  • isolation_serializable,这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行,防止脏读、不可重复读、幻读

  • 查询mysql隔离级别

    • show variables like '%tx_isola

    • 默认是可重复读

  • 设置隔离级别,只有这一个session是未提交读级别

    • set seesion transaction isolation level read uncommitted
  • begin是开启事务,commit是提交事务

  • 工作中千万不要使用串行化,会锁表

07 高并发下,如何做到安全的修改同一行数据

案例

  • 下订单,在高并发下,不允许产生相同订单号

怎么解决

  • jvm

    • synchonized(lock){

      }

    • try{

      ​ lock.lock();

      }finally{

      ​ lock.unlock();

      }

  • 分布式环境,分布式锁

    • 数据库,性能差
    • redis,死锁
    • zookeeper,

Zookeeper

zookeeper数据结构

  • 数据结构类似linux
  • 每一个节点都有值

案例测试

  • 启动zkserver.cmd

  • zookeeper的默认端口是2181

zookeeper在底层提供两个功能

  • 管理(存储、读取)用户程序提交的数据
    • 可以理解为数据库
  • 并为用户程序提供数据节点监听服务
    • 理解为数据库的触发器,数据有变更时都会提醒

zookeeper有四种节点

  • persistent-持久化目录节点
    • 客户端与zk断开连接后,该节点依旧存在
    • create /aaa,再次创建同名节点会报错
  • persistent_sequential-持久化顺序编号目录节点
    • 客户端与zk断开连接后,该节点依旧存在,只是zk给该节点名称进行顺序编号
    • create /aaa_000001
    • 对于带编号,输入create /aaa,它会自增
  • ephemeral-临时目录节点
    • 客户端与zk断开连接后,该节点被删除
  • ephemeral_sequential-临时顺序编号目录节点
    • 客户端与zk断开连接后,该节点被删除,只是zk给该节点名称进行顺序编号

zookeeper基于异常的分布式锁

  • 1.client创建lock临时节点不带序号
    2.如果创建成功,获得锁,如果发生异常,则说明已经存在了锁,它会一直监听lock,当lock删除后,再次回到步骤1
    
  • 释放锁的操作

    • zkclient.delete(path)

zookeeper基于相互监听的分布式锁

  • 性能比较高,但是占用资源

  • 基于临时带序号节点

  • 1.client创建临时顺序节点,获取L值
    2.getChildren获取最小的i值
    3.判断i是否等于L,如果相等,则获得锁,如果不相等,则监控比当前节点小1的那个节点,判断L-1节点是否存在,如果不存在则进入步骤2,如果存在,则会注册一个监听器,等待L-1节点被删除的通知,当收到L-1被删除的通知后,再进入步骤2
    
  • 释放锁的操作

    • zkclient.delete(path)

08 a服务调用b服务的多个接口,响应时间最短方案

  • 浏览器,请求a服务,a服务要去调用b服务的用户信息接口,同时也要条用b服务的用户余额接口,b系统要把这些信息组合成json串返回给浏览器

常规实现

  • 在a服务中一个service里面,同步阻塞式的把三个服务调用回来组装起来,才返回给浏览器

存在的问题

  • 假设调第一个接口要3s,第二个接口要2s,则总共调用时间至少需要5s以上
    • 获取用户信息用了2705毫秒,获取用户余额用了2013毫秒,总共耗时是5154毫秒
  • 上面代码执行是串行执行的

接口聚合,并行

  • 静态申明一个线程池,10个线程数

  • public class UserServiceThread{
    	
    	@Autowired
        RemoteService remoteService;
        
        static ExecutorService taskExe = Excutors.newFixedThreadPool(10);
        
        public Object getUserInfo(String userId) throws InterruptedException, ExecutionException{
        	long t1 = System.currentTimeMillis();
            
            Callable<JSONObject> queryUserInfo = new Callable<JSONObject>() {
            	
                @Overrider
                public JSONObject call() throws Exception{
                	String v1 = remoteService.getUserInfo(userId);
                    JSONObject userInfo = JSONObject.parseObject(v1);
                    return userInfo;
                }
            }
            
            Callable<JSONObject> queryMoneyInfo = new Callable<JSONObject>() {
    
                @Overrider
                public JSONObject call() throws Exception{
                    String v2 = remoteService.getUserMoney(userId);
                    JSONObject moneyInfo = JSONObject.parseObject(v2);
                    return moneyInfo;
                }
            }
        
            
            FutureTask<JSONObject> userInfoTask = new FutureTask<JSONObject>(queryUserInfo);
            FutureTask<JSONObject> monkeyInfoTask = new FutureTask<JSONObject>(queryMoneyInfo);
        	
            taskExe.submit(userInfoTask);
            taskExe.submit(moneyInfoTask);
            
            // get阻塞获取时最好带超时时间
            JSONObject result = new JSONObject();
            resutlt.putAll(userInfoTask.get());
            resutlt.putAll(moneyUserInfo.get());
            
            return result;
        }
    
    
    }
    
  • 此时获取用户余额时间为2086毫秒,获取用户基本信息时间为2086毫秒,执行总时间为2216毫秒,最后花费的时间就是并发执行接口中响应时间最长的接口

09 a系统给b系统转100块钱,如何实现

考核的点

  • 数据如何保证一致性,性能优化,cas锁

  • http://127.0.0.1:9080/trans/sendOrder?orderid=1

  • a系统是订单系统,b系统是物流系统

实例测试

  • 用户下订单,订单调用物流系统发货

  • 在发货接口里面做了两件事,一是调用发货系统发货接口,二是更新订单状态为发货状态,所以在发货接口上加事务注解@Transactional保持数据一致性,确保两件事都做完了

    • @Override
      @Transactional
      public String sendOrder(Orde order){
      	String orderid = order.getOrderid();
      	String flag = transService.invoke(url,orderid);
      	order = new Order();
      	order.setOrderid(orderid);
      	order.setOrderstatus(flag);
      	orderDao.update(order);
      	return flag;
      }
      
  • 测试接口http://127.0.0.1:9080/trans/sendOrder?orderid=1

    • 分别调用orderid=2、3发货,再调用订单号1的查询订单操作,此时订单号为1的报错了,报连接数超出,这是因为数据库的连接池配置maxActive=2,即使改大了,也是治标不治本
    • 如果方法上加了@Transactional,会发起一个数据库连接,直到这个方法执行完才会释放,所以不要简简单单的加@Transactional注解,可能会导致数据库连接拿不到,会造成性能下降

性能优化–编程式事务

  • 需要引入TransactionTemplate,针对上面单纯加事务注解造成的性能损失,主要是String flag = transService.invoke(url,orderid);调用第三方代码造成的

  • 编程式事务是可以对某些代码块加事务,而不是整个方法加事务

    • @Autowired
      private TransactionTemplate template;
      
      @Override
      public String sendOrderByTemplate(Order order){
      	String orderid = order.getOrderid();
      	
      	template.execute(new TransactionCallback<Object>() {
      		@Overrider
      		public Object doInTransaction(TransactionStatus status){
      			Order order = new Order();
      			order.setOrderid(orderid);
      			order.setOrderstatus("4");// 4:正在处理中
      			orderDao.update(order);
      			retur null;
      		}
      	});
      	
          // 这里的调用物流系统是没有加事务的,也就是没有占用数据库连接
      	String falg = transService.invoke(url,orderid);
      	
      	template.execute(new TransactionCallback<Object>(){
      		@Overrider
      		public Object doInTransaction(TransactionStatus status){
      			Order order = new Order();
      			order.setOrderid(orderid);
      			order.setOrderstatus(flag);
      			orderDao.update(order);
                  return null;
      		}
      	});
      }
      
  • 为了防止第三方系统挂掉,导致用户重复操作

    • 在掉第三方系统前,先把用户的请求记录在表中,然后订单状态记录成正在处理中,当第三方系统挂了,此时表中依然记录的是正在处理中
    • 当第二天第三方系统恢复正常了,订单系统的综合调度系统自动查询哪些订单是因为物理系统挂掉而失败的,抽取出来,再去调用物流系统,完成整个流程

实例测试

  • 测试接口http://127.0.0.1:9080/trans/sendOrderByTemplate?orderid=1
    • 分别调用orderid=2、3发货,再调http://127.0.0.1:9080/trans/query?orderid=1,此时不报错了

提交订单按钮

  • 重复点多次

  • 接口幂等性没有满足

    • 基于状态机的乐观锁

    • 表里面多加一个version字段

    • // 通过for循环创建6个线程一次来请求发货接口
      // 0 1 2 3 4 5
      
      @Overrider
      public String sendOrderByLockStatus(Order order){
      	String orderid = order.getOrderid();
          // 第1个线程返回true
          Boolean lockStatus = template.execute(new TransactionCallback<Object>() {
      		@Overrider
      		public Object doInTransaction(TransactionStatus status){
      			Order order = new Order();
      			order.setOrderid(orderid);
      			order.setOrderstatus("4");// 4:正在处理中
                  order.setVersion(0);
                  // update `order` set orderstatus = #{orderstatus,jdbcType=VARCHAR},version = version+1 where orderid = #{orderid,jdbcType=VARCHAR} and version = #{version}
                  // sql中where条件会判断version等于0的那一条
                  // 所以只有第一个进入的线程能够成功
                  // true..false..false..false..false..false
                  // true和false就是状态,version就是利用了锁,所以就是基于状态机的乐观锁
      			return 1 == orderDao.updateByVersion(order);
      			
      		}
      	});
          
          if(lockStatus){
              // 这里的调用物流系统是没有加事务的,也就是没有占用数据库连接
              String falg = transService.invoke(url,orderid);
      
              template.execute(new TransactionCallback<Object>(){
                  @Overrider
                  public Object doInTransaction(TransactionStatus status){
                      Order order = new Order();
                      order.setOrderid(orderid);
                      order.setOrderstatus(flag);
                      orderDao.update(order);
                      return null;
                  }
              });
          }
      
      
      }
      

10 动态代理的几种实现方式及优缺点

动态代理

  • 使用反射和字节码的技术,在运行期创建指定接口或类的子类(动态代理)以及其实例对象的技术,通过这个技术可以无侵入的为代码进行增强

    • spring的很多aop功能,通过事务注解加入了事务功能
  • java动态代理技术实现主要有两种方式

    • jdk原生动态代理
    • cglib动态代理
  • java源文件(.java) --编译--> java字节码(.class)  --类加载--> class对象 --实例化--> 实例对象
    
    • 动态代理省略了java源文件,直接在内存中生成java字节码,然后继续进行后面的步骤

jdk原生动态代理

  • Proxy:Proxy是所有动态代理的父类,它提供了一个静态方法来创建动态代理的class对象和实例
  • InvocationHandler:每个动态代理实例都有一个关联的InvocationHandler。在代理实例上调用方法时,方法调用将被转发到InvocationHandler的invoke方法

jdk动态代理实例

  • 类1

    • public interface UserService{
      	void addUser(User user);
      
      }
      
  • 类2

    • public class UserServiceImpl implements UserService{
      	
          @Override
          public final void addUser(User user){
          	System.out.println("用户数据入库成功,数据为:"+user.toString());
          }
      }
      
  • 类3

    • User类
  • 类4

    • public class UserServiceInterceptor implements InvocationHandler{
      	
          private Object realObj;
          
          private static Logger logger = Logger.getLogger(UserServiceInterceptor.class,getName());
          
          public Object getRealObj(){
          	return realObj;
          }
          
          public void setRealObj(Object realObj){
          	this.realObj = realObj;
          }
          
          public UserServiceInterceptor(Object realObj){
          	super();
              this.realObj = realObj;
          }
          
          // 无侵入的代码增强,增强的是addUser方法,去额外做检查,user的name长度要大于1,否则抛出异常
          // args是传入addUser方法里面的参数
          public Object invoke(Object proxy, Method method, Object[] args)throws Throwable{
              // 前置增强,额外的长度检验
          	if(args!=null && args.length>0 && args[0] instanceof User){
              	User user = (User) args[0];
              	if(user.getName().trim().length()<=1){
                  	throw new RuntimeException("用户姓名输入长度需要大于1!");
                  }
              }
              
              // 利用反射去调用addUser方法
              Object ret = method.invoke(realObj,args);
              // 同时增加了日志,属于后置增强
              Logger.info("数据库操作成功!");
              return ret;
          }
      
      }
      
  • 类5

    • public class Client{
      	
          public static void main(String[] args){
          	User user = new User();
              user.setAddress("地址");
              user.setAge(20);
              user.setName("1");
              UserService us = new UserServiceImpl();
              // 此处把真正要增强的us传了进去,赋值给realObj变量
              UserServiceInterceptor usi = new UserServiceInterceptor(us);
              UserService proxy = (UserService)Proxy.newProxyInstance(us.getClass().getClassLoader(),us.getClass().getInterfaces(),usi);
              proxy.addUser(user);
              System.out.println("----------------");
              System.out.println(proxy.hashCode());
          
          }
      }
      
    • proxy有一个静态方法newProxyInstance,用于生成动态代理,有三个入参,第一个是列加载器,第二个是实现的接口,第三个是InvocationHandler本身

    • proxy显示的对象的类名是$Proxy0,但是在代码中并没有这个类,而是通过UserService proxy = (UserService)Proxy.newProxyInstance(us.getClass().getClassLoader(),us.getClass().getInterfaces(),usi);这一行代码生成了字节码和class对象,并创造出proxy这个实例

CGLIB动态代理

  • CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理;
    • Enhancer:来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor;
    • MethodInterceptor:动态代理对象的方法调用都会转发到intercept方法进行增强;

CGLIB动态代理实例

  • 不像jdk,代理类必须和被代理类实现相同的接口,cglib是没有这样的约束的

  • 类1

    • public class UserServiceImpl{
      	
          @Override
          public final void addUser(User user){
          	System.out.println("用户数据入库成功,数据为:"+user.toString());
          }
      }
      
  • 类2

    • public class UserServiceInterceptor implements MethodInterceptor{
          
          private static Logger logger = Logger.getLogger(UserServiceInterceptor.class,getName());
         
          public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)throws Throwable{
              // 前置增强,额外的长度检验
          	if(args!=null && args.length>0 && args[0] instanceof User){
              	User user = (User) args[0];
              	if(user.getName().trim().length()<=1){
                  	throw new RuntimeException("用户姓名输入长度需要大于1!");
                  }
              }
              
              // 和jdk唯一的区别,jdk是通过反射调用
              // 这里的写法也就是去调用实际的方法
              // create出来的对象是被代理对象的子类,所以要去调用真正的方法,所以是调用父类方法
              Object ret = proxy.invokeSuper(obj,args);
              // 同时增加了日志,属于后置增强
              Logger.info("数据库操作成功!");
              return ret;
          }
      
      }
      
  • 类3

    • public class Client{
      	
          public static void main(String[] args){
          	User user = new User();
              user.setAddress("地址");
              user.setAge(20);
              user.setName("");
              Enhancer enhancer = new Enhancer();
              // 设置动态代理的父类是被代理类
              enhancer.setSuperclass(UserServiceImpl.class);
              // 设置进行方法增强的类
              enhancer.setCallback(new UserServiceInterceptor());
              // 通过create方法获取代理对象
              UserServiceImpl usi1 = (UserServiceImpl) enhancer.create();
              usi1.addUser(user);
              System.out.println("----------------");
              System.out.println(proxy.hashCode());
          
              //UserServiceImpl us = new UserServiceImpl();
              //enhancer.setCallback(new UserServiceDelegateInterceptor(us));
              //UserServiceImpl usi2 = (UserServiceImpl) enhancer.create();
              //usi2.addUser(user);
          }
      }
      

总结

  • jdk原生动态代理是java原生支持的,不需要任何外部依赖,但是它只能基于接口进行代理
  • CGLIB通过继承的方式进行代理,无论目标对象有没有实现接口都可以代理,但是无法处理final的情况

11 多线程下读概率远远大于写概率,如何解决并发问题

  • 多线程下保证安全,一般使用内置锁加锁,但是加锁有什么问题呢?

    • 两个多线程对同一个值各加10000次,所得结果不是20000,可以通过synchronized和ReentrantLock来加锁
  • 对于读多写少的业务场景,我们如何解决?

    • 使用volatile关键字(一写多读)
    • 使用读写锁
    • 使用写时复制容器(CopyOnWrite系列)(很少写,很多读)

使用volatile关键字(一写多读)

  • volatile不能保证原子性,但是可以保证可见性,任意时刻都只会有1个线程写入

多读多写情况

  • private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock read  = lock.readLock(); // 读锁
    private final Lock write = lock.writeLock();// 写锁
    
    • 在读取数据时用读锁,写数据时用写锁
  • 假设读写比例是10,如果有30个读线程,就有3个写线程

    • 当使用内置锁时(ReentrantLock),会发现读取时间耗费的越来越长
    • 使用读写锁时,读取时间基本一致

很少写,很多读

  • 最常见的黑名单、白名单、商品类名的访问和更新场景
    • 对于不能搜索的关键字放在黑名单里面,不用经常更新,每天晚上更新一次就可以了,这时就可以使用CopyOnWriteArrayList/CopyOnWriteArraySet

CopyOnWriteArrayList/CopyOnWriteArraySet

  • 当往容器中添加元素时,并不会直接往原容器中添加,而是拷贝出一个新容器,当添加完毕后,再用新容器替换旧容器
    • 优点:读取完全不用加锁
    • 缺点:1.内存占用。2.数据一致性问题,只能保证最终一致性,不能保证实时一致性

12 按线程池内部机制,当提交新任务时,有哪些异常要考虑

  • 为什么要用线程池?

    • 业务开发运用最多的开发框架,几乎所有要异步或者并发执行的任务都可以用到
    • 业务开发过程中很少自己new一个线程
      • 1.线程池可以降低资源消耗,线程的创建和销毁很消耗性能
      • 2.提高响应速度,单独创建一个线程执行任务,需要三部分时间,创建一个线程的时间+任务执行时间+销毁消除的时间,利用线程池可以去除掉第一和第三部分时间
      • 3.提高线程的可管理性,线程在计算机里面属于一种宝贵资源,大量创建不仅会大量消耗系统资源,同时会降低系统稳定性,而且可以统一分配、监控、使用
  • 线程池的创建

    • public ThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue,
                                ThreadFactory threadFactory) {
          this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
               threadFactory, defaultHandler);
      }
      
      • threadFactory,为线程设置名字
  • 为什么线程运行规则?先创建core线程,再放入队列,再创建空闲线程,再执行拒绝策略

    • 1.创建线程需要获取全局锁,对性能有一定的影响
    • 2.线程属于一种宝贵资源,能尽可能少的线程完成任务最好
  • 提交任务

13 @Transaction注解一般写在什么位置?如何控制器回滚?

可以用在哪些位置

  • 不建议注解在接口上,因为类不能继承接口,无法使用cglib动态代理,只能使用jdk动态代理
  • 主要使用class的业务使用类上
    • 业务方法必须是public方法,不能用在protected和private方法,用在后两种一样不会有作用
    • 事务注解的方法不能被本地方法调用,指通过this.这种形式来进行调用的

实例测试

  • 如果在有事务注解的方法中发生异常,会导致事务回滚,但是如果把方法访问级别改成protected,则会导致事务不能成功回滚

transaction事务回滚规则

  • 按照下面的顺序依次判断
    • 1.noRollbackFor指定哪种异常不回滚
    • 2.rollbackFor指定哪种异常回滚
    • 3.如果上面两个参数都没有配置,如果是RuntimeException异常就会回滚,其他类型的异常不会回滚,但是事务是生效的

14 说说Spring的IOC容器初始化流程

  • IOC就是一个容器,用来存放Bean,它是一个很大的hashmap对象Map<String,Object> map = new HashMap<>(),map.put(“orderServiceImpl”, new OrderServiceImpl()),@Autowired注解会从ioc容器中取出对应的对象,并set进注解下的变量中

实例说明

  • public class MainTest1{
    	public static void main(String args[]){
            // 把beans.xml的类加载到容器
    		ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
            // 从容器获取bean
            Person person = (Person) app.getBean("person");
            
            // ApplicationContext是父类没有这个方法,它的子类有
            // 需要把当前容器销毁掉
            // 清空bean,关闭beanFactory
            //app.close();
            System.out.println(person);
    	}
    }
    
    • ClassPathXmlApplicationContext,容器载入类,把配置文件中配置好的bean放入ioc容器中

      • 不管spring3、spring4、spring5,都有一个公共的方法,refresh(),标准的ioc创建过程

      • 是通过BeanFactory.getBean()的方法来取出对应装配的对象,跟到最后,也是从map中拿,BeanFactory是核心类,是用来生产bean和获取bean的

      • @Override
        public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
                // Prepare this context for refreshing.
                prepareRefresh();
        
                // 先创建BeanFactory
                // Tell the subclass to refresh the internal bean factory.
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        
                // Bean工厂的环境变量
                // Prepare the bean factory for use in this context.
                prepareBeanFactory(beanFactory);
        
                try {
                    
                    // 这一大堆处理器是用来保证bean工厂和bean启动准确
                    // Allows post-processing of the bean factory in context subclasses.
                    postProcessBeanFactory(beanFactory);
        
                    // Invoke factory processors registered as beans in the context.
                    invokeBeanFactoryPostProcessors(beanFactory);
        
                    // Register bean processors that intercept bean creation.
                    registerBeanPostProcessors(beanFactory);
        
                    // 标签、国际化
                    // Initialize message source for this context.
                    initMessageSource();
        
                    // 事件
                    // Initialize event multicaster for this context.
                    initApplicationEventMulticaster();
        
                    // 监听
                    // Initialize other special beans in specific context subclasses.
                    onRefresh();
        
                    // Check for listener beans and register them.
                    registerListeners();
        
                    // 最后有一步,会把非懒加载的单实例bean加载进去
                    // Instantiate all remaining (non-lazy-init) singletons.
                    finishBeanFactoryInitialization(beanFactory);
        
                    // 完成ioc容器的创建,之前写了很多事件下发到spring的各个组件中,然后告诉他们ioc容器已经创建完了
                    // Last step: publish corresponding event.
                    finishRefresh();
                }
        
                catch (BeansException ex) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Exception encountered during context initialization - " +
                                    "cancelling refresh attempt: " + ex);
                    }
        
                    // Destroy already created singletons to avoid dangling resources.
                    destroyBeans();
        
                    // Reset 'active' flag.
                    cancelRefresh(ex);
        
                    // Propagate exception to caller.
                    throw ex;
                }
        
                finally {
                    // Reset common introspection caches in Spring's core, since we
                    // might not ever need metadata for singleton beans anymore...
                    resetCommonCaches();
                }
            }
        }
        
      • protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
            // Initialize conversion service for this context.
            if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
                beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
                beanFactory.setConversionService(
                    beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
            }
        
            // Register a default embedded value resolver if no bean post-processor
            // (such as a PropertyPlaceholderConfigurer bean) registered any before:
            // at this point, primarily for resolution in annotation attribute values.
            if (!beanFactory.hasEmbeddedValueResolver()) {
                beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
                    @Override
                    public String resolveStringValue(String strVal) {
                        return getEnvironment().resolvePlaceholders(strVal);
                    }
                });
            }
        
            // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
            String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
            for (String weaverAwareName : weaverAwareNames) {
                getBean(weaverAwareName);
            }
        
            // Stop using the temporary ClassLoader for type matching.
            beanFactory.setTempClassLoader(null);
        
            // Allow for caching all bean definition metadata, not expecting further changes.
            beanFactory.freezeConfiguration();
        
            // bean的实例化
            // Instantiate all remaining (non-lazy-init) singletons.
            beanFactory.preInstantiateSingletons();
        }
        
      • @Override
        public void preInstantiateSingletons() throws BeansException {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Pre-instantiating singletons in " + this);
            }
        
            // 收集所有的beanname
            // Iterate over a copy to allow for init methods which in turn register new bean definitions.
            // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
            List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
        
            // Trigger initialization of all non-lazy singleton beans...
            for (String beanName : beanNames) {
                RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
                if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                    if (isFactoryBean(beanName)) {
                        final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
                        boolean isEagerInit;
                        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                            isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                                @Override
                                public Boolean run() {
                                    return ((SmartFactoryBean<?>) factory).isEagerInit();
                                }
                            }, getAccessControlContext());
                        }
                        else {
                            isEagerInit = (factory instanceof SmartFactoryBean &&
                                           ((SmartFactoryBean<?>) factory).isEagerInit());
                        }
                        if (isEagerInit) {
                            getBean(beanName);
                        }
                    }
                    else {
                        // 获取bean
                        getBean(beanName);
                    }
                }
            }
        
            // Trigger post-initialization callback for all applicable beans...
            for (String beanName : beanNames) {
                Object singletonInstance = getSingleton(beanName);
                if (singletonInstance instanceof SmartInitializingSingleton) {
                    final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
                    if (System.getSecurityManager() != null) {
                        AccessController.doPrivileged(new PrivilegedAction<Object>() {
                            @Override
                            public Object run() {
                                smartSingleton.afterSingletonsInstantiated();
                                return null;
                            }
                        }, getAccessControlContext());
                    }
                    else {
                        smartSingleton.afterSingletonsInstantiated();
                    }
                }
            }
        }
        
      • protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {
        
            final String beanName = transformedBeanName(name);
            Object bean;
        
            // 先看ioc容器中是否有bean,有则直接获取到
            // Eagerly check singleton cache for manually registered singletons.
            Object sharedInstance = getSingleton(beanName);
            if (sharedInstance != null && args == null) {
                if (logger.isDebugEnabled()) {
                    if (isSingletonCurrentlyInCreation(beanName)) {
                        logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                                     "' that is not fully initialized yet - a consequence of a circular reference");
                    }
                    else {
                        logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
                    }
                }
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
            }
        
            else {
                // Fail if we're already creating this bean instance:
                // We're assumably within a circular reference.
                if (isPrototypeCurrentlyInCreation(beanName)) {
                    throw new BeanCurrentlyInCreationException(beanName);
                }
        
                // Check if bean definition exists in this factory.
                BeanFactory parentBeanFactory = getParentBeanFactory();
                if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                    // Not found -> check parent.
                    String nameToLookup = originalBeanName(name);
                    if (args != null) {
                        // Delegation to parent with explicit args.
                        return (T) parentBeanFactory.getBean(nameToLookup, args);
                    }
                    else {
                        // No args -> delegate to standard getBean method.
                        return parentBeanFactory.getBean(nameToLookup, requiredType);
                    }
                }
        
                if (!typeCheckOnly) {
                    markBeanAsCreated(beanName);
                }
        
                try {
                    final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                    checkMergedBeanDefinition(mbd, beanName, args);
        
                    // Guarantee initialization of beans that the current bean depends on.
                    String[] dependsOn = mbd.getDependsOn();
                    if (dependsOn != null) {
                        for (String dep : dependsOn) {
                            if (isDependent(beanName, dep)) {
                                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                                                "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                            }
                            registerDependentBean(dep, beanName);
                            getBean(dep);
                        }
                    }
        
                    // 如果ioc容器中没有这个bean,则会创建bean
                    // Create bean instance.
                    if (mbd.isSingleton()) {
                        sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                            @Override
                            public Object getObject() throws BeansException {
                                try {
                                    // 创建bean,进入doCreateBean方法
                                    // instanceWrapper = createBeanInstance(beanName, mbd, args)创建对象
                                    // populateBean(beanName, mbd, instanceWrapper);属性赋值
                                    // initializeBean(beanName, exposedObject, mbd)初始化bean,applyBeanPostProcessorsBeforeInitialization是在初始化bean之前要做的事,applyBeanPostProcessorsAfterInitialization是初始化bean之后要做的事,然后放入ioc容器中
                                    return createBean(beanName, mbd, args);
                                }
                                catch (BeansException ex) {
                                    // Explicitly remove instance from singleton cache: It might have been put there
                                    // eagerly by the creation process, to allow for circular reference resolution.
                                    // Also remove any beans that received a temporary reference to the bean.
                                    destroySingleton(beanName);
                                    throw ex;
                                }
                            }
                        });
                        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                    }
        
                    else if (mbd.isPrototype()) {
                        // It's a prototype -> create a new instance.
                        Object prototypeInstance = null;
                        try {
                            beforePrototypeCreation(beanName);
                            prototypeInstance = createBean(beanName, mbd, args);
                        }
                        finally {
                            afterPrototypeCreation(beanName);
                        }
                        bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                    }
        
                    else {
                        String scopeName = mbd.getScope();
                        final Scope scope = this.scopes.get(scopeName);
                        if (scope == null) {
                            throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                        }
                        try {
                            Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
                                @Override
                                public Object getObject() throws BeansException {
                                    beforePrototypeCreation(beanName);
                                    try {
                                        return createBean(beanName, mbd, args);
                                    }
                                    finally {
                                        afterPrototypeCreation(beanName);
                                    }
                                }
                            });
                            bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                        }
                        catch (IllegalStateException ex) {
                            throw new BeanCreationException(beanName,
                                                            "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                                            "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                                            ex);
                        }
                    }
                }
                catch (BeansException ex) {
                    cleanupAfterBeanCreationFailure(beanName);
                    throw ex;
                }
            }
        
            // Check if required type matches the type of the actual bean instance.
            if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
                try {
                    return getTypeConverter().convertIfNecessary(bean, requiredType);
                }
                catch (TypeMismatchException ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Failed to convert bean '" + name + "' to required type '" +
                                     ClassUtils.getQualifiedName(requiredType) + "'", ex);
                    }
                    throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
                }
            }
            return (T) bean;
        }
        

15 说说springboot启动机制

  • 怎么整合第三方依赖?
    • maven的父集成
  • 怎么做到无配置文件集成的springmvc?
  • tomcat是怎么启动的?

SpringBootApplication

  • @SpringBootConfiguration
    • 本身就是@Configuration,本身其实也是一个Spring容器的配置类
  • @EnableAutoConfiguration
  • @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    • 功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository)或者bean定义,最终将这些bean定义加载到Spring容器中
    • 我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描

@EnableAutoConfiguration

  • @Import(AutoConfigurationImportSelector.class)

    • @Import把一个类通过这种方式交给spring容器进行管理
  • AutoConfigurationImportSelector

    • @Override
      public String[] selectImports(AnnotationMetadata annotationMetadata) {
          if (!isEnabled(annotationMetadata)) {
              return NO_IMPORTS;
          }
          AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
              .loadMetadata(this.beanClassLoader);
          AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                                                                                    annotationMetadata);
          return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
      }
      
    • 其中getAutoConfigurationEntry方法中的getCandidateConfigurations方法,会去读META-INF/spring.factories这个文件

    • META-INF/spring.factories

      # Initializers
      org.springframework.context.ApplicationContextInitializer=\
      org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
      org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
      
      # Application Listeners
      org.springframework.context.ApplicationListener=\
      org.springframework.boot.autoconfigure.BackgroundPreinitializer
      
      # Auto Configuration Import Listeners
      org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
      org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
      
      # Auto Configuration Import Filters
      org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
      org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
      org.springframework.boot.autoconfigure.condition.OnClassCondition,\
      org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
      
      # Auto Configure
      org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
      org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
      org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
      org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
      org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
      org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
      org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
      org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
      org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
      org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
      org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
      org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveRestClientAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
      org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
      org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
      org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
      org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
      org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
      org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
      org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
      org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
      org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
      org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
      org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
      org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
      org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
      org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
      org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
      org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
      org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
      org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
      org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
      org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
      org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
      org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
      org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
      org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
      org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
      org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
      org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
      org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
      org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
      org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
      org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
      org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
      org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
      org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
      org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
      org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
      org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
      org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
      org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
      org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
      org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
      org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
      org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
      org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
      org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
      org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
      org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
      org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
      org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
      org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
      org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
      org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
      org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
      org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
      org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
      org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
      org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
      org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
      org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
      
      # Failure analyzers
      org.springframework.boot.diagnostics.FailureAnalyzer=\
      org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
      org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
      org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
      org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
      org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
      
      # Template availability providers
      org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
      org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
      org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
      org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
      org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
      org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
      
      

      org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\

      org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

      org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\

ServletWebServerFactoryAutoConfiguration
  • @Bean
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
        ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }
    
    • 加载tomcat的容器
WebMvcAutoConfiguration
  • {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebMvc Web MVC}.

    • 整个类相当于EnableWebMvc注解,把springmvc加载进来

总结

  • SpringBoot内置了Tomcat,使用它可以启动环境
  • 可以使用@EnableWebMvc集成SpringMVC的功能

手写一个简单的springboot,集成tomcat和springmvc

  • 类1

    • public class TomcatApp{
      	public static void main(String[] args)throws ServletException, LifecycleException{
          	// 使用java内置Tomcat运行SpringMVC框架 
              // springmvc注解启动方式,就会创建springmvc容器
              start();
          }
          
          public static void start() throws ServletException, LifecycleException{
          	
              // 创建Tomcat容器
              Tomcat tomcatServer = new Tomcat();
              // 端口配置
              tomcatServer.setPort(9090);
              // 读取项目路径,加载静态资源
              StandardContext ctx = (StandardContext)tomcatServer.addWebapp("/",new File("springboot_mvc/src/main/java/cn/enjoy/TomcatApp"));
              // 禁止重新载入
              ctx.setReloadable(false);
              // class文件读取地址
              File addtionWebInfClasses = new File("target/classes");
              // 创建WebRoot
              WebResourceRoot resources = new StandardRoot(ctx);
              // tomcat内部读取class执行
              resources.addPreResources(new DirResourceSet(resources,"WEB-INF/classes",additionWebInfoClasses.getAbsolutePath(),"/");
              tomcatServer.start();
              // 异步等待请求执行
              tomcatServer.getServer().await();
          
          }
      
      
      }
      
  • 类2

    • @Configuration
      @EnableWebMvc
      @ComponentScan(basePackages = {"cn.enjoy.controller"})
      public class WebConfig implements WebMvcConfigurer{
      	
          // 需要配置视图转换器
          // 创建SpringMVC视图解析器
          public void configureViewResolvers(ViewResolverRegistry registry){
          	InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
              viewResolver.setPrefix("/WEB-INF/views/");
              viewResolver.setSuffix(".jsp");
              // 可以在JSP页面中通过${}访问beans
              viewResolver.setExposeContextBeansAsAttributes(true);
              
              registry.viewResolver(viewResolver);
          }
      }
      
  • 类3—实现controller

    • @RestController
      public class IndexController{
      	@Autowired
          private UserService userService;
          
          @RequestMapping(value="/index", produces="text/html,charset=UTF-8")
          public String index(){
          	// return "纯手写springboot ok啦"
              return userService.index();
          }
      
      }
      

测试

  • 启动TomcatApp这个类
    • 访问localhost:9090/index

16 redis高性能的原因大概有哪些

  • 1.redis是把数据存放到内存中,对于计算机而言,内存中数据存取是最快的,跟HashMap很类似
    • redis中的写法:set key value
  • 2.set key value,value值是比较简单的,redis就是把value当成字符串,不会管你有多复杂
  • 3.单线程,不存在多线程抢锁的问题
  • 4.多路复用
    • 浏览器请求到tomcat,需要消耗tomcat一个线程-线程1来处理请求,如果又来了第二个请求,此时又要消耗一个线程-线程2来进行处理,这种不是多路复用
    • 如果是redis,多路是指同时有多个网络连接到redis(多个java程序发送redis命令到redis上),复用指复用同一个线程-线程a(redis是单线程),假如同时收到set命令和get命令,会把这些命令放到一个队列中去,redis执行指令的时候,按队列顺序依次执行
  • 5.resp协议,一条指令发送到redis,会按resp协议拼装,包括的内容有1.按空格解析有几组数据,2.每一段长度是多少,3.指令类型,4.key的长度,5.key的名字,6.value的长度,7.value的值

17 你是怎样控制缓存的更新?(被动方式/主动方式/增量/全量)

缓存框架引入

  • pom

    • <dependency>
          <groupId>org.springframework.data</groupId>
          <artifactId>spring-data-redis</artifactId>
          <version>1.7.1.RELEASE</version>
      </dependency>
      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
          <version>2.8.1</version>
      </dependency>
      
  • spring配置文件

    • <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:p="http://www.springframework.org/schema/p"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                                 http://www.springframework.org/schema/beans/spring-beans.xsd
                                 http://www.springframework.org/schema/context
                                 http://www.springframework.org/schema/context/spring-context.xsd">
      
          <context:component-scan base-package="com.dmall.nosql.redis"/>
      
          <!-- redis 配置 -->
          <bean id="redisInitConfig" class="com.dmall.nosql.redis.init.InitConfig"
                p:cachePrefix="${redis.prefix}"
                />
          <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"
                p:maxIdle="${redis.pool.maxIdle}"
                p:maxTotal="${redis.pool.maxActive}"
                p:maxWaitMillis="${redis.pool.maxWait}"
                p:testOnBorrow="${redis.pool.testOnBorrow}"
                />
          
          <bean id="jedisConnectionFactory"
                class="com.dmall.nosql.redis.init.JedisConnectionFactory"
                p:hostName="${master.redis.server.ip}"
                p:port="${master.redis.server.port}"
                p:poolConfig-ref="poolConfig"/>
      
          <bean id="redisExecuteService" class="com.dmall.nosql.redis.core.impl.RedisExecuteService" abstract="true">
              <property name="connectionFactory" ref="jedisConnectionFactory"/>
              <property name="valueSerializer">
                  <bean class="com.dmall.nosql.redis.serializer.impl.JdkRedisSerializerImpl"/>
              </property>
              <property name="keySerializer">
                  <bean class="com.dmall.nosql.redis.serializer.impl.JdkRedisSerializerImpl"/>
              </property>
          </bean>
          
      </beans>
      
      • 应该包括

        1.jedisPoolConfig—redis.clients.jedis.JedisPoolConfig

        2.jedisConnectionFactory—org.springframework.data.redis.connection.jedis.JedisConnectionFactory

        3.redisTemplate—org.springframework.data.redis.core.RedisTemplate

        4.cacheManager—org.springframework.data.redis.cache.RedisCacheManager

cacheManager

  • spring对缓存操作的进一步封装,因为缓存不一定是用redis搭建的,也有可能是memcached、mogodb,为了屏蔽缓存的实现,抽出这么一层,不管你用哪种缓存工具

缓存在系统部署中的位置

  • 缓存信息的本质就是硬盘数据的副本,归根究底是一种用空间换时间的技术,数据一致性问题不可避免

数据一致性问题演示

  • 查询收益总金额

    • sql查询需要0.34秒左右
    • 带缓存的查询
      • 1.先查缓存,缓存有,直接返回
      • 2.缓存不存在,查数据库
      • 3.把数据库数据保存到缓存,并返回
  • 测试

    • 1.有一个查询线程,不断用带缓存的查询去查询收益总金额
    • 2.新增一笔金额,此时数据库金额更新,和缓存中的数据不一样了

解决缓存数据一致性问题方案

  • 1.数据实时同步失效

    • 增量/主动

    • 强一致性,更新数据库之后淘汰缓存,读请求更新缓存,为避免缓存雪崩,更新缓存的过程需要进行同步控制,同一时间只允许一个请求访问数据库,为了保证数据的一致性还要加上缓存失效

    • @Override
      @Transactional
      public ProfitDetail addProfitDetail(ProfitDetail detail){
      	mapper.insert(detail);//1.更新数据库
          cs.cacheRemove(detail.getUsercode(),CACHE_NAME);//2.淘汰缓存
          return detail;
      }
      
    • @Override
      // 优先从缓存中加载收益总金额
      public BigDecimal getProfitAmountByCache(String userCode){
          // 1.从缓存中加载数据
          BigDecimal cacheResult = cs.cacheResult(userCode,CACHE_NAME);
          // 2.如果缓存中有数据直接返回给调用端
          if(cacheResult != null){
              logger.info("==========get data from cache=============");
              return cacheResult;
          }
      
          BigDecimal profitAmount = null;
          // 对用户级别加锁
          acquireLock(userCode);//竞争锁
          try{
              cacheResult = cs.cacheResult(userCode,CACHE_NAME);
              // 2.如果缓存中有数据直接返回给调用端
              if(cacheResult != null){
                  logger.info("==========get data from cache=============");
                  return cacheResult;
              }
              // 3.如果缓存中没有数据从数据库读取数据
              logger.info("==========get data from db=============");
              profitAmount = mapper.getProfitAmount(userCode);
              // 4.从数据库查询的结果不为空,则把数据放入缓存中,方便下次查询
              if(profitAmount!=null){
              	cs.cachePut(userCode,profitAmount,CACHE_NAME);
              }
          }catch(Exception e){
      		// TODO: handle exception
          }finally{
          	releaseLock(userCode);//释放锁
          }
      }
      
    • // 锁对象集,粒度到key值
      private final ConcurrentHashMap<Object,ReentrantLock> locks = new ConcurrentHashMap<>();
      
      public void acquireLock(String userCode){
      	Lock lock = getLockForKey(userCode);//获得锁对象
          lock.lock();
      }
      
      public Lock getLockForKey(Object key){
      	ReentrantLock lock = new ReentrantLock();
          ReentrantLock previous = locks.putIfAbsent(key,lock);//把新锁添加到locks集合中,如果添加成功使用新锁,如果添加失败则使用locks集合中的锁
          return previous == null? lock:previous;
      }
      
      public void releaseLock(String userCode){
      	ReentrantLock lock = (ReentrantLock) getLockForKey(userCode);// 获得锁对象
          if(lock.isHeldByCurrentThread()){//判断锁对象是否由当前线程持有
          	lock.unlock();
          }
      }
      
    • <bean id="cacheManager" class="org.springframwork.data.redis.cache.RedisCacheManager">
          <constructor-arg index="0" ref="redisTemplate"></constructor-arg>
          <property>
              <map>
                  <entry key="amount" value="100"></entry>
                  <entry key="amount_backup" value="1000"></entry>
              </map>
          </property>
      </bean>    
      

      设置收益总金额的失效时间是100秒

  • 2.数据准实时更新

    • 增量/被动

    • 准一致性,更新数据库后,异步更新缓存,使用多线程技术或者mq实现

    • activeMq,端口8161

    • <dependency>
          <groupId>org.apache.activeMq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.11.1</version>
      </dependency>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jms</artifactId>
          <version>4.3.3.RELEASE</version>
      </dependency>
      
    • @Service
      public class CacheMesgListener implements MessageListener{
      	@Resource
          private CacheService cs;
          
          @Resource
          private ProfitDetailMapper mapper;
          
          private static final String CACHE_NAME = "amount";
          
          @Override
          public void onMessage(Message message){
          	TextMessage tm = (TextMessage)message;
              try{
              	String userCode = tm.getText();
                  BigDecimal profitAmount = mapper.getProfitAmount(userCode);
                  cs.cachePut(userCode,profitAmount,CACHE_NAME);
              } catch(JMSException){
              	e.printStackTrace();
              }
          }
      }
      
    • @Resource
      private JmsTemplate jt;
      
      @Override
      public ProfitDetail addProfitDetail(ProfitDetail detail){
      	mapper.insert(detail);//1.更新数据库
          jt.send(new MessageCreator() {
          	@Override
              public Message createMessage(Session session)throws JMSException{
              	return session.createTextMessage(detail.getUserCode());
              }
          });
          return detail;
      }
      
  • 3.任务调度更新

    • 全量/被动
    • 最终一致性,采用任务调度框架,按照一定频率更新

数据准实时更新数据流图

  • 1.业务服务更新数据

  • 2.业务服务发送消息到MQ

  • 3.缓存更新服务监听MQ的信息,当收到信息后,缓存更新服务会去数据库读取数据,同时更新缓存

  • 特点

    • 业务服务和缓存解耦,有专门的缓存更新服务来处理缓存
    • 半夜定时任务全量更新

18 浅析http和https的三次握手有什么区别

  • http是不安全的
    • 数据是明文传输
    • 数据拦截、数据篡改、数据攻击
  • https的安全需求
    • 数据加密
    • 身份验证
    • 数据完整性

非对象加密

  • 对称加密
    • 对称加密就是一个密钥,可以加密也可以解密
  • 非对称加密
    • 非对称加密就是公钥加密的内容,必须用私钥才能解密,私钥加密的内容,必须用公钥才能解密
    • 公钥:地球都知道
    • 私钥:只有使用方自己知道

https

  • 1.请求https连接获取证书(公钥)
    2.客户端给服务器发送(对称加密<公钥>):随机数的密文
    3.客户端同时给服务器发送:(对称加密<公钥>):随机数+私钥 的密文
    4.服务器根据公钥解密出随机数,同时解密出私钥
    5.客户端使用非对称加密进行数据传输,客户端使用公钥加密,服务器使用私钥解密
    

19 谈谈session/cookie机制,如何实现会话跟踪

Cookie

  • 1.cookie是为会话存储的键值信息。(key:value)
  • 2.cookie不可跨域名。(只能拿到当前域名下的cookie,包含父级域名)
  • 3.cookie有有效期限制
  • 4.path携带cookie的请求路径(path是/表示全部请求都带cookie)

session

  • 1.session是服务器端基于内存的缓存技术,用来保存对每个用户的会话数据

  • 2.通过session id来区分用户,用户只要连接到服务器,服务器就会为之分配一个唯一的session ID。

  • 3.session也有失效时间。(超过时限未得到过连接的session将被销毁)

  • 4.session用法:

    HttpSession session = request.getSession();//取得sessio
    session.setAttribute(String key,Object value);// 将对象存到session
    session.getAttribute(String key);//获取session中存的Object对象
    session.removeAttribute(String key);//将键值为key的对象从session中删除
    

cookie与session的交互

  • 1.服务器通过response.addCookie(Cookie cookie)向response Header中设置cookie。

    • 如:Set-Cookie:jsession=08e0456d-afa3-45f3-99ef-a42e2fc977d3;Domain=dev.com;path=/;
  • 2.浏览器接受response Header的cookie值并保持。在下次请求的request Header中,携带此cookie信息。

    • 如:Cookie:jsession=08e0456d-afa3-45f3-99ef-a42e2fc977d3
  • 3.session被服务器设置到cookie中,以后浏览器的请求皆携带此sessionid,服务器据此检索对应session

  • 4.当程序请求session时,首先根据cookie中携带的session id检索session(检索不到,会创建一个)

浏览器与tomcat交互过程

  • 1.浏览器第一次请求tomcat,无cookie
  • 2.tomcat创建一个session,并将sessionid放入cookie传回给浏览器
  • 3.浏览器后续请求,携带cookie
  • 4.tomcat根据sessionid检索到session

总结

  • 1.cookie数据存放在客户的浏览器上,session数据放在服务器上
  • 2.cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session
  • 3.session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie
  • 4.单个cookie保存的数据不能超过4k,很多浏览器都限制一个站点最多保存20个cookie
  • 5.若客户端禁止cookie:一般使用url重写,就是把sessionid直接附加到url后面

20 什么是一致性hash

什么是一致性hash-hash算法的问题

  • 分布式架构缓存处理

    • hash算法分数数据存储hash(n)%4
      • 4是缓存服务器数量
    • 同时也可以快速查找数据而不用遍历所有的服务器
  • 业务拓展缓存服务器加一台

    • 要么服务器数据全部需要重新计算存储-----hash(n)%5
      • 代价比较高
    • 要么需要遍历所有缓存服务器

一致性hash算法

  • hash环
    • 一致性hash算法是对2^32取模,对服务器确定,确定次数据在环上的位置(比如A,B,C,D)
  • 数据存放
    • 数据进来后对2^32取模,得到一个值K1,在hash环中顺时针找到服务器节点

使用场景

  • B服务失效
    • 如果是B失效了,将B的数据迁移至C即可,对于原本散列在A和D的数据,不需要做任何改变
  • 总结
    • 一致性hash算法(DHT)通过减少影响范围的方式解决了增减服务器导致的数据散列问题,从而解决了分布式环境下负载均衡问题。

21 mq有可能发生重复消费,如果避免,如何做到幂等?

  • 什么是mq

    • 消息队列
    • 应用程序和应用程序之间远程通讯方法,应用程序可以通过消息中间件所提供的队列来写入消息和消费消息,实现一种通信的功能,避免远程调用
  • mq的作用

    • 异步处理,下单后发邮件、发短信,由消息中间件派发给别的服务去处理
    • 应用解耦
    • 流量削峰,秒杀场景, 大量流量涌入,服务器有限,可以把用户请求放在mq队列中
    • mq还可以用在日志处理上,kafka,
  • 重复消息是怎么回事?为何会产生重复消息?

    • 为了保证消息必达,消息超时重传机制,导致消息会重复发送
      • mq生产者发送给消息队列后,对于消息队列是否收到消息,是需要给mq生产者应答的,如果消息队列出问题了,mq生成者就会重试,再次发送
      • 消息队列的负载高,消费消息变慢了,导致超时
      • 生成者和消息队列通信的过程发生网络抖动,导致超时
    • 消息已经到达了消息队列,但是投递给消费者的过程中产生了多次投递
      • mq消费者出问题了,收到消息无法应答
      • 网络问题
      • 消费者消息时间太长了,超过超时时间
      • 消息中间件出问题,消费者应答信息丢失
      • 消息队列正常投递给消费者,消费者正常应答消息中间件,但是更新消息投递状态时出问题了,导致重复投递
  • 重复消息带来的问题,如何解决重复消息?

    • 带来的问题
      • 重复支付
    • 最常见的是幂等操作,更有效的是分布式事务,但是分布式事务比较复杂
  • 解决重复消息采用幂等操作

    • update table_A set count = 10 where id = 1

      • 幂等
    • update table_A set count = count+1 where id = 1

      • 非幂等
    • 两种幂等方式

      • 采用乐观锁,进行数据更新时必须带上版本号,如果版本号不一致不能更新成功,并且每个版本号只有一次执行成功的机会,一旦失败,生产者必须重新获取数据的最新版本号,再次发起更新
      • 表上唯一性索引,不让重复处理,比如订单id

22 如何做限流策略,令牌桶和漏斗算法的使用场景

  • a请求b接口,但是b接口的qps最大是1000/s,当超过这个数量就会把b接口打垮,所以需要限流

漏桶算法

  • a接口要调用b接口,需要经过一个漏斗(队列),这个漏斗中的水以恒定速率流出来,当调用频率快于滴水速率,漏斗就会装满了,此时再有请求过来,要么直接报错,要么放入一个队列,之后再处理

令牌桶算法

  • 令牌池
  • a接口调用b接口前,需要先去令牌池拿令牌,如果调用接口速率快于令牌池生成速率,拿不到令牌就直接拒绝

漏桶和令牌桶的区别

  • 对于秒杀而言,秒杀1000件商品,令牌桶需要先准备好1000个令牌,得保证在秒杀时间之前已经生成好,对于这种突发流量,漏桶算法不适用,令牌桶算法是更适用的
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值