[JAVA]码农翻身要点记录

文章目录

      • 计算机系统
        • 线程
        • TCP/IP
        • CPU
        • 进程
        • 程序装载
        • 线程
        • 硬盘
        • 文件
        • 外设
        • 数据库
        • socket
        • 程序运行
        • 加法器
        • 递归
      • JAVA
        • java系列
        • 持久化
        • JDBC
        • 面向接口编程
          • 简单工厂
          • 数据驱动
          • 工厂方法
        • JDBC
          • 两阶段提交
        • JSP
        • 动态代理
        • 注解
        • 泛型
        • 日志系统
        • 正交设计
        • Spring本质
          • AOP
          • IOC
      • 后端架构
        • Nginx、应用程序和缓存同在一 台机器
        • 分家
        • redis分片
        • 哈希映射
          • 余数算法
          • 一致性哈希算法
          • 哈希槽
        • 数据库读写分离

计算机系统

线程

  • 线程调度,线程开始时间未知,执行中可能被打断,有io操作需要让出cpu进行等待,从就绪态进入运行态仍然需要cpu调度处理
  • 线程池中的线程不会被kill,线程池中的线程一直运行直到系统重启
  • memcached线程缓存了很多数据,加快了io访问
  • 不同线程有不同的工作,jdbc线程需要从mencached或数据库线程中获取数据
  • 锁,实现进程之间的互斥
  • 死锁,通过限制锁的获取顺序来避免死锁,可以运用字符串的哈希值进行比较
  • 多个线程对同一临界资源的访问可以通过信号量解决(Dijkstra还创造了最短路径算法)
  • 像a++这类中断的实现是操作系统内核通过屏蔽中断实现的
  • 通过阻塞队列避免进程的忙等待
  • 生产者消费者问题需要两个不同的信号量
  • 用互斥锁和条件变量可是实现信号量
  • 平台和工具会对现成的同步进行封装,不需要考虑信号量的实现和使用(例如jdk)

TCP/IP

  • 信息的传输要建立连接通道
  • 发送失败后需要重新发送
  • 发送的信息被分成小块,在路径上有路由去判断传输路径,最后在终点处按照顺序组装
  • 传输双方通过三次握手建立连接,分别企鹅人双方的传输和接受能力是否正常
  • 滑动窗口协议保证多个数据可以同时传输

CPU

  • CPU的工作室运行指令,指令存放在内存中
  • BIOS存储了计算机开机启动的所有信息,启动时CPU需要准备好中断向量表
  • CPU将首扇区装载到内存中0X7C00处,检查后梁字节是否为0XAA00,通过bootloader将操作系统装入内存
  • 操作系统接手计算机,创建进程信息,包括进程描述,进程控制
  • CPU的主要工作内容是操作寄存器,寄存器是cpu内部的告诉存储器
  • 内存相对于CPU速度较慢,缓存利用局部性原理将内存中的信息缓存起来,加快了cpu的读写访问
  • 流水线(将每一条指令的执行进行细分),cpu同时执行多条指令时,将IF取指,ID译码,EX执行等重叠进行操作,不同指令需要的寄存器不同,保证所有寄存器始终处于忙碌状态
    [JAVA]码农翻身要点记录_第1张图片

进程

  • 冯诺依曼结构将数据和程序一视同仁,将计算机从逻辑上氛围五大部件:运算器、控制器、存储区、输入和输出设备
  • 单道批处理系统,每次运行一个程序,io操作室cpu空闲等待
  • 多道程序,内存中放入多个程序,一个进行io时,切换到另一个运行。为了控制不同程序,创造了PCB进程控制块,记录进程相关信息(运行时间,等待时间等)
  • 地址重定位,不同程序都运行在统一内存中,可能会产生覆盖,通过今天重定位直接修改程序指令完成区分。静态重定位在程序变化时需要频繁变动指令,通过增加寄存器(基址寄存器)实现了进程的动态重定位。真实地址=指令地址+基址寄存器
  • 为了控制不同进程的访问权限,避免进程数据的互访,增加寄存器记录进程的长度
  • 将基址寄存器,界限寄存器和内存计算方法封装形成MMU内存管理单元
  • 分时系统,为了避免用户交互进程的等待时间过长,通过时间片轮转的方式在不同进程间进行切换,实现多个进程同时运行的假象
  • 内存分块,将进程按照固定大小如4KB氛围若干块,并以块为单位装载到内存中,需要借助局部性原理进行优化
  • 虚拟内存分页,为每个进程都提供一个相同的虚拟空间例如大小为4G,程序指令使用虚拟空间中的地址,操作系统维护页表,实现虚拟空间像物理空间的映射。为了保持访问速度,CPU维持页表缓存TLB快表。内存中不存在进程要处理的页时,会发生缺页错误。
  • 不同进程的页表存储了进程所用到的页,以及页的状态。
  • 分段分页,为了保持不同进程分块的合理性(代码块,数据块,堆栈),方便表示只读段,对每个程序都进行标准化。操作系统维持段表,将程序的不同部分查找段表找到段基址,与偏移地址相加得到线性地址,并经过也标的转化得到最终的物理地址。当有进程访问无权限的段时,cpu会产生segmentation fault段错误
    [JAVA]码农翻身要点记录_第2张图片

