Java面试题

目录

1... JAVA系列... 10

1.1                 常用容器... 10

1.2                 ArrayList 和 linkedlist 区别。ArrayList 是否会越界... 12

1.3                 ArrayList 和 hashset 的区别。hashset 存的数是否有序。... 12

1.4                 HashMap与HashTable的区别... 13

1.5                 如何决定使用 HashMap 还是 TreeMap. 13

1.6                 HashMap 的实现原理... 14

1.7                 HashSet 的实现原理... 14

1.8                 Iterator怎么使用?有什么特点?... 14

1.9                 Iterator和Listlterator有什么区别?... 15

1.10               volatile 和 synchronized 区别... 15

1.11               synchronized 和 Lock 有什么区别?... 15

1.12               多态的原理... 15

1.13               反射... 16

1.14               队列... 16

1.15               基于数组的循环队列实现... 17

1.16               接口幂等性... 17

1.17               多线程系列... 19

1.17.1            Java 线程间实现同步... 19

1.17.2            notify()与 notifyAll()的区别... 19

1.17.3            创建线程池有哪几种方式?... 20

1.17.4            如何创建线程池?... 20

1.17.5            ThreadPoolExecutor机制... 21

1.17.5.1      概述... 21

1.17.5.2      核心构造方法讲解... 21

1.17.5.3      重点讲解:... 21

1.17.5.4      线程池的运作流程... 22

1.18               JVM系列... 23

1.18.1            JVM 的主要组成部分及其作用... 23

1.18.2            JVM包含两个子系统和两个组件: 23

1.18.3            作用 :... 24

1.18.4            深拷贝和浅拷贝... 25

1.18.5            Java会存在内存泄漏... 25

1.18.6            强引用、软引用、弱引用、虚引用... 26

1.18.7            堆栈的区别... 29

1.18.8            判断对象是否可以被回收... 29

1.18.9            分代垃圾回收器工作原理... 30

1.18.10          JVM 的垃圾回收算法... 32

1.18.11          G1垃圾回收过程... 33

1.18.11.1    Remembered Set 35

2... 框架... 36

2.1                 Spring家族... 36

2.1.1              SpringMVC系列... 36

2.1.1.1        SpringMVC运行流程描述... 36

2.1.1.2        Springmvc的优点: 37

2.1.1.3        SpringMVC常用的注解... 37

2.1.1.4        SpringMVC怎么样设定重定向和转发的?... 37

2.1.1.5        如何解决POST请求中文乱码问题,GET的又如何处理呢?... 38

2.1.1.6        Spring MVC的异常处理 ?... 39

2.1.1.7        SpringMvc的控制器是不是单例模式,有什么问题,怎么解决?... 39

2.1.1.8        在拦截请求中,我想拦截get方式提交的方法,怎么配置?... 39

2.1.1.9        怎样在方法里面得到Request,或者Session?... 39

2.1.1.10      前台有很多个参数传入,并且这些参数都是一个对象的,怎样快速得到这个对象?    39

2.1.1.11      SpringMvc中函数的返回值是什么?... 39

2.1.1.12      SpringMvc用什么对象从后台向前台传递数据的?... 39

2.1.1.13      怎么样把ModelMap里面的数据放入Session里面?... 39

2.1.1.14      SpringMvc里面拦截器是怎么写的:... 40

2.1.1.1        拦截器... 40

2.1.1.2        过滤器... 40

2.1.1.3        拦截器和过滤器的区别:... 41

2.1.2              Spring系列... 43

2.1.2.1        Spring是什么?. 43

2.1.2.2        Spring 的优点?... 43

2.1.2.3        Spring的AOP理解:... 43

2.1.2.4        Spring AOP中的动态代理... 44

2.1.2.5        Spring的IoC理解:... 44

2.1.2.6        BeanFactory和ApplicationContext有什么区别?... 45

2.1.2.7        Spring Bean的生命周期?... 46

2.1.2.8        解释Spring支持的几种bean的作用域。... 47

2.1.2.9        Spring框架中的单例Beans是线程安全的么?... 47

2.1.2.10      Spring如何处理线程并发问题?... 47

2.1.2.11      Spring基于xml注入bean的几种方式:... 48

2.1.2.12      Spring的自动装配:... 48

2.1.2.13      @Autowired和@Resource之间的区别... 49

2.1.2.14      常用注解... 50

2.1.2.15      Spring 框架中都用到了哪些设计模式?... 50

2.1.2.16      Spring事务的实现方式和实现原理:... 51

2.1.2.17      Spring框架中有哪些不同类型的事件?... 52

2.1.2.18      解释一下Spring AOP里面的几个名词:... 52

2.1.2.19      Spring通知有哪些类型?... 53

2.1.3              SpringBoot系列... 55

2.1.3.1        什么是 Spring Boot?... 55

2.1.3.2        Spring Boot 有哪些优点?... 55

2.1.3.3        Spring Boot Starter 的工作原理... 55

2.1.3.4        Spring Boot 中的监视器是什么?... 55

2.1.3.5        运行 Spring Boot有哪几种方式?... 55

2.1.3.6        如何在自定义端口上运行 Spring Boot 应用程序?... 56

2.1.3.7        如何使用 Spring Boot 实现异常处理?... 56

2.1.3.8        如何使用 Spring Boot 实现分页和排序?... 56

2.1.3.9        什么是 JavaConfig?... 56

2.1.3.10      Spring Boot 的核心注解是哪个?主要由哪几个注解组成?... 57

2.1.3.11      Spring Boot 自动配置原理是什么?... 57

2.1.3.12      如何理解 Spring Boot 配置加载顺序?... 58

2.1.3.13      什么是 YAML?... 59

2.1.3.14      YAML 配置的优势在哪里 ?. 59

2.1.3.15      Spring Boot 是否可以使用 XML 配置 ?. 59

2.1.3.16      application和properties 有何区别 ?. 59

2.1.3.17      spring boot 核心的两个配置文件:... 59

2.1.3.18      什么是 Spring Profiles?... 60

2.1.3.19      如何实现 Spring Boot 应用程序的安全性?... 60

2.1.3.20      比较一下 Spring Security 和 Shiro 各自的优缺点 ?. 60

2.1.3.21      Spring Boot 中如何解决跨域问题 ?. 61

2.1.3.22      什么是 CSRF 攻击?... 62

2.1.3.23      Spring Boot 中的监视器是什么?... 62

2.1.3.24      如何在 Spring Boot 中禁用 Actuator 端点安全性?... 62

2.1.3.25      我们如何监视所有 Spring Boot 微服务?... 62

2.1.3.26      什么是 WebSockets?... 62

2.1.3.27      什么是 Spring Data ?. 63

2.1.3.28      SpringData 项目所支持的关系数据存储技术:... 63

2.1.3.29      什么是 Spring Batch?... 63

2.1.3.30      什么是 FreeMarker 模板?... 63

2.1.3.31      如何集成 Spring Boot 和 ActiveMQ?... 63

2.1.3.32      什么是 Apache Kafka?... 64

2.1.3.33      什么是 Swagger?你用 Spring Boot 实现了它吗?... 64

2.1.3.34      前后端分离,如何维护接口文档 ?. 64

2.1.3.35      Spring Boot项目如何热部署?... 64

2.1.3.36      使用了哪些 starter maven 依赖项?... 64

2.1.3.37      Spring Boot 中的 starter 到底是什么 ?. 65

2.1.3.38      spring-boot-starter-parent 有什么用 ?. 65

2.1.3.39      Spring Boot 打成的 jar 和普通的 jar 有什么区别 ?. 65

2.1.3.40      运行 Spring Boot 有哪几种方式?... 65

2.1.3.41      Spring Boot 需要独立的容器运行吗?... 66

2.1.3.42      开启 Spring Boot 特性有哪几种方式?... 66

2.1.3.43      微服务中如何实现 session 共享 ?. 66

2.1.3.44      如何使用 Spring Boot 实现异常处理?... 66

2.1.3.45      如何使用 Spring Boot 实现分页和排序?... 66

2.1.3.46      Spring Boot 中如何实现定时任务 ?. 66

2.1.4              SpringCloud系列... 67

2.1.4.1        什么是 SpringCloud. 67

2.1.4.2        SpringBoot和SpringCloud. 67

2.1.4.3        SpringCloud和Dubbo. 67

2.1.4.4        Rest和RPC对比... 68

2.1.4.5        SpringCloud的核心组件... 68

2.1.4.6        SpringCloud架构原理:... 69

2.1.4.7        Zuul网关功能... 69

2.1.4.8        springcloud如何实现服务的注册?. 69

2.1.4.9        Eureka的服务注册与发现... 69

2.1.4.10      Eureka的我保护模式... 70

2.1.4.11      Eureka和ZooKeeper的区别... 70

2.1.4.12      负载均衡的意义是什么?. 71

2.1.4.13      ribbon的负载均衡策略... 71

2.1.4.14      什么是feigin?它的优点是什么?... 72

2.1.4.15      Ribbon和Feign的区别... 72

2.1.4.16      什么是Hystrix. 72

2.1.4.17      雪崩效应... 73

2.1.4.18      Hystrix的服务熔断和服务降级... 73

2.1.4.19      Hystrix断路器的作用... 73

2.1.4.20      微服务之间是如何独立通讯的... 74

2.1.4.21      微服务的优缺点? 说下在项目开发中碰到的坑... 74

2.1.4.22      你所知道的微服务技术栈... 75

2.1.4.23      什么是Spring Cloud Bus. 75

2.1.4.24      什么是SpringCloudConfig. 75

2.2                 数据库系列... 76

2.2.1              基础... 76

2.2.1.1        数据库三范式:... 76

2.2.1.2        SQL优化... 76

2.2.1.3        索引具体采用的哪种数据结构... 77

2.2.1.4        hash索引和btree索引的区别... 77

2.2.1.5        建立索引的规则:... 79

2.2.1.6        创建索引... 79

2.2.1.7        防止SQL注入四的种方法... 80

2.2.2              Mybatis系列... 81

2.2.2.1        什么是Mybatis?... 81

2.2.2.2        Mybaits的优缺点:... 81

2.2.2.3        MyBatis框架适用场合:... 82

2.2.2.4        MyBatis与Hibernate的区别... 82

2.2.2.5        #{}和${}的区别是什么?... 82

2.2.2.6        当实体类中的属性名和表中的字段名不一样 ,怎么办 ?... 82

2.2.2.7        模糊查询like语句该怎么写?. 83

2.2.2.8        通常一个Xml映射文件,都会写一个Dao接口与之对应,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?... 84

2.2.2.9        Mybatis是如何进行分页的?分页插件的原理是什么?... 84

2.2.2.10      Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?        84

2.2.2.11      如何执行批量插入... 85

2.2.2.12      如何获取自动生成的(主)键值?. 86

2.2.2.13      在mapper中如何传递多个参数?. 86

2.2.2.14      一对一、一对多的关联查询... 88

2.2.2.15      MyBatis实现一对一有几种方式,具体怎么操作... 89

2.2.2.16      MyBatis实现一对多有几种方式,怎么操作... 89

2.2.2.17      Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?        89

2.2.2.18      Mybatis是否支持延迟加载?它的实现原理是什么?... 89

2.2.2.19      Mybatis的一级、二级缓存... 90

2.2.2.20      什么是MyBatis的接口绑定?有哪些实现方式?... 90

2.2.2.21      使用MyBatis的mapper接口调用时有哪些要求?... 91

2.2.2.22      MyBatis面向Mapper编程工作原理... 91

2.2.2.23      Mybatis的Executor执行器之间有什么区别... 91

2.2.2.24      Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?... 92

2.2.3              Redis 的数据结构... 93

2.2.3.1        redis中的五种常用类型... 93

2.2.3.2        缓存穿透... 97

2.2.3.3        缓存击穿... 97

2.2.3.4        缓存雪崩... 98

2.2.3.5        数据库和缓存一致性... 101

2.2.3.6        4种相关模式... 102

2.2.3.7        Redis更新... 104

2.2.4              MySQL数据库引擎 Innodb 和 myisam 区别... 105

2.2.5              数据库的悲观锁和乐观锁应用场景... 106

2.2.6              海量数据过滤(100亿),黑名单过滤一个 url。... 107

3... 设计模式... 108

3.1                 设计模式的六大原则... 108

3.1.1              开放封闭原则(Open Close Principle):... 108

3.1.2              里氏代换原则(Liskov Substitution Principle):... 108

3.1.3              依赖倒转原则(Dependence Inversion Principle)... 108

3.1.4              接口隔离原则(Interface Segregation Principle)... 109

3.1.5              迪米特法则(最少知道原则)(Demeter Principle)... 109

3.1.6              单一职责原则(Principle of single responsibility)... 109

3.2                 单例模式... 110

3.2.1              懒汉式 线程不安全... 111

3.2.2              懒汉式 线程安全... 112

3.2.3              饿汉式 线程安全... 112

3.2.4              静态内部类方式 线程安全... 113

3.2.5              枚举单例 线程安全: 114

3.3                 工厂模式... 115

3.3.1              工厂模式好处... 115

3.3.2              工厂模式分类... 115

3.3.3              简单工厂... 116

3.3.4              工厂方法模式... 117

3.3.5              抽象工厂模式... 118

3.4                 代理模式... 119

3.4.1              什么是代理模式... 119

3.4.2              代理模式应用场景... 119

3.4.3              代理的分类... 119

3.4.4              三种代理的区别... 119

3.5                 建造者模式... 121

3.6                 模板方法模式... 121

3.7                 外观模式... 121

3.8                 原型模式... 121

3.9                 策略模式... 121

3.10               观察者模式... 121

4... 网络连接... 123

4.1                 http 和 https 的区别... 123

4.2                 七层模型各部分作用... 123

4.3                 三次握手... 124

4.4                 四次挥手... 127

4.5                 什么是 WebSockets?... 129

5... 数据结构... 130

5.1                 二叉查找树(BST):不平衡... 130

5.2                 平衡二叉树(AVL):旋转耗时... 130

5.3                 红黑树:树太高... 131

5.4                 B+树... 132

6... 运维... 133

6.1                 Kubernetes (K8S) 133

6.2                 Docker 135

  1. JAVA系列

    1. 常用容器

Java面试题_第1张图片

比较

List

Set

Map

继承接口

Collection

Collection

常见实现类

AbstractList(其常用子类有ArrayList、LinkedList、Vector)

AbstractSet(其常用子类有HashSet、LinkedHashSet、TreeSet)

HashMap、HashTable

常见方法

add( )、remove( )、clear( )、get( )、contains( )、size( )

add()、remove()、clear()、contains( )、size()

put( )、get( )、remove( )、clear( )、containsKey( )、containsValue( )、keySet( )、values( )、size( )

元素

可重复

不可重复(用equals()判断)

不可重复

顺序

有序

无序(实际上由HashCode决定)

线程安全

Vector线程安全

Hashtable线程安全

    1. ArrayList 和 linkedlist 区别。ArrayList 是否会越界

1、ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。(两者均非线程安全)

2、对于随机访问,ArrayList效率优于LinkedList。因为ArrayList直接通过数组下标找到元素;LinkedList要移动指针遍历每个元素直到找到为止

3、对于插入和删除操作,LinkedList效率优于ArrayList。因为ArrayList新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。

4、LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素

ArrayList自动扩容、没有长度限制

越界异常都发生在数组扩容之时,分别为10、15、22、33、49和73。数组的自动扩容机制,数组初始长度为10,第一次扩容为15=10+10/2,第二次扩容22=15+15/2,第三次扩容33=22+22/2...以此类推

当添加一个元素的时候,它可能会有两步来完成:1. 在 elementData[Size] 的位置存放此元素;2. 增大 Size 的值。

证明:两个线程a和b;当集合中已经添加了14个元素时,一个线程率先进入add()方法,在执行ensureCapacityInternal(size + 1)时,发现还可以添加一个元素,故数组没有扩容,但随后该线程被阻塞在此处。接着另一线程进入add()方法,执行ensureCapacityInternal(size + 1),由于前一个线程并没有添加元素,故size依然为14,依然不需要扩容,所以该线程就开始添加元素,使得size++,变为15,数组已经满了。而刚刚阻塞在elementData[size++] = e;语句之前的线程开始执行,它要在集合中添加第16个元素,而数组容量只有15个,所以就发生了数组下标越界异常!

    1. ArrayList 和 hashset 的区别。hashset 存的数是否有序。
  1. HashSet实现Set接口,ArrayList实现List接口,Set和List接口都实现Collection接口;
  2. list集合内的元素是有序的,元素可以重复,查询效率高,增删效率低;set元素无序且不可重复,是散列结构(通过散列表:散列单元指向链表)。
  3. HashSet与ArrayList均线程不安全
  4. ArrayList是一个有顺序的数组,每次放入其中的都是对象的引用,可重复放入。
  5. HashSet是一个无序集合,每次放入一个新的元素,都会对其进行检查对象是否存在,如果存在则不会放入。

