SpringBoot启动时控制台输出:
Class.forName(com.mysql.jdbc.Driver); //加载驱动
Connection con = DriverManager.getConnection(“jdbc:mysql://…”); //创建与mysql中某个数据库的连接
String sq = “delete from table1 where id=? and name=?”//构建sql语句,以?作为占位符,这个位置的值待设置
PreparedStatement ps = con.prepareStatement(sq); //创建PreparedStatement时就传入sql语句,实现了预编译
ps.setString(1,“03”); //设置sql语句的占位符的值,注意第一个参数位置是1不是0
ps.setString(2,“mao”); //设置占位符的值还可以通过setObject(int,object)完成
ps.execute(); //执行这个PreparedStatement

实际开发中,一般采用PreparedStatement访问数据库,它不仅能防止sql注入,还是预编译的(不用改变一次参数就要重新编译整个sql语句,效率高),此外,它执行查询语句得到的结果集是离线的,连接关闭后,仍然可以访问结果集。

String param = “‘test’ or 1=1”;
String sql = “select file from file where name = ?”;
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, param);
ResultSet resultSet = preparedStatement.executeQuery();
System.out.println(resultSet.next());
输出结果为false,DB中执行的SQL为
select file from file where name = ‘‘test’ or 1=1’
我们可以看到输出的SQL文是把整个参数用引号包起来,并把参数中的引号作为转义字符,从而避免了参数也作为条件的一部分

public interface test {
int age=18;//常量
//等价于 public static final int age=18;
}

首先你进入登录页面,输入用户名以及密码进行登录,Spring Security首先会进入UsernamePasswordAuthenticationFilter进行验证,如果认证成功它会调用一个叫RememberMeService的类,这个类会调用TokenRepository这个类,而这个类会将一个生成的Token写入数据库中。当你下次再进入该网站的时候,它会直接进入RemeberMeAuthenticationFilter这个过滤器中,通过Cookie读取之前的Token,然后拿着这个Token通过RememberService到数据库中查找用户信息,如果存在该Token,则取出该Token对应的用户名以及其他信息放入UserDetailService,然后进入调用的URL。

当我们重新启动项目访问页面时,由于session被销毁,但是cookie中存在Token,它会到RememberMeAuthenticationFilter中读取session中的值,由于是重启项目,session被销毁,因此session为空,它会去到cookie中取是否存在Token,遍历cookie获取到RememberMe的Token,然后根据Token获取用户信息返回调用的URL信息。

ASCII码
A:65
a:97
正则表达式:
+:匹配一个或多个
.:匹配一个
*:匹配0个或多个
?:匹配0个或1个
.:匹配.

创建新执行线程的2种方法:
一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建对象,开启线程。run方法相当于其他线程的main方法。
另一种方法是声明一个实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。
调用start方法,线程就绪,由jvm负责调度执行。
实现Runnable接口避免了单继承的局限性,所以较为常用。实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
线程的匿名内部类使用
Thread th=new Thread(){
public void run() {
System.out.println(Thread.currentThread().getName()+“run”);
};
};
th.start();

java.util.concurrent.ExecutorService;
java.util.concurrent.Executors;
java.util.concurrent.Future;
java.util.concurrent.Callable;

ExecutorService es=Executors.newFixedThreadPool(2);
es.submit(new MyRunnable());
es.shutdown();
或者
System.out.println(es.submit(new MyCallable()).get());//Callable有返回值
es.shutdown();

同步代码块: 在代码块声明上 加上synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

同步方法:在方法声明上加上synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}
synchronized 修饰方法时锁定的是调用该方法的对象。它并不能使调用该方法的多个对象在执行顺序上互斥。

ThreadLocal在每个线程中对变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题。
get() set(…) remove()
ThreadLocal是如何为每个线程创建变量的副本的:
  首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本。
  初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
  然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
1)实际通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)threadLocals是ThreadLocalMap类型的,键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,例如longLocal存储threadID,stringLocal存储threadname。
用于数据库连接 session共享
ThreadLocal可以让线程拥有本地变量,在web环境中,为了方便代码解耦,我们通常用它来保存上下文信息,然后用一个util类提供访问入口,从controller层到service层可以很方便的获取上下文。
为了避免后续逻辑中多次查询/请求缓存服务器, 拿到对象后放到线程本地变量中。
ThreadLocal的生命周期不等于一次request请求的生命周期,每个request请求的响应是tomcat从线程池中分配的线程, 线程会被下个请求复用,所以请求结束后必须删除线程本地变量。
子线程怎么拿到父线程的ThreadLocal数据?jdk给我们提供了解决办法,ThreadLocal有一个子类InheritableThreadLocal,创建ThreadLocal时候我们采用InheritableThreadLocal类可以实现子线程获取到父线程的本地变量。
在线程池的场景下,怎么能让“子线程”正常拿到父线程传递过来的变量呢?在创建子线程的时候主动传递过去。

连接池是缓存并托管数据库连接,主要是为了提高性能。而ThreadLocal缓存连接,是为了把同一个数据库连接“分享”给同一个线程的不同调用方法。(连接池跨线程,threadlocal是同一线程内)
即使不同时间,多个线程使用的是同一个数据库连接,因为threadlocal为每个线程创建独立的连接副本,则不会干扰数据库事务。

ThreadPoolExecutor线程池
优化线程的内存开销
执行顺序:
• 当线程数小于核心线程数时,创建线程。
• 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
• 当线程数大于等于核心线程数,且任务队列已满,若线程数小于最大线程数,创建线程。
• 若线程数等于最大线程数,则执行拒绝策略
核心线程数:
CPU密集型(CPU密集型也叫计算密集型,指的是运算较多,cpu占用高,读/写I/O(硬盘/内存)较少):corePoolSize = CPU核数 + 1
IO密集型(与cpu密集型相反,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。):corePoolSize = CPU核数 * 2
任务队列:
无界队列:LinkedBlockingQueue 将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。
有界队列:ArrayBlockingQueue,PriorityBlockingQueue 当线程池中线程达到corePoolSize时,新进任务被放在队列里排队等待处理。有助于防止资源耗尽,但是可能较难调整和控制,队列大小和最大池大小可能需要相互折衷。
同步移交队列:SynchronousQueue 直接提交,线程池不对任务进行缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建一个新的线程处理此任务。这种策略需要线程池具有无限增长的可能性

Handler拒绝策略:
AbortPolicy 丢弃任务,抛运行时异常。
CallerRunsPolicy 由当前调用的任务线程执行任务。
DiscardPolicy 忽视,什么都不会发生。
DiscardOldestPolicy 从队列中踢出最先进入队列的任务。

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
阻塞队列提供了四种处理方法:
方法\处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用
异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue full”)异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。

返回特殊值:插入方法会返回是否成功,成功则返回true。移除方法,则是从队列里拿出一个元素,如果没有则返回null
一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。
超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出。
ReentrantLock锁在同一个时间点只能被一个线程锁持有;而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取。
ReentrantLock分为“公平锁”和“非公平锁”。它们的区别体现在获取锁的机制上是否公平。“锁”是为了保护竞争资源,防止多个线程同时操作线程而出错,ReentrantLock在同一个时间点只能被一个线程获取(当某线程获取到“锁”时,其它线程就必须等待);ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。
ThreadPoolExecutor是Java的线程池
ThreadPoolTaskExecutor是spring封装的线程池

jdk自带的四种线程池创建方式