程序装载

  • 程序需要通过loader装载到内存中
  • 装载器读取header信息,将代码和数据在硬盘上的位置记录下来,等待运行时装载
  • 装载器找到程勋的入口点,在装载入内存之前操作系统为进程建立虚拟空间和页表等数据结构
    [JAVA]码农翻身要点记录_第3张图片
  • cpu查找页表,将进程的虚拟地址转变为物理地址,如果页表存在位表示缺页,则产生缺页错误,有缺页处理程序将页加载到内存中,并修改页表
  • 猜测,进程初始页表项后x位为空,出现缺页错误时为进程块分配页,并记录在页表中。将已有的页调入时如果物理地址被占用则修改页表项并重新映射有待进一步学习
  • 程序在内存中被分割成不同的块

线程

  • 将进程作为资源的容器(进程是计算机中资源分配的最小单元),在该容器中运行轻量级的线程,线程共享进程的所有资源。
    [JAVA]码农翻身要点记录_第4张图片
  • 线程也需要记录自身状态

硬盘

  • 硬盘的内部结构,包括多个盘片,每个盘片氛围多个磁道,每个磁道氛围多个扇区,每个扇区512KB
  • 硬盘读写需要寻道和旋转

文件

  • LBA的寻址方式将磁盘分为不同的块,将柱面磁头扇区转换为不同的块号码。文件系统实现了这一点。
  • 文件是人类的最小存储单位
  • 文件存放在不同的磁盘块中,可以采用顺序存储和链式存储,但是检索速度较慢
  • 为每个磁盘建立索引块,存储文件所使用的磁盘块列表,每个索引节点inode存储了使用的磁盘块和大小等信息。(linux的文件系统即采用inode)
    [JAVA]码农翻身要点记录_第5张图片
  • 索引节点中空间有限,可以通过磁盘块嵌套的方式实现更大的索引空间
    [JAVA]码农翻身要点记录_第6张图片
  • 目录同样有inode节点,存储了目录的属性和存放该目录的磁盘块号
    [JAVA]码农翻身要点记录_第7张图片
  • 文件的查找过程即通过目录索引下级目录,通过目录索引文件节点
    [JAVA]码农翻身要点记录_第8张图片
  • 文件系统在删除文件是可能出现系统崩溃,需要考虑持久性问题。借鉴数据路的记录日志,将操作之前要做的操作记录下来形成日志,然后再正式操作。系统崩溃时检查日志,对操作进行修复,即日志文件系统。(详细见操作系统导论)
  • 空闲块的管理。可以通过链表和bitmap进行管理。
  • 文件系统有很多比如 NTFS、FAT、Ext2、Ext3等
    [JAVA]码农翻身要点记录_第9张图片
  • 硬盘首扇区由MBR主引导记录,分区表DPT和结束标志组成
  • 台区表中记录了每个分区的起始位置,以及哪个磁盘分区是活动分区,并装载引导块进行执行
  • 每个分区被分为多个块组
  • 超级快包括磁盘块综述,块大小,空闲块,inode个数等等

外设

  • 操作系统将外设划分为块设备和字符设备
  • 硬盘、CD-ROM、U盘是典型的块设备,数据存储在固定大小的块中,每个块都有一个地址
  • 鼠标、打印机等,是由一个个字符组成的流
  • 总线和端口是cpu和io设备通信的方式,不同设备以总线bus的方式相连接,不同设备有自身的编号,该标号称为io端口。一些cpu会将io端口映射到内存中,称为内存映射io
    [JAVA]码农翻身要点记录_第10张图片
  • 轮询和中断。cpu进行io操作时需要对io设备进行访问,访问的方式就是不断检查询问,为了避免cpu因此霸占总线,不同的io设备可以向cpu申请中断。
  • 中断的管理由中断控制器负责,进行不用中断优先级的判断。
  • 中断实际就是一种异步的、事件驱动的处理思想,nodejs和ajax都有体现(需要进一步学习)
  • 中断的数据传输方式对于小的数据量是可行的,但是对硬盘读写这种大量数据的操作,需要DMAA直接内存获取的机制进行。DMA相当于一个负责io设备和内存之间的专用处理器