HashSet、ArrayList、LinkedList使用add()方法将元素放入集合中,HashMap使用put()方法将元素放入map中

    1. HashMap与HashTable的区别
  1. HashMap线程不安全,HashTable线程安全,由于HashMap非线程安全,效率上可能高于Hashtable。
  2. HashMap、Hashtable不保证插入顺序,但是循环遍历时,输出顺序是不会改变的。
  3. HashMap是Hashtable的轻量级实现(非线程安全的实现),都实现了Map接口。 
  4. HashMap不能包含重复键,但可以包含重复值。
  5. HashMap允许null key和null value,而hashtable不允许。
  6. HashMap去掉了Hashtable中的contains方法,都提供的有containsvalue和containsKey方法。

最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。

    1. 如何决定使用 HashMap 还是 TreeMap
  1. HashMap: 适用于在Map中插入、删除和定位元素。Treemap: 适用于按自然顺序或自定义顺序遍历键(key)。
  2. HashMap 通常比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在需要排序的Map时候才用TreeMap.
  3. HashMap 非线程安全 TreeMap 非线程安全
  4.  
    HashMap的结果是没有排序的,而TreeMap输出的结果是排好序的。

HashMap的键的性质-它通常是一个简单的String甚至是一个数字。

而 String和Integer的计算哈希码的速度 比 整个对象的默认哈希码 计算要快得多;换句话说:

如果HashMap的键与存储在HashSet中的键是相同的对象,则性能将没有真正的区别。区别在于HashMap的键是哪种对象

    1. HashMap 的实现原理

HashMap概述:HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

HashMap的数据结构:在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根据hash值得到这个元素在数组中的位置(下标),如果该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。

需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)

    1. HashSet 的实现原理
  1. HashSet底层由HashMap实现
  2. HashSet的值存放于HashMap的key上
  3. HashMap的value统一为null
    1. Iterator怎么使用?有什么特点?

Java中lterator功能比较简单,并且只能单向移动:

  1. 使用方法iterator()要求容器返回一个Iterator,第一次調用Iterator的next()方法时,它返回序列的第一个元素,注意: iterator()方法是java.lang.lterable接口,被Collection继承

(2)使用next()获得序列中的下一个元素.

(3)使用hasNext()检查序列中是否还有元素.

(4)使用remove()将迭代器新返回的元素删除.

Iterator是Java迭代器最简单的实现,为List设计的Listlterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素

    1. Iterator和Listlterator有什么区别?
  1. Iterator可用来過历Set和List集合,但是Listterator只能用来這历List
  2. Iterator对集合只能是前向遍历, Listtierator可以前向也可以后向.
  3. ListIterator实现了Iterator接口,井包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等
    1. volatile 和 synchronized 区别
  1. volatile本质是告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  2. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别
  3. volatile仅能实现变量的修改可见性,不能保证原子性(同生共死);而synchronized则可以保证变量的修改可见性和原子性
  4. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  5. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
    1. synchronized 和 Lock 有什么区别?
  1. 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  2. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  3. synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  4. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  5. Lock锁适合大量代码的同步问题,synchronized锁适合少量代码的同步问题。
    1. 多态的原理

多态性:顾名思义就是拥有“多种形态”的含义,是指属性或方法在子类中表现为多种形态。(重写,重载  是多态的体现)

多态指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

抽象类和接口区别

1 .   接口中的方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),当然在jdk8中可以有default修饰的方法。 但是default修饰的方法也是且只能是public的。 jdk1.8之后允许接口中有静态方法,但是静态方法的访问修饰符也必须public,且默认省略就是public。 接口的访问修饰符必须是public,且省略就是public (这是在接口在单个文件中);接口如果定义在其他的文件中,缺省的时候就是包访问权限。抽象类可以包含普通方法。

2.    接口只能定义静态常量属性 (接口中的变量会被隐式的指定为 public static final 变量),抽象类既可以定义普通属性,也可以定义静态常量属性。

3 .   接口不包含构造方法,抽象类里可以包含构造方法。

4.   抽象类不能被实例化,但不代表它不可以有构造函数,抽象类可以有构造函数,备继承类扩充

5.   接口是核心,其定义了要做的事情,包含了许多的方法,但没有定义这些方法应该如何做。

6.    如果许多类实现了某个接口,那么每个类都要用代码实现那些方法

7.   如果某一些类的实现有共通之处,则可以抽象出来一个抽象类,让抽象类实现接口的公用的代码,而那些个性化的方法则由各个子类去实现。

死锁的原因

主要是:(1) 因为系统资源不足。(2) 进程运行推进的顺序不合适。(3) 资源分配不当等。如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

产生死锁的四个必要条件:(1) 互斥条件:一个资源每次只能被一个进程使用。(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立

    1. 反射

反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力

Java反射:

在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法

Java反射机制主要提供了以下功能:

  1. 在运行时判断任意一个对象所属的类。
  2. 在运行时构造任意一个类的对象。
  3. 在运行时判断任意一个类所具有的成员变量和方法。
  4. 在运行时调用任意一个对象的方法。 
    1. 队列

   
 队列是只允许在一端插入数据操作,在另一端进行删除数据操作的特殊线性表;进行插入操作的一端称为队尾(入队列),进行删除操作的一端称为队头(出队列);队列具有先进先出(FIFO)的特性。

队列通常提供的操作:

入队: 通常命名为push() ;  出队: 通常命名为pop()  ; 求队列中元素个数

判断队列是否为空;  获取队首元素。

    1. 基于数组的循环队列实现

 
以数组作为底层数据结构时,一般讲队列实现为循环队列。这是因为队列在顺序存储上的不足:每次从数组头部删除元素(出队)后,需要将头部以后的所有元素往前移动一个位置,这是一个时间复杂度为O(n)的操作:

    1. 接口幂等性

接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性

查询、删除操作是天然的幂等操作。

两种实现方案:  1. 通过代码逻辑判断实现    2. 使用token机制实现  

  1. 通过代码逻辑判断实现:

   通过一个中间字段进行判断;例如订单操作,orderStatus 处于01两种状态时,对订单执行0->1 的状态流转操作应该是具有幂等性的。这时候需要在执行update操作之前检测orderStatus是否已经=1,如果已经=1则直接返回true即可。

  1. 使用token机制实现:

1. 生成全局唯一的token,token放到redisjvm内存,token会在页面跳转时获取.存放到pageScope,支付请求提交先获取token

 2. 提交后后台校验token,执行提交逻辑,提交成功同时删除token,生成新的token更新redis ,这样当第一次提交后token更新了,页面再次提交携带的token是已删除的token后台验证会失败不让提交

  token特点:   要申请,一次有效性,可以限流

     注意: redis要用删除操作来判断token,删除成功代表token校验通过,如果用select+delete来校验token,存在并发问题,不建议使用

    1. 多线程系列
      1. Java 线程间实现同步

使用  synchronized关键字或lock()锁  实现多线程步;

synchronized: 具有原子性,有序性和可见性; volatile:具有有序性和可见性

原子性:

是指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。在多线程情况下,提供互斥访问,同一时刻只能有一个线程对数据进行操作。

可见性:

是指当一个线程修改了共享变量后,其他线程能够立即得知这个修改。

有序性:

在本线程内观察,所有操作都是有序的(即指令重排不会导致单线程程序执行结果与排序前有任何差别)。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。Volatile能避免重排序,确保有序性

      1. notify()与 notifyAll()的区别

调用了notify后只有一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。

      1. 创建线程池有哪几种方式?
  • newFixedThreadPool(int nThreads)

创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。

  • newCachedThreadPool()

创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。

  • newSingleThreadExecutor()

这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。

  • newScheduledThreadPool(int corePoolSize)

创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

      1. 如何创建线程池?

《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 new ThreadPoolExecutor 实例的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

Executors 返回线程池对象的弊端如下:

FixedThreadPool 和 SingleThreadExecutor :

允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。+

CachedThreadPool 和 ScheduledThreadPool :

允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。

 OOM:全称“Out Of Memory”,来源于java.lang.OutOfMemoryError。官方说明: Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. 意思就是说,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。

      1. ThreadPoolExecutor机制
        1. 概述

1、ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等等服务;

        1. 核心构造方法讲解

下面是ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueueThreadFactory threadFactory)核心的构造参数讲解

corePoolSize                核心线程池大小

maximumPoolSize       最大线程池大小

keepAliveTime               线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间

TimeUnit                       keepAliveTime时间单位

workQueue                    阻塞任务队列

threadFactory                新建线程工厂

RejectedExecutionHandler 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理

        1. 重点讲解:

比较容易误解的是:corePoolSize,maximumPoolSize,workQueue之间关系。

1.     当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。

2.    当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行

3.     当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务

4.   当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理

5.      当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程

6.       当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

        1. 线程池的运作流程

    1. JVM系列
      1.  
        JVM 的主要组成部分及其作用
      2. JVM包含两个子系统和两个组件:

两个子系统Class loader(类装载)Execution engine(执行引擎)

两个组件Runtime data area(运行时数据区)Native Interface(本地接口)

Class loader(类装载)根据给定的全限定类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。

Execution engine(执行引擎):执行classes中的指令。

Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。

Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。

      1. 作用 :

首先通过编译器把 Java 代码转换成字节码;类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。下图为Java程序运行机制详细说明

类的加载是指将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。

程序计数器(Program Counter Register):当前线程所执行字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;

Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息;

本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;

Java (Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;

方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

        1. 双亲委派模型的工作过程

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

        1.  
          类加载器的种类

Bootstrap ClassLoader:启动类加载器,这个类加载器将负责存放在/lib目录中、被-Xbootclasspath参数所指定的路径中,并且是虚拟机会识别的jar类库加载到内存中。更直白点说,就是我们常用的java.lang开头的那些类,一定是被Bootstrap ClassLoader加载的。

Extension ClassLoader:扩展类加载器,这个类加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载/lib/ext目录中的、或者被java.ext.dirs系统变量指定的路径中的所有类库。

Application ClassLoader:应用程序类加载器,这个类加载器由sun.misc.Launcher$AppClassLoader实现,它负责加载用户CLASSPATH环境变量指定的路径中的所有类库。如果应用程序中没有自定义过自己的类加载器,这个就是一个Java程序中默认的类加载器。

用户自定义的类加载器:用户在需要的情况下,可以实现自己的自定义类加载器,一般而言,在以下几种情况下需要自定义类加载器:

(1)隔离加载类。某些框架为了实现中间件和应用程序的模块的隔离,就需要中间件和应用程序使用不同的类加载器;

(2)修改类加载的方式。类加载的双亲委派模型并不是强制的,用户可以根据需要在某个时间点动态加载类;

(3)扩展类加载源,例如从数据库、网络进行类加载;

(4)防止源代码泄露。Java代码很容易被反编译和篡改,为了防止源码泄露,可以对类的字节码文件进行加密,并编写自定义的类加载器来加载自己的应用程序的类。

        1. 类的生命周期

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、

解析(Resulution)、初始化(Initialization)、使用(Using)、卸载(Unloading)

加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的。必须按此顺序开始。,不一定按此顺序结束。

      1. 深拷贝和浅拷贝

浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,

深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,

使用深拷贝的情况下,释放内存的时候不会出现,因浅拷贝而释放同一个内存的错误。

浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。

深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。

      1. Java会存在内存泄漏

内存泄漏

指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。

但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。

Java垃圾回收机制

在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

圾回收机制有效的防止了内存泄露,可以有效的使用可使用的内存。

垃圾回收有分代复制垃圾回收、标记垃圾回收、增量垃圾回收。

      1. 强引用、软引用、弱引用、虚引用

强引用(StrongReference):

在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。类似 “Object obj = new Object()” 这类的引用。

当一个对象被强引用变量引用时,它处于可达状态,是不可能被垃圾回收器回收的,即使该对象永远不会被用到也不会被回收。

当内存不足,JVM 开始垃圾回收,对于强引用的对象,就算是出现了 OOM 也不会对该对象进行回收,打死都不收。因此强引用有时也是造成 Java 内存泄露的原因之一

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示地将相应(强)引用赋值为 null,一般认为就是可以被垃圾收集器回收。(具体回收时机还要要看垃圾收集策略)。

demo 中尽管 o1已经被回收,但是 o2 强引用 o1,一直存在,所以不会被GC回收

软引用(SoftReference)

软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference 类来实现,可以让对象豁免一些垃圾收集。

弱引用(WeakReference)

 
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短。

虚引用(PhantomReference)

    “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

    虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

 
Java 的4种引用的级别由高到低依次为:强引用  >  软引用  >  弱引用  >  虚引用

当垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象Object来标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收

      1. 堆栈的区别

物理地址:

堆的物理地址分配对于对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记—压缩);堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理

栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。

内存分别:

堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。

栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。

存放的内容:

堆存放的是new创建的对象和数组。堆主要用来存放对象的

栈存放:局部变量,操作数栈,返回结果。栈主要是用来执行程序的。

PS:静态变量放在方法区;静态的对象还是放在堆。

程序的可见度:

堆对于整个应用程序都是共享、可见的。

栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。

总结:

堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,所以存取速度较慢。 

栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。

      1. 判断对象是否可以被回收

引用计数器法:

为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数

 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;

可达性分析算法:

从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

      1. 分代垃圾回收器工作原理

Java堆从 GC的角度还可以细分为:

新生代(Eden区、From Survivor区和To Survivor区)老年代

新生代:

是用来存放新生的对象。一般占据堆的1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为Eden 区、ServivorFrom、 ServivorTo 3个区。

老年代:主要存放应用程序中生命周期长的内存对象。

老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC 前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。

MajorGC采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出OOM (Out of Memory)异常。

永久代:

指内存的永久保存区域,主要存放Class 和Meta (元数据)的信息,Class在被加载的时候被放入永久区域,它和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class 的增多而胀满,最终抛出OOM异常。

JAVA8与元数据 :

在Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。

  元空间的本质和永久代类似,元空间与永久代之间最大的区别在于: 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。

类的元数据放入native memory,字符串池和类的静态变量放入java 堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。

Minor GC和Major GC区别:

Minor GC:

新生代GC,指发生在新生代的垃圾收集动作,所有的Minor GC都会触发全世界的暂停(stop-the-world),停止应用程序的线程,不过这个过程非常短暂。简单理解就是发生在年轻代的GC。三步(复制--清空--互换).

Major GC/Full GC

老年代GC,指发生在老年代的GC。

Minor GC的触发条件为:

  1. 当产生一个新对象,新对象优先在Eden区分配。如果Eden区放不下这个对象,虚拟机会使用复制算法发生一次Minor GC,清除掉无用对象,同时将存活对象移动到Survivor的其中一个区(fromspace区或者tospace区)。
  2. 第一次Yong GC(Minor GC),Eden区还存活的对象复制到Surviver区的“To”区,“From”区还存活的对象也复制到“To”区,
  3. 再清空Eden区和From区,这样就等于“From”区完全是空的了,而“To”区也不会有内存碎片产生,
  4. 等到第二次Yong GC时,“From”区和“To”区角色互换,很好的解决了内存碎片的问题。
  5. 虚拟机会给每个对象定义一个对象年龄(Age)计数器,对象在Survivor区中每“熬过”一次GC,年龄就会+1。待到年龄到达一定岁数(默认是15岁),虚拟机就会将对象移动到年老代。
  6. 如果新生对象在Eden区无法分配空间时,此时发生Minor GC。发生MinorGC,对象会从Eden区进入Survivor区,如果Survivor区放不下从Eden区过来的对象时,此时会使用分配担保机制将对象直接移动到年老代。

Major GC的触发条件:

Major GC又称为Full GC。当年老代空间不够用的时候,虚拟机会使用“标记—清除”或者“标记—整理”算法清理出连续的内存空间,分配对象使用。

      1. JVM 的垃圾回收算法

标记-清除算法:

标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。

复制算法:

按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。

标记-整理算法:

标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。

分代算法:

根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。

JVM 的垃圾回收器

回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,

回收老年代的收集器包括Serial Old、Parallel Old、CMS,

还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。

Serial收集器(复制算法):

新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

ParNew收集器 (复制算法):

新生代并行收集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;

Parallel Scavenge收集器 (复制算法):

新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;

Serial Old收集器 (标记-整理算法):

