笔记
Docker
Docker怎么实现容器之间交互
- network来创建一个桥接网络,在docker run的时候将容器指定到新创建的桥接网络中,这样同一桥接网络中的容器就可以通过互相访问。
深拷贝与浅拷贝的区别
浅拷贝
- 对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝
深拷贝
- 对基本类型进行值传递,对引用数据类型传递一个新的对象,并且复制内容
java如何实现深拷贝
- 实现深拷贝的前提必须实现Cloneable接口并且其引用也要实现Cloneable接口
成员变量和局部变量需不需要初始化
- 成员变量不用初始化,因为成员变量在堆内存中会分配默认值
- 局部变量需要初始化,因为局部变量是在方法中,而jvm执行过程中运行时的数据都在栈帧中,局部变量的内存空间在编译的期间就要完成内存的分配,所以局部变量在创建的时候必须告诉初始值进行初始化已确定内存的大小
抽象类和接口的区别
抽象类
- 抽象类使用abstract修饰
- 不能被实例化,也就是说不能new
- 含有抽象方法的类一定是抽象类,但是抽象类不一定有抽象方法
接口
- 接口使用interface修饰
- 接口不能被实例化
- 一个类只能继承一个类,但是可以实现多个接口
- 接口中的方法都是抽象方法
相同之处
- 都是不能被实例化
- 一个类继承了抽象类或者实现接口,需要实现这个抽象类或者接口所有的抽象方法
- 可以将抽象类或者接口类型当做引用类型去传递
什么是内存泄漏,什么是内存溢出
内存泄漏
- 就是当我们创建对象或者变量之后,并且该对象和变量在程序中都不在使用了,但在程序中有没有回收这些已经没用的变量和对象,这就事内存泄漏
- 解决办法
- 少使用一些静态的常量,因为常量在jvm中都在常量池中,常量池在jvm中在方法区(也就是永久代),不会被回收
内存溢出
- 就是当我们在去申请一块内存空间的时候,jvm已经没有多余的内存给我们分配,也就是说内存不够用了,这就事内存溢出
- 解决办法
- 修改jvm的启动参数(-Xmx -Xms 一般将这两值设置为相同,以避免在每次GC之后还要调整堆的大小,建议堆的值设置为当前可用内存的百分之八十)
- 根据日志分析问题,然后排查
java中static和final有什么区别
static
- static分为静态变量,静态块,静态方法 所以说静态代码块 = 静态变量 > (因为静态方法只有在调用的时候才会被初始化)
- 如果一个类还没有加载的啥时候
- 首先会加载父类的静态变量和静态代码块,谁先加载依据代码的位置来定,依次是从上往下
- 然后紧接着加载该类的静态变量和静态代码块变量的初始化
- 紧接着加载父类的实例变量的初始化
- 之后执行父类的构造函数
- 再去加载该类的实例变量初始化
- 执行该类的构造函数
final
- 如果final用在类上,那么该类不能被继承
- 如果final用在方法上,那么这个方法不能被重写
- 如果final用在成员变量或者局部变量上,不能被更改
java 中序列化与反序列化的区别
序列化
- java序列化是指java在保存为二进制字节码的过程
反序列化
- 反序列化是指java把二进制字节码重新转换为java对象的过程
序列化的底层是怎么实现的
- 序列化的底层是通过IO流来实现的
switch中是可以使用string么?
- 在Java5以前,switch(expr)中,exper只能是byte,short,char,int类型(或其包装类)的常量表达式。
- 从Java5开始,java中引入了枚举类型,即enum类型。
- 从Java7开始,exper还可以是String类型。
- 但是long在所有版本中都是不可以的。
- jdk1.7并没有新的指令来处理switch string,而是通过调用switch中string. hashCode,将string转换为int从而进行判断。hashcode返回的是int型
java中基本数据类型占用的字节数
- int 4个字节
- byte 1个字节
- short 2个字节
- long 8个字节
- char 2个字节 可以存储一个汉字 在utf-8中是三个字节 在GBK中是两个字节
- float 4个字节
- dubbo 8个字节
- boolean 1个字节
mysql中的字节类型 分别占用多少字节
tinyint ,占用1个字节,他的数的范围是(-127~128)
smallint,占用2个字节,他的可存储的范围是在(-32768~32767)
int ,占用4个字节,这里其实是和java中的数据类型互通的
bigint,占用8个字节,范围更大
float,占用4个字节
double,占用8个字节
资源控制模型
- RBAC模型 资源所有者能访问哪些资源
分布式事务处理方案
TCC
- TCC 的全称是: Try 、 Confirm 、 Cancel 。
- Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。
- Confirm 阶段:这个阶段说的是在各个服务中执行实际的操作
- Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)因为这个事务回滚实际上是严重依赖于你自己写代码来回滚和补偿了,会造成补偿代码巨大,非常之恶心。
seata 阿里的分布式事务中间件
ConcurrentHashMap
volatitle 修饰
volatile关键字对于基本类型的修改可以在随后对多个线程的读保持一致,但是对于引用类型如数组,实体bean,仅仅保证引用的可见性,但并不保证引用内容的可见性。。
源码中 内部类 Node实现了Entity数组volatitle修饰了val的值还有next数组 node
transient volatile Node<K,V>[] table; private transient volatile Node<K,V>[] nextTable;
在1.8中ConcurrentHashMap的get操作全程不需要加锁,这也是它比其他并发集合比如hashtable、用Collections.synchronizedMap()包装的hashmap;安全效率高的原因之一。
get操作全程不需要加锁是因为Node的成员val是用volatile修饰的和数组用volatile修饰没有关系。
数组用volatile修饰主要是保证在数组扩容的时候保证可见性。
threadlocal
- 使用线程池时 线程不会被回收 threadlocal变量不会失效 所以要在使用完成之后 remove掉 不然回导致内存泄漏
- threadlocal里面有个threadlocalMap 他是模仿hashMap 采用的是 hash+数据的结构
jvm
- jvm的启动参数
- -Xmn 设置新生代的大小
- -Xms 设置堆的初始值 也是最小值
- -Xmx 设置堆的最大值
- jvm 的堆是所有线程共享的一块内存区域 所有对象和数组都是在堆上进行内存分配 为了进行高效的垃圾回收 虚拟机把堆内存分为了 新生代 老年代 和永久代(1.8中没有永久代)
GC
为什么会出现GC
- 所谓GC,也就是垃圾收集,Java语言的最大特点就是JVM屏蔽了底层的细节,对于内存的管理,可以完全交由JVM去管理。也就是说即使我们不了解GC的原理,一样可以正常且有效的使用这门语言。那么,为什么还需要花时间去了解GC呢?当代码编写到一定的程度,我们需要开始关注性能,而充分了解了底层GC的原理,才能更好的去做性能调优相关的工作
对象是“死”,是“活”
- 要执行GC,需要先判断对象是否还被引用,用通俗的话讲,就是需要判断对象是“死”,还是“活”,那么如何判断呢?判断对象状态的算法有哪些呢?
引用计数算法
- 顾名思义,通过判断对象是否还被引用,来判定是否可以进行回收。当对象被引用则将引用次数+1,当不再引用则-1,引用次数为0,则代表没有对象引用。当对象没有被引用时,则可能被GC回收。这种算法的优点是:高效。缺点是:循环引用的情况无法有效回收(所谓循环引用:也就是A引用B,同时B也引用A),所以当前主流判断对象“死”和“活”并不采用这种算法
可达性分析算法
- 从GC Root的根开始向下搜索,当遇到对象与Root之间无关联时,则标记为可以被回收。那么什么样的对象可以作为Root呢?大家可以思考一下
常见的GC算法
标记—清除算法
- 所谓标记-清除算法,就是进行遍历,筛选出可以回收的对象,做标记。这种算法的主要缺点是,标记清除之后,会产生很多碎片化的空间
复制算法
- 如何避免出现很多碎片化的空间呢,于是出现了复制算法,将内存空间一分为二,每次只使用一半的空间,当使用完毕,则将还存活的对象复制到另外一半上面。这种算法的缺点是,每次内存的利用率只有一半。当然,如果对象的存活率很高,复制效率也会相对比较低
标记—整理算法
- 考虑到对象存活率高,复制算法效率会比较低,可以采用标记-整理算法,将不再引用的对象进行移动移动整理到某个边界位置,然后将边界外的对象进行统一清理。
分代收集算法
- 当前主流的JVM大都采用分代收集算法,也就是说根据不同的区域,使用不同的算法。例如:新生代,存活率比较低,可以采用复制算法;老年代,存活率很高,可以采用标记-清除或者标记-整理算法
redis
缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题
缓存雪崩
- 缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间
(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。 -
解决办法:
大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。
缓存穿透
- 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
-
解决办法;
最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。 - 另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。
- 5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。如果这些数据是一些32bit大小的数据该如何解决?如果是64bit的呢?
- 对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。
- Bitmap: 典型的就是哈希表
- 缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。
- 对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。
缓存穿透与缓存击穿的区别
- 指一个key非常热点,大并发集中对这个key进行访问,当这个key在失效的瞬间,仍然持续的大并发访问就穿破缓存,转而直接请求数据库。
解决方案;在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。
缓存预热
- 缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
- 直接写个缓存刷新页面,上线时手工操作下
- 数据量不大,可以在项目启动的时候自动进行加载
- 定时刷新缓存
缓存更新
- 除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
- 定时去清理过期的缓存
- 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡
Memcache与Redis的区别
- 存储方式 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis有部份存在硬盘上,redis可以持久化其数据
- 数据支持类型 memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 ,提供list,set,zset,hash等数据结构的存储
- 使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
- value 值大小不同:Redis 最大可以达到 512M;memcache 只有 1mb。
- redis的速度比memcached快很多
- Redis支持数据的备份,即master-slave模式的数据备份。
单线程的redis为什么快
- 纯内存操作
- 单线程操作避免了上下文切换
- 采用了非阻塞IO
redis的数据类型以及每种数据类型的使用场景
String
- 这个其实没啥好说的,最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存
hash
- 这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果
list
- 使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产者和消费者的场景。LIST可以很好的完成排队,先进先出的原则
set
- 因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。
另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能
redis的过期策略以及内存淘汰机制
- redis采用的是定期删除+惰性删除策略。
- 定期删除 ,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
持久化机制
- AOF AOF记录服务器操作所有的写操作的命令 并在服务器启动时通过重新执行这些命令来还原数据 AOF 文件全部以redis协议的格式来保存 新命令会追加到文件末尾 redis还可以在后台对AOF文件进行重写 可以设置时间来定期来更新
- 2:RDB 持久化可以在指定时间间隔内生成数据集的时间和快照
- 3:Redis还可以同时使用AOF持久化和RDB持久化 在这种情况下 当redis重启时 他会优先使用AOF来还原数据
- 4:也可以关闭持久化功能
redis为啥设呢么是单线程的
- 因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽
- 非阻塞IO的优点
- 绝大部分请求是纯粹的内存操作(非常快速)2)采用单线程,避免了不必要的上下文切换和竞争条件
- 支持丰富数据类型,支持string,list,set,sorted set,hash
- 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除如何解决redis的并发竞争key问题
redis常见的性能问题和解决方案
- Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
- 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
- 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内
- 尽量避免在压力很大的主库上增加从库
- 主从复制不要用图状结构,用单向链表结构更为稳定
redis实现分布式锁
- Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁
- 将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作
- 死锁
- 通过Redis中expire()给锁设定最大持有时间,如果超过,则Redis来帮我们释放锁
- 使用 setnx key “当前系统时间+锁持有的时间”和getset key “当前系统时间+锁持有的时间”组合的命令就可以实现
mysql
Mysql两种引擎MyISAM和InnoDB的特点
- MyISAM
- 引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:
- 不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁;
- 不支持事务;
- 不支持外键;
- 不支持崩溃后的安全恢复;
- 在表有读取查询的同时,支持往表中插入新纪录;
- 支持BLOB和TEXT的前500个字符索引,支持全文索引;
- 支持延迟更新索引,极大提升写入性能;
- 对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用;
- 引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:
- InnoDB
- 在MySQL 5.5后成为默认索引,它的特点是:
- 支持行锁,采用MVCC来支持高并发;
- 支持事务;
- 支持外键;
- 支持崩溃后的安全恢复;
- 在MySQL 5.5后成为默认索引,它的特点是:
锁机制
- 共享锁(读锁) 其他事物可以读 但不能写
- 排他锁(写锁) 其他事物不能读取,也不能写
乐观锁和悲观锁
- 乐观锁
- 顾名思义 就是很乐观 每次操作数据的时候都会认为别人不会修改数据 乐观锁适用于多读应用类型 这样可以提高吞吐量 可以使用版本号机制
- 乐观锁不能解决脏读问题
- java实现乐观锁 冲突检测 数据更新
- CAS 乐观锁机制 多线程操作同一组数据时 只有一个线程能够对数据进行操作 而且其他的线程都失败
- 悲观锁
- 共享锁 读锁
- 排它锁
- 就是很悲观 每次操作都会认为别人会修改数据 每次拿数据都会上锁 传统的关系型数据库很多都运用到这种锁机制 比如行锁 表锁但是加锁会让数据库产生额外的开销 还会增加死锁的机会 降低系统性能
- java实现悲观锁 for update
- mysql支持分布式事务
- 应用程序 资源管理器 事务管理器
- 分布式事务采用两阶段提交
- 第一阶段 所有的事务节点开始准备 告诉事务管理器read
- 第二阶段 事务管理器告诉每个节点是commit还是rollbac如果一个节点失败 就需要全局节点全部rollback 保证事务的原子性
- 分布式事务的sql语法
sql的执行顺序
- from
- on
- join
- where
- group by(开始使用select中的别名,后面的语句中都可以使用) 这里可以使用表中普通字段的别名,不能使用聚合函数的别名
- avg,sum....
- having
- select
- distinct
- order by
SQL Select语句完整的执行顺序
- from子句组装来自不同数据源的数据;
- where子句基于指定的条件对记录行进行筛选;
- group by子句将数据划分为多个分组;
- 使用聚集函数进行计算;
- 使用having子句筛选分组;
- 计算所有的表达式;
- 使用order by对结果集进行排序。
必须掌握的30种sql优化
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select id from t where num=0
应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num=10 or num=20 可以这样查询:select id from t where num=10 union all select id from t where num=20
下面的查询也将导致全表扫描:select id from t where name like '%abc%'若要提高效率,可以考虑全文检索。
-
in 和 not in 也要慎用,否则会导致全表扫描,如:select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:select id from t where num between 1 and 3
-
如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:select id from t where num=@num可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num/2=100应改为:select id from t where num=100*2
-
应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'--name以abc开头的id
select id from t where datediff(day,createdate,'2005-11-30')=0--'2005-11-30'生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
-
不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...
并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
-
很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
避免频繁创建和删除临时表,以减少系统表资源的消耗。- 临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
尽量避免大事务操作,提高系统并发能力。
java的基本数据结构
线性表
- 最常用的最简单的数据结构,它是n个数据元素的有限序列
栈
- 先进先出
队列
- 一端添加元素,另一端取出元素。入栈出栈。使用场景,因为队列先进先出的特点,在多线程阻塞队列管理中特别适用
链表
- 物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个节点,一个是存储元素的数据域(存储空间),另外一个是指向下一个节点的指针域。根据指针的指向,链表形成不同的结构,例如单链表、双链表,循环链表
- 链表优点:不需要初始化容量,可以任意加减元素,添加或删除元素只需要改变前后两个元素节点的指针域指向地址即可,所以添加,删除很快
- 缺点:含有大量指针域,占用空间大,需要查找元素需要遍历链表来查找,比较耗时
- 使用场景:数据量小,需要频繁增加,删除的操作。
- Vector是线程安全的,因为Vector好多方法是sychornized关键字修饰的
- Map接口:也是接口,但是没有继承collection接口,描述了从不重复的键到值的映射,键值对,
- HashMap散列表,基于哈希表实现,就是键值对的映射关系,元素顺序不固定,适合对元素插入删除定位等操作。
- TreeMap:红黑树实现,TreeMap的元素保持着某种固定的顺序,更加适合对元素的遍历。
- Iterator接口:所有实现Iterator接口方法能以迭代方式逐个访问集合中的各元素,并可以从Collection中除去适当的元素。
- Hsahtable类:继承Map接口,实现key-value的哈希表。HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
IO
同步与异步
- 同步
- 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回
- 异步
- 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果
- 同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果
阻塞和非阻塞
- 阻塞
- 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续
- 非阻塞
- 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情
BIO
- Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO
- New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
- NIO的核心组件
- Channel(通道)
- Buffer(缓冲区)
- Selector(选择器)
- NIO读数据和写数据的方式
- 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
- 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据
AIO
- Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
Zookeeper
是什么
- ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务
zookeper文件系统
- Zookeeper提供一个多层级的节点命名空间(节点称为znode)。与文件系统不同的是,这些节点都可以设置关联的数据,而文件系统中只有文件节点可以存放数据而目录节点不行。Zookeeper为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M
四种类型的znode
- 持久化目录节点
- 持久化顺序编号目录节点
- 临时目录节点
- 临时顺序编号目录节点
zookeper的通知机制
- client端会对某个znode建立一个watcher事件,当该znode发生变化时,这些client会收到zk的通知,然后client可以根据znode变化来做出业务上的改变等。
zookeper可以做什么
- 命名服务
- 配置管理
- 集群管理
- 分布式锁
- 队列管理
zk的命名服务
- 命名服务是指通过指定的名字来获取资源或者服务的地址,利用zk创建一个全局的路径,即是唯一的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等
摘自
https://blog.csdn.net/t4i2b10X4c22nF6A/article/details/104322605
dubbo
dubbo分层
- Dubbo 是一款高性能 Java RPC 架构。它实现了面向接口代理的 RPC 调用,服务注册和发现,负载均衡,容错,扩展性等等功能。
- 业务层
- RPC层
- Remoting层
dubbo的工作流程
- 提供者(Provider)启动时,会向注册中心写入自己的元数据信息(调用方式)。
- 消费者(Consumer)启动时,也会在注册中心写入自己的元数据信息,并且订阅服务提供者,路由和配置元数据的信息。
- 服务治理中心(duubo-admin)启动时,会同时订阅所有消费者,提供者,路由和配置元数据的信息。
- 当提供者离开或者新提供者加入时,注册中心发现变化会通知消费者和服务治理中心。
注册中心的工作原理
ZooKeeper 是负责协调服务式应用的。
它通过树形文件存储的 ZNode 在 /dubbo/Service 目录下面建立了四个目录,分别是:
- Providers 目录下面,存放服务提供者 URL 和元数据
- Consumers 目录下面,存放消费者的 URL 和元数据
- Routers 目录下面,存放消费者的路由策略。
- Configurators 目录下面,存放多个用于服务提供者动态配置 URL 元数据信息。
dubbo的负载均衡
- Random LoadBalance,随机,按照权重设置随机概率做负载均衡。
- RoundRobinLoadBalance,轮询,按照公约后的权重设置轮询比例。
- LeastActiveLoadBalance,按照活跃数调用,活跃度差的被调用的次数多。活跃度相同的 Invoker 进行随机调用。
- ConsistentHashLoadBalance,一致性 Hash,相同参数的请求总是发到同一个提供者。
摘自
Spring
spring中bean的作用域
- singleton 唯一bean实例,Spring中的bean默认都是单例的
- prototype 每次请求都会创建一个新的bean实例
- request 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效
- session 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效
- global-session 全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话
JDK动态代理和静态代理
静态代理
- 静态代理的功能就是扩展原来的对象方法的功能 通过代理对象保证真实对象功能的前提下去扩展一些新的功能或者说去增加一些繁琐且必须要做的事情
- 但是当我们的业务接口过多的情况下,我们的代理对象也会变得更加的多 而且代理对象和真实对象都必须要实现同一个干活的接口 这也是缺点
动态代理
-
动态代理就是解决了静态代理的缺点 我们不需要让他们去实现同一个接口 动态代理的核心就是
实现InvocationHandler 接口类重写invoke方法Object invoke(Object proxy, Method method, Object[] args) :在代理实例上处理方法调用并返回结果。和public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) 这两个核心方法。
-
还有一点核心的就是使用ava.lang.reflect.Proxy类,他提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类
/** * Ojbect proxy:表示需要代理的对象 * Method method:表示要操作的方法 * Object[] args:method方法所需要传入的参数(可能没有可能为null *// Object invoke(Object proxy, Method method, Object[] args)。
-
jdk动态代理的实现
class ServiceProxy implements InvocationHandler { private Object target=null;//保存真实业务对象 /** * 返回动态代理类的对象,这样用户才可以利用代理类对象去操作真实对象 * @param obj 包含有真实业务实现的对象 * @return 返回代理对象 */ public Object getProxy(Object obj) { //保存真实业务对象 this.target=obj; return proxy.newProxyInstance (obj.getClass().getClassLoader(),obj.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args)throws Throwable { //通过反射调用真实业务对象的业务方法,并且返回 Object result=method.invoke(target, args); return result; } } //如何去使用这个代理对象 public static void main(String[] args) { UserService service=(UserService) new ServiceProxy().getProxy( new UserServiceImpl()); service.saveUser(); }
CGLib
CGLib采用底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类的调用方法,并顺势织入横切逻辑.它运行期间生成的代理对象是目标类的扩展子类.所以无法通知final、private的方法,因为它们不能被覆写.是针对类实现代理,主要是为指定的类生成一个子类,覆盖其中方法.
在spring中默认情况下使用JDK动态代理实现AOP,如果proxy-target-class设置为true或者使用了优化策略那么会使用CGLIB来创建动态代理.Spring AOP在这两种方式的实现上基本一样.以JDK代理为例,会使用JdkDynamicAopProxy来创建代理,在invoke()方法首先需要织入到当前类的增强器封装到拦截器链中,然后递归的调用这些拦截器完成功能的织入.最终返回代理对象.-
事务的隔离级别
- 读未提交
- 读已提交
- 可重复读
- 可串行化
-
事务的传播行为
事务行为 说明 PROPAGATION_REQUIRED 支持当前事务,假设当前没有事务。就新建一个事务 PROPAGATION_SUPPORTS 支持当前事务,假设当前没有事务,就以非事务方式运行 PROPAGATION_MANDATORY 支持当前事务,假设当前没有事务,就抛出异常 PROPAGATION_REQUIRES_NEW 支持当前事务,假设当前没有事务,就抛出异常 PROPAGATION_NOT_SUPPORTED 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起 PROPAGATION_NEVER 以非事务方式运行,假设当前存在事务,则抛出异常 PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。