1.常用的单例模式
概述:像Windows系统的任务管理器一样,你无论打开多少次,始终显示的一个窗口。如何保证一个类只有一个实例并且这个实例易于被访问呢,定义一个统一的全局变量可以确保对象随时可以被访问,但不能防止创建多个对象。一个最好的办法就是让类自身负责创建和保存它的唯一实例,并保证不创建其他实例,它还提供了一个访问该实例的方法,这就是单例模式的动机。。一点都不鸡冻-_-||。。
单例模式的定义:
单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
Singleton Pattern:Ensure a class has only one instance, and provide a global point of access to it.
单例模式是一种对象创建型模式。单例模式有三个要点:一是某个类只有一个实;二是它必须自行创建这个实例;三是它必须像整个系统提供这个实例。
单例模式的实现:
懒汉
public class LazySingleton
{
private static LazySingleton instance = null;
//程序运行时创建一个静态只读的辅助对象
private static readonly object syncRoot = new object();
private LazySingleton() { }
public static LazySingleton GetInstance()
{
//第一重判断,先判断实例是否存在,不存在再加锁处理
if(instance == null)
{
//加锁的程序在某一时刻只允许一个线程访问
lock (syncRoot)
{
//第二重判断
if(instance == null)
{
instance = new LazySingleton();//创建单例实例
}
}
}
return instance;
}
}
饿汉
public class EagerSingleton
{
private static EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton GetInstance()
{
return instance;
}
}
2.Java常用的结构有哪些
数组 栈 队列 链表 树 堆 图 散列表
3.Spring AOP是什么意思,通俗讲解
切面编程,就是在你项目原有的功能基础上,通过AOP去添加新的功能,这些功能是建立在原有功能的基础上的,而且原有的功能并不知道你已经添加了新的功能。比如说,你去ATM取钱,取钱是一个功能,取完钱后向你的手机发送一条取钱信息,这就是新加的功能。
AOP就是在某一个类或方法执行前后打个标记,声明在执行到这里之前要先执行什么,执行完这里之后要接着执行什么。插入了新的执行方法。
当我们需要在许多类中添加相同逻辑(或记录等其他)代码的时候,一般我们编程会在每一个类中都写上这些代码。当需要修改的时候,我们又必须找出这些类来删除这些逻辑代码。这里,你觉不觉得有什么问题。这好像关系到复用的问题,那么可以用聚合或继承来完成?那么再继续下去,我们需要这些逻辑代码指定到类中的某个方法前面执行,或者在方法后面执行,又或者我想指定在类的某一个位置去执行它,那么这就不是复用的问题了,而是要修改类了,变成动态的了。那么就出现了aop这个概念-面向切面编程。
4.JDK,JRE,JVM三者关系
JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。Java Runtime Environment(JRE)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。JVM是Java Virtual Machine(Java虚拟机)的缩写,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。JDK 包含JRE,JRE包含JVM。
5.一般什么原因会造成死锁,怎么解决?
操作系统中有若干进程并发执行,它们不断申请、使用、释放系统资源,虽然系统的进
程协调、通信机构会对它们进行控制,但也可能出现若干进程都相互等待对方释放资源才能
继续运行,否则就阻塞的情况。此时,若不借助外界因素,谁也不能释放资源,谁也不能解
除阻塞状态。根据这样的情况,操作系统中的死锁被定义为系统中两个或者多个进程无限期
地等待永远不会发生的条件,系统处于停滞状态,这就是死锁。
产生死锁的原因主要是:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则
就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之
一不满足,就不会发生死锁。
死锁的解除与预防:
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和
解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确
定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态
的情况下占用资源。因此,对资源的分配要给予合理的规划。
6.HashMap,LinkedHashMap,TreeMap区别
Map 主要用于存储键(key)值(value)对,根据键得到值,因此键不允许键重复,但允许值重复。
HashMap
是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力。
LinkedHashMapLinkedHashMap也是一个HashMap,但是内部维持了一个双向链表,可以保持顺序
TreeMap 可以用于排序
linkedMap在于存储数据你想保持进入的顺序与被取出的顺序一致的话,优先考虑LinkedMap,hashMap键只能允许为一条为空,value可以允许为多条为空,键唯一,但值可以多个。
经本人测试linkedMap键和值都不可以为空
7.多线程两种实现方法
多线程有两种实现方法,分别是继承Thread类与实现Runnable接口
同步的实现方法有两种,分别是synchronized,wait与notify
先看一下java线程运行时各个阶段的运行状态
java实现多线程有两种方法
1、继承Thread类
2、实现Runnable接口
这两种方法的共同点:
不论用哪种方法,都必须用Thread(如果是Thead子类就用它本身)产生线程,然后再调用start()方法。
两种方法的不同点:
1、继承Thread类有一个缺点就是单继承,而实现Runnable接口则弥补了它的缺点,可以实现多继承
2、继承Thread类必须如果产生Runnable实例对象,就必须产生多个Runnable实例对象,然后再用Thread产生多个线程;而实现Runnable接口,只需要建立一个实现这个类的实例,然后用这一个实例对象产生多个线程。即实现了资源的共享性
基于以上两点所以建议用第二种方法
8.什么是消息队列?
一、什么是消息队列?
消息队列不知道大家看到这个词的时候,会不会觉得它是一个比较高端的技术,反正我是觉得它好像是挺牛逼的。
消息队列,一般我们会简称它为MQ(Message Queue),嗯,就是很直白的简写。
我们先不管消息(Message)这个词,来看看队列(Queue)。这一看,队列大家应该都熟悉吧。
队列是一种先进先出的数据结构。
消息队列可以简单理解为:把要传输的数据放在队列中。
9.为什么站点使用https加密之后还能看到相关数据
这是因为:https(ssl)加密是发生在应用层与传输层之间,所以在传输层看到的数据才是经过加密的,而我们捕捉到的http post,是应用层的数据,此时还没有经过加密。这些明文信息,其实就是你的本地数据。
加密数据只有客户端和服务器端才能得到明文,客户端到服务端的通信过程是安全的。
可能有些读者会对此表示担忧了:这样的话密码不是会被本地恶意软件截获么?只能说的确存在这样的可能。不过在银行电商等安全防护程度较高的网站,除了https加密外,还有安全控件加密,用户必须下载安全控件后才能输入密码,以支付宝为例:通过下图可以发现就算在本地也无法查看账号信息。
针对这个问题,Gworg CA建议:行政、金融、电商等需要高安全防护的站点在实行https加密外,还应该部署Gworg客户端证书,以强身份认证的方式替代不安全的账号密码认证,确保登录过程中的安全无忧,同时还能弱口令漏洞,最大程度保护用户信息安全。
此外,针对使用安全控件辅助登录的站点,Gworg还建议使用代码签名证书,以保证安全控件在下载传输过程中没有被篡改及破坏。从而保证代码的完整性,保护用户不会被病毒、恶意代码和间谍软件所侵害,真正做到全面安全防护。
10.JAVA中,启动线程的三种方法
1、继承Thread类, 重写run方法,在main函数中,调用start方法。代码如下:
//播放音乐线程类
class MusicThread extends Thread {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("播放音乐" + i);
}
}
}
//主线程类
public class ThreadDemo3 {
public static void main(String[] args) {
//启动播放音乐的线程
MusicThread thread1 = new MusicThread();
thread1.start();
//主线程
for (int i = 0; i < 50; i++) {
System.out.println("打游戏" + i);
}
}
}
2、实现Runnable接口,重写run方法,在main函数中,调用start方法。代码如下:
//1):定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类.
class MusicImplements implements Runnable{
//2):在A类中覆盖Runnable接口中的run方法.
public void run() {
//3):在run方法中编写需要执行的操作
for(int i = 0; i < 50; i ++){
System.out.println("播放音乐"+i);
}
}
}
public class ImplementsRunnableDemo {
public static void main(String[] args) {
for(int j = 0; j < 50; j ++){
System.out.println("运行游戏"+j);
if(j == 10){
//4):在main方法(线程)中,创建线程对象,并启动线程
MusicImplements mi = new MusicImplements();
Thread t = new Thread(mi);
t.start();
}
}
}
}
3、实现Callable接口,重写call()方法,在main函数中调用start()方法。
import java.util.concurrent.Callable;
public class ThreadImplementsCallable implements Callable<Integer> {
private int i;
@Override
public Integer call() throws Exception {
for(; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
}
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) {
Callable<Integer> callable = new ThreadImplementsCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
for(i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 5) {
new Thread(futureTask).start();
new Thread(futureTask).start();
}
}
try {
System.out.println("futureTask ruturn: " + futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
11.乐观锁与悲观锁原理及实现
一、乐观锁
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
核心SQL代码:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
CAS操作方式:即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
二、悲观锁
总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。
12.xss攻击,csrf攻击,SQL注入
1、xss攻击,跨站脚本攻击值得是攻击者在网页中嵌入恶意脚本程序,当用户打开浏览器,脚本程序便开始在客户端的浏览器上执行,一盗取客户端cookie等信息,利用系统对用户的信任。
防御措施:将html转移处理htmlspe
2、csrf攻击是跨站请求伪造,攻击者盗用了你的身份,以你的名义向第三方网站发送恶意请求,利用系统对页面浏览器的信任。
防御措施:①将cookie设置为HttpOnly。如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性。
//设置cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly")
②设置token
③通过refer识别:根据http协议,在http投中有一个字符安叫refer,它记录了该http请求的来源地址
3、SQL注入,通过预处理可以解决
13.如何避免内存泄漏、溢出的几种常用方法
尽早释放无用对象的引用。
好的办法是使用临时变量的时候,让引用变量在退出活动域后自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。
程序进行字符串处理时,尽量避免使用String,而应使用StringBuffer。
因为每一个String对象都会独立占用内存一块区域
尽量少用静态变量。
因为静态变量是全局的,GC不会回收。
避免集中创建对象尤其是大对象,如果可以的话尽量使用流操作。
JVM会突然需要大量内存,这时会触发GC优化系统内存环境;
尽量运用对象池技术以提高系统性能。
生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。
不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。
可以适当的使用hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃。
优化配置。
- 设置-Xms、-Xmx相等;
- 设置NewSize、MaxNewSize相等;
- 设置Heap size, PermGen space;
14.哪些情况下索引会失效?
对查询进行优化,尽量避免全表扫描,避免导致索引失效
- 在where子句中进行null值判断的话会导致引擎放弃索引而产生全表扫描
SELECT id FROM table WHERE num is null
在建立数据库的时候因尽量为字段设置默认值,如int类型可以使用0,varchar类型使用 ''
当你在指定类型大小如int(11)时,其实空间就已经固定了,即时存的是null也是这个大小
- 避免在where子句中使用
!= ,< >
这样的符号,否则会导致引擎放弃索引而产生全表扫描
SELECT id FROM table WHERE num != 0
- 避免在where子句中使用
or
来连接条件,因为如果俩个字段中有一个没有索引的话,引擎会放弃索引而产生全表扫描
SELECT id FROM table WHERE num = 0 OR num = 1
- 可以考虑使用
between
,但是只能是连续的数值
SELECT id FROM table WHERE num BETWEEN 0 AND 1
- 避免在where子句中使用
int
关于网上说的MySQL在使用int
不走索引的问题,严谨的来说的话分为俩种情况- 走索引的
SELECT id FROM table WHERE num IN (1)
-
- 不走索引的
SELECT id FROM table WHERE num IN (1,2)
其实在in里面,如果只有一个值的话是等价于num = 1的
- 避免在where子句中
=
的左边使用表达式操作或者函数操作- 表达式
SELECT id FROM table WHERE num / 2 = 1
-
- 函数操作
SELECT id FROM table WHERE SUBSTRING(name,1,2) = 'wise'
- 避免在where子句中使用
like
模糊查询
SELECT id FROM table WHERE name LIKE 'wise'
- 在使用联合索引是要注意最左原则,例如如果当前联合索引是
index(a,b,c)
,那么如果where子句中有a
就会用到联合索引,但是如果只用到b,c
就会失去索引效果
SELECT id FROM table WHERE name LIKE 'wise'
15.数据库分区、分表、分库、分片
一、分区的概念
数据分区是一种物理数据库的设计技术,它的目的是为了在特定的SQL操作中减少数据读写的总量以缩减响应时间。
分区并不是生成新的数据表,而是将表的数据均衡分摊到不同的硬盘,系统或是不同服务器存储介子中,实际上还是一张表。另外,分区可以做到将表的数据均衡到不同的地方,提高数据检索的效率,降低数据库的频繁IO压力值,分区的优点如下:
1、相对于单个文件系统或是硬盘,分区可以存储更多的数据;
2、数据管理比较方便,比如要清理或废弃某年的数据,就可以直接删除该日期的分区数据即可;
3、精准定位分区查询数据,不需要全表扫描查询,大大提高数据检索效率;
4、可跨多个分区磁盘查询,来提高查询的吞吐量;
5、在涉及聚合函数查询时,可以很容易进行数据的合并;
这种形式分区是对表的行进行分区,通过这样的方式不同分组里面的物理列分割的数据集得以组合,从而进行个体分割(单分区)或集体分割(1个或多个分区)。所有在表中定义的列在每个数据集中都能找到,所以表的特性依然得以保持。
举个简单例子:一个包含十年发票记录的表可以被分区为十个不同的分区,每个分区包含的是其中一年的记录。(朋奕注:这里具体使用的分区方式我们后面再说,可以先说一点,一定要通过某个属性列来分割,譬如这里使用的列就是年份)
这种分区方式一般来说是通过对表的垂直划分来减少目标表的宽度,使某些特定的列被划分到特定的分区,每个分区都包含了其中的列所对应的行。
举个简单例子:一个包含了大text 和B L OB 列的表,这些text和BLOB列又不经常被访问,这时候就要把这些不经常使用的text和BLOB了划分到另一个分区,在保证它们数据相关性的同时还能提高访问速度。
在数据库供应商开始在他们的数据库引擎中建立分区(主要是水平分区)时,DBA和建模者必须设计好表的物理分区结构,不要保存冗余的数据(不同表中同时都包含父表中的数据)或相互联结成一个逻辑父对象(通常是视图)。这种做法会使水平分区的大部分功能失效,有时候也会对垂直分区产生影响。
就是把一张表的数据分成N个区块,在逻辑上看最终只是一张表,但底层是由N个物理区块组成的
就是把一张表按一定的规则分解成N个具有独立存储空间的实体表。系统读写时需要根据定义好的规则得到对应的字表明,然后操作它。
一旦分表,一个库中的表会越来越多
将整个数据库比作图书馆,一张表就是一本书。当要在一本书中查找某项内容时,如果不分章节,查找的效率将会下降。而同理,在数据库中就是分区。
-
单个表数据量越大,读写锁,插入操作重新建立索引效率越低。
-
单个库数据量太大(一个数据库数据量到1T-2T就是极限)
-
单个数据库服务器压力过大
-
读写速度遇到瓶颈(并发量几百)
-
一张表的查询速度已经慢到影响使用的时候。
-
sql经过优化
-
数据量大
-
表中的数据是分段的
-
对数据的操作往往只涉及一部分数据,而不是所有的数据
主要可以提升查询效率
mysql5 开始支持分区功能
-
CREATE TABLE sales (
-
id INT AUTO_INCREMENT,
-
amount DOUBLE NOT NULL,
-
order_day DATETIME NOT NULL,
-
PRIMARY KEY(id, order_day)
-
) ENGINE=Innodb
-
PARTITION BY RANGE(YEAR(order_day)) (
-
PARTITION p_2010 VALUES LESS THAN (2010),
-
PARTITION p_2011 VALUES LESS THAN (2011),
-
PARTITION p_2012 VALUES LESS THAN (2012),
-
PARTITION p_catchall VALUES LESS THAN MAXVALUE);
-
一张表的查询速度已经慢到影响使用的时候。
-
sql经过优化
-
数据量大
-
当频繁插入或者联合查询时,速度变慢
分表后,单表的并发能力提高了,磁盘I/O性能也提高了,写操作效率提高了
-
查询一次的时间短了
-
数据分布在不同的文件,磁盘I/O性能提高
-
读写锁影响的数据量变小
-
插入数据库需要重新建立索引的数据减少
需要业务系统配合迁移升级,工作量较大
-
分区和分表的目的都是减少数据库的负担,提高表的增删改查效率。
-
分区只是一张表中的数据的存储位置发生改变,分表是将一张表分成多张表。
-
当访问量大,且表数据比较大时,两种方式可以互相配合使用。
-
当访问量不大,但表数据比较多时,可以只进行分区。
-
Range(范围)
-
Hash(哈希)
-
按照时间拆分
-
Hash之后按照分表个数取模
-
在认证库中保存数据库配置,就是建立一个DB,这个DB单独保存user_id到DB的映射关系
-
单台DB的存储空间不够
-
随着查询量的增加单台数据库服务器已经没办法支撑
其主要目的是为突破单节点数据库服务器的 I/O 能力限制,解决数据库扩展性问题。
将系统中不存在关联关系或者需要join的表可以放在不同的数据库不同的服务器中。
按照业务垂直划分。比如:可以按照业务分为资金、会员、订单三个数据库。
需要解决的问题:跨数据库的事务、jion查询等问题。
例如,大部分的站点。数据都是和用户有关,那么可以根据用户,将数据按照用户水平拆分。
按照规则划分,一般水平分库是在垂直分库之后的。比如每天处理的订单数量是海量的,可以按照一定的规则水平划分。需要解决的问题:数据路由、组装。
对于时效性不高的数据,可以通过读写分离缓解数据库压力。需要解决的问题:在业务上区分哪些业务上是允许一定时间延迟的,以及数据同步问题。
垂直分库-->水平分库-->读写分离
-
事务的支持,分库分表,就变成了分布式事务
-
join时跨库,跨表的问题
-
分库分表,读写分离使用了分布式,分布式为了保证强一致性,必然带来延迟,导致性能降低,系统的复杂度变高。
对于不同的方式之间没有严格的界限,特点不同,侧重点不同。需要根据实际情况,结合每种方式的特点来进行处理。
选用第三方的数据库中间件(Atlas,Mycat,TDDL,DRDS),同时业务系统需要配合数据存储的升级。
单库单表是最常见的数据库设计,例如,有一张用户(user)表放在数据库db中,所有的用户都可以在db库中的user表中查到。
随着用户数量的增加,user表的数据量会越来越大,当数据量达到一定程度的时候对user表的查询会渐渐的变慢,从而影响整个DB的性能。如果使用mysql, 还有一个更严重的问题是,当需要添加一列的时候,mysql会锁表,期间所有的读写操作只能等待。
可以通过某种方式将user进行水平的切分,产生两个表结构完全一样的user_0000,user_0001等表,user_0000 + user_0001 + …的数据刚好是一份完整的数据。
随着数据量增加也许单台DB的存储空间不够,随着查询量的增加单台数据库服务器已经没办法支撑。这个时候可以再对数据库进行水平拆分。
总的来说,优先考虑分区。当分区不能满足需求时,开始考虑分表,合理的分表对效率的提升会优于分区。
16. Dubbo和SpringCloud的对比
来源(背景):
Dubbo,是阿里巴巴服务化治理的核心框架,并被广泛应用于阿里巴巴集团的各成员站点。
Spring Cloud,从命名我们就可以知道,它是Spring Source的产物,Spring社区的强大背书可以说是Java企业界最有影响力的组织了,除了Spring Source之外,还有Pivotal和Netfix是其强大的后盾与技术输出。其中Netflix开源的整套微服务架构套件是Spring Cloud的核心。
传输:
Dubbo由于是二进制的传输,占用带宽会更少;
Spring Cloud是http协议传输,带宽会比较多,同时使用http协议一般会使用JSON报文,消耗会更大。但是在国内95%的公司内,网络消耗不是什么太大问题,如果真的成了问题,通过压缩、二进制、高速缓存、分段降级等方法,很容易解。
开发难度:
Dubbo的开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决;
Spring Cloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级
后续改进:
Dubbo通过dubbofilter,很多东西没有,需要自己继承,如监控,如日志,如限流,如追踪
Spring Cloud自己带了很多监控、限流措施,但是功能可能和欧美习惯相同,国内需要进行适当改造,但更简单,就是ServletFilter而已,但是总归比dubbo多一些东西是好的;
注册中心:
Dubbo的注册中心可以选择zk,redis等多种;
Spring Cloud:的注册中心只能用eureka或者自研;
配置中心:
dubbo:如果我们使用配置中心、分布式跟踪这些内容都需要自己去集成,无形中增加了使用难度。
Spring Cloud:提供了微服务的一整套解决方案:服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等
核心部件的比较:
Dubbo:
Provider:暴露服务的提供方,可以通过 jar 或者容器的方式启动服务。
Consumer:调用远程服务的服务消费方。
Registry:服务注册中心和发现中心。
Monitor:统计服务和调用次数,调用时间监控中心。(Dubbo 的控制台页面中可以显示,目前只有一个简单版本。)
Container:服务运行的容器。
Spring Cloud:
Service Provider: 暴露服务的提供方。
Service Consumer:调用远程服务的服务消费方。
EureKa Server: 服务注册中心和服务发现中心。
架构的完整度:
Dubbo只是实现了服务治理;
Spring Cloud下面有17个子项目(可能还会新增)分别覆盖了微服务架构下的方方面面,服务治理只是其中的一个方面;
一定程度来说,Dubbo只是Spring Cloud Netflix中的一个子集。
服务依赖方式:
Dubbo:服务提供方与消费方通过接口的方式依赖,服务调用设计如下:
Interface 层:服务接口层,定义了服务对外提供的所有接口。
Molel 层:服务的 DTO 对象层。
Business层:业务实现层,实现 Interface 接口并且和 DB 交互。
因此需要为每个微服务定义各自的 Interface 接口,并通过持续集成发布到私有仓库中。调用方应用对微服务提供的抽象接口存在强依赖关系,开发、测试、集成环境都需要严格的管理版本依赖。
通过 maven 的 install & deploy 命令把 Interface 和 Model 层发布到仓库中,服务调用方只需要依赖 Interface 和 Model 层即可。在开发调试阶段只发布 Snapshot 版本,等到服务调试完成再发布;Release 版本,通过版本号来区分每次迭代的版本。通过 xml 配置方式即可接入 Dubbo,对程序无入侵。
总之:服务提供方与消费方通过接口的方式依赖,Dubbo 服务依赖略重,需要有完善的版本管理机制,但是程序入侵少。
Spring Cloud:
服务提供方和服务消费方通过 Json 方式交互,因此只需要定义好相关 Json 字段即可,消费方和提供方无接口依赖。通过注解方式来实现服务配置,对于程序有一定入侵。
通过 Json 交互,省略了版本管理的问题,但是具体字段含义需要统一管理,自身 Rest API 方式交互,为跨平台调用奠定了基础。
总体:
Dubbo:使用Dubbo构建的微服务架构就像组装电脑,各环节我们的选择自由度很高,但是最终结果很有可能因为一条内存质量不行就点不亮了,总是让人不怎么放心,但是如果你是一名高手,那这些都不是问题;
Spring Cloud就像品牌机,在Spring Source的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础有足够的了解。
优缺点(综上得到):
Dubbo
优点:
1.支持各种通信协议,而且消费方和服务方使用长链接方式交互,通信速度上略胜 ;
2.采用rpc方式,性能上比Spring Cloud的rpc更好;
3.dubbo的网络消耗小于springcloud
缺点:
1.如果我们使用配置中心、分布式跟踪这些内容都需要自己去集成;
2.开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决;
Spring Cloud:
优点:
1、产出于Spring大家族,Spring在企业级开发框架中来头很大,可以保证后续的更新、完善。
2、spring cloud社区活跃,教程丰富,遇到问题很容易找到解决方案;
3、spring cloud功能比dubbo更加完善;
5、spring cloud采用rest访问方式,rest的技术无关性使用效果更棒;
6、spring cloud轻轻松松几行代码就完成了熔断、均衡负责、服务中心的各种平台功能;
7、从公司招聘工程师方面,spring cloud更有优势,因为其技术更新更炫;
8、提供了微服务的一整套解决方案:服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等;作为一个微服务治理的大家伙,考虑的很全面,几乎服务治理的方方面面都考虑到了,方便开发开箱即用;
缺点:
1.如果对于系统的响应时间有严格要求,长链接更合适。
2.接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级
17.dubbo 熔断,限流,降级
1.1 名词解释
consumer表示服务调用方
provider标示服务提供方,dubbo里面一般就这么讲。
下面的A调用B服务,一般是泛指调用B服务里面的一个接口。
2.1 超时(timeout)
在接口调用过程中,consumer调用provider的时候,provider在响应的时候,有可能会慢,如果provider 10s响应,那么consumer也会至少10s才响应。如果这种情况频度很高,那么就会整体降低consumer端服务的性能。
这种响应时间慢的症状,就会像一层一层波浪一样,从底层系统一直涌到最上层,造成整个链路的超时。
所以,consumer不可能无限制地等待provider接口的返回,会设置一个时间阈值,如果超过了这个时间阈值,就不继续等待。
这个超时时间选取,一般看provider正常响应时间是多少,再追加一个buffer即可。
2.2 重试(retry)
超时时间的配置是为了保护服务,避免consumer服务因为provider 响应慢而也变得响应很慢,这样consumer可以尽量保持原有的性能。
但是也有可能provider只是偶尔抖动,那么超时后直接放弃,不做后续处理,就会导致当前请求错误,也会带来业务方面的损失。
那么,对于这种偶尔抖动,可以在超时后重试一下,重试如果正常返回了,那么这次请求就被挽救了,能够正常给前端返回数据,只不过比原来响应慢一点。
重试时的一些细化策略:
重试可以考虑切换一台机器来进行调用,因为原来机器可能由于临时负载高而性能下降,重试会更加剧其性能问题,而换一台机器,得到更快返回的概率也更大一些。
2.2.1 幂等(idempotent)
如果允许consumer重试,那么provider就要能够做到幂等。
即,同一个请求被consumer多次调用,对provider产生的影响(这里的影响一般是指某些写入相关的操作) 是一致的。
而且这个幂等应该是服务级别的,而不是某台机器层面的,重试调用任何一台机器,都应该做到幂等。
2.3 熔断(circuit break)
重试是为了应付偶尔抖动的情况,以求更多地挽回损失。
可是如果provider持续的响应时间超长呢?
如果provider是核心路径的服务,down掉基本就没法提供服务了,那我们也没话说。 如果是一个不那么重要的服务,却因为这个服务一直响应时间长导致consumer里面的核心服务也拖慢,那么就得不偿失了。
单纯超时也解决不了这种情况了,因为一般超时时间,都比平均响应时间长一些,现在所有的打到provider的请求都超时了,那么consumer请求provider的平均响应时间就等于超时时间了,负载也被拖下来了。
而重试则会加重这种问题,使consumer的可用性变得更差。
因此就出现了熔断的逻辑,也就是,如果检查出来频繁超时,就把consumer调用provider的请求,直接短路掉,不实际调用,而是直接返回一个mock的值。
等provider服务恢复稳定之后,重新调用。
2.3.1 简单的熔断处理逻辑
目前我们框架有通过注解使用的熔断器,大家可以参考应用在项目中。
2.4 限流(current limiting)
上面几个策略都是consumer针对provider出现各种情况而设计的。
而provider有时候也要防范来自consumer的流量突变问题。
这样一个场景,provider是一个核心服务,给N个consumer提供服务,突然某个consumer抽风,流量飙升,占用了provider大部分机器时间,导致其他可能更重要的consumer不能被正常服务。
所以,provider端,需要根据consumer的重要程度,以及平时的QPS大小,来给每个consumer设置一个流量上线,同一时间内只会给A consumer提供N个线程支持,超过限制则等待或者直接拒绝。
2.4.1 资源隔离
provider可以对consumer来的流量进行限流,防止provider被拖垮。
同样,consumer 也需要对调用provider的线程资源进行隔离。 这样可以确保调用某个provider逻辑不会耗光整个consumer的线程池资源。
2.4.2 服务降级
降级服务既可以代码自动判断,也可以人工根据突发情况切换。
2.4.2.1 consumer 端
consumer 如果发现某个provider出现异常情况,比如,经常超时(可能是熔断引起的降级),数据错误,这是,consumer可以采取一定的策略,降级provider的逻辑,基本的有直接返回固定的数据。
2.4.2.2 provider 端
当provider 发现流量激增的时候,为了保护自身的稳定性,也可能考虑降级服务。
比如,1,直接给consumer返回固定数据,2,需要实时写入数据库的,先缓存到队列里,异步写入数据库。
3 从宏观角度重新思考
宏观包括比A -> B 更复杂的长链路。
长链路就是 A -> B -> C -> D这样的调用环境。
而且一个服务也会多机部署,A 服务实际会存在 A1,A2,A3 …
微观合理的问题,宏观未必合理。
下面的一些讨论,主要想表达的观点是:如果系统复杂了,系统的容错配置要整体来看,整体把控,才能做到更有意义。
3.1 超时
如果A给B设置的超时时间,比B给C设置的超时时间短,那么肯定不合理把,A超时时间到了直接挂断,B对C支持太长超时时间没意义。
R表示服务consumer自身内部逻辑执行时间,TAB表示consumer A开始调用provider B到返回的时间 。
那么那么TAB > RB + TBC 才对。
3.2 重试
重试跟超时面临的问题差不多。
B服务一般100ms返回,所以A就给B设置了110ms的超时,而B设置了对C的一次重试,最终120ms正确返回了,但是A的超时时间比较紧,所以B对C的重试被白白浪费了。
A也可能对B进行重试,但是由于上一条我们讲到的,可能C确实性能不好,每次B重试一下就OK,但是A两次重试其实都无法正确的拿到结果。
N标示设置的重试次数
修正一下上面section的公式,TAB > RB+TBC * N。
虽然这个公式本身没什么问题,但是,如果站在长链路的视角来思考,我们需要整体规划每个服务的超时时间和重试次数,而不是仅仅公式成立即可。
比如下面情况:
A -> B -> C。
RB = 100ms,TBC=10ms
B是个核心服务,B的计算成本特别大,那么A就应该尽量给B长一点的超时时间,而尽量不要重试调用B,而B如果发现C超时了,B可以多调用几次C,因为重试C成本小,而重试B成本则很高。 so …
3.3 熔断
A -> B -> C,如果C出现问题了,那么B熔断了,则A就不用熔断了。
3.4 限流
B只允许A以QPS<=5的流量请求,而C却只允许B以QPS<=3的qps请求,那么B给A的设定就有点大,上游的设置依赖下游。
而且限流对QPS的配置,可能会随着服务加减机器而变化,最好是能在集群层面配置,自动根据集群大小调整。
3.5 服务降级
服务降级这个问题,如果从整体来操作,
1,一定是先降级优先级地的接口,两权相害取其轻
2,如果服务链路整体没有性能特别差的点,比如就是外部流量突然激增,那么就从外到内开始降级。
3如果某个服务能检测到自身负载上升,那么可以从这个服务自身做降级。
3.6 涟漪
A -> B -> C,如果C服务出现抖动,而B没有处理好这个抖动,造成B服务也出现了抖动,A调用B的时候,也会出现服务抖动的情况。
这个暂时的不可用状态就想波浪一样从底层传递到了上层。
所以,从整个体系的角度来看,每个服务一定要尽量控制住自己下游服务的抖动,不要让整个体系跟着某个服务抖动。
3.7 级联失败(cascading failure)
系统中有某个服务出现故障,不可用,传递性地导致整个系统服务不可用的问题。
跟上面涟漪(自造词)的区别也就是严重性的问题。
涟漪描述服务偶发的不稳定层层传递,而级联失败基本是导致系统不可用。 一般,前者可能会因为短时间内恢复而未引起重视,而后者一般会被高度重视。
3.8 关键路径
关键路径就是,你的服务想正常工作,必须要完整依赖的下游服务链,比如数据库一般就是关键路径里面的一个节点。
尽量减少关键路径依赖的数量,是提高服务稳定性的一个措施。
数据库一般在服务体系的最底层,如果你的服务可以会自己完整缓存使用的数据,解除数据库依赖,那么数据库挂掉,你的服务就暂时是安全的。
3.9 最长路径
想要优化你的服务的响应时间,需要看服务调用逻辑里面的最长路径,只有缩短最长时间路径的用时,才能提高你的服务的性能。
18.Redis 的过期策略都有哪些?
常见的有两个问题:
• 往 redis 写入的数据怎么没了?
可能有同学会遇到,在生产环境的 redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 redis 你就没用对啊。redis 是缓存,你给当存储了是吧?
啥叫缓存?用内存当缓存。内存是无限的吗,内存是很宝贵而且是有限的,磁盘是廉价而且是大量的。可能一台机器就几十个 G 的内存,但是可以有几个 T 的硬盘空间**。redis 主要是基于内存来进行高性能、高并发的读写操作的**。
那既然内存是有限的,比如 redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。
• 数据明明过期了,怎么还占用着内存?
这是由 redis 的过期策略来决定。
面试题剖析
redis 过期策略
redis 过期策略是:定期删除+惰性删除。
所谓定期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
假设 redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。
但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。
获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。
但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?
答案是:走内存淘汰机制。
内存淘汰机制
redis 内存淘汰机制有以下几个:
• noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
• allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
• allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
• volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
• volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
• volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
19、java中容器有哪些?
1、什么是容器?
容器就是用来存放和管理对象的对象。
(1)数组
数组是一种典型的容器,但是数组j的容量是有限制的。
(2)java.Util下的集合容器
五大接口:
Collection接口:List、Set、Queue接口。
Map接口。
Iterator迭代器。
每个接口下面有其多个实现类。具体容器见以下链接。
List接口:java容器——List接口(代码模拟ArrayList和LinkedList类的底层实现)
Set接口:java学习:java容器——Set接口分析
Queue接口:java学习:java容器——Queue接口分析及应用
Map接口:java学习:java容器——Map接口(代码模拟HashMap的底层实现)
Iterator迭代器:java学习:java容器——Iterator迭代器详细分析
20.springBoot事务
Transactional注解
21.spring三种注入方式
构造器注入,setter注入,接口注入
22.array和arrayList有什么区别
①Array是Java中的数组,声明数组有三种方式
int[] a=new int[10];
int a[]=new int[10];
int a[]={1,2,3,4};
可以看出:在定义一个数组的时候,必须指定这个数组的数据类型及数组的大小,也就是说数组中存放的元素个数固定并且类型一样
②ArrayList是动态数组,也就是数组的复杂版本,它可以动态的添加和删除元素,被称为”集合“,集合的声明如下
ArrayList list = new ArrayList(10);
ArrayList list1 = new ArrayList();
可以看出:在不使用泛型的情况下,这个list是可以添加进不同类型的元素的,而且arraylist是可以不用指定长度的。在使用泛型时,我们就只能添加一种类型的数据了
23.springbean循环依赖以及解决方案
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
是在A类中通过A类的构造函数注入了B类,而在B类中又通过B类的构造函数注入了A类导致的Spring Bean循环依赖问题。
简明扼要的说,就是——不使用基于构造函数的依赖注入。可通过下面方式解决。
在字段上使用@Autowired注解,让Spring决定在合适的时机注入。【推荐】
用基于setter方法的依赖注射取代基于构造函数的依赖注入来解决循环依赖。
24.jvm双亲委派机制
要了解双亲委派机制得先了解个概念:
类加载器:“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块成为“类加载器”。
通俗的讲,虚拟机是根据类的全限定名来加载类的,那么有个问题,如果同时存在两个或多个全限定名完全一致的情况下。该如何选择加载哪个类。这就是双亲委派机制要做的工作。
在这里强加个知识点:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有真正的意义,否则,即使这两个类来源于同一个class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。
回到双亲委派的问题上,接下来了解下类加载器的种类:
1-启动类加载器,负责加载%JAVA_HOME%\bin目录下的所有jar包,或者是-Xbootclasspath参数指定的路径;
2-扩展类加载器:负责加载%JAVA_HOME%\bin\ext目录下的所有jar包,或者是java.ext.dirs参数指定的路径;
3-应用程序类加载器:负责加载用户类路径上所指定的类库,如果应用程序中没有自定义加载器,那么次加载器就为默认加载器。
加载器之间的层次关系:
双亲委派机制得工作过程:
1-类加载器收到类加载的请求;
2-把这个请求委托给父加载器去完成,一直向上委托,直到启动类加载器;
3-启动器加载器检查能不能加载(使用findClass()方法),能就加载(结束);否则,抛出异常,通知子加载器进行加载。
4-重复步骤三;
以上就是双亲委派机制的原理。
接下来举个例子:
大家所熟知的Object类,直接告诉大家,Object默认情况下是启动类加载器进行加载的。假设我也自定义一个Object,并且制定加载器为自定义加载器。现在你会发现自定义的Object可以正常编译,但是永远无法被加载运行。
这是因为申请自定义Object加载时,总是启动类加载器,而不是自定义加载器,也不会是其他的加载器。
人和事物都有缺陷,双亲委派机制也不例外,三次得到破坏:
1-jdk1.2之间,用户直接去调用loadClass()方法;不能保证双亲委派机制的基本规则。后改成findClass()方法。
2-双亲委派机制的自我缺陷,使用了线程上下文类加载器。这种行为打破了双亲委派机制模型的层次关系来逆向使用类加载器,实际上违背了双亲委派机制的一般性原则。
3-用户对程序动态性的追求而导致的。例如鼠标,键盘灯热部署。
25.常用名词解释
POJO(Plain Ordinary Java Object): POJO 专指只有 setter / getter / toString的简单类,包括DO/DTO/BO/VO等。
GAV(GroupId、ArtifactctId、Version): Maven坐标,是用来唯一标识jar包。
OOP(Object Oriented Programming):泛指类、对象的编程处理方式。
ORM(Object Relation Mapping): 对象关系映射,对象领域模型与底层数据之间的转换,本文泛指mybatis等框架。
NPE(java.lang.NullPointerException): 空指针异常。
SOA(Service-Oriented Architecture): 面向服务架构,它可以根据需求通过网络对松散耦合的粗粒度应用组件进行分布式部署、组合和使用,有利于提升组件可重用性,可维护性。
IDE(Integrated Development Environment): 用于提供程序开发环境的应用程序,一般 包括代码编辑器、编译器、调试器和图形用户界面等工具,泛指eclipse等。
OOM(Out Of Memory): 源于 java.lang.OutOfMemoryError,当JVM 没有足够的内存来为对象分配空间并且垃圾回收器也无法回收空间时,系统出现的严重状况。
一方库: 本工程内部子项目模块依赖的库(jar包)。
二方库: 公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar包)。
三方库: 公司之外的开源库(jar包)。
26.Redis缓存雪崩,击穿,穿透概念以及解决方案
缓存雪崩
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
这就是缓存雪崩
大约在 3 年前,国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3~4 点,公司损失了几千万。
缓存雪崩的事前事中事后的解决方案如下。
- 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
- 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。
限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?走降级!可以返回一些默认的值,或者友情提示,或者空白的值。
好处:
- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。
缓存穿透
对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。
黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。
举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。
解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN
。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
缓存击穿
缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。
27.java常见的异常
1. java.lang.nullpointerexception
这个异常大家肯定都经常遇到,异常的解释是"程序遇上了空指针",简单地说就是调用了未经初始化的对象或者是不存在的对象,这个错误经常出现在创建图片,调用数组这些操作中,比如图片未经初始化,或者图片创建时的路径错误等等。对数组操作中出现空指针,很多情况下是一些刚开始学习编程的朋友常犯的错误,即把数组的初始化和数组元素的初始化混淆起来了。数组的初始化是对数组分配需要的空间,而初始化后的数组,其中的元素并没有实例化,依然是空的,所以还需要对每个元素都进行初始化(如果要调用的话)
2. java.lang.classnotfoundexception
这个异常是很多原本在jb等开发环境中开发的程序员,把jb下的程序包放在wtk下编译经常出现的问题,异常的解释是"指定的类不存在",这里主要考虑一下类的名称和路径是否正确即可,如果是在jb下做的程序包,一般都是默认加上package的,所以转到wtk下后要注意把package的路径加上。
3. java.lang.arithmeticexception
这个异常的解释是"数学运算异常",比如程序中出现了除以零这样的运算就会出这样的异常,对这种异常,大家就要好好检查一下自己程序中涉及到数学运算的地方,公式是不是有不妥了。
4. java.lang.arrayindexoutofboundsexception
这个异常相信很多朋友也经常遇到过,异常的解释是"数组下标越界",现在程序中大多都有对数组的操作,因此在调用数组的时候一定要认真检查,看自己调用的下标是不是超出了数组的范围,一般来说,显示(即直接用常数当下标)调用不太容易出这样的错,但隐式(即用变量表示下标)调用就经常出错了,还有一种情况,是程序中定义的数组的长度是通过某些特定方法决定的,不是事先声明的,这个时候,最好先查看一下数组的length,以免出现这个异常。
5. java.lang.illegalargumentexception
这个异常的解释是"方法的参数错误",很多j2me的类库中的方法在一些情况下都会引发这样的错误,比如音量调节方法中的音量参数如果写成负数就会出现这个异常,再比如g.setcolor(int red,int green,int blue)这个方法中的三个值,如果有超过255的也会出现这个异常,因此一旦发现这个异常,我们要做的,就是赶紧去检查一下方法调用中的参数传递是不是出现了错误。
6. java.lang.illegalaccessexception
这个异常的解释是"没有访问权限",当应用程序要调用一个类,但当前的方法即没有对该类的访问权限便会出现这个异常。对程序中用了package的情况下要注意这个异常。
其他还有很多异常,我就不一一列举了,我要说明的是,一个合格的程序员,需要对程序中常见的问题有相当的了解和相应的解决办法,否则仅仅停留在写程序而不会改程序的话,会极大影响到自己的开发的。关于异常的全部说明,在api里都可以查阅。
算术异常类:ArithmeticExecption
空指针异常类:NullPointerException
类型强制转换异常:ClassCastException
数组负下标异常:NegativeArrayException
数组下标越界异常:ArrayIndexOutOfBoundsException
违背安全原则异常:SecturityException
文件已结束异常:EOFException
文件未找到异常:FileNotFoundException
字符串转换为数字异常:NumberFormatException
操作数据库异常:SQLException
输入输出异常:IOException
方法未找到异常:NoSuchMethodException
java.lang.AbstractMethodError
抽象方法错误。当应用试图调用抽象方法时抛出。
java.lang.AssertionError
断言错。用来指示一个断言失败的情况。
java.lang.ClassCircularityError
类循环依赖错误。在初始化一个类时,若检测到类之间循环依赖则抛出该异常。
java.lang.ClassFormatError
类格式错误。当Java虚拟机试图从一个文件中读取Java类,而检测到该文件的内容不符合类的有效格式时抛出。
java.lang.Error
错误。是所有错误的基类,用于标识严重的程序运行问题。这些问题通常描述一些不应被应用程序捕获的反常情况。
java.lang.ExceptionInInitializerError
初始化程序错误。当执行一个类的静态初始化程序的过程中,发生了异常时抛出。静态初始化程序是指直接包含于类中的static语句段。
java.lang.IllegalAccessError
违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
java.lang.IncompatibleClassChangeError
不兼容的类变化错误。当正在执行的方法所依赖的类定义发生了不兼容的改变时,抛出该异常。一般在修改了应用中的某些类的声明定义而没有对整个应用重新编译而直接运行的情况下,容易引发该错误。
java.lang.InstantiationError
实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
java.lang.InternalError
内部错误。用于指示Java虚拟机发生了内部错误。
java.lang.LinkageError
链接错误。该错误及其所有子类指示某个类依赖于另外一些类,在该类编译之后,被依赖的类改变了其类定义而没有重新编译所有的类,进而引发错误的情况。
java.lang.NoClassDefFoundError
未找到类定义错误。当Java虚拟机或者类装载器试图实例化某个类,而找不到该类的定义时抛出该错误。
java.lang.NoSuchFieldError
域不存在错误。当应用试图访问或者修改某类的某个域,而该类的定义中没有该域的定义时抛出该错误。
java.lang.NoSuchMethodError
方法不存在错误。当应用试图调用某类的某个方法,而该类的定义中没有该方法的定义时抛出该错误。
java.lang.OutOfMemoryError
内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
java.lang.StackOverflowError
堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出时抛出该错误。
java.lang.ThreadDeath
线程结束。当调用Thread类的stop方法时抛出该错误,用于指示线程结束。
java.lang.UnknownError
未知错误。用于指示Java虚拟机发生了未知严重错误的情况。
java.lang.UnsatisfiedLinkError
未满足的链接错误。当Java虚拟机未找到某个类的声明为native方法的本机语言定义时抛出。
java.lang.UnsupportedClassVersionError
不支持的类版本错误。当Java虚拟机试图从读取某个类文件,但是发现该文件的主、次版本号不被当前Java虚拟机支持的时候,抛出该错误。
java.lang.VerifyError
验证错误。当验证器检测到某个类文件中存在内部不兼容或者安全问题时抛出该错误。
java.lang.VirtualMachineError
虚拟机错误。用于指示虚拟机被破坏或者继续执行操作所需的资源不足的情况。
java.lang.ArithmeticException
算术条件异常。譬如:整数除零等。
java.lang.ArrayIndexOutOfBoundsException
数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
java.lang.ArrayStoreException
数组存储异常。当向数组中存放非数组声明类型对象时抛出。
java.lang.ClassCastException
类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
java.lang.ClassNotFoundException
找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
java.lang.CloneNotSupportedException
不支持克隆异常。当没有实现Cloneable接口或者不支持克隆方法时,调用其clone()方法则抛出该异常。
java.lang.EnumConstantNotPresentException
枚举常量不存在异常。当应用试图通过名称和枚举类型访问一个枚举对象,但该枚举对象并不包含常量时,抛出该异常。
java.lang.Exception
根异常。用以描述应用程序希望捕获的情况。
java.lang.IllegalAccessException
违法的访问异常。当应用试图通过反射方式创建某个类的实例、访问该类属性、调用该类方法,而当时又无法访问类的、属性的、方法的或构造方法的定义时抛出该异常。
java.lang.IllegalMonitorStateException
违法的监控状态异常。当某个线程试图等待一个自己并不拥有的对象(O)的监控器或者通知其他线程等待该对象(O)的监控器时,抛出该异常。
java.lang.IllegalStateException
违法的状态异常。当在Java环境和应用尚未处于某个方法的合法调用状态,而调用了该方法时,抛出该异常。
java.lang.IllegalThreadStateException
违法的线程状态异常。当县城尚未处于某个方法的合法调用状态,而调用了该方法时,抛出异常。
java.lang.IndexOutOfBoundsException
索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
java.lang.InstantiationException
实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
java.lang.InterruptedException
被中止异常。当某个线程处于长时间的等待、休眠或其他暂停状态,而此时其他的线程通过Thread的interrupt方法终止该线程时抛出该异常。
java.lang.NegativeArraySizeException
数组大小为负值异常。当使用负数大小值创建数组时抛出该异常。
java.lang.NoSuchFieldException
属性不存在异常。当访问某个类的不存在的属性时抛出该异常。
java.lang.NoSuchMethodException
方法不存在异常。当访问某个类的不存在的方法时抛出该异常。
java.lang.NullPointerException
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
java.lang.NumberFormatException
数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
java.lang.RuntimeException
运行时异常。是所有Java虚拟机正常操作期间可以被抛出的异常的父类。
java.lang.SecurityException
安全异常。由安全管理器抛出,用于指示违反安全情况的异常。
java.lang.StringIndexOutOfBoundsException
字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。
java.lang.TypeNotPresentException
类型不存在异常。
28.存储过程和函数还有触发器
什么是存储过程?
存储过程是在大型数据库系统中,一组为了完成特定功能的SQL语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字和参数(如果该存储过程有参数)来执行它。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程。
优点:
(1)重复使用:存储过程可以重复使用,可以减少开发人员的工作量。
(2)提高效率:存储过程在第一次使用的时候会编译,一次编译后以后不用再次编译,提高了效率。
(3)减少网络流量:存储过程位于服务器上,调用的时候只需要传递存储过程的名称以及参数就可以了,因此降低了网络传输的数据量。
(4)安全性:参数化的存储过程可以防止SQL注入,而且可以将Grant、Deny以及Revoke权限应用于存储过程。
缺点:
(1)调试麻烦
(2)移植性差
(3)维护性差
什么是函数?
存储函数为一组存储程序,带函数名、参数,且返回一个结果集。存储函数和存储过程的结构类似,但必须有一个return 子句来返回结果。
什么是触发器?
在mysql中,触发器是存储在数据库目录中的一组SQL语句,每当与表相关联的事件发生时,即会执行或触发触发器,例如插入、更新或删除。触发器与数据表关系密切,主要用于保护表中的数据;特别是当有多个表具有一定的相互联系的时候,触发器能够让不同的表保持数据的一致性。在MySQL中,只有执行INSERT、UPDATE和DELETE操作时才能激活触发器,其它SQL语句则不会激活触发器。
存储过程和函数的区别
1、总述
存储过程和存储函数统称为存储例程。两者的语法很相似,但却是不同的内容。
存储函数限制比较多,比如不能用临时表,只能用表变量。还有一些函数都不可用等等。
而存储过程的限制就相对比较少,要实现的功能比较复杂一些。
2、返回值上的不同
存储函数将向调用者返回一个且仅有一个结果值。
存储过程将返回一个或多个结果集(函数做不到这一点),或者只是来实现某种效果或动作而无需返回结果。
3、调用方式的不同
存储函数嵌入在sql中使用的,可以在select中调用,就像内置函数一样,比如cos()、sin()。
4、参数的不同
存储函数的参数类型类似于IN参数
存储过程的参数类型有三种:IN参数、OUT参数、INOUT参数
in:数据只是从外部传入内部使用(值传递),可以是数值也可以是变量
out:只允许过程内部使用(不用外部数据),给外部使用的(引用传递:外部的数据会被先清空才会进入到内部),只能是变量
inout:外部可以在内部使用,内部修改的也可以给外部使用,典型的引用 传递,只能传递变量。
存储过程是用户定义的一系列sql语句的集合,设计特定表或其它对象的任务,用户可以调用存储过程。
而函数通常是数据库已定义的方法,它接收参数并返回某种类型的值并且不涉及特定用户表。
29.为什么站点使用https加密之后还能看到相关数据
这是因为:https(ssl)加密是发生在应用层与传输层之间,所以在传输层看到的数据才是经过加密的,而我们捕捉到的http post,是应用层的数据,此时还没有经过加密。这些明文信息,其实就是你的本地数据。
加密数据只有客户端和服务器端才能得到明文,客户端到服务端的通信过程是安全的。
可能有些读者会对此表示担忧了:这样的话密码不是会被本地恶意软件截获么?只能说的确存在这样的可能。不过在银行电商等安全防护程度较高的网站,除了https加密外,还有安全控件加密,用户必须下载安全控件后才能输入密码,以支付宝为例:通过下图可以发现就算在本地也无法查看账号信息。
针对这个问题,Gworg CA建议:行政、金融、电商等需要高安全防护的站点在实行https加密外,还应该部署Gworg客户端证书,以强身份认证的方式替代不安全的账号密码认证,确保登录过程中的安全无忧,同时还能弱口令漏洞,最大程度保护用户信息安全。
此外,针对使用安全控件辅助登录的站点,Gworg还建议使用代码签名证书,以保证安全控件在下载传输过程中没有被篡改及破坏。从而保证代码的完整性,保护用户不会被病毒、恶意代码和间谍软件所侵害,真正做到全面安全防护。
30.常见的数据结构(栈、队列、数组、链表和红黑树)
(一)栈
栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其
他任何位置进行添加、查找、删除等操作。
简单的说:采用该结构的集合,对元素的存取有如下的特点
先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹
夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的
子弹。
栈的入口、出口的都是栈的顶端位置。
这里两个名词需要注意:
压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
(二)队列
队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,
而在表的另一端进行删除。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山
洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。
(三)链表
链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每
个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的
链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次
类推,这样多个人就连在一起了。
查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
增删元素快:
增加元素:只需要修改连接下个元素的地址即可。
删除元素:只需要修改连接下个元素的地址即可。
(四)红黑树
二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。
简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子结点。
二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。
如图:
我们要说的是二叉树的一种比较有意思的叫做红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然
是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
红黑树的约束:
1. 节点可以是红色的或者黑色的
2. 根节点是黑色的
3. 叶子节点(特指空节点)是黑色的
4. 每个红色节点的子节点都是黑色的
5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
红黑树的特点:
速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
31.liunx常用命令
系统信息
arch 显示机器的处理器架构(1)
uname -m 显示机器的处理器架构(2)
uname -r 显示正在使用的内核版本
dmidecode -q 显示硬件系统部件 - (SMBIOS / DMI)
hdparm -i /dev/hda 罗列一个磁盘的架构特性
hdparm -tT /dev/sda 在磁盘上执行测试性读取操作
cat /proc/cpuinfo 显示CPU info的信息
cat /proc/interrupts 显示中断
cat /proc/meminfo 校验内存使用
cat /proc/swaps 显示哪些swap被使用
cat /proc/version 显示内核的版本
cat /proc/net/dev 显示网络适配器及统计
cat /proc/mounts 显示已加载的文件系统
lspci -tv 罗列 PCI 设备
lsusb -tv 显示 USB 设备
date 显示系统日期
cal 2007 显示2007年的日历表
date 041217002007.00 设置日期和时间 - 月日时分年.秒
clock -w 将时间修改保存到 BIOS
关机 (系统的关机、重启以及登出 )
shutdown -h now 关闭系统(1)
init 0 关闭系统(2)
telinit 0 关闭系统(3)
shutdown -h hours:minutes & 按预定时间关闭系统
shutdown -c 取消按预定时间关闭系统
shutdown -r now 重启(1)
reboot 重启(2)
logout 注销
文件和目录
cd /home 进入 '/ home' 目录'
cd .. 返回上一级目录
cd ../.. 返回上两级目录
cd 进入个人的主目录
cd ~user1 进入个人的主目录
cd - 返回上次所在的目录
pwd 显示工作路径
ls 查看目录中的文件
ls -F 查看目录中的文件
ls -l 显示文件和目录的详细资料
ls -a 显示隐藏文件
ls *[0-9]* 显示包含数字的文件名和目录名
tree 显示文件和目录由根目录开始的树形结构(1)
lstree 显示文件和目录由根目录开始的树形结构(2)
mkdir dir1 创建一个叫做 'dir1' 的目录'
mkdir dir1 dir2 同时创建两个目录
mkdir -p /tmp/dir1/dir2 创建一个目录树
rm -f file1 删除一个叫做 'file1' 的文件'
rmdir dir1 删除一个叫做 'dir1' 的目录'
rm -rf dir1 删除一个叫做 'dir1' 的目录并同时删除其内容
rm -rf dir1 dir2 同时删除两个目录及它们的内容
mv dir1 new_dir 重命名/移动 一个目录
cp file1 file2 复制一个文件
cp dir/* . 复制一个目录下的所有文件到当前工作目录
cp -a /tmp/dir1 . 复制一个目录到当前工作目录
cp -a dir1 dir2 复制一个目录
ln -s file1 lnk1 创建一个指向文件或目录的软链接
ln file1 lnk1 创建一个指向文件或目录的物理链接
touch -t 0712250000 file1 修改一个文件或目录的时间戳 - (YYMMDDhhmm)
file file1 outputs the mime type of the file as text
iconv -l 列出已知的编码
iconv -f fromEncoding -t toEncoding inputFile > outputFile creates a new from the given input file by assuming it is encoded in fromEncoding and converting it to toEncoding.
find . -maxdepth 1 -name *.jpg -print -exec convert "{}" -resize 80x60 "thumbs/{}" \; batch resize files in the current directory and send them to a thumbnails directory (requires convert from Imagemagick)
文件搜索
find / -name file1 从 '/' 开始进入根文件系统搜索文件和目录
find / -user user1 搜索属于用户 'user1' 的文件和目录
find /home/user1 -name \*.bin 在目录 '/ home/user1' 中搜索带有'.bin' 结尾的文件
find /usr/bin -type f -atime +100 搜索在过去100天内未被使用过的执行文件
find /usr/bin -type f -mtime -10 搜索在10天内被创建或者修改过的文件
find / -name \*.rpm -exec chmod 755 '{}' \; 搜索以 '.rpm' 结尾的文件并定义其权限
find / -xdev -name \*.rpm 搜索以 '.rpm' 结尾的文件,忽略光驱、捷盘等可移动设备
locate \*.ps 寻找以 '.ps' 结尾的文件 - 先运行 'updatedb' 命令
whereis halt 显示一个二进制文件、源码或man的位置
which halt 显示一个二进制文件或可执行文件的完整路径
挂载一个文件系统
mount /dev/hda2 /mnt/hda2 挂载一个叫做hda2的盘 - 确定目录 '/ mnt/hda2' 已经存在
umount /dev/hda2 卸载一个叫做hda2的盘 - 先从挂载点 '/ mnt/hda2' 退出
fuser -km /mnt/hda2 当设备繁忙时强制卸载
umount -n /mnt/hda2 运行卸载操作而不写入 /etc/mtab 文件- 当文件为只读或当磁盘写满时非常有用
mount /dev/fd0 /mnt/floppy 挂载一个软盘
mount /dev/cdrom /mnt/cdrom 挂载一个cdrom或dvdrom
mount /dev/hdc /mnt/cdrecorder 挂载一个cdrw或dvdrom
mount /dev/hdb /mnt/cdrecorder 挂载一个cdrw或dvdrom
mount -o loop file.iso /mnt/cdrom 挂载一个文件或ISO镜像文件
mount -t vfat /dev/hda5 /mnt/hda5 挂载一个Windows FAT32文件系统
mount /dev/sda1 /mnt/usbdisk 挂载一个usb 捷盘或闪存设备
mount -t smbfs -o username=user,password=pass //WinClient/share /mnt/share 挂载一个windows网络共享
磁盘空间
df -h 显示已经挂载的分区列表
ls -lSr |more 以尺寸大小排列文件和目录
du -sh dir1 估算目录 'dir1' 已经使用的磁盘空间'
du -sk * | sort -rn 以容量大小为依据依次显示文件和目录的大小
rpm -q -a --qf '%10{SIZE}t%{NAME}n' | sort -k1,1n 以大小为依据依次显示已安装的rpm包所使用的空间 (fedora, redhat类系统)
dpkg-query -W -f='${Installed-Size;10}t${Package}n' | sort -k1,1n 以大小为依据显示已安装的deb包所使用的空间 (ubuntu, debian类系统)
用户和群组
groupadd group_name 创建一个新用户组
groupdel group_name 删除一个用户组
groupmod -n new_group_name old_group_name 重命名一个用户组
useradd -c "Name Surname " -g admin -d /home/user1 -s /bin/bash user1 创建一个属于 "admin" 用户组的用户
useradd user1 创建一个新用户
userdel -r user1 删除一个用户 ( '-r' 排除主目录)
usermod -c "User FTP" -g system -d /ftp/user1 -s /bin/nologin user1 修改用户属性
passwd 修改口令
passwd user1 修改一个用户的口令 (只允许root执行)
chage -E 2005-12-31 user1 设置用户口令的失效期限
pwck 检查 '/etc/passwd' 的文件格式和语法修正以及存在的用户
grpck 检查 '/etc/passwd' 的文件格式和语法修正以及存在的群组
newgrp group_name 登陆进一个新的群组以改变新创建文件的预设群组
文件的权限 - 使用 "+" 设置权限,使用 "-" 用于取消
ls -lh 显示权限
ls /tmp | pr -T5 -W$COLUMNS 将终端划分成5栏显示
chmod ugo+rwx directory1 设置目录的所有人(u)、群组(g)以及其他人(o)以读(r )、写(w)和执行(x)的权限
chmod go-rwx directory1 删除群组(g)与其他人(o)对目录的读写执行权限
chown user1 file1 改变一个文件的所有人属性
chown -R user1 directory1 改变一个目录的所有人属性并同时改变改目录下所有文件的属性
chgrp group1 file1 改变文件的群组
chown user1:group1 file1 改变一个文件的所有人和群组属性
find / -perm -u+s 罗列一个系统中所有使用了SUID控制的文件
chmod u+s /bin/file1 设置一个二进制文件的 SUID 位 - 运行该文件的用户也被赋予和所有者同样的权限
chmod u-s /bin/file1 禁用一个二进制文件的 SUID位
chmod g+s /home/public 设置一个目录的SGID 位 - 类似SUID ,不过这是针对目录的
chmod g-s /home/public 禁用一个目录的 SGID 位
chmod o+t /home/public 设置一个文件的 STIKY 位 - 只允许合法所有人删除文件
chmod o-t /home/public 禁用一个目录的 STIKY 位
文件的特殊属性 - 使用 "+" 设置权限,使用 "-" 用于取消
chattr +a file1 只允许以追加方式读写文件
chattr +c file1 允许这个文件能被内核自动压缩/解压
chattr +d file1 在进行文件系统备份时,dump程序将忽略这个文件
chattr +i file1 设置成不可变的文件,不能被删除、修改、重命名或者链接
chattr +s file1 允许一个文件被安全地删除
chattr +S file1 一旦应用程序对这个文件执行了写操作,使系统立刻把修改的结果写到磁盘
chattr +u file1 若文件被删除,系统会允许你在以后恢复这个被删除的文件
lsattr 显示特殊的属性
打包和压缩文件
bunzip2 file1.bz2 解压一个叫做 'file1.bz2'的文件
bzip2 file1 压缩一个叫做 'file1' 的文件
gunzip file1.gz 解压一个叫做 'file1.gz'的文件
gzip file1 压缩一个叫做 'file1'的文件
gzip -9 file1 最大程度压缩
rar a file1.rar test_file 创建一个叫做 'file1.rar' 的包
rar a file1.rar file1 file2 dir1 同时压缩 'file1', 'file2' 以及目录 'dir1'
rar x file1.rar 解压rar包
unrar x file1.rar 解压rar包
tar -cvf archive.tar file1 创建一个非压缩的 tarball
tar -cvf archive.tar file1 file2 dir1 创建一个包含了 'file1', 'file2' 以及 'dir1'的档案文件
tar -tf archive.tar 显示一个包中的内容
tar -xvf archive.tar 释放一个包
tar -xvf archive.tar -C /tmp 将压缩包释放到 /tmp目录下
tar -cvfj archive.tar.bz2 dir1 创建一个bzip2格式的压缩包
tar -xvfj archive.tar.bz2 解压一个bzip2格式的压缩包
tar -cvfz archive.tar.gz dir1 创建一个gzip格式的压缩包
tar -xvfz archive.tar.gz 解压一个gzip格式的压缩包
zip file1.zip file1 创建一个zip格式的压缩包
zip -r file1.zip file1 file2 dir1 将几个文件和目录同时压缩成一个zip格式的压缩包
unzip file1.zip 解压一个zip格式压缩包
RPM 包 - (Fedora, Redhat及类似系统)
rpm -ivh package.rpm 安装一个rpm包
rpm -ivh --nodeeps package.rpm 安装一个rpm包而忽略依赖关系警告
rpm -U package.rpm 更新一个rpm包但不改变其配置文件
rpm -F package.rpm 更新一个确定已经安装的rpm包
rpm -e package_name.rpm 删除一个rpm包
rpm -qa 显示系统中所有已经安装的rpm包
rpm -qa | grep httpd 显示所有名称中包含 "httpd" 字样的rpm包
rpm -qi package_name 获取一个已安装包的特殊信息
rpm -qg "System Environment/Daemons" 显示一个组件的rpm包
rpm -ql package_name 显示一个已经安装的rpm包提供的文件列表
rpm -qc package_name 显示一个已经安装的rpm包提供的配置文件列表
rpm -q package_name --whatrequires 显示与一个rpm包存在依赖关系的列表
rpm -q package_name --whatprovides 显示一个rpm包所占的体积
rpm -q package_name --scripts 显示在安装/删除期间所执行的脚本l
rpm -q package_name --changelog 显示一个rpm包的修改历史
rpm -qf /etc/httpd/conf/httpd.conf 确认所给的文件由哪个rpm包所提供
rpm -qp package.rpm -l 显示由一个尚未安装的rpm包提供的文件列表
rpm --import /media/cdrom/RPM-GPG-KEY 导入公钥数字证书
rpm --checksig package.rpm 确认一个rpm包的完整性
rpm -qa gpg-pubkey 确认已安装的所有rpm包的完整性
rpm -V package_name 检查文件尺寸、 许可、类型、所有者、群组、MD5检查以及最后修改时间
rpm -Va 检查系统中所有已安装的rpm包- 小心使用
rpm -Vp package.rpm 确认一个rpm包还未安装
rpm2cpio package.rpm | cpio --extract --make-directories *bin* 从一个rpm包运行可执行文件
rpm -ivh /usr/src/redhat/RPMS/`arch`/package.rpm 从一个rpm源码安装一个构建好的包
rpmbuild --rebuild package_name.src.rpm 从一个rpm源码构建一个 rpm 包
YUM 软件包升级器 - (Fedora, RedHat及类似系统)
yum install package_name 下载并安装一个rpm包
yum localinstall package_name.rpm 将安装一个rpm包,使用你自己的软件仓库为你解决所有依赖关系
yum update package_name.rpm 更新当前系统中所有安装的rpm包
yum update package_name 更新一个rpm包
yum remove package_name 删除一个rpm包
yum list 列出当前系统中安装的所有包
yum search package_name 在rpm仓库中搜寻软件包
yum clean packages 清理rpm缓存删除下载的包
yum clean headers 删除所有头文件
yum clean all 删除所有缓存的包和头文件
DEB 包 (Debian, Ubuntu 以及类似系统)
dpkg -i package.deb 安装/更新一个 deb 包
dpkg -r package_name 从系统删除一个 deb 包
dpkg -l 显示系统中所有已经安装的 deb 包
dpkg -l | grep httpd 显示所有名称中包含 "httpd" 字样的deb包
dpkg -s package_name 获得已经安装在系统中一个特殊包的信息
dpkg -L package_name 显示系统中已经安装的一个deb包所提供的文件列表
dpkg --contents package.deb 显示尚未安装的一个包所提供的文件列表
dpkg -S /bin/ping 确认所给的文件由哪个deb包提供
APT 软件工具 (Debian, Ubuntu 以及类似系统)
apt-get install package_name 安装/更新一个 deb 包
apt-cdrom install package_name 从光盘安装/更新一个 deb 包
apt-get update 升级列表中的软件包
apt-get upgrade 升级所有已安装的软件
apt-get remove package_name 从系统删除一个deb包
apt-get check 确认依赖的软件仓库正确
apt-get clean 从下载的软件包中清理缓存
apt-cache search searched-package 返回包含所要搜索字符串的软件包名称
查看文件内容
cat file1 从第一个字节开始正向查看文件的内容
tac file1 从最后一行开始反向查看一个文件的内容
more file1 查看一个长文件的内容
less file1 类似于 'more' 命令,但是它允许在文件中和正向操作一样的反向操作
head -2 file1 查看一个文件的前两行
tail -2 file1 查看一个文件的最后两行
tail -f /var/log/messages 实时查看被添加到一个文件中的内容
文本处理
cat file1 file2 ... | command <> file1_in.txt_or_file1_out.txt general syntax for text manipulation using PIPE, STDIN and STDOUT
cat file1 | command( sed, grep, awk, grep, etc...) > result.txt 合并一个文件的详细说明文本,并将简介写入一个新文件中
cat file1 | command( sed, grep, awk, grep, etc...) >> result.txt 合并一个文件的详细说明文本,并将简介写入一个已有的文件中
grep Aug /var/log/messages 在文件 '/var/log/messages'中查找关键词"Aug"
grep ^Aug /var/log/messages 在文件 '/var/log/messages'中查找以"Aug"开始的词汇
grep [0-9] /var/log/messages 选择 '/var/log/messages' 文件中所有包含数字的行
grep Aug -R /var/log/* 在目录 '/var/log' 及随后的目录中搜索字符串"Aug"
sed 's/stringa1/stringa2/g' example.txt 将example.txt文件中的 "string1" 替换成 "string2"
sed '/^$/d' example.txt 从example.txt文件中删除所有空白行
sed '/ *#/d; /^$/d' example.txt 从example.txt文件中删除所有注释和空白行
echo 'esempio' | tr '[:lower:]' '[:upper:]' 合并上下单元格内容
sed -e '1d' result.txt 从文件example.txt 中排除第一行
sed -n '/stringa1/p' 查看只包含词汇 "string1"的行
sed -e 's/ *$//' example.txt 删除每一行最后的空白字符
sed -e 's/stringa1//g' example.txt 从文档中只删除词汇 "string1" 并保留剩余全部
sed -n '1,5p;5q' example.txt 查看从第一行到第5行内容
sed -n '5p;5q' example.txt 查看第5行
sed -e 's/00*/0/g' example.txt 用单个零替换多个零
cat -n file1 标示文件的行数
cat example.txt | awk 'NR%2==1' 删除example.txt文件中的所有偶数行
echo a b c | awk '{print $1}' 查看一行第一栏
echo a b c | awk '{print $1,$3}' 查看一行的第一和第三栏
paste file1 file2 合并两个文件或两栏的内容
paste -d '+' file1 file2 合并两个文件或两栏的内容,中间用"+"区分
sort file1 file2 排序两个文件的内容
sort file1 file2 | uniq 取出两个文件的并集(重复的行只保留一份)
sort file1 file2 | uniq -u 删除交集,留下其他的行
sort file1 file2 | uniq -d 取出两个文件的交集(只留下同时存在于两个文件中的文件)
comm -1 file1 file2 比较两个文件的内容只删除 'file1' 所包含的内容
comm -2 file1 file2 比较两个文件的内容只删除 'file2' 所包含的内容
comm -3 file1 file2 比较两个文件的内容只删除两个文件共有的部分
字符设置和文件格式转换
dos2unix filedos.txt fileunix.txt 将一个文本文件的格式从MSDOS转换成UNIX
unix2dos fileunix.txt filedos.txt 将一个文本文件的格式从UNIX转换成MSDOS
recode ..HTML < page.txt > page.html 将一个文本文件转换成html
recode -l | more 显示所有允许的转换格式
文件系统分析
badblocks -v /dev/hda1 检查磁盘hda1上的坏磁块
fsck /dev/hda1 修复/检查hda1磁盘上linux文件系统的完整性
fsck.ext2 /dev/hda1 修复/检查hda1磁盘上ext2文件系统的完整性
e2fsck /dev/hda1 修复/检查hda1磁盘上ext2文件系统的完整性
e2fsck -j /dev/hda1 修复/检查hda1磁盘上ext3文件系统的完整性
fsck.ext3 /dev/hda1 修复/检查hda1磁盘上ext3文件系统的完整性
fsck.vfat /dev/hda1 修复/检查hda1磁盘上fat文件系统的完整性
fsck.msdos /dev/hda1 修复/检查hda1磁盘上dos文件系统的完整性
dosfsck /dev/hda1 修复/检查hda1磁盘上dos文件系统的完整性
初始化一个文件系统
mkfs /dev/hda1 在hda1分区创建一个文件系统
mke2fs /dev/hda1 在hda1分区创建一个linux ext2的文件系统
mke2fs -j /dev/hda1 在hda1分区创建一个linux ext3(日志型)的文件系统
mkfs -t vfat 32 -F /dev/hda1 创建一个 FAT32 文件系统
fdformat -n /dev/fd0 格式化一个软盘
mkswap /dev/hda3 创建一个swap文件系统
SWAP文件系统
mkswap /dev/hda3 创建一个swap文件系统
swapon /dev/hda3 启用一个新的swap文件系统
swapon /dev/hda2 /dev/hdb3 启用两个swap分区
备份
dump -0aj -f /tmp/home0.bak /home 制作一个 '/home' 目录的完整备份
dump -1aj -f /tmp/home0.bak /home 制作一个 '/home' 目录的交互式备份
restore -if /tmp/home0.bak 还原一个交互式备份
rsync -rogpav --delete /home /tmp 同步两边的目录
rsync -rogpav -e ssh --delete /home ip_address:/tmp 通过SSH通道rsync
rsync -az -e ssh --delete ip_addr:/home/public /home/local 通过ssh和压缩将一个远程目录同步到本地目录
rsync -az -e ssh --delete /home/local ip_addr:/home/public 通过ssh和压缩将本地目录同步到远程目录
dd bs=1M if=/dev/hda | gzip | ssh user@ip_addr 'dd of=hda.gz' 通过ssh在远程主机上执行一次备份本地磁盘的操作
dd if=/dev/sda of=/tmp/file1 备份磁盘内容到一个文件
tar -Puf backup.tar /home/user 执行一次对 '/home/user' 目录的交互式备份操作
( cd /tmp/local/ && tar c . ) | ssh -C user@ip_addr 'cd /home/share/ && tar x -p' 通过ssh在远程目录中复制一个目录内容
( tar c /home ) | ssh -C user@ip_addr 'cd /home/backup-home && tar x -p' 通过ssh在远程目录中复制一个本地目录
tar cf - . | (cd /tmp/backup ; tar xf - ) 本地将一个目录复制到另一个地方,保留原有权限及链接
find /home/user1 -name '*.txt' | xargs cp -av --target-directory=/home/backup/ --parents 从一个目录查找并复制所有以 '.txt' 结尾的文件到另一个目录
find /var/log -name '*.log' | tar cv --files-from=- | bzip2 > log.tar.bz2 查找所有以 '.log' 结尾的文件并做成一个bzip包
dd if=/dev/hda of=/dev/fd0 bs=512 count=1 做一个将 MBR (Master Boot Record)内容复制到软盘的动作
dd if=/dev/fd0 of=/dev/hda bs=512 count=1 从已经保存到软盘的备份中恢复MBR内容
光盘
cdrecord -v gracetime=2 dev=/dev/cdrom -eject blank=fast -force 清空一个可复写的光盘内容
mkisofs /dev/cdrom > cd.iso 在磁盘上创建一个光盘的iso镜像文件
mkisofs /dev/cdrom | gzip > cd_iso.gz 在磁盘上创建一个压缩了的光盘iso镜像文件
mkisofs -J -allow-leading-dots -R -V "Label CD" -iso-level 4 -o ./cd.iso data_cd 创建一个目录的iso镜像文件
cdrecord -v dev=/dev/cdrom cd.iso 刻录一个ISO镜像文件
gzip -dc cd_iso.gz | cdrecord dev=/dev/cdrom - 刻录一个压缩了的ISO镜像文件
mount -o loop cd.iso /mnt/iso 挂载一个ISO镜像文件
cd-paranoia -B 从一个CD光盘转录音轨到 wav 文件中
cd-paranoia -- "-3" 从一个CD光盘转录音轨到 wav 文件中(参数-3)
cdrecord --scanbus 扫描总线以识别scsi通道
dd if=/dev/hdc | md5sum 校验一个设备的md5sum编码,例如一张 CD
32.dubbo和 spring cloud区别
dubbo和 spring cloud之间的差别有以下几点;
1. Dubbo的特性仅仅是 Spring云系统的一部分。作为 SOA时代的产品, Dubbo的重点是服务呼叫、流量分配、流量监控和保险。Spring Cloud是在微型服务体系的时代诞生的,它涉及到了各个方面,再加上 Spring和 Spring Boot的支持,这两个框架的起点并不相同, Dubbo定位服务治理和 Spring Cloud是一个生态系统。
33.Redis查询为什么快
redis查询速度快的方法:1、redis查询完全基于内存;2、redis中的数据结构简单;3、redis采用单线程;4、redis使用多路I/O复用模型;5、redis构建了VM机制。
34.nginx负载均衡策略
1、轮询
最基本的配置方法,上面的例子就是轮询的方式,它是upstream模块默认的负载均衡默认策略。每个请求会按时间顺序逐一分配到不同的后端服务器。
2、weight
权重方式,在轮询策略的基础上指定轮询的几率。
3、ip_hash
指定负载均衡器按照基于客户端IP的分配方式,这个方法确保了相同的客户端的请求一直发送到相同的服务器,以保证session会话。这样每个访客都固定访问一个后端服务器,可以解决session不能跨服务器的问题。
4、least_conn
把请求转发给连接数较少的后端服务器。轮询算法是把请求平均的转发给各个后端,使它们的负载大致相同;但是,有些请求占用的时间很长,会导致其所在的后端负载较高。这种情况下,least_conn这种方式就可以达到更好的负载均衡效果。
5、第三方策略
第三方的负载均衡策略的实现需要安装第三方插件。
①fair
按照服务器端的响应时间来分配请求,响应时间短的优先分配。
②url_hash
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,要配合缓存命中来使用。同一个资源多次请求,可能会到达不同的服务器上,导致不必要的多次下载,缓存命中率不高,以及一些资源时间的浪费。而使用url_hash,可以使得同一个url(也就是同一个资源请求)会到达同一台服务器,一旦缓存住了资源,再此收到请求,就可以从缓存中读取。
35.乐观锁和悲观锁
一、乐观锁
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
核心SQL代码:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
CAS操作方式:即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
一、悲观锁
总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。
36.启动线程的三种方法
1、继承Thread类, 重写run方法,在main函数中,调用start方法。
2、实现Runnable接口,重写run方法,在main函数中,调用start方法。
3、实现Callable接口,重写call()方法,在main函数中调用start()方法。
37.怎么防御xss攻击,csrf攻击,sql注入
1、xss攻击,跨站脚本攻击值得是攻击者在网页中嵌入恶意脚本程序,当用户打开浏览器,脚本程序便开始在客户端的浏览器上执行,一盗取客户端cookie等信息,利用系统对用户的信任。
防御措施:将html转移处理htmlspe
2、csrf攻击是跨站请求伪造,攻击者盗用了你的身份,以你的名义向第三方网站发送恶意请求,利用系统对页面浏览器的信任。
防御措施:①将cookie设置为HttpOnly。如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性。
//设置cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly")
②设置token
③通过refer识别:根据http协议,在http投中有一个字符安叫refer,它记录了该http请求的来源地址
3、SQL注入,通过预处理可以解决
38.java中线程与进程的区别是什么?
线程与进程的主要区别是:(同一进程中的)线程在共享的内存空间中运行,而进程在不同的内存空间中运行;线程使用wait(),notify(),notifyAll()等方法直接与其他线程(同一进程)通信,而进程需要使用“IPC”来与其他进程通信。
什么是进程?
正在运行中的应用程序,通常称为进程。每个进程都有自己独立的地址空间(内存空间),每当用户启动一个进程时,操作系统就会为该进程分配一个独立的内存空间,让应用程序在这个独立的内存空间中运行。
什么是线程?
线程是一个轻量级的子进程,是最小的处理单元;是一个单独的执行路径。可以说:线程是进程的子集(部分)。
线程是独立的。如果在一个线程中发生异常,则不会影响其他线程。它使用共享内存区域。
进程和线程的关系:
如上图所示,操作系统中可以拥有多个进程,一个进程里可以拥有多个线程,线程在进程内执行。
进程和线程的区别:
1、容易创建新线程。但是,创建新进程需要重复父进程。
2、线程可以控制同一进程的其他线程。进程无法控制兄弟进程,只能控制其子进程。
3、进程拥有自己的内存空间。线程使用进程的内存空间,且要和该进程的其他线程共享这个空间;而不是在进程中给每个线程单独划分一点空间。
4、(同一进程中的)线程在共享内存空间中运行,而进程在不同的内存空间中运行。
5、线程可以使用wait(),notify(),notifyAll()等方法直接与其他线程(同一进程)通信;而,进程需要使用“进程间通信”(IPC)来与操作系统中的其他进程通信。
说明:
IPC,即进程间通信,特指操作系统提供的允许进程管理共享数据的一种机制。
39.Spring工作原理
内部最核心的就是IOC了,
动态注入,让一个对象的创建不用new了,可以自动的生产,这其实就是利用java里的反射
反射其实就是在运行时动态的去创建、调用对象,Spring就是在运行时,跟xml Spring的配置
文件来动态的创建对象,和调用对象里的方法的 。
Spring还有一个核心就是AOP这个就是面向切面编程,可以为某一类对象 进行监督和控制(也就是
在调用这类对象的具体方法的前后去调用你指定的 模块)从而达到对一个模块扩充的功能。这些都是通过
配置类达到的。
Spring目的:就是让对象与对象(模块与模块)之间的关系没有通过代码来关联,都是通过配置类说明
管理的(Spring根据这些配置 内部通过反射去动态的组装对象)
要记住:Spring是一个容器,凡是在容器里的对象才会有Spring所提供的这些服务和功能。
Spring里用的最经典的一个设计模式就是:模板方法模式。(这里我都不介绍了,是一个很常用的设计模式)
Spring里的配置是很多的,很难都记住,但是Spring里的精华也无非就是以上的两点,把以上两点跟理解了 也就基本上掌握了Spring.
40.Spring AOP与IOC
一、 IoC(Inversion of control): 控制反转
1、IoC:
概念:控制权由对象本身转向容器;由容器根据配置文件去创建实例并创建各个实例之间的依赖关系
核心:bean工厂;在Spring中,bean工厂创建的各个实例称作bean
二、AOP(Aspect-Oriented Programming): 面向方面编程
1、 代理的两种方式:
静态代理:
针对每个具体类分别编写代理类;
针对一个接口编写一个代理类;
动态代理:
针对一个方面编写一个InvocationHandler,然后借用JDK反射包中的Proxy类为各种接口动态生成相应的代理类
41.Spring 优点
它是一个开源的项目,而且目前非常活跃;它基于IoC(Inversion of Control,反向控制)和AOP的构架多层j2ee系统的框架,但它不强迫你必须在每一层 中必须使用Spring,因为它模块化的很好,允许你根据自己的需要选择使用它的某一个模块;它实现了很优雅的MVC,对不同的数据访问技术提供了统一的 接口,采用IoC使得可以很容易的实现bean的装配,提供了简洁的AOP并据此实现Transcation Managment,等等
优点
a. Spring能有效地组织你的中间层对象,不管你是否选择使用了EJB。如果你仅仅使用了Struts或其他为J2EE的 API特制的framework,Spring致力于解决剩下的问题。
b. Spring能消除在许多工程中常见的对Singleton的过多使用。根据我的经验,这是一个很大的问题,它降低了系统的可测试性和面向对象的程度。
c. 通过一种在不同应用程序和项目间一致的方法来处理配置文件,Spring能消除各种各样自定义格式的属性文件的需要。曾经对某个类要寻找的是哪个魔法般的属性项或系统属性感到不解,为此不得不去读Javadoc甚至源编码?有了Spring,你仅仅需要看看类的JavaBean属性。Inversion of Control的使用(在下面讨论)帮助完成了这种简化。
d. 通过把对接口编程而不是对类编程的代价几乎减少到没有,Spring能够促进养成好的编程习惯。
e. Spring被设计为让使用它创建的应用尽可能少的依赖于他的APIs。在Spring应用中的大多数业务对象没有依赖于Spring。
f. 使用Spring构建的应用程序易于单元测试。
g. Spring能使EJB的使用成为一个实现选择,而不是应用架构的必然选择。你能选择用POJOs或local EJBs来实现业务接口,却不会影响调用代码。
h. Spring帮助你解决许多问题而无需使用EJB。Spring能提供一种EJB的替换物,它们适用于许多web应用。例如,Spring能使用AOP提供声明性事务管理而不通过EJB容器,如果你仅仅需要与单个数据库打交道,甚至不需要一个JTA实现。
i. Spring为数据存取提供了一个一致的框架,不论是使用的是JDBC还是O/R mapping产品(如Hibernate)。
42.LinkedHashMap和hashMap的区别
LinkedHashMap继承于HashMap,底层基于HashMap和双向链表来实现的。
HashMap无序,LinkedHashMap有序,可分为插入顺序和访问顺序两种。
如果是访问顺序,那put和get操作已经存在的Entry时都会把Entry移动到双向链表的标为(先删除再插入)。遍历时按照插入顺序排序,原因在于LinkedHashMap的内部类LinkedHashIterator,执行Iterator.next访问链表的下一个元素,所以可以按照插入顺序的输出。
LinkedHashMap存储数据,还是跟HashMap一样使用Entry的方式,双向链表只是为了保证顺序。
HashMap的遍历速度和他的容量有关,而LinkedHashMap只跟实际数量有关。
LinkedHashMap按照插入顺序排序,HashMap基于哈希表乱序
43.dubbo 熔断,限流,降级
1 写在前面
1.1 名词解释
consumer表示服务调用方
provider标示服务提供方,dubbo里面一般就这么讲。
下面的A调用B服务,一般是泛指调用B服务里面的一个接口。
1.2 拓扑图
大写字母表示不同的服务,后面的序号表示同一个服务部署在不同机器的实例。
2 从微观角度思考
2.1 超时(timeout)
在接口调用过程中,consumer调用provider的时候,provider在响应的时候,有可能会慢,如果provider 10s响应,那么consumer也会至少10s才响应。如果这种情况频度很高,那么就会整体降低consumer端服务的性能。
这种响应时间慢的症状,就会像一层一层波浪一样,从底层系统一直涌到最上层,造成整个链路的超时。
所以,consumer不可能无限制地等待provider接口的返回,会设置一个时间阈值,如果超过了这个时间阈值,就不继续等待。
这个超时时间选取,一般看provider正常响应时间是多少,再追加一个buffer即可。
2.2 重试(retry)
超时时间的配置是为了保护服务,避免consumer服务因为provider 响应慢而也变得响应很慢,这样consumer可以尽量保持原有的性能。
但是也有可能provider只是偶尔抖动,那么超时后直接放弃,不做后续处理,就会导致当前请求错误,也会带来业务方面的损失。
那么,对于这种偶尔抖动,可以在超时后重试一下,重试如果正常返回了,那么这次请求就被挽救了,能够正常给前端返回数据,只不过比原来响应慢一点。
重试时的一些细化策略:
重试可以考虑切换一台机器来进行调用,因为原来机器可能由于临时负载高而性能下降,重试会更加剧其性能问题,而换一台机器,得到更快返回的概率也更大一些。
2.2.1 幂等(idempotent)
如果允许consumer重试,那么provider就要能够做到幂等。
即,同一个请求被consumer多次调用,对provider产生的影响(这里的影响一般是指某些写入相关的操作) 是一致的。
而且这个幂等应该是服务级别的,而不是某台机器层面的,重试调用任何一台机器,都应该做到幂等。
2.3 熔断(circuit break)
重试是为了应付偶尔抖动的情况,以求更多地挽回损失。
可是如果provider持续的响应时间超长呢?
如果provider是核心路径的服务,down掉基本就没法提供服务了,那我们也没话说。 如果是一个不那么重要的服务,却因为这个服务一直响应时间长导致consumer里面的核心服务也拖慢,那么就得不偿失了。
单纯超时也解决不了这种情况了,因为一般超时时间,都比平均响应时间长一些,现在所有的打到provider的请求都超时了,那么consumer请求provider的平均响应时间就等于超时时间了,负载也被拖下来了。
而重试则会加重这种问题,使consumer的可用性变得更差。
因此就出现了熔断的逻辑,也就是,如果检查出来频繁超时,就把consumer调用provider的请求,直接短路掉,不实际调用,而是直接返回一个mock的值。
等provider服务恢复稳定之后,重新调用。
2.3.1 简单的熔断处理逻辑
目前我们框架有通过注解使用的熔断器,大家可以参考应用在项目中。
2.4 限流(current limiting)
上面几个策略都是consumer针对provider出现各种情况而设计的。
而provider有时候也要防范来自consumer的流量突变问题。
这样一个场景,provider是一个核心服务,给N个consumer提供服务,突然某个consumer抽风,流量飙升,占用了provider大部分机器时间,导致其他可能更重要的consumer不能被正常服务。
所以,provider端,需要根据consumer的重要程度,以及平时的QPS大小,来给每个consumer设置一个流量上线,同一时间内只会给A consumer提供N个线程支持,超过限制则等待或者直接拒绝。
2.4.1 资源隔离
provider可以对consumer来的流量进行限流,防止provider被拖垮。
同样,consumer 也需要对调用provider的线程资源进行隔离。 这样可以确保调用某个provider逻辑不会耗光整个consumer的线程池资源。
2.4.2 服务降级
降级服务既可以代码自动判断,也可以人工根据突发情况切换。
2.4.2.1 consumer 端
consumer 如果发现某个provider出现异常情况,比如,经常超时(可能是熔断引起的降级),数据错误,这是,consumer可以采取一定的策略,降级provider的逻辑,基本的有直接返回固定的数据。
2.4.2.2 provider 端
当provider 发现流量激增的时候,为了保护自身的稳定性,也可能考虑降级服务。
比如,1,直接给consumer返回固定数据,2,需要实时写入数据库的,先缓存到队列里,异步写入数据库。
3 从宏观角度重新思考
宏观包括比A -> B 更复杂的长链路。
长链路就是 A -> B -> C -> D这样的调用环境。
而且一个服务也会多机部署,A 服务实际会存在 A1,A2,A3 …
微观合理的问题,宏观未必合理。
下面的一些讨论,主要想表达的观点是:如果系统复杂了,系统的容错配置要整体来看,整体把控,才能做到更有意义。
3.1 超时
如果A给B设置的超时时间,比B给C设置的超时时间短,那么肯定不合理把,A超时时间到了直接挂断,B对C支持太长超时时间没意义。
R表示服务consumer自身内部逻辑执行时间,TAB表示consumer A开始调用provider B到返回的时间 。
那么那么TAB > RB + TBC 才对。
3.2 重试
重试跟超时面临的问题差不多。
B服务一般100ms返回,所以A就给B设置了110ms的超时,而B设置了对C的一次重试,最终120ms正确返回了,但是A的超时时间比较紧,所以B对C的重试被白白浪费了。
A也可能对B进行重试,但是由于上一条我们讲到的,可能C确实性能不好,每次B重试一下就OK,但是A两次重试其实都无法正确的拿到结果。
N标示设置的重试次数
修正一下上面section的公式,TAB > RB+TBC * N。
虽然这个公式本身没什么问题,但是,如果站在长链路的视角来思考,我们需要整体规划每个服务的超时时间和重试次数,而不是仅仅公式成立即可。
比如下面情况:
A -> B -> C。
RB = 100ms,TBC=10ms
B是个核心服务,B的计算成本特别大,那么A就应该尽量给B长一点的超时时间,而尽量不要重试调用B,而B如果发现C超时了,B可以多调用几次C,因为重试C成本小,而重试B成本则很高。 so …
3.3 熔断
A -> B -> C,如果C出现问题了,那么B熔断了,则A就不用熔断了。
3.4 限流
B只允许A以QPS<=5的流量请求,而C却只允许B以QPS<=3的qps请求,那么B给A的设定就有点大,上游的设置依赖下游。
而且限流对QPS的配置,可能会随着服务加减机器而变化,最好是能在集群层面配置,自动根据集群大小调整。
3.5 服务降级
服务降级这个问题,如果从整体来操作,
1,一定是先降级优先级地的接口,两权相害取其轻
2,如果服务链路整体没有性能特别差的点,比如就是外部流量突然激增,那么就从外到内开始降级。
3如果某个服务能检测到自身负载上升,那么可以从这个服务自身做降级。
3.6 涟漪
A -> B -> C,如果C服务出现抖动,而B没有处理好这个抖动,造成B服务也出现了抖动,A调用B的时候,也会出现服务抖动的情况。
这个暂时的不可用状态就想波浪一样从底层传递到了上层。
所以,从整个体系的角度来看,每个服务一定要尽量控制住自己下游服务的抖动,不要让整个体系跟着某个服务抖动。
3.7 级联失败(cascading failure)
系统中有某个服务出现故障,不可用,传递性地导致整个系统服务不可用的问题。
跟上面涟漪(自造词)的区别也就是严重性的问题。
涟漪描述服务偶发的不稳定层层传递,而级联失败基本是导致系统不可用。 一般,前者可能会因为短时间内恢复而未引起重视,而后者一般会被高度重视。
3.8 关键路径
关键路径就是,你的服务想正常工作,必须要完整依赖的下游服务链,比如数据库一般就是关键路径里面的一个节点。
尽量减少关键路径依赖的数量,是提高服务稳定性的一个措施。
数据库一般在服务体系的最底层,如果你的服务可以会自己完整缓存使用的数据,解除数据库依赖,那么数据库挂掉,你的服务就暂时是安全的。
3.9 最长路径
想要优化你的服务的响应时间,需要看服务调用逻辑里面的最长路径,只有缩短最长时间路径的用时,才能提高你的服务的性能
44.线程池
线程池是一种多线程处理形式,处理过程中将任务加入到队列,然后在创建线程后自己主动启动这些任务。线程池线程都是后台线程。每一个线程都使用默认的堆栈大小,以默认的优先级执行。并处于多线程单元中。
假设某个线程在托管代码中空暇(如正在等待某个事件),则线程池将插入还有一个辅助线程来使全部处理器保持繁忙。
假设全部线程池线程都始终保持繁忙,但队列中包括挂起的工作,则线程池将在一段时间后创建还有一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程能够排队,但他们要等到其它线程完毕后才启动
45.如何避免内存泄漏、溢出的几种常用方法
尽早释放无用对象的引用。
好的办法是使用临时变量的时候,让引用变量在退出活动域后自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。
程序进行字符串处理时,尽量避免使用String,而应使用StringBuffer。
因为每一个String对象都会独立占用内存一块区域
尽量少用静态变量。
因为静态变量是全局的,GC不会回收。
避免集中创建对象尤其是大对象,如果可以的话尽量使用流操作。
JVM会突然需要大量内存,这时会触发GC优化系统内存环境
尽量运用对象池技术以提高系统性能。
生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。
不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。
可以适当的使用hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃。
优化配置。
- 设置-Xms、-Xmx相等;
- 设置NewSize、MaxNewSize相等;
- 设置Heap size, PermGen space;
46.mysql索引的数据结构
mysql索引的数据结构是树,常用的存储引擎innodb采用的是B+Tree。
B+树是B-树变体,相对于B-树,叶子结点的值包含了所有的值,所有父结点的值是重复了叶子结点的值,
父结点只起索引查找的作用,同时所叶子结点也也构成了一条有序的链表。
mysql中存储引擎为innodb的索引,采用的数据结构即是B+树。
特点:
a)有m个子结点的父结点就有m个关键字;
b)所有叶子结点包含了所有关键字(值),且构成由小到大的有序链表;
c) 所有非叶子结点起索引作用,结点仅包含子树所有结点的最大值;
d)所有叶子结点都在同一层;
注意:叶子结点包含了所有的关键字(值)。
47.什么情况下数据库索引会失效
1、like查询以“%”开头;
2、or语句前后没有同时使用索引;
3、组合索引中不是使用第一列索引;
4、在索引列上使用“IS NULL”或“IS NOT NULL”操作;
5、在索引字段上使用“not”,“<>”,“!=”等等。
48.redis的过期策略
redis过期策略以及内存淘汰机制:
redis主要基于内存来进行高性能的高并发的读写操作。
redis的过期策略是定期删除+惰性删除
过期删除:redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除,这里需要明确是redis每隔100ms检查的不是所有的key而是随机抽取了一些,假设我们再redis里边放了10w个key,都设置了过期时间,如果每隔几百毫秒检查10w个key,那redis基本上是卡死了,cpu负载会很高切都消耗在检查过期的key上了,所以呢这里是随机抽取一些key来检查
那么其实这样就有另外一个问题了,如果这个key过期了,但是到了时间并没有被删除掉,那这个时候就引入了另外一个概念,惰性删除。
惰性删除:
当你在获取某个key的时候,redis会检查一下,这个key如果已经到达过期时间,那么就什么都不返回,
但是这个又引发了另外一个问题,如果过期删除漏掉了很多已经过期的key,那么这些key一直占用这个内存,那么这个时候怎么办? 这时候可以走内存淘汰机制
内存淘汰机制:
redis 内存淘汰机制有以下几个:
• noeviction: 默认淘汰机制,不会进行淘汰,当内存不足以容纳新写入数据时,新写入操作会报错。
• allkeys-lru:当内存不足以容纳新写入数据时,设置时间戳来记录最近一次的访问时间。在第一次淘汰数据时,会先随机选择 N 个数据作为一个候选集合,然后淘汰 lru 值最小的。后续再淘汰数据时,会挑选数据进入候选集合,进入集合的条件是:它的 lru 小于候选集合中最小的 lru。如果候选集合中数据个数达到了 maxmemory-samples,Redis 就会将 lru 值小的数据淘汰出去。
• allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key。
• volatile-lfu:当内存不足以容纳新写入数据时,基于访问次数来淘汰数据,每一个数据增加一个计数器,来记录访问次数,在键空间中,移除最近最少使用的 key。
• volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
• volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
49.java中有几种类型的流?
jdk为每种类型的流提供了一些抽象类以供继承,请说出它们分别是什么?
解题思路:了解io流的体系(重要)就可以了
从大的方面来分可以分为字节流和字符流.字符流提供了提供了reader和writer;字节流提供了outputstream 和inputstream.
50.字符流和字节流有什么区别?
从读写的原理上来进行区分会好记一点(字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串;而字节流处理单元为 1 个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,如果是音频文件、图片、歌曲,就用字节流好点(避免数据丢失);如果是关系到中文(文本)的,用字符流好点)
字符流以字符或者字符数组的形式读写数据,只能读写二进制文件;字节流能读写各种类型的数据.
51.什么是java序列化,如何实现java序列化
像这样的题,一般先做名词解释,然后阐述作用和使用方法
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化(将对象转换成二进制)。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
注解:当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
将需要序化的类实现Serializable接口就可以了,该接口没有任何方法,可以理解为一个标记,即表明这个类可以序列化。注意的是被关键字static、transient修饰的变量不能被序列化。在被序列化后,transient修饰的变量会被设为初始值。如int型的是0、对象型的是null.
1.概念
序列化:把Java对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。
2.用途
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
52.什么是比特(Bit),什么是字节(Byte),什么是字符(Char),它们长度是多少,各有什么区别(一般是笔试题的选择题里面出的多一点)
Bit是最小的传输单位,byte是最小的存储单位,1byte=8bit,char 是一种基本数据类型,1char=2byte
53.BufferedReader属于哪种流,它主要是用来做什么的,它里面有那些经典的方法
望文知意,Reader是字符流,而buffer是缓冲的作用,缓冲区是基于内存的,起到读写高效的作用;所以BufferedReader是高效字符流
BufferedReader是字符流,也是一种包装流,用来增强reader流.主要用来读取数据的,最经典的方法是readline,可以一次读一行,是reader不具备的.
54.谈一谈io流中用到的适配器模式和装饰者模式
解题思路:首先,要知道装饰者模式和适配器模式的作用;其次,可以自己举个例子把它的作用生动形象地讲出来;最后,简要说一下要完成这样的功能需要什么样的条件。
装饰器模式:就是动态地给一个对象添加一些额外的职责(对于原有功能的扩展)。
1.它必须持有一个被装饰的对象(作为成员变量)。
2.它必须拥有与被装饰对象相同的接口(多态调用、扩展需要)。
3.它可以给被装饰对象添加额外的功能。
比如,在io流中,FilterInputStream类就是装饰角色,它实现了InputStream类的所有接口,并持有InputStream的对象实例的引用,BufferedInputStream是具体的装饰器实现者,这个装饰器类的作用就是使得InputStream读取的数据保存在内存中,而提高读取的性能。
适配器模式:将一个类的接口转换成客户期望的另一个接口,让原本不兼容的接口可以合作无间。
1.适配器对象实现原有接口
2.适配器对象组合一个实现新接口的对象
3.对适配器原有接口方法的调用被委托给新接口的实例的特定方法(重写旧接口方法来调用新接口功能。)
比如,在io流中, InputStreamReader类继承了Reader接口,但要创建它必须在构造函数中传入一个InputStream的实例,InputStreamReader的作用也就是将InputStream适配到Reader。 InputStreamReader实现了Reader接口,并且持有了InputStream的引用。这里,适配器就是InputStreamReader类,而源角色就是InputStream代表的实例对象,目标接口就是Reader类。
适配器模式主要在于将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的;而装饰器模式不是要改变被装饰对象的接口,而是保持原有的接口,但是增强原有对象的功能,或改变原有对象的方法而提高性能。
55.spring cloud和dubbo的区别
两者都是现在主流的微服务框架,但却存在不少差异:
初始定位不同:SpringCloud定位为微服务架构下的一站式解决方案;Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用和治理
生态环境不同:SpringCloud依托于Spring平台,具备更加完善的生态体系;而Dubbo一开始只是做RPC远程调用,生态相对匮乏,现在逐渐丰富起来。
调用方式:SpringCloud是采用Http协议做远程调用,接口一般是Rest风格,比较灵活;Dubbo是采用Dubbo协议,接口一般是Java的Service接口,格式固定。但调用时采用Netty的NIO方式,性能较好。
组件差异比较多,例如SpringCloud注册中心一般用Eureka,而Dubbo用的是Zookeeper
SpringCloud生态丰富,功能完善,更像是品牌机,Dubbo则相对灵活,可定制性强,更像是组装机。相关资料:
SpringCloud:Spring公司开源的微服务框架,SpirngCloud 定位为微服务架构下的一站式解决方案。
Dubbo:阿里巴巴开源的RPC框架,Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断
SpringCloudAlibaba
两者的生态对比:
功能 Dubbo SpringCloud
服务注册中心 Zookeeper Eureka(主流)、Consul、zookeeper
服务调用方式 RPC基于Dubbo协议 REST API 基于Http协议
服务监控 Dubbo-Monitor Spring Boot Admin
熔断器 不完善 Spring Cloud Netflix Hystrix
服务网关 无 Spring Cloud Netflix Zuul、Gateway
分布式配置 无 Spring Cloud Config
服务跟踪 无 Spring Cloud Sleuth+Zipkin(一般)
数据流 无 Spring Cloud Stream
批量任务 无 Spring Cloud Task
信息总线 无 Spring Cloud Bus
Spring Cloud 的功能很明显比 Dubbo 更加强大,涵盖面更广,而且作为 Spring 的旗舰项目,它也能够与 Spring Framework、Spring Boot、Spring Data、Spring Batch 等其他 Spring 项目完美融合,这些对于微服务而言是至关重要的。
使用 Dubbo 构建的微服务架构就像组装电脑,各环节选择自由度很高,但是最终结果很有可能因为一条内存质量不行就点不亮了,总是让人不怎么放心,但是如果使用者是一名高手,那这些都不是问题。
而 Spring Cloud 就像品牌机,在 Spring Source 的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础原理有足够的了解。
56. 主键 超键 候选键 外键
主 键:
数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。
超 键:
在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。
候选键:
是最小超键,即没有冗余元素的超键。
外 键:
在一个表中存在的另一个表的主键称此表的外键。
57.数据库事务的四个特性及含义
数据库事务transanction正确执行的四个基本要素。ACID,原子性(Atomicity)、一致性(Correspondence)、隔离性(Isolation)、持久性(Durability)。
原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
隔离性:隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行 相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请 求,使得在同一时间仅有一个请求用于同一数据。
持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
58.视图的作用,视图可以更改么?
视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们。
视图不能被索引,也不能有关联的触发器或默认值,如果视图本身内有order by 则对视图再次order by将被覆盖。
创建视图:create view XXX as XXXXXXXXXXXXXX;
对于某些视图比如未使用联结子查询分组聚集函数Distinct Union等,是可以对其更新的,对视图的更新将对基表进行更新;但是视图主要用于简化检索,保护数据,并不用于更新,而且大部分视图都不可以更新。
59.drop,delete与truncate的区别
drop直接删掉表 truncate删除表中数据,再插入时自增长id又从1开始 delete删除表中数据,可以加where字句。
(1) DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。
(2) 表和索引所占空间。当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,而DELETE操作不会减少表或索引所占用的空间。drop语句将表所占用的空间全释放掉。
(3) 一般而言,drop > truncate > delete
(4) 应用范围。TRUNCATE 只能对TABLE;DELETE可以是table和view
(5) TRUNCATE 和DELETE只删除数据,而DROP则删除整个表(结构和数据)。
(6) truncate与不带where的delete :只删除数据,而不删除表的结构(定义)drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger)索引(index);依赖于该表的存储过程/函数将被保留,但其状态会变为:invalid。
(7) delete语句为DML(data maintain Language),这个操作会被放到 rollback segment中,事务提交后才生效。如果有相应的 tigger,执行的时候将被触发。
(8) truncate、drop是DLL(data define language),操作立即生效,原数据不放到 rollback segment中,不能回滚
(9) 在没有备份情况下,谨慎使用 drop 与 truncate。要删除部分数据行采用delete且注意结合where来约束影响范围。回滚段要足够大。要删除表用drop;若想保留表而将表中数据删除,如果于事务无关,用truncate即可实现。如果和事务有关,或老师想触发trigger,还是用delete。
(10) Truncate table 表名 速度快,而且效率高,因为:
truncate table 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。
(11) TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。
(12) 对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。
60.索引的工作原理及其种类
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。
图展示了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在O(log2n)的复杂度内获取到相应数据。
创建索引可以大大提高系统的性能。
第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
也许会有人要问:增加索引有如此多的优点,为什么不对表中的每一个列创建一个索引呢?因为,增加索引也有许多不利的方面。
第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
索引是建立在数据库表中的某些列的上面。在创建索引的时候,应该考虑在哪些列上可以创建索引,在哪些列上不能创建索引。一般来说,应该在这些列上创建索引:在经常需要搜索的列上,可以加快搜索的速度;在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度;在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。
同样,对于有些列不应该创建索引。一般来说,不应该创建索引的的这些列具有下列特点:
第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
第二,对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
第三,对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
第四,当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。
根据数据库的功能,可以在数据库设计器中创建三种索引:唯一索引、主键索引和聚集索引。
唯一索引
唯一索引是不允许其中任何两行具有相同索引值的索引。
当现有数据中存在重复的键值时,大多数数据库不允许将新创建的唯一索引与表一起保存。数据库还可能防止添加将在表中创建重复键值的新数据。例如,如果在employee表中职员的姓(lname)上创建了唯一索引,则任何两个员工都不能同姓。 主键索引 数据库表经常有一列或列组合,其值唯一标识表中的每一行。该列称为表的主键。 在数据库关系图中为表定义主键将自动创建主键索引,主键索引是唯一索引的特定类型。该索引要求主键中的每个值都唯一。当在查询中使用主键索引时,它还允许对数据的快速访问。 聚集索引 在聚集索引中,表中行的物理顺序与键值的逻辑(索引)顺序相同。一个表只能包含一个聚集索引。
如果某索引不是聚集索引,则表中行的物理顺序与键值的逻辑顺序不匹配。与非聚集索引相比,聚集索引通常提供更快的数据访问速度。
局部性原理与磁盘预读
由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。
由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。
预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。
B-/+Tree索引的性能分析
到这里终于可以分析B-/+Tree索引的性能了。
上文说过一般使用磁盘I/O次数评价索引结构的优劣。先从B-Tree分析,根据B-Tree的定义,可知检索一次最多需要访问h个节点。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧:
每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。
B-Tree中一次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3)。
而红黑树这种结构,h明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的I/O渐进复杂度也为O(h),效率明显比B-Tree差很多。
综上所述,用B-Tree作为索引结构效率是非常高的。
58.连接的种类
查询分析器中执行:
--建表table1,table2:
create table table1(id int,name varchar(10))
create table table2(id int,score int)
insert into table1 select 1,'lee'
insert into table1 select 2,'zhang'
insert into table1 select 4,'wang'
insert into table2 select 1,90
insert into table2 select 2,100
insert into table2 select 3,70
如表
-------------------------------------------------
table1 | table2 |
-------------------------------------------------
id name |id score |
1 lee |1 90|
2 zhang| 2 100|
4 wang| 3 70|
-------------------------------------------------
以下均在查询分析器中执行
一、外连接
1.概念:包括左向外联接、右向外联接或完整外部联接
2.左连接:left join 或 left outer join
(1)左向外联接的结果集包括 LEFT OUTER 子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值(null)。
(2)sql 语句
select * from table1 left join table2 on table1.id=table2.id
-------------结果-------------
idnameidscore
------------------------------
1lee190
2zhang2100
4wangNULLNULL
------------------------------
注释:包含table1的所有子句,根据指定条件返回table2相应的字段,不符合的以null显示
3.右连接:right join 或 right outer join
(1)右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。
(2)sql 语句
select * from table1 right join table2 on table1.id=table2.id
-------------结果-------------
idnameidscore
------------------------------
1lee190
2zhang2100
NULLNULL370
------------------------------
注释:包含table2的所有子句,根据指定条件返回table1相应的字段,不符合的以null显示
4.完整外部联接:full join 或 full outer join
(1)完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。
(2)sql 语句
select * from table1 full join table2 on table1.id=table2.id
-------------结果-------------
idnameidscore
------------------------------
1lee190
2zhang2100
4wangNULLNULL
NULLNULL370
------------------------------
注释:返回左右连接的和(见上左、右连接)
二、内连接
1.概念:内联接是用比较运算符比较要联接列的值的联接
2.内连接:join 或 inner join
3.sql 语句
select * from table1 join table2 on table1.id=table2.id
-------------结果-------------
idnameidscore
------------------------------
1lee190
2zhang2100
------------------------------
注释:只返回符合条件的table1和table2的列
4.等价(与下列执行效果相同)
A:select a.*,b.* from table1 a,table2 b where a.id=b.id
B:select * from table1 cross join table2 where table1.id=table2.id (注:cross join后加条件只能用where,不能用on)
三、交叉连接(完全)
1.概念:没有 WHERE 子句的交叉联接将产生联接所涉及的表的笛卡尔积。第一个表的行数乘以第二个表的行数等于笛卡尔积结果集的大小。(table1和table2交叉连接产生3*3=9条记录)
2.交叉连接:cross join (不带条件where...)
3.sql语句
select * from table1 cross join table2
-------------结果-------------
idnameidscore
------------------------------
1lee190
2zhang190
4wang190
1lee2100
2zhang2100
4wang2100
1lee370
2zhang370
4wang370
------------------------------
注释:返回3*3=9条记录,即笛卡尔积
4.等价(与下列执行效果相同)
A:select * from table1,table2
59.数据库范式
1 第一范式(1NF)
在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。
所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,第一范式就是无重复的列。
2 第二范式(2NF)
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。
第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是非主属性非部分依赖于主关键字。
3 第三范式(3NF)
满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余)
60.数据库优化的思路
这个我借鉴了慕课上关于数据库优化的课程。
1.SQL语句优化
1)应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
2)应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
3)很多时候用 exists 代替 in 是一个好的选择
4)用Where子句替换HAVING 子句 因为HAVING 只会在检索出所有记录之后才对结果集进行过滤
2.索引优化
尽量避免索引失效,如果关键字段比较多,可以用联合索引。
3.数据库结构优化
1)范式优化: 比如消除冗余(节省空间。。) 2)反范式优化:比如适当加冗余等(减少join) 3)拆分表: 分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。
4)拆分其实又分垂直拆分和水平拆分: 案例: 简单购物系统暂设涉及如下表: 1.产品表(数据量10w,稳定) 2.订单表(数据量200w,且有增长趋势) 3.用户表 (数据量100w,且有增长趋势) 以mysql为例讲述下水平拆分和垂直拆分,mysql能容忍的数量级在百万静态数据可以到千万 垂直拆分:解决问题:表与表之间的io竞争 不解决问题:单表中数据量增长出现的压力 方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上 水平拆分: 解决问题:单表中数据量增长出现的压力 不解决问题:表与表之间的io争夺
方案: 用户表通过性别拆分为男用户表和女用户表 订单表通过已完成和完成中拆分为已完成订单和未完成订单 产品表 未完成订单放一个server上 已完成订单表盒男用户表放一个server上 女用户表放一个server上(女的爱购物 哈哈)
61.存储过程与触发器的区别
触发器与存储过程非常相似,触发器也是SQL语句集,两者唯一的区别是触发器不能用EXECUTE语句调用,而是在用户执行Transact-SQL语句时自动触发(激活)执行。触发器是在一个修改了指定表中的数据时执行的存储过程。通常通过创建触发器来强制实现不同表中的逻辑相关数据的引用完整性和一致性。由于用户不能绕过触发器,所以可以用它来强制实施复杂的业务规则,以确保数据的完整性。触发器不同于存储过程,触发器主要是通过事件执行触发而被执行的,而存储过程可以通过存储过程名称名字而直接调用。当对某一表进行诸如UPDATE、INSERT、DELETE这些操作时,SQLSERVER就会自动执行触发器所定义的SQL语句,从而确保对数据的处理必须符合这些SQL语句所定义的规则。