数据库

  • 数据库实际上是通过在应用程序或用户与物理层或文件之间增加一个逻辑层,并用通用的结构化查询语言sql实现数据的增删改查(所有的计算机问题都可以通过增加一个中间层来解决)
    [JAVA]码农翻身要点记录_第11张图片
  • 使用分层结构后,物理层可以单独实现优化,增加缓存,索引和查询优化等手段。层内的变化不影响其它层的使用
  • 并发访问,通过采用CS架构,数据库可以在不同客户机之间共享服务器的数据。然而复杂度的大大增加,会产生并发访问的问题。
  • 事务管理。实际上为了保证数据库的操作能够原子性地完成,可以通过undo日志的方式实现数据库的安全。操作(事务)具有幂等性,undo日志记录了事务的开始结束,然而日志系统也会存在断电等意外事件导致的崩溃。所以1、在将数据写入硬盘前,首先要把对应的日志写入硬盘的日志文件。2、提交事务等标志事物结束的日志记录必须在所有数据写入硬盘后再写入日志文件。
  • 安全。为了实现不同用户对数据库的不同权限,需要增加权限控制系统。
  • 权限主要有三类:1、对数据的算则更新插入。2、对结构的操作,创建修改表。3、数据库管理,创建影虎备份数据。然后通过权限管理系统将不同的权限赋予不同的用户。
    [JAVA]码农翻身要点记录_第12张图片

socket

  • tcp链接的建立需要三次握手,传输过程需要确认和控制,断开连接要经过四次挥手。
  • 由于tcp协议的复杂性,程序员不需要靠自己实现连接的建立传输和断开,而是由操作系统内核实现,并未应用程序提供抽象的接口。
  • socket实际上连接了不同节点的端口port
    [JAVA]码农翻身要点记录_第13张图片
  • tcp是进程之间的通信,同一时刻可以有多进程访问多个服务器,需要通过端口号进行区分。
  • 服务器端的socket是被动地,启动后需要不断监听客户端发起的链接,并且需要将不同的客户端链接区分开来
  • 客户端的socket程序需要实现,建立socket对象,与指定ip和端口建立连接,发送和接受信息,断开连接。
  • 服务器端的socket程序需要实现,建立socket对象,绑定ip和端口,监听客户端请求,将请求转化为已连接对象,对已连接对象的信息收发。已连接对象都使用统一端口(例如80),通过客户机ip和端口进行区分
    [JAVA]码农翻身要点记录_第14张图片

程序运行

  • 内存可以抽象为线性结构,每个基本存储单元大小为1byte或8bit
  • cpu中的不同寄存器相互协作实现指令的运行。
  • cpu指令实际上就是取数据,存数据,数学和逻辑运算,跳转
  • 编程语言从机器语言到汇编语言再到高级语言,从二进制到助记符再到现在的编程语言,越来越便于人类的阅读
  • 高级语言向低价语言的转化就是编译
  • 高级语言经过词法分析进行分片,每个片段称为token,建立符号表表示不同token的类型。
  • 通过上下文无关的语法分析将token按照语法规则组件成树
    [JAVA]码农翻身要点记录_第15张图片
  • 语义分析检查标识符类型作用域的正确性,运算合法性,取值范围等,最终生成汇编语言
  • 程序的编译链接和装载

加法器

  • 进制的转化
  • 原码,反码和补码
  • 整数和浮点数(详见IEEE745标准)在计算机中的表示
  • 标志寄存器各位的意义

递归

  • 尾递归,避免了递归深度过大占用占空间。尾递归相当于在函数死亡后重新调用,而非产生新的函数。

JAVA

java系列

  • 构建工具: Ant 、 Maven 、 Jekins
  • 应用服务器: Tomcat 、 Jetty 、 JBoss 、 WebSphere 、 WebLogic
  • Web 开发: Spring 、 Hibernate 、 MyBatis 、 Struts
  • 开发工具: Eclip町、 NetBeans 、 lntelliJ IDEA 、 JBuilder

Ruby on Rails, '8结合了 PHP 体系的优点(快速开发)和 Java 体系的优点(程序规整),特别适合快速开发简单的 Web 网站 像 String 、ArrayList 等只能由自己的心腹去装载。每个心腹都有上线,顶层的叫 Bootstrap ClassLoader,下一层级叫 Extension ClassLoader,避免黑客攻击

Java 函数栈,每个结程都有一个栈帧,代表一个方法调用,这一栈帧就是一个方法的调用链。

GC Roots 的对象,从这些节点出发四处搜索被 GC Roots 直接引用的对象, 然后再找这些对象所引用的对象,这么一层一层找下去,就形成了一条以 GC Roots 为起点的引用链条 。 如果不在这条链条上那就很高可能被清理悼

持久化

Java 对象都必须在内存中才能工作,而内存最怕“断电”

序列化,可以把内存中那些重要的对象转换为二进制文件存储到砸盘上

反序列化,把二进制文件变成 Java 对象,继续在内存中

序列化虽然解决了一部分问题,但是效率低

使用关系数据库存储大规模数据,关系数据库就是用类似二维表格的方式来存储数据的,把对象属性变成数据库的行和列,叫 Object-Relational Mapping,java只负责定义接口,具体的 JDBC 实现必须由各个数据库提供

