基础知识-SQL语法

SQL 语法

基础

DDL: create table/alter table/drop table/create index/drop index/create database/alter database

DML: select/update/delete/insert into

TOP: select top number|percent from …. 其中 number 可以接 order by 排序, percent 不可以

通配符: ‘%’ 若干个, ‘_’ 一个, ‘[ANF]’ 包含 A 或 N 或 F, ‘[!ANF]’ 不包含 A 或 N 或 F

Between..And: 在 mysql 中不包含右边界

AS: 别名 select name as n, sale as s …

JOIN table ON col.A = col.B: 内 JOIN 只返回两张表有的记录, 左 JOIN 返回左表全部记录, 右 JOIN 返回右表全部记录, Full JOIN 返回全部表记录

UNION: 用于合并两个或多个 SELECT 语句的结果集, 内部的 SELECT 语句必须拥有相同数量的列, 列也必须拥有相似的数据类型, UNION 会去重, 若不去重则用 UNION ALL

SELECT INTO: 从一个表中选取数据,然后把数据插入另一个表中. SELECT * INTO new_table_name FROM old_tablename

CREATE INDEX index_name ON table_name (column_name): 创建索引

ALTER TABLE table_name ADD column_name datatype: 添加列

ALTER TABLE table_name DROP COLUMN column_name: 删除列

ALTER TABLE table_name ALTER COLUMN column_name datatype: 修改列类型

CREATE VIEW view_name AS …: 创建视图

GROUP BY: 合计函数 (比如 SUM) 常常需要, 按列规约结果

HAVING:在 SQL 中增加 HAVING 子句原因是,WHERE 关键字无法与合计函数一起使用。

高级

MySQL

  • IF( expr1 , expr2 , expr3 )

  • CASE WHEN: case 列名 when 条件 then 结果 else 其它结果 end 别名

1
2
3
4
5
6
7
8
9
10
11
12
SELECT 
CASE detail.`status` WHEN '0' THEN '未开仓'
WHEN '1' THEN '已开仓'
WHEN '2' THEN '已平仓'
ELSE '取消订单' END
status,
CASE o.type WHEN '0' THEN '单期'
WHEN '1' THEN '多期'
ELSE '策略' END
typeName
FROM t_order_detail detail
LEFT JOIN t_order o ON o.id = detail.orderId
  • IFNULL( expr1 , expr2 ): 在 expr1 的值不为 NULL的情况下都返回 expr1,否则返回 expr2
如需转载,请注明出处

Java分布式服务框架

简述

业界主流(O:Open Source): Thrift(O), Avro-RPC(O), Hessian(O), gRPC(O), Dubbo(O), HSF, Coral Service(亚马逊), DSF(华为)

分布式服务框架包括: RPC组件, 配置化服务发布, 基于服务注册中心的订阅和发布, 服务治理

RPC 组件: 通信框架, 编码, 协议栈

涉及到的技术: Socket 通信, 多线程, 协议栈 -> Netty

关键字: 长连接, NIO(多路复用)

epoll 没有最大连接句柄 1024/2048 的限制, 意味着只需要一个线程负责 Selector 的轮询, 就可以接入成千上万的客户端

可靠性设计靠心跳来实现: TCP 层面的心跳检测(Keep-Alive), 协议层的心跳检测, 应用层的心跳检测

Netty 的心跳检测实际上利用了链路空闲检测机制实现的, 默认为读写空闲(链路持续时间 t 没有接受或者发送消息)

Netty 的 EventLoopGroup 线程组会默认创建 CPU Core * 2 个线程, 使用的时候一定要评估线程数指定, 最好不要使用默认, 或者创建一个数组, 按照 Hash 复用 EventLoopGroup

服务组件: 路由

基于服务注册中心(例如: Zookeeper)的订阅发布机制, 消费者可通过主动查询和被动通知的方式获取服务提供者的地址, 消费者本地也缓存服务地址列表, 当注册中心挂掉时, 仍可向服务发起通信

消费者访问服务的负载均衡: 随机, 轮循, 服务调用延时(消费者缓存服务调用延时, 计算权重, 让延时高的服务接收更少的消息), 一致性哈希

本地路由优先策略: injvm(本地 JVM 中), innative(相同物理机或者VM)

一致性哈希: 希望在增删节点(集群)的时候,让尽可能多的数据不失效, 精华:每个实际节点的N个虚拟节点尽量随机分布在整数环上,增加cache节点时,就能尽量保证移动到新cache节点的key来自于不同的cache节点, 从而保证负载均衡, 即 hash(key) -> 虚拟节点 -> 真实节点

集群容错

什么场景需要容错: 通信链路故障, 服务端超时, 服务端调用异常失败

容错策略: 失败自动切换(Failover), 失败通知(Failback), 失败缓存(Failcache), 快速失败(Failfast)

不同的容错策略适用于不同的业务服务, 容错接口开放使得服务提供者能够按需配置自己的策略

HSF 默认采取失败自动切换的容错

服务调用: 同步, 异步, 泛化

用户发起远程服务调用之后, 经历层层业务逻辑处理、消息编码、最终序列化后的消息会被放入到通信框架的消息队列中

异步实际上是返回 Future 对象, 调用者可以通过 get 阻塞等待获取结果, 也可以通过 Future-Listener 进行回调

泛化调用: 客户端没有API接口及数据模型时, 泛化引用将参数及返回值中的所有 POJO 均用 Map 表示; 服务端没有API接口及数据模型时, 泛化实现将参数及返回值中的所有 POJO 均用 Map 表示; 常用于通用测试

如需转载,请注明出处

Java并发编程实践

线程安全

定义: 1)不管运行时线程采取何种调度方式, 2)代码中不需要任何额外的同步或协同, 这个类都能表现出正确的行为, 无状态的对象, 不可变对象一定是线程安全的

同步机制: synchronized, volatile, lock, atom

两个维度: 防止某个线程正在使用对象状态而另一个线程在同时修改状态(同步机制); 确保当一个线程修改了对象状态后, 其他线程能够看到发生的状态变化(可见性)

synchronized 是可重入的, 重入的一种实现方法是为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时, 这个锁就被认为是没有被任何线程持有

重排序: 在没有同步的情况下, 编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中, 要相对内存操作的执行顺序进行判断, 几乎无法得出正确的结论

进一步衍生: 重排序的As-if-serial语义意思是,所有的动作(Action)都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。
比如,为了保证这一语义,重排序不会发生在有数据依赖的操作之中

加锁的意义: 保证互斥行为(可见性和原子性)以及内存可见性, 确保所有线程都能看到共享变量的最新值, 所有执行读操作或者写操作的线程都必须在同一个锁上同步

volatile 的实现原理: 不会将该变量上的操作重排序(内存屏障), 也不会放入缓存寄存器中

同步机制可确保原子性和可见性, volatile 只能保证可见性(++count 无法保证原子性)

ThreadLocal, 线程封闭, 不共享的变量: 为变量在每个线程中都创建一个副本,每个线程可以访问自己内部的副本变量, 实现为: ThreadLocal -> Map -> thread.threadLocals -> 内部类 ThreadLocalMap, 场景: 数据库连接, Session 管理

想要做到线程安全: 同步保证原子性操作, 轻量 Volatile 保证可见性, 不变的对象一定是线程安全的(final)

内存屏障:
1) Happens-before的前后两个操作不会被重排序且后者对前者的内存可见
2) LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见

volatile语义中的内存屏障:

  • 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;

  • 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;

final语义中的内存屏障:

  • 写final域:在编译器写final域完毕,构造体结束之前,会插入一个StoreStore屏障,保证前面的对final写入对其他线程/CPU可见,并阻止重排序。

  • 读final域:在上述规则2中,两步操作不能重排序的机理就是在读final域前插入了LoadLoad屏障。

锁的实现

乐观锁和悲观锁

  • 乐观锁, 持有后再请求, 告知失败, 请求线程可在一定时间内重试, 不会挂起

  • 悲观锁, 持有后再请求, 被挂起, 直到锁释放后恢复线程执行, 有可能获得该锁

构建线程安全类

同步容器: Vector 和 HashTable, 方法级加锁(synchronized), 并发效率低

并发容器: ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList 等, 灵活加锁, 并发效率高