// 第一种线程池:固定个数的线程池,可以为每个CPU核绑定一定数量的线程数
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(processors * 2);
// 缓存线程池,无上限
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 单一线程池,永远会维护存在一条线程
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
// 固定个数的线程池,可定期或者延时执行任务。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

Mysql DEMICAL
column_name DECIMAL(P,D); 可以存储D位小数的P位数。MySQL分别为整数和小数部分分配存储空间。 MySQL使用二进制格式存储DECIMAL值。它将9位数字包装成4个字节。
DECIMAL(19,9)对于小数部分具有9位数字,对于整数部分具有19位= 10位数字,小数部分需要4个字节。 整数部分对于前9位数字需要4个字节,1个剩余字节需要1个字节。DECIMAL(19,9)列总共需要9个字节。

表的设计具体注意的问题:
1、数据行的长度不要超过8020字节,如果超过这个长度的话在物理页中这条数据会占用两行从而造成存储碎片,降低查询效率。
2、能够用数字类型的字段尽量选择数字类型而不用字符串类型的(电话号码),这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接回逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
3、对于不可变字符类型char和可变字符类型varchar 都是8000字节,char查询快,但是耗存储空间,varchar查询相对慢一些但是节省存储空间。在设计字段的时候可以灵活选择,例如用户名、密码等长度变化不大的字段可以选择CHAR,对于评论等长度变化大的字段可以选择VARCHAR。
4、字段的长度在最大限度的满足可能的需要的前提下,应该尽可能的设得短一些,这样可以提高查询的效率,而且在建立索引的时候也可以减少资源的消耗。

尽量不用自增字段作外键
均衡考虑数据冗余与关联查询
保证在实现功能的基础上,尽量减少对数据库的访问次数;通过搜索参数,尽量减少对表的访问行数,最小化结果集,从而减轻网络负担;能够分开的操作尽量分开处理,提高每次的响应速度;在数据窗口使用SQL时,尽量把使用的索引放在选择的首列;算法的结构尽量简单;在查询时,不要过多地使用通配符如SELECT * FROM T1语句,要用到几列就选择几列如:SELECT COL1,COL2 FROM T1;在可能的情况下尽量限制结果集行数如:SELECT TOP 300 COL1,COL2,COL3 FROM T1,因为某些情况下用户是不需要那么多的数据的。
尽量使语句符合查询优化器的规则避免全表扫描而使用索引查询。
尽量规范使用where子句,使用索引查询而不使用全表查询。
通过8个方法优化Mysql数据库:创建索引、复合索引、索引不会包含有NULL值的列、使用短索引、排序的索引问题、like语句操作、不要在列上进行运算、不使用NOT IN和<>操作
1、创建索引
对于查询占主要的应用来说,索引显得尤为重要。很多时候性能问题很简单的就是因为我们忘了添加索引而造成的,或者说没有添加更为有效的索引导致。如果不加索引的话,那么查找任何哪怕只是一条特定的数据都会进行一次全表扫描,如果一张表的数据量很大而符合条件的结果又很少,那么不加索引会引起致命的性能下降。但是也不是什么情况都非得建索引不可,比如性别可能就只有两个值,建索引不仅没什么优势,还会影响到更新速度,这被称为过度索引。
2、复合索引
比如有一条语句是这样的:select * from users where area=‘beijing’ and age=22;
如果我们是在area和age上分别创建单个索引的话,由于mysql查询每次只能使用一个索引,所以虽然这样已经相对不做索引时全表扫描提高了很多效率,但是如果在area、age两列上创建复合索引的话将带来更高的效率。如果我们创建了(area, age, salary)的复合索引,那么其实相当于创建了(area,age,salary)、(area,age)、(area)三个索引,这被称为最佳左前缀特性。因此我们在创建复合索引时应该将最常用作限制条件的列放在最左边,依次递减。
3、索引不会包含有NULL值的列
只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。
4、使用短索引
对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的 列,如果在前10 个或20 个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
5、排序的索引问题
mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。
6、like语句操作
一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。
7、不要在列上进行运算
select * from users where YEAR(adddate)<2007;
将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成
select * from users where adddate<‘2007-01-01’;
8、不使用NOT IN和<>操作
NOT IN和<>操作都不会使用索引将进行全表扫描。NOT IN可以NOT EXISTS代替,id<>3则可使用id>3 or id<3来代替。

通过分布式架构解决性能(高并发)问题,通过集群架构解决故障服务(高可用)问题。

Redis主要把数据存储在内存中,其“缓存”的性质远大于其“数据存储“的性质,其中数据的增删改查也只是像变量操作一样简单;
MongoDB却是一个“存储数据”的系统,增删改查可以添加很多条件,就像SQL数据库一样灵活。

Redis怎么做到数据不丢的?
1.持久化
RDB快照:是全量备份,在存储上是非常紧凑的二进制内存数据。父进程不受快照操作的阻塞。 COW(copy on write):将数据段分成N个数据页,当主进程修改任意一个页面数据的时候,将会把此页面从共享内存中复制一份分离出来,然后对复制出来的新页面进行修改,此时子进程的内存页是没有变化的。
• AOF日志:是增量备份,是内存数据修改的指令文本记录,它跟RDB有一个区别就是包含过期key,当key被惰性删除或者定期删除会在文件中追加一条del指令。AOF在长期运行中会比较庞大,定期会重写,而且备份回放AOF比快照回放慢得多。这里需要注意的一点是:Redis在收到客户端指令时会先将指令存储到AOF日志文件中,然后再执行指令。这样好处就是如果发生宕机已经持久化到AOF中的指令可以通过回放来恢复数据,防止数据意外丢失。当AOF随着时间越来越大的时候就会需要对其重写:Redis提供了bgrewriteaof指令对AOF进行重写,其原理就是开辟一个子线程对当前内存进行遍历转化成一个新的文本指令,生成一个新的AOF文件保存在操作系统cache中,操作系统再异步将操作系统cache中的数据写到磁盘,生成真正的AOF文件。生成新AOF文件结束后将在这期间发生的增量指令追加到AOF文件中,追加完成后立即使用最新AOF文件删除旧AOF文件。当Redis的AOF写入操作系统cache成功后未来得及刷盘就宕机的话,这部分数据是会丢失的。引入解决此问题的办法:操作系统函数fsync,它可以将操作系统cache中指定内容强制刷新到磁盘中。只要调用fsync执行成功就能保证AOF文件不丢失。(fsync三种方式,AOF_FSYNC_NO:不保存; AOF_FSYNC_EVERYSEC:每一秒钟保存一次;AOF_FSYNC_ALWAYS:每执行一个命令保存一次)
2.数据恢复
重启Redis时,rdbLoad函数就会被执行,它将读取RDB文件,并将RDB文件数据载入到内存中。但是我们很少用选择某一时间的RDB快照方式来恢复数据,因为这样会丢失很多数据,较好的选择就是通过AOF日志来恢复数据,但是回放AOF日志文件是相对很耗时的一个操作。我们可以采用手动RDB+AOF的方式恢复:选某一时间点的RDB文件恢复数据后,跟RDB最后生成时间通过运维工具去修改AOF日志文件,选择RDB文件时间之后的AOF指令保存为新的AOF文件回放(为了保证数据的完整性,一般会选择RDB时间前一点点)。到了Redis4.0, AOF不在是全量日志,而是自这次持久化开始到持久化结束时间发生的增量日志。这样就很大程度的提高了数据恢复的速度也减少了手工运维的烦恼。
3.主从同步
• 增量同步:Redis主节点会将自己存储在Buffer中的操作指令异步同步给从库,从节点收到同步成功指令后会像主节点上报自己同步到文件偏移量。因为Redis主库的Buffer使用的是环形数组数据存储结构,如果Buffer满了会从数组的头部开始覆盖写入,如果主从延迟过大,就会存在Buffer中的写入速度大于同步速度而导致指令丢失的可能。
• 快照同步:为了解决增量同步主从延迟数据丢失的问题引入了快照同步方式,这个过程较消耗性能。当发现增量同步有丢失数据的风险时,主库会fork一个子进程对主库做一次当前内存快照备份发送给从库。从节点接收到主库的RDB备份后,释放掉当前所有的数据,让回放RDB文件。回放完成后通知主库再进行增量同步。在整个快照过程中主节点的Buffer还在不停的前移,如果复制回放rdb文件的时间比Buffer前移的更慢的话,就会导致在RDB同步的过程中Buffer中未同步的指令又被覆盖则rdb同步失败,这样就会导致主库再次进入快照过程中,将新的Rdb文件同步给从库。这样就陷入一个死循环中,我们在平时设计中要考虑到主从延迟导致的Buffer堆积空间大小,给定一个包含网络延迟导致的Buffer堆积的合理大小,做到不低估,不浪费。
• 无盘复制:所谓无盘复制就是主节点通过Socket将快照发生到从节点,主节点一边遍历内存一边发送RDB文件内容,从节点将收到的完整的RDB文件存储到磁盘,再进行回放。
• 同步复制:Redis的主从复制大部分都是异步的,想要保证数据的强一致性,我们可以使用Redis提供的wait指令进行同步复制,假如将wait的等待时间设置为无限等待从库同步完成,那么当网络发生分区或者延迟较高的时候,就出现严重阻塞,影响Redis的可用性。
过期key的主从同步:需要注意的一点是在中从同步过程中对于过期key处理是不同的。有一条中原则就是:过期key统一由主节点删除。主节点在删除一条key时会显示的向所有从节点发送一条del指令,从节点在自己的内存遇到过期key时只需要向客户端返回过期,不做删除动作,等待主节点同步del来删除。

