- Java基础概念
- 5.算法
- String相关
- 集合
- 多线程
- Maven
- JavaWeb
- 数据库
- 1. 基本的sql语句格式
- 2. MySQL存储引擎
- 3. MySQL 索引使用的数据结构 , 说一下 Btree 或者 B+tree 的具体原理?
- 4.【掌握】MySQL 数据库的优化-索引
- 5.【了解】MySQL 如何实现读写分离
- 6.【了解】什么是存储过程?
- 7. 什么是 sql 注入?如何防范?
- 8. 请说一下事务的特点、隔离级别、以及不同隔离级别会产生的问题
- 9. 为什么mysql选可重复读作为默认的隔离级别?你们项目中选了哪个隔离级别?为什么?
- 10.【了解/掌握】分库分表如何实现
- 10. 分库分表策略
- 11. 为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?
- 12.锁的分类
- 13.MySQL数据库的优化
- Linux
- Nginx
- Redis
- Spring
- SpringMVC
- 【了解】Dubbo
- 【了解】Zookeeper
- 【掌握】SpringBoot
- 【掌握】SpringCloud
- docker
- 【了解/掌握】SpringSecurity
- 项目相关
Java基础概念
1.Java的反射是什么?
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
获取Class类对象的三种方式
- 类名.class属性
- 对象名.getClass()方法
- Class.forName(全类名)方法
一个类的Class对象是唯一的,所以不同方式获取的Class对象是同一个。
2.IO流
- 字节流:FileInputStream、FileOutputStream
可以操作(拷贝)所有类型的文件
- 字节缓冲流:BufferedOutputStream BufferedInputStream
可以提高读和写效率,不能直接操作文件,需要传递字节流
字节流跟字符流的区别
1、 以*stream 结尾都是字节流,reader 和 writer 结尾都是字符流。
2、 InputStream 是所有字节输入流的父类,OutputStream 是所有字节输出流的父类。
3、 Reader 是字符输入流的父类,Writer 是字符输出流的父类
4、 区别:
4.1、读写的时候一个是按字节读写,一个是按字符。
4.2、在读写文件需要对内容按行处理,比如比较特定字符,处理某一行数据的时候一般会选择字符流。只是读写文件,和文件内容无关的,一般选择字节流
3.抽象类和接口的区别
接口用来定义规范、用来做功能的拓展;抽象类是对子类共性功能的抽取
4.jdk1.8的新特性有哪些
- Lambda表达式(本质是一段匿名内部类)
- Stream API(把真正的函数式编程风格引入到Java中)
- 接口中的默认方法和静态方法
- 新时间日期API (指的是LocalDate,使用起来更灵活,可以获取月份中的第几天等)
- hashmap底层原理改变(jdk1.7版本底层是使用哈希表,数据+链表的形式,jdk1.8之后,数组+链表+红黑树)
5.算法
冒泡排序
概述:对要进行排序的数据中相邻的数据进行两两比较,将较大的数据放在后面,依次对所有的数据进行操作,直至所有数据按要求完成排序。
求质数(素数)
String相关
1.【掌握】String 和 StringBuffer,StringBuilder 的区别
1、String 是“字符串常量”,也就是不可改变的对象。 2、StringBuffer 与 StringBuilder,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,不像 String 一样创建一些对象进行操作,所以速度就快了。 3、SringBuffer:线程安全的;StringBuilder:线程非安全的 4、使用场景
- 如果要操作少量的数据用 String
- 单线程操作字符串缓冲区下操作大量数据用 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据 用 StringBuffer 。【在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类】
- 如果要操作少量的数据用 String
看如何操作:
常量的声明,少量的变量运算,选String
频繁的字符串运算,如拼接,修改,删除等,选StringBuilder或StringBuffer
StringBuilder和StringBuffer如何选?
涉及线程安全,选StringBuffer
不涉及线程安全,选StringBuilder
为什么这样选?
String:String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了
当两个字符串拼接时,会产生一个新的字符串去接收拼接的结果,但前两个字符串并没有销毁,当拼接操作变多以后,产生大量的String 临时类,消耗内存,增加对垃圾回收占用CPU的时间
StingBuilder和StringBuffer:StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
2. StringBuilder和String之间的相互转化
- StringBuilder转换为String
public String toString( ):通过toString( )就可以实现把StringBuilder转换为String
- String转换为StringBuilder
public StringBuilder(String s) :通过构造方法就可以实现把String转换为StringBuilder
3. String概念
- Java程序中所有的双引号字符串,都是String类的对象
- 字符串不可变,它们的值在创建后不能被更改
字符串对象中是不能直接修改内容,因为没有相应的set方法,只能是整个对象的替换
4. 构造方法能创建对象,双引号也能创建字符串对象,有什么区别?
双引号创建的字符串对象,在字符串常量池中存储;通过构造方法创建的字符串对象,在堆内存中存储
①双引号创建字符串对象
以””方式给出的字符串,只要字符序列相同(顺序和大小写) ,无论在程序代码中出现几次,JVM都只会建立一个String对象,并在字符串常量池中维护
字符串常量池:
当使用双引号创建字符串对象的时候,系统会检查该字符串是否在字符串常量池中存在
不存在:创建; 存在:不会重新创建,而是直接复用
②构造方法创建对象
通过new创建的字符串对象,每一次new都会申请一个内存空间 ,虽然内容相同,但是地址值不同
上面的代码中, JVM会首先创建一个字符数组,然后每一次new的时候都会有一 个新的地址
5. String特点——常见面试题
特点:
- Java程序中所有的双引号字符串,都是String类的对象
- 字符串不可变,它们的值在创建后不能被更改
- 虽然String的值是不可变的,但是它们可以被共享(共享指的是使用双引号创建的字符串对象,在字符串常量池中存储的字符串是可以复用的)
集合
1.【了解】重写 equals 为什么要重写 hashcode
在 Java API 文档中关于 hashCode 方法有一条这样的规定:
如果两个对象通过调用 equals 方法是相等的,那么这两个对象调用 hashCode 方法必须返回相同的值。
我们知道创建一个对象如果不重写 equals 和 hashCode 方法 ,默认集成超类 Object 中的两方法, Object 类中hashCode 方法是通过 Object 对象的地址计算出来的,因为 Object 对象只与自身相等,所以同一个对象的地址总是相等的,计算取得的哈希码也必然相等,对于不同的对象,由于地址不同,所获取的哈希码自然也不会相等。因此到这里我们就明白了,如果一个类重写了 equals 方法,但没有重写 hashCode 方法, 将会直接违法了上面的规定,这样的话,如果我们通过映射表(Map 接口)操作相关对象时,就无法达到我们预期想要的效果。
总结来说就是两点
1.使用hashcode方法提前校验,可以避免每一次比对都调用equals方法,提高效率
2.保证是同一个对象,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现。
2. 什么情况下重写 hashcode和equals
没有进行重写之前:
equals方法是Object的方法,比较的是内存地址;而默认的hashcode方法返回的是一个哈希值,也是对象的内存地址值,当我们没有重写这两个方法的时候,创建两个对象,成员变量完全一样,把这两个对象放入map集合,会发现不报错,这是因为hashMap底层是哈希表,这两个对象的哈希值明显不一样,所以可以放进去。
当我们想要一种业务上的对象相等,比如实现成员变量值相同,我们就认为是同一个对象,那么就需要重写equals和hashcode
Java集合都有哪些?
l Java集合分为Collection(List、Set)和Map两大类
l List:ArrayList、LinkedList、Vector、Stack
l Set:HashSet、LinkedHashSet、TreeSet
l Map:HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap、Hashtable
哪些集合类时线程安全的
l 线程安全:当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。
l 线程不安全:多个线程能够同时操作某个数据,从而出现数据不一致
l 对于线程不安全的问题,一般会使用synchronized 关键字加锁同步控制。
l 线程安全:Vector、Stack、HashTable、ConcurrentHashMap。
l 非线程安全:ArrayList、HashMap、HashSet、TreeMap、TreeSet。
3. 几大集合存储特性以及区别?
List、Set、Map 的区别:
- List 是允许存重复关系,有序的,可以插入多个 null 元素
- Set 不允许重复对象,无序的,只允许一个 null 元素,没有索引
- Map 是键值对存储数据,键是唯一的,可以有相同的值
HashMap 与 HashTable 的区别
- HashMap 是线程非安全的,非同步的;允许 null key 和 null value;HashMap 的初始容量是 16
HashTable 是线程安全的,同步的,不允许 null key 和 null value; HashTable的初始容量是11,扩容策略是翻倍加1,即当前容量 capacity * 2 + 1
4.【掌握】List
ArrayList、LinkedList、Vector的区别:
- ArrayList集合:底层是数组结构实现,查询快、增删慢;非线程安全,非同步的;默认容量是 10,扩容是 1.5 倍扩容
- LinkedList集合:底层是双向链表结构实现,查询慢、增删快
- Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是较少使用。为了提高效率,Vector默认扩充为2倍
ArrayList为什么它查询快增删慢?
- 查询快
ArrayList底层是数组的结构,查询快是因为它是连续存放元素的,当我们找到第一个元素的首地址,再加上每个元素的占据的字节大小就能定位到对应的元素,不需要进行遍历。
- 增删慢(使用数组的弊端)
- 每当插入或删除操作时 都需要向前或向后的移动元素
- 当插入元素时 需要判定是否需要扩容操作
- 扩容操作:创建一个新数组 增加length 再将元素放入进去,较为繁琐
ArrayList有最大或者最小的容量吗?
有最大最小容量限制,默认初始容量为10,当超过数组的长度时,按照1.5倍进行扩容,最大容量为Integer.MAX_VALUE - 8,【为什么是减8,因为数组对象有一个额外的元数据,用于表示数组的大小。】
LinkedList为什么查询慢、增删快?
- 查询慢(双向链表的弊端)
双向链表的查询逻辑是他会根据index的大小判断是从前开始遍历还是从后开始遍历,如果index更靠前就从前遍历,更靠后就从后遍历,也就是在查询过程中,会一次次的移动指针直到获取到index的值。 - 增删快
只需要修改插入位置或删除位置左右数据的引用目标即可。
总结:如果查询修改比较多应该选用ArrayList,如果增加和删除操作比较多应该选择Linkedlist
5.【掌握】Set
Set集合概述和特点
- 不可以存储重复元素
- 存取顺序不一致
- 没有索引
5.1 TreeSet
可以将元素按照规则进行排序———》!!想要使用TreeSet,需要制定排序规则
如果存储的是已存在的引用类型数据,比如Integer、String等,那么底层已经有实现自然排序。
如果存储的是自定义对象,那么必须实现自然排序或比较器排序,否则直接打印集合时会出现类转换异常
- 两种排序比较方式总结【理解】
- 自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
- 比较器排序: 创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
- 在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,必须使用比较器排序
两种方式中关于返回值的规则
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
- 如果返回值为正数,表示当前存入的元素是较大值,存右边
TreeSet集合:底层是红黑树实现,遍历时,先获取左边,再获取中间,再获取右边
二叉树(任意一个节点的度要小于等于2)
二叉查找树(左节点上的值小于自己,右节点上的值大于自己)
平衡二叉树(二叉树左右两个子树的高度差不超过1、任意节点的左右两个子树都是一颗平衡二叉树)
红黑树的特点
- 平衡二叉B树
- 每一个节点可以是红或者黑
- 红黑树不是高度平衡的,它的平衡是通过”自己的红黑规则”进行实现的
- 红黑树的红黑规则
- 每一个节点或是红色的,或者是黑色的
- 根节点必须是黑色
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的
- 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连 的情况)
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
5.2 HashSet
底层数据结构是哈希表
哈希值【理解】
- 哈希值(哈希码值)简介
是JDK根据对象的地址或者属性值,算出来的int类型的整数数值
- 如何获取对象的哈希值
Object类中的public int hashCode():根据对象的地址值计算哈希值并返回
- 哈希值的特点
- 如果没有重写hashCode方法,那么是根据对象的地址值计算出哈希值。同一个对象多次调用hasCode()方法返回的哈希值是相同的,不同对象的哈希值是不一样的。
- 如果重写了hashCode方法,那么是根据对象的属性值计算出哈希值。如果不同对象的属性值是一样的,那么计算出来的哈希值也是一样的。
哈希表结构
- JDK1.8以前(不包括JDK8),底层采用数组+链表实现。
- JDK1.8以后,底层进行了优化。底层采用数组+链表+红黑树实现。
- 节点个数少于等于8个:数组 + 链表
- 当链表上的元素过多时,不利于添加,也不利于查询。
- 节点个数多于8个:数组 + 红黑树
6.【掌握】HashMap
HashMap的特点:
- HashMap底层是哈希表结构的
- 依赖hashCode方法和equals方法保证键的唯一
如果键要存储的是自定义对象,需要重写hashCode和equals方法
如果键存储的是Java已有引用数据类型对象,那么不需要重写hashCode和equals方法
HashMap 的底层数据结构?HashMap 的 put 方法是如何实现添加数据到 HashMap 集合中的?
1、 jdk1.7 之前 HashMap 底层数据结构是:哈希表数组+链表的数据结构。jdk1.8 之后底层数据结构是:哈
希表数组+链表+红黑树。
2、 当调用 put 方法时
1) 对 Key 求 Hash 值,然后再计算出对应的数组索引。
2) 如果数组索引处的值为null,直接放在该数组位置上。
3) 如果不为null,则调用equals方法比较键的属性值,如果返回true,则覆盖替换旧值;如果返回false,则以链表的方式将新值放入到数组索引处,链接到旧值后面。
4) 如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树
5) 如果数组满了(容量 16*加载因子 0.75),就需要 resize(扩容 2 倍后重排)。
3、 get 过程
当我们调用 get()方法,HashMap 会使用键对象的 hashcode 找到 bucket 位置,找到 bucket 位置之后, 会调用 keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
TreeMap的特点:
- TreeMap底层是红黑树结构的
- 依赖自然排序或者比较器排序,对键进行排序(只关心键,不关心值)
如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则
7.【了解】HashMap 和 ConcurrentHashMap 的区别?
HashMap是线程不安全的(多线程环境下可能会存在数据安全问题)。
Hashtable是线程安全的,采取悲观锁synchronized的形式保证数据的安全性,只要有线程访问,会将整张表锁起来,效率低下(悲观锁)
ConcurrentHashMap也是线程安全的,效率较高。 在JDK7和JDK8中,底层原理不一样。
1、 HashMap:底层数组+ 链表实现,可以存储 null 键和 null 值,线程不安全。
- 初始 size 为 16,扩容:newsize = oldsize*2,size 一定为 2 的 n 次幂。
- 扩容针对整个 M a p ,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入。
- 插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)。
- 当 Map 中元素总数超过 Entry 数组的 75%,触发扩容操作,为了减少链表长度,元素分配更均匀。
- 计算 index 方法:index = hash & (tab.length – 1)。
- jdk1.8 之后加入了红黑树。
2、 ConcurrentHashMap:
- 底层采用分段的数组(长度16的大数组和长度2的小数组)+ 链表实现,线程安全。
- 通过把整个 Map 分为 N 个 Segment,可以提供相同的线程安全,但是效率提升 N 倍,默认提升 16 (支持16个线程同时访问)倍。(读操作不加锁,由于 HashEntry 的 value 变量是 volatile 的,也能保证读取到最新的值。)
- ConcurrentHashMap 允许多个修改操作并发进行,其关键在于使用了锁分离技术,有些方法需要跨段,比如 size()和 containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。
- 扩容:段内(长度为2的小数组)扩容(段内元素超过该段对应 Entry 数组长度的 75%触发扩容,不会对整个 Map 进行扩容),插入前检测需不需要扩容,有效避免无效扩容。
-
多线程
1.【了解】什么是乐观锁、什么是悲观锁?(MySQL处也有介绍)
多线程处synchronized和CAS的区别 :
相同点:
在多线程情况下,都可以保证共享数据的安全性。
不同点:
synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每次操作共享数据之前,都会上锁。(悲观锁)
cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。
如果别人修改过,那么我再次获取现在最新的值。(自旋)
如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)2.【掌握】ThreadPool(线程池)用法与优势?
1、 ThreadPool 优点
a) 减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
b) 可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约 1MB 内存,线程开的越多,消耗的内存也就越大,最后死机)
线程池优势:
第一:降低资源消耗。通过重复利用已创建的线程 降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2、 比较重要的几个类:
3、 任务执行顺序:
1)当线程数小于 corePoolSize 时,创建线程执行任务
2)当线程数大于等于 corePoolSize 并且 workQueue 没有满时,放入 workQueue 中
3)线程数大于等于 corePoolSize 并且当 workQueue 满时,新任务新建线程运行,线程总数要小于 maximumPoolSize
4)当线程总数等于 maximumPoolSize 并且 workQueue 满了的时候执行 handler 的 rejectedExecution。 也就是拒绝策略 线程池的设计思路
- 线程池-Executors默认线程池
使用Executors中所提供的静态方法来创建线程池
static ExecutorService newCachedThreadPool() 创建一个默认的线程池
Maven
1.Maven 的作用,Maven 依赖版本冲突怎么解决?
Maven 的作用
- 依赖管理:通过引入坐标的形式,引入工程需要的jar包
- 一键构建:通过maven的插件,一键执行,可以完成对工程的一些操作(编译、打包、测试等)
- 统一的目录结构
Maven 依赖冲突解决方案:统一 jar 包。
- 通过 IDE 自带的 maven 依赖管理进行查看冲突的 jar 包。
- 通过 maven 命令mvn dependency:tree -Dverbose 进行查看。在执行结果中出现 omitted for conflict with 这样的字样,就表示项目中存在依赖冲突的 jar 包。
JavaWeb
1.【掌握】重定向与转发的区别?
1、请求次数:重定向是浏览器向服务器发送一个请求并收到响应后再次向一个新地址发出请求,转发是服务器收到请求后为了完成响应跳转到一个新的地址;重定向至少请求两次,转发请求一次;
2、地址栏不同:重定向地址栏会发生变化,转发地址栏不会发生变化;
3、是否共享数据:重定向两次请求不共享数据,转发一次请求共享数据(在request级别使用信息共享,使用重定向必然出错);
4、跳转限制:重定向可以跳转到任意URL,转发只能跳转本站点资源;
5、发生行为不同:重定向是客户端行为,转发是服务器端行为;2.【了解】如何防止表单重复提交?
1、 利用 JavaScript 防止表单重复提交:
定义一个表单是否已经提交的标识,默认值为 false,第一次点击提交后就将标识改为 true,只有标识为false才可以提交,为 true 则阻止表单提交。
2、 设置按钮不可用:
在第一次点击提交按钮后将提交按钮改为不可用状态,就不可以再次点击该按钮了。但是当刷新页面时还是可以再次提交。
3、 利用 session 防止表单重复提交:
在服务器端生成一个唯一的随机标识号,也就是Token(令牌 ),同时在当前用户的 Session 域 中保护这个 Token. 然后将 Token 发送到客户端的 form 表单中,在 form 表单中使用隐藏域来存储这个 Token,表单提交的时候连同这个 Token 一起提交到服务器端,然后在服务器端判断客户端提交上来的 Token 与服务器端生成的 Token 是否一致,如果不一致,那就是重复提交了,此时服务器可以不处理重复提交的表单,如果相同则处理表单提交,处理 完成后清除当前用户的 Session 域中存储的标识号3.Post 和 Get 请求区别?
- Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post通过请求体传输,所有操作对用户来说都是不可见的。
2. Get传送的数据量较小,这主要是因为受URL长度限制;Post传送的数据量较大,一般被默认为不受限制。
3. Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。??
4. Get执行效率却比Post方法好。Get是form提交的默认方法
5.get 产生一个 tcp 数据包;post 产生两个 tcp 数据包。??4. Cookie 和 Session 区别
1、 存储位置不同:
cookie 的数据信息存放在客户端浏览器上。
session 的数据信息存放在服务器上。
2、 存储容量不同:
单个 cookie 保存的数据<=4KB,一个站点最多保存 20 个 Cookie。
对于 session 来说并没有上限,但出于对服务器端的性能考虑,session 内不要存放过多的东西, 并且设置 session 删除机制。
3、 存储方式不同:
cookie 中只能保管 ASCII 字符串,并需要通过编码方式存储为 Unicode 字符或者二进制数据。
session 中能够存储任何类型的数据,包括且不限于 string,integer,list,map 等
4、 隐私策略不同:
cookie 对客户端是可见的,别有用心的人可以分析存放在本地的 cookie 并进行 cookie 欺骗,所 以它是不安全的。
session 存储在服务器上,对客户端是透明的,不存在敏感信息泄漏的风险。
5、 有效期上不同:
开发可以通过设置 cookie 的属性,达到使 cookie 长期有效的效果
session 依赖于名为 JSESSIONID 的 cookie,而 cookie JSESSIONID 的过期时间默认为-1,只需关闭窗口该 session 就会失效,因而 session 不能达到长期有效的效果。
6、 服务器压力不同:
cookie 保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie 是很好的选择。
session 是保管在服务器端的,每个用户都会产生一个 session。假如并发访问的用户十分多,会产生十分多的 session,耗费大量的内存。
7、 跨域支持上不同:
cookie 支持跨域名访问。
session 不支持跨域名访问。5.说一下 session 的工作原理?从浏览器和服务器的交互流程上说一说?
用户第一次请求服务器时,服务器端会生成一个sessionid
服务器端将生成的sessionid返回给客户端,通过set-cookie
客户端收到sessionid会将它保存在cookie中,当客户端再次访问服务端时会带上这个sessionid
当服务端再次接收到来自客户端的请求时,会先去检查是否存在sessionid,不存在就新建一个sessionid重复1,2的流程,如果存在就去遍历服务端的session文件,找到与这个sessionid相对应的文件,文件中的键值便是sessionid,值为当前用户的一些信息
此后的请求都会交换这个 Session ID,进行有状态的会话。
数据库
1. 基本的sql语句格式
**分组查询一般会配合聚合函数使用
分页查询
SELECT 列名 FROM 表名
[WHERE 条件]
[GROUP BY 分组列名]
[HAVING 分组后条件过滤]
[ORDER BY 排序列名 排序方式]
LIMIT 当前页数,每页显示的条数;2. MySQL存储引擎
MySQL 支持多种存储引擎,在处理不同类型的应用时,可以通过选择不同的存储引擎提高应用的效 率,或者提供灵活的存储。
MySQL5.7支持的引擎包括:InnoDB、MyISAM、MEMORY、Archive、Federate、CSV、BLACKHOLE等
其中较为常用的有三种:InnoDB、MyISAM、MEMORY
- 常用的存储引擎
- MyISAM存储引擎
- 访问快,不支持事务和外键
- InnoDB存储引擎(MySQL5.5版本后默认的存储引擎)
- 支持事务和外键 ,占用磁盘空间大 ,支持并发控制(支持集群索引),支持分布式事务处理
- MEMORY存储引擎
- 内存存储 , 速度快 ,不安全 ,适合小量快速访问的数据。
- MyISAM存储引擎
3. MySQL 索引使用的数据结构 , 说一下 Btree 或者 B+tree 的具体原理?
目前大部分数据库系统采用 B-Tree 或其变种 B+Tree 作为索引结构,MySQL 采用了 B+Tree。
B Tree 指的是平衡树,并且所有叶子节点位于同一层。BTree每一个节点上,除了保存键值外,还会保存真实的数据,那么在读取一个磁盘块时,所有的数据还会被读取出来,会增加磁盘查询数据时的IO次数,查询效率不会很高。
B+Tree的非叶子节点只存储key值,所有数据存储在叶子节点。这样做的好处是检查磁盘的IO次数,提高查询速度。而且叶子节点之间都有连接指针,方便进行范围查询。
4.【掌握】MySQL 数据库的优化-索引
用的最多的是左连接 查询速度最快<br /> 减少数据库的连接次数<br /> 批量插入 不要在for循环中 进行一次次的插入insert into() insert into().....,要使用insert into()()()<br />索引优化
- 考虑在 where 及 order by 和 join 涉及的列上建立索引,能够对查询进行优化,从而避免全表扫 描。
- 不要在 where 条件中对字段进行关于 null 的判断,否则会导致放弃使用索引而进行全表扫描。
- 当某一列有大量重复数据,如 sex 性别字段中男女各一半,即使给这个字段建立索引,索引也可能 并不有效,因为 sql 是根据表中数据进行查询优化的。
- 索引太多的话虽然能够提高查询效率,但是 insert 和 update 时可能会重建索引,插入和更新效率 将会降低,一般一个表的索引数最好不要超过 6 个。
尽量避免更新索引列的数据,因为索引数据列的顺序是表记录的物理存储顺序,一旦改变数据值将导致整个表记录的顺序调整,耗费大量资源,所以当某列需要频繁更新并频繁查询,那么需要全面 衡量是否将该列设为索引。
5.【了解】MySQL 如何实现读写分离
1、 使用前提:
当我们的数据量很大时,数据库服务器的压力变大,这时候我们需要从架构方面来解决这一问题,在一个网站中读的操作很多,写的操作很少,这时候我们需要配置读写分离,把读操作和写操作分离出来, 最大程度的利用好数据库服务器。
2、 实现原理:读写分离的实现原理就是在执行 SQL 语句的时候,判断到底是读操作还是写操作,把读的操作转向到读服务器上 (从服务器,一般是多台),写的操作转到写的服务器上(主服务器,一般是一台,视数据量来看)。当然为了保证多台数据库数据的一致性,需要主从复制。
- 主从复制的实现原理是:MySQL 中有一种日志,叫做 bin 日志(二进制日志),会记录下所有修改过数据库的 sql 语句。
主从复制的原理实际是多台服务器都开启 bin 日志,然后主服务器会把执行过的 sql 语句记录到 bin 日志中,之后从服务器读取这个 bin 日志,把该日志的内容保存到自己中继日志里面,从服务器再把中继日志中记录的 sql 语句同样的执行一遍。这样从服务器上的数据就和主服务器相同了。
6.【了解】什么是存储过程?
存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的 SQL 语句集,它存储在 数据库中,一次编译后永久有效,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数) 来执行它。存储过程是数据库中的一个重要对象。在数据量特别庞大的情况下利用存储过程能达到倍速 的效率提升。
存储过程有三种类型参数:
IN 是参数的默认模式,这种模式就是在程序运行的时候已经具有值,在程序体中值不会改变。
OUT 模式定义的参数只能在过程体内部赋值,表示该参数可以将某个值传递回调用他的过程。
IN OUT 表示高参数可以向该过程中传递值,也可以将某个值传出去。
- 使用存储过程的好处:
重复使用:存储过程可以重复使用,从而可以减少数据库开发人员的工作量。
减少网络流量:存储过程位于服务器上,调用的时候只需要传递存储过程的名称以及参数就可以了, 因此降低了网络传输的数据量。
安全性:参数化的存储过程可以防止 SQL 注入式攻击,而且可以将 Grant、Deny 以及 Revoke 权限应用于存储 过程。
7. 什么是 sql 注入?如何防范?
- 定义:
sql 注入是一种将 sql 代码添加到输入参数中,改变原来 sql 的含义并传递到 sql 服务器解析并执行的一种攻击手法。如在查询的条件 where 后加上 id = -1 OR 1=1 。会导致 sql 注入。
2. 防范:
- 使用 PreparedStatement 进行预编译处理 sql。
- 使用正则表达式过滤传入的参数。
- 后端程序进行判断,特殊字符进行处理
- 利用框架,比如mybatis等框架进行防范
8. 请说一下事务的特点、隔离级别、以及不同隔离级别会产生的问题
事务的四大特征
原子性:被事务管理的一组操作是一个整体,要么全部成功,要么全部失败。
一致性:事务在操作前和操作后的总量保持一致
隔离性:事务和事务之间的操作应该具备隔离性。隔离性分为四个等级,每个隔离级别能够解决的问题不同
持久性:事务无论是提交还是回滚,都会持久化保存到数据库中
隔离级别分为四个
读未提交:一个事务读到了另一个事务还没有提交的数据。可能是产生的问题:脏读、虚读、幻读
读已提交:一个事务只能读到另一个事务提交之后的数据,但是会在一次事务中读到不同的内容。可能产生的问题:虚读、幻读
可重复读:一个事务在未提交之前,读到的内容是不会变化的,即可以放心的重复读取数据。可能产生的问题:幻读
串行化:对每一次操作进行锁表,最安全。没有任何问题,但是效率最低
9. 为什么mysql选可重复读作为默认的隔离级别?你们项目中选了哪个隔离级别?为什么?
选可重复读作为默认的隔离级别,是为了解决主从复制的问题。主从复制基于binlog日志,在mysql5.0版本前,binlog格式只支持statement格式(记录修改的sql语句),这种情况下会出现主从数据不一致问题。将mysql的隔离级别设置为可重复读,可以保证主从复制不出问题。
我们项目中选择读已提交作为隔离级别
因为在可重复读隔离级别下,存在间隙锁,导致出现死锁的几率比读已提交大的多!
10.【了解/掌握】分库分表如何实现
- 分库分表的概念
- 将庞大的数据进行拆分
- 水平拆分:根据表的数据逻辑关系,将同一表中的数据按照某种条件,拆分到多台数据库服务器上,也叫做横向拆分。例如:一张1000万的大表,按照一模一样的结构,拆分成4个250万的小表,分别保存到4个数据库中。
- 垂直拆分:根据业务的维度,将不同的表切分到不同的数据库之上,也叫做纵向拆分。例如:所有的订单都保存到订单库中,所有的用户都保存到用户库中,同类型的表保存在同一库,不同的表分散在不同的库中。
- 垂直拆分还是水平拆分:
当我们单个库太大时,我们先要看一下是因为表太多还是数据量太大,如果是表太多,则应该将部分表进行迁移(可以按业务区分),这就是所谓的垂直切分。
如果是数据量太大,则需要将表拆成更多的小表,来减少单表的数据量,这就是所谓的水平拆分。
- 垂直分库:
垂直分库针对的是一个系统中的不同业务进行拆分,比如用户一个库,商品一个库,订单一个库。一个购物网站对外提供服务时,会同时对用户、商品、订单表进行操作。没拆分之前, 全部都是落到单一的库上的,这会让数据库的单库处理能力成为瓶颈。如果垂直分库后还是将用户、商品、 订单放到同一个服务器上,只是分到了不同的库,这样虽然会减少单库的压力,但是随着用户量增大,这会让整个数据库的处理能力成为瓶颈,还有单个服务器的磁盘空间、内存也会受非常大的影响。 所以我们要将其拆分到多个服务器上,这样上面的问题都解决了,以后也不会面对单机资源问题。
- 垂直分表:
也就是“大表拆小表”,基于列字段进行的。一般是表中的字段较多,将不常用的, 数据较大, 长度较长(比如 text 类型字段)的拆分到“扩展表“。一般是针对那种几百列的大表,也避免查询时,数据量太大造成的“跨页”问题。
- 水平分表:
和垂直分表有一点类似,不过垂直分表是基于列的,而水平分表是基于全表的。水平拆分可以大大 减少单表数据量,提 升查询效率。
- 水平分库分表:
将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破 IO、连接数、硬件资源等的瓶颈
10. 分库分表策略
- HASH 取模
假设有用户表 user,将其分成 3 个表 user0,user1,user2.路由规则是对 3 取模,当 uid=1 时, 对应到的是 user1,uid=2 时,对应的是 user2。
- 范围分片
从 1-10000 一个表,10001-20000 一个表。
- 地理位置分片
华南区一个表,华北一个表。
- 时间分片
11. 为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?
面试官心态模拟:
分库分表对于面试者来说,只是知道有分库分表这么一回事,还是说明白分库分表是根据什么来分的 呢?分库分表到底是怎么分的呢?我希望听到的是:是什么导致你的系统需要分库分表,以及这项技术解决了什么事情、怎么解决的。
分表:比如你单表都几千万数据了,你确定你能扛住么?绝对不行,单表数据量太大,会极大影响你的 sql 执行的性能,到了后面你的 sql 可能就跑的很慢了。一般来说,单表到几百万的时候,性能就会相对差一些了,你就得分表了。分表是啥意思?就是把一个表的数据放到多个表中,然后查询的时候你就查一个表。比如按照用户 id 来分表,将一个用户的数据就放在一个表中。然后操作的时候你对一个用户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内,比如每个表就固定在 200 万以内。
分库:分库是啥意思?就是你一个库一般我们经验而言,最多支撑到并发 2000,一定要扩容了,而且一个健康的单库并发值你最好保持在每秒 1000 左右,不要太大。那么你可以将一个库的数据拆分到多个库中,访问的时候就访问一个库好了。
面试技巧:
从系统业务场景出发,分析业务产生的问题(并发高、数据量大问题),然后引入分库分表的概念,从而这些问题得到了解决。(电商项目采用的是分库)
话术:
在电商项目中,像秒杀、下单等业务会同时对用户、商品、订单表进行操作。没拆分之前, 全部都是落到单一的库上的,这会让数据库的单库处理能力成为瓶颈。如果垂直分库后还是将用户、商品、 订单放到同一个服务器上,只是分到了不同的库,这样虽然会减少单库的压力,但是随着用户量增大,这会让整个数据库的处理能力成为瓶颈,还有单个服务器的磁盘空间、内存也会受非常大的影响。 所以我们要将其拆分到多个服务器上,这样上面的问题都解决了,以后也不会面对单机资源问题。
12.锁的分类
13.MySQL数据库的优化
1.sql语句优化
对查询进行优化, 应尽量避免全表扫描, 首先应考虑在 where 及 order by 涉及的列上建立索引。 应尽量避免在 where 子句中对字段进行 null 值判断, 创建表时 NULL 是默认值
2.索引优化
索引太多的话虽然能够提高查询效率,但是 insert 和 update 时可能会重建索引,插入和更新效率将会降低,一般一个表的索引数最好不要超过 6 个
考虑在 where 及 order by 和 join 涉及的列上建立索引,能够对查询进行优化,从而避免全表扫描。
3.临时表优化
避免频繁创建和删除临时表,以减少系统表资源的消耗。
Linux
1.【掌握】Linux 常见的命令有哪些?
top:实时查看系统进程状态(包括内存、CPU信息)
ps -ef:显示系统中所有的完整进程信息(信息完整)
ps-ef|grep 进程名:查看进程 ,包含grep进程
kill -9 进程 id :杀死进程
cd 文件夹名 : 进入文件夹
ls : 列出文件夹下面的文件
ls-l:显示不隐藏的文件和文件夹详细信息
ls | wc -l : 统计当前文件夹文件个数
cat 文件名 :查看文件内容
view 文件名 :查看日志文件
vi 文件名/vim 文件名 :编辑文件
touch 文件名:创建文件
//文本编辑
vi/vim
//进入文件阅读(命令模式)
vim 文件名
//按i 进入编辑模式
//按ESC 退出编辑模式
//按: 进入末行模式 (保存)
//按2下ESC 可以返回命令模式
//q退出 不保存
//q!强行退出 不保存
//wq 保存并退出
//wq! 强行保存并退出
tail -f 文件名 :动态查看日志文件 ,最后10行
tail -f -n 1500 文件名 : 查看基于当前往回找 1500 行日志
chmod:修改权限
u:用户角色
g:用户组角色
o:其他用户角色
a:所有角色
+:添加权限
-:去除权限
=:设定权限
chmod -R u=rwx,g=rx,o=r a.txt
chmod -R a=rwx a.txt
//去掉其他用户的所有权限
chmod -R o-rwx a.txt
Nginx
1.【了解】Nginx 的作用
负载均衡功能:多在高并发情况下需要使用。其原理就是将数据流量分摊到多个服务器执行,减轻每台服务器的压力, 多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量(SpringCloud的ribbon也具有负载功能)
反向代理功能:先通过域名解析器,再找到域名对应的服务器
http服务器功能
2. 什么是 Nginx?在什么情况下使用 Nginx?什么是反向代理、什么是正向代理?
- 什么是 Nginx:
Nginx 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。其特点是占有内存少,并发能力强,事实上 Nginx 的并发能力确实在同类型的网页服务器中表现较好。
- Nginx 使用场景:
- 多个项目要用相同域名对外提供服务。
2. 业务量增长,单台服务器压力过大,不足以支撑业务,需要多台服务器同时工作分担压力。
3. 高并发情况下,静态文件较多的时候。
- 反向代理:
反向代理就是可以根据客户端访问的url地址判断将请求转发给哪个具体的内网服务器处理,这个过程请求来源是具体的,但是请求具体由哪台服务器处理并不明确,那么Nginx在其中扮演的就是一个反向代理角色。
反向代理,客户端对代理是无感知的,客户端不需要任何配置就可以访问,客户端将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址。
反向代理,”它代理的是服务端”,
反向代理的作用:保证内网的安全,通常将反向代理作为公网访问地址,Web服务器是内网
老师版本:
反向代理就是将接收到的请求,转发给内部网络上的其他服务器,并接收处理结果,将处理结果返回给请求端。即接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet 上请求连接的客户端。
- 正向代理:
正向代理服务器位于客户端和服务器之间,为了从服务器获取数据,客户端要向代理服务器发送一个请求,并指定目标服务器,然后代理服务器将目标服务器返回的数据转交给客户端。这里客户端需要要进行一些正向代理的设置的。
正向代理,”它代理的是客户端”。
正向代理最大的特点是客户端非常明确要访问的服务器地址;服务器只清楚请求来自哪个代理服务器,而不清楚来自哪个具体的客户端;正向代理模式屏蔽或者隐藏了真实客户端信息。
通常都被简称为代理,就是在用户无法正常访问外部资源. 通过代理的方式,让用户绕过防火墙,从 而连接到目标网络或者服务。正向代理的工作原理就像一个跳板。类似 VPN 原理
3.Nginx 有几种负载均衡模式
轮询(默认) :每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器 down 掉,能自动剔除。
weight:指定轮询几率,weight 和访问比率成正比,用于后端服务器性能不均的情况。
ip_hash:每个请求按访问 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器,可以解决 session 的问 题。
fair(第三方):按后端服务器的响应时间来分配请求,响应时间短的优先分配。
url_hash(第三方):通过请求 url 进行 hash,再通过 hash 值选择后端 server。 **
Redis
1. 在项目中缓存是如何使用的?
——回答顺序可以是:我在这个项目中有哪几个业务场景使用到了缓存,使用缓存的目的是什么,具体是如何完成的
举几个常见的业务场景:
- 使用验证码快速登入
- 注册的时候使用验证码校验注册真实性
- 定期清理垃圾图片
- 文章
- 活动
- 首页中的广告(将mysql中的广告数据读取到redis中)
- 购物车 (用户添加商品到购物车时,直接将要加入购物车的商品详情存入到Redis,每次查看购物车的时候直接从Redis中获取)
那么使用缓存主要有 2 大用处:
- 高性能:
一个请求过来了,经过了 MySQL,查询到结果耗时 600ms,但是这个结果接下来几个小时可能都不会变,或者说变化了不用立即反馈给用户,那么此时我们可以将此次查询出来的结果放在缓存中,一个 key 对应一个value,我们直接从缓存中查询的话,耗时 2ms,性能提升了 200 倍。
就是说对于一些需要复杂操作耗时查出来的结果,且确定后面不怎么变化,但是有很多读请求,那么直接将查询出来的结果放在缓存中,后面直接读缓存就好。
- 高并发:
假如系统中没有引入缓存,系统在高峰期一秒钟过来请求有 1 万,MySQL 是会挂掉的,如果引入缓存,缓存单机支撑的并发量轻松一秒达到几万十几万,单机承载并发量是 MySQL 的几十倍。缓存是走内存的,内存天然就支撑高并发。
2. 缓存如果使用不当会造成什么后果?(使用缓存有遇到什么问题吗?) | 开发过程中遇到过什么问题吗?可以大体说一说存在的问题,你是怎么解决的
通常我们使用缓存,大体上有这三类的问题:
- 缓存与数据库双写不一致(缓存与数据库数据一致性问题)
- 缓存雪崩、缓存穿透 、缓存击穿
- 缓存并发竞争
缓存与数据库双写不一致
一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。
串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
解决方案:
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。更新的时候,先更新数据库,然后再删除缓存。
为什么是删除缓存,而不是更新缓存?
原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。
另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这样,但是对于比较复杂的缓存数据计算的场景,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。但是问题在于,这个缓存到底会不会被频繁访问到?举个例子,一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次、100 次;但是这个缓存在 1 分钟内只被读取了 1 次,有大量的冷数据。实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。用到缓存才去算缓存。
其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。像 mybatis,hibernate,都有懒加载思想。
最初级的缓存不一致问题及解决方案
问题:先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
解决思路:先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。
比较复杂的数据不一致问题分析
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了…
为什么上亿流量高并发场景下,缓存会出现这个问题?
只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。其实如果说你的并发量很低的话,特别是读并发很低,每天访问量就 1 万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。但是问题是,如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况。
解决方案如下:
读请求和写请求串行化,串到一个内存队列里去。
更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。
一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。
这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。
缓存雪崩、缓存穿透 、缓存击穿
3. Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
Redis 主要用以下几种数据类型、以及对应不同的应用场景:
l String:key-value类型,做简单的KV 缓存。应用:常规计数:微博数、粉丝数、验证码(过期时间)。
l Hash:类似map的一种结构,将结构化的数据(无嵌套的对象)缓存到Redis里,读写缓存时可操作hash里某个字段。应用:存储对象:用户信息、商品信息。
l List:有序列表,链表型结构,类似于LinkedList。应用:粉丝列表、消息列表。
l Set:无序集合,自动去重。可以基于set玩交集、并集、差集的操作。应用:共同关注(交集)、共同粉丝(交集)、垃圾图片(差集)。
l Sorted Set:排序的set,去重但可以排序。写进去的时候给权重参数score,根据score进行排序。应用:直播礼物排行榜。
4. Redis 是 nosql 数据库,是否适合存储大数据?
Redis 是 nosql 数据库, 但是 Redis 是 key-value 形式的 nosql 数据库, 数据是存储到内存中的, 适合于快速存取一般作为缓存使用。所以不适合于大数据的存储。 并且 Redis 是单线程的如果某个操作进行 大数据的存储的话其他的进程都处于等待状态, 这样就降低了性能。所以在 Redis 中不适合于大数据的存储。 如果是类似商品评论这样的价值不高的大批量数据,我们的做法是采用 mongodb。
5. Redis 是个单线程程序,速度为什么会这么快
1、 完全基于内存,大部分请求都是纯粹的内存操作,非常快速,数据存储在内存中,例如 HashMap,优势 是查找和操作的时间复杂度 都是 O(1)。
2、 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的。
3、 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或多线程导致的切换而消耗 cpu, 不用去 考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
4、 使用异步非阻塞 IO(重点说)
同步/异步:是否主动读写数据;阻塞/非阻塞:是否需要等待。
同步:执行一个操作之后,等待结果,然后才继续执行后续的操作。
异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。
阻塞:进程给 CPU 传达一个任务之后,一直等待 CPU 处理完成,然后才执行后面的操作。
非阻塞:进程给 CPU 传达任务后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。 这样的过程 其实也叫轮询。
5、 底层自己构建了 VM 机制,因为一般系统调用系统函数的话会浪费一定的时间去移动和请求
6. 项目中是如何使用缓存的
1、 使用 Spring Data Redis。
2、 使用 SpringCache 整合 Redis, 相关的注解
@Cacheable:使用这个注解的方法在执行后会缓存其返回结果。
@CacheEvict:使用这个注解的方法在其执行前或执行后移除 Spring Cache 中的某些元素。
7. Redis 的过期策略都有哪些?
l 目标:在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降
l 定时删除:创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作。节约内存,但cpu压力大(拿时间换换空间)
l 惰性删除:数据到达过期时间,不做处理。等下次访问该数据时,判断数据是否过期,未过期返回,已过期删除。Cpu效率高,但内存占用严重(拿空间换时间)
l 定期删除:周期性轮询redis库中的时效性数据,采用随机抽取的策略。比较折衷。
内存淘汰机制 :最常用的,当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key
l 运用:Redis中设置时间过期功能适用于token、短信验证码
8. Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?
- 持久化的方式
RDB:将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据。
AOF:将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程。
- 持久化机制的具体底层原理
RDB
bgsave的过程分成两个过程,第一个是服务端拿到指令直接告诉客户端开始执行了;另外一个过程是一个子进程在完成后台的保存操作,创建RDB文件把它存起来,操作完以后回一个消息。
AOF
AOF写数据最经常执行的策略
everysec(每秒):每秒将缓冲区中的指令同步到AOF文件中,在系统突然宕机的情况下丢失1秒内的数据 数据准确性较高,性能较高,建议使用,也是默认配置
- 不同的持久化机制的优缺点
RDB优点:
- RDB是一个紧凑压缩的二进制文件,存储效率较高
- RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
- RDB恢复数据的速度要比AOF快很多
- 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复。
RDB缺点
- 无法做到实时持久化,具有较大的可能性丢失数据
- 基于fork创建子进程,内存产生额外消耗,要牺牲掉一些性能
- 存储数据量较大,效率较低,基于快照思想,每次读写都是全部数据,当数据量巨大时,效率非常低
- 大数据量下的IO性能较低
- Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象
AOF优点
- 不写全数据,仅记录部分数据
- 降低区分数据是否改变的难度,改记录数据为记录操作过程
- 对所有操作均进行记录,AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 fsync 操作, 最多丢失 1 秒钟的数据
AOF缺点
- 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大
- AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;
- AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。
综合比对
- RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊
- 如不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用AOF
- 如能承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用RDB
- 灾难恢复选用RDB
- 双保险策略,同时开启 RDB和 AOF,重启后,Redis优先使用 AOF 来恢复数据,降低丢失数据的量;使用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
Spring
1.对spring的理解。IOC、AOP的实现原理是什么?
spring框架作为一款优秀的开源的javaee轻量级框架,他的出现主要是针对后端三 层架构的开发模式,提供了全套的解决方案,将后端的开发模型和语法变得更加的规范和简单,同时spring框架基于ioc和aop的核心,将控制反转和面向切面编程融入到了后端开发的各个地方,让程序变得更加的解耦和可维护。
并且,spring他本身除了自己的提供了JDBCTemplate 和springmvc等持久层和web层的解决方案之外,他还可以集成其他优秀的框架,并简化其他框架的配置方式,让其他框架比单独使用的时候更加强大。
ioc的中文叫做控制反转,他的意思是,将原本创建对象的权限由我们转交给spring。具体的操作是,不在java类中通过new对象这种硬编码的操作方式创建对象,而是让spring在执行过程中动态的读取配置文件中配置的对象信息,从而创建对象,然后将对象放置到spring容器中,当想要获得对象的时候,从spring容器中获取。
2. Spring 事务的种类和各自的区别
Spring 支持编程式事务管理和声明式事务管理两种方式:
- 编程式事务管理使用 TransactionTemplate 或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理,Spring 推荐使用 TransactionTemplate。
- 声明式事务管理建立在 AOP 之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或 者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就 是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional 注解的方式),便可以将事务规则应用到业务逻辑中。
- 显然声明式事务管理要优于编程式事务管理,这正是 Spring 倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的 POJO 对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,声明式事务的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
SpringMVC
1. SpringMVC 的执行流程?
【了解】Dubbo
1.Dubbo 是什么?
Dubbo 是一个分布式、高性能、透明化的 RPC 服务框架,提供服务自动注册、自动发现等高效服务治理方案, 可以和 Spring 框架无缝集成
Dubbo作为一个RPC框架,其最核心的功能就是要实现跨网络的远程调用。
2.Dubbo 的主要应用场景?
- 透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何 API 侵入。
- 软负载均衡及容错机制,可在内网替代 F5 等硬件负载均衡器,降低成本,减少单点。
服务自动注册与发现,不再需要写死服务提供方地址,注册中心 基于接口名查询服务提供者的 IP地址,并且能够平滑添加或删除服务提供者。
3. Dubbo 有哪些注册中心?
Multicast 注册中心: Multicast 注册中心不需要任何中心节点,只 要广播地址,就能进行服务注册和发现。基于网络中组播传输实现;
- Zookeeper 注册中心: 基于分布式协调系统 Zookeeper 实现,采用 Zookeeper 的 watch 机制实现数据变更;
- Redis 注册中心: 基于 Redis 实现,采用 key/Map 存储,往 key 存储 服务名和类型,Map 中 key 存储服务 URL,value 存储服务过期时间。基于 Redis 的发布/订阅模式通知数据变更;
-
4.Dubbo 集群提供了哪些负载均衡策略?
Random:按权重随机,默认值。按权重设置随机概率。
RoundRobin: 按权重轮询。
LeastActive: 最少活跃调用数,相同活跃数的随机。
ConsistentHash:一 致性Hash,相同参数的请求总是发到同一提供者。5.Dubbo 与 Spring 的关系?
Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展进行加载。
6.Dubbo 和 Spring Cloud 的关系?
Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。 而 Spring Cloud 诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了 Spirng、Spirng Boot 的优势之上,两个框架在开始目标就不一致,Dubbo 定位服务治理、Spirng Cloud 是一个生态。
7.Dubbo 和 Spring Cloud 的区别?
最大的区别:Dubbo 底层是使用 Netty 这样的 NIO 框架,是基于 TCP 协议传输的,配合以 Hession 序列化完成 RPC 通信。
而 SpringCloud 是基于 Http 协议+Rest 接口调用远程过程的通信, 相对来说,Http 请求会有更大的报文,占的带宽也会更多。但是 REST 相比 RPC 更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖。8.Dubbo 中 zookeeper 做注册中心,如果注册中心集群都挂掉,发布者和订阅者之间还能通信么?
可以,因为dubbo服务消费者在第一次调用时,会将服务提供方地址接口等数据缓存到本地,每次调用时,按照本地存储的地址进行调用。
注册中心对等集群,任意一台宕机后,将会切换到另一台;注册中心全部宕机后,服务的提供者和消费者仍能通过本地缓存通讯。
挂掉是不要紧的,但前提是你没有增加新的服务,如果你要调用新的服务,则是不能办到的【了解】Zookeeper
在业务场景中,我主要是把Zookeeper作为Dubbo的注册中心使用,用它来记录服务提供方和服务消费方
1.Zookeeper都有哪些应用场景?
分布式协调
这个其实是 zookeeper 很经典的一个用法,简单来说,就好比,你 A 系统发送个请求到 mq,然后B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上对某个节点的值注册个监听器,一旦 B系统处理完了就修改 zookeeper 那个节点的值,A 系统立马就可以收到通知,完美解决。(如图示 1 所示)
- 分布式锁
举个栗子。对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也尝试去创建那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等着,等第一个机器执行完
了自己再执行。(如图示 2 所示)
- 元数据/配置信息管理
zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用zookeeper 来做一些元数据、配置信息的管理,包括 dubbo 注册中心不也支持 zookeeper 么?(如图示 3 所示)
【掌握】SpringBoot
1. 对SpringBoot的了解
SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率
SpringBoot功能
1) 自动配置
SpringBoot 可以自动配置Spring 的各种组件,并不依赖代码生成和 XML 配置文件
2) 起步依赖
起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。
3) 辅助功能
提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等。(如把服务器内嵌到SpringBoot)
SpringBoot 是微服务框架的起点,他简化了配置过程、部署过程、监控过程。它默认配置了很多框架的使用方 式,就像 maven 整合了所有的 jar 包,SpringBoot 整合了很多的框架,同时将其他技术同Spring 结合起来
2.SpringBoot 和 Spring 有什么区别?
SpringBoot 是在 Spring4.0 版本的基础上进行升级的框架。他们的区别在于 Spring 具备的 SpringBoot 都具备。SpringBoot 比 Spring 新增了如下特性:
- 独立运行 Spring 项目: SpringBoot 可以以 jar 包形式独立运行,运行一个 SpringBoot 项目只需要通过 java -jar xx.jar 来运行。
- 内嵌 servlet 容器: SpringBoot 可以选择内嵌 Tomcat、jetty、undertow,这样我们无须以 war 包形式部署项目。
- 提供 starter 简化 maven 配置: Springboot 提供了一系列的 start pom 来简化 Maven 的依赖加载,例如,当你使用了 Spring- bootstarter-web,会自动加入依赖包
- 自动装配 Spring: SpringBoot 会根据在类路径中的 jar 包,类、为 jar 包里面的类自动配置 Bean,这样会极大地减少我们要使用的配置。当然,SpringBoot 只考虑大多数的开发场景,并不是所有的场景,若在实际开 发中 我们需要配置 Bean,而 SpringBoot 没有提供支持,则可以自定义自动配置。
- 准生产的应用监控 :SpringBoot 提供基于 http ssh telnet 对运行时的项目进行监控。
无代码生产和 xml 配置 :SpringBoot 不是借助与代码生成来实现的,而是通过条件注解来实现的,这是 Spring4.x 提供的新特 性
3. SpringBoot 的启动流程,用到了哪些组件?
SpringBoot 启动流程主要分为三步:
1) SpringApplication 初始化模块。通过 main 方法入口,调用 run 方法后会创建一个SpringApplication 实 例,并在 SpringApplication 构造器中调用 initialize 方法初始操作。包括创建应用的监听器(SpringApplicationRunListeners)、配置是否为 web 环境、配置资源 resource 等其他对象的操作。
2) 在完成第一部分各个模块实例的创建后,接着会执行 SpringApplication 的 run 方法,该方法主要执行启动应用监听器模块、环境配置模块、Banner 模块以及应用上下文模块的初始化工作,当初始化工作完成之后 SpringBoot 启动流程完成。
3) SpringBoot 自动化配置模块,这个模块是 SpringBoot 的准备工作部分(第一部分实例创建)以及初始化配置工作(第二部分初始化)过程都需要使用的模块,该模块的自动配置文件收集器会收集配置 文件中的配置工厂 类,在 ApplicationContext 环境中创建组件所需的 bean 实例。并存在容器中。
SpringBoot 四大核心组件Auto-configuration:针对 Spring 应用程序和常见的应用功能,SpringBoot 能自动提供相关配置, 从而实现简化配置甚至零配置。
- Starter:简化 jar 包的引用,解决 jar 版本冲突问题。
- Cli:SpringBoot 可选特性,主要针对 Groovy 语言使用。
- Actuator:是 SpringBoot 的程序监控器,可监控 Spring 应用程序上下文中的 Bean、查看自动配置决策、 Controller 映射、线程活动、应用程序健康状况等
4. 说一说 SpringBoot 都有什么配置文件?有哪几个重要的注解?
SpringBoot 使用一个全局的配置文件,配置文件名是固定的 :
application.properties
application.yml
重要注解
@SpringBootApplication 项目的应用接口,一般作用为引导类上
@SpringBootConfiguration:其实就是对原 @Configuration 注解的简单封装。
@ComponentScan :用来开启组件扫描,可以自动扫描指定包路径下的@Component 注解类,并将 bean 实例注册到 context 中。
@EnableAutoConfiguration:用来提供自动装配,是这三个注解中最重要的一个注解。她是SpringBoot 新添加的注解,提供了强大的自动依赖功能,是 SpringBoot 这么方便的大功臣
【掌握】SpringCloud
1. SOA与SpringCloud微服务架构的区别
SOA:解决上述分布式服务器凌乱问题
SOA: (Service- Oriented Architecture,面向服务的架构):是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和契约联系起来。
ESB: (Enterparise Servce Bus):企业服务总线,服务中介。主要是提供了一个服务于服务之间的交互。ESB包含的功能如:负载均衡,流量控制,加密处理,服务的监控,异常处理,监控告急等等。
微服务架构:
SpringCloud是微服务架构的具体技术实现,它是整合其他主流框架的集合,只能使用Spring Boot 风格进行开发。
模块化
微服务是系统架构上的一种设计风格,它是将一个原本独立的系统拆分成多个可独立运行的小型服务,每个服务都对应自己的数据库,服务之间一般通过 HTTP 的 RESTfuL API 进行通信协作。
Spring Cloud是微服务架构的技术实现
Spring Cloud 是一系列框架的有序集合。
Spring Cloud 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来。
小结:SpringCloud是微服务的具体技术实现,它是整合其他主流框架的集合,只能使用Spring Boot 风格进行开发。
●微服务架构是在SOA上做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。
●微服务架构= 80%的SOA服务架构思想+ 100%的组件化架构思想+ 80%的领域建模思想
特点:
●服务实现组件化:开发者可以自由选择开发技术。也不需要协调其他团队
●服务之间交互一 般使用REST API
●去中心化:每个微服务有自己私有的数据库持久化业务数据
●自动化部署:把应用拆分成为一个一个独立的单个服务,方便自动化部署、测试、运维
2.SpringCloud 用到了哪些组件?
- Eureka:实现服务的注册与发现
- Ribbon:负载均衡
- Hystrix:隔离、降级、限流、熔断:预防雪崩
熔断:预防雪崩
当服务C中出现的错误太多,熔断会直接把服务C的所有服务全部断绝掉。当服务C恢复正常后,可以重新使用。
- Config:远程配置服务组件。用于配置管理。
- Gateway:网关就是系统的入口,封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、缓存、负载均衡、流量管控(限流)、路由转发等
在目前的网关解决方案里,有Nginx+ Lua、Netflix Zuul (19年上半年停止使用)、Spring Cloud Gateway等等
3.为什么要使用微服务网关?
如果不使用网关,那么
- 在微服务架构中,不同的微服务可以有不同的网络地址,各个微服务之间通过互相调用完成用户请求,客户端可能通过调用N个微服务的接口完成一个用户请求。
- 存在的问题:
1.客户端多次请求不同的微服务,增加客户端的复杂性---》客户端直接请求网关
2.认证复杂,每个服务都要进行认证----》在网关进行所有用户的身份认证
3.http请求不同服务次数增加,性能不高
网关就是系统的入口,封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、缓存、负载均衡、流量管控(限流)、路由转发等
4. 微服务的服务间调用的组件有用过吗?
在我们项目上,经常使用服务间的调用组件
有 SpringCloud 提供了 feign,配置该应用为 feign 客户端,编写接口使用注解声明调用那个应用服 务 id,编 写方法使用注解@getMapping 指定远端服务访问的访问路径。用@Autowired 引用定义的 feign 接口,直接使用。
- 还有一种是 Spring 的 RestTemplate我们用这个获取 token 。调用RestTemplate的exchange方法,指定路径、请求方式、httpentity 封装请求头,请求体携带参数、再用指定类型接受返回结果
docker
1.什么是docker?
docker是一种容器技术,解决软件跨环境迁移问题。
- 镜像(Image):(软件的安装包)Docker 镜像(Image),就相当于是 一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包 含了完整的一套 Ubuntu16.04 最小系统的 root 文件系 统。
- 容器(Container):(软件安装成功好的体现)镜像(Image)和容器(Contain er)的关系,就像是面向对象程序设计中的类和对象一 样,镜像是静态的定义,容器是镜像运行时的实体。容 器可以被创建、启动、停止、删除、暂停等。
仓库(Repository):仓库可看成一个代码控制中心, 用来保存镜像。
【了解/掌握】SpringSecurity
编写类实现
UserDetailsService
,重写loadUserByUsername
,最后返回的是UserDetails对象
Rabbit项目相关
1. 怎么实现不同服务器之间的 session 共享?
通过数据库 MySQL 共享 session
采用一台专门的 m y s q l 服务器来存储所有的 session 信息。用户访问随机的 web 服务器时, 会去这个专门的数据库服 务器 check 一下 session 的情况,以达到 session 同步的目的。缺点就是:依懒性太强,MySQL 服务器无法工作,影 响整个系统;
- 通过 cookie 共享 session
把用户访问页面产生的 session 放到 cookie 里面,就是以 cookie 为中转站。当访问服务器 A 时, 登录成功之后将产 生的 session 信息存放在 cookie 中;当访问请求分配到服务器 B 时,服务器B 先判断服务器有没有这个session,如果没有,在去看看客户端的 cookie 里面有没有这个session,如果 cookie 里面有,就把 cookie 里面 的 sessoin 同 步到 web 服务器 B,这样就可以实现 session 的同步了。
缺点:cookie 的安全性不高,容易伪造、客户端禁止使用 cookie 等都可能造成无法共享 session
- 通过服务器之间的数据同步 session
使用一台作为用户的登录服务器,当用户登录成功之后,会将 session 写到当前服务 器上,我们通过脚本或者守护 进程将 session 同步到其他服务器上,这时当用户跳转 到其他服务器,session 一致,也就不用再次登录。
缺陷:速度慢,同步 session 有延迟性,可能导致跳转服务器之后,session 未同步。 而且单向同步时,登录服务器宕机,整个系统都不能正常运行。
- 通过 memcache 同步 session
memcache 可以做分布式,如果没有这功能,他也不能用来做 session 同步。他可以把 web 服务 器中的内存组合起 来,成为一个”内存池”,不管是哪个服务器产生的 sessoin 都可以放到这个 “内存池”中,其他的都可以使用。
优点:以这种方式来同步 session,不会加大数据库的负担,并且安全性比用 cookie 大大的提高, 把 session 放到内存里面,比从文件中读取要快很多。
缺点:memcache 把内存分成很多种规格的存储块,有块就有大小,这种方式也就决定了, memcache 不能完全 利用内存,会产生内存碎片,如果存储块不足,还会产生内存溢出。
- 通过 Redis 共享 session
Redis 与 memcache 一样,都是将数据放在内存中。区别的是 Redis 会周期性的把更新 的数据写 入磁盘或者把修改操 作写入追加的记录文件,并且在此基础上实现了 master-slave(主从)同步。
根据实际开发应用,一般选择使用 memcache 或 Redis
密码为什么要用Bcrypt算法进行加密
24、【掌握】RabbitMQ
3、【了解】JVM原理
25、【掌握】项目相关
26、【了解】算法
18、【掌握】SSM
循环的方式
- 三种循环的使用场景
- 如果需要操作索引 ,使用普通for循环
- 如果在遍历的过程中需要删除元素,请使用迭代器
- 如果仅仅想遍历 ,那么使用增强for
注意点:
迭代器:(hasNext()和next()可以实现遍历集合,不需要直接操作索引)
实现Iterable接口的类才可以使用迭代器和增强for(查API表格后,只有单列Collection可以使用,双列Map不可以)
其他循环:forEach循环
- stream流运算进行循环获取数据,封装到集合