阻塞队列实现生产者-消费者: BlockingQueue 的实现有 LinkedBlockingQueue 和 ArrayBlockingQueue 都是 FIFO 队列, PriorityBlockingQueue 是优先级队列, 其中 put 和 take 方法是阻塞, offer 和 poll 是非阻塞, 对于无边界的队列而言永远不会阻塞

同步工具类: 信号量(Semaphore), 栅栏(Barrier, 调用 await 的线程会中断, 直到等于 partion 时再开始执行), 闭锁(Latch, 调用 await 的线程会中断, 直到 countDown 为 0 被唤醒)

区别性: 闭锁是一次性的, 不能反复使用, 栅栏可以多次使用; 闭锁用于等待事件,而栅栏用于等待其他线程

工作密取: 消费者各自拥有一个双端队列 Deque, 如果一个消费者完成了自己双端队列中的全部工作,那么他就可以从其他消费者的双端队列末尾秘密的获取工作。具有更好的可伸缩性, 使用场景为网络爬虫

CopyOnWrite 容器适用于读多写少的并发场景, set 方法加锁, get 方法不加锁, set 方法是 copy 一份新的内容, 修改, 再讲原引用指向新的, 这篇文章一目了然: https://www.cnblogs.com/dolphin0520/p/3938914.html

结构化并发程序

无限制创建线程的不足: 线程生命周期开销非常高, 资源消耗大, 稳定性低

Executor 线程池框架, 抽象了执行任务的主要对象不是 Thread, 而是 Executor, 其实现了对生命周期的支持(ExecutorService), 以及统计信息收集, 应用程序管理机制和性能监控等机制, Executor 基于生产者 - 消费者模式

Executors 是线程池的工厂类, 本质上是初始化 ThreadPoolExecutor, 实际开发的时候应该避免使用工厂类

ExecutorService 封装了线程生命周期接口

线程中断, 取消, 关闭

调用 interrput 并不意味着立即停止目标线程正在进行的工作, 而只是传递了请求中断的消息, 通常, 中断是实现取消的最合理方式

通过 Future 和 Executor 框架可以构建可取消的任务

ExecutorService 提供了正常关闭 shutdown() 和 立刻关闭 shutdownNow()两种方法, 正常关闭速度慢但线程安全, 它会等到队列中所有任务都执行完成后才关闭

还可以通过 JVM 钩子关闭线程, 但是极度不推荐, 线程可分为两种: 普通线程和守护线程

使用 newTaskFor 钩子函数来改进用来封装非标准取消的方法。这是 ThreadPoolExecutor 的新特性, 当提交一个 callable 给 ExecutorService 时,submit 返回一个 Future,可以用 Future 来取消任务。

newTaskFor 钩子是一个工厂方法,创建一个 Future 来代表任务,这个 Future 属于由 FutureTask 实现的 RunnableFuture 接口,这个接口可以自定义 cancel 方法,实现自定义的取消方式

线程池

线程池大小如何界定, 是 Fix 还是 Cache?

  • 计算密集型: 线程池大小为: Ncpu + 1, I/O 密集型: 线程池大小为: 2 * Ncpu

  • newFixedThreadPool

    • corePoolSize == maximumPoolSize

    • workQueue 是无界队列(容易造成资源耗尽)

    • 当需要限制当前任务的数量以满足资源管理需求时, 那么可以选择固定大小的线程池(服务器网络请求限制)

  • newCachedThreadPool

    • workQueue 是有界队列, 饱和之后执行拒绝策略, 而实现中采用了 SynchronousQueue(同步移交)

    • 提供比固定大小的线程池更好的排队性能(而不是一直hang住等待线程)

1
2
3
4
5
6
7
public ThreadPoolExecutor(int corePoolSize, // 基本大小
int maximumPoolSize, // 最大大小
long keepAliveTime, //
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}

只有当任务相互独立时, 为线程池或者工作队列设置界限才是合理的, 如果任务之间存在依赖性, 那么有界的线程池或队列就可能导致线程 “饥饿” 死锁问题

活跃性, 性能和测试

死锁与线程获得锁的顺序有关, 在数据库系统的设计中考虑了死锁的检测以及恢复, 即随机选择放弃一个事务, 让其他事务执行

可伸缩性是指: 当增加计算资源时(例如 CPU, 内存, 存储容量或 I/O 带宽), 程序的吞吐量或者处理能力能相应增加

缩小锁的范围(方法 -> 对象 -> 并发容器), 减小锁的粒度, 锁分段(ConcurrentHashMap)

锁的实现

锁干了什么? 答: 对共享资源的互斥性以及内存可见性

内置锁有什么功能局限? 答: 无法中断一个正在等待获取锁的线程, 或者无法在请求获取一个锁时无限等待下去, 阻塞加锁

显示锁有什么好处? 答: 锁粒度更低, 可实现如 ConcurrentHashMap 式的分段加锁

内置锁: Synchronized 和 volatile

显示锁: ReentrantLock

内置锁的实现原理

synchronized 有以下3种应用方式

  • 修饰实例方法,作用于当前实例加锁

  • 修饰静态方法,作用于当前类对象加锁

  • 修饰代码块,指定加锁对象,对给定对象加锁

同步代码块的实现是基于进入和退出管程(Monitor)对象实现, 翻译成字节码是 monitorenter 和 monitorexit

同步方法的实现是JVM通过ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法

volatile 的实现原理: 不会将该变量上的操作重排序(内存屏障), 也不会放入缓存寄存器中

volatile语义中的内存屏障:

  • 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;

  • 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;

Synchronized 可确保原子性和可见性, volatile 只能保证可见性(++count 无法保证原子性)

显示锁的实现原理

1
2
3
4
5
6
7
8
9
//juc lock
public interface lock{
void lock();
void lockInterruptibly() throws InterruptedException; //中断锁
boolean tryLock(); //轮询锁
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException; //定时锁
void unlock();
Condition newCondition();
}

公平性: 在公平锁上, 线程将按照它们发出请求的顺序来获取锁, 非公平则允许插队, 取决于场景中挂起和恢复线程的开销大小, 在激烈竞争的情况下, 则采取非公平锁比较好

实现: CopyOnWriteArrayList

一个资源可以被多个读操作访问, 或者被一个写操作访问, 但两者不能同时进行

内部是非公平锁, 读不加锁, 写加锁

显示锁的底层是 AQS(AbstractQueuedSynchronizer), 它使得一组线程(称之为等待线程集合)能够通过某种方式来等待特定的条件变成真, 并自动唤醒, 与传统队列不同的是它的对象是一个个正在等待的线程

总结: 与内置锁相比, 显示锁提供了一些扩展功能, 在处理锁的不可用性方面有着更高的灵活性

读写锁允许多个读线程并发地访问被保护的对象, 当访问以读取操作为主的数据结构时, 它能够提高程序的可伸缩性

无锁

CAS(Compare-and-Swap): 操作系统级别支持的一种无锁操作, 通过代码中显示的重试机制来实现

1
2
3
4
5
6
7
public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value; //当前值
if(oldValue == expectedValue){ //当前值 == 期望值 才改变当前值
value = newValue;
}
return oldValue; //返回当前值
}

原子变量类是基于 CAS 实现的, 比锁的粒度更细, 量级更轻, 以下是 i++ 原子性的实现

1
2
3
4
5
int v;
do{
v = value.get();
}while(v != value.compareAndSwap(v, v + 1));
return v + 1;

在高度竞争的情况下, 锁的性能将超过原子变量的性能, 但通常竞争量很小的情况下, 原子变量更优, 这是因为锁在发生竞争时会挂起线程, 降低了CPU使用率, 调用原子变量的类可自行负责竞争管理(重试, 退让)

如需转载,请注明出处

基础知识-MySQL

MySQL架构概述

MySQL 最重要、最与众不同的特性是它的存储引擎架构,这种架构的设计将查询处理(Query Processing)及其他系统任务(Server Task)和数据的存储/提取相分离。本文概要地描述了MySQL的服务器架构以及各存储引擎之间的主要区别。

MySQL逻辑架构

下图展示了MySQL的逻辑架构图。

MySQL的逻辑架构分为三层。

  • 第一层: 最上层的服务并不是MySQL所独有,大多数基于网络的客户端/服务器的工具或者服务都有类似架构。比如连接处理、授权认证、安全等。

  • 第二层: 大多数MySQL的核心服务都在,包括查询、分析、优化、缓存以及所有的内置函数,所有的跨存储引擎的功能都在这一层实现:存储过程、触发器、视图。

  • 第三层: 包含了存储引擎。存储引擎负责MySQL中数据的存储和提取。服务器通过API与存储引擎进行通信,这些接口屏蔽了存储引擎间的差异性,使得这些差异对上层的查询过程透明。存储引擎不会去解析SQL(除InnoDB会解析外键之外),不同存储引擎之间也不会相互通信,而只是简单地响应上层服务器的请求。

