2011年8月24日
> 引言
在Jorm中,主键的生成策略主要有AUTO、UUID、GUID、FOREIGN、SEQUENCE、INCREMENT、IDENTITY、ASSIGNED,下面分别来讲述这几种策略的应用场景
> GenerationType.AUTO
Jorm的默认主键策略,自动增长型,自增步长为1,适用数据类型int,long,如:
private int id // 默认策略就是AUTO,故可以不写主键策略
或
@Id(GenerationType.AUTO) // 默认策略可以省去不写的哦~
private int id
> GenerationType.INCREMENT
顾名思义,增长型,适用数据类型int,long。自增步长为1
1> 使用默认自增步长1,如:
@Id(GenerationType.INCREMENT)
@Column("item_id")
private long id;
2> 使用自定义步长,如:
@Id(value = GenerationType.INCREMENT, incrementBy=3) // 这里自增步长为3,注意写法
private int id;
> GenerationType.IDENTITY
对于那些实现了自动增长的数据库,可以使用IDENTITY,如MySQL,SQL Server,PostreSQL,前提是
MySQL数据库中建表语句定义了主键为:id(你的主键列名) int NOT NULL AUTO_INCREMENT 或
id(你的主键列名) bigint NOT NULL AUTO_INCREMENT
SQL Server数据库中建表语句定义了主键为:id int identity(xx, xx) 如此类似
PostreSQL数据库中建表语句定义了主键为:id bigserial 或 id serial
使用例子
@Id(GenerationType.IDENTITY)
@Column("id")
private long sid;
> GenerationType.UUID
与数据库无关的策略,适用数据类型:字符串类型,适用所有数据库,长度须大于或等于32
@Id(GenerationType.UUID)
private String id;
> GenerationType.GUID
与UUID有点类似,不过这个id值是又数据库来生成的,适用于数据库MySQL、PostgreSQL、SQL Server、Oracle等
@Id(GenerationType.GUID)
private String id;
> GenerationType.FOREIGN
适用于一对一关系中引用了另一个对象的主键作为自己的主键的情形,如:
@Id(GenerationType.FOREIGN)
@Column("identity_number")
private String identity;
> GenerationType.SEQUENCE
这个不用多说,应用于Oracle、H2、PostgreSQL等有sequence序列功能的数据库
> GenerationType.ASSIGNED
用户自定义生成,需要由程序员手工给主键主动赋值
posted @
2011-10-10 15:17 jadmin 阅读(1488) |
评论 (3) |
编辑 收藏
直接上代码吧:
> Demo one
public void batch_op_one() {
session = Jorm.getSession();
JdbcBatcher batcher = session.createBatcher();
batcher.addBatch("delete from t_id_auto");
batcher.addBatch("delete from t_incre");
batcher.addBatch("delete from t_user");
batcher.execute();
session.beginTransaction();
long start;
try {
start = System.currentTimeMillis();
String sql = "INSERT INTO t_user(sex,age,career,name,id) VALUES(?,?,?,?,?)";
for (int i = 0; i < 100000; i++) {
batcher.addBatch(sql, new Object[] {"男", Numbers.random(98), Strings.random(10), Strings.fixed(6), (i+1) });}
String sqlx = "INSERT INTO t_id_auto(name, id) VALUES(?, ?)";
for (int i = 0; i < 100000; i++) {
batcher.addBatch(sqlx, new Object[] {Strings.fixed(6), (i+1)});
if(i > 200) {
//Integer.parseInt("kkk");
}
}
batcher.execute();
System.out.println(System.currentTimeMillis() - start);
} catch (Exception e) {
session.rollback();
} finally {
session.endTransaction();
session.close();
}
}
> Demo two
public void batch_op_two() {
session = Jorm.getSession();
session.beginTransaction();
session.clean(User.class);
JdbcBatcher batcher = session.createBatcher();
batcher.setBatchSize(500);// 指定每批处理的记录数
User u;
int times = 20 * 100;
long start = System.currentTimeMillis();
for(int i = 0; i < times; i++) {
String sex = (i % 2 == 0 ? "男" : "女");
u = new User(Strings.fixed(6), sex, Numbers.random(100), Strings.random(16));
batcher.save(u);
}
batcher.execute();
session.endTransaction();
long cost = (System.currentTimeMillis() - start);
System.out.println("Total:" + cost);
System.out.println("Each:" + (float) cost / times);
session.close();
}
项目地址:http://javaclub.sourceforge.net/jorm.html
下载地址: http://sourceforge.net/projects/javaclub/files/jorm/
posted @
2011-10-09 20:09 jadmin 阅读(1287) |
评论 (0) |
编辑 收藏
关系数据库不支持继承,我们可以做如下的映射,这些映射都是牺牲关系模式的范式基础的
1, 用一个表包含所有继承层次的所有字段,然后标识列来标示是哪个类。这种映射方法最简单,但是是违反规范化的,而且有些字段要强制为NULL值,无法保证关系数据模型的数据完整性,这种映射方式性能最高,最简单。
2, 每个具体类一张表(意思就是父类不需要表),所有父属性在具体类表中重复,这种映射如果要查询父类要全部扫描子类表,而且一旦父类变化,这些字表要全部变化。
3, 每个类一张表,表里只包含所属类的属性,然后子类和父类共享外键,这种映射避免了第2种的可怕的修改,但是查询的时候要执行连接。
posted @
2011-09-27 09:38 jadmin 阅读(195) |
评论 (0) |
编辑 收藏
在一般情况下,在新增领域对象后,都需要获取对应的主键值。使用应用层来维护主键,在一定程度上有利于程序性能的优化和应用移植性的提高。在采用数据库自增主键的方案里,如果JDBC驱动不能绑定新增记录对应的主键,就需要手工执行查询语句以获取对应的主键值,对于高并发的系统,这很容易返回错误的主键。通过带缓存的DataFieldMaxValueIncrementer,可以一次获取批量的主键值,供多次插入领域对象时使用,它的执行性能是很高的。
我们经常使用数据的自增字段作为表主键,也即主键值不在应用层产生,而是在新增记录时,由数据库产生。这样,应用层在保存对象前并不知道对象主键值,而必须在保存数据后才能从数据库中返回主键值。在很多情况下,我们需要获取新对象持久化后的主键值。在Hibernate等ORM框架,新对象持久化后,Hibernate会自动将主键值绑定到对象上,给程序的开发带来了很多方便。
在JDBC 3.0规范中,当新增记录时,允许将数据库自动产生的主键值绑定到Statement或PreparedStatement中。
使用Statement时,可以通过以下方法绑定主键值: int executeUpdate(String sql, int autoGeneratedKeys)
也可以通过Connection创建绑定自增值的PreparedStatement: PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
当autoGeneratedKeys参数设置为Statement.RETURN_GENERATED_KEYS值时即可绑定数据库产生的主键值,设置为Statement.NO_GENERATED_KEYS时,不绑定主键值。下面的代码演示了Statement绑定并获取数据库产生的主键值的过程:
Statement stmt = conn.createStatement();
String sql = "INSERT INTO t_topic(topic_title,user_id) VALUES(‘测试主题’,’123’)";
stmt.executeUpdate(sql,Statement.RETURN_GENERATED_KEYS); // ①指定绑定表自增主键值
ResultSet rs = stmt.getGeneratedKeys();
if( rs.next() ) {
intkey = rs.getInt(); // ②获取对应的表自增主键值
}
Spring利用这一技术,提供了一个可以返回新增记录对应主键值的方法: int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder) ,其中第二个参数类型org.springframework.jdbc.support.KeyHolder,它是一个回调接口,Spring使用它保存新增记录对应的主键,该接口的接口方法描述如下:
Number getKey() throws InvalidDataAccessApiUsageException;
当仅插入一行数据,主键不是复合键且是数字类型时,通过该方法可以直接返回新的主键值。如果是复合主键,或者有多个主键返回时,该方法抛出 InvalidDataAccessApiUsageException。该方法是最常用的方法,因为一般情况下,我们一次仅插入一条数据并且主键字段类型为数字类型;
如果是复合主键,则列名和列值构成Map中的一个Entry。如果返回的是多个主键,则抛出InvalidDataAccessApiUsageException异常;
Map getKeys() throws InvalidDataAccessApiUsageException;
如果返回多个主键,即PreparedStatement新增了多条记录,则每一个主键对应一个Map,多个Map构成一个List。
List getKeyList():
Spring为KeyHolder接口指代了一个通用的实现类GeneratedKeyHolder,该类返回新增记录时的自增长主键值。假设我们希望在新增论坛板块对象后,希望将主键值加载到对象中,则可以按以下代码进行调整:
public voidaddForum(final Forum forum) {
final String sql = "INSERT INTO t_forum(forum_name,forum_desc) VALUES(?,?)";
KeyHolder keyHolder = newGeneratedKeyHolder(); // ①创建一个主键执有者
getJdbcTemplate().update(newPreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, forum.getForumName());
ps.setString(2, forum.getForumDesc());
returnps;
}
}, keyHolder);
forum.setForumId(keyHolder.getKey().intValue()); // ②从主键执有者中获取主键
}
这样,在调用addForum(Forum forum)新增forum领域对象后,forum将拥有对应的主键值,方便后继的使用。在JDBC 3.0之前的版本中,PreparedStatement不能绑定主键,如果采用表自增键(如MySQL的auto increment或SQLServer的identity)将给获取正确的主键值带来挑战——因为你必须在插入数据后,马上执行另一条获取新增主键的查询语句。下面给出了不同数据库获取最新自增主键值的查询语句:
posted @
2011-09-25 14:27 jadmin 阅读(986) |
评论 (0) |
编辑 收藏
1) Assigned
主键由外部程序负责生成,无需Hibernate参与。
2) hilo
通过hi/lo 算法实现的主键生成机制,需要额外的数据库表保存主键生成历史状态。
3) seqhilo
与hilo 类似,通过hi/lo 算法实现的主键生成机制,只是主键历史状态保存在Sequence中,适用于支持Sequence的数据库,如Oracle。
4) increment
主键按数值顺序递增。此方式的实现机制为在当前应用实例中维持一个变量,以保存着当前的最大值,之后每次需要生成主键的时候将此值加1作为主键。 这种方式可能产生的问题是:如果当前有多个实例访问同一个数据库,那么由于各个实例各自维护主键状态,不同实例可能生成同样的主键,从而造成主键重复异常。因此,如果同一数据库有多个实例访问,此方式必须避免使用。
5) identity
采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL中的主键生成机制。
6) sequence
采用数据库提供的sequence 机制生成主键。如Oralce 中的Sequence。
7) native
由Hibernate根据底层数据库自行判断采用identity、hilo、sequence其中一种作为主键生成方式。
8) uuid.hex
由Hibernate基于128 位唯一值产生算法生成16 进制数值(编码后以长度32 的字符串表示)作为主键。
9) uuid.string
与uuid.hex 类似,只是生成的主键未进行编码(长度16)。在某些数据库中可能出现问题(如PostgreSQL)。
10) foreign
使用外部表的字段作为主键。一般而言,利用uuid.hex方式生成主键将提供最好的性能和数据库平台适应性。
另外由于常用的数据库,如Oracle、DB2、SQLServer、MySql 等,都提供了易用的主键生成机制(Auto-Increase 字段或者Sequence)。我们可以在数据库提供的主键生成机制上,采用generator-class=native的主键生成方式。不过值得注意的是,一些数据库提供的主键生成机制在效率上未必最佳,
大量并发insert数据时可能会引起表之间的互锁。数据库提供的主键生成机制,往往是通过在一个内部表中保存当前主键状态(如对于自增型主键而言,此内部表中就维护着当前的最大值和递增量), 之后每次插入数据会读取这个最大值,然后加上递增量作为新记录的主键,之后再把这个新的最大值更新回内部表中,这样,一次Insert操作可能导致数据库内部多次表读写操作,同时伴随的还有数据的加锁解锁操作,这对性能产生了较大影响。 因此,对于并发Insert要求较高的系统,推荐采用uuid.hex 作为主键生成机制。
如果需要采用定制的主键生成算法,则在此处配置主键生成器,主键生成器须实现org.hibernate.id.IdentifierGenerator 接口
关键词: Hibernate 主键 主键生成方式 IdentifierGenerator
posted @
2011-09-25 13:47 jadmin 阅读(998) |
评论 (0) |
编辑 收藏
摘要:
阅读全文
posted @
2011-09-23 16:17 jadmin 阅读(1268) |
评论 (1) |
编辑 收藏
主要更新:
----------------------------------------
* [35] fix: oracle下一个分页取limit数错误的bug.
* [34] fix: oracle下检测是否支持Savepoints时,一个未捕获的异常.
* [33] add: 对bonecp的支持
* [32] add: 对proxool的支持
* [31] add: 对commons-dbcp的支持
* [30] fix: classpath没有config.properties文件会报错
posted @
2011-09-23 10:53 jadmin 阅读(191) |
评论 (0) |
编辑 收藏
> 引言
有时候我们有这样的需求,对象有一个属性可能有多个值,需要在数据库中作为一个字段存储
还是以User为例,career存储多个职业
> 建表
以MySQL为例,执行下面的sql建立数据表
CREATE TABLE `t_user` (
`id` int(11) NOT NULL,
`name` varchar(50) DEFAULT NULL,
`sex` char(4) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`career` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
> 代码
实体类 User.java
@Entity(table = "t_user")
@PK(value = "id")
public class User implements Serializable {
/** desc */
private static final long serialVersionUID = -4750351638245912867L;
@Id
private int id;
private String name;
private String sex;
private Integer age;
@Basic(processor=DefinedFieldProcessor.class)
private String[] career;
@NoColumn
private int kvalue;
public JawaUser() {
super();
}
public JawaUser(String name, String sex, Integer age, String[] career) {
super();
this.name = name;
this.sex = sex;
this.age = age;
this.career = career;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String[] getCareer() {
return career;
}
public void setCareer(String[] career) {
this.career = career;
}
public int getKvalue() {
return kvalue;
}
public void setKvalue(int kvalue) {
this.kvalue = kvalue;
}
public String toString() {
return "User [age=" + age + ", career=" + Arrays.toString(career)
+ ", id=" + id + ", kvalue=" + kvalue + ", name=" + name
+ ", sex=" + sex + "]";
}
}
属性字段处理类 DefinedFieldProcessor.java
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.javaclub.jorm.Session;
import org.javaclub.jorm.common.CommonUtil;
import org.javaclub.jorm.common.Reflections;
import org.javaclub.jorm.jdbc.process.FieldProcessor;
public class DefinedFieldProcessor implements FieldProcessor {
public Object insert(Session session, Object entity, Field field) {
String[] crs = (String[]) Reflections.getFieldValue(entity, field);
if(!CommonUtil.isEmpty(crs)) {
StringBuilder sbf = new StringBuilder();
for (int i = 0; i < crs.length; i++) {
if(i > 0) {
sbf.append(",");
}
sbf.append(crs[i]);
}
return sbf.toString();
}
return "";
}
public void load(Session session, Object entity, Field field, ResultSet rs,
int idx) throws SQLException {
String str = rs.getString(idx);
String[] crs = str.split(",");
Reflections.setFieldValue(entity, field, crs);
}
}> 测试
import org.javaclub.jorm.Jorm;
import org.javaclub.jorm.Session;
import org.javaclub.jorm.common.Numbers;
import org.javaclub.jorm.common.Strings;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class FieldProcessorTest {
static Session session;
@BeforeClass
public static void setUpBeforeClass() {
session = Jorm.getSession();
}
@AfterClass
public static void destroy() {
Jorm.free();
}
@Test
public void test_save() {
session.clean(User.class);
User u;
for (int i = 0; i < 100; i++) {
String sex = (i % 2 == 0 ? "男" : "女");
String[] cr = {};
if(i % 3 == 0) {
cr = new String[] {Strings.fixed(2), Strings.random(5), Strings.fixed(6)};
} else if(i % 3 == 1) {
cr = new String[] {Strings.fixed(2), Strings.random(5)};
} else {
cr = new String[] {Strings.fixed(2)};
}
u = new User(Strings.fixed(6), sex, Numbers.random(100), cr);
session.save(u);
}
for (int i = 0; i < 10; i++) {
u = session.read(User.class, i + 1);
System.out.println(u);
}
}
}
posted @
2011-09-22 20:16 jadmin 阅读(1213) |
评论 (0) |
编辑 收藏
> 准备
以MySQL为例,执行下面的sql建立数据表
CREATE TABLE `t_user` (
`id` int(11) NOT NULL,
`name` varchar(50) DEFAULT NULL,
`sex` char(4) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`career` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
> 引入jar或maven依赖,需要jar包
gerald-jorm-1.0.5.jar 最新版本下载:http://sourceforge.net/projects/javaclub/files
commons-logging-1.1.1.jar
log4j-1.2.14.jar
mysql-connector-java-5.1.6.jar
javassist-3.11.0.GA.jar 或 cglib-nodep-2.2.2.jar (根据实际情况选择性加入)
> 配置文件
在你的java工程的classpath下建立config.properties和jdbc.cfg.xml文件
config.properties内容:
# 下面路径可以根据实际情况指定,为相对classpath的路径地址
jdbc.config.path=jdbc.cfg.xml
jdbc.cfg.xml内容:
<?xml version='1.0' encoding="UTF-8"?>
<jdbc-configuration>
<constant name="show_sql" value="true" />
<constant name="jdbc.batch_size" value="600" />
<constant name="bytecode.provider" value="cglib" />
<connections default="simple">
<connection name="simple">
<property name="connection.implementation">org.javaclub.jorm.jdbc.connection.impl.SimpleConnection</property>
<property name="connection.dialect">MySQLDialect</property>
<property name="connection.driver">com.mysql.jdbc.Driver</property>
<property name="connection.jdbcurl">jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8</property>
<property name="connection.database">test</property>
<property name="connection.username">root</property>
<property name="connection.password">root</property>
</connection>
<connection name="c3p0">
<property name="connection.implementation">org.javaclub.jorm.jdbc.connection.impl.PooledConnection</property>
<property name="connection.dialect">MySQLDialect</property>
<property name="connection.driver">com.mysql.jdbc.Driver</property>
<property name="connection.jdbcurl">jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8</property>
<property name="connection.database">test</property>
<property name="connection.username">root</property>
<property name="connection.password">root</property>
<property name="connection.pool.min">1</property>
<property name="connection.pool.max">8</property>
<property name="connection.test.sql">select 1</property>
</connection>
</connections>
</jdbc-configuration>
> 实体类User.java
@PK(value = "id")
@Entity(table="t_user")
public class User {
@Id
private int id;
private String name;
private String sex;
private Integer age;
private String career;
@NoColumn
private int kvalue;
public User() {
super();
}
public User(String name, String sex, Integer age, String career) {
super();
this.name = name;
this.sex = sex;
this.age = age;
this.career = career;
}
public User(Integer id, String name, String sex, Integer age, String career) {
super();
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
this.career = career;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getCareer() {
return career;
}
public void setCareer(String career) {
this.career = career;
}
public int getKvalue() {
return kvalue;
}
public void setKvalue(int kvalue) {
this.kvalue = kvalue;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("[" + id + ", " + name + ", " + sex + ", " + age + ", " + career + "]");
return sb.toString();
}
}
这里数据库字段和java实体类User的属性在命名上是一致的,如果不一致,比如如果表创建sql为:
CREATE TABLE `t_user` (
`user_id` int(11) NOT NULL,
`user_name` varchar(50) DEFAULT NULL,
`sex` char(4) DEFAULT NULL,
`col_age` int(11) DEFAULT NULL,
`career_job` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
那么对应的实体User应该写成:
@PK(value = "id")
@Entity(table="t_user")
public class User {
@Id
@Column("user_id")
private int id;
@Column("user_name")
private String name;
// 与数据库字段命名一致,可以不指定@Column
private String sex;
@Column("col_age")
private Integer age;
@Column("career_job")
private String career;
@NoColumn
private int kvalue;
public User() {
super();
}
public User(String name, String sex, Integer age, String career) {
super();
this.name = name;
this.sex = sex;
this.age = age;
this.career = career;
}
public User(Integer id, String name, String sex, Integer age, String career) {
super();
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
this.career = career;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getCareer() {
return career;
}
public void setCareer(String career) {
this.career = career;
}
public int getKvalue() {
return kvalue;
}
public void setKvalue(int kvalue) {
this.kvalue = kvalue;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("[" + id + ", " + name + ", " + sex + ", " + age + ", " + career + "]");
return sb.toString();
}
}
> 对User的增删查改,UserCrudTest.java,记得引入junit-4.8.2.jar
public class UserCrudTest {
static Session session;
@BeforeClass
public static void before() {
session = Jorm.getSession();
}
@AfterClass
public static void after() {
Jorm.free();
}
@Test
public void save_user() {
session.clean(User.class);
User user = null;
for (int i = 0; i < 600; i++) {
String sex = (i % 2 == 0 ? "男" : "女");
user = new User(Strings.fixed(5), sex, Numbers.random(98), Strings.random(8));
session.save(user);
}
}
@Test // 批量保存
public void batch_save_user() {
session.clean(User.class);
JdbcBatcher batcher = session.createBatcher();
User user = null;
for (int i = 0; i < 600; i++) {
String sex = (i % 2 == 0 ? "男" : "女");
user = new User(Strings.fixed(5), sex, Numbers.random(98), Strings.random(8));
batcher.save(user);
}
batcher.execute();
}
@Test
public void loadUser() {
User user = session.read(User.class, 1);
// 这里user是一个代理对象,因为@Entity(table="t_user", lazy = true)
System.out.println(user.getCareer());// 发出查询sql
}
@Test
public void deletUser() {
User user = session.read(User.class, 1);
if(null != user) {
session.delete(user);
}
user = session.read(User.class, 1);
System.out.println(user);
}
@Test
public void test_update_proxy() {
User u;
u = session.read(User.class, 2);
Assert.assertNotNull(u);
Assert.assertTrue(u instanceof JormProxy);
u.setName("Gerald.Chen");
session.update(u);
System.out.println(u.getName());
u = session.read(User.class, 2);
Assert.assertTrue("Gerald.Chen".equals(u.getName()));
}
@Test
public void queryUser() {
SqlParams<User> params = new SqlParams<User>();
params.setObjectClass(User.class);
params.setFirstResult(8);
params.setMaxResults(20);
List<User> users = session.list(params);
System.out.println(users.size());
System.out.println(users);
}
}
posted @
2011-09-21 18:42 jadmin 阅读(1406) |
评论 (5) |
编辑 收藏
> 特点
1.支持多数据源管理和配置
2.自动封装Entity
3.支持事务
4.支持存储过程的方便调用
5.支持lazy加载
6.支持分页查询
7.支持多种数据库H2,MySQL,Oracle,PostgrSQL,SQLServer
> 要求
1.JDK 1.5 or later
2.如需要lazy加载,需要引入cglib或javaassit,具体可配置
> 示例
1.添加
Session session = Jorm.getSession();
User u = new User("Gerald.Chen", "男", 21, "job");;
session.save(u);
2.删除
session.clean(User.class);// 清空表
session.delete(User.class, "id > 100");// 指定条件删除
session.delete(user);
3.查询
User user = session.read(User.class, 1);// 根据主键加载
// 加载第一个
User user = session.loadFirst(User.class, "(SELECT * FROM t_user WHERE id > ?)", 88);
// 分页查询
SqlParams<User> params = new SqlParams<User>("SELECT * FROM t_user WHERE id > ?", new Object[] { 6 });
params.setObjectClass(User.class);
params.setFirstResult(3);
params.setMaxResults(10);
List<User> users = session.list(params);
// 查询单个属性
String sql = "SELECT name FROM t_user WHERE id = 28";
String name = session.queryUniqueObject(sql);
// 查询属性列表
List<String> names = session.list(String.class, "SELECT name FROM t_user WHERE id > ?", 200);
List<Integer> ages = session.list(int.class, "SELECT age FROM t_user WHERE age > 18");
4.存储过程
final String pro = "{? = call hello_proc(?)}";
String r = session.call(new ProcedureCaller() {
public CallableStatement prepare() throws SQLException {
CallableStatement cs = this.getSession().getConnection().prepareCall(pro);
cs.setString(2, "World");
cs.registerOutParameter(1, Types.CHAR);
return cs;
}
public String callback(CallableStatement cs) throws SQLException {
cs.execute();
return cs.getString(1);
}
});
5.事务
session.clean(User.class);
User u;
session.beginTransaction();
try {
for(int i = 0; i < 1000; i++) {
String sex = (i % 2 == 0 ? "男" : "女");
u = new User(Strings.fixed(6), sex, Numbers.random(100), Strings.random(16));
session.save(u);
if(i == 886) {
Integer.parseInt("kkk");
}
}
session.commit();
} catch (Exception e) {
session.rollback();
} finally {
session.endTransaction();
}
这是一个完全基于JDBC的轻量java orm framework, 目标定位于使用方便,简单,后续会增加许多新的特性
下载地址:http://sourceforge.net/projects/javaclub/files
posted @
2011-09-20 18:52 jadmin 阅读(255) |
评论 (0) |
编辑 收藏
> 原理
其实断点续传的原理很简单,就是在 Http 的请求上和一般的下载有所不同而已。
打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:
假设服务器域名为 wwww.sjtu.edu.cn,文件名为 down.zip。
GET /down.zip HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-
excel, application/msword, application/vnd.ms-powerpoint, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Connection: Keep-Alive
服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:
200
Content-Length=106786028
Accept-Ranges=bytes
Date=Mon, 30 Apr 2001 12:56:11 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT
所谓断点续传,也就是要从文件已经下载的地方开始继续下载。所以在客户端浏览器传给 Web 服务器的时候要多加一条信息 -- 从哪里开始。
下面是用自己编的一个"浏览器"来传递请求信息给 Web 服务器,要求从 2000070 字节开始。
GET /down.zip HTTP/1.0
User-Agent: NetFox
RANGE: bytes=2000070-
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
仔细看一下就会发现多了一行 RANGE: bytes=2000070-
这一行的意思就是告诉服务器 down.zip 这个文件从 2000070 字节开始传,前面的字节不用传了。
服务器收到这个请求以后,返回的信息如下:
206
Content-Length=106786028
Content-Range=bytes 2000070-106786027/106786028
Date=Mon, 30 Apr 2001 12:55:20 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT
和前面服务器返回的信息比较一下,就会发现增加了一行:Content-Range=bytes 2000070-106786027/106786028返回的代码也改为 206 了,而不再是 200 了。
> 关键点
(1) 用什么方法实现提交 RANGE: bytes=2000070-。
当然用最原始的 Socket 是肯定能完成的,不过那样太费事了,其实 Java 的 net 包中提供了这种功能。代码如下:
URL url = new URL("http://www.sjtu.edu.cn/down.zip");
HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection();
// 设置 User-Agent
httpConnection.setRequestProperty("User-Agent","NetFox");
// 设置断点续传的开始位置
httpConnection.setRequestProperty("RANGE","bytes=2000070");
// 获得输入流
InputStream input = httpConnection.getInputStream();
从输入流中取出的字节流就是 down.zip 文件从 2000070 开始的字节流。大家看,其实断点续传用 Java 实现起来还是很简单的吧。接下来要做的事就是怎么保存获得的流到文件中去了。
(2)保存文件采用的方法。我采用的是 IO 包中的 RandAccessFile 类。操作相当简单,假设从 2000070 处开始保存文件,代码如下:RandomAccess oSavedFile = new RandomAccessFile("down.zip","rw");long nPos = 2000070;// 定位文件指针到 nPos 位置oSavedFile.seek(nPos);byte[] b = new byte[1024];int nRead;// 从输入流中读入字节流,然后写到文件中while((nRead=input.read(b,0,1024)) > 0) { oSavedFile.write(b,0,nRead);}
posted @
2011-09-08 21:51 jadmin 阅读(105) |
评论 (0) |
编辑 收藏
SymmetricDS是一个平台独立的数据同步和复制的解决方案。
配置数据模型:
运行时数据模型:
posted @
2011-09-02 09:15 jadmin 阅读(230) |
评论 (0) |
编辑 收藏
> 问题:给40亿个不重复的unsigned int的整数,没排过序的,然后再给几个数,如何快速判断这几个数是否在那40亿个数当中?
> 解决:unsigned int
的取值范围是0到2^32-1。我们可以申请连续的2^32/8=512M的内存,用每一个bit对应一个unsigned
int数字。首先将512M内存都初始化为0,然后每处理一个数字就将其对应的bit设置为1。当需要查询时,直接找到对应bit,看其值是0还是1即可。
posted @
2011-08-30 21:01 jadmin 阅读(137) |
评论 (0) |
编辑 收藏
lazy的属性有false、true、extra
false和true用得比较多,extra属性是不大容易重视的,其实它和true差不多
extra有个小的智能的地方是,即调用集合的size/contains等方法的时候,hibernate并不会去加载整个集合的数据,而是发出一条聪明的SQL语句,以便获得需要的值,只有在真正需要用到这些集合元素对象数据的时候,才去发出查询语句加载所有对象的数据
posted @
2011-08-30 20:00 jadmin 阅读(107) |
评论 (0) |
编辑 收藏
本文将介绍在Linux(Red Hat 9)环境下搭建Hadoop集群,此Hadoop集群主要由三台机器组成,主机名分别为:
linux 192.168.35.101
linux02 192.168.35.102
linux03 192.168.35.103
从map reduce计算的角度讲,linux作为master节点,linux02和linux03作为slave节点。
从hdfs数据存储角度讲,linux作为namenode节点,linux02和linux03作为datanode节点。
一台namenode机,主机名为linux,hosts文件内容如下:
127.0.0.1 linux localhost.localdomain localhost
192.168.35.101 linux linux.localdomain linux
192.168.35.102 linux02
192.168.35.103 linux03
两台datanode机,主机名为linux02和linux03
>linux02的hosts文件
127.0.0.1 linux02 localhost.localdomain localhost
192.168.35.102 linux02 linux02.localdomain linux02
192.168.35.101 linux
192.168.35.103 linux03
>inux03的hosts文件
127.0.0.1 linux03 localhost.localdomain localhost
192.168.35.103 linux03 linux03.localdomain linux03
192.168.35.101 linux
192.168.35.102 linux02
1.安装JDK
> 从java.cun.com下载jdk-6u7-linux-i586.bin
> ftp上传jdk到linux的root目录下
> 进入root目录,先后执行命令
chmod 755 jdk-6u18-linux-i586-rpm.bin
./jdk-6u18-linux-i586-rpm.bin
一路按提示下去就会安装成功
> 配置环境变量
cd进入/etc目录,vi编辑profile文件,将下面的内容追加到文件末尾
export JAVA_HOME=/usr/java/jdk1.6.0_18
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
注意:三台机器都要安装JDK~
2.设置Master/Slave机器之间可以通过SSH无密钥互相访问
最好三台机器的使用相同的账户名,我是直接使用的root账户
操作namenode机linux:
以用户root登录linux,在/root目录下执行下述命令:
ssh-keygen -t rsa
一路回车下去即可在目录/root/.ssh/下建立两个文件id_rsa.pub和id_rsa。
接下来,需要进入/root/.ssh目录,执行如下命令:
cd .ssh
再把is_rsa.pub文件复制到linux02和linux03机器上去。
scp -r id_rsa.pub root@192.168.35.102:/root/.ssh/authorized_keys_01
scp -r id_rsa.pub root@192.168.35.103:/root/.ssh/authorized_keys_01
操作datanode机linux02:
以用户root登录linux02,在目录下执行命令:
ssh-keygen -t rsa
一路回车下去即可在目录/root/.ssh/下建立两个文件 id_rsa.pub和id_rsa。
接下来,需要进入/root/.ssh目录,执行如下命令:
cd .ssh
再把is_rsa.pub文件复制到namenode机linux上去。
scp -r id_rsa.pub root@192.168.35.101:/root/.ssh/authorized_keys_02
操作datanode机linux03:
以用户root登录linux03,在目录下执行命令:
ssh-keygen -t rsa
一路回车下去即可在目录/root/.ssh/下建立两个文件 id_rsa.pub和id_rsa。
接下来,需要进入/root/.ssh目录,执行如下命令:
cd .ssh
再把is_rsa.pub文件复制到namenode机linux上去。
scp -r id_rsa.pub root@192.168.35.101:/root/.ssh/authorized_keys_03
*******************************************************************************
上述方式分别为linux\linux02\linux03机器生成了rsa密钥,并且把linux的id_rsa.pub复制到linux02\linux03上去了,而把linux02和linux03上的id_rsa.pub复制到linux上去了。
接下来还要完成如下步骤:
linux机:
以root用户登录linux,并且进入目录/root/.ssh下,执行如下命令:
cat id_rsa.pub >> authorized_keys
cat authorized_keys_02 >> authorized_keys
cat authorized_keys_03 >> authorized_keys
chmod 644 authorized_keys
linux02机:
以root用户登录linux02,并且进入目录/root/.ssh下,执行如下命令:
cat id_rsa.pub >> authorized_keys
cat authorized_keys_01 >> authorized_keys
chmod 644 authorized_keys
linux03机:
以root用户登录linux03,并且进入目录/root/.ssh下,执行如下命令:
cat id_rsa.pub >> authorized_keys
cat authorized_keys_01 >> authorized_keys
chmod 644 authorized_keys
通过上述配置,现在以用户root登录linux机,既可以无密钥认证方式访问linux02和linux03了,同样也可以在linux02和linux03上以ssh linux方式连接到linux上进行访问了。
3.安装和配置Hadoop
> 在namenode机器即linux机上安装hadoop
我下载的是hadoop-0.20.2.tar.gz,ftp上传到linux机的/root目录上,解压到安装目录/usr/hadoop,最终hadoop的根目录是/usr/hadoop/hadoop-0.20.2/
编辑/etc/profile文件,在文件尾部追加如下内容:
export HADOOP_HOME=/usr/hadoop/hadoop-0.20.2
export PATH=$HADOOP_HOME/bin:$PATH
> 配置Hadoop
core-site.xml:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<!-- Put site-specific property overrides in this file. -->
<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://192.168.35.101:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/tmp/hadoop/hadoop-${user.name}</value>
</property>
</configuration>
hdfs-site.xml:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<!-- Put site-specific property overrides in this file. -->
<configuration>
<property>
<name>dfs.name.dir</name>
<value>/home/hadoop/name</value>
</property>
<property>
<name>dfs.data.dir</name>
<value>/home/hadoop/data</value>
</property>
<property>
<name>dfs.replication</name>
<value>2</value>
</property>
</configuration>
mapred-site.xml
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<!-- Put site-specific property overrides in this file. -->
<configuration>
<property>
<name>mapred.job.tracker</name>
<value>192.168.35.101:9001</value>
</property>
</configuration>
masters
192.168.35.101
slaves
192.168.35.102
192.168.35.103
至此,hadoop的简单配置已经完成
> 将在namenode机器上配置好的hadoop部署到datanode机器上
这里使用scp命令进行远程传输,先后执行命令
scp -r /usr/hadoop/hadoop-0.20.2 root@192.168.35.102:/usr/hadoop/
scp -r /usr/hadoop/hadoop-0.20.2 root@192.168.35.103:/usr/hadoop/
4.测试
以root用户登入namenode机linux,进入目录/usr/hadoop/hadoop-0.20.2/
cd /usr/hadoop/hadoop-0.20.2
> 执行格式化
[root@linux hadoop-0.20.2]# bin/hadoop namenode -format
11/07/26 21:16:03 INFO namenode.NameNode: STARTUP_MSG:
/************************************************************
STARTUP_MSG: Starting NameNode
STARTUP_MSG: host = linux/127.0.0.1
STARTUP_MSG: args = [-format]
STARTUP_MSG: version = 0.20.2
STARTUP_MSG: build = https://svn.apache.org/repos/asf/hadoop/common/branches/branch-0.20 -r 911707; compiled by 'chrisdo' on Fri Feb 19 08:07:34 UTC 2010
************************************************************/
Re-format filesystem in /home/hadoop/name ? (Y or N) Y
11/07/26 21:16:07 INFO namenode.FSNamesystem: fsOwner=root,root,bin,daemon,sys,adm,disk,wheel
11/07/26 21:16:07 INFO namenode.FSNamesystem: supergroup=supergroup
11/07/26 21:16:07 INFO namenode.FSNamesystem: isPermissionEnabled=true
11/07/26 21:16:07 INFO common.Storage: Image file of size 94 saved in 0 seconds.
11/07/26 21:16:07 INFO common.Storage: Storage directory /home/hadoop/name has been successfully formatted.
11/07/26 21:16:07 INFO namenode.NameNode: SHUTDOWN_MSG:
/************************************************************
SHUTDOWN_MSG: Shutting down NameNode at linux/127.0.0.1
************************************************************/
> 启动hadoop
[root@linux hadoop-0.20.2]# bin/start-all.sh
starting namenode, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-namenode-linux.out
192.168.35.102: starting datanode, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-datanode-linux02.out
192.168.35.103: starting datanode, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-datanode-linux03.out
192.168.35.101: starting secondarynamenode, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-secondarynamenode-linux.out
starting jobtracker, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-jobtracker-linux.out
192.168.35.103: starting tasktracker, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-tasktracker-linux03.out
192.168.35.102: starting tasktracker, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-tasktracker-linux02.out
[root@linux hadoop-0.20.2]#
> 用jps命令查看进程
[root@linux hadoop-0.20.2]# jps
7118 SecondaryNameNode
7343 Jps
6955 NameNode
7204 JobTracker
[root@linux hadoop-0.20.2]#
posted @
2011-08-25 16:01 jadmin 阅读(124) |
评论 (0) |
编辑 收藏
引言
Hadoop分布式文件系统(HDFS)被设计成适
合运行在通用硬件(commodity
hardware)上的分布式文件系统。它和现有的分布式文件系统有很多共同点。但同时,它和其他的分布式文件系统的区别也是很明显的。HDFS是一个高
度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。HDFS放宽了一部分POSIX约束,来实
现流式读取文件系统数据的目的。HDFS在最开始是作为Apache Nutch搜索引擎项目的基础架构而开发的。HDFS是Apache Hadoop
Core项目的一部分。这个项目的地址是http://hadoop.apache.org/core/。
前提和设计目标
硬件错误
硬件错误是常态而不是异常。HDFS可能由成百上千的服务器所构成,每个服务器上存储着文件系统的部分数据。我们面对的现实是构成系统的组件数目是巨大
的,而且任一组件都有可能失效,这意味着总是有一部分HDFS的组件是不工作的。因此错误检测和快速、自动的恢复是HDFS最核心的架构目标。
流式数据访问
运行在HDFS上的应用和普通的应用不同,需要流式访问它们的数据集。HDFS的设计中更多的考虑到了数据批处理,而不是用户交互处理。比之数据访问的低
延迟问题,更关键的在于数据访问的高吞吐量。POSIX标准设置的很多硬性约束对HDFS应用系统不是必需的。为了提高数据的吞吐量,在一些关键方面对
POSIX的语义做了一些修改。
大规模数据集
运行在HDFS上的应用具有很大的数据集。HDFS上的一个典型文件大小一般都在G字节至T字节。因此,HDFS被调节以支持大文件存储。它应该能提供整
体上高的数据传输带宽,能在一个集群里扩展到数百个节点。一个单一的HDFS实例应该能支撑数以千万计的文件。
简单的一致性模型
HDFS应用需要一个“一次写入多次读取”的文件访问模型。一个文件经过创建、写入和关闭之后就不需要改变。这一假设简化了数据一致性问题,并且使高吞吐
量的数据访问成为可能。Map/Reduce应用或者网络爬虫应用都非常适合这个模型。目前还有计划在将来扩充这个模型,使之支持文件的附加写操作。
“移动计算比移动数据更划算”
一个应用请求的计算,离它操作的数据越近就越高效,在数据达到海量级别的时候更是如此。因为这样就能降低网络阻塞的影响,提高系统数据的吞吐量。将计算移
动到数据附近,比之将数据移动到应用所在显然更好。HDFS为应用提供了将它们自己移动到数据附近的接口。
异构软硬件平台间的可移植性
HDFS在设计的时候就考虑到平台的可移植性。这种特性方便了HDFS作为大规模数据应用平台的推广。
Namenode 和 Datanode
HDFS采用master/slave架构。一个HDFS集群是由一个Namenode和一定数目的Datanodes组成。Namenode是一个中心
服务器,负责管理文件系统的名字空间(namespace)以及客户端对文件的访问。集群中的Datanode一般是一个节点一个,负责管理它所在节点上
的存储。HDFS暴露了文件系统的名字空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组
Datanode上。Namenode执行文件系统的名字空间操作,比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的
映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。
Namenode和Datanode被设计成可以在普通的商用机器上运行。这些机器一般运行着GNU/Linux操作系统(OS)。
HDFS采用Java语言开发,因此任何支持Java的机器都可以部署Namenode或Datanode。由于采用了可移植性极强的Java语言,使得
HDFS可以部署到多种类型的机器上。一个典型的部署场景是一台机器上只运行一个Namenode实例,而集群中的其它机器分别运行一个Datanode
实例。这种架构并不排斥在一台机器上运行多个Datanode,只不过这样的情况比较少见。
集群中单一Namenode的结构大大简化了系统的架构。Namenode是所有HDFS元数据的仲裁者和管理者,这样,用户数据永远不会流过Namenode。
文件系统的名字空间 (namespace)
HDFS支持传统的层次型文件组织结构。用户或者应用程序可以创建目录,然后将文件保存在这些目录里。文件系统名字空间的层次结构和大多数现有的文件系统
类似:用户可以创建、删除、移动或重命名文件。当前,HDFS不支持用户磁盘配额和访问权限控制,也不支持硬链接和软链接。但是HDFS架构并不妨碍实现
这些特性。
Namenode负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被Namenode记录下来。应用程序可以设置HDFS保存的文件的副本数目。文件副本的数目称为文件的副本系数,这个信息也是由Namenode保存的。
数据复制
HDFS被设计成能够在一个大集群中跨机器可靠地存储超大文件。它将每个文件存储成一系列的数据块,除了最后一个,所有的数据块都是同样大小的。为了容
错,文件的所有数据块都会有副本。每个文件的数据块大小和副本系数都是可配置的。应用程序可以指定某个文件的副本数目。副本系数可以在文件创建的时候指
定,也可以在之后改变。HDFS中的文件都是一次性写入的,并且严格要求在任何时候只能有一个写入者。
Namenode全权管理数据块的复制,它周期性地从集群中的每个Datanode接收心跳信号和块状态报告(Blockreport)。接收到心跳信号意味着该Datanode节点工作正常。块状态报告包含了一个该Datanode上所有数据块的列表。
副本存放: 最最开始的一步
副本的存放是HDFS可靠性和性能的关键。优化的副本存放策略是HDFS区分于其他大部分分布式文件系统的重要特性。这种特性需要做大量的调优,并需要经
验的积累。HDFS采用一种称为机架感知(rack-aware)的策略来改进数据的可靠性、可用性和网络带宽的利用率。目前实现的副本存放策略只是在这
个方向上的第一步。实现这个策略的短期目标是验证它在生产环境下的有效性,观察它的行为,为实现更先进的策略打下测试和研究的基础。
大型HDFS实例一般运行在跨越多个机架的计算机组成的集群上,不同机架上的两台机器之间的通讯需要经过交换机。在大多数情况下,同一个机架内的两台机器间的带宽会比不同机架的两台机器间的带宽大。
通过一个机架感知的
过程,Namenode可以确定每个Datanode所属的机架id。一个简单但没有优化的策略就是将副本存放在不同的机架上。这样可以有效防止当整个机
架失效时数据的丢失,并且允许读数据的时候充分利用多个机架的带宽。这种策略设置可以将副本均匀分布在集群中,有利于当组件失效情况下的负载均衡。但是,
因为这种策略的一个写操作需要传输数据块到多个机架,这增加了写的代价。
在大多数情况下,副本系数是3,HDFS的存放策略是将一个副本存放在本地机架的节点上,一个副本放在同一机架的另一个节点上,最后一个副本放在不同机架
的节点上。这种策略减少了机架间的数据传输,这就提高了写操作的效率。机架的错误远远比节点的错误少,所以这个策略不会影响到数据的可靠性和可用性。于此
同时,因为数据块只放在两个(不是三个)不同的机架上,所以此策略减少了读取数据时需要的网络传输总带宽。在这种策略下,副本并不是均匀分布在不同的机架
上。三分之一的副本在一个节点上,三分之二的副本在一个机架上,其他副本均匀分布在剩下的机架中,这一策略在不损害数据可靠性和读取性能的情况下改进了写
的性能。
当前,这里介绍的默认副本存放策略正在开发的过程中。
副本选择
为了降低整体的带宽消耗和读取延时,HDFS会尽量让读取程序读取离它最近的副本。如果在读取程序的同一个机架上有一个副本,那么就读取该副本。如果一个HDFS集群跨越多个数据中心,那么客户端也将首先读本地数据中心的副本。
安全模式
Namenode启动后会进入一个称为安全模式的特殊状态。处于安全模式的Namenode是不会进行数据块的复制的。Namenode从所有的
Datanode接收心跳信号和块状态报告。块状态报告包括了某个Datanode所有的数据块列表。每个数据块都有一个指定的最小副本数。当
Namenode检测确认某个数据块的副本数目达到这个最小值,那么该数据块就会被认为是副本安全(safely
replicated)的;在一定百分比(这个参数可配置)的数据块被Namenode检测确认是安全之后(加上一个额外的30秒等待时
间),Namenode将退出安全模式状态。接下来它会确定还有哪些数据块的副本没有达到指定数目,并将这些数据块复制到其他Datanode上。
文件系统元数据的持久化
Namenode上保存着HDFS的名字空间。对于任何对文件系统元数据产生修改的操作,Namenode都会使用一种称为EditLog的事务日志记录
下来。例如,在HDFS中创建一个文件,Namenode就会在Editlog中插入一条记录来表示;同样地,修改文件的副本系数也将往Editlog插
入一条记录。Namenode在本地操作系统的文件系统中存储这个Editlog。整个文件系统的名字空间,包括数据块到文件的映射、文件的属性等,都存
储在一个称为FsImage的文件中,这个文件也是放在Namenode所在的本地文件系统上。
Namenode在内存中保存着整个文件系统的名字空间和文件数据块映射(Blockmap)的映像。这个关键的元数据结构设计得很紧凑,因而一个有4G
内存的Namenode足够支撑大量的文件和目录。当Namenode启动时,它从硬盘中读取Editlog和FsImage,将所有Editlog中的
事务作用在内存中的FsImage上,并将这个新版本的FsImage从内存中保存到本地磁盘上,然后删除旧的Editlog,因为这个旧的
Editlog的事务都已经作用在FsImage上了。这个过程称为一个检查点(checkpoint)。在当前实现中,检查点只发生在Namenode
启动时,在不久的将来将实现支持周期性的检查点。
Datanode将HDFS数据以文件的形式存储在本地的文件系统中,它并不知道有关HDFS文件的信息。它把每个HDFS数据块存储在本地文件系统的一
个单独的文件中。Datanode并不在同一个目录创建所有的文件,实际上,它用试探的方法来确定每个目录的最佳文件数目,并且在适当的时候创建子目录。
在同一个目录中创建所有的本地文件并不是最优的选择,这是因为本地文件系统可能无法高效地在单个目录中支持大量的文件。当一个Datanode启动时,它
会扫描本地文件系统,产生一个这些本地文件对应的所有HDFS数据块的列表,然后作为报告发送到Namenode,这个报告就是块状态报告。
通讯协议
所有的HDFS通讯协议都是建立在TCP/IP协议之上。客户端通过一个可配置的TCP端口连接到Namenode,通过ClientProtocol协议与Namenode交互。而Datanode使用DatanodeProtocol协议与Namenode交互。一个远程过程调用(RPC)模型被抽象出来封装ClientProtocol和Datanodeprotocol协议。在设计上,Namenode不会主动发起RPC,而是响应来自客户端或 Datanode 的RPC请求。
健壮性
HDFS的主要目标就是即使在出错的情况下也要保证数据存储的可靠性。常见的三种出错情况是:Namenode出错, Datanode出错和网络割裂(network partitions)。
磁盘数据错误,心跳检测和重新复制
每个Datanode节点周期性地向Namenode发送心跳信号。网络割裂可能导致一部分Datanode跟Namenode失去联系。
Namenode通过心跳信号的缺失来检测这一情况,并将这些近期不再发送心跳信号Datanode标记为宕机,不会再将新的IO请
求发给它们。任何存储在宕机Datanode上的数据将不再有效。Datanode的宕机可能会引起一些数据块的副本系数低于指定值,Namenode不
断地检测这些需要复制的数据块,一旦发现就启动复制操作。在下列情况下,可能需要重新复制:某个Datanode节点失效,某个副本遭到损
坏,Datanode上的硬盘错误,或者文件的副本系数增大。
集群均衡
HDFS的架构支持数据均衡策略。如果某个Datanode节点上的空闲空间低于特定的临界点,按照均衡策略系统就会自动地将数据从这个Datanode
移动到其他空闲的Datanode。当对某个文件的请求突然增加,那么也可能启动一个计划创建该文件新的副本,并且同时重新平衡集群中的其他数据。这些均
衡策略目前还没有实现。
数据完整性
从某个Datanode获取的数据块有可能是损坏的,损坏可能是由Datanode的存储设备错误、网络错误或者软件bug造成的。HDFS客户端软件实
现了对HDFS文件内容的校验和(checksum)检查。当客户端创建一个新的HDFS文件,会计算这个文件每个数据块的校验和,并将校验和作为一个单
独的隐藏文件保存在同一个HDFS名字空间下。当客户端获取文件内容后,它会检验从Datanode获取的数据跟相应的校验和文件中的校验和是否匹配,如
果不匹配,客户端可以选择从其他Datanode获取该数据块的副本。
元数据磁盘错误
FsImage和Editlog是HDFS的核心数据结构。如果这些文件损坏了,整个HDFS实例都将失效。因而,Namenode可以配置成支持维护多
个FsImage和Editlog的副本。任何对FsImage或者Editlog的修改,都将同步到它们的副本上。这种多副本的同步操作可能会降低
Namenode每秒处理的名字空间事务数量。然而这个代价是可以接受的,因为即使HDFS的应用是数据密集的,它们也非元数据密集的。当
Namenode重启的时候,它会选取最近的完整的FsImage和Editlog来使用。
Namenode是HDFS集群中的单点故障(single point of failure)所在。如果Namenode机器故障,是需要手工干预的。目前,自动重启或在另一台机器上做Namenode故障转移的功能还没实现。
快照
快照支持某一特定时刻的数据的复制备份。利用快照,可以让HDFS在数据损坏时恢复到过去一个已知正确的时间点。HDFS目前还不支持快照功能,但计划在将来的版本进行支持。
数据组织
数据块
HDFS被设计成支持大文件,适用HDFS的是那些需要处理大规模的数据集的应用。这些应用都是只写入数据一次,但却读取一次或多次,并且读取速度应能满
足流式读取的需要。HDFS支持文件的“一次写入多次读取”语义。一个典型的数据块大小是64MB。因而,HDFS中的文件总是按照64M被切分成不同的
块,每个块尽可能地存储于不同的Datanode中。
Staging
客户端创建文件的请求其实并没有立即发送给Namenode,事实上,在刚开始阶段HDFS客户端会先将文件数据缓存到本地的一个临时文件。应用程序的写
操作被透明地重定向到这个临时文件。当这个临时文件累积的数据量超过一个数据块的大小,客户端才会联系Namenode。Namenode将文件名插入文
件系统的层次结构中,并且分配一个数据块给它。然后返回Datanode的标识符和目标数据块给客户端。接着客户端将这块数据从本地临时文件上传到指定的
Datanode上。当文件关闭时,在临时文件中剩余的没有上传的数据也会传输到指定的Datanode上。然后客户端告诉Namenode文件已经关
闭。此时Namenode才将文件创建操作提交到日志里进行存储。如果Namenode在文件关闭前宕机了,则该文件将丢失。
上述方法是对在HDFS上运行的目标应用进行认真考虑后得到的结果。这些应用需要进行文件的流式写入。如果不采用客户端缓存,由于网络速度和网络堵塞会对吞估量造成比较大的影响。这种方法并不是没有先例的,早期的文件系统,比如AFS,就用客户端缓存来提高性能。为了达到更高的数据上传效率,已经放松了POSIX标准的要求。
流水线复制
当客户端向HDFS文件写入数据的时候,一开始是写到本地临时文件中。假设该文件的副本系数设置为3,当本地临时文件累积到一个数据块的大小时,客户端会
从Namenode获取一个Datanode列表用于存放副本。然后客户端开始向第一个Datanode传输数据,第一个Datanode一小部分一小部
分(4
KB)地接收数据,将每一部分写入本地仓库,并同时传输该部分到列表中第二个Datanode节点。第二个Datanode也是这样,一小部分一小部分地
接收数据,写入本地仓库,并同时传给第三个Datanode。最后,第三个Datanode接收数据并存储在本地。因此,Datanode能流水线式地从
前一个节点接收数据,并在同时转发给下一个节点,数据以流水线的方式从前一个Datanode复制到下一个。
可访问性
HDFS给应用提供了多种访问方式。用户可以通过Java API接口访问,也可以通过C语言的封装API访问,还可以通过浏览器的方式访问HDFS中的文件。通过WebDAV协议访问的方式正在开发中。
DFSShell
HDFS以文件和目录的形式组织用户数据。它提供了一个命令行的接口(DFSShell)让用户与HDFS中的数据进行交互。命令的语法和用户熟悉的其他shell(例如 bash, csh)工具类似。下面是一些动作/命令的示例:
动作 | 命令 |
---|
创建一个名为/foodir的目录 | bin/hadoop dfs -mkdir /foodir |
创建一个名为/foodir的目录 | bin/hadoop dfs -mkdir /foodir |
查看名为/foodir/myfile.txt的文件内容 | bin/hadoop dfs -cat /foodir/myfile.txt |
DFSShell 可以用在那些通过脚本语言和文件系统进行交互的应用程序上。
DFSAdmin
DFSAdmin 命令用来管理HDFS集群。这些命令只有HDSF的管理员才能使用。下面是一些动作/命令的示例:
动作 | 命令 |
---|
将集群置于安全模式 | bin/hadoop dfsadmin -safemode enter |
显示Datanode列表 | bin/hadoop dfsadmin -report |
使Datanode节点datanodename退役 | bin/hadoop dfsadmin -decommission datanodename |
浏览器接口
一个典型的HDFS安装会在一个可配置的TCP端口开启一个Web服务器用于暴露HDFS的名字空间。用户可以用浏览器来浏览HDFS的名字空间和查看文件的内容。
存储空间回收
文件的删除和恢复
当用户或应用程序删除某个文件时,这个文件并没有立刻从HDFS中删除。实际上,HDFS会将这个文件重命名转移到/trash目录。只要文件还在/trash目录中,该文件就可以被迅速地恢复。文件在/trash中保存的时间是可配置的,当超过这个时间时,Namenode就会将该文件从名字空间中删除。删除文件会使得该文件相关的数据块被释放。注意,从用户删除文件到HDFS空闲空间的增加之间会有一定时间的延迟。
只要被删除的文件还在/trash目录中,用户就可以恢复这个文件。如果用户想恢复被删除的文件,他/她可以浏览/trash目录找回该文件。/trash目录仅仅保存被删除文件的最后副本。/trash目录与其他的目录没有什么区别,除了一点:在该目录上HDFS会应用一个特殊策略来自动删除文件。目前的默认策略是删除/trash中保留时间超过6小时的文件。将来,这个策略可以通过一个被良好定义的接口配置。
减少副本系数
当一个文件的副本系数被减小后,Namenode会选择过剩的副本删除。下次心跳检测时会将该信息传递给Datanode。Datanode遂即移除相应的数据块,集群中的空闲空间加大。同样,在调用setReplicationAPI结束和集群中空闲空间增加间会有一定的延迟。
参考资料
posted @
2011-08-24 12:59 jadmin 阅读(127) |
评论 (0) |
编辑 收藏