JDBC 的劣势也很明显:这是一个非常“低级” 的接口,程序员需要处理太多
的细节,冗余代码太多,写一个简单的查询就得一大堆代码伺候,打开 Connection,创建Statement ,执行 SQL ,遍历 ResultSet ,还得记得关闭 Connection ,要不然资源会泄露

中间件 ( Middleware ),专门负责底层操作系统和上层应用程序都不愿意做的事情 。 有一个标准就是 EJB,但是开发烦琐,难以测试,性能低下。Gavin King 的,终于无法忍受金玉真外、服絮真中的 EJB ,自己搞了一个 O/RMapping 框架出来,名字很有意思,叫作 Hibernate。使用 Hibernate ,你可以把 Java 的属性用声明的方式映射到数据库表,完全不用你操心 Connection 、SQL 这些细节,另一个叫作 iBatis 的 O/RMapping 框架也出现了。Rod Johnson 给了 EJB 致命一击,他写了一本书,名为 Expert One-on-One。J2EE Development without EJB ,公然宣扬不使用 EJB ,而要使用更加轻量级的框架,也就是他鼓跑出来的 Spring。Spring 不但自己提供了轻量级的访问数据库的方法 JdbcTemplate,还能轻松地集成 Hibernate、旧atis 等一批工具

JDBC

和 MySQL 定义一个应用层的协议,就是所谓的你发什么请求、 我给你什么晌应、消息的格式和次序等。

[JAVA]码农翻身要点记录_第16张图片

MySQL 商量出的应用层协议 ,不管是由什么语言写的程序,筐你是 Java 、 P严hon 、 Ruby 、 P田……只要能使用 Socket,就可以遵照 MySQL 的应用层协议进行访问 。 通过网络给数据库发送 SQL 语句 ,直接使用 Socket 编程太低级了,必须再一个抽象层屏蔽这些细节 。首先得有一个叫连接( Connection )的东西,用来代表和数据库的连接 。想执行 SQL 怎么办?用一个 Statement 来表示吧 。SQL 返回的结果也得有一个抽象的概念: ResultSet 。

Connection conn = ..... //获取数据库连接
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery ("select id,name from users");
while (rs.next()){
	int id =rs.getlnt("id");
	String name = rs.getString("name");
	...其他处理 .
}

由 Connection 可以创建 Statement, Statement 执行查询可以得到 ResultSet 。
ResultSet 提供了对数据进行遍历的方法 ,就是 rs. next()、 rs .getlnt(xxx)、 rs .getString(xxx)。无论是 Connection ,还是 Statement、 ResultSet,官们都应该是接口,而不能是具体的实现
[JAVA]码农翻身要点记录_第17张图片

面向接口编程