连接管理与安全性

每一个客户端连接都会在服务器进程中拥有一个线程,这个连接的查询只会在这个单独的线程中执行。服务器会缓存线程,因此不需要为每个新建的连接创建和销毁线程。

但客户端连接到MySQL服务器时,服务器需要对其进行认证。认证基于用户名、原始主机信息和密码。一旦客户端连接成功,服务器会继续验证该客户端是否具有执行某个特定查询的权限。

优化与执行

MySQL会解析查询,并创建内部数据结构(解析树),然后对其进行各种优化,包括重写查询、决定表的读取顺序,以及选择合适的索引等。

优化器不关心表使用的哪种存储引擎,但存储引擎对于优化查询是有影响的。

对于SELECT语句,在查询解析之前,服务器会先检查查询缓存(Query Cache),如果能够在其中找到对应的查询,服务器就不必再执行查询解析

并发控制

读写锁

  • 共享锁 读锁 非阻塞
  • 排他锁 写锁 阻塞

写锁比读锁有更高的优先级,可以插入到读锁队列的前面

锁粒度

锁策略:在锁的开销和数据的安全性之间寻求平衡

  • 表锁 开销最小
  • 行锁 最大并发度,最大锁开销
事务

事务是一组原子性的 SQL 查询,或者说一个独立的工作单元。事务内的语句要么全部执行成功,要么全部执行失败。

  • START TRANSACTION 开始事务
  • COMMIT 提交事务
  • ROLLBACK 撤销事务

事务的特征ACID

  • atomicity 原子性 一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。
  • consistency 一致性 数据库总是从一个一致性的状态转换到另外一个一致性的状态。
  • isolation 隔离性 通常来说,一个事务所做的修改在最终提交前,对其它事务是不可见的。
  • durability 持久性 一旦事务提交,则其所做的修改就会永久保存到数据库中

隔离级别

SQL标准定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。

  • READ UNCOMMITED(未提交读)

事务中的修改即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这被称为脏读(Dirty Read)。

  • READ COMMIT(提交读)

一个事务从开始到提交之前,所做的任何修改对其它事务都是不可见的。这个级别有时候也叫不可重复读(nonrepeateble read),因为两次执行相同的查询,可能会得到不一样的结果。

  • REPEATABLE READ(可重复读)

解决了脏读和不可重复读,理论上,无法解决幻读的问题。
幻读(Phantom Read),是指当某个事务在读取某个范围内的记录时,另一个事务又在该范围内插入了新的记录,当之前的事务再次读取范围的记录时,会产生幻行。

InnoDB 和 XtraDB 存储引擎通过多版本并发控制解决了幻读的问题。
可重复读是 MySQL 的默认隔离级别

  • SERIALIZABLE(可串行化)

SERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免前面说的幻读的问题。

死锁

死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定一个资源时,也会产生死锁。
InnoDB 目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。

死锁的产生有双重因素:有些是真正的数据冲突,有些是由于存储引擎的实现方式导致的。死锁发生后,只有部分或者完全回滚其中一个事务才能打破死锁。大多数情况下,只需重新执行因死锁回滚的事务即可。

事务日志

事务日志可以帮助提高事务的效率。使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到硬盘。事务日志采用的是追加的方式,事务日志持久后,内存中被修改的数据在后台可以慢慢刷回到磁盘。通常称为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘。

如果数据的修改已经记录到日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据。

MySQL中的事务

MySQL 中提供了两种事务型的存储引擎:InnoDB 和 NDB Cluster。另外还有一些第三方的存储引擎,比如 XtraDB 和 PBXT。

MySQL 默认采用自动提交(AUTOCOMMIT)模式。也就是说,如果不是显式地开始一个事务,则每个查询都被当做一个事务执行提交操作。在当前连接中,可以通过设置AUTOCOMMIT变量来启用或者禁用自动提交模式。还有一些命令,在执行前强制执行 COMMIT 提交当前的活动事务。如A LTER TABLE、LOCK TABLES 等。MySQL 可以通过执行 SET TRANSACTION LEVEL 命令来设置隔离级别。MySQL能够识别所有的4个ANSI隔离级别,InnoDB 引擎也支持所有的隔离级别。

MySQL 服务器层不管理事务,事务是由下层的存储引擎实现的。所有在同一个事务中,使用多种存储引擎是不可靠的。
InnoDB 采用的两阶段锁定协议(two-phase locking protocol)。在事务执行过程中,随时都可以执行锁定,锁只有在执行COMMIT或者ROLLBACK 的时候才会释放,并且所有的锁是在同一时刻被释放。InnoDB 会根据隔离级别在需要的时候自动加锁。InnoDB 也支持通过特定的语句进行显示锁定,如SET … LOCK IN SHARE MODE等。

多版本并发控制(MVCC)

可以认为MVCC是行级锁的一个变种,但是在很多情况下避免了加锁操作,开销更低。

MVCC的实现,是通过保存数据在某一个时间点的快照来实现的。不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始时间的不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

不同存储引擎的 MVCC 实现不同,典型的有乐观并发控制和悲观并发控制。InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。存储的不是实际的时间值,而是系统版本号。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。保存这两个额外的系统版本号,使大多数读操作都可不用加锁。不足之处是每行记录都需要额外的存储空间。MVCC 只在 REPEATABLE READ 和 READ COMMITTED 两个隔离级别下工作。

存储引擎

InnoDB

MySQL默认的事务型引擎。
InnoDB采用MVCC来支持高并发,并且实现了四个标准的隔离级别。
默认REPEATABLE READ,通过间隙锁(next-key locking)策略防止幻读的出现。间隙锁使得InnoDB不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。

InnoDB 是基于聚簇索引建立的,对之间查询有很高的性能。它的二级索引(非主键索引)中必须包含主键列,如果主键列很大的话,其它所有索引都会很大。

从磁盘读取数据时采用可预测性预读,能够自动在内存中创建hash索引以加速读操作的自适应哈希索引(adaptive hash index),以及能够加速插入操作的插入缓冲区(insert buffer)等。
参见 官方手册《InnoDB事务模型和锁》

InnoDB可以通过一些机制和工具支持真正的热备份,Oracle的MySQL Enterprise Backup、Percona的XtraBackup可以做到这一点。MySQL的其它存储引擎不支持热备份,要获取一致性视图需要停止对所有表的写入。

MyISAM

MyISAM 支持全文本索引、压缩、空间函数(GIS)等。不支持事务和行级锁,崩溃后无法安全恢复。

MyISAM 将表存在两个文件中:数据文件(.MYD)索引文件(.MYI)。如果表是变长行,则默认配置只能处理256TB的数据。

特性
加锁与并发
对整张表而不是行加锁。但是在表有读取查询的同时,也可以往表中插入新的记录(并发插入,concurrent insert)

修复
CHECK TABLE mytabl 检查表的错误。
REPAIR TABLE mytable 进行修复。
当MySQL服务器关闭,可以用myisamchk命令行工具进行检查和修复。

索引
即使是 BLOB 和 TEXT 等长字段,也可以基于其前 500个 字符创建索引。MyISAM也支持全文本索引,是一种基于分词创建的索引,可以支持复杂的查询。

延迟更新索引键(Delay Key Write)
在创建 MyISAM 表的时候,如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立刻将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区(in-memory key buffer),只有在清理键缓存区或者关闭表的时候才会将对应的索引块写入到磁盘,极大地提升了写入性能。

压缩表
如果表在创建并导入数据后,不会再进行修改操作,那么这样的或许适合采用MyISAM压缩表。可以使用 myisampack 对 MyISAM 表进行压缩。压缩表能极大减少磁盘空间占用和磁盘 I/O。

MySQL内建的其它存储引擎

Archive、Blackhole、CVS、Frederated、Memory、NDB 集群引擎

总结

MySQL 拥有分层的架构,上层是服务器层的服务和查询执行引擎,下层则是存储引擎。