4.Sentinel
引入了哨兵架构模式的自动故障恢复,这样也同样因为引入新的组件导致系统复杂度跟着上升。首先Redis哨兵服务也需要单独部署,保证高可用。所以还需要引入分布式协调组件例如zookeeper等,这样就能保证Redis哨兵始终能够感知到Redis集群的状态,当然也就存在所有分布式架构存在问题。Redis哨兵负责监控主节点的健康,当主节点不可用时,会自动选择一个从节点切换为主节点。客户端在请求主节点时访问失败会通过Redis哨兵查询主节点的地址,成功后再将新的主节点列表缓存到客户端中。等故障主节点恢复后会作为一个新的只读从节点加入集群。这个只读状态很重要,如果是用户主动采取主从切换到的话,客户端从本地缓存中访问老的主库时会抛出ReadonlyErr异常,就会触发客户拉去新的主节点列表。以上除了引入哨兵的服务其他与主从架构如出一辙。我们下面来讲一下讲求性能的提示而演变的架构
5.RedisCluster
分布式集群存储方式RedisCluster,它的单个节点上不在是全量数据,而只含有整个集群的一份数据。这样既改善了存储空间浪费的问题,同时也增横向增加了Redis服务整体的吞吐性。RedisCluster将所有数据存储区域划分为16384个slots(槽位),每个节点负责一部分槽位,槽位的信息存储于每个节点中。当客户端请求进来时候会拉去一份槽位信息列表缓存在本地,RedisCluster的每个节点会将集群的配置信息持久化到自己的配置文件中,所以需要引入一套可维护的配置文件管理方案,尽量做到自动化。
• 槽位算法:RedisCluster 默认会根据key使用crc32算法进行hash得到一个整数,然后用这个整数对16384取模定位key所在的槽位。它还运行用户在key字符串里面嵌入tag将key强制写入指定的槽位。
• 迁移:当有新的节点加入或者断开节点时,就会触发Redis槽位迁移。当一个槽位正在迁移时候在原节点的状态为migrating,在目标节点的状态为importing。原节点的单个key执行dump指令得到序列化内容,再向目标节点发送restore携带序列化内容作为参数的指令,目标节点接收到内容后反序列化复制到内存中,响应给原节点成功。原节点收到成功响应后把当前节点的key删掉就完成了节点数据迁移。这个过程是一个同步的操作,在复制完成之前原节点时处于阻塞状态的,不会进入新的数据,直到原节点的key被删除完成。如果key内容过大就会导致迁移阻塞时间过长,出现卡顿现象,所以再次强调大key的危害。上面说完了在迁移过程中服务端的变化,现在我们来说一下槽位迁移对于客户端的变化:这时候新旧节点会同时存在部分key,客户端访问到旧节点,如果旧节点存在就正常处理返回。如果客户端访问的数据不在旧节点,它会向客户端发生一个重定向指令(-ASK targetNodeAddr),客户端收到重定向后,先去目标节点执行一个不带参数的asking指令,然后在目标节点执行操作。因为在没有完全迁移完槽位目标节点还不归新节点管理,如果只适合直接发生操作指令,目标节点会返回给客户端一个-MOVED重定向指令,让它去原节点执行,这样就出现了重定向循环。不带参的asking指令目的就是打开目标节点选项让它当做自己的槽位的请求来处理。通过上面的过程得知在迁移过程中,平时的一个指令需要三个ttl才能完成。
• 跳转:当RedisCluster发生槽位变化的数据迁移时,这时候客户端保存的槽位信息就和RedisCluster的槽位信息不一致,当客户端访问到错误的槽位时候,当前槽位会相应给客户端一个可能包含此数据的槽位信息,当客户端访问成功后更新本地槽位信息。
• 容错:RedisCluster为每个主节点设置了若干从节点,主节点故障时,集群会主动提升某个从节点作为主节点,当无主节点时Redis整个不可用。也可以通过 cluster-require-full-coverage参数设置允许部分节点故障,其他节点依然可以对完提供服务。实际在异常无处不在生成环境中突然部分节点变得不可用,间隔一会又突然好了是很常见的时候,为了解决这问题我们也通过设置容忍最大离线时间(cluster-node-timeout)来避免,当超过这个最大超时时间则认为节点不可用。还有一个作为被乘数系数来放大超时时间的参数:cluster-slave-validity-factor,当值为0的时候是不能容忍异常短暂离线,系数越大相对越宽松。RedisCluster作为一个去中心化的中间件,一个节点认为某个节点离线,叫可能离线,当所有或者当大多数节点认为某节点离线才叫真正离线,此时集群会剔除此节点或者触发主从切换。它是用Gossip协议来广播自己的状态以及对整个集群变化的感知。比如一个节点发现某节点离线,它会将这个信息向整个集群广播,其他节点也会受到这个信息,如果收到信息的节点发现目标节点状态正常则不更新这条信息,并发送目标节点正常的消息给整个集群,就这样一个个节点的传播消息。当整个集群半数以上节点都持有某目标节点已离线的的信息时候才认为,某目标节点离线的这一事实,否则不予处理。