老年代单线程收集器,Serial收集器的老年代版本;

Parallel Old收集器 (标记-整理算法):

老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;

CMS(Concurrent Mark Sweep)收集器(标记-清除算法):

CMS使用的是标记-清除的算法实现的,老年代并行收集器,是以牺牲吞吐量为代价来获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。

G1(Garbage First)收集器 (标记-整理算法):

         Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

      1. G1垃圾回收过程

G1垃圾回收过程主要包括三个:

  1. 年轻代回收(young gc)过程
  2. 老年代并发标记(concurrent marking)过程
  3. 混合回收过程(mixed gc)。

应用程序分配内存,当年轻代的Eden区用尽时开始年轻代回收过程;当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程;标记完成马上开始混合回收过程。

尽管G1堆内存仍然是分代的,但是同一个代的内存不再采用连续的内存结构。

三个关于内存的概念:代,区和内存分段

G1把堆内存分为年轻代和老年代。年轻代分为Eden和Survivor两个区,老年代分为Old和Humongous两个区。代和区都是逻辑概念。

G1把堆内存分为大小相等的内存分段,默认情况下会把内存分为2048个内存分段,可以用-XX:G1HeapRegionSize调整内存分段的个数。比如32G堆内存,2048个内存分段每段的大小为16M。这相当于把内存化整为零。内存分段是物理概念,代表实际的物理内存空间。

每个内存分段都可以被标记为Eden区,Survivor区,Old区,或者Humongous区。这样属于不同代,不同区的内存分段就可以不必是连续内存空间了。

新分配的对象会被分配到Eden区的内存分段上,每一次年轻代的回收过程都会把Eden区存活的对象复制到Survivor区的内存分段上,把Survivor区继续存活的对象年龄加1,如果Survivor区的存活对象年龄达到某个阈值(比如15,可以设置),Survivor区的对象会被复制到Old区。

复制过程是把源内存分段中所有存活的对象复制到空的目标内存分段上,复制完成后,源内存分段没有了存活对象,变成了可以使用的空的Eden内存分段了;而目标内存分段的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。Humongous区用于保存大对象,如果一个对象占用的空间超过内存分段的一半(比如上面的8M),则此对象将会被分配在Humongous区。如果对象的大小超过一个甚至几个分段的大小,则对象会分配在物理连续的多个Humongous分段上。Humongous对象因为占用内存较大并且连续会被优先回收。

        1. Remembered Set

理解回收过程,需要先了解记忆集合(Remembered Set),以下简称RS。

为了在回收单个内存分段的时候不必对整个堆内存的对象进行扫描(单个内存分段中的对象可能被其他内存分段中的对象引用)引入了RS数据结构。

RS使得G1可以在年轻代回收的时候不必去扫描老年代的对象,从而提高了性能。

每一个内存分段都对应一个RS,RS保存了来自其他分段内的对象对于此分段的引用。

对于属于年轻代的内存分段(Eden和Survivor区的内存分段)来说,RS只保存来自老年代的对象的引用。这是因为年轻代回收是针对全部年轻代的对象的,反正所有年轻代内部的对象引用关系都会被扫描,所以RS不需要保存来自年轻代内部的引用。

对于属于老年代分段的RS来说,也只会保存来自老年代的引用,这是因为老年代的回收之前会先进行年轻代的回收,年轻代回收后Eden区变空了,G1会在老年代回收过程中扫描Survivor区到老年代的引用。

  1. 框架
    1. Spring家族
      1. SpringMVC系列
        1.  
          SpringMVC运行流程描述

工作流程:

1 用户发送请求至前端控制器DispatcherServlet;