MySQL 最初基于 ISAM 构建(后来被 MyISAM 取代),其后陆续添加了更多的存储引擎和事务支持。从 MySQL 5.5 起,InnoDB 成为默认的存储引擎。InnoDB 对绝大多数用户来说都是最佳选择。

如需转载,请注明出处

网络基础知识

详解 HTTP

关键字: TCP/IP 四层模型

TCP/IP 协议族是指通常与网络相关联的协议集合, 分为四层: 应用层(HTTP, DNS, FTP, SSH, SMTP), 传输层(TCP, UDP), 网络层(IP, ARP), 数据层(操作系统、网卡、驱动、光纤等)

HTTP 数据 -> TCP 首部 (HTTP 数据) -> IP 首部 (TCP 首部 (HTTP 数据)) -> 以太网首部 (IP 首部 (TCP 首部 (HTTP 数据)))

我们把这种数据信息包装的做法称之为 封装(encapsulate)

ARP 是一种解析地址的协议, 根据 IP 查出 MAC 地址

TCP 三次握手(SYN - SYN/ACK - ACK) 后建立连接, 进行 HTTP 数据通信, 在四次挥手后断开连接(FIN - ACK - FIN - ACK)

URL 是 URI 的子集

关键字: HTTP 协议

HTTP 是不保存状态的协议(stateless), Session, Cookie 技术解决于此

HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层规范, 规范把 HTTP 请求分为三个部分:状态行、请求头、消息主体

支持的 Method: GET/POST/PUT/DELETE/HEAD/OPTIONS/TRACE/CONNECT

查询服务端支持的方法: OPTIONS * HTTP/1.1

每次访问都要建立 TCP 的四次握手, 因此 keep-alive 可以持久化连接, 期间进行数据通信

管道(pipelining) 是将多个 HTTP 请求整批提交的技术(请求1 -> 请求2 -> 请求3 -> 响应1 -> 响应2 -> 响应3), 仅 HTTP/1.1 支持此技术, 只有 GET 和 HEAD 请求可以进行管线化

关键字: HTTP 报文

HTTP 报文分为: 报文首部 + CRLF + 报文主体, 其中请求报文的主体一般为空

提升传输速率的方式有: 压缩 和 分块传输

关键字: HTTP 状态码

1XX: 信息性状态码(Informactional), 接收的请求正在处理

2XX: 成功状态码(Success), 请求正常处理完毕

3XX: 重定向状态码(Redirection), 需要进行附加操作以完成请求

4XX: 客户端错误状态码(Client Error), 服务器无法处理请求

5XX: 服务器错误状态码(Server Error), 服务器处理请求出错

关键字: HTTP 首部

Content-Type: 标识报文主体的对象类型

Cache-Control: 缓存控制, 强刷是 no-cache

Connection: keep-alive or close 持久化连接

Upgrade: 用于检测 HTTP 协议及其他协议是否可使用更高的版本进行通信

Accept: text/html, text/plain, text/css, application/xhtml+xml, application/xml, image/jpeg, image/gif, image/png, video/mpeg, video/quicktime, application/octet-stream, application/zip

关键字: HTTPS

HTTPS = 加密 + 认证 + 完整性保护 + HTTP

HTTPS 并非是应用层的一种新协议, 只是 HTTP 通信接口部分用 SSL(Secure Socket Layer) 和 TLS(Transport Layer Security) 协议代替而已, HTTP - > SSL -> TCP = HTTPS

SSL 是广泛使用的网络安全技术, 核心就是公开密钥加密的加密处理方式

HTTPS 通信步骤: 1-9 步建立 SSL, 10 - 11 进行数据传输, 12 断开

HTTPS 慢, 认证需要证书, 贵

关键字: HTTP 认证

四种方式: BASIC, DIGEST, SSL客户端认证, FormBase 基于表单认证

关键字: 追加协议

SPDY: 多路复用流, 赋予请求优先级, 压缩 HTTP 首部, 推送功能, 服务器提示功能

WekSocket: 使用浏览器全双工通信 ws://

WebDAV: 服务器管理文件

HTTP/2.0

关键字: 攻击

XSS: 被动攻击, HTML内置好恶意代码, 等待用户访问触发

CSRF: 被动攻击, 利用已完成认证的用户(Cookie)进行非预期的操作

详解 TCP/IP

关键字: OSI 参考模型

应用层: 针对特定应用的协议

表示层: 设备固有数据格式和网络标准数据格式的转换

会话层: 通信管理, 负责建立和断开通信连接

传输层: 管理两个节点之间的数据传输, 负责可靠传输

网络层: 地址管理与路由选择

数据层: 互连设备之间传送和识别数据帧

物理层: 物理信号(01高低电压)传输

关键字: TCP/IP 协议群

包含了: 应用协议(HTTP, SMTP, FTP, TELNET, SNMP), 传输协议(TCP, UDP), 网际协议(IP, ICMP, ARP), 路由控制协议(RIP, OSPF, BGP)

RFC 全称为 Request For Comment: 征求意见文档, 目前在互联网上有各种 RFC 的文档标识声明

UDP 应用于 广播, 多播, 单播, 任播等通信和多媒体领域

FTP 进行文件传输时会建立两个 TCP 连接, 分别是发出传输请求时所要用到的控制连接和实际传输数据所要用到的数据连接

关键字: 数据链路

网络拓扑: 总线型, 环型, 星型, 网状型

半双工与全双工通信: 只发送或者只接收的通信方式称为半双工, 同时发送接收的称为全双工

无线通信种类: RFID, 蓝牙(PAN), Wi-Fi(LAN), WiMAX(MAN), 通信网络(WAN)

PPP 指 1对1 连接计算机的协议, 点对点

关键字: IP 协议

IP(Internet Protocol): IP 寻址、路由、IP 分包与组包 三大作用模块, IP 为了实现简单化与高速化采用面向无连接的方式

IP 地址: 32位正整数, 最多允许 43 亿台计算机连接到网络, 每个网卡(NIC)都会配置一个地址, 一台路由器至少两个地址

网络标识表明网段, 同一网段内的网络标识必须相同, 主机标识表明主机号, 同一网段内不允许重复

192.168.128.11/24 24 表示从头数到第几位为止属于网络标识, 11 是主机标识, 路由器根据网络标识转发包

根据网络标识位数不同而分类: A类(8位, 首位0), B类(16位, 首位10), C类(24位, 首位110), D类(32位: 多播)

主机标识全部为1, 就成了广播地址

子网掩码: 实际上就是将原来 A 类, B类, C类等分类中的主机地址部分用作子网地址, 可将原网络分为多个物理网络

32位, 对应 IP 地址网络标识部分全部为 1, 对应 IP 地址主机标识部分全部为 0

路由: netstat -rn

IPv6 地址: 128位正整数

关键字: DNS, ICMP, ARP, NAT

DNS: 如同互联网中的分布式数据库, 主机名与 IP 地址的对应信息叫做 A 记录, 反之, 从 IP 地址检索主机名称的信息叫做 PTR
CNAME: 主机别名对应的规范名称, A: 主机名的IPv4地址, AAAA: 主机名的IPv6地址

APR: 用于IP 解析为 MAC 地址的协议, RARP: MAC 地址定位 IP 的协议

ICMP: 用于确认 IP 包是否成功送达目标地址, 通知在发送过程中 IP 包被废弃的具体原因, 改善网络设置等

DHCP: 用于分配 IP 地址, 即插即用

NAT: 用于在本地网络中使用私有地址, 在连接互联网时转而使用全局 IP 地址的技术

关键字: TCP, UDP

通信中通常采用 5 个信息来识别一个通信, 他们是: TCP 首部(源 IP 地址, 目标 IP 地址, 协议号), IP 首部(源端口号, 目标端口号), 只要其中某一项不同, 则被认为是其他通信

端口号由其使用的传输层协议决定, ssh 22, https 443, ftp 21, dns 53, snmp 161

UDP 适用于 包总量较少的通信(DNS, SNMP等), 视频、音频等多媒体通信(即时通信), 限定于 LAN 等特定网络中的应用通信, 广播通信

TCP 确认应答和窗口控制

  • TCP 通过肯定的确认应答(ACK) 实现可靠的数据传输

  • TCP 通过窗口提高传输速度, 窗口大小是指无需等待确认应答而可以继续发送数据的最大值

因此, 在窗口比较大, 又出现报文段丢失的情况下, 同一个需要的确认应答将会被重复不断的返回, 而发送端主机如果连续3次收到同一个确认应答, 就会将其所对应的数据进行重发, 这种机制比之前提到的超时管理更加高效