Spring 核心组件 bean context core
Bean包裹的是Object,而Object中必然有数据,Context就是给这些数据提供生存环境,发现每个Bean之间的关系,为他们建立并维护好这种关系。这样来说,Context就是一个Bean关系的集合,这个关系集合就是我们所说的IOC容器。那么Core又有什么作用呢?Core就是发现、建立和维护每个Bean之间的关系所需的一系列工具,就是我们经常说的Util。
Bean组件在Spring的org.springframework.beans包下,主要完成了Bean的创建、Bean的定义以及Bean的解析三件事。
SpringBean的创建是典型的工厂模式,BeanFactory为顶级接口,BeanFactory的最终默认实现类是DefaultListableBeanFactory。
Bean的定义主要由BeanDefinition描述,其完整的描述了在Spring配置文件中定义的节点的所有信息,包括子节点。当一个节点被成功解析之后,在Spring内部它会被转化成一个BeanDefinition对象,接下来的所有操作都是对这个对象进行的。
Context类结构的顶级父类是ApplicationContext,它除了能标识一个应用环境的基本信息以外,还继承了5个接口(包括DefaultListableBeanFactory,ResourcePatternResolver接口——加载、解析和描述资源),这5个接口主要是扩展了Context的功能。ApplicationContext的子类主要包含两个方向,一是ConfigurableApplicationContext,表明context可修改,可以动态添加或修改已有配置,另一类是webapplicationcontext,为web准备的,可以直接访问servletcontext。再往下就是构建Context的文件类型,接着就是访问Context的方式。
Core组件一个重要的组成部分就是定义了资源的访问方式。Core组价把所有的资源都抽象成一个接口,这样,对于资源使用者来说,不需要考虑文件的类型。对资源提供者来说,也不需要考虑如何将资源包装起来交给别人使用(Core组件内所有的资源都可以通过InputStream类来获取)。另外,Core组件内资源的加载都是由ResourceLoader接口完成的,只要实现这个接口就可以加载所有的资源。

现在一般都用ApplicantContext代替BeanFactory

最详细的Spring核心IOC的源码分析(每次看都有不同的感悟)_Nuomizhende45-CSDN博客_spring源码分析
DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:
• 文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
• 通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
• 通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);
• 通过ViewResolver解析逻辑视图名到具体视图实现;
• 本地化解析;
• 渲染具体的视图等;
• 如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。

  1. spring data jpa实现了jpa(java persistence api)功能,即可以实现pojo转换为关系型数据库记录的功能,通俗来讲就是可以不写任何的建表sql语句了。jpa是spring data jpa功能的一个子集。
    而mybatis并没有jpa功能,建表语句还是要自己写的。

  2. spring data jpa是全自动框架,不需要写任何sql。而mybatis是半自动框架,需要自己写sql,mybatis-plus为mybatis赋能,使其也可以基本上不需要写任何模板sql。

  3. debug模式下看生成的sql,mybatis下的sql可读性很好,而spring data jpa下的查询sql可读性并不好。
    如spring data jpa的findOne(id)方法,执行的sql如下,看起来很奇怪,不是很直接。

  4. spring data jpa的insert与update都调用同一个方法save,如果带有主键id(如果启用了乐观锁,那么还有version字段),那么就是更新,否则就是新增,所以addOrUpdate是一个接口,而mybatis中提供insert方法和updateById方法。
    由于spring data jpa调用同一个方法,所以其要执行两条sql,先执行查询,再执行插入/更新。
    另外就是返回值,spring data jpa的返回值是Employee对象,而mybatis的返回值是影响的行数,当然mybatis也可以得到新增后的id,返回新增后的对象

  5. spring data jpa的dynamic sql是使用JpaSpecificationExecutor,而mybatis中是使用xml来构造dynamic sql

BeanFactory和FactoryBean的区别:
1.BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。
2. BeanFactory是负责生产和管理bean的一个工厂,是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。
FactoryBean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似 。一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean的形式。根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取(getBean(&id))。(当配置文件中的class属性配置的实现类是FactoryBean时,通过getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。)

中间件(狂神说RabbitMQ)_404の猫.℡的博客-CSDN博客_狂神说rabbitmq笔记

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO(NIO.2):异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
BIO
同步阻塞式IO,在while循环中服务端会调用accept方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成。
如果BIO要能够同时处理多个客户端请求,就必须使用多线程,即每次accept阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续accept等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理,大概原理图就像这样:

虽然此时服务器具备了高并发能力,即能够同时处理多个客户端请求了,但是却带来了一个问题,随着开启的线程数目增多,将会消耗过多的内存资源,导致服务器变慢甚至崩溃,NIO可以一定程度解决这个问题。
NIO
同步非阻塞式IO,关键是采用了事件驱动的思想来实现了一个多路转换器。
NIO与BIO最大的区别就是只需要开启一个线程就可以处理来自多个客户端的IO事件,这是怎么做到的呢?
就是多路复用器,可以监听来自多个客户端的IO事件:
A. 若服务端监听到客户端连接请求,便为其建立通信套接字(java中就是通道),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通信套接字。
B. 若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理。
C. 监听多个客户端的连接请求和接收数据请求同时还能监听自己时候有数据要发送。

总之就是在一个线程中就可以调用多路复用接口(java中是select)阻塞同时监听来自多个客户端的IO请求,一旦有收到IO请求就调用对应函数处理。
各自应用场景
一旦有请求到来(不管是几个同时到还是只有一个到),都会调用对应IO处理函数处理,所以:
(1)NIO适合处理连接数目特别多,但是连接比较短(轻操作)的场景,Jetty,Mina,ZooKeeper等都是基于java nio实现。
(2)BIO方式适用于连接数目比较小且固定的场景,这种方式对服务器资源要求比较高,并发局限于应用中。