(2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;

(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;

(4)DispatcherServlet 调用 HandlerAdapter处理器适配器;

(5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);

(6)Handler执行完成返回ModelAndView;

(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;

(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;

(9)ViewResolver解析后返回具体View;

(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)

(11)DispatcherServlet响应用户。

        1. Springmvc的优点:
  1. 可以支持各种视图技术,而不仅仅局限于JSP;、
  2. 与Spring框架集成(如IoC容器、AOP等);
  3. 清晰的角色分配:前端控制器(dispatcherServlet) , 处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)
  4. 支持各种请求资源的映射策略。
        1. SpringMVC常用的注解

@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。

@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。

@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户端。

SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代?

一般用@Controller注解,

也可以使用@RestController;@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。

        1. SpringMVC怎么样设定重定向和转发的?

转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4"

 
重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"

        1. 如何解决POST请求中文乱码问题,GET的又如何处理呢?

(1)解决post请求乱码问题:

在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;

CharacterEncodingFilter

org.springframework.web.filter.CharacterEncodingFilter

encoding

utf-8

CharacterEncodingFilter

/*

(2)get请求中文参数出现乱码解决方法有两个:

①  修改tomcat配置文件添加编码与工程编码一致,如下:

protocol="HTTP/1.1"  redirectPort="8443"/>

②   另外一种方法对参数进行重新编码:

StringuserName=newString(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")

ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。

        1. Spring MVC的异常处理 ?

可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器,在异常处理器中添视图页面即可。

        1. SpringMvc的控制器是不是单例模式,有什么问题,怎么解决?

是单例模式,所以在多线程访问的时候有线程安全问题,不建议用同步,会影响性能的

解决方案:是在控制器里面不能写成员变量

        1. 在拦截请求中,我想拦截get方式提交的方法,怎么配置?

可以在@RequestMapping注解里面加上method=RequestMethod.GET。

        1. 怎样在方法里面得到Request,或者Session?

直接在方法的形参中声明request,SpringMvc就自动把request对象传入。

如果想在拦截的方法里面得到从前台传入的参数,怎么得到?

直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。

        1. 前台有很多个参数传入,并且这些参数都是一个对象的,怎样快速得到这个对象?

直接在方法中声明这个对象,SpringMvc就自动会把属性赋值到这个对象里面。

        1. SpringMvc中函数的返回值是什么?

返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的,但一般用String比较好。

        1. SpringMvc用什么对象从后台向前台传递数据的?

通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式拿到。

        1. 怎么样把ModelMap里面的数据放入Session里面?

可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。

        1. SpringMvc里面拦截器是怎么写的:

有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可:

   

   

   

   

          

          

   

        1. 拦截器

依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理

        1. 过滤器

依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等

        1. 拦截器和过滤器的区别:
  1. 拦截器基于java的反射机制,而过滤器是基于函数回调
  2. 拦截器不依赖于servlet容器,过滤器依赖servlet容器
  3. 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用
  4. 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
  5. 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器注入一个service,可以调用业务逻辑

过滤器(Filter)的触发时机是容器后,servlet之前,所以过滤器的 

doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) 的入参是ServletRequest,而不是HttpServletRequest,因为过滤器在HttpServlet(拦截器)之前;

 
过滤器:

 
拦截器:拦截器是被包裹在过滤器之中

      1. Spring系列
        1. Spring是什么?

Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。

常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置。

Spring主要由以下几个模块组成:

Spring Core:核心类库,提供IOC服务;

Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);

Spring AOP:AOP服务;

Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;

Spring ORM:对现有的ORM框架的支持

Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;

Spring MVC:提供面向Web应用的Model-View-Controller实现。

        1. Spring 的优点?
  1. spring属于低侵入式设计,代码的污染极低;
  2. spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
  3. Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
  4. spring对于主流的应用框架提供了集成支持。
        1. Spring的AOP理解:

OOP面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。

AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。

AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

        1. Spring AOP中的动态代理

主要有两种方式,JDK动态代理和CGLIB动态代理:

  1. JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
  2. 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
  3. InvocationHandler 的 invoke(Objectproxy,Methodmethod,Object[] args):proxy是最终生成的代理实例;method 是被代理目标实例的某个具体方法;args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
        1. Spring的IoC理解:
  1. IOC(Inversion Of Controll,控制反转),DI(Dependency Injection)叫依赖注入,是对IoC更简单的诠释。IOC就是将原本在程序中手动创建(new)对象的控制权,交由给Spring框架来管理。
  2. IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。对象与对象之间松散耦合,也利于功能的复用。
  3. 最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
  4. Spring IOC三种注入方式 :构造器注入、setter方法注入、根据注解注入。
        1. BeanFactory和ApplicationContext有什么区别?
  1. BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
  2. BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
    1. 继承MessageSource,因此支持国际化。
    2. 统一的资源文件访问方式。
    3. 提供在监听器中注册bean的事件。
    4. 同时加载多个配置文件。
    5. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
    6. BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
    7. ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
    8. 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
  3. BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
  4. BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

        1. Spring Bean的生命周期?

首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;

Spring上下文中的Bean生命周期也类似,如下:

  1. 实例化Bean:

对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

  1. 设置对象属性(依赖注入):

实例化后的对象被封装在BeanWrapper对象中, 接着Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。

  1. 处理Aware接口:

接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:

①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;

②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。

③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;

  1. BeanPostProcessor

如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。

  1. InitializingBean与init-method:

如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;

以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。

  1. DisposableBean

当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;

  1. destroy-method

最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

        1. 解释Spring支持的几种bean的作用域。

Spring容器中的bean可以分为5个范围:

  1. singleton:唯一bean实例,Spring中的bean默认都是单例的。
  2. prototype:每次请求都会创建一个新的bean实例。
  3. request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
  4. session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
  5. global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。
        1. Spring框架中的单例Beans是线程安全的么?

Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。

但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。

        1. Spring如何处理线程并发问题?

在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。

ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

        1. Spring基于xml注入bean的几种方式:
  1. Set方法注入;
  2. 构造器注入:①通过index设置参数的位置;②通过type设置参数类型;
  3. 静态工厂注入;
  4. 实例工厂;
        1. Spring的自动装配:

在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。

在Spring框架xml配置中共有5种自动装配:

(1)no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。

(2)byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。

(3)byType:通过参数的数据类型进行自动装配。

(4)constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。

(5)autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

基于注解的方式:

使用@Autowired注解来自动装配指定的bean。

在使用@Autowired注解之前需要在Spring配置文件进行配置,

。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。

自动装配的局限性是:

重写:你仍需用配置来定义依赖,意味着总要重写自动装配。

基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。

模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。

在使用@Autowired时,首先在容器中查询对应类型的bean:

  1. 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
  2. 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
  3. 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

@Autowired可用于:构造函数、成员变量、Setter方法

        1. @Autowired和@Resource之间的区别

1、 @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。

2、 @Autowired默认按类型装配(这个注解是属于spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下:

@Autowired () @Qualifier ( "baseDao" )

private BaseDao baseDao;

3、@Resource(这个注解属于J2EE的)默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

@Resource (name= "baseDao" )

private BaseDao baseDao;

        1. 常用注解

@Controller("Bean的名称")

定义控制层Bean,如Action

@Autowired  (Srping提供的)

默认按类型匹配,自动装配(Srping提供的),可以写在成员属性上,或写在setter方法上

@Service("Bean的名称")

定义业务层Bean

@Resource  

默认按名称装配,当找不到名称匹配的bean再按类型装配.

@Repository("Bean的名称")

定义DAO层Bean

--定义Bean的作用域和生命过程

@Scope("prototype")

值有:singleton,prototype,session,request,session,globalSession

@Component  

定义Bean, 不好归类时使用.

@PostConstruct 

相当于init-method,使用在方法上,当Bean初始化时执行。

@PreDestroy 

相当于destory-method,使用在方法上,当Bean销毁时执行。

@Autowired 根据类型注入, 

@Resource 默认根据名字注入,其次按照类型搜索

@Autowired @Qualifie("userService") 两个结合起来可以根据名字和类型注入

@ResponseBody的作用将返回的 对象(类数据对象) 直接转成了json格式

@Required注解

这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。

@Autowired注解

@Autowired注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。

        1. Spring 框架中都用到了哪些设计模式?
  1. 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
  2. 单例模式:Bean默认为单例模式。
  3. 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
  4. 模板方法:用来解决代码重复的问题。

比如.RestTemplate,JmsTemplate,JpaTemplate。

  1. 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现--ApplicationListener。
        1. Spring事务的实现方式和实现原理:

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

(1)Spring事务的种类:

spring支持编程式事务管理声明式事务管理两种方式:

  1. 编程式事务管理使用TransactionTemplate。
  2. 声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。

声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

(2)spring的事务传播行为:

spring事务的传播行为指:当多个事务同时存在的时候,spring如何处理这些事务的行为。

① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘

③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

(3)Spring中的隔离级别

①ISOLATION_DEFAULT:这是个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。

②ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。

③ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。

④ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。

⑤ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。

        1. Spring框架中有哪些不同类型的事件?

Spring 提供了以下5种标准的事件:

  1. 上下文更新事件(ContextRefreshedEvent):

在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。

  1. 上下文开始事件(ContextStartedEvent):

当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。

  1. 上下文停止事件(ContextStoppedEvent):

当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。

  1. 上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
  2. 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。

如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。

        1. 解释一下Spring AOP里面的几个名词:
  1. 切面(Aspect):被抽取的公共模块,可能会横切多个对象。在Spring AOP中,切面可以使用通用类(基于模式的风格)或者在普通类中以@AspectJ注解来实现。
  2. 连接点(Join point):指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。
  3. 通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
  4. 切入点(Pointcut):切入点是指 我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add*、search*。
  5. 引入(Introduction):(也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现IsModified接口,以便简化缓存机制。
  6. 目标对象(Target Object):被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做被通知(adviced)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
  7. 织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。
  8. 切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。切入点使得定位通知(advice)可独立于OO层次。例如,一个提供声明式事务管理的around通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。
        1. Spring通知有哪些类型?

(1)前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

(2)返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

(3)抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。

(4)后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

(5)环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。

同一个aspect,不同advice的执行顺序:

①没有异常情况下的执行顺序:

around before advice

before advice

target method 执行

around after advice

after advice

afterReturning

②有异常情况下的执行顺序:

around before advice

before advice

target method 执行

around after advice

after advice

afterThrowing:异常发生

java.lang.RuntimeException: 异常发生

      1. SpringBoot系列
        1. 什么是 Spring Boot?

Spring Boot 是 Spring 开源组织下的子项目,是 Spring 的组件,简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

        1. Spring Boot 有哪些优点?
  1. 容易上手,提升开发效率,为 Spring 开发提供一个更快、更广泛的入门体验。开箱即用,远离繁琐的配置。
  2. 提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查和外部化配置等。
  3. 没有代码生成,也不需要XML配置。
  4. 避免大量的 Maven 导入和各种版本冲突。
        1. Spring Boot Starter 的工作原理
  1. Spring Boot 在启动时会去依赖的 Starter 包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的 Jar 包去扫描项目所依赖的 Jar 包。
  2. 根据 spring.factories 配置加载 AutoConfigure 类
  3. 根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context
        1. Spring Boot 中的监视器是什么?

Spring boot actuator 是 spring 启动框架中的重要功能之一。

Spring boot 监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。

监视器模块公开了一组可直接作为 HTTP URL 访问的REST 端点来检查状态。

        1. 运行 Spring Boot有哪几种方式?

(1)打包用命令或者放到容器中运行;

(2)用Maven/ Gradle插件运行;

(3)直接执行main方法运行。

        1. 如何在自定义端口上运行 Spring Boot 应用程序?

为了在自定义端口上运行 Spring Boot 应用程序,可以在application.properties 中指定端口。server.port = 8090

        1. 如何使用 Spring Boot 实现异常处理?

Spring 提供了一种使用 ControllerAdvice 处理异常的非常有用的方法。 我们通过实现一个 ControlerAdvice 类,来处理控制器类抛出的所有异常。

        1. 如何使用 Spring Boot 实现分页和排序?

使用 Spring Boot 实现分页非常简单。使用 Spring Data-JPA 可以实现将可分页的传递给存储库方法

        1. 什么是 JavaConfig?

Spring JavaConfig 是 Spring 社区的产品,它提供了配置 Spring IoC 容器的Java 方法。因此它有助于避免使用 XML 配置。

使用 JavaConfig 的优点在于:

面向对象的配置。由于配置被定义为 JavaConfig 中的类,因此用户可以充分利用Java 中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean 方法等。

减少或消除 XML 配置。基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在 XML 和 Java 之间来回切换。JavaConfig 为开发人员提供了一种纯 Java 方法来配置与 XML 配置概念相似的 Spring 容器。从技术角度来讲,只使用 JavaConfig 配置类来配置容器是可行的,但实际上很多人认为将JavaConfig 与 XML 混合匹配是理想的。

类型安全和重构友好。JavaConfig 提供了一种类型安全的方法来配置 Spring容器。由于 Java 5.0 对泛型的支持,现在可以按类型而不是按名称检索 bean,不需要任何强制转换或基于字符串的查找。

        1. Spring Boot 的核心注解是哪个?主要由哪几个注解组成?

        1. Spring Boot 自动配置原理是什么?

1、当SpringBoot应用启动的时候,就从主方法里面进行启动的。

@SpringBootApplication
public 
class SpringBoot02ConfigAutoconfigApplication {

public static void main(String[] args) {

SpringApplication.run(SpringBoot02ConfigAutoconfigApplication.class, args);

}

}

它主要加载了@SpringBootApplication注解主配置类,这个@SpringBootApplication注解主配置类里边最主要的功能就是SpringBoot开启了一个@EnableAutoConfiguration注解的自动配置功能。

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan(excludeFilters ={

@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),

@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

public @interface SpringBootApplication{

2、@EnableAutoConfiguration作用:

它主要利用了一个EnableAutoConfigurationImportSelector选择器给Spring容器中来导入一些组件。

@Import(EnableAutoConfigurationImportSelector.class)
public 
@interface EnableAutoConfiguration 

3注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心

@Configuration:

@SpringBootConfiguration:我们点进去以后可以发现底层是Configuration注解,说白了就是支持JavaConfig的方式来进行配置(使用Configuration配置类等同于XML文件)。

@EnableAutoConfiguration:

开启自动配置功能,给容器导入META-INF/spring.factories 里定义的自动配置类。筛选有效的自动配置类。每一个自动配置类结合对应的 xxxProperties.java 读取配置文件进行自动配置功能。

@ComponentScan:

开启组件扫描。默认是扫描当前类下的package。将@Controller/@Service/@Component/@Repository等注解加载到IOC容器中。

        1. 如何理解 Spring Boot 配置加载顺序?

在 Spring Boot 里面,可以使用以下几种方式来加载配置。

1)properties文件;   2)YAML文件;

3)系统环境变量;     4)命令行参数;

        1. 什么是 YAML?

YAML 是一种人类可读的数据序列化语言。它通常用于配置文件。与属性文件相比,如果我们想要在配置文件中添加复杂的属性,YAML 文件就更加结构化,而且更少混淆。可以看出 YAML 具有分层配置数据。

        1.  YAML 配置的优势在哪里 ?
  1. 配置有序,在一些特殊的场景下,配置有序很关键
  2. 支持数组,数组中的元素可以是基本数据类型也可以是对象
  3. 简洁: 相比 properties 配置文件,YAML 还有一个缺点,就是不支持 @PropertySource 注解导入自定义的 YAML 配置。
        1. Spring Boot 是否可以使用 XML 配置 ?

Spring Boot 推荐使用 Java 配置而非 XML 配置,但是 Spring Boot 中也可以使用 XML 配置,通过 @ImportResource 注解可以引入一个 XML 配置。

        1. application和properties 有何区别 ?

单纯做 Spring Boot 开发,可能不太容易遇到 bootstrap.properties 配置文件,但是在结合 Spring Cloud 时,这个配置就会经常遇到了,特别是在需要加载一些远程配置文件的时侯。

        1. spring boot 核心的两个配置文件:

bootstrap (. yml 或者 . properties):boostrap 由父 ApplicationContext 加载的,比 applicaton 优先加载,配置在应用程序上下文的引导阶段生效。一般来说我们在 Spring Cloud Config 或者 Nacos 中会用到它。且 boostrap 里面的属性不能被覆盖;

application (. yml 或者 . properties): 由ApplicatonContext 加载,用于 spring boot 项目的自动化配置。

        1. 什么是 Spring Profiles?

Spring Profiles 允许用户根据配置文件(dev,test,prod 等)来注册 bean。因此,当应用程序在开发中运行时,只有某些 bean 可以加载,而在 PRODUCTION中,某些其他 bean 可以加载。假设我们的要求是 Swagger 文档仅适用于 QA 环境,并且禁用所有其他文档。这可以使用配置文件来完成。Spring Boot 使得使用配置文件非常简单。


<profile>
        dev
        
                dev
        

        
                true
        
  profile>
  <profile>
        dvi
        
                 dvi
         

  profile>

        1. 如何实现 Spring Boot 应用程序的安全性?

为了实现 Spring Boot 的安全性,我们使用 spring-boot-starter-security 依赖项,并且必须添加安全配置。它只需要很少的代码。配置类将必须扩展WebSecurityConfigurerAdapter 并覆盖其方法。

        1. 比较一下 Spring Security 和 Shiro 各自的优缺点 ?

由于 Spring Boot 官方提供了大量的非常方便的开箱即用的 Starter ,包括 Spring Security 的 Starter ,使得在 Spring Boot 中使用 Spring Security 变得更加容易,甚至只需要添加一个依赖就可以保护所有的接口,所以,如果是 Spring Boot 项目,一般选择 Spring Security 。当然这只是一个建议的组合,单纯从技术上来说,无论怎么组合,都是没有问题的。Shiro 和 Spring Security 相比,主要有如下一些特点:

Spring Security 是一个重量级的安全管理框架;Shiro 是一个轻量级的安全管理框架

Spring Security 概念复杂,配置繁琐;Shiro 概念简单、配置简单

Spring Security 功能强大;Shiro 功能简单

        1. Spring Boot 中如何解决跨域问题 ?

跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的 SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。

@Configuration

public class CorsConfig implements WebMvcConfigurer {

    @Override

    public void addCorsMappings(CorsRegistry registry) {

        registry.addMapping("/**").allowedOrigins("*").allowCredentials(true)

     .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").maxAge(3600);

    }

}

项目中前后端分离部署,所以需要解决跨域的问题。

我们使用cookie存放用户登录的信息,在spring拦截器进行权限控制,当权限不符合时,直接返回给用户固定的json结果。

当用户登录以后,正常使用;当用户退出登录状态时或者token过期时,由于截器和跨域的顺序有问题,出现了跨域的现象。

我们知道一个http请求,先走filter,到达servlet后才进行拦截器的处理,如果我们把cors放在filter里,就可以优先于权限拦截器执行。

@Configuration

public class CorsConfig {

    @Bean

    public CorsFilter corsFilter() {

        CorsConfiguration corsConfiguration = new CorsConfiguration();

        corsConfiguration.addAllowedOrigin("*");

        corsConfiguration.addAllowedHeader("*");

        corsConfiguration.addAllowedMethod("*");

        corsConfiguration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();

        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);

        return new CorsFilter(urlBasedCorsConfigurationSource);

    }

}

        1. 什么是 CSRF 攻击?

CSRF 代表跨站请求伪造。这是一种攻击,迫使最终用户在当前通过身份验证的Web 应用程序上执行不需要的操作。CSRF 攻击专门针对状态改变请求,而不是数据窃取,因为攻击者无法查看对伪造请求的响应。

        1. Spring Boot 中的监视器是什么?

Spring boot actuator 是 spring 启动框架中的重要功能之一。Spring boot 监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作为 HTTP URL 访问的REST 端点来检查状态。

        1. 如何在 Spring Boot 中禁用 Actuator 端点安全性?

默认情况下,所有敏感的 HTTP 端点都是安全的,只有具有 Actuator角色的用户才能访问它们。安全性是使用标准的 HttpServletRequest.isUserInRole 方法实施的。 我们可以使用来禁用安全性。只有在执行机构端点在防火墙后访问时,才建议禁用安全性。

        1. 我们如何监视所有 Spring Boot 微服务?

Spring Boot 提供监视器端点以监控各个微服务的度量。这些端点对于获取有关应用程序的信息(如它们是否已启动)以及它们的组件(如数据库等)是否正常运行很有帮助。但是,使用监视器的一个主要缺点或困难是,我们必须单独打开应用程序的知识点以了解其状态或健康状况。想象一下涉及 50 个应用程序的微服务,管理员将不得不击中所有 50 个应用程序的执行终端。为了帮助我们处理这种情况,我们将使用位于的开源项目。 它建立在 Spring Boot Actuator 之上,它提供了一个 Web UI,使我们能够可视化多个应用程序的度量。

        1. 什么是 WebSockets?

WebSocket 是一种计算机通信协议,通过单个 TCP 连接提供全双工通信信道。

1、WebSocket 是双向的 -使用 WebSocket 客户端或服务器可以发起消息发送。

2、WebSocket 是全双工的 -客户端和服务器通信是相互独立的。

3、单个 TCP 连接 -初始连接使用 HTTP,然后将此连接升级到基于套接字的连接。然后这个单一连接用于所有未来的通信

4、Light -与 http 相比,WebSocket 消息数据交换要轻得多。

        1. 什么是 Spring Data ?

Spring Data 是 Spring 的一个子项目。用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据库的访问变得方便快捷。Spring Data 具有如下特点:

  1. SpringData 项目支持 NoSQL 存储:
  2. MongoDB (文档数据库)
  3. Neo4j(图形数据库)
  4. Redis(键/值存储)
  5. Hbase(列族数据库)
        1. SpringData 项目所支持的关系数据存储技术:

JDBC     JPA

Spring Data Jpa 致力于减少数据访问层 (DAO) 的开发量. 开发者唯一要做的,就是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成!Spring Data JPA 通过规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。

        1. 什么是 Spring Batch?

Spring Boot Batch 提供可重用的函数,这些函数在处理大量记录时非常重要,包括日志/跟踪,事务管理,作业处理统计信息,作业重新启动,跳过和资源管理。它还提供了更先进的技术服务和功能,通过优化和分区技术,可以实现极高批量和高性能批处理作业。简单以及复杂的大批量批处理作业可以高度可扩展的方式利用框架处理重要大量的信息。

        1. 什么是 FreeMarker 模板?

FreeMarker 是一个基于 Java 的模板引擎,最初专注于使用 MVC 软件架构进行动态网页生成。使用 Freemarker 的主要优点是表示层和业务层的完全分离。程序员可以处理应用程序代码,而设计人员可以处理 html 页面设计。最后使用freemarker 可以将这些结合起来,给出最终的输出页面。

        1. 如何集成 Spring Boot 和 ActiveMQ?

对于集成 Spring Boot 和 ActiveMQ,我们使用依赖关系。 它只需要很少的配置,并且不需要样板代码。

        1. 什么是 Apache Kafka?

Apache Kafka 是一个分布式发布 - 订阅消息系统。它是一个可扩展的,容错的发布 - 订阅消息系统,它使我们能够构建分布式应用程序。这是一个 Apache 顶级项目。Kafka 适合离线和在线消息消费。

        1. 什么是 Swagger?你用 Spring Boot 实现了它吗?

Swagger 广泛用于可视化 API,使用 Swagger UI 为前端开发人员提供在线沙箱Swagger 是用于生成 RESTful Web 服务的可视化表示的工具,规范和完整框架实现。它使文档能够以与服务器相同的速度更新。当通过 Swagger 正确定义时,消费者可以使用最少量的实现逻辑来理解远程服务并与其进行交互。因此,Swagger消除了调用服务时的猜测。

        1. 前后端分离,如何维护接口文档 ?

前后端分离开发日益流行,大部分情况下,我们都是通过 Spring Boot 做前后端分离开发,前后端分离一定会有接口文档,不然会前后端会深深陷入到扯皮中。一个比较笨的方法就是使用 word 或者 md 来维护接口文档,但是效率太低,接口一变,所有人手上的文档都得变。在 Spring Boot 中,这个问题常见的解决方案是 Swagger ,使用 Swagger 我们可以快速生成一个接口文档网站,接口一旦发生变化,文档就会自动更新,所有开发工程师访问这一个在线网站就可以获取到最新的接口文档,非常方便。

        1. Spring Boot项目如何热部署?

使用spring-boot-devtools  在项目的pom文件中添加依赖:

 

        

     org.springframework.boot     

     spring-boot-devtools 

然后:使用 shift+ctrl+alt+"/" (IDEA中的快捷键) 选择"Registry" 然后勾选 compiler.automake.allow.when.app.running

        1. 使用了哪些 starter maven 依赖项?

使用了下面的一些依赖项

spring-boot-starter-activemq

spring-boot-starter-security

这有助于增加更少的依赖关系,并减少版本的冲突。

        1. Spring Boot 中的 starter 到底是什么 ?

首先,这个 Starter 并非什么新的技术点,基本上还是基于 Spring 已有功能来实现的。首先它提供了一个自动化配置类,一般命名为 XXXAutoConfiguration ,在这个配置类中通过条件注解来决定一个配置是否生效(条件注解就是 Spring 中原本就有的),然后它还会提供一系列的默认配置,也允许开发者根据实际情况自定义相关配置,然后通过类型安全的属性注入将这些配置属性注入进来,新注入的属性会代替掉默认属性。正因为如此,很多第三方框架,我们只需要引入依赖就可以直接使用了。当然,开发者也可以自定义 Starter

        1. spring-boot-starter-parent 有什么用 ?

新创建一个 Spring Boot 项目,默认都是有 parent 的,这个 parent 就是 spring-boot-starter-parent ,spring-boot-starter-parent 主要有如下作用:

  1. 定义了 Java 编译版本为 8 。
  2. 使用 UTF-8 格式编码。
  3. 继承自 spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写依赖时才不需要写版本号。
  4. 执行打包操作的配置。
  5. 自动化的资源过滤。
  6. 自动化的插件配置。
  7. 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。
        1. Spring Boot 打成的 jar 和普通的 jar 有什么区别 ?

Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 java -jar xxx.jar 命令来运行,这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。

Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes 目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。

        1. 运行 Spring Boot 有哪几种方式?

1)打包用命令或者放到容器中运行

2)用 Maven/ Gradle 插件运行

3)直接执行 main 方法运行

        1. Spring Boot 需要独立的容器运行吗?

可以不需要,内置了 Tomcat/ Jetty 等容器。

        1. 开启 Spring Boot 特性有哪几种方式?

1)继承spring-boot-starter-parent项目

2)导入spring-boot-dependencies项目依赖

        1. 微服务中如何实现 session 共享 ?

在微服务中,一个完整的项目被拆分成多个不相同的独立的服务,各个服务独立部署在不同的服务器上,各自的 session 被从物理空间上隔离开了,但是经常,我们需要在不同微服务之间共享 session ,常见的方案就是 Spring Session + Redis 来实现 session 共享。将所有微服务的 session 统一保存在 Redis 上,当各个微服务对 session 有相关的读写操作时,都去操作 Redis 上的 session 。这样就实现了 session 共享,Spring Session 基于 Spring 中的代理过滤器实现,使得 session 的同步操作对开发人员而言是透明的,非常简便。

        1. 如何使用 Spring Boot 实现异常处理?

Spring 提供了一种使用 ControllerAdvice 处理异常的非常有用的方法。 我们通过实现一个 ControlerAdvice 类,来处理控制器类抛出的所有异常。

        1. 如何使用 Spring Boot 实现分页和排序?

使用 Spring Boot 实现分页非常简单。使用 Spring Data-JPA 可以实现将可分页的传递给存储库方法。

        1. Spring Boot 中如何实现定时任务 ?

定时任务也是一个常见的需求,Spring Boot 中对于定时任务的支持主要还是来自 Spring 框架。

Spring Boot 中使用定时任务主要有两种不同的方式,

一个是使用 Spring 中的 @Scheduled 注解,另一个则是使用第三方框架 Quartz

使用 Spring 中的 @Scheduled 的方式主要通过 @Scheduled 注解来实现。

使用 Quartz ,则按照 Quartz 的方式,定义 Job 和 Trigger 即可。

      1. SpringCloud系列
        1. 什么是 SpringCloud

spring cloud 是一系列框架的有序集合。利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。

        1. SpringBoot和SpringCloud

SpringBoot是Spring推出用于解决传统框架配置文件冗余,装配组件繁杂的基于Maven的解决方案,旨在快速搭建单个微服务。

而SpringCloud专注于解决各个微服务之间的协调与配置,服务之间的通信、熔断、负载均衡等技术维度问题,并且SpringCloud是依赖于SpringBoot的,而SpringBoot并不是依赖与SpringCloud,甚至还可以和Dubbo进行优秀的整合开发

总结:

  1. SpringBoot专注于快速方便的开发单个个体的微服务
  2. SpringCloud是关注全局微服务协调整理治理框架,整合管理各个微服务,为各个微服务之间提供,配置管理,服务发现,断路器,路由,事件总线等集成服务
  3. SpringBoot不依赖于SpringCloud,SpringCloud依赖于SpringBoot,属于依赖关系
  4. SpringBoot专注于快速,方便的开发单个的微服务个体,SpringCloud关注全局的服务治理框架
        1. SpringCloud和Dubbo 
  1. SpringCloud是Apache旗下的Spring体系下的微服务解决方案
  1. Dubbo是阿里系的分布式服务治理框架
  2. Dubbo使用第三方的ZooKeeper作为其底层的注册中心,实现服务的注册和发现
  3. Spring Cloud使用Spring Cloud Netflix Eureka实现注册中心,当然SpringCloud也可以使用ZooKeeper实现,但一般我们不会这样做。
  4. 服务网关,Dubbo并没有本身的实现,只能通过其他第三方技术的整合,
  5. SpringCloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发
  6. SpringCloud还支持断路器,与git完美集成分布式配置文件支持版本控制,事务总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素
  1. 文档质量和社区活跃度

SpringCloud社区活跃度远高于Dubbo,毕竟由于梁飞团队的原因导致Dubbo停止更新迭代五年,而中小型公司无法承担技术开发的成本导致Dubbo社区严重低落

  1. 服务的调用方式:

Dubbo使用的是RPC远程调用,而SpringCloud使用的是 Rest API

        1. Rest和RPC对比
  1. REST风格的系统交互更方便,RPC调用服务提供方和调用方式之间依赖太强。
  2. REST调用系统性能较低,RPC调用效率比REST高。
  3. REST的灵活性可以跨系统跨语言调用
  4. RPC只能在同语言内调用。
  5. REST可以和Swagger等工具整合,自动输出接口API文档
        1. SpringCloud的核心组件

Zuul如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务

Eureka各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里

Ribbon服务间发起请求的时候,实现负载均衡,从一个服务的多台机器中选择一台。

Feign基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。

Hystrix提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。

        1.  
          SpringCloud架构原理:
        2. Zuul网关功能

代理+路由+过滤  三大功能

  1. 其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。
  2. 过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
  3. Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。Zuul服务最终还是会注册进Eureka
        1. springcloud如何实现服务的注册?
  1. 服务发布时,指定对应的服务名,将服务注册到 注册中心(eureka/zookeeper)
  2. 注册中心加@EnableEurekaServer,服务用@EnableDiscoveryClient,然后用ribbon或feign进行服务直接的调用发现。
        1. Eureka的服务注册与发现

Eureka包含两个组件:Eureka Server和Eureka Client

Eureka Server提供服务注册服务

各个节点启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到

EurekaClient是一个Java客户端

用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载 算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)

        1. Eureka的我保护模式

默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳, EurekaServer将会注销该实例(默认90秒) 。但是当网络分区故障发生时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了—因为微服务本身其实是健康的,此时本不应该注销这个微服务。

Eureka通过“自我保护模式”来解决这个问题—当EurekaServer节点在短时间内( 如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障 )丢失过多客户端时(可能发生了网络分区故障) ,那么这个节点就会进入自我保护模式。一旦进入该模式, EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务) 。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。

在自我保护模式中, Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到國值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留) ,也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定

可以使用eureka.server.enable-self-preservation = false禁用自我保护模式

        1. Eureka和ZooKeeper的区别

CAP理论指出,分布式系统不可能同时满足c(一致性)、A(可用性)和P(分区容错性)。我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。

  1. ZooKeeper保证的是CP;Eureka保证的是AP
  2. ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用
  3. Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的。自我保护机制会导致Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务,Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用)。当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性)
  4. Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪
  5. ZooKeeper有Leader和Follower角色,Eureka各个节点平等
  6. ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题
  7. Eureka本质上是一个工程,而ZooKeeper只是一个进程
        1. 负载均衡的意义是什么?

负载均衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。

负载均衡旨在优化资源使用,最大吞吐量,最小响应时间并避免任何单一资源的过载。使用多个组件进行负载均衡而不是单个组件可能会通过冗余来提高可靠性和可用性。

负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务进程。

        1. ribbon的负载均衡策略

ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。feign默认集成了ribbon。

RoundRobinRule: 轮询策略

Ribbon以轮询的方式选择服务器,这个是默认值。服务器列表中的服务会被循环访问;

RandomRule: 随机策略

Ribbon会随机从服务器列表中选择一个进行访问;

BestAvailableRule: 最大可用策略

即先过滤出故障服务器后,选择一个当前并发请求数最小的;

WeightedResponseTimeRule: 带有加权的轮询策略

对各个服务器响应时间进行加权处理,然后在采用轮询的方式来获取相应的服务器;

AvailabilityFilteringRule: 可用过滤策略

先过滤出故障的或并发请求大于阈值的一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选出一个;

ZoneAvoidanceRule: 区域感知策略

先使用主过滤条件(区域负载器,选择最优区域)对所有实例过滤并返回过滤后的实例清单,依次使用次过滤条件列表中的过滤条件对主过滤条件的结果进行过滤,判断最小过滤数(默认1)和最小过滤百分比(默认0),最后对满足条件的服务器则使用RoundRobinRule(轮询方式)选择一个服务器实例。

        1. 什么是feigin?它的优点是什么?
  1. feign采用的是基于接口的注解
  2. feign整合了ribbon,具有负载均衡的能力
  3. 整合了Hystrix,具有熔断的能力

使用:  1.  添加pom依赖。

2.  启动类添加@EnableFeignClients

3.  定义一个接口@FeignClient(name=“xxx”)指定调用哪个服务

        1. Ribbon和Feign的区别
  1. Ribbon添加maven依赖 spring-starter-ribbon 使用@RibbonClient(value="服务名称") 使用RestTemplate调用远程服务对应的方法。
  2. feign添加maven依赖 spring-starter-feign 服务提供方提供对外接口 调用方使用 在接口上使用@FeignClient("指定服务名")
  3. 启动类使用的注解不同,Ribbon用的是@RibbonClient,Feign用的@EnableFeignClients。
  4. 服务的指定位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
  5. 调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。
  6. Feign则是在Ribbon的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建http请求。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。
        1. 什么是Hystrix

防雪崩利器:具备服务降级,服务熔断,依赖隔离,监控(Hystrix Dashboard)

优先核心服务:非核心服务不可用或弱可用。通过HystrixCommand注解指定。fallbackMethod(回退函数)中具体实现降级逻辑。

@EnableHystrix:开启熔断

@HystrixCommand(fallbackMethod=”XXX”):声明一个失败回滚处理函数XXX,当被注解的方法执行超时(默认是1000毫秒),就会执行fallback函数,返回错误提示。

        1. 雪崩效应

假设微服务A调用微服务B,B调用微服务C,如果微服务C的调用响应时间过长或者不可用,微服务C会积压大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务A也变为不可用,整个调用链路被拖垮,进而引起系统崩溃,就是所谓的“雪崩效应”。

        1. Hystrix的服务熔断和服务降级

为了避免引起雪崩效应就需要进行服务熔断和服务降级处理。

服务熔断:熔断机制是应对雪崩效应的一种微服务链路保户机制,当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的相应信息。当检测当该节点微服务调用响应正常后恢复调用链路,熔断机制的注解是@HystrixCommand

服务降级:服务降级处理是在客户端实现完成的,与服务端没有关系

  整体资源快不够了,忍痛将某些服务单元先关掉,关闭后还要返回一些可处理的备选方法,待渡过难关,再开启回来

区别:

触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;

管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)

        1. Hystrix断路器的作用

Hystrix组件,实现了断路器模式

当较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystric 是5秒20次) 断路器将会被打开。

断路器全开状态:一段时间内 达到一定的次数无法调用 并且多次监测没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务

半开: 短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭

关闭:当服务一直处于正常状态 能正常调用

在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

        1. 微服务之间是如何独立通讯的
    1. 远程过程调用(Remote Procedure Invocation):

       也就是我们常说的服务的注册与发现,直接通过远程过程调用来访问别的service。

       优点:简单,常见,因为没有中间件代理,系统更简单

       缺点:只支持请求/响应的模式,不支持别的,比如通知、请求/异步响应、发布/订阅、发布/异步响应,降低了可用性,因为客户端和服务端在请求过程中必须都是可用的

    1. 消息:

       使用异步消息来做服务间通信。服务间通过消息管道来交换消息,从而通信。

       优点: 把客户端和服务端解耦,更松耦合,提高可用性,因为消息中间件缓存了消息,直到消费者可以消费,支持很多通信机制比如通知、请求/异步响应、发布/订阅、发布/异步响应

       缺点: 消息中间件有额外的复杂

        1. 微服务的优缺点? 说下在项目开发中碰到的坑

优点

  1. 每一个服务足够内聚,代码容易理解
  2. 开发效率提高,一个服务只做一件事
  3. 微服务能够被小团队单独开发
  4. 微服务是松耦合的,是有功能意义的服务
  5. 可以用不同的语言开发,面向接口编程
  6. 易于与第三方集成

缺点

  1. 分布式系统的负责性
  2. 多服务运维难度,随着服务的增加,运维的压力也在增大
  3. 系统部署依赖
  4. 服务间通信成本
  5. 数据一致性
  6. 系统集成测试
  7. 性能监控
        1. 你所知道的微服务技术栈

维度(springcloud)

服务开发:springboot spring springmvc

服务配置与管理:Netfix公司的Archaiusm ,阿里的Diamond

服务注册与发现:Eureka,Zookeeper

服务调用:Rest RPC gRpc

服务熔断器:Hystrix

服务负载均衡:Ribbon Nginx

服务接口调用:Fegin

消息队列:Kafka Rabbitmq activemq

服务配置中心管理:SpringCloudConfig

服务路由(API网关)Zuul

事件消息总线:SpringCloud Bus

        1. 什么是Spring Cloud Bus

spring cloud bus 将分布式的节点用轻量的消息代理连接起来,它可以用于广播配置文件的更改或者服务直接的通讯,也可用于监控。如果修改了配置文件,发送一次请求,所有的客户端便会重新读取配置文件。

使用:  1.  添加依赖        2.  配置rabbimq

        1. 什么是SpringCloudConfig

在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。

使用:

1、添加pom依赖

2、配置文件添加相关配置

3、启动类添加注解@EnableConfigServer

    1. 数据库系列
      1. 基础
        1. 数据库三范式:

第一范式:数据表中每个字段都必须是不可拆分的最小单元,也就是确保每一列的原子性;

第二范式:满足一范式后,表中每一列必须有唯一性,都必须依赖于主键;

第三范式:满足二范式后,表中的每一列只与主键直接相关而不是间接相关(外键也是直接相关),字段没有冗余。

        1. SQL优化
  1. 在表中建立索引,优先考虑where、group by使用到的字段。
  2. 尽量避免使用select *,返回无用的字段会降低查询效率。使用具体的字段代替*,只返回使用到的字段。
  3. 尽量避免用in 和not in,会导致数据库引擎放弃索引进行全表扫描。若是连续数值,用between代替。如:SELECT * FROM t WHERE id BETWEEN 2 AND 3

如果是子查询,可以用exists代替。如:

SELECT * FROM t1 WHERE EXISTS (SELECT * FROM t2 WHERE t1.username = t2.username)

  1. 尽量避免使用or,会导致数据库引擎放弃索引进行全表扫描。如:SELECT * FROM t WHERE id = 1 OR id = 3

优化方式:可以用union代替or。如下:SELECT * FROM t WHERE id = 1  UNION  SELECT * FROM t WHERE id = 3(PS:如果or两边的字段是同一个,如例子中这样。貌似两种方式效率差不多,即使union扫描的是索引,or扫描的是全表)

  1. 尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描。如下:SELECT * FROM t WHERE username LIKE '%li%'

优化方式:尽量在字段后面使用模糊查询。如下:SELECT * FROM t WHERE username LIKE 'li%'

  1. 尽量避免进行null值的判断(避免在where子语句中使用!=或<>),会导致数据库引擎放弃索引进行全表扫描。如下:SELECT * FROM t WHERE score IS NULL

优化方式:可以给字段添加默认值0,对0值进行判断。

如下:SELECT * FROM t WHERE score = 0

  1. 尽量避免在where条件中等号的左侧进行表达式、函数操作,会导致数据库引擎放弃索引进行全表扫描。如下:SELECT * FROM t2 WHERE score/10 = 9

优化方式:将表达式、函数操作移动到等号右侧。如下:SELECT * FROM t2 WHERE score = 10*9

  1. 当数据量大时,避免使用where 1=1的条件。通常为了方便拼装查询条件,我们会默认使用该条件,数据库引擎会放弃索引进行全表扫描。如下:

优化方式:用代码拼装sql时进行判断,没where加where,有where加and。

       总结:最大化利用索引;尽可能避免全表扫描;减少无效数据的查询;

        1. 索引具体采用的哪种数据结构

索引其实是一种数据结构,能够帮助我们快速的检索数据库中的数据

常见的MySQL主要有两种结构:Hash索引和B+ Tree索引,我们使用的是InnoDB引擎,默认的是B+树

        1. hash索引和btree索引的区别

索引名

hash

Btree

持最左前缀

匹配原则

不支持,只有索引的全部字段都用上才会匹配到

支持,用上索引的第一个字段就可以匹配索引

MyISAM和

InnoDB是否

支持?

不支持(只有Memory和NDB引擎索引支持)

支持

范围查询

能否命中

索引?

不可以,只有“=”,“IN”,“<=>”(等价于的意思)查询能命中

可以

一定会全表

扫描吗

数据结构

hash表,通过键去找值的一种数据结构 

 
B-tree,多路搜索树,并不是二叉的

Btree:

BTree索引是最常用的mysql数据库索引算法,因为它不仅可以被用在=,>,>=,<,<=和between这些比较操作符上,而且还可以用于like操作符,只要它的查询条件是一个不以通配符开头的常量,例如:

select * from user where name like ‘jack%’;

select * from user where name like ‘jac%k%’;

如果一通配符开头,或者没有使用常量,则不会使用索引,例如:

select * from user where name like ‘%jack’;

select * from user where name like simply_name;

Hash

Hash索引只能用于对等比较,例如=,<=>(相当于=)操作符。由于是一次定位数据,不像BTree索引需要从根节点到枝节点,最后才能访问到页节点这样多次IO访问,所以检索效率远高于BTree索引。

但为什么我们使用BTree比使用Hash多呢?主要Hash本身由于其特殊性,也带来了很多限制和弊端:

1.   Hash索引仅仅能满足“=”,“IN”,“<=>”查询,不能使用范围查询。

2.   联合索引中,Hash索引不能利用部分索引键查询。

对于联合索引中的多个列,Hash是要么全部使用,要么全部不使用,并不支持BTree支持的联合索引的最优前缀,也就是联合索引的前面一个或几个索引键进行查询时,Hash索引无法被利用。

3.   Hash索引无法避免数据的排序操作

由于Hash索引中存放的是经过Hash计算之后的Hash值,而且Hash值的大小关系并不一定和Hash运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算。

4.   Hash索引任何时候都不能避免表扫描

Hash索引是将索引键通过Hash运算之后,将Hash运算结果的Hash值和所对应的行指针信息存放于一个Hash表中,由于不同索引键存在相同Hash值,所以即使满足某个Hash键值的数据的记录条数,也无法从Hash索引中直接完成查询,还是要通过访问表中的实际数据进行比较,并得到相应的结果。

5.   Hash索引遇到大量Hash值相等的情况后性能并不一定会比BTree高

对于选择性比较低的索引键,如果创建Hash索引,那么将会存在大量记录指针信息存于同一个Hash值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据访问,而造成整体性能底下。

        1. 建立索引的规则:
  1. 利用最左前缀:Mysql会一直向右查找直到遇到范围操作(>,<,like、between)就停止匹配。比如a=1 and b=2 and c>3 and d=6;此时如果建立了(a,b,c,d)索引,那么后面的d索引是完全没有用到,当换成了(a,b,d,c)就可以用到。
  2. 不能过度索引:在修改表内容的时候,索引必须更新或者重构,所以索引过多时,会消耗更多的时间。
  3. 尽量扩展索引而不要新建索引
  4. 最适合的索引的列是出现在where子句中的列或连接子句中指定的列。
  5. 不同值较少的列不必要建立索引(性别)。
        1. 创建索引

CREATE   [UNIQUE|CLUSTERED]  INDEX  INDEX_NAME  ON  TABLE_NAME(NAME)

其中UNIQUE和CLUSTERED为可选项,分别是建立唯一索引和聚簇索引,

具体解释为:

UNIQUE:  表示此索引的每一个索引值只对应唯一的数据。

CLUSTERED: 表示要建立的索引时聚簇索引,即索引项的顺序与表中记录的物理顺序一致的索引组织。

只要数据库认为可以使用某个已经创建的索引,索引就会自动运用。

        1. 防止SQL注入四的种方法
  1. (简单又有效的方法)PreparedStatement, 原理:sql注入只对sql语句的准备(编译)过程有破坏作用而PreparedStatement已经准备好了,执行阶段只是把输入串作为数据处理,而不再对sql语句进行解析,准备,因此也就避免了sql注入问题.
  2. 使用正则表达式过滤传入的参数
  3. 字符串过滤
  4. JSP页面判断代码

      1. Mybatis系列
        1. 什么是Mybatis?
  1. Mybatis是一个半ORM(对象关系映射)框架,内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
  2. MyBatis 可以使用XML 或注解来配置和映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
  3. 通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
        1. Mybaits的优缺点:

优点:

  1. 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
  2. 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
  3. 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
  4. 能够与Spring很好的集成;
  5. 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

缺点:

  1. SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
  2. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

        1. MyBatis框架适用场合:
  1. MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
  2. 对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
        1. MyBatis与Hibernate的区别
  1. Mybatis半是一个ORM框架,需要程序员自己编写Sql语句。hibernate是一个ORM框架。
  2. Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。
  3. Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。
        1. #{}和${}的区别是什么?
  1. #{}是预编译处理,${}是字符串替换。
  2. Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值
  3. Mybatis在处理${}时,就是把${}替换成变量的值
  4. 使用#{}可以有效的防止SQL注入,提高系统安全性
  5. $方式一般用于传入数据库对象,例如传入表名.

select * from user where name = #{name};

select * from user where name = '${name}'; 必须加单引号

        1. 当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

第1种: 通过在查询的sql语句中定义字段名的别名,让别名和实体类的属性名一致。(id,orderno,price均为别名

第2种: 通过来映射字段名和实体类属性名的一一对应的关系。

<select id="getEmployeeById" resultMap="myMap">

       select * from employees where id = #{id}

  select>

 

  <resultMap type="com.atguigu.mybatis.entities.Employee" id="myMap">

      

       <id column="id" property="id"/>

      

       <result column="last_name" property="lastName"/>

       <result column="email" property="email"/>

       <result column="dept_id" property="deptId"/>

resultMap>

        1. 模糊查询like语句该怎么写?
  1.  
    使用${...}

  1. 使用#{...} 
  2. 使用CONCAT()函数连接参数形式

        1. 通常一个Xml映射文件,都会写一个Dao接口与之对应,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
  1. Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。
  2. Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。
  3. Mapper接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
        1. Mybatis是如何进行分页的?分页插件的原理是什么?
  1. Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
  2. 分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
        1. Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
  1. resultMap标签,逐一定义数据库列名和对象属性名之间的映射关系。
  2. 使用sql列的别名功能,将列的别名书写为对象属性名。

有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

        1. 如何执行批量插入

首先,创建一个简单的insert语句:

    insert into names (name) values (#{value})

然后在java代码中像下面这样执行批处理插入:

  list names = new arraylist();

  names.add(“fred”);

  names.add(“barney”);

  names.add(“betty”);

  names.add(“wilma”);

  // 新获取一个模式为BATCH,自动提交为falsesession

// 如果自动提交设置为true,将无法控制提交的条数,改为最后统一提交,可能导致内存溢出

  sqlsession sqlsession =

      sqlsessionfactory.opensession(ExecutorType.BATCH,false);

  try {

  namemapper mapper = sqlsession.getmapper(namemapper.class);

  for (string name : names) {

      mapper.insertname(name);

  }

  sqlsession.commit();

  }catch(Exception e){

  e.printStackTrace();

  sqlSession.rollback();

  throw e;

}finally {

    sqlsession.close();

  }

        1. 如何获取自动生成的(主)键值?

insert 方法总是返回一个int值 ,这个值代表的是插入的行数。

如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。

示例:

insert into names (name) values (#{name})

name name = new name();

name.setname(“fred”);

int rows = mapper.insertname(name);

// 完成后,id已经被设置到对象中

system.out.println(“rows inserted = ” + rows);

system.out.println(“generated key value = ” + name.getid());

        1. 在mapper中如何传递多个参数?

1)第一种:

//DAO层的函数

Public UserselectUser(String name,String area); 

//对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。

 

(2)第二种: 使用 @param 注解:

public interface usermapper {

user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword);

}

然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):

(3)第三种:多个参数封装成map

try{

//映射文件的命名空间.SQL片段的ID,就可以调用对应的映射文件中的SQL

//由于我们的参数超过了两个,而方法中只有一个Object参数收集,因此我们使用Map集合来装载我们的参数

Map map = new HashMap();

    map.put("start", start);

    map.put("end", end);

    return sqlSession.selectList("StudentID.pagination", map);

}catch(Exception e){

e.printStackTrace();

  sqlSession.rollback();

throw e;

}finally{

  MybatisUtil.closeSqlSession();

}

        1. 一对一、一对多的关联查询

 

     

     

    ClassesResultMap"> 

         

         

     

        

         

         

     

     

     

     

    ClassesResultMap2"> 

     

     

                  Teacher"> 

         

             

     

        Student"> 

         

         

     

     

        1. MyBatis实现一对一有几种方式,具体怎么操作
  1. 有联合查询和嵌套查询
  2. 联合查询是几个表联合查询,只查询一次,通过在resultMap里面配置association节点配置一对一的类就可以完成;
  3. 嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。
        1. MyBatis实现一对多有几种方式,怎么操作
  1. 有联合查询和嵌套查询。
  2. 联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;
  3. 嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
        1. Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
  1. 不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;
  2. 原因就是namespace+id是作为Map的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。

但是,在以前的Mybatis版本的namespace是可选的,不过新版本的namespace已经是必须的了。

        1. Mybatis是否支持延迟加载?它的实现原理是什么?
  1. Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一
  2. collection指的就是一对多查询。
  3. 在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

原理:

使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。

当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

        1. Mybatis的一级、二级缓存
  1. 一级缓存:

基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

  1. 二级缓存:

与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;

  1. 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。
        1. 什么是MyBatis的接口绑定?有哪些实现方式?

接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定,我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。

接口绑定有两种实现方式:

  1. 一种是通过注解绑定,就是在接口的方法上面加上@Select、@Update等注解,里面包含Sql语句来绑定;
  2. 另外一种就是通过xml里面写SQL来绑定,在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定,当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。

        1. 使用MyBatis的mapper接口调用时有哪些要求?
  1. Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
  2. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
  3. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
  4. Mapper.xml文件中的namespace即是mapper接口的类路径。
        1. MyBatis面向Mapper编程工作原理

Mapper接口是没有实现类的,当调用接口方法时,采用了JDK的动态代理,先从Configuration配置类MapperRegistry对象中获取mapper接口和对应的代理对象工厂信息(MapperProxyFactory)。

然后利用代理对象工厂MapperProxyFactory创建实际代理类(MapperProxy),最后在MapperProxy类中通过MapperMethod类对象内保存的中对应方法的信息,以及对应的sql语句的信息进行分析。

最终确定对应的增强方法进行调用。

        1. Mybatis的Executor执行器之间有什么区别

Mybatis有三种基本的Executor执行器,

SimpleExecutorReuseExecutorBatchExecutor

  1. SimpleExecutor

每执行一次updateselect,就开启一个Statement对象,用完立刻关闭Statement对象。

  1. ReuseExecutor

执行updateselect,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。

  1. BatchExecutor

执行update(没有selectJDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

        1. Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?

Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。

Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind

其执行原理为,使用OGNLsql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。

      1.   Redis 的数据结构

        1. redis中的五种常用类型

分别是string,Hash,List,Set,ZSet。

string命令使用

命令

简述

使用

GET

获取存储在给定键中的值

GET name

SET

设置存储在给定键中的值

SET name value

DEL

删除存储在给定键中的值

DEL name

INCR

将键存储的值加1

INCR key

DECR

将键存储的值减1

DECR key

INCRBY

将键存储的值加上整数

INCRBY key amount

DECRBY

将键存储的值减去整数

DECRBY key amount

List结构命令使用

命令

简述

使用

RPUSH

将给定值推入到列表右端

RPUSH key value

LPUSH

将给定值推入到列表左端

LPUSH key value

RPOP

从列表的右端弹出一个值,并返回被弹出的值

RPOP key

LPOP

从列表的左端弹出一个值,并返回被弹出的值

LPOP key

LRANGE

获取列表在给定范围上的所有值

LRANGE key 0 -1

LINDEX

通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

LINEX key index

Set集合命令使用

命令

简述

使用

SADD

向集合添加一个或多个成员

SADD key value

SCARD

获取集合的成员数

SCARD key

SMEMBER

返回集合中的所有成员

SMEMBER key member

SISMEMBER

判断 member 元素是否是集合 key 的成员

SISMEMBER key member

Hash散列命令使用

命令

简述

使用

HSET

添加键值对

HSET hash-key sub-key1 value1

HGET

获取指定散列键的值

HGET hash-key key1

HGETALL

获取散列中包含的所有键值对

HGETALL hash-key

HDEL

如果给定键存在于散列中,那么就移除这个键

HDEL hash-key sub-key1

Zset有序集合命令使用(不允许重复)

命令

简述

使用

ZADD

将一个带有给定分值的成员添加到哦有序集合里面

ZADD zset-key 178 member1

ZRANGE

根据元素在有序集合中所处的位置,从有序集合中获取多个元素

ZRANGE zset-key 0-1 withccores

ZREM

如果给定元素成员存在于有序集合中,那么就移除这个元素

ZREM zset-key member1

类型

特点

使用场景

string

简单的key-value类型,value其实不仅是String,也可以是数字

定时持久化,操作日志,常规计数, 微博数, 粉丝数等功能

hash

是一个string类型的field和value的映射表,hash特别适合用于存储对象

存储部分变更数据,如用户信息等

list

有序可重复的列表

twitter的关注列表,粉丝列表,最新消息排行,消息队列

set

无序不可重复的列表

在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。

Sorted set

带有score的Set

排行榜

Redis 是基于内存的么

redis是一个内存数据库, 所有数据基本上都存在于内存当中, 会定时以追加或者快照的方式刷新到硬盘中。Redis最为常用的数据类型主要有以下:

 
String  Hash  List  Set  Sorted set  pub/sub  Transactions

Redis 的 list zset 的底层实现

        1. 缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案:

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

布隆过滤器:

bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小,

        1. 缓存击穿

问题来源:

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

解决方案

1、设置热点数据永远不过期。

2、接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 服务 不可用时候,进行熔断,失败快速返回机制。

3、加互斥锁

        1. 缓存雪崩

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。

设置热点数据永远不过期。

缓存污染(或满了)

缓存污染问题说的是缓存中一些只会被访问一次或者几次的的数据,被访问完后,再也不会被访问到,但这部分数据依然留存在缓存中,消耗缓存空间。

缓存污染会随着数据的持续增加而逐渐显露,随着服务的不断运行,缓存中会存在大量的永远不会再次被访问的数据。缓存空间是有限的,如果缓存空间满了,再往缓存里写数据时就会有额外开销,影响Redis性能。这部分额外开销主要是指写的时候判断淘汰策略,根据淘汰策略去选择要淘汰的数据,然后进行删除操作。

¶ 最大缓存设置多大

系统的设计选择是一个权衡的过程:大容量缓存是能带来性能加速的收益,但是成本也会更高,而小容量缓存不一定就起不到加速访问的效果。一般来说,我会建议把缓存容量设置为总数据量的 15% 到 30%,兼顾访问性能和内存空间开销。

对于 Redis 来说,一旦确定了缓存最大容量,比如 4GB,你就可以使用下面这个命令来设定缓存的大小了:

CONFIG SET maxmemory 4gb

不过,缓存被写满是不可避免的, 所以需要数据淘汰策略。

¶ 缓存淘汰策略

Redis共支持八种淘汰策略,分别是noeviction、volatile-random、volatile-ttl、volatile-lru、volatile-lfu、allkeys-lru、allkeys-random 和 allkeys-lfu 策略。

怎么理解呢?主要看分三类看:

不淘汰 noeviction (v4.0后默认的)

对设置了过期时间的数据中进行淘汰

随机:volatile-random

ttl:volatile-ttl

lru:volatile-lru

lfu:volatile-lfu

全部数据进行淘汰

随机:allkeys-random

lru:allkeys-lru

lfu:allkeys-lfu

具体对照下:

noeviction

该策略是Redis的默认策略。在这种策略下,一旦缓存被写满了,再有写请求来时,Redis 不再提供服务,而是直接返回错误。这种策略不会淘汰数据,所以无法解决缓存污染问题。一般生产环境不建议使用。

其他七种规则都会根据自己相应的规则来选择数据进行删除操作。

volatile-random

这个算法比较简单,在设置了过期时间的键值对中,进行随机删除。因为是随机删除,无法把不再访问的数据筛选出来,所以可能依然会存在缓存污染现象,无法解决缓存污染问题。

volatile-ttl

这种算法判断淘汰数据时参考的指标比随即删除时多进行一步过期时间的排序。Redis在筛选需删除的数据时,越早过期的数据越优先被选择。

volatile-lru

LRU算法:LRU 算法的全称是 Least Recently Used,按照最近最少使用的原则来筛选数据。这种模式下会使用 LRU 算法筛选设置了过期时间的键值对。

Redis优化的LRU算法实现:

Redis会记录每个数据的最近一次被访问的时间戳。在Redis在决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为一个候选集合。接下来,Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去。通过随机读取待删除集合,可以让Redis不用维护一个巨大的链表,也不用操作链表,进而提升性能。

Redis 选出的数据个数 N,通过 配置参数 maxmemory-samples 进行配置。个数N越大,则候选集合越大,选择到的最久未被使用的就更准确,N越小,选择到最久未被使用的数据的概率也会随之减小。

volatile-lfu

会使用 LFU 算法选择设置了过期时间的键值对。

LFU 算法:LFU 缓存策略是在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数  相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。

Redis的LFU算法实现:

当 LFU 策略筛选数据时,Redis 会在候选集合中,根据数据 lru 字段的后 8bit 选择访问次数最少的数据进行淘汰。当访问次数相同时,再根据 lru 字段的前 16bit 值大小,选择访问时间最久远的数据进行淘汰。

Redis 只使用了 8bit 记录数据的访问次数,而 8bit 记录的最大值是 255,这样在访问快速的情况下,如果每次被访问就将访问次数加一,很快某条数据就达到最大值255,可能很多数据都是255,那么退化成LRU算法了。所以Redis为了解决这个问题,实现了一个更优的计数规则,并可以通过配置项,来控制计数器增加的速度。

参数 :

Lfu-log-factor ,用计数器当前的值乘以配置项 lfu_log_factor 再加 1,再取其倒数,得到一个 p 值;然后,把这个 p 值和一个取值范围在(0,1)间的随机数 r 值比大小,只有 p 值大于 r 值时,计数器才加 1。

lfu-decay-time, 控制访问次数衰减。LFU 策略会计算当前时间和数据最近一次访问时间的差值,并把这个差值换算成以分钟为单位。然后,LFU 策略再把这个差值除以 lfu_decay_time 值,所得的结果就是数据 counter 要衰减的值。

lfu-log-factor设置越大,递增概率越低,lfu-decay-time设置越大,衰减速度会越慢。

我们在应用 LFU 策略时,一般可以将 lfu_log_factor 取值为 10。 如果业务应用中有短时高频访问的数据的话,建议把 lfu_decay_time 值设置为 1。可以快速衰减访问次数。

Volatile-lfu 策略是 Redis 4.0 后新增。

allkeys-lru

使用 LRU 算法在所有数据中进行筛选。具体LFU算法跟上述 volatile-lru 中介绍的一致,只是筛选的数据范围是全部缓存,这里就不在重复。

allkeys-random

从所有键值对中随机选择并删除数据。volatile-random 跟 allkeys-random算法一样,随机删除就无法解决缓存污染问题。

allkeys-lfu 使用 LFU 算法在所有数据中进行筛选。具体LFU算法跟上述 volatile-lfu 中介绍的一致,只是筛选的数据范围是全部缓存,这里就不在重复。

allkeys-lfu 策略是 Redis 4.0 后新增。

        1. 数据库和缓存一致性

问题来源:

使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库:

读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存(Redis)和数据库(MySQL)间的数据一致性问题。

不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。举一个例子:

1.  如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。

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

因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。

        1. 4种相关模式

更新缓存的的Design Pattern有四种:Cache aside, Read through, Write through, Write behind caching;

总结:

读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。

更新的时候,先更新数据库,然后再删除缓存。

其具体逻辑如下

失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

命中:应用程序从cache中取数据,取到后返回。

更新:先把数据存到数据库中,成功后,再让缓存失效。

注意,我们的更新是先更新数据库,成功后,让缓存失效。那么,这种方式是否可以没有文章前面提到过的那个问题呢?我们可以脑补一下。

一个是查询操作,一个是更新操作的并发,首先,没有了删除cache数据的操作了,而是先更新了数据库中的数据,此时,缓存依然有效,所以,并发的查询操作拿的是没有更新的数据,但是,更新操作马上让缓存的失效了,后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题,后续的查询操作一直都在取老的数据。

这是标准的design pattern(设计模式),包括Facebook的论文《Scaling Memcache at Facebook (opens new window)》也使用了这个策略。为什么不是写完数据库后更新缓存?你可以看一下Quora上的这个问答《Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend? (opens new window)》,主要是怕两个并发的写操作导致脏数据。

那么,是不是Cache Aside这个就不会有并发问题了?不是的,比如,一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。

但,这个case理论上会出现,不过,实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

所以,这也就是Quora上的那个答案里说的,要么通过2PC或是Paxos协议保证一致性,要么就是拼命的降低并发时脏数据的概率,而Facebook使用了这个降低概率的玩法,因为2PC太慢,而Paxos太复杂。当然,最好还是为缓存设置上过期时间。

¶ 方案:队列 + 重试机制

更新数据库数据;

缓存因为种种问题删除失败

将需要删除的key发送至消息队列

自己消费消息,获得需要删除的key

继续重试删除操作,直到成功

然而,该方案有一个缺点,对业务线代码造成大量的侵入。

于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

¶ 方案:异步更新缓存(基于订阅binlog的同步机制)

技术整体思路:

MySQL binlog增量订阅消费+消息队列+增量数据更新到redis

1)读Redis:热数据基本都在Redis

2)写MySQL: 增删改都是操作MySQL

3)更新Redis数据:MySQ的数据操作binlog,来更新到Redis

        1. Redis更新

1)数据操作主要分为两大块:

一个是全量(将全部数据一次写入到redis)

一个是增量(实时更新)

这里说的是增量,指的是mysql的update、insert、delate变更数据。

2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。

这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。

其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。

这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。

当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis。

        1. Redis 持久化

redis 提供了两种持久化的方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。

RDB:就是在不同的时间点,将 redis 存储的数据生成快照并存储到磁盘等介质上;

AOF:则是换了一个角度来实现持久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。

 RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。

如果你没有数据持久化的需求,也完全可以关闭 RDB 和 AOF 方式,这样的话,redis 将变成一个纯内存数据库,就像 memcache 一样。

          1. RDB 方式,是将 redis 某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法。

redis 在进行数据持久化的过程中,会先将数据写入到一个临时文件中,待持久化过程都结束了,才会用这个临时文件替换上次持久化好的文件。正是这种特性,让我们可以随时来进行备份,因为快照文件总是完整可用的。

对于 RDB 方式,redis 会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何 IO 操作的,这样就确保了 redis 极高的性能。

如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。

虽然 RDB 有不少优点,但它的缺点也是不容忽视的。如果你对数据的完整性非常敏感,那么 RDB 方式就不太适合你,因为即使你每 5 分钟都持久化一次,当 redis 故障时,仍然会有近 5 分钟的数据丢失。所以,redis 还提供了另一种持久化方式,那就是 AOF。

          1. AOF,英文 Append Only File,即只允许追加不允许改写的文件。

如前面介绍的,AOF 方式是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍,就这么简单。

我们通过配置 redis.conf 中的 appendonly yes 就可以打开 AOF 功能。如果有写操作(如 SET 等),redis 就会被追加到 AOF 文件的末尾。

默认的 AOF 持久化策略是每秒钟 fsync 一次(fsync 是指把缓存中的写指令记录到磁盘中),因为在这种情况下,redis 仍然可以保持很好的处理性能,即使 redis 故障,也只会丢失最近 1 秒钟的数据。

如果在追加日志时,恰好遇到磁盘空间满、inode 满或断电等情况导致日志写入不完整,也没有关系,redis 提供了 redis-check-aof 工具,可以用来进行日志修复。

因为采用了追加方式,如果不做任何处理的话,AOF 文件会变得越来越大,为此,redis 提供了 AOF 文件重写(rewrite)机制,即当 AOF 文件的大小超过所设定的阈值时,redis 就会启动 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集。举个例子或许更形象,假如我们调用了 100 次 INCR 指令,在 AOF 文件中就要存储 100 条指令,但这明显是很低效的,完全可以把这 100 条指令合并成一条 SET 指令,这就是重写机制的原理。

在进行 AOF 重写时,仍然是采用先写临时文件,全部完成后再替换的流程,所以断电、磁盘满等问题都不会影响 AOF 文件的可用性,这点大家可以放心。

AOF 方式的另一个好处,我们通过一个“场景再现”来说明。某同学在操作 redis 时,不小心执行了 FLUSHALL,导致 redis 内存中的数据全部被清空了,这是很悲剧的事情。不过这也不是世界末日,只要 redis 配置了 AOF 持久化方式,且 AOF 文件还没有被重写(rewrite),我们就可以用最快的速度暂停 redis 并编辑 AOF 文件,将最后一行的 FLUSHALL 命令删除,然后重启 redis,就可以恢复 redis 的所有数据到 FLUSHALL 之前的状态了。是不是很神奇,这就是 AOF 持久化方式的好处之一。但是如果 AOF 文件已经被重写了,那就无法通过这种方法来恢复数据了。

虽然优点多多,但 AOF 方式也同样存在缺陷,比如在同样数据规模的情况下,AOF 文件要比 RDB 文件的体积大。而且,AOF 方式的恢复速度也要慢于 RDB 方式。

如果你直接执行 BGREWRITEAOF 命令,那么 redis 会生成一个全新的 AOF 文件,其中便包括了可以恢复现有数据的最少的命令集。

如果运气比较差,AOF 文件出现了被写坏的情况,也不必过分担忧,redis 并不会贸然加载这个有问题的 AOF 文件,而是报错退出。这时可以通过以下步骤来修复出错的文件:

1.备份被写坏的 AOF 文件\ 2.运行 redis-check-aof –fix 进行修复\ 3.用 diff -u 来看下两个文件的差异,确认问题点\ 4.重启 redis,加载修复后的 AOF 文件

          1. redis持久化 – AOF重写

AOF 重写的内部运行原理,我们有必要了解一下。

在重写即将开始之际,redis 会创建(fork)一个“重写子进程”,这个子进程会首先读取现有的 AOF 文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。

与此同时,主工作进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的 AOF 文件中,这样做是保证原有的 AOF 文件的可用性,避免在重写过程中出现意外。

当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新 AOF 文件中。

当追加结束后,redis 就会用新 AOF 文件来代替旧 AOF 文件,之后再有新的写指令,就都会追加到新的 AOF 文件中了。

        1. Redis三种集群模式(如何保证高可用)
          1. 主从模式

1、需求

           为了避免单点故障,通常的做法是将数据库复制多个副本部署在不同的服务器上。这样,即使有一台服务器出现了故障,其他服务器仍然可以继续提供服务。 

           为此,Redis提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。

2、分工

              在复制的概念中,数据库分为两类。一类是主数据库(master),一类是从数据库(slave)。 master可以进行读写操作,当写操作发生变化时,会自动将数据同步给slave。slave一般只提供读操作,并接收主数据库同步过来的数据。一个master可以对应多个slave。一个slave只能对应一个master。

              引入主从复制的目的有两个:一是读写分离,分担master的压力。二是容灾备份。 

   3、原理

  1. slave启动成功之后,连接master,发送sync命令;
  2. master接收sync命令之后,开始执行BGSAVE命令生成RDB文件,并使用缓冲区记录此后执行的所有写命令。
  3. master执行完BGSAVE后,向所有的slave发送快照文件。并在发送期间记录被执行的写命令。
  4. slave接收到快照文件后载入收到的快照。
  5. master快照发送完毕后,开始向slave发送缓冲区的写命令。
  6. slave完成对快照文件的加载,开始接受命令请求。并执行主数据库缓冲区的写命令。(从数据库初始化完成。)
  7. master每执行一个写命令就像slave发送相同的写命令。slave接受并执行写命令。(从数据库初始化完成后的操作)
  8. 出现断开重连后,2.8之后的版本会将断线期间的命令传给重数据库,增量复制。
  9. 主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。

  4、 主从复制优点:

①支持主从复制,master会自动将数据同步到slave,可以进行读写分离;

②为了分载 Master 的读操作压力,Slave 服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成;

③Slave 同样可以接受其它 Slaves 的连接和同步请求,这样可以有效的分载 Master 的同步压力;

④Master Server 是以非阻塞的方式为 Slaves 提供服务。所以在 Master-Slave 同步期间,客户端仍然可以提交查询或修改请求;

⑤Slave Server 同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据;

5、主从复制缺点:

①Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复(也就是要人工介入);

②主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性(需要手动将一台slave切换成master);

③如果多个 Slave 断线了,需要重启的时候,尽量不要在同一时间段进行重启。因为只要 Slave 启动,就会发送sync 请求和主机全量同步,当多个 Slave 重启的时候,可能会导致 Master IO 剧增从而宕机。

Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂;

          1. 哨兵(Sentinel)

 1、作用

       主从复制当master出现故障时,需要手动切换master。哨兵模式就是为了解决手动切换这个问题。

Redis2.8中提供了哨兵工具,来实现自动化的系统监控和故障恢复。哨兵是一个独立的进程。原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行多个Redis实例。

        ① 通过发送命令,让Redis服务器返回运行状态,包括master和slave。

      ② 当哨兵(Sentinel)检测到master宕机,会自动将slave切换成master。然后通过发布订阅模式通知其他的slave。让其他slave切换主机。 

2、故障自动切换

       假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行 failover 过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行 failover(故障转移) 操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。 

3、工作机制  

       ① 每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的 Master 主服务器,Slave 从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。

       ② 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)

       ③ 如果一个 Master 主服务器被标记为主观下线(SDOWN),则正在监视这个 Master 主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认 Master 主服务器的确进入了主观下线状态

       ④ 当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认 Master 主服务器进入了主观下线状态(SDOWN), 则 Master 主服务器会被标记为客观下线(ODOWN)

       ⑤ 在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有 Master 主服务器、Slave 从服务器发送 INFO 命令。

       ⑥ 当 Master 主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master 主服务器的所有 Slave 从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。

       ⑦ 若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master 主服务器的客观下线状态就会被移除。若 Master 主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。

4、为什么要那么多哨兵组成网络

       当只有1个Sentinel的时候,如果这个Sentinel挂掉了就不能实现故障的自动切换。在Sentinel网络中,只要还有1个 Sentinel活着,就可以实现故障的自动切换。  

5、Sentinel(哨兵)优点

      ① 哨兵模式是基于主从的,所有主从的优点,哨兵模式都具有。

     ② 主从可以自动切换,系统更健壮,高可用性。(可以看做自动版的主从复制)

6、Sentinel(哨兵)缺点

      因为只有1个master,较难支持在线扩容,在集群容量达到上限时,在线扩容很复杂。

          1. cluster分片集群模式(Redis官方推荐)

 A、      采用去中心化的思想,没有中心节点的说法,它使用hash slot方式将16348个hash slot覆盖到所有节点上,对于存储的每个key值,使用CRC16(KEY)&16348=slot得到他对应的hash slot,并在访问key的时候就去找他的hash slot在哪一个节点上,然后由当前访问节点从实际被分配了这个hash slot的节点去取数据,节点之间使用轻量协议通信 减少带宽占用 性能很高,自动实现负载均衡与高可用,自动实现failover并且支持动态扩展。

B、      其内部中也需要配置主从,并且内部也是采用哨兵模式,如果有半数节点发现某个异常节点,共同决定更改异常节点的状态,如果改节点是主节点,则对应的从节点自动顶替为主节点,当原先的主节点上线后,则会变为从节点。如果集群中的master没有slave节点,则master挂掉后整个集群就会进入fail状态,因为集群的slot映射不完整。如果集群超过半数以上的master挂掉,无论是否有slave,集群都会进入fail状态。

C、        根据官方推荐 集群部署至少要3台以上的master节点。

      1. MySQL数据库引擎 Innodb 和 myisam 区别

1事务处理

MyISAM不提供事务支持,而InnoDB是事务安全型的(支持事务处理等高级处理);

2锁机制不同

MyISAM是表级锁,而InnoDB是行级锁;

3 select ,update ,insert ,delete 操作:

MyISAM:如果执行大量的SELECT,MyISAM是更好的选择

InnoDB:如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表

4查询表的行数不同:

MyISAM:select count(*) from table,MyISAM只要简单的读出保存好的行数,注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的

InnoDB : InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行

5外键支持:

mysiam表不支持外键,而InnoDB支持

      1.    
        数据库的悲观锁和乐观锁应用场景

      1. 海量数据过滤(100亿),黑名单过滤一个 url。

布隆过滤器:  它实际上是一个很长的二进制矢量和一系列随机映射函数。它可以用来判断一个元素是否在一个集合中。

              它的优势是只需要占用很小的内存空间以及有着高效的查询效率。它的本质是一个位数组:位数组就是数组的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1。

假设布隆过滤器有 3 个哈希函数:f1, f2, f3 和一个位数组 arr。

对值(单个url)进行三次哈希计算,得到三个值 n1, n2, n3。
       把位数组中三个元素 arr[n1], arr[n2], arr[3] 都置为 1

当要判断一个值是否在布隆过滤器中,对元素进行三次哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

  1. 设计模式

    1. 设计模式的六大原则
      1. 开放封闭原则(Open Close Principle):

尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化

      1. 里氏代换原则(Liskov Substitution Principle):

使用的基类可以在任何地方使用继承的子类,完美的替换基类。

      1. 依赖倒转原则(Dependence Inversion Principle)

依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,面向接口编程,依赖于抽象而不依赖于具体。

      1. 接口隔离原则(Interface Segregation Principle)

使用多个隔离的接口,比使用单个接口要好,降低依赖,降低耦合。

      1. 迪米特法则(最少知道原则)(Demeter Principle)

一个类尽量减少自己对其他对象的依赖,原则是低耦合,高内聚,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。

      1. 单一职责原则(Principle of single responsibility)

一个方法 一个类只负责一个职责,各个职责的程序改动,降低类和类的耦合,提高可读性,增加可维护性和可拓展性,降低可变性的风险。

    1. 单例模式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:单例类只能有一个实例。

单例类必须自己创建自己的唯一实例,并给所有其他对象提供这一实例。

优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。

2、避免对资源的多重占用(比如写文件操作)。

缺点:扩展困难,单例类的职责过重,与单一职责原则冲突。

使用场景:

1、要求生产唯一序列号。

2、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

步骤 1  创建一个 Singleton 类。

public class SingleObject {

//创建 SingleObject 的一个对象

private static SingleObject instance = new SingleObject();

//让构造函数为 private,这样该类就不会被实例化

private SingleObject(){}

//获取唯一可用的对象

public static SingleObject getInstance(){

return instance;

}

public void showMessage(){

System.out.println("Hello World!");

}

}

步骤 2从 singleton 类获取唯一的对象。

public class SingletonPatternDemo {

public static void main(String[] args) {

//获取唯一可用的对象

SingleObject object = SingleObject.getInstance();

object.showMessage(); //显示消息

}

}

      1. 懒汉式 线程不安全

是否 Lazy 初始化:是

实现难度:易

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

步骤 1创建一个 Singleton 类。

public class Singleton {

//创建 SingleObject 的一个对象

private static Singleton instance;

//让构造函数为 private,这样该类就不会被实例化

private Singleton (){}

//获取唯一可用的对象

public static Singleton getInstance(){

if (instance == null) {

instance = new Singleton();

}

return instance;

}

public void showMessage(){

System.out.println("Hello World!");

}

}

步骤 2从 singleton 类获取唯一的对象。

public class SingletonPatternDemo {

public static void main(String[] args) {

//获取唯一可用的对象

Singleton object = Singleton.getInstance();

object.showMessage(); //显示消息

}

}

      1. 懒汉式 线程安全

是否 Lazy 初始化:是

实现难度:易

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,

但是,效率很低,99% 情况下不需要同步。

优点:第一次调用才初始化,避免内存浪费。

缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁

public class Singleton {

private static Singleton instance;

private Singleton (){}

public static synchronized Singleton getInstance() {

if (instance == null) {

Instance = new Singleton();

}

return instance;

}

}

      1. 饿汉式 线程安全

是否 Lazy 初始化:否

实现难度:易

描述:这种方式比较常用,但容易产生垃圾对象。

优点:没有加锁,执行效率会提高。

缺点:类加载时就初始化,浪费内存。它基于 classloader 机制避免了多线程的同步问题,(类初始化时,会立即加载该对象,线程天生安全,调用效率高。)

public class Singleton {

private static Singleton instance = new Singleton();

private Singleton (){}

public static Singleton getInstance() {

return instance;

}

}

      1. 静态内部类方式 线程安全

静态内部方式: 结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。

public class Singleton {

    private Singleton () {

        System.out.println("私有Singleton构造参数初始化");

    }

    public static class SingletonClassInstance {

        private static final Singleton slt = new Singleton ();

    }

    // 方法没有同步

    public static Singleton getInstance() {

        return SingletonClassInstance.slt;

    }

    public static void main(String[] args) {

        Singleton s1 = Singleton.getInstance();

        Singleton s2 = Singleton.getInstance();

        System.out.println(s1 == s2);

    }

}

      1. 枚举单例 线程安全:

使用枚举实现单例模式

优点:  实现简单、调用效率高,枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞,

缺点:  没有延迟加载。

public class Singleton  {

    public static Singleton  getInstance() {

        return Demo.INSTANCE.getInstance();

    }

    public static void main(String[] args) {

        Singleton  s1 = Singleton .getInstance();

        Singleton  s2 = Singleton .getInstance();

        System.out.println(s1 == s2);

    }

    //定义枚举

  private static enum Demo {

      INSTANCE;

      // 枚举元素为单例

      private Singleton  Singleton ;

      private Demo() {

         System.out.println("枚举Demo私有构造参数");

         Singleton  = new Singleton ();

      }

      public Singleton  getInstance() {

         return Singleton ;

      }

  }

}

    1. 工厂模式
      1. 工厂模式好处

工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。

利用工厂模式可以降低程序的耦合性,为后期的维护修改提供了很大的便利。

将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。

      1. 工厂模式分类

简单工厂 :用来生产同一等级结构中的任意产品。(不支持拓展增加产品)

工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)  

抽象工厂 :用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)

      1. 简单工厂

优点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。

缺点:很明显工厂类集中了所有实例的创建逻辑,容易违反GRASPR的高内聚的责任分配原则

//创建工厂

public interface Car {

public void run();

}

//创建工厂的产品(宝马)

public class Bmw implements Car {

  public void run() {

      System.out.println("我是宝马汽车...");

  }

}

//创建工另外一种产品(奥迪)

public class AoDi implements Car {

  public void run() {

      System.out.println("我是奥迪汽车..");

  }

}

//创建核心工厂类,由他决定具体调用哪产品

public class CarFactory {

   public static Car createCar(String name) {

      if ("".equals(name)) {return null;}

      if(name.equals("奥迪")){return new AoDi(); }

      if(name.equals("宝马")){return new Bmw(); }

      return null;

  }

}

//演示创建工厂的具体实例

public class Client01 {

  public static void main(String[] args) {

      Car aodi  = CarFactory.createCar("奥迪");

      Car bmw  = CarFactory.createCar("宝马");

      aodi.run();

      bmw.run();

  }}

      1. 工厂方法模式

工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节

//创建工厂

public interface Car {  public void run();  }

//创建工厂方法调用接口(所有的产品需要new出来必须继承他来实现方法)

public interface CarFactory {  Car createCar();  }

//创建工厂的产品(奥迪)

public class AoDi implements Car {

  public void run() {

      System.out.println("我是奥迪汽车..");

  }

}

//创建工厂另外一种产品(宝马)

public class Bmw implements Car {

  public void run() {

      System.out.println("我是宝马汽车...");

  }

}

//创建工厂方法调用接口的实例(奥迪)

public class AoDiFactory implements CarFactory {

  public Car createCar() {

      return new AoDi();

  }

}

//创建工厂方法调用接口的实例(宝马)

public class BmwFactory implements CarFactory {

  public Car createCar() {

      return new Bmw();

  }

}

//演示创建工厂的具体实例

public class Client {

  public static void main(String[] args) {

      Car aodi = new AoDiFactory().createCar();

      Car jili = new BmwFactory().createCar();

      aodi.run();

      jili.run();

  }}

      1. 抽象工厂模式

抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。

//创建第一个子工厂,及实现类

public interface Car {void run();}

class CarA implements Car{

    public void run() {

       System.out.println("宝马");

    }

}

class CarB implements Car{

    public void run() {

       System.out.println("摩拜");

    }

}

//创建第二个子工厂,及实现类

public interface Engine {//发动机

    void run();

}

class EngineA implements Engine {

    public void run() {

        System.out.println("转的快!");

    }

}

class EngineB implements Engine {

    public void run() {

        System.out.println("转的慢!");

    }

}

//创建一个总工厂,及实现类(由总工厂的实现类决定调用那个工厂的那个实例)

public interface TotalFactory {

  Car createChair(); // 创建汽车

  Engine createEngine();// 创建发动机

}

//总工厂实现类,由他决定调用哪个工厂的那个实例

class TotalFactoryReally implements TotalFactory {

  public Engine createEngine() {

      return new EngineA();

  }

  public Car createChair() {return new CarA();}

}

//运行测试

public class Test {

    public static void main(String[] args) {

        TotalFactory totalFactory2 = new TotalFactoryReally();

        Car car = totalFactory2.createChair();

        car.run();

        TotalFactory totalFactory = new TotalFactoryReally();

        Engine engine = totalFactory.createEngine();

        engine.run();

    }

}

    1. 代理模式
      1. 什么是代理模式

通过代理控制对象的访问,可以在这个对象调用方法之前、调用方法之后去处理/添加新的功能。(也就是AO的P微实现)

代理在原有代码乃至原业务流程都不修改的情况下,直接在业务流程中切入新代码,增加新功能,这也和Spring的(面向切面编程)很相似

      1. 代理模式应用场景

Spring AOP、日志打印、异常处理、事务控制、权限控制等

      1. 代理的分类

静态代理(静态定义代理类)

动态代理(动态生成代理类,也称为Jdk自带动态代理)

Cglib 、javaassist(字节码操作库)

      1. 三种代理的区别

静态代理:简单代理模式,是动态代理的理论基础。常见使用在代理模式

jdk动态代理:使用反射完成代理。需要有顶层接口才能使用,常见是mybatis的mapper文件是代理。

cglib动态代理:也是使用反射完成代理,可以直接代理类(jdk动态代理不行),使用字节码技术,不能对 final类进行继承。(需要导入jar包)

    1. 建造者模式

建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的方式进行创建。

工厂类模式是提供的是创建单个类的产品

而建造者模式则是将各种产品集中起来进行管理,用来具有不同的属性的产品

    1. 模板方法模式

定义一个操作中的算法骨架(父类),而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构来重定义该算法的

    1. 外观模式

外观模式:也叫门面模式,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。

它向现有的系统添加一个接口,用这一个接口来隐藏实际的系统的复杂性。

使用外观模式,他外部看起来就是一个接口,其实他的内部有很多复杂的接口已经被实现

    1. 原型模式

原型设计模式简单来说就是克隆

原型表明了有一个样板实例,这个原型是可定制的。原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。

    1. 策略模式

定义了一系列的算法 或 逻辑 或 相同意义的操作,并将每一个算法、逻辑、操作封装起来,而且使它们还可以相互替换。(其实策略模式Java中用的非常非常广泛);我觉得主要是为了 简化 if…else 所带来的复杂和难以维护。

    1. 观察者模式

先讲什么是行为性模型,行为型模式关注的是系统中对象之间的相互交互,解决系统在运行时对象之间的相互通信和协作,进一步明确对象的职责。

观察者模式,是一种行为性模型,又叫发布-订阅模式,他定义对象之间一种一对多的依赖关系,使得当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。

观察者模式主要用于1对N的通知。当一个对象的状态变化时,他需要及时告知一系列对象,令他们做出相应。

  1. 网络连接
    1. http 和 https 的区别

HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。

HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上SSL 握手需要的 9 个包,所以一共是 12 个包。

HTTP 和 HTTPS 用的端口也不一样,前者是 80,后者是 443。

在 OSI 网络模型中,HTTP 工作于应用层,而 HTTPS 工作在传输层

    1.  
      七层模型各部分作用

应用层:

为应用程序提供服务并规定应用程序中通信相关的细节。包括文件传输、电子邮件、远程登录(虚拟终端)等协议。

表示层(PPDU):

处理数据的格式,处理数据加密和压缩。将应用处理的信息转换为适合网络传输的格式,或将来自下一层的数据转换为上层能够处理的格式。因此它主要负责数据格式的转换。

会话层(SPDU)

负责建立和断开通信连接(数据流动的逻辑通路),以及数据的分割等数据传输相关的管理。

传输层(TPDU):

起着可靠传输的作用。只在通信双方节点上进行处理,而无需在路由器上处理。

网络层(报文):

将数据传输到目标地址。目标地址可以是多个网络通过路由器连接而成的某一个地址。因此这一层主要负责寻址和路由选择。

数据链路层(帧):

负责物理层面上互连的、节点之间的通信传输。例如与1个以太网相连的2个节点之间的通信。建立和维护数据链路,提供物理地址(MAC地址)。

物理层(比特):

负责0、1比特流(0、1序列)与电压的高低、光的闪灭之间的互换。进行比特流的传输(比特流也是数据流,在不同的介质下表现的形式也不一样,在光纤中是光信号传递,这个比特流也就是光信号,如果是wifi,传递用的是光信号,那么比特流也就是光信号)。

    1. 三次握手

第一次握手

首先客户端向服务器端发送一段TCP报文,其中:

标记位为SYN,表示“请求建立新连接”;

序号为Seq=X(X一般为1);

第二次握手

服务器端收到来自客户端的TCP报文后,结束LISTEN阶段。并返回一段TCP报文,其中:

标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);序号为Seq=y;确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值。

第三次握手

客户端收到来自服务器端的确认收到数据的TCP报文后,明确了从客户端到服务器的数据传输是正常的。返回最后一段TCP报文。其中:

标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);

序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;

确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值

为什么TCP客户端最后还要发送一次确认呢?

目的:主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。

如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。

如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。

客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。

    1. 四次挥手

第一次挥手

1)首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:

标记位为FIN,表示“请求释放连接“;

序号为Seq=u;随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。

第二次挥手

2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:

标记位为ACK,表示“接收到客户端发送的释放连接的请求”;

确认号为Ack=u+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;

序号为Seq= v

随后服务器端开始准备释放服务器端到客户端方向上的连接。客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段

前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了

第三次挥手

3)服务器端自从发出ACK确认报文之后,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:

标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。

序号为Seq=Y;确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。

随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。

第四次挥手

4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:

标记位为ACK,表示“接收到服务器准备好释放连接的信号”。

序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。

确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。

随后客户端开始在TIME-WAIT阶段等待2MSL。服务器端收到从客户端发出的TCP报文之后结束LAST-ACK阶段,进入CLOSED阶段。由此正式确认关闭服务器端到客户端方向上的连接。客户端等待完2MSL之后,结束TIME-WAIT阶段,进入CLOSED阶段,由此完成“四次挥手”。

    1. 什么是 WebSockets?

WebSocket 是一种计算机通信协议,通过单个 TCP 连接提供全双工通信信道。

1、WebSocket 是双向的 -使用 WebSocket 客户端或服务器可以发起消息发送。

2、WebSocket 是全双工的 -客户端和服务器通信是相互独立的。

3、单个 TCP 连接 -初始连接使用 HTTP,然后将此连接升级到基于套接字的连接。然后这个单一连接用于所有未来的通信

4、Light -与 http 相比,WebSocket 消息数据交换要轻得多。

  1. 数据结构
    1. 二叉查找树(BST):不平衡

二叉查找树(BST,Binary Search Tree),也叫二叉排序树,在二叉树的基础上需要满足:任意节点的左子树上所有节点值不大于根节点的值,任意节点的右子树上所有节点值不小于根节点的值。如下是一颗BST。如图一

          

基于二叉查找树的这种特点,在查找某个节点的时候,可以采取类似于二分查找的思想,快速找到某个节点。n 个节点的二叉树,正常情况下,查找的时间复杂度为 O(logN)。之所以说是正常情况下,是因为二叉查找树有可能出现一种极端的情况,如图二;此时BST退化为链表,时间复杂度退化为O(n)。为了解决这个问题,引入了平衡二叉树。

    1. 平衡二叉树(AVL):旋转耗时

AVL树是严格的平衡二叉树,所有节点的左右子树高度差不能超过1;AVL树查找、插入和删除在平均和最坏情况下都是O(lgn)。

AVL实现平衡的关键在于旋转操作:插入和删除可能破坏二叉树的平衡,此时需要通过一次或多次树旋转来重新平衡这个树。当插入数据时,最多只需要1次旋转(单旋转或双旋转);但是当删除数据时,会导致树失衡,AVL需要维护从被删除节点到根节点这条路径上所有节点的平衡,旋转的量级为O(lgn)。

由于旋转的耗时,AVL树在删除数据时效率很低;在删除操作较多时,维护平衡所需的代价可能高于其带来的好处,因此AVL实际使用并不广泛。

    1. 红黑树:树太高

与AVL树相比,红黑树并不追求严格的平衡,而是大致的平衡:只是确保从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。

特点:

  1. 根节点总是黑色的。
  2. 每个节点非黑即红。
  3. 每个叶子节点(NIL)都是黑色的。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点]
  4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定)。
  5. 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑色节点。[这里指到叶子节点的路径]

Java 使用到红黑树的有 TreeSet 和 JDK8 的 HashMap。红黑树的【插入/删除】都要满足以上 5 个特性,操作非常复杂。

 
从实现来看,红黑树最大的特点是每个节点都属于两种颜色(红色或黑色)之一,且节点颜色的划分需要满足特定的规则(具体规则略)。红黑树示例如下

    1. B+树

B+树也是多路平衡查找树,其与B树的区别主要在于:

  1. B树中每个节点(包括叶节点和非叶节点)都存储真实的数据,B+树中只有叶子节点存储真实的数据,非叶节点只存储键。在MySQL中,这里所说的真实数据,可能是行的全部数据(如Innodb的聚簇索引),也可能只是行的主键(如Innodb的辅助索引),或者是行所在的地址(如MyIsam的非聚簇索引)。
  2. B树中一条记录只会出现一次,不会重复出现,而B+树的键则可能重复重现——一定会在叶节点出现,也可能在非叶节点重复出现。
  3. B+树的叶节点之间通过双向链表链接。
  4. B树中的非叶节点,记录数比子节点个数少1;而B+树中记录数与子节点个数相同。

由此,B+树与B树相比,有以下优势:

  1. 更少的IO次数:B+树的非叶节点只包含键,而不包含真实数据,因此每个节点存储的记录个数比B数多很多(即阶m更大),因此B+树的高度更低,访问时所需要的IO次数更少。此外,由于每个节点存储的记录数更多,所以对访问局部性原理的利用更好,缓存命中率更高。
  2. 更适于范围查询:在B树中进行范围查询时,首先找到要查找的下限,然后对B树进行中序遍历,直到找到查找的上限;而B+树的范围查询,只需要对链表进行遍历即可。
  3. 更稳定的查询效率:B树的查询时间复杂度在1到树高之间(分别对应记录在根节点和叶节点),而B+树的查询复杂度则稳定为树高,因为所有数据都在叶节点。
  4. B+树也存在劣势:由于键会重复出现,因此会占用更多的空间。但是与带来的性能优势相比,空间劣势往往可以接受,因此B+树的在数据库中的使用比B树更加广泛。

  1. 运维
    1. Kubernetes (K8S)

整体流程

用户执行kubectl/userClient向api server发起一个命令,经过认证授权后,经过scheduler的各种策略,得到一个目标node,

然后告诉api server,api server 会请求相关node的kubelet,通过kubelet把pod运行起来,Api server还会将pod的信息保存在etcd;

pod运行起来后,controllermanager就会负责管理pod的状态,如若pod挂了,controllermanager就会重新创建一个一样的pod,或者像扩缩容等;

pod有一个独立的ip地址,但pod的IP是易变的,如异常重启,或服务升级的时候,IP都会变,这就有了service;

完成service工作的具体模块是kube-proxy;在每个node上都会有一个kube-proxy,在任何一个节点上访问一个service的虚拟ip,都可以访问到pod;

service的IP可以在集群内部访问到,在集群外呢?service可以把服务端口暴露在当前的node上,外面的请求直接访问到node上的端口就可以访问到service了;

 
kube-dns实现通过名字来访问到一个pod

    1. Docker

你可能感兴趣的:(Java,java,spring)