拥塞控制及慢启动

简单的说,就是TCP传输过程中,为了避免一下子将网络冲爆,引入的机制。而慢启动,顾名思义,一开始慢慢传,发现没有问题,再增加传输速度。而一旦发现传输有超时,协议会认为网络拥堵,又降低传输速度。
起始的传输速度,就是由初始拥塞窗口,initial congestion window,简称initcwnd参数控制的

关键字: 路由协议

路由算法: 距离向量算法, 链路状态算法

主要路由协议: RIP(base on UDP), RIP2(base on UDP), OSPFbase on IP), EGP(base on IP), BGP(base on TCP)

如需转载,请注明出处

面试基础问题

数据库

  1. 查询表中重复字段

SELECT Distinct(email) FROM Person A WHERE EXISTS(SELECT email FROM Person B WHERE A.email = B.email HAVING count(*)>=2)

  1. 索引原理, SQL执行策略

Mysql目前主要有以下几种索引类型:FULLTEXT, HASH, BTREE, RTREE

索引我们分为四类来讲 单列索引(普通索引,唯一索引,主键索引)、组合索引、全文索引、空间索引

最左前缀原则: 在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边, 比如复合索引(a, b, c)会作用于包含 a,b,c/a,b/a 的 where 语句, 因此 a 位置应该是查询最频繁的列

where 执行顺序是从左往右执行的,在数据量小的时候不用考虑,但数据量多的时候要考虑条件的先后顺序,此时应遵守一个原则:排除越多的条件放在第一个

from 语句多表做笛卡尔乘积, 从右往左, 小数量的表放在最右边

如何成功避开索引:

  • like语句,‘%w’不会使用索引,‘w%’会使用索引

  • 列类型为字符串类型,查询时没有用单引号引起来

  • 在where查询语句中使用表达式

  • 在where查询语句中对字段进行NULL值判断

  • 在where查询中使用了or关键字, myisam表能用到索引, innodb不行;(用UNION替换OR,可以使用索引)

  • where中复合索引未按顺序查询的

  1. 事务的实现

START TRANSACTION …. COMMIT …. ROLLBACK

ACID, 其中隔离性通过 锁 实现, 一致性通过 undo log 实现, 原子性和持久性通过 redo log 来实现

redo(重做) 和 undo(回滚) 比较:

  • 都是恢复操作: redo: 恢复提交事务修改的页操作/ undo: 回滚行记录到某个特定版本

  • 记录内容不同: redo: 是物理日志,记录的是物理的修改操作/ undo: 是逻辑日志,根据每行记录进行记录

  • 读取方式不同: redo : 在数据库运行时,不需要读取操作(注:数据库恢复时,才用redo)/ 在数据库运行时,需要随机读取(注:回滚时用)

两种隔离级别: READ COMMIT、REPEATABLE READ

A, B 账户各有 1000 元, 当 A 账户的某一个事务修改为 800 元并提交, B 账户的一个事务在提交前后读取到两个不同的 A 账户金额, 即: 同一个事务中查询结果未能保持一致

未解决上述问题, 使得当前事务每次读取的结果集都相同,而不管其他事务有没有提交, 但出现幻读的情形引出 MVCC

多版本并发控制: MVCC 是通过保存数据在某个时间点的快照来实现的

InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间。这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID.

MySQL 锁:

读锁:也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

写锁:又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

表锁:操作对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb),是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其他事务可读不可写,若加写锁,则其他事务增删改都不行。

行级锁:操作对象是数据表中的一行。是MVCC技术用的比较多的,但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器。但行级锁对系统开销较大,处理高并发较好。

  1. 优化点

为什么不使用 select *

  • 不需要的字段会增加数据传输的时间,即使mysql服务器和客户端是在同一台机器上,使用的协议还是tcp,通信也需要额外的时间。

  • select 可能会获取到自己不需要的列,如果以后表结构修改了,同样也可能会对代码产生影响。比如表增加了一个字段,而我代码与其对接的对象属性里没有这个字段,select 就会导致报错

  1. drop, truncate 和 delete

drop 删除表数据和定义, truncate 删除表数据, 不可 rollback, 使用的系统和事务日志资源少, delete 删除表数据, 按行删, 可接 where 语句, 可 rollback

  1. binlog

记录数据库增删改, 不记录查询的二进制日志, 用于数据恢复

语法

  1. 集合类的实现

TreeMap 红黑树, 默认按 key 升序遍历

HashMap threshold(最大容量) = len(初始化容量) * load factor(负载因子)

LinkedHashMap 双向链表加HashMap, accessOrder 为 false 维持 Key 的插入顺序遍历, 为 true 时可实现 LRU

网络

HTTP1.1的Pipeling方式本质是将请求串行化处理,后面的请求必须等待前面请求的返回才能被执行,一旦有某请求超时等,后续请求只能被阻塞,即线头阻塞;

HTTP/2多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行;

数据结构

BST 二叉排序树, O(logN) 操作复杂度, 但是依赖于输入的建树顺序, 容易退化成 O(N)

AVL 平衡二叉排序树, 左右子树高度差小于1, 确保复杂度为O(logN)

B树家族, 多路搜索树, 为什么B+树适合索引: 数据存在叶节点, 每次检索都是到根到叶节点的过程, 其他节点存储的数据就是索引

  • 关键字的数量不同;B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用,但是B树虽然也有m个子结点,但是其只拥有 m-1 个关键字。

  • 存储的位置不同;B+ 树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是完整的数据,但是B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。

  • 分支结点的构造不同;B+ 树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁盘块的偏移量),也就是说内部结点仅仅包含着索引信息。

  • 查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。

BRT 红黑树, 自平衡二叉搜索树, 变色/左旋/右旋, 插入、删除、查找都是 O(logN)

框架

Bean 生命周期(模板答案): 通过工厂创建/通过上下文创建

  • 实例化一个 Bean(也就是 new 操作)

  • 设置 Bean 的属性值(也就是 IOC 注入, 从 xml 中读取, 调用 set 接口设置)

  • 如果 Bean 实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String) 方法(也就是设置 Bean 的名字)

  • 如果 Bean 实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory (设置 Bean 的创建工厂)

  • (上下文独有) 如果 Bean 实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法 (设置上下文)

  • 如果 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessBeforeInitialization(Object obj, String s)方法(修改 Bean 内容, 还有 afterPropertiesSet 等等)

  • 如果 Bean 在配置文件中配置了 init-method 属性会自动调用其配置的初始化方法(调用 init-method 初始化方法)

  • 如果 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessAfterInitialization(Object obj, String s)方法

  • 如果 Bean 实现了 DisposableBean 接口,会调用那个其实现的destroy()方法;

  • 如果 Bean 配置了destroy-method 属性,会自动调用其配置的销毁方法(调用 destroy-method 销毁方法)

Bean 单例实现

发生在 AbstractBeanFactory 的 getBean -> doGetBean -> getSingleton 进行bean的创建, 双重判断加锁的单例模式

https://www.cnblogs.com/zhaoyan001/p/6365064.html

静态常量, 静态代码块线程安全, 但不是 lazy-init

getInstance 加锁, 单锁, 线程不安全

double-check 加锁, 线程安全

推荐静态内部类, 调用内部类时候才会创建

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {

private Singleton() {}

private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}

Spring 事务

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

编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中

异常处理

Service 层处理事务回滚(@Transactional), Controller 处理业务异常(通常自己封装Runtime) 然后用 @ControllerAdvice + @ExceptionHandler 进行友好展示

AOP的两种实现

SpringAOP 是 Spring 这个庞大的集成框架为了集成 AspectJ 而出现的一个模块

从实现上来讲

SpringAOP 是 基于代理实现的(Proxying), 可选两种方式

  • JDK动态代理(Dynamic Proxy)

    • 基于标准JDK的动态代理功能

    • 只针对实现了接口的业务对象

  • CGLIB

    • 通过动态地对目标对象进行子类化来实现AOP代理,上面截图中的SampleBean$$EnhancerByCGLIB$$1767dd4b即为动态创建的一个子类

    • 需要指定@EnableAspectJAutoProxy(proxyTargetClass = true)来强制使用

    • 当业务对象没有实现任何接口的时候默认会选择CGLIB

AspectJ 是 基于字节码操作(Bytecode Manipulation)