memcached是一个高性能的分布式内存缓存服务器,它的分布式其实在服务端是不具有分布式的特征的,是依靠客户端的分布式算法进行了分布式,memcached是一个纯内存型的数据库,这样在读写速度上相对来说比较快。
常规的程序使用内存无非是两种,一种是预先分配,一种是动态分配。动态分配从效率的角度来讲相对来说要慢点,因为它需要实时的去分配内存使用,但是这种方式的好处就是可以节约内存使用空间;memcached采用的是预先分配的原则,这种方式是拿空间换时间的方式来提高它的速度,这种方式会造成不能很高效的利用内存空间,但是memcached采用了Slab Allocation机制来解决内存碎片的问题,Slab Allocation的基本原理就是按照预先规定的大小,将分配的内存分割成特定长度的块,并把尺寸相同的块分成组(chunk的集合)
memcached本身内部不会监视记录是否过期,而是当get时依靠记录的过期时间检查是否过期,这也是memcached的一种惰性过期机制。默认情况下memcached内部也维护着一套LRU置换算法,当设定的内存满的时候,会进行最近很少使用的数据置换出去从而分配空间,所以对于提升memcached命中率的问题主要还是一是根据业务存放的value值来调整好chunk的大小以达到最大效率的利用内存;二是扩大内存保证所有缓存的数据不被置换出去。
对于memcached的分布式完全就是依靠客户端的一致哈希算法来达到分布式的存储,因为本身各个memcached的服务器之间没办法通信,并不存在副本集或者主从的概念,它的分布式算法主要是先求出每一个memcached的服务器节点的哈希值,并将它们分配到2的32次方的圆上,然后根据存储的key的哈希值来映射到这个圆上,属于哪个区间顺时针找到的节点就存到这个服务器节点上
当添加新的memcached节点的时候必然会打乱现有这个圆的结构,这时候是没办法完全保证你以前的key依然会存在之前的节点上,但是这种结构却是能保证在添加缓存服务器的时候把损失降到最小,受结构调整后key不能命中的只有在这个圆上新增的服务器节点逆时针的第一台服务器上,其他的是不受影响的
memcached和redis一样内部的存储都是key/Value的形式,正是这种哈希表数据结构保证了在内存中查找的时间的复杂度为O(1)
memcached的内部操作还具有CAS原子操作,这种利用CPU指令集的操作来保证在单个节点下数据的一致性,效率相对来说比加锁要高很多。

  1. 拥塞:即对资源的需求超过了可用的资源。若网络中许多资源同时供应不足,网络的性能就要明显变坏,整个网络的吞吐量随之负荷的增大而下降。
    拥塞控制:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、路由器,以及与降低网络传输性能有关的所有因素。
    流量控制:指点对点通信量的控制,是端到端正的问题。流量控制所要做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
    拥塞控制代价:需要获得网络内部流量分布的信息。在实施拥塞控制之前,还需要在结点之间交换信息和各种命令,以便选择控制的策略和实施控制。这样就产生了额外的开销。拥塞控制还需要将一些资源分配给各个用户单独使用,使得网络资源不能更好地实现共享。
  2. 几种拥塞控制方法
    慢开始( slow-start )、拥塞避免( congestion avoidance )、快重传( fast retransmit )和快恢复( fast recovery )。
    2.1 慢开始和拥塞避免
    发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞。
    发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。
    慢开始算法:当主机开始发送数据时,如果立即所大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。因此,较好的方法是 先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。
    每经过一个传输轮次,拥塞窗口 cwnd 就加倍。一个传输轮次所经历的时间其实就是往返时间RTT。不过“传输轮次”更加强调:把拥塞窗口cwnd所允许发送的报文段都连续发送出去,并收到了对已发送的最后一个字节的确认。
    另,慢开始的“慢”并不是指cwnd的增长速率慢,而是指在TCP开始发送报文段时先设置cwnd=1,使得发送方在开始时只发送一个报文段(目的是试探一下网络的拥塞情况),然后再逐渐增大cwnd。
    为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量(如何设置ssthresh)。慢开始门限ssthresh的用法如下:
    当 cwnd < ssthresh 时,使用上述的慢开始算法。
    当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
    当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。
    拥塞避免算法:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
    无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认),就要把慢开始门限ssthresh设置为出现拥塞时的发送 方窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生 拥塞的路由器有足够时间把队列中积压的分组处理完毕。
    如下图,用具体数值说明了上述拥塞控制的过程。现在发送窗口的大小和拥塞窗口一样大。

    <1>. 当TCP连接进行初始化时,把拥塞窗口cwnd置为1。前面已说过,为了便于理解,图中的窗口单位不使用字节而使用报文段的个数。慢开始门限的初始值设置为16个报文段,即 cwnd = 16 。
    <2>. 在执行慢开始算法时,拥塞窗口 cwnd 的初始值为1。以后发送方每收到一个对新报文段的确认ACK,就把拥塞窗口值另1,然后开始下一轮的传输(图中横坐标为传输轮次)。因此拥塞窗口cwnd 随着传输轮次按指数规律增长。当拥塞窗口cwnd增长到慢开始门限值ssthresh时(即当cwnd=16时),就改为执行拥塞控制算法,拥塞窗口按线 性规律增长。
    <3>. 假定拥塞窗口的数值增长到24时,网络出现超时(这很可能就是网络发生拥塞了)。更新后的ssthresh值变为12(即变为出现超时时的拥塞窗口数值 24的一半),拥塞窗口再重新设置为1,并执行慢开始算法。当cwnd=ssthresh=12时改为执行拥塞避免算法,拥塞窗口按线性规律增长,每经过 一个往返时间增加一个MSS的大小。
    强调:“拥塞避免”并非指完全能够避免了拥塞。利用以上的措施要完全避免网络拥塞还是不可能的。“拥塞避免”是说在拥塞避免阶段将拥塞窗口控制为按线性规律增长,使网络比较不容易出现拥塞。

2.2 快重传和快恢复

2.2 快重传和快恢复
如果发送方设置的超时计时器时限已到但还没有收到确认,那么很可能是网络出现了拥塞,致使报文段在网络中的某处被丢弃。这时,TCP马上把拥塞窗口 cwnd 减小到1,并执行慢开始算法,同时把慢开始门限值ssthresh减半。这是不使用快重传的情况。
快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时才进行捎带确认。

接收方收到了M1和M2后都分别发出了确认。现在假定接收方没有收到M3但接着收到了M4。显然,接收方不能确认M4,因为M4是收到的失序报文段。根据 可靠传输原理,接收方可以什么都不做,也可以在适当时机发送一次对M2的确认。但按照快重传算法的规定,接收方应及时发送对M2的重复确认,这样做可以让 发送方及早知道报文段M3没有到达接收方。发送方接着发送了M5和M6。接收方收到这两个报文后,也还要再次发出对M2的重复确认。这样,发送方共收到了 接收方的四个对M2的确认,其中后三个都是重复确认。快重传算法还规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段M3,而不必 继续等待M3设置的重传计时器到期。由于发送方尽早重传未被确认的报文段,因此采用快重传后可以使整个网络吞吐量提高约20%。
与快重传配合使用的还有快恢复算法,其过程有以下两个要点:
<1>. 当发送方连续收到三个重复确认,就执行“乘法减小”算法,把慢开始门限ssthresh减半。这是为了预防网络发生拥塞。请注意:接下去不执行慢开始算法。
<2>. 由于发送方现在认为网络很可能没有发生拥塞,因此与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd现在不设置为1),而是把cwnd值设置为 慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。
下图给出了快重传和快恢复的示意图,并标明了“TCP Reno版本”。
区别:新的 TCP Reno 版本在快重传之后采用快恢复算法而不是采用慢开始算法。

也有的快重传实现是把开始时的拥塞窗口cwnd值再增大一点,即等于 ssthresh + 3 X MSS 。这样做的理由是:既然发送方收到三个重复的确认,就表明有三个分组已经离开了网络。这三个分组不再消耗网络 的资源而是停留在接收方的缓存中。可见现在网络中并不是堆积了分组而是减少了三个分组。因此可以适当把拥塞窗口扩大了些。
在采用快恢复算法时,慢开始算法只是在TCP连接建立时和网络出现超时时才使用。
采用这样的拥塞控制方法使得TCP的性能有明显的改进。
接收方根据自己的接收能力设定了接收窗口rwnd,并把这个窗口值写入TCP首部中的窗口字段,传送给发送方。因此,接收窗口又称为通知窗口。因此,从接收方对发送方的流量控制的角度考虑,发送方的发送窗口一定不能超过对方给出的接收窗口rwnd 。
发送方窗口的上限值 = Min [ rwnd, cwnd ]
当rwnd < cwnd 时,是接收方的接收能力限制发送方窗口的最大值。
当cwnd < rwnd 时,则是网络的拥塞限制发送方窗口的最大值

MVCC简介 - 绿洲2017 - 博客园 (cnblogs.com)
MVCC是一种多版本并发控制机制。
锁机制可以控制并发操作,但是其系统开销较大,而MVCC可以在大多数情况下代替行级锁,使用MVCC,能降低其系统开销.
MVCC是通过保存数据在某个时间点的快照来实现的. 不同存储引擎的MVCC. 不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制.
InnoDB在每行数据都增加两个隐藏字段,一个记录创建的版本号,一个记录删除的版本号。
当隔离级别是REPEATABLE READ时select操作,InnoDB必须每行数据来保证它符合两个条件:
1、InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。
2、这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除。
符合这两个条件的行可能会被当作查询结果而返回。

  • INSERT: InnoDB为这个新行记录当前的系统版本号。
  • DELETE: InnoDB将当前的系统版本号设置为这一行的删除ID。
  • UPDATE:InnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这个版本号写到旧行的删除版本里。