Properties info = new Properties();
info.put ("host","localhost");
info.put ("port", "3306);
info.put("database","stu_db");
info.put("username ”,"andy");
info.put ("password","123456");
Connection conn = new MySQLConnectionlmpl(info);

如果My SQL 把MySQLConnectionlmpl 这个类名改成了MySQLConnectionJDBC4Impl ,则编译可能失败,无法实现面向接口编程

简单工厂

增加一个Driver的抽象层,各个程序可以通过Driver获得Connection接口而不用new一个接口

public classDrive{
	public static Connection getConnection(String dbType,properties info){
	if("mysql".equals(dbtype)){
	return new MysqlConnectionImpl(info);
	}
	if(”oracle” .equals{dbType)){
		return new OracleConnectionlmpl(info);
	}
	if(”db2”.equals(dbType)){
		return new DB2Connectionlmpl(info);
	}
	throw new RuntimeException("unsupported dbtype =+ dbType);
	}
}
// 使用Driver获取连接
Properties info = new Properties();
info. put (”host",”localhost”);
info. put (” port”,3306);
info .put(”database",”stu_db”);
info .put(” username ” 3 ” andy”) ;
info .put(” password”,123456);
Connection conn = Driver.getConnection (” mysql ”,info);

[JAVA]码农翻身要点记录_第18张图片

数据驱动

新加一个数据库连接的实现,比如 MysqlConnectionlmpl,还得修改你的 Driver 代码,要是Driver 这个类已经打包在 JDK 当中了,就无法修改了

可以用配置文件啊,用数据驱动的方法,每个想使用 JDBC 的程序都需要提供一个类似这样的文件 Connection-type.properties

mysql = com.mysql.jdbc.MySqlConnectionimpl
db2 = com.ibm.db2.Db2Connectionimpl
oracle = com.oracle.jdbc.OracleConnection
sqlserver = com.Microsoft.jdbc.SqlServerConnection

这个文件中已经描述了数据库类型和 JDBC Connection 实现类之间的关系 ,所以Java 就可以用反射的办法来创建各个数据库的 Connection 实例

public class Driver {
	public static Connection getConnection(String dbType, Properties info){
	Class<?> clz = getConnectionlmplClass(dbType);
	try {
		Constructor<?> c = clz.getConstructor(Properties.class);
		return (Connection) c.newlnstance(info);
	} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
private static Class<?> getConnectionimplClass(String dbType) {
	//读取配置文件,从中根据dbType来读取相应的Connection实现类
	}
}

害户在使用的时候需要提供一个 .properties 文件,而 .properties 文件必须写对数据名称和类名才可以工作(存在暴露的问题)

MysqlConnectionlmpl 是由 Oracle 提供的,属于 mysql.jar
Db2Connectionlmpl 是由 IBM 提供的,属于 db2 .jar
OracleConnectionimpl 是由 Oracle 提供的,属于 oracle-jdbc.jar

创建这些实现类的过程不应该被暴露出来,应该让各个厂商在各自的.jar 中去创建各家的 Connection 实例对象

工厂方法

[JAVA]码农翻身要点记录_第19张图片
工厂本身也变成了接口,三 个实现 MySqlDriver 、 OracleDriver、 Db2Driver,分别创建对 应的
Mysq!Connectionimpl 、 OracleConnectionlmpl 、 Db2Connectionimpl

每个数据库的 Driver 类和自己对应的 Connection 实现类是打包在一个 .jar 中

[JAVA]码农翻身要点记录_第20张图片

//属于 JDK 的 Driver类
public interface Driver{
	public Connection getConnection(Properties info);
}

//属于mysql-jdbc.jar的MysqlDriver类
public class MySqlDriver implements Driver{
	public Connection getConnection(Properties info) {
	return new MySqlConnectionlmpl(info);
	}
}
//属于oracle-jdbc.jar的OracleDriver类
public class OracleDriver implements Driver {
	public Connection getConnection(Properties info) {
		return new OracleConnectionimpl(info);
	}
}
//属于db2-jdbc.jar的Db2Driver类
public class Db2Driver implements Driver {
	public Connection getConnection(Properties info) {
		return new DB2Connectionimpl(info);
	}
}

不用直接 new Driver,可以通过反射的方式先把类装载进来,然后再创建一个 Driver 实例

Class <?> clz = Class.forName("com.coderising.mysql.MySqlDriver、”);
Driver driver、= (Driver) clz.newinstance();
Connection conn = driver.getConnection(info);

如果要求用户不使用反射,可以将Driver也隐藏起来,创建一个DriverManager

public class DriverManagerprivate static List<Driver> registeredDrivers = new ArrayList<>();
	public static Connection getConnection(String url, String user,
String password){
		Properties info = new Properties();
		info.put(” user”,user);
		info.put ("password", password);
		for(Driver driver:registeredDrivers){
			Connection conn = driver.getConnection(url,info);
			if( conn != null){
				return conn;
		throw new RuntimeException("can't create a connection");
	public static void register(Driver driver){
		if(!registeredDrivers.contains(driver)){
		registeredDrivers.add(driver);
		}
	}
}

MysqlDriver的实现

public class MySqlDriver implements Driver{
	static{
		DriverManager.register(new MySqlDriver())}
	public Connection getConnection(String url, Properties info) {
		if(acceptsURL (url) ){
			return new MySqlConnectionimpl(info);
}
		return null;
}
	public boolean acceptsURL(String url){
		return url.startsWith(”jdbc:mysql”);
    }
}

使用DriverManger获得Connection

Class.forName("com.coderising.mysql.MySqlDriver"); //装载类并初始化注册到DriverManager中
Connection conn = DriverManager.getConnection(
				” jdbc:mysql://localhost:3306/studb",
				” root ”,123456);

Class.forName()万法就可以把某个数据库的 Driver 类装载, Driver 类被装载的时候就会把自己注册到 DriverManager 中,等到 DriverManager.getConnection 执行的时候就会遍历各个注册过的 Driver 去尝试获得 Connection,各个 Driver 可以判断 URL 是不是自己所支持的( acceptsURL()方法),如果是,则返回一个 Connection 对象。

JDBC

关系数据库相比于简单的文件系统有一个巨大的好处,就是支持事务。事务 4 个特性:原子性( Atomicity )、一致性( Consistency )、 隔离性( Isolation )和持久性( Durability ),简称 ACID 。数据量大到一定程度,势必要拆台数据库,就会出现跨数据库的事务 ,事务只在一个数据库中南用

两阶段提交

特设一个全局的事务管理器,它来负责协调各个数据库的事务提交。 为了实现分布式事务,需要两个阶段 :

  1. 全局的事务管理器向各个数据库发出准备消息 。 各个数据库需妥在本地把一切都准备好,执行操作,锁住资源,记录 redo/undo 日志, 但是并不提交
  2. 如采所有的数据库都报告说准备好了,那么全局的事务管理器就下命令:提交!这时候,各个数据库才真正提交。

[JAVA]码农翻身要点记录_第21张图片

这个分布式规范叫做Java Transaction API ( JTA )

分布式事务伴随着大量节点的通信交换,协调者要确定真他节点是否完成,加上网络带来的超时,导致 JTA 性能低下,在分布式 、 高并发和要求高性能的场景下举步维艰。

最终一致性:请求在消息队列中暂时存,等到数据处理完成,数据还是一致的。 向 消息队列中插入消息,事务就结束了 , 根本不用什么两阶段提交,性能很好

幂等性 :对一个事务进行操作的时候,可以一直重复地操作,那个事务不受影响

一个名为 Dan Pritchet 的人,把这种方法总结了一下,称之为 BASE 模型

JSP

把页面模板和数据装配起来,变成HTML发送给浏览器 。Web 编程刚刚诞生的时候,大家只能用 Perl 、 C 语言等以 CGI 的万式来输出HTML ,就是用字符串拼接而己。1996 年 ,“恶名远扬 ”的微软推出了 ASP ( Active Server Page ),这个新的页面装配工和 CGI 小伙子可是大不相同 , 因为他能够支持在 HTML 页面中嵌入代码

<%@ Language="VBScript" %>

    
        <% For i=3 to 5 %>
         >
        	你好,欢迎来到我的主页
        
<% Next %>

原来的 CGI 是在代码中混杂 HTML ,现在的 ASP 则是在 HTML 中混杂代码! MVC 能够把展示和逻辑分开,可以用Serviet 来充当控制器( Controller ),用 Java 类来充当模型( Model ),而视图自然就是 JSP。显示逻辑还是必不可少的,所以像分支、循环这样的控制语句不可或缺。 Java 做一层封装,给码农提供一套标准的、叫作 JSP Standard Tag Library( JSTL )


	欢迎你,系统管理员 !

[JAVA]码农翻身要点记录_第22张图片

LoggerHandler 充当了一个中间层(见图 2- 1 9 ),我们自动化生成的类 $HelloWorldlOO 会调用宫,并且把真正的 sayHello()方法传递给巴(上面代码中的method 变量),于是 sayHello()方法就被添加上 Logger 的 Sta此Log()和 endLog()方法

[JAVA]码农翻身要点记录_第23张图片

//使用动态代理增强HelloWorld
IHelloWorld hw = new HelloWorld();
LoggerHandler handler = new LoggerHandler(hw);
IHelloWorld proxy = (IHelloWorld) Proxy.newProxyinstance(
Thread.currentThread().getContextClassloader(),
hw.getClass().getlnterfaces(), handler);
proxy.sayHello();

Proxy. newProxyInstance(…)这里,就是动态地生成了一个类,这个类对程序员来说是动态生成的,也是看不到源码的

注解

元数据,就是描述数据的数据

@Override 一旦用到这个方法上,那就意味着要覆盖父类/接口的方法了,于是我的@Override 就给这个方 提供了额外的信息。所谓的注解有点像加强版的注释,这个‘注程’不但有一定的格式,还有特定的含义。JDK 中己经内置了 @Override 、 @Deprecated 、@SuppressWarnings 等注解,但是用处不大,干脆允许自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
boolean ignore() default false;

元注解,可以认为是注解的注解,@Target 表示该注解的应用目标, 可以是类、方法 、 方法参数等;@Retention 表示这个注解要保留到什么时候,可以只在源码中,或者在 .class 文件中, 或者在运行时

无论是注解还是 XML 配置,都没有占据垄断地位,很多人把二者混合起来使用了!对于一些需要集中配置的场合,如数据源的配置,自然使用 XML。另外,对于@Control!町、 @RequestMapping、@Transactional 这样的注解,大家更喜欢和 Java 方法写在一起,显得简单而直观

泛型

向 List 当中添加 了一个字符串和整数,看起来没有问题。可是使用List 的人就麻烦了,他必须知道第一个元素是字符串类型,第二个元素是整型 ,还得强制转型,要不然就会出错。 这么做会增加使用者的责任,编译器也无法帮忙,在运行时才会抛出Class Cast 异常。实例化了一个 List ,所以你只能往里面添加整数, 如果添加真他类型的值如字符串,编译器就能检查出来,直接报错。C++ 把这种能力称为泛型( Generics )。泛型极大限度地减少了运行期那些转型导致的异常,简化了代码,

日志系统

要求:

  1. 日志消息除了能打印到控制台,还可以输出到文件,甚至可以通过邮件发送出去(如生产环境出错的消息)。
  2. 日志内容可以格式化,如变成纯文本、 XML 、 HTML 恪式等。
  3. 对于不同的 Java class 、不同的 package,以及不同级别的日志,应该可以灵活地输出到不同的文件中
  4. 能对日志进行分级。有些日志纯属 debug ,在本机或者测试环境下使用,方便程序员进行调试,生产环境完全不需要 。 有些日志是描述错误( error )的,在生产环境下出错必须记录下来,帮助后续的分析。

日志需要类表达日志的概念,至少有两个属性:时间戳和消息本身,于是创建LoggingEvent。
日志可以输出到不同的地方即LogDestination,创建Appender
[JAVA]码农翻身要点记录_第24张图片

日志内容的格式化,定义Formatter接口格式化信息

[JAVA]码农翻身要点记录_第25张图片

不同的class、package输出目的地不同,在获取日志时,获取Logger并传入类名或包名

日志要分级,定义一个 Priority 类,里面定义 5 个常量 DEBUG 、 INFO 、 W成N、 ERROR、FATAL ,表示 5 个不同的级别就可以了 。这 5 个级别有高低之分, DEBUG 级别最低, FATAL 级别最高

给 Logger 增加一些辅助编程的方法,如Logger.debug(…)、 Logger.info(…),
Logger.warn(…)等 , 这样将来就可以轻松地输出各种级别的日志了。

正交设计

当从系统中提取出正交的概念的时候,那就威力无比了,因为变化被封装在一个维度上,可以把这些概念任意组合

[JAVA]码农翻身要点记录_第26张图片

Spring本质

进行软件系统设计的时候,一项非常重要的工作就是把一个大系统技业务功能分解成一个个低辑台、高内聚的模块,分而治之。分解以后就会发现一些很有趣的东西,这些东西是通用的,或者是跨越多个模块的。

  • 日志 :对特定的操作输出曰志来记录 。
  • 安全 :在执行操作之前进行操作检查 。
  • 事务: 在方法开始之前要开始事务,在方法结束之后要呈交或者回滚事务 。
  • 性能统计:要统计每个万法的执行时间 。

可以被称为非功能性需求,但它们是多个业务模块都需要的,是跨越模块的。与曰志、安全、事务 、 性能统计相关的代码几乎要把真正的业务代码淹没了。前辈已经遇到了类似的问题,他们已经想出了一些解决办法 , 例如使用 “模板方法”这个著窑的设计模式 。父类( BaseCommand )中已经把那些 “乱七八糟”的非功能性代码写好了,只留了一个口子(抽象方法 doBusiness() )让子类去实现。这种方式的巨大缺陷就是父类会定义一切:要执行哪些非功能性代码,以什么顺序执行, 等等。子类只能无条件接受,完全没有反抗的余地。

AOP

装饰器模式的缺点:

  • 一个处理日志 / 安全/事务/性能统计的类为什么要实现业务撞口( Command )(需要传入业务接口
  • 如果真他业务模块没有实现 Command 接口,但是也想利用日志/安全/事务/性能统计等功能,无法实现

最好的办法是:把日志 /安全 / 事务/性能统计这样的非功能性代码和业务代码完全隔离开来!因为官们的关注点和业务代码的关注点完全不同,包们之间应该是正交的
[JAVA]码农翻身要点记录_第27张图片

  • 切入点( PointCut ),白可以是一个方法或一组方法(可以通过通配符来支持)

  • 在方法调用之前/之后,需要执行 xxx”,就是通知( Advice )。

Java 是一门静态的强类型语言,代码-§写好,编译成 Java 类以后,就可以在运行时通过反射( Reflection )来查看类的信息,但是要想对编译好的类进行修改是不可能的

突破限制的技术:

  1. 修改现有类:在编译的时候做手脚,根据 AOP 的配置信息,悄悄地把日志、安全、事务、性能统计等“切面”代码和业务类编译到一起
  2. 在运行期做手脚,在业务类加载以后,为该业务类动态地生成一个代理类,让代理类去调用执行这些“切面” 代码,增强现有的业务类,业务类不用进行任何改变 。害户直接使用的是代理类对象 , 而不是原有的业务类对象

动态生成代理类的方法有两种 : 第一种是使用 Java 动态代理技术,这种技术要求业务类必须有接 口 ( Interface )才能工作;第二种就是使用 CGLib,只要业务类没有被标记为final 就可以 , 因为宫会生成一个业务类的子类来作为代理类

类被增强生成代理类,但是无法获取,“容器( Container )” 接管对象创建的艰巨任务,我们只需对窑器说“给我一个 PlaceOrderCommand 对象”,窑器就会创建一个 PlaceOrderCommand的代理对象出来,加上对 AOP 功能的调用代码,然后把这个代理对象返回给我们使用。 我们还以为使用的是旧的 PlaceOrderCommand,实际上百已经被做了手脚

[JAVA]码农翻身要点记录_第28张图片

IOC

演员就像一个个 Java 对象 ,最旱的时候自己去创建自己所依赖的对象,育了演艺公司( Spring 窑器)的介入,所有的依赖关系都由演艺公司负责搞定 , 于是控制就翻转了

后端架构

发送 SQL 的时候,都得通过数据库连接( Connection )的通道,建立这个通道是极为昂贵的。对于每个 Connection,他在数据库那里需要开辟不少缓冲区,用来读取表中的数据,进行 join 操作、sort 操作等,既费时又费力。所以数据库严格地限制创建数据库连接的个数,

Nginx、应用程序和缓存同在一 台机器

计算机行业的所有问题都可以通过增加一个抽象层来解决,我看我们就在应用程序和数据库之间增加一个抽象层——缓存。用户登录以后,要获得相关的信息,原来都是发 SQL 去查 , 而现在先从缓存中找,如果 ‘命中 ’了,就不用查数据库了;如果没有‘命中 ’ ,则再从数据库中查,查出来的数据也放到缓存中 ,以便加快下次访问的速度

[JAVA]码农翻身要点记录_第29张图片

缰存和应用程序都在我 Tomcat 中运行,在同一个进程里,缓存的 Java 对象可以直接访问,效率最高。由于所高的东西都在一台机器上,而一台机器的能力毕竟有限,用户量大的时候处理起来还是力不从心,更重要的是内存、硬盘、网卡等偶尔罢工就会导致整个系统停摆

分家

[JAVA]码农翻身要点记录_第30张图片

缓存数据库追求的就是快速和简单,数据都在内存中。Redis 给了 Tomcat 一个 Java 客户端端软件,叫作 Jedis ,使用这个客户端软件就可以访问
[JAVA]码农翻身要点记录_第31张图片

redis分片

[JAVA]码农翻身要点记录_第32张图片

这么多缰存服务器,每个 Redis 存的数据都不一样,对于应用程序来说,每次向 Redis 中存放数据的时候,到底选哪个?是存到 1 号缓服务器,还是 2号3号,数据存储也要尽可能均匀

哈希映射

余数算法
一致性哈希算法

用服务器的ip或者 hostname ,调用 hash 函数: hash(ip) = hashcode , 就得到了,然后就可以把这台服务器的 hashcode 对应到这个圆圈上的某个位置当然,这个 hashcode 的值应该在 2 32 2^{32} 232之内

[JAVA]码农翻身要点记录_第33张图片
数据可以映射到圆圈上的某个位置,然后从这个位置开始,顺时针找到第一台服务器

[JAVA]码农翻身要点记录_第34张图片

虽然增加了一台服务器 4,但是受到影响的只有一部分数据,那就是在服务器 2 和服务器 4 之间的 (key,value),如(key2, value2),当查找这些数据的时候,还会顺时针,找到了服务器 4,但是数据却不在服务器 4

[JAVA]码农翻身要点记录_第35张图片

如果服务器经过 Hash 计算以后,在你的圆圈上不均匀,挤在了一起,那岂不会发生某些服务器负载过高而某些服务器负载太低的情况? 虚拟服务器把一台真实的服务器看作多台虚拟的服务器,让官们分布在圆圈上,增加均匀性

[JAVA]码农翻身要点记录_第36张图片

哈希槽

Hash 槽比一致性哈希要简单,共有 16384 个槽,每台服务器分管其中一部分。比如有三台服务器,第一台服务器负责[0,5460]这个范围,第二台服务器负责[5461,10992]这个范围,第三台服务器负责[ 10923,16383]这个范围

[JAVA]码农翻身要点记录_第37张图片

使用了一种 CRC16 算法,先对 key 产生一个整数值,再对 1 6384 求余数.增加服务器 4,那就会从服务器 1 、服务器 2、服务器 3 负责的槽中各自台出一些交给服务器 4管理,对应的数据也要搬过去。

[JAVA]码农翻身要点记录_第38张图片

salve 节点虽然是备份,但是时刻准备着替换 master 。 如果 master 节点nodeA 挂掉,那我就用某种算洁白动从剩下的 salve 中选取一个当作新的 master

[JAVA]码农翻身要点记录_第39张图片

Keepalived 能够把我们兄弟俩形成一种 master-slave 结构,同一时刻只有一个工作,另一个原地待命 。 如果工作的那个挂掉了,待命的那个接管。对外只提供了一个 ip地址,看起来好像只有一台机器

[JAVA]码农翻身要点记录_第40张图片

Nginx 只是转发请求,不保存状态,实现高可用性很容易 , 但是 Tomcat 这边有 session用户的登录信息、购物东等都是状态信息,处理不好状态的问题,Tomcat 集群的威力就大打折扭,无法完成真正的失效转移,甚至无法使用

[JAVA]码农翻身要点记录_第41张图片

数据库读写分离

先设置一个 master 的数据库,然后再设置一个或者几个 slave 的数据库。 不过限制 master 库可读可写, slave 库只能读、不能写 。最后还要将 master 库的数据尽快地复制到 slave 库,让数据保持一致

[JAVA]码农翻身要点记录_第42张图片

你可能感兴趣的:(JAVA)