@Around -> @Before -> @After -> @AfterReturning

其中 Around 参数为 ProceedingJoinPoint, 需要调用 proceedingJoinPoint.proceed() 才可调用方法

I/O 与 进程

进程有内核态和用户态两种运行方式, 用户态可以使用 CPU 和内存来完成一些任务, 内核态可以对硬件外设进行操作(读取磁盘文件、发送数据网络)

I/O 模型

服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:

  • 同步阻塞IO(Blocking IO):即传统的IO模型。

  • 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。

  • 多路复用IO(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。

  • 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。

同步和异步的区别在于多个任务和事件发生时,一个事件的发生或执行是否会导致整个流程的暂时等待

阻塞和非阻塞的区别在于当发出请求一个操作时,如果条件不满足,是会一直等待还是返回一个标志信息。

缓存

Redis与Memcached的区别与比较

  • Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。

  • Redis支持数据的备份,即master-slave模式的数据备份。

  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中

  • Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型。

Tair

  • MDB: 适用容量小(一般在M级别,50G之内), 读写QPS高(万级别)的缓存场景, 是内存型产品

  • RDB: 可持久化, Redis

  • LDB: 适用于确实有持久化需求,读写QPS较高(万级别)的应用场景, 目前使用的 SSD 硬盘, 基于开源的 LevelDB 引擎

  • PDB: 即将上线

  • FASTDUMP: ODPS 快速写入到分布式缓存Tair的解决方案

MDB: key 最大 1K, value 最大 1M, 是典型 slab 的最大限制(内存分配机制)

MDB 架构: client, 两台 ConfigServer(互为主备, 路由), 多个 DataServer(存储引擎)

两台 Configserver 互为主备。通过和 DataSrver 之间的心跳检测获取集群中存活可用的 DataServer,构建数据在集群中的分布信息(对照表)。

Dataserver 负责数据的存储,并按照 Configserver 的指示完成数据的复制和迁移工作。Client 在启动的时候,从 Configserver 获取数据分布信息,根据数据分布信息,和相应的 Dataserver 进行交互,完成用户的请求。

ConfigServer 的考点: 一致性 Hash

相邻的虚拟节点可能是多台不同的物理机,这样可以分散压力。无虚拟节点的话,只是简单的把压力转给另一台物理机

数据结构

如需转载,请注明出处

Linux 常用命令

系统信息类
1
cat /proc/meminfo | grep MemTotal //查看系统内存信息
系统日志类

系统日志一般都存在/var/log下

1
2
3
cat /etc/syslog.conf //查看系统日志配置位置

dmesg //查看kernel信息
如需转载,请注明出处

读书笔记-深入分析 Java Web 技术内幕(三)

第三部分: Java 服务端技术

1. Servlet 工作原理解析

Servlet 是 Java Web 的核心技术, Servlet 容器是一个独立发展的标准化产品: Jetty, Tomcat, JBoss 等

以 Tomcat 为例, Context 容器负责管理 Servlet 容器(隶属于组件 Container), 一个 Context 对应一个 Web 工程

1
2
3
4
5
6
7
Context ctx = new StandardContext();
...
ctx.addLifecycleListener(new DefaultWebXmlListener())
...
ContextConfig ctxCfg = new ContextConfig();
ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");
...

Tomcat 的启动逻辑是基于观察者模式设计的, 所有的容器都会继承 Lifecycle 接口, 所有容器的修改和状态变更会有由它去通知已经注册的观察者(Listener)

Tomcat 的启动创建了 Context 容器, 在 Context 容器中, Web 应用得以初始化, 而这个初始化的过程中, web.xml 决定了 Servlet 对象的创建和初始化

1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>yunos-mdm</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:conf/spring/mvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup> // 如果大于 0, 那么在 Context 容器启动时就会被实例化
</servlet>

Servlet 的体系结构围绕着: ServletConfig, ServletRequest, ServletResponse, ServletContext(上下文) 四个类

Servlet 最终的工作原理是靠映射完成的, 这个类就是 org.apache.tomcat.util.http.mapper

Listener 是基于观察者设计模式的, Filter 是基于责任链设计模式的

当前 Cookie 有两个版本(0,1), 不同的浏览器支持 Cookie 的数量和大小都不同, Cookie 被加到 header 中发生在构建响应 Response 中, 基于这些限制, Session 则创建在服务端, 以一个 id 作为标识, 客户端使用这个标识可通过 URL Path Paramter, Cookie 中的一个字段来实现

服务器管理 Session 的容器: org.apache.cataline.Manager, 它来负责接管过期回收, 服务器关闭序列化到磁盘(SESSIONS.ser)等问题

Session 的安全性优于 Cookie, 适合存储用户隐私和重要的数据, 引出分布式 Session 框架的核心解决要点

  • 订阅服务器(如 Zookeeper) 负责 Cookie 项的统一配置推送

  • 分布式缓存系统(如 Tair, Memcache) 负责 Session 的快速存储和读取, 而实现就是配置一个 SessionFilter 在创建 Session 之前进行拦截读存等操作

处理 Cookie 被盗取的情况: 当用户登录成功后根据用户的私密信息生成一个签名, 以表示当前这个唯一的合法登录状态, 即 Session 签名; 处理 Cookie 压缩的情况: 可配置一个 Filter 在页面输出是对 Cookie 进行全部的压缩或者部分压缩, 使用 gzip 和 defalte 算法, 节省带宽; 处理表单重复提交的问题: crsf 的 hidden token 验证

3. Tomcat 的系统架构与设计模式

Tomcat 核心架构: Server - 若干个 Service - N 个 Connector 和 1 个 Container, 整个 Tomcat 生命周期由 Server 控制

1
2
3
4
// conf/server.xml
Service 是 Catalina
// 其中 Context 是在 context.xml 中
Container 是 Engine - Host - Context - Wrapper

实现上来讲 StandServer、StandService 配合 LifeCycle 接口实现全程事件监听和触发控制

Connector 组件是 Tomcat 中的两个核心组件之一, 它的主要任务是负责接收浏览器发过来的 TCP 的连接请求, 创建一个 Request 和 Response 对象分别用于和请求端交换数据, 然后会产生一个线程来处理这个请求, 而处理这个请求的线程就是 Container 组件要做的事情了

1
2
3
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="15000" redirectPort="8443" maxParameterCount="1000"
maxThreads="250" maxPostSize="2097152" acceptCount="200" useBodyEncodingForURI="true" />

Tomcat 使用的设计模式有

  • 外观模式(门面模式): Request 和 Response 对象封装、StandardWrapper 到 ServletConfig 封装、ApplicationContext 到 ServletContext 封装, 一句话, Facade 类控制暴露的数据

  • 观察者模式: LifeCycle 接口接管 Tomcat 生命周期

  • 命令模式: Connector 是通过命令模式调用 Container 的

  • 责任链模式: Container 是通过责任链模式一步步传递给 Servlet 的

4. Jetty 的工作原理解析

Jetty 应该是目前最活跃也很有前景的一个 Servlet 引擎, 任何被扩展的组件都可以作为一个 Handler 添加到 Server 中, Jetty 将帮你管理这些 Handler

整个 Jetty 核心是由 Server 和 Connector 两个组件构成, Server 组件是基于 Handler 容器工作的, 关联了 Connector, 生命周期也是观察者设计模式(LifeCycle)

Jetty 可以基于两种协议工作, 一种是 Http(作为提供独立的 Web 服务), 另一种是 AJP(集成到 JBoss 和 Apache 中), Connector 的工作方式默认都是 NIO, 但支持 BIO

apr(Apache Portable Runtime/Apache可移植运行时库),Tomcat将以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作, 从而大大地提高Tomcat对静态文件的处理性能。从操作系统级别来解决异步的IO问题, 大幅度的提高性能。 Tomcat apr也是在Tomcat上运行高并发应用的首选模式

与 Tomcat 的比较

  1. Jetty 的架构比 Tomcat 简单, 何被扩展的组件都可以作为一个 Handler 添加到 Server 中, Jetty 将以责任链的方式来管理这些 Handler

  2. 性能方面 Jetty 默认 NIO, Tomcat 默认 BIO, 前者适用于大量长连接的场景(聊天软件), 后者适用于短连接的场景

  3. 最新的 Servlet 特性 Jetty 的支持比 Tomcat 早许多, 这也是架构导致的原因

5. Spring 框架的设计理念与设计模式分析

Spring 的三大核心组件: Core, Context, Bean, Bean 包装的是 Object, Context 来维护 Bean 的关系集合(IoC 容器, 从 Context 中获得各种 Bean), Core 用来发现、建立和维护每个 Bean 之间的关系所需要的一系列工具

Bean 组件在 Spring 的 org.springframework.beans 包下, 主要解决: Bean 的定义、创建、及对 Bean 的解析

Bean 的创建是典型的工厂模式, 主要由三个子类: ListenBeanFactory, HierachicalBeanFactory, AutowireCapableBeanFactory, Bean 的解析主要就是对 Spring 配置文件的解析

Context 组件在 Spring 的 org.springframework.context 包下, 作为 Spring 的 IoC 容器, 完成了标识应用环境、利用 BeanFactory 创建 Bean 对象, 保存对象关系表, 能够捕获各种事件的工作

Core 组件把所有资源都抽象成了一个接口的方式来进行访问(ResourceLoader)

IoC 容器实际上是 Context 组件结合其他两个组件共同构建了一个 Bean 的关系网, 在这个构建过程中, BeanFactoryPostProcessor 和 BeanPostProcessor 分别在构建 BeanFactory 和 构建 Bean 对象时调用, InitializingBean 和 DisposableBean 分别在 Bean 的实例创建和销毁时调用

AOP 是基于动态代理实现的, 拦截器就是其中的一个特性

6. Spring MVC 的工作机制与设计模式

要使用 SpringMVC, 只需要在 web.xml 中配置一个 DispatchcerServlet 用于路径映射, 定义一个 ViewResolver 用于视图解析器, 再定义一个业务逻辑的处理流程规则

DispatcherServlet 初始化调用了 initStrategies 方法

1
2
3
4
5
6
7
8
9
10
protected void initStrategies(ApplicationContext context){
initMultipartResolver(context); // 用于处理文件上传服务
initLocaleResolver(context); // 国际化问题
initThemeResolver(context); // 主题
initHandlerMappings(context); // 映射
initHandlerAdapters(context); // 规则
initHandlerExceptionResolvers(context); // 异常处理
initRequestToViewNamerTranslator(context); // View 前缀后缀
initViewResolvers(context); // View 解析成页面
}
7. 深入分析 iBatis 框架之系统架构与映射原理

ORM 框架干的事情: 1. 根据 JDBC 规范建立与数据库的连接 2. 通过反射打通 Java 对象与数据库参数交互之间相互转化的关系

做的事情如下, iBatis 就是把这几行代码分解包装

1
2
3
4
5
6
7
8
9
Class.forName("xxx.xxx.xxx.xxx.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement st = conn.prepareStatement(sql);
st.setInt(0, 1);
st.execute();
ResultSet rs = st.getResultSet();
while(rs.next()){
String result = rs.getString(colname);
}
如需转载,请注明出处

读书笔记-深入分析 Java Web 技术内幕(二)

第二部分: Java 基础知识

1. Javac 编译原理

Java 源代码经历词法分析器、语法分析器、语义分析,代码生成最终转换为字节码

词法分析器: Scanner(扫描全文件)、Lexer(识别符合 Java 语法规范的 Token 序列)

语法分析器: AST(抽象语法树)

语义分析器: 让 AST 的信息更加完善, 包含了: 映射符号表、处理注解、处理标注(@Unchecked), 检查变量合法性、数据流分析(还是各种语法检查)、语义分析(优化代码逻辑, 消除无用代码, 解除语法糖)

代码生成器: 遍历语法树, 生成字节码, 写入 class 文件

以上 4 个环节都会对语法树进行不同额处理操作, 采用的设计模式是访问者模式

想要操作 Java 源码: 业内 Eclipse JDT

2. 深入 class 文件结构

有两种翻译 class 的工具:

  1. Oolong 是 JVM 的汇编语言, 使用方式: java COM.sootNsmoke.oolong.Gnoloo Message.class 生成 Message.j 文件

  2. 通过 JDK 自带的 javap, 使用方式: javap -verbose Message > message.txt

3. 深入分析 ClassLoader 工作机制
  1. 将 class 加载到 JVM 中
  • defineClass(byte[], int, int): 将字节流解析成 JVM 能够识别的 Class 对象

  • findClass(String): 类的加载规则实现于此

  • loadClass(String): 获取类的对象

  • resolveClass(Class<?>): 让 JVM 加载类的同时并链接

  1. 审查每个类应该由谁加载: 父优先的等级加载机制
  • Bootstrap ClassLoader: 主要加载 JVM 自身工作需要的类, 这个 ClassLoader 完全是由 JVM 控制, 没有子类, 参数: -Xbootclasspath:

  • ExtClassLoader: 用于加载目标为 System.getProperty(“java.ext.dirs”) 的类, 是 AppClassLoader 的父类, 参数:
    -Djava.ext.dirs

  • AppClassLoader: 用于加载 classpath 的类, 也是所有自实现的类加载器的父类, 参数: -Djava.class.path=

  1. 重解析: 将字节码重新解析成统一要求的对象格式
  • 字节码验证, Class类数据结构分析及、相应的内存分配、最后的符号表的链接

  • 类中的静态属性和初始化赋值, 静态代码块的执行

常见的类加载错误分析

  • ClassNotFoundException

检查当前 classpath 目录下有没有指定的文件存在, 用 this.getClass().getClassLoader().getResource(“”).toString() 获取 classpath 路径

  • NoClassDefFoundError

使用 new 关键字、属性引用某个类、继承了某个接口或类、方法的某个参数中引用了某个类时, 触发 JVM 隐式加载这些类发现不存在

Tomcat 的 ClassLoader 层级

  • ExtClassLoader -> AppClassLoader -> StandardClassLoader -> WebappClassLoader

加载 Tomcat 容器本身仍然是 AppClassLoader, 在 WebAppClassLoader 中增加了缓存, 取代了加载时优先查找 JVM 的 findLoaderClass 缓存

类的热部署原理: 创建不同的 ClassLoader 实例对象, 然后通过这个不同的实例对象类加载同名的类

4. JVM 体系结构与工作方式

CPU 是跟指令集挂钩的, 顾名思义就是计算机能识别的机器语言, 如 RISC, CISC, MMX 等, 来自于 Intel 和 AMD

JVM 也有用一套指令集, 俗称 JVM 字节码指令集, 用来执行 class 的字节码

JVM 结构组成:

  • 类加载器

  • 执行引擎(负责执行 class 文件中包含的字节码指令, 相当于实际机器上的 CPU, 目标是内存区的栈)

    • SUN 的 Hotspot 是基于栈的执行引擎

    • Google 的 Dalvik 是基于寄存器的执行引擎

  • 内存区(内存管理: 方法区、Java 堆、栈、PC寄存器、本地方法区)

  • 本地方法调用(调用 C/C++ 实现本地方法的代码返回结果)

在执行引擎技术里面衍生了 JIT: 在Java编程语言和环境中,即时编译器(JIT compiler,just-in-time compiler)是一个把 Java 的字节码(包括需要被解释的指令的程序)转换成可以直接发送给处理器的指令的程序

5. JVM 内存管理

Java 涉及到需要分配的内存有: 堆(-Xmx 和 -Xms), 线程(为其分配个堆栈), 类和类加载器(存活于 PermGen 区), NIO(直接使用本地内存), JNI(加载实现的类库)

在 Java 虚拟机规范中将 Java 运行时数据划分为 6 种, 分别为: PC 寄存器, Java 栈(为线程分配的空间), 堆(线程共享), 方法区(存储的是类结构信息, 类加载器加载完就存放与此, 线程共享, 永久区中), 本地方法区(为 Native 方法准备的空间), 运行时常量池(存放每个 class 文件中的常量表, 隶属于方法区的一部分)

根可达算法决定了哪些对象是垃圾, 垃圾回收算法决定了怎么回收: 基于分代的GC(Young, Old, Perm 区依次递进)

Serial Collector 算法 & Parallel Collector 算法 & CMS Collector 算法

GC 日志参数:

-verbose:gc 可以辅助输出一些详细的 GC 信息

-XX:+PrintGCDetails 输出 GC 详细信息

-XX:+PrintGCApplicationStoppedTime 输出 GC 造成应用程序暂停的时间

-XX:+PrintGCDateStamps GC 发生的时间信息

-XX:+PrintHeapAtGC 在 GC 前后输出堆中各个区域的大小

如需转载,请注明出处

读书笔记-深入分析 Java Web 技术内幕(一)

第一部分: Java Web 开发中的基础知识

1. 深入 Web 请求过程

1.1 HTTP 散点知识

Apache, IIS, Nginx, Tomcat, JBoss 都是基于 HTTP 的服务器, HTTP 是应用层的协议, 采用无状态的短连接通信方式

在浏览器里输入 www.baidu.com 敲击回撤的瞬间, 完成了以下操作

  • 浏览器请求 DNS 把域名解析成对应的 IP(域名到IP地址映射: DNS域名服务)

  • 浏览器根据这个 IP 在互联网上找到对应服务器, 发起 GET 请求, 返回数据资源

    • 请求 CDN 寻求静态资源

    • 请求的数据会先到负载均衡, 缓存, 如需要最终才到数据库

HttpClient 工具包 和 curl 命令都能够发起 HTTP 请求, 浏览器正是做了这些工作, 实际上就是建立一个 Socket 通信的过程

要理解 HTTP, 最重要的就是要熟悉 HTTP 中的 HTTP Header, HTTP Header 控制着用户浏览器的渲染行为和服务器的执行逻辑

显而易见, HTTP Request Header 是客户端要告诉服务器的信息, 而 HTTP Response Header 是服务器返回要告诉客户端的信息

Ctrl + F5 的实现逻辑是: 在请求头中加入了 Cache-Control: no-cache 和 Pragma: no-cache, 用于指定所有缓存机制在整个请求/响应链中必须服从的指令

其他用于标识缓存过期的头字段有: Expires, Last-Modified, Etag

1.2 DNS 散点知识

DNS 将域名转化为 IP 的先后关键步骤:

  • [本机完成] 1) 浏览器缓存 - 2) 操作系统hosts文件

  • [本机发起请求] 3) LDNS 即在操作系统中配置的 DNS 服务器地址 - 4) 直接请求 Root 域名服务器(全球就9台)

  • [本机得到反馈结果] 5) 返回给 LDNS 一个主域名服务器地址 gTLD

  • [本机发起请求] 6) LDNS 去请求 gTLD

  • [本机得到反馈结果] 7) 返回给 LDNS 网站注册的域名服务器 - 8)请求这个注册的域名服务器得到 IP - 9) LDNS 存储这个映射缓存 - 10) 存到本机(浏览器)