MySQL InnoDB存储引擎,实现的是基于多版本的并发控制协议——MVCC (Multi-Version Concurrency Control)。MVCC最大的好处:读不加锁,读写不冲突。

乐观锁:数据版本号,其实是一种认为并行事务不会冲突而不加锁的思想,版本号只是一种实现方法。
Innodb中的mvcc是乐观并发控制,但mvcc也有悲观并发控制。

多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读
乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制,认为事务间争用没有那么多,所以先进行修改,在提交事务前,检查一下事务开始后,有没有新提交改变,如果没有就提交,如果有就放弃并重试。乐观并发控制类似自选锁。乐观并发控制适用于低数据争用,写冲突比较少的环境。

Select * from *** for update
Innodb支持next-key锁,即行锁(记录锁)和间隙所。间隙所指的是将两条记录之间也锁起来,因此Next-key是一个前开后闭的区间。innodb对于行的查询,都是采用next-key,主要目的是解决幻读的问题。唯一索引加锁时,next-key锁退化(优化)为行锁。

mysql innodb引擎什么时候表锁什么时候行锁?
InnoDB基于索引的行锁
• InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁
• 在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。 在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。
innodb行锁和表锁的情况
• 由于InnoDB预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL才会执行Row lock (只锁住被选取的资料例) ,否则MySQL将会执行Table Lock (将整个资料表单给锁住):
• (明确指定主键,并且有此笔资料,row lock)
SELECT * FROM products WHERE id=‘3’ FOR UPDATE;
SELECT * FROM products WHERE id=‘3’ and type=1 FOR UPDATE;
• (明确指定主键,若查无此笔资料,无lock)
SELECT * FROM products WHERE id=’-1’ FOR UPDATE;
• (无主键,table lock)
SELECT * FROM products WHERE name=‘Mouse’ FOR UPDATE;
• (主键不明确,table lock)
SELECT * FROM products WHERE id<>‘3’ FOR UPDATE;
• (主键不明确,table lock)
SELECT * FROM products WHERE id LIKE ‘3’ FOR UPDATE;
• 尽量使用明确的主键来检索
有时有索引也会导致表锁的情况
• 导致这个的原因第一是sql语句写法问题,没有合理构建和使用索引。
• 第二个原因是mysql的优化器,有时优化器发现:即使使用了索引,还是要做全表扫描,故而放弃了索引,也就没有使用行锁,却使用了表锁
索引类型对锁类型的影响
• 主键:众所周知,自带最高效的索引属性
• 唯一索引:属性值重复率为0,可以作为业务主键
• 普通索引:属性值重复率大于0,不能作为唯一指定条件
• 注意:对于普通索引,当“重复率”低时,甚至接近主键或者唯一索引的效果时,依然是行锁;但是如果“重复率”高时,Mysql不会把这个普通索引当做索引,即会造成一个没有索引的SQL,从而形成表锁。

InnoDB存储引擎支持两种常见的索引。
一种是B+树,一种是哈希。B+树中的B代表的意思不是二叉(binary),而是平衡(balance),因为B+树最早是从平衡二叉树演化来的,但是B+树又不是一个平衡二叉树,是一棵平衡查找树。
同时,B+树索引并不能找到一个给定键值的具体行。B+树索引只能找到的是被查找数据行所在的页(磁盘上的页,索引以文件方式放在磁盘上,缺页异常)。然后数据库通过把页读入内存,再在内存中进行查找,最后得到查找的数据。
B+树就是为磁盘或其他直接存取辅助设备而设计的一种多路查找树,是多叉树。在B+树中,所有记录节点都是按照键值的大小顺序存放在同一层的叶节点中,各个叶子节点通过指针进行连接。由于一个节点中存放了多条的数据,那么检索的时候,进行的磁盘IO次数将会少掉很多。
在B+树插入的时候,为了保持平衡,对于新插入的键值可能需要做大量的拆分页操作,而B+树主要用于磁盘,因此页的拆分意味着磁盘操作,因此应该在可能的情况下尽量减少页的拆分。因此,B+树提供了旋转的功能。
有n个子树的中间节点包含n个元素,每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
所有叶子节点包含元素的信息以及指向记录的指针,且叶子节点按关键字自小到大顺序链接。
所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。

单一节点存储更多元素,减少IO
所有查询都要找到叶子节点,查询稳定
所有叶子节点形成有序链表,方便范围查询
聚集索引是按表的主键构造的B+树,叶子节点存放的为整张表的行记录数据,每张表只能有一个聚集索引。优化器更倾向采用聚集索引。因为直接就能获取行数据。

InnoDB存储引擎使用聚集索引,实际的数据行和相关键值保存在一块。因而,在InnoDB中要使用索引访问数据始终需要两次查找,而不是一次。因为索引叶子节点中存储的不是行的物理位置,而是主键的值。即:二次索引–>主键–>数据的叶子–>通过数据叶子节点中的page directory找到数据行。

因为每一张InnoDB的表都会有一个主键索引,但是如果没有显式指定怎么办?如果没有手工去指定主键索引的话,那么,InnoDB引擎会指派一个unique的列作为主键,如果没有unique的字段的话,那么便会自动生成一个隐含的列作为主键。

所以,在在InnoDB的设计中,应该尽可能的使用一个与业务无关auto_increment的自增主键,而不要去使用uuid之类的随机(无序)的聚集键。同时,由于所有的索引都使用主键的索引,如果主键索引过长,也会使辅助索引相应的变大。

聚集索引的存储并不是物理上的连续,而是逻辑上连续的。一方面,页通过双向链表连接,页按照主键的顺序排列;另一方面,每个页中的记录也是通过双向链表进行维护,物理存储上可以同样不按照主键存储。

对于目前的MySQL来说,所有的对于索引的添加或者删除操作,MySQL数据库都是要先创建一张新的临时表,然后再把数据导入临时表,再删除原来的表,然后再把临时表命名为原来的表。所以,如果一张表中数据太多的话,那么后期添加删除索引需要花费很长的时间,因而最好在数据库设计初期便设计好索引。

辅助索引也叫非聚集索引,叶子节点除了键值以外还包含了一个bookmark,用来告诉InnoDB在哪里可以找到对应的行数据,InnoDB的辅助索引的bookmark就是相对应行数据的聚集索引键。也就是先获取指向主键索引的主键,然后通过主键索引来找到一个完整的行。

Hash索引:对索引计算hash值,将hash值和数据地址存到hash表里

