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 等一批工具
和 MySQL 定义一个应用层的协议,就是所谓的你发什么请求、 我给你什么晌应、消息的格式和次序等。
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,官们都应该是接口,而不能是具体的实现
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);
新加一个数据库连接的实现,比如 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 实例对象
工厂本身也变成了接口,三 个实现 MySqlDriver 、 OracleDriver、 Db2Driver,分别创建对 应的
Mysq!Connectionimpl 、 OracleConnectionlmpl 、 Db2Connectionimpl
每个数据库的 Driver 类和自己对应的 Connection 实现类是打包在一个 .jar 中
//属于 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 DriverManager{
private 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 对象。
关系数据库相比于简单的文件系统有一个巨大的好处,就是支持事务。事务 4 个特性:原子性( Atomicity )、一致性( Consistency )、 隔离性( Isolation )和持久性( Durability ),简称 ACID 。数据量大到一定程度,势必要拆台数据库,就会出现跨数据库的事务 ,事务只在一个数据库中南用
特设一个全局的事务管理器,它来负责协调各个数据库的事务提交。 为了实现分布式事务,需要两个阶段 :
这个分布式规范叫做Java Transaction API ( JTA )
分布式事务伴随着大量节点的通信交换,协调者要确定真他节点是否完成,加上网络带来的超时,导致 JTA 性能低下,在分布式 、 高并发和要求高性能的场景下举步维艰。
最终一致性:请求在消息队列中暂时存,等到数据处理完成,数据还是一致的。 向 消息队列中插入消息,事务就结束了 , 根本不用什么两阶段提交,性能很好
幂等性 :对一个事务进行操作的时候,可以一直重复地操作,那个事务不受影响
一个名为 Dan Pritchet 的人,把这种方法总结了一下,称之为 BASE 模型
把页面模板和数据装配起来,变成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 )
欢迎你,系统管理员 !
LoggerHandler 充当了一个中间层(见图 2- 1 9 ),我们自动化生成的类 $HelloWorldlOO 会调用宫,并且把真正的 sayHello()方法传递给巴(上面代码中的method 变量),于是 sayHello()方法就被添加上 Logger 的 Sta此Log()和 endLog()方法
//使用动态代理增强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 )。泛型极大限度地减少了运行期那些转型导致的异常,简化了代码,
日志系统
要求:
- 日志消息除了能打印到控制台,还可以输出到文件,甚至可以通过邮件发送出去(如生产环境出错的消息)。
- 日志内容可以格式化,如变成纯文本、 XML 、 HTML 恪式等。
- 对于不同的 Java class 、不同的 package,以及不同级别的日志,应该可以灵活地输出到不同的文件中
- 能对日志进行分级。有些日志纯属 debug ,在本机或者测试环境下使用,方便程序员进行调试,生产环境完全不需要 。 有些日志是描述错误( error )的,在生产环境下出错必须记录下来,帮助后续的分析。
日志需要类表达日志的概念,至少有两个属性:时间戳和消息本身,于是创建LoggingEvent。
日志可以输出到不同的地方即LogDestination,创建Appender
日志内容的格式化,定义Formatter接口格式化信息
不同的class、package输出目的地不同,在获取日志时,获取Logger并传入类名或包名
日志要分级,定义一个 Priority 类,里面定义 5 个常量 DEBUG 、 INFO 、 W成N、 ERROR、FATAL ,表示 5 个不同的级别就可以了 。这 5 个级别有高低之分, DEBUG 级别最低, FATAL 级别最高
给 Logger 增加一些辅助编程的方法,如Logger.debug(…)、 Logger.info(…),
Logger.warn(…)等 , 这样将来就可以轻松地输出各种级别的日志了。
正交设计
当从系统中提取出正交的概念的时候,那就威力无比了,因为变化被封装在一个维度上,可以把这些概念任意组合
Spring本质
进行软件系统设计的时候,一项非常重要的工作就是把一个大系统技业务功能分解成一个个低辑台、高内聚的模块,分而治之。分解以后就会发现一些很有趣的东西,这些东西是通用的,或者是跨越多个模块的。
- 日志 :对特定的操作输出曰志来记录 。
- 安全 :在执行操作之前进行操作检查 。
- 事务: 在方法开始之前要开始事务,在方法结束之后要呈交或者回滚事务 。
- 性能统计:要统计每个万法的执行时间 。
可以被称为非功能性需求,但它们是多个业务模块都需要的,是跨越模块的。与曰志、安全、事务 、 性能统计相关的代码几乎要把真正的业务代码淹没了。前辈已经遇到了类似的问题,他们已经想出了一些解决办法 , 例如使用 “模板方法”这个著窑的设计模式 。父类( BaseCommand )中已经把那些 “乱七八糟”的非功能性代码写好了,只留了一个口子(抽象方法 doBusiness() )让子类去实现。这种方式的巨大缺陷就是父类会定义一切:要执行哪些非功能性代码,以什么顺序执行, 等等。子类只能无条件接受,完全没有反抗的余地。
AOP
装饰器模式的缺点:
- 一个处理日志 / 安全/事务/性能统计的类为什么要实现业务撞口( Command )(需要传入业务接口)
- 如果真他业务模块没有实现 Command 接口,但是也想利用日志/安全/事务/性能统计等功能,无法实现
最好的办法是:把日志 /安全 / 事务/性能统计这样的非功能性代码和业务代码完全隔离开来!因为官们的关注点和业务代码的关注点完全不同,包们之间应该是正交的
-
切入点( PointCut ),白可以是一个方法或一组方法(可以通过通配符来支持)
-
在方法调用之前/之后,需要执行 xxx”,就是通知( Advice )。
Java 是一门静态的强类型语言,代码-§写好,编译成 Java 类以后,就可以在运行时通过反射( Reflection )来查看类的信息,但是要想对编译好的类进行修改是不可能的
突破限制的技术:
- 修改现有类:在编译的时候做手脚,根据 AOP 的配置信息,悄悄地把日志、安全、事务、性能统计等“切面”代码和业务类编译到一起
- 在运行期做手脚,在业务类加载以后,为该业务类动态地生成一个代理类,让代理类去调用执行这些“切面” 代码,增强现有的业务类,业务类不用进行任何改变 。害户直接使用的是代理类对象 , 而不是原有的业务类对象
动态生成代理类的方法有两种 : 第一种是使用 Java 动态代理技术,这种技术要求业务类必须有接 口 ( Interface )才能工作;第二种就是使用 CGLib,只要业务类没有被标记为final 就可以 , 因为宫会生成一个业务类的子类来作为代理类
类被增强生成代理类,但是无法获取,“容器( Container )” 接管对象创建的艰巨任务,我们只需对窑器说“给我一个 PlaceOrderCommand 对象”,窑器就会创建一个 PlaceOrderCommand的代理对象出来,加上对 AOP 功能的调用代码,然后把这个代理对象返回给我们使用。 我们还以为使用的是旧的 PlaceOrderCommand,实际上百已经被做了手脚
IOC
演员就像一个个 Java 对象 ,最旱的时候自己去创建自己所依赖的对象,育了演艺公司( Spring 窑器)的介入,所有的依赖关系都由演艺公司负责搞定 , 于是控制就翻转了
后端架构
发送 SQL 的时候,都得通过数据库连接( Connection )的通道,建立这个通道是极为昂贵的。对于每个 Connection,他在数据库那里需要开辟不少缓冲区,用来读取表中的数据,进行 join 操作、sort 操作等,既费时又费力。所以数据库严格地限制创建数据库连接的个数,
Nginx、应用程序和缓存同在一 台机器
计算机行业的所有问题都可以通过增加一个抽象层来解决,我看我们就在应用程序和数据库之间增加一个抽象层——缓存。用户登录以后,要获得相关的信息,原来都是发 SQL 去查 , 而现在先从缓存中找,如果 ‘命中 ’了,就不用查数据库了;如果没有‘命中 ’ ,则再从数据库中查,查出来的数据也放到缓存中 ,以便加快下次访问的速度
缰存和应用程序都在我 Tomcat 中运行,在同一个进程里,缓存的 Java 对象可以直接访问,效率最高。由于所高的东西都在一台机器上,而一台机器的能力毕竟有限,用户量大的时候处理起来还是力不从心,更重要的是内存、硬盘、网卡等偶尔罢工就会导致整个系统停摆
分家
缓存数据库追求的就是快速和简单,数据都在内存中。Redis 给了 Tomcat 一个 Java 客户端端软件,叫作 Jedis ,使用这个客户端软件就可以访问
redis分片
这么多缰存服务器,每个 Redis 存的数据都不一样,对于应用程序来说,每次向 Redis 中存放数据的时候,到底选哪个?是存到 1 号缓服务器,还是 2号3号,数据存储也要尽可能均匀
哈希映射
余数算法
一致性哈希算法
用服务器的ip或者 hostname ,调用 hash 函数: hash(ip) = hashcode , 就得到了,然后就可以把这台服务器的 hashcode 对应到这个圆圈上的某个位置当然,这个 hashcode 的值应该在 2 32 2^{32} 232之内
数据可以映射到圆圈上的某个位置,然后从这个位置开始,顺时针找到第一台服务器
虽然增加了一台服务器 4,但是受到影响的只有一部分数据,那就是在服务器 2 和服务器 4 之间的 (key,value),如(key2, value2),当查找这些数据的时候,还会顺时针,找到了服务器 4,但是数据却不在服务器 4
如果服务器经过 Hash 计算以后,在你的圆圈上不均匀,挤在了一起,那岂不会发生某些服务器负载过高而某些服务器负载太低的情况? 虚拟服务器把一台真实的服务器看作多台虚拟的服务器,让官们分布在圆圈上,增加均匀性
哈希槽
Hash 槽比一致性哈希要简单,共有 16384 个槽,每台服务器分管其中一部分。比如有三台服务器,第一台服务器负责[0,5460]这个范围,第二台服务器负责[5461,10992]这个范围,第三台服务器负责[ 10923,16383]这个范围
使用了一种 CRC16 算法,先对 key 产生一个整数值,再对 1 6384 求余数.增加服务器 4,那就会从服务器 1 、服务器 2、服务器 3 负责的槽中各自台出一些交给服务器 4管理,对应的数据也要搬过去。
salve 节点虽然是备份,但是时刻准备着替换 master 。 如果 master 节点nodeA 挂掉,那我就用某种算洁白动从剩下的 salve 中选取一个当作新的 master
Keepalived 能够把我们兄弟俩形成一种 master-slave 结构,同一时刻只有一个工作,另一个原地待命 。 如果工作的那个挂掉了,待命的那个接管。对外只提供了一个 ip地址,看起来好像只有一台机器
Nginx 只是转发请求,不保存状态,实现高可用性很容易 , 但是 Tomcat 这边有 session用户的登录信息、购物东等都是状态信息,处理不好状态的问题,Tomcat 集群的威力就大打折扭,无法完成真正的失效转移,甚至无法使用
数据库读写分离
先设置一个 master 的数据库,然后再设置一个或者几个 slave 的数据库。 不过限制 master 库可读可写, slave 库只能读、不能写 。最后还要将 master 库的数据尽快地复制到 slave 库,让数据保持一致