跟踪域名解析结果: dig www.baidu.com [+trace] [+cmd] 、 nslookup

清除域名缓存结果: ipconfig /flushdns 、/etc/init.d/nscd restart

几种域名解析的方式主要分为 A记录、MX记录、CNAME记录、NS记录 和 TXT记录

  • A记录: 域名对应 IP, 多个域名可以对应一个 IP, 但反之不能

  • MX记录: 邮件服务器对应的 IP

  • CNAME记录: 全称是 Canonical Name(别名解析), 一个域名可以设置多个别名

  • NS记录: 为某个域名指定 DNS 解析服务器, 也就是这个域名有指定的 IP 的 DNS 服务器去解析

  • TXT记录: 为某个主机名或域名设置说明类的文字

1.3 CDN 散点知识

形象的比喻: CDN = 镜像(Mirror) + 缓存(Cache) + 整体负载均衡(GSLB)

负载均衡的框架通常有三种: 链路负载均衡(DNS), 集群负载均衡(软硬件(LVS + HAProxy + F5)), 操作系统负载均衡(多队列网卡)

CDN 的动态加速技术原理就是在 CDN 的 DNS 解析中通过动态的链路探测来寻找回源最好的一条路径, 从而加速用户访问的效率, 节省带宽

2. 深入分析 Java I/O 的工作机制