1.0 十大经典排序算法 | 菜鸟教程 (runoob.com)
排序算法总结 | 菜鸟教程 (runoob.com)
冒泡排序 稳定 O(n2) O(1) 两两相邻比较交换
两层循环,外层i:[0,arr.Length),内层j:[0,arr.Length-1-i),大的往后,依次把大的放在最后
或者,内层j:[arr.length-1,i),小的往前,依次把小的放到头上
外层循环设置标志位,内层循环存在逆序则设置标志位,外层循环判断标志位,未被设置则直接结束循环
选择排序 不稳定
(交换位置) O(n2) O(1) 未排序数列找最小的数位与已排序数列后一数位(即未排序数列第一位)交换
内层循环j:[i+1,arr.length),寻找最小元素位置min
外层循环:将min与i+1元素互换
插入排序 稳定 O(n2) O(1) 将未排序数依次插到已排序数列合适位置
内层循环中,在已排序数列中依次将大于待排序数的已排序数后移,找到待插入的合适位置
快速排序 不稳定 O(N*logN) O(logN) 分治,划分区间,大值放右边,小值放左边
归并排序 稳定 O(NlogN) O(n) 分治,归并,将小的有序数列合并得到大的有序数列
堆排序 不稳定 O(NlogN) O(1) 选择排序
将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序
构建堆时,先从最下层的第一个非叶子节点(length/2-1)开始,从左到右从上到下比较,把大值调整到上面,再去处理其上层非叶子节点,处理上层非叶子节点时,会循环把下面的节点都处理到。
希尔排序 不稳定 O(n1.3-2) O(1) 利用增量将数列划分为子序列
先依次插入排序(i,i+增量)两个元素,再把增量逐步减小至1,最后再插入排序
三层循环,最外层是增量step,开始是数列长度一半,并逐渐减半,中间层i:[step,length),最内层j:在子序列中插入排序
图解排序算法(一)之3种简单排序(选择,冒泡,直接插入) - dreamcatcher-cx - 博客园 (cnblogs.com)
图解排序算法(三)之堆排序 - dreamcatcher-cx - 博客园 (cnblogs.com)
图解排序算法(四)之归并排序 - dreamcatcher-cx - 博客园 (cnblogs.com)
图解排序算法(五)之快速排序——三数取中法 - dreamcatcher-cx - 博客园 (cnblogs.com)
图解排序算法(二)之希尔排序 - dreamcatcher-cx - 博客园 (cnblogs.com)

Java内存模型(MESI、内存屏障、volatile和锁及final内存语义)_曹自标的博客-CSDN博客

volatile内存语义:当对一个volatile变量进行写操作的时候,JMM会把该线程对应的本地内存中的共享变量的值刷新到主内存中。当读一个volatile变量的时候,JMM会把该线程对应的本地内存设置为无效,要求线程从主内存中读取数据。
volatile的实现:根据volatile重排序规则,在写操作前面插入Store Store屏障;在写操作后面插入storeload屏障;在读操作后面插入loadload和loadstore屏障
锁释放与volatile写语义相同。锁获取与volatile读语义相同。
锁的底层依靠volatile变量和cas操作实现。
final域内存语义:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过;在读一个对象的final域之前,一定会先读包含这个final域的对象引用。
final域重排序规则:
final域是基础数据类型:在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序(store store屏障)。初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序(load load屏障)。
final域是引用数据类型:在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

什么是CAS机制,通俗易懂大白话版。_操作 (sohu.com)
CAS是英文单词Compare and Swap的缩写,翻译过来就是比较并替换。CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
synchronized:悲观锁
cas:乐观锁,不断重试(自旋)尝试更新
java语言CAS底层如何实现?利用unsafe提供的原子性操作方法。
什么是ABA问题?怎么解决?当一个值从A变成B,又更新回A,普通CAS机制会误判通过检测。利用版本号比较可以有效解决ABA问题。

使用意向锁(IntentionLocks)可以更容易地支持多粒度封锁。
在存在行级锁和表级锁的情况下,事务T想要对表A加X锁,就需要先检测是否有其它事务对表A或者表A中的任意一行加了锁,那么就需要对表A的每一行都检测一次,这是非常耗时的。意向锁在原来的X/S锁之上引入了IX/IS,IX/IS都是表锁,用来表示一个事务想要在表中的某个数据行上加X锁或S锁。
有以下两个规定:一个事务在获得某个数据行对象的S锁之前,必须先获得表的IS锁或者更强的锁;一个事务在获得某个数据行对象的X锁之前,必须先获得表的IX锁。
通过引入意向锁,事务T想要对表A加X锁,只需要先检测是否有其它事务对表A加了X/IX/S/IS锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务T加X锁失败。各种锁的兼容关系如下:

任意IS/IX锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正加锁;S锁只与S锁和IS锁兼容,也就是说事务T想要对数据行加S锁,其它事务可以已经获得对表或者表中的行的S锁。

Java面试常考的 BIO,NIO,AIO 总结_小树的博客-CSDN博客_bio nio
BIO NIO AIO
同步(利用线程池实现伪异步) 同步 异步
阻塞 非阻塞 非阻塞
流,单向 通道,双向
流 缓冲区
一个线程处理一个IO请求 一个线程处理多个IO请求
NIO中的所有IO都是从 Channel(通道) 开始的。
从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
从通道进行数据写入 :创建一个缓冲区,填充数据,然后请求通道写入数据。

socket IO
阻塞式IO 非阻塞式IO IO复用(事件驱动) 信号驱动式IO 异步IO
一直阻塞 先不断调用去查询内核数据,再阻塞等待复制 先select/poll/epoll阻塞等待内核数据,再阻塞等待复制 先生成一个信号等待内核数据,再阻塞等待复制 一直不阻塞

redis底层数据结构
字典 redis系列之------字典 - wenbochang - 博客园 (cnblogs.com)
Redis字典使用散列表最为底层实现,一个散列表里面有多个散列表节点,每个散列表节点就保存了字典中的一个键值对。

Redis使用链表法解决散列冲突。每个散列表节点都有一个next指针,多个散列表节点next可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以使用这个单向链表连接起来。
随着操作的进行,散列表中保存的键值对会也会不断地增加或减少,为了保证负载因子维持在一个合理的范围,当散列表内的键值对过多或过少时,内需要定期进行rehash,以提升性能或节省内存。
Redis渐进式rehash的详细步骤:

  1. 为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。
  2. 在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 ,表示 rehash 工作正式开始。
  3. 在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。
  4. 随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。
    说明:
    1.因为在进行渐进式 rehash 的过程中,字典会同时使用 ht[0] 和 ht[1] 两个哈希表,所以在渐进式 rehash 进行期间,字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行。
  5. 在渐进式 rehash 执行期间,新添加到字典的键值对一律会被保存到 ht[1] 里面,而 ht[0] 则不再进行任何添加操作:这一措施保证了 ht[0] 包含的键值对数量会只减不增,并随着 rehash 操作的执行而最终变成空表。
    查找,先在dict[0]找,再去dict[1]找
    Redis详解(四)------ redis的底层数据结构 - YSOcean - 博客园 (cnblogs.com)
    链表、字典、跳跃表、整数集合、压缩列表

Redis应用:
共同好友 排行榜 计数器 缓存热点数据 消息队列 热点数据 会话缓存(多服务) 分布式锁 查找表 ……

Redis Memcached
5种数据类型 String
AOF RDB持久化 不支持持久化,数据一直在内存中
分布式存储 集群 本身不支持分布式存储,客户端通过一致性哈希来实现
内存划分为块,解决内存碎片,但利用率不高

Redis事务
一次性、顺序性、排他性
事务中的多个命令被一次性发送给服务器。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
Redis事务没有隔离级别的概念,也不保证原子性,不会回滚,事务中任意命令执行失败,其余的命令仍会被执行。
Redis事务的三个阶段:开始事务,命令入队,执行事务

无论exec事务执行成功或失败,watch对变量的监控都自动取消
当命令中有命令性错误时(命令入队列失败),所有命令都不执行
当命令中有语法性错误时(eg: set name 1/0),其他正确命令会被执行,错误命令抛出异常。

Redis主从复制过程:
1.主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
2.从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
3.主服务器每执行一次写命令,就向从服务器发送相同的写命令

Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。

Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。

博客:hash
点赞:点赞用户set
按点赞量/发布时间排序:借助zset的score