2.1 Java I/O 类库的基础架构: java.io

  • 基于字节操作的 I/O 接口: InputStream 和 OutputStream

  • 基于字符操作的 I/O 接口: Writer 和 Reader

  • 基于磁盘操作的 I/O 接口: File

  • 基于网络操作的 I/O 接口: Socket

前两者是数据格式, 后两者是传输方式, 共同影响着 I/O 的效率

字符到字节必须经过编码转换, 这个过程相当耗时

在纯 Java 环境下, Java 序列化能够很好的工作, 但是在多语言环境下, 用 Java 序列化存储后, 很难用其他语言还原出结果。在这种情况下, 还是要尽量存储通用的数据机构, 如 JSON 或者 XML 结构数据, 当前也有比较好的序列化工具支持, 如 Google 的 protobuf 等

适配器和装饰器模式, 适配器是适配接口方便调用(InputStreamReader 适配了 InputStream 对象的 Reader 接口), 装饰器的增加接口功能提升效率(FileInputStream 装饰了 InputStream)

2.2 网络 I/O 的工作机制

影响网络传输的因素: 网络带宽, 传输距离, TCP 拥塞控制(为 TCP 设置一个缓冲区, 让传输方和接收方的步调一致)

Socket 一般都基于 TCP/IP 的流套接字

NIO 是相对于 BIO: 阻塞 I/O 来的, BIO 会导致线程阻塞等待操作系统处理, 在大规模访问量时性能堪忧, 当然可以采取线程池的方法来缓解, 但线程池中线程的创建与回收也需要成本, 大量长连接的请求会一直占用线程池, 线程优先级难以掌控, 众多因素呼唤出了 NIO

NIO 的两个关键词: Channel 和 Selector, Channel 好比传输方式, Selector 是这些传输方式的监控调度, Selector 是一个阻塞线程专门处理连接请求, 监控注册在其上面的 Channel 是否有数据传输发生, 一旦监控到了, 则让 Channel 进行相应的数据传输, 而 Channel 的数据传输发生在另一个非阻塞线程, 这样 Selector 不停的分发 I/O 链路让 Channel 进行通信, 换句话说:

Selector 检测到通信信道 I/O 有数据传输时, 通过 select() 方法取得 SocketChannel, 将数据读取或者写入 Buffer 缓冲区

NIO 提供了两个优化的文件访问方法: FileChannel.transferTo / FileChannel.transferFrom 和 FileChannel.map

2.3 I/O 调优

  • 磁盘 I/O 优化
  1. 增加缓存, 减少磁盘访问次数

  2. 优化磁盘的管理系统, 设计最优的磁盘方式策略, 以及磁盘的寻址策略(这是底层操作系统考虑的)

  3. 设计合理的磁盘存储数据块, 以及访问这些数据块的策略, 比如索引

  4. 应用合理的 RAID 策略提升磁盘 I/O

  • 网络 I/O 优化
  1. TCP 网络参数调优

1.1 cat /proc/net/netstat: 查看 TCP 的统计信息

1.2 cat /proc/net/snmp: 查看当前系统的连接情况

1.3 netstat -s: 查看网络的统计信息

1.4 ab -c 30 -n 100000 ip:port: 向 ip 并发发送 30 个请求共 100000 个

  1. 减少网络交互的次数: 两端设置缓存, 合并请求

  2. 减少网络传输数据量的大小: 压缩

  3. 尽量减少编码: 提前将字符转化成字节

  4. 交互方式的场景选择: 同步与异步/阻塞与非阻塞

3. 深入分析 Java Web 中的中文编码问题

3.1 几种常见的编码

在计算机中存储信息的最小单元是 1 个字节, 即 8 个 bit, 所以能表示的字符范围是 0~255 个, 人类要表示的符号太多, 无法用 1 个字节来完全表示, 要解决这个矛盾必须要有一个新的数据结构 char, 而从 char 到 byte 则必须编码

存储空间与编码效率: ASCII 码, ISO-8859-1, GB2312, GBK(兼容前者, 表示的汉字更多) UTF-16, UTF-8, 其中 UTF-8 是最适合中文的编码方式(不用查码表, 计算可寻, 单字节1汉字节3, 相对于UTF16扩张一倍空间而言, UTF8省), 编码的发生场景常见于存储数据到磁盘或者数据要经过网络传输

不同的编码决定着最终存储的大小, 看的是字节数而不是字符数, 一个 int 用 4 个字节存储, 一个 char 用 2 个字节存储

  • 一次 HTTP 请求在很多地方需要编码: URL 的编解码、HTTP Header 的编解码、POST 表单的编解码、HTTP BODY 的编解码
1
2
3
4
5
6
7
8
9
10
11
12
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
如需转载,请注明出处