Linux
关机前 who查看其他还有哪些用户在使用 sync将数据同步到磁盘
shutdown -s -t 0 立即关机
shutdown -p立即关机
shutdown -h 休眠
shut -r 重启
shut -a 取消关机

info查看指令详细信息 分页显示
–help 指令基本用法
man 查看指令详细信息 1:可执行文件或指令,8:root的管理命令 5:配置文件

ln 默认hard link实体链接,不能对目录创建链接,删除一个,文件还存在
ln -s symbolic link符号链接,能对目录创建链接,删除,文件消失,类比windows的快捷方式

指令搜索:which
文件搜索:whereis locate find

压缩指令只能对一个文件进行压缩,而打包能够将多个文件打包成一个大文件。tar 不仅可以用于打包,也可以使用 gip、bzip2、xz 将打包文件进行压缩。

ps -l查看自己的进程
ps aux查看系统所有进程
ps aux | grep xxx查看特定的进程
pstree -A查看所有进程树
top -d 2每两秒刷新显示进程信息
netstat -anp | grep port查看特定端口的进程

当一个子进程改变了它的状态时(停止运行,继续运行或者退出),有两件事会发生在父进程中:子进程向父进程发送 SIGCHLD 信号;父进程调用waitpid() 或者 wait()。
其中子进程发送的 SIGCHLD 信号包含了子进程的信息,比如进程 ID、进程状态、进程使用 CPU 的时间等。在子进程退出时,它的进程描述符不会立即释放,父进程通过 wait() 和 waitpid() 来获得一个已经退出的子进程的信息。
一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程由init进程收养,因此不会对系统造成损害。
一个子进程退出,而父进程不知道,子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。
要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。

基于 双向链表 + HashMap 实现 LRU 算法:
访问某个节点时,将其从原来的位置删除,并重新插入到链表头部。这样就能保证链表尾部存储的就是最近最久未使用的节点,当节点数量大于缓存最大空间时就淘汰链表尾部的节点。
为了使删除操作时间复杂度为O(1),就不能采用遍历的方式找到某个节点。HashMap存储着Key到节点的映射,通过Key就能以O(1)的时间得到节点,然后再以O(1)的时间将其从双向队列中删除。
HashMap<K,Node>map
Node<K,V>

内容分发网络(Content distribution network,CDN)是一种互连的网络系统,它利用更靠近用户的服务器从而更快更 可靠地将 HTML、CSS、JavaScript、音乐、图片、视频等静态资源分发给用户。

一致性哈希 哈希环 解决传统哈希在增删节点时大量数据迁移的问题 只影响相邻节点

重定向,response,客户端两次请求,状态码302
转发,request,客户端一次请求,url不变

计算机专业相关知识零碎记录相关推荐

  1. 主板是计算机所有部分连接的基础,计算机基础相关知识答案.doc

    计算机基础相关知识答案 计算机基础相关知识 一.填空题: 1.计算机的硬件主要由(控制器).(运算器).(存储器).(输入输出设备)以及电源等硬件组成. 2.计算机硬件系统可以分为两大部分,即(主机 ...

  2. 学计算机辐射,离散数学对计算机专业系统知识辐射作用.doc

    离散数学对计算机专业系统知识辐射作用 离散数学对计算机专业系统知识辐射作用 摘 要:由于计算机专业考研统考课程中无离散数学内容,离散数学在计算机专业教育中越来越不被重视,针对目前离散数学课程在计算机专 ...

  3. 主板是计算机所有部分连接的基础,计算机基础相关知识答案

    计算机基础相关知识答案 (4页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.9 积分 计算机基础相关知识1. 计算机的硬件主要由(控制器).(运算器) ...

  4. 计算机专业基础知识(中)

    大纲 分专题讲解 4. 操作系统的基本概念.功能.组成及分类 概念理解 主要功能 组成和分类 主流的操作系统 推荐书籍和课程 5. Windows 操作系统的基本概念和常用术语,文件.文件夹.库等 W ...

  5. 计算机专业的相关论文,计算机专业相关论文

    提高教学质量是高等院校各项工作的重中之重,而教学管理是提高教学质量的重要途径,加强教学管理对规范高校各项管理工作和提高教学水平起着十分重要的作用.下面是小编为大家整理的计算机专业相关论文,供大家参考. ...

  6. 计算机专业技能知识,2017年度计算机专业技能知识资料基础知识资料试题'及其答案...

    2017年度计算机专业技能知识资料基础知识资料试题'及其答案 (19页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.90 积分 ''第一部分   一 ...

  7. 计算机专业考试基础知识,计算机专业基础知识要点及考试考试.docx

    计算机专业基础知识要点及考试考试 PAGE PAGE 27 数据结构要点第一章 概 论数据就是指能够被计算机识别.存储和加工处理的信息的载体.数据元素是数据的基本单位,可以由若干个数据项组成.数据项是 ...

  8. 2020计算机年理论论考试题,2020年计算机专业网络理论知识试题(20页)-原创力文档...

    2020 年计算机专业网络理论知识试题 姓名: [ 填空题 ] * _________________________________ 1.Internet 起源于美国的()网络,它实际上是一个网际网 ...

  9. 课外知识计算机方面,单元一 计算机基础相关知识.pptx

    单元一 计算机基础相关知识.pptx 单元一 计算机基础知识电子课件计算机的发展历程计算机的特点及分类计算机的应用计算机系统的组成计算机中的信息表示计算机多媒体技术计算机信息安全计算机硬件设备知识要点 ...

最新文章

  1. 根据 JS 自动定义页面缩放比(根据分辨率进行适配)
  2. python大学课程-利用python完成大学刷课(从0到完成的思路)
  3. Hibernate CRUD操作
  4. C++ GUI Qt4编程(12)-6.1FindFileDialog
  5. @ConditionalOnClass注解的作用
  6. Maven的pom.xml文件详解------The Basics
  7. mysql区分大小写搜索
  8. Chrome Workspace开发者调试工具
  9. PAT 1085. PAT单位排行 (25) - 乙级
  10. android分享数据到不同平台组件化
  11. Android ssl 异常,SSL握手异常,同时通过https连接使用Android中的自签名证书Nougat
  12. HashMap、LinkedHashMap、HashTable、HashSet笔记
  13. breakall lisp文件_CAD导入草图大师后自动成面
  14. 2016版excel_在抱怨加班之前,先看看你有没有熟练使用这13个Excel大神技巧?
  15. eclipse快捷方式打不开解决方法
  16. 以太网未识别的网络win10_win10系统遇到以太网无法识别网络如何解决
  17. 面向高稳定,高性能之-Hbase数据实时同步到ElasticSearch(之二)
  18. 缓存Cache概述——缓存Cache1.1.1
  19. Facebook sdk嵌入,登陆与注销
  20. linux系统安装hba驱动,Linux系统上iSCSI HBA的安装和配置

热门文章

  1. vuex的基本应用(vuex的购物车案例)
  2. html5的canvas制作口红机闯关游戏(一)
  3. 前端开发——在线工具推荐
  4. 微信属于计算机操作系统吗,一款国产操作系统的微信电脑版使用体验
  5. 计算机中央处理单元是哪些,1.1.1-1.1.2 计算机系统硬件基本组成 - 中央处理单元...
  6. sharepoint 模板安装
  7. 佛祖保佑永无BUG python版本
  8. 冒烟测试和回归测试的区别
  9. cosolog打印带样式的文字及图片
  10. 长春工业大学计算机研究生专业课,长春工业大学(专业学位)计算机技术研究生考试科目和考研参考书目...