在河里抓鱼的熊

抓鱼杂谈

Archive for the ‘技术’ Category

解决一个jUnit运行时错误

without comments

情况概述:

已经经过测试、运行过多遍的一个jUnit测试用例突然不能用了,运行时提示:

java.lang.Exception: Method setupOnce() should be static
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)

代码如下:

@RunWith(Parameterized.class)
public class SqlIndexLogicTest {

	Object[] paramNow;

	private String expected;
	private JSONArray target;

	@Parameters
	public static Collection<object []> params() throws JSONException{
		return Arrays.asList(new Object[][]{
				{"SELECT * FROM (SELECT REQUEST_TIME,RESULT_ID,KEY_CODE,REGION_CODE FROM SD_UKMS_LOG GROUP BY REQUEST_TIME,REGION_CODE,KEY_CODE,RESULT_ID ORDER BY REGION_CODE) WHERE 1=1 and request_time>to_date('2011-09-21', 'yyyy-mm-dd')",
					new JSONArray("[1506, 0, 5, {'request_time':['2011-09-21']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code=931",
					new JSONArray("[1510, 0, 5, {'region_code':['931']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code in ('931','000')",
					new JSONArray("[1510, 0, 5, {'region_code':['931','0000']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code in ('931','0000') and request_time< =to_date('2012-01-01', 'yyyy-mm-dd')",
					new JSONArray("[1510, 0, 5, {'region_code':['931','0000'], 'request_time':['2012-01-01']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG)",
					new JSONArray("[1509, 0, 5]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1",
					new JSONArray("[1509, 0, 5, {'region_code':[], 'cas_id':[], 'request_time':[]}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1",
					new JSONArray("[1509, 0, 5, {'region_code':[''], 'cas_id':[''], 'request_time':['']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code=931",
					new JSONArray("[1509, 0, 5, {'region_code':['931'], 'cas_id':[''], 'request_time':['']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code=931 and cas_id=13519000753",
					new JSONArray("[1509, 0, 5, {'region_code':['931'], 'cas_id':['13519000753'], 'request_time':['']}]")},
				{"SELECT * FROM (SELECT REGION_CODE,CAS_ID,REQUEST_TIME,UKMS_ID FROM SD_UKMS_LOG) WHERE 1=1 and region_code=931 and cas_id=13519000753 and request_time<=to_date('2011-09-21', 'yyyy-mm-dd')",
					new JSONArray("[1509, 0, 5, {'region_code':['931'], 'cas_id':['13519000753'], 'request_time':['2011-09-21']}]")},
		});
	}

	public SqlIndexLogicTest(String expected, JSONArray target){
		this.target = target;
		this.expected = expected;
	}

	@BeforeClass
	public void setUpOnce() throws JSONException{
	}

	@AfterClass
	public static void tearDownOnce(){

	}

	@Before
	public void init() throws JSONException{
		JSONObject conditionObj = target.optJSONObject(3);
		Map<String, String[]> whereParams = new TreeMap<string , String[]>();
		if(conditionObj != null){
			@SuppressWarnings("unchecked")
			Iterator</string><string> it = conditionObj.keys();
			while(it.hasNext()){
				String key = it.next();
				System.out.println("key:"+key);
				JSONArray subCondArr = conditionObj.getJSONArray(key);
				String[] subCondArrStr = new String[subCondArr.length()];
				for(int j=0; j<subcondarr .length(); j++){
					subCondArrStr[j] = subCondArr.getString(j);
				}
				whereParams.put(key, subCondArrStr);
			}
		}
		paramNow = new Object[]{target.get(0), target.get(1), target.get(2), whereParams, target.optJSONArray(4)};
	}

	@After
	public void destroy(){
	}

	@Ignore("pause this method test")
	@Test
	public void queryNormal() throws DAOException, JSONException{
		org.json.JSONObject jo = SqlIndexLogic.getInstance()
				.query((Integer) paramNow[0], (Integer) paramNow[1], (Integer) paramNow[2], (Map<String, String[]>) paramNow[3], (JSONArray) paramNow[4]);
		System.out.println(jo);
	}

	@Test
	public void buildWrapedSQL() throws DAOException, JSONException{
		String baseSQL = SqlIndexLogic.getInstance().getBaseSQL((Integer) paramNow[0]);
		StringBuilder sql = SqlIndexLogic.getInstance()
				.buildWrapedSQL(baseSQL, (Integer) paramNow[0], (Integer) paramNow[1], (Integer) paramNow[2], (Map<string , String[]>) paramNow[3], (JSONArray) paramNow[4]);
		System.out.println(sql);
		assertEquals(expected, sql.toString());
	}

}

解决方法:

将setupOnce()方法改成static修饰即可。

原因:

@BeforeClass和@AfterClass注解的两个方法需要静态化,以符合jUnit调用机制。而那个@BeforeClass注解的setupOnece()方法的static修饰符不知道什么时候不小心删掉了。

Written by qianxiong

January 18th, 2012 at 4:44 pm

Posted in 技术,笔记,经验之谈

Tagged with ,

身份证校验码规则(程序实现)

without comments

概述

18位身份证标准在国家质量技术监督局于1999年7月1日实施的GB11643-1999《公民身份号码》中做了明确规定。

GB11643-1999《公民身份号码》为GB11643-1989《社会保障号码》的修订版,其中指出将原标准名称“社会保障号码”更名为“公民身份号码”,另外GB11643-1999《公民身份号码》从实施之日起代替GB11643-1989。

公民身份号码是特征组合码,由十七位数字本体码和一位校验码组成。排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位校验码。其含义如下:

  1. 地址码:表示编码对象常住户口所在县(市、旗、区)的行政区划代码,按GB/T2260的规定执行。
  2. 出生日期码:表示编码对象出生的年、月、日,按GB/T7408的规定执行,年、月、日分别用4位、2位、2位数字表示,之间不用分隔符。
  3. 顺序码:表示在同一地址码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配给女性。

校验位的计算方式

  1. 对前17位数字本体码加权求和

    公式为:S = Sum(Ai * Wi), i = 0, … , 16
    其中Ai表示第i位置上的身份证号码数字值,Wi表示第i位置上的加权因子,其各位对应的值依次为: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2

  2. 以11对计算结果取模

    Y = mod(S, 11)

  3. 根据模的值得到对应的校验码,对应关系为:

    Y值: 0 1 2 3 4 5 6 7 8 9 10
    校验码: 1 0 X 9 8 7 6 5 4 3 2

  4. javascript编程实现(chrome中测试通过):

    function getCheckCode(code){
    	if(typeof code !== "string")
    		alert("身份证号需要以字符串格式输入,不是数字。请在输入的身份证号两端加上双引号(\")");
    	var W = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2];
    	var checkcode = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
    	var A = new String(code), S=0;
    	for(var i=0; i<A.length; i++){S+=(A[i]*W[i])}
    	console.log('A.length='+A.length+'\nS='+S);
    	var Y = S%11;
    	return checkcode[Y];
    }
    

Written by qianxiong

December 17th, 2011 at 9:24 pm

使用动态规划法实现斐波那契数列计算

without comments

动态规划法概述

动态规划是一种在数学和计算机科学中使用的,用于求解包含重叠子问题的最优化问题的方法。其基本思想是,将原问题分解为相似的子问题,在求解的过程中通过子问题的解求出原问题的解。动态规划的思想是多种算法的基础,被广泛应用于计算机科学和工程领域。比较著名的应用实例有:求解最短路径问题,背包问题,项目管理,网络流优化等。

动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。

斐波那契数列计算

一般方法

javascript实现:

function fib(n){
	if(n == 1 || n == 0)
		return 1;
	return fib(n-1)+fib(n-2)
}

当计算fib(5)时
fib(5)
fib(4) + fib(3)
(fib(3) + fib(2)) + (fib(2) + fib(1))
((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
(((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
这种方法的问题在于对相同的子问题进行了多次重复计算,其计算量呈指数上升。如果能将计算过的结果存储起来,就避免了大规模的重复计算。

使用动态规划

原理:

将计算过的值存储起来,不用每次都递归计算得出。这样将极大的提高计算效率

javascript实现:

var fibmap = {0:1, 1:1};
function fib2(n){
	if(typeof fibmap[n] === "undefined"){
		fibmap[n] = fib2(n-2) + fib2(n-1);
	return fibmap[n];
}

Written by qianxiong

December 17th, 2011 at 5:04 pm

转换整个项目编码

without comments

处于种种原因,我们需要将项目由原来的GBK编码改成UTF-8编码。如果针对单个文件,我们可能的做法是:将文件按原来的方式打开,copy所有内容,然后将文件编码指定为UTF-8,重新打开,CTRL+A, 接着CTRL+V。但项目文件好几百个,甚至上千个。这种方法显然行不通。

可喜的是,Linux有着非常强大的功能,只要操作得当,必能将人从繁重的工作中解脱出来。高效的做法是:

find . -regex ".+\.\(tpl\|js\|jsp\|css\|java\|xml\)$" | xargs enca | grep 'GB2312' | awk -F: '{print $1}' | xargs enconv

以上命令在我的运行过程中情况如下,转换435个文件,用时不超过5秒。

以上命令有两个前提:

  1. 当前环境的编码是UTF-8
  2. 已经安装了enca包

如果上面前提不成立,则需要按如下处理:

  1. 针对1不成立,修改命令如下:
    find . -regex ".+\.\(tpl\|js\|jsp\|css\|java\|xml\)$" | xargs enca | grep 'GB2312' | awk -F: '{print $1}' | xargs enconv -x UTF-8
  2. 针对2不成立,则安装enca包。如ubuntu:
    sudo apt-get install enca
还是那句老话:有玉砸玉,有砖拍砖!

Written by qianxiong

November 30th, 2011 at 7:17 pm

Posted in 技术,笔记,经验之谈

Tagged with , ,

【转】逻辑卷管理

without comments

原文出处:http://www.ibm.com/developerworks/cn/linux/l-lvm2/

卷管理在 -ix 环境(UNIX®、AIX 等等)中并不是新特性。逻辑卷管理(logical volume management,LVM)在 Linux® 内核 2.4v1 和 2.6.9v2 中就已经出现了。本文讨论 LVM2 最有用的特性(LVM2 是一个相当新的用户空间工具集,它提供逻辑卷管理功能),并提供几种简化系统管理任务的方法。

逻辑卷管理(LVM)指系统将物理卷管理抽象到更高的层次,常常会形成更简单的管理模式。通过使用 LVM,所有物理磁盘和分区,无论它们的大小和分布方式如何,都被抽象为单一存储(single storage)源。例如,在图 1 所示的物理到逻辑映射布局中,最大的磁盘是 80GB 的,那么用户如何创建更大(比如 150GB)的文件系统呢?
图 1. 物理到逻辑的映射
物理到逻辑的映射

LVM 可以将分区和磁盘聚合成一个虚拟磁盘(virtual disk),从而用小的存储空间组成一个统一的大空间。这个虚拟磁盘在 LVM 术语中称为卷组(volume group)

建立比最大的磁盘还大的文件系统并不是这种高级存储管理方法的惟一用途。还可以使用 LVM 完成以下任务:

  • 在磁盘池中添加磁盘和分区,对现有的文件系统进行在线扩展
  • 用一个 160GB 磁盘替换两个 80GB 磁盘,而不需要让系统离线,也不需要在磁盘之间手工转移数据
  • 当存储空间超过所需的空间量时,从池中去除磁盘,从而缩小文件系统
  • 使用快照(snapshot)执行一致的备份(本文后面会进一步讨论)

LVM2 是一个新的用户空间工具集,它为 Linux 提供逻辑卷管理功能。它完全向后兼容原来的 LVM 工具集。在本文中,将介绍 LVM2 最有用的特性以及几种简化系统管理任务的方法。(随便说一句,如果您正在寻找关于 LVM 的基本指南,那么可以看看 参考资料 中列出的 LVM HowTo。)

我们来看看 LVM 的结构是什么样子的。

LVM 的结构

LVM 被组织为三种元素:

  • 卷(Volume):物理 和逻辑卷 和卷组
  • 区段(Extent):物理 和逻辑区段
  • 设备映射器(Device mapper):Linux 内核模块

Linux LVM 组织为物理卷(PV)、卷组(VG)和逻辑卷(LV)。物理卷 是物理磁盘或物理磁盘分区(比如 /dev/hda 或 /dev/hdb1)。卷组 是物理卷的集合。卷组 可以在逻辑上划分成多个逻辑卷。

图 2 显示一个三个磁盘构成的布局。
图 2. 物理到逻辑卷的映射
物理到逻辑卷的映射

物理磁盘 0 上的所有四个分区(/dev/hda[1-4])以及完整的物理磁盘 1(/dev/hdb)和物理磁盘 2(/dev/hdd)作为物理卷添加到卷组 VG0 中。

卷组是实现 n-to-m 映射的关键(也就是,将 n 个 PV 看作 m 个 LV)。在将 PV 分配给卷组之后, 就可以创建任意大小的逻辑卷(只要不超过 VG 的大小)。在图 2 的示例中,创建了一个称为 LV0 的卷组,并给其他 LV 留下了一些空间(这些空间也可以用来应付 LV0 以后的增长)。

LVM 中的逻辑卷就相当于物理磁盘分区;在实际使用中,它们就是 物理磁盘分区。

在创建 LV 之后,可以使用任何文件系统对它进行格式化并将它挂载在某个挂载点上,然后就可以开始使用它了。图 3 显示一个经过格式化的逻辑卷 LV0 被挂载在 /var。
图 3. 物理卷到文件系统的映射
物理卷到文件系统的映射

区段

为了实现 n-to-m 物理到逻辑卷映射,PV 和 VG 的基本块必须具有相同的大小;这些基本块称为物理区段(PE)逻辑区段(LE)。尽管 n 个物理卷映射到 m 个逻辑卷,但是 PE 和 LE 总是一对一映射的。

在使用 LVM2 时,对于每个 PV/LV 的最大区段数量并没有限制。默认的区段大小是 4MB,对于大多数配置不需要修改这个设置,因为区段的大小并不影响 I/O 性能。但是,区段数量太多会降低 LVM 工具的效率,所以可以使用比较大的区段,从而降低区段数量。但是注意,在一个 VG 中不能混用不同的区段大小,而且用 LVM 修改区段大小是一种不安全的操作,会破坏数据。所以建议在初始设置时选择一个区段大小,以后不再修改。

不同的区段大小意味着不同的 VG 粒度。例如,如果选择的区段大小是 4GB,那么只能以 4GB 的整数倍缩小或扩展 LV。

图 4 用 PE 和 LE 显示与前一个示例相同的布局(VG0 中的空闲空间也由空闲 LE 组成,尽管图中没有显示它们)。
图 4. 物理到逻辑区段的映射
物理到逻辑区段的映射

另外,请注意图 4 中的区段分配策略。LVM2 并非总是连续分配 PE;细节参见关于 lvm 的 Linux 手册页(见 参考资料 中的链接)。系统管理员可以设置不同的分配策略,但是一般不需要这么做,因为默认策略(名为一般分配策略(normal allocation policy))使用符合常规的规则,比如不把并行的条带放在同一物理卷上。

如果决定创建第二个 LV(LV1),那么最终的 PE 布局可能像图 5 这样。
图 5. 物理到逻辑区段的映射
物理到逻辑区段的映射

设备映射器

设备映射器(也称为dm_mod)是一个 Linux 内核模块(也可以是内置的),最早出现在 2.6.9 内核中。它的作用是对设备进行映射 —— LVM2 必须使用这个模块。

在大多数主流发行版中,设备映射器会被默认安装,常常会在引导时或者在安装或启用 LVM2/EVMS 包时自动装载(EVMS 是一种替代 LVM 的工具,更多信息见 参考资料)。如果没有启用这个模块,那么对dm_mod执行modprobe命令,在发行版的文档中查找在引导时启用它的方法:modprobe dm_mod

在创建 VG 和 LV 时, 可以给它们起一个有意义的名称(而不是像前面的示例那样使用 VG0、LV0 和 LV1 等名称)。设备映射器的作用就是将这些名称正确地映射到物理设备。对于前面的示例,设备映射器会在 /dev 文件系统中创建下面的设备节点:

  • /dev/mapper/VG0-LV0
    • /dev/VG0/LV0 是以上节点的链接
  • /dev/mapper/VG0-LV1
    • /dev/VG0/LV1 是以上节点的链接

(注意名称的格式标准:/dev/{vg_name}/{lv_name} -> /dev/mapper/{vg_name}{lv_name})。

与物理磁盘相反,无法直接访问卷组(这意味着没有 /dev/mapper/VG0 这样的文件,也不能执行dd if=/dev/VG0 of=dev/VG1)。常常使用lvm(8)命令访问卷组。

常见任务

在使用 LVM2 时常常执行的任务包括系统检验(是否安装了 LVM2)以及创建、扩展和管理卷。

系统准备好运行 LVM2 了吗?

检查您的 Linux 发行版是否安装了 LVM2 软件包。如果还没有,就安装它(最好安装发行版附带的软件包)。

设备映射器模块必须在系统启动时装载。用lsmod | grep dm_mod命令检查当前是否装载了这个模块。如果没有装载,那么可能需要安装并配置更多的软件包(文档会说明如何启用 LVM2)。

如果只是想测试一下(或者挽救某个系统),那么可以使用以下命令启动 LVM2:
清单 1. 启动 LVM2 的基本命令

#this should load the Device-mapper module
modprobe dm_mod
#this should find all the PVs in your physical disks
pvscan
#this should activete all the Volume Groups
vgchange -ay

如果打算将根文件系统放在一个 LVM LV 中,那么还要注意 initial-ramdisk 映像。同样,发行版常常会负责处理这个问题 —— 在安装 LVM2 包时,它们常常会重新构建或更新 initrd 映像,在其中添加适当的内核模块和启动脚本。但是,可能需要查看发行版的文档,确保系统支持 LVM2 根文件系统。

注意,通常只有当探测到根文件系统在一个 VG 中时,initial-ramdisk 映像才会启用 LVM。这种探测常常是通过分析root=内核参数执行的。不同的发行版以不同的方式判断根文件系统是否在卷组中。细节参见发行版的文档。如果不确定的话,就需要检查 initrd 或 initramdisk 的配置。

 创建新的卷

使用您喜欢的分区工具(比如 fdisk、parted 或 gparted),创建一个供 LVM 使用的新分区。尽管 LVM 支持在整个磁盘上使用 LVM,但是 建议这么做:其他操作系统可能认为这个磁盘没有初始化,可能会破坏它!更好的方法是创建一个覆盖整个磁盘的分区。

大多数分区工具常常默认使用分区 ID 0×83(或 Linux)来创建新分区。可以使用这个默认 ID,但是为了便于组织,最好将它改为 0x8e(或 Linux LVM)。

在创建分区之后,应该会在分区表中看到一个(或多个)Linux LVM 分区:

root@klausk:/tmp/a# fdisk -l
Disk /dev/hda: 80.0 GB, 80026361856 bytes
255 heads, 63 sectors/track, 9729 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
   Device Boot      Start         End      Blocks   Id  System
/dev/hda1   *           1        1623    13036716    7  HPFS/NTFS
/dev/hda2            1624        2103     3855600   8e  Linux LVM
/dev/hda3            2104        2740     5116702+  83  Linux
/dev/hda4            3000        9729    54058725    5  Extended
/dev/hda5            9569        9729     1293232+  82  Linux swap / Solaris
/dev/hda6            3000        4274    10241374+  83  Linux
/dev/hda7            4275        5549    10241406   83  Linux
/dev/hda8            5550        6824    10241406   83  Linux
/dev/hda9            6825        8099    10241406   83  Linux
/dev/hda10           8100        9568    11799711   8e  Linux LVM
Partition table entries are not in disk order
root@klausk:/tmp/a#

现在用pvcreate对每个分区进行初始化:
清单 2. 分区初始化

root@klausk:/tmp/a# pvcreate /dev/hda2 /dev/hda10
  Physical volume "/dev/hda2" successfully created
  Physical volume "/dev/hda10" successfully created
root@klausk:/tmp/a#

在一个步骤中同时创建 PV 和 VG:vgcreate
清单 3. 创建 PV 和 VG

root@klausk:~# vgcreate test-volume /dev/hda2 /dev/hda10
  Volume group "test-volume" successfully created
root@klausk:~#

上面的命令创建一个称为 test-volume 的逻辑卷,它使用 /dev/hda2 和 /dev/hda10 作为最初的 PV。

在创建 VG test-volume 之后,使用vgdisplay命令查看刚创建的 VG 的基本信息:
清单 4. 查看刚创建的 VG 的基本信息

root@klausk:/dev# vgdisplay -v test-volume
    Using volume group(s) on command line
    Finding volume group "test-volume"
  --- Volume group ---
  VG Name               test-volume
  System ID
  Format                lvm2
  Metadata Areas        2
  Metadata Sequence No  1
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                0
  Open LV               0
  Max PV                0
  Cur PV                2
  Act PV                2
  VG Size               14.93 GB
  PE Size               4.00 MB
  Total PE              3821
  Alloc PE / Size       0 / 0
  Free  PE / Size       3821 / 14.93 GB
  VG UUID               lk8oco-ndQA-yIMZ-ZWhu-LtYX-T2D7-7sGKaV

  --- Physical volumes ---
  PV Name               /dev/hda2
  PV UUID               8LTWlw-p1OJ-dF6w-ZfMI-PCuo-8CiU-CT4Oc6
  PV Status             allocatable
  Total PE / Free PE    941 / 941

  PV Name               /dev/hda10
  PV UUID               vC9Lwb-wvgU-UZnF-0YcE-KMBb-rCmU-x1G3hw
  PV Status             allocatable
  Total PE / Free PE    2880 / 2880

root@klausk:/dev#

在清单 4 中,可以看到有两个 PV 被分配给这个 VG,总大小为 14.93GB,有 3,821 个 4MB 的 PE,这些 PE 都是空闲的!

既然卷组已经准备好了,就可以像使用磁盘一样用它创建分区(LV)、删除分区和重新设置分区大小 —— 注意,卷组是一个抽象实体,只有 LVM 工具集能够看到它们。使用lvcreate创建一个新的逻辑卷:
清单 5. 创建新的逻辑卷(分区)

root@klausk:/# lvcreate -L 5G -n data test-volume
  Logical volume "data" created
root@klausk:/#

清单 5 创建一个名为 data 的 5GB LV。创建这个 LV 之后,可以检查它的设备节点:
清单 6. 检查 LV 的设备节点

root@klausk:/# ls -l /dev/mapper/test--volume-data
brw-rw---- 1 root disk 253, 4 2006-11-28 17:48 /dev/mapper/test--volume-data
root@klausk:/# ls -l /dev/test-volume/data
lrwxrwxrwx 1 root root 29 2006-11-28 17:48 /dev/test-volume/data ->
/dev/mapper/test--volume-data
root@klausk:/#

还可以用lvdisplay命令查看 LV 的属性:
清单 7. 查看 LV 的属性

root@klausk:~# lvdisplay /dev/test-volume/data
  --- Logical volume ---
  LV Name                /dev/test-volume/data
  VG Name                test-volume
  LV UUID                FZK4le-RzHx-VfLz-tLjK-0xXH-mOML-lfucOH
  LV Write Access        read/write
  LV Status              available
  # open                 0
  LV Size                5.00 GB
  Current LE             1280
  Segments               1
  Allocation             inherit
  Read ahead sectors     0
  Block device           253:4

root@klausk:~#

在这里可以看到,在实际使用时 LV 的名称/路径是 /dev/{VG_name}/{LV_name},比如 /dev/test-volume/data。除了用作 /dev/{VG_name}/{LV_name} 链接的目标之外,不应该在其他地方使用 /dev/mapper/{VG_name}-{LV_name} 文件。大多数 LVM 命令要求以 /dev/{vg-name}/{lv-name} 格式指定操作的目标。

建立逻辑卷之后,可以使用任何文件系统对它进行格式化,然后将它挂载在某个挂载点上:
清单 8. 挂载 LV

root@klausk:~# mkfs.reiserfs /dev/test-volume/data
root@klausk:~# mkdir /data
root@klausk:~# mount -t reiserfs /dev/test-volume/data /data/
root@klausk:~# df -h /data
Filesystem            Size  Used Avail Use% Mounted on
/dev/mapper/test--volume-data
                      5.0G   33M  5.0G   1% /data
root@klausk:~#

还可以编辑fstab(5)文件,从而在引导时自动挂载这个文件系统:
清单 9. 自动挂载 LV

#mount Logical Volume 'data' under /data
/dev/test-volume/data   /data   reiserfs        defaults        0 2

在实际使用中,逻辑卷的表现就像一个块设备,比如可以将它用作数据库的原始分区。实际上,如果希望对数据库执行一致的备份,那么使用 LVM 快照是标准的最佳实践。

扩展卷

扩展卷是非常容易的。如果卷组中有足够的空闲空间,那么只需使用lvextend来扩展卷,不需要卸载它。然后,还要扩展逻辑卷中的文件系统(请记住,它们是两回事儿)。根据所用文件系统的不同,也可以进行在线扩展(即在挂载状态下进行扩展)。

如果 VG 中没有足够的空间,那么首先需要添加更多的物理磁盘。步骤如下:

  • 使用一个物理磁盘创建一个分区。建议将分区类型改为 0x8e(Linux LVM),这样便于识别 LVM 分区/磁盘。使用pvcreate对物理磁盘进行初始化:pvcreate /dev/hda3
  • 然后,使用vgextend将它添加到现有的 VG 中:vgextend test-volume /dev/hda2

还可以同时创建或添加多个物理磁盘:

pvcreate /dev/hda2 /dev/hda3 /dev/hda5
vgextend test-volume /dev/hda2 /dev/hda3 /dev/hda5

添加了 PV 之后,就有了足以扩展逻辑卷的空间,就可以使用lvextend扩展逻辑卷了:lvextend -L 8G /dev/test-volume/data。这个命令将 /dev/test-volume/data LV 的大小扩展到 8GB。

lvextend有一些有用的参数:

  • 如果希望让 LV 增加 5GB,那么可以使用-L +5G
  • 可以指定扩展部分的位置(也就是,用哪些 PV 提供新的空间);只需将希望使用的 PV 附加在命令后面。
  • 还可以以 PE 为单位指定绝对/相对扩展大小。

细节参见lvextend(8)

在扩展 LV 之后,不要忘记扩展文件系统(这样才能实际使用增加的空间)。根据文件系统类型,这个操作可以在文件系统挂载状态下在线执行。

清单 10 是一个用resize_reiserfs重新设置 LV 大小的示例(随便说一句,可以在挂载的文件系统上使用这个命令):resize_reiserfs /dev/test-volume/data

管理卷

为了管理卷,需要知道如何减小 LV 和删除 PV。

减小逻辑卷
可以按照扩展 LV 的方式使用lvreduce命令减小 LV。从 LVM 的角度来说,这个操作可以在卷在线的情况下执行;但是,大多数文件系统不支持缩小在线文件系统。清单 10 给出这个过程的示例:
清单 10. 减小 LV

#unmount LV
umount /path/to/mounted-volume
#shrink filesystem to 4G
resize_reiserfs -s 4G /dev/test-volume/data
#reduce LV
lvreduce -L 4G /dev/vg00/test

请注意大小和单位:文件系统不应该比 LV 大!

删除物理卷
假设出现了以下情况:一个卷组包含两个 80GB 的磁盘,希望将它们替换为 160GB 的磁盘。在使用 LVM 时,可以按照添加 PV 的方式从 VG 中删除 PV(即在在线情况下执行删除)。但是注意,不能删除 LV 中正在使用的 PV。对于这些情况,可以使用pvmove,它可以释放在线的 PV,这样就可以轻松地替换它们。在热交换环境中,甚至可以交换所有磁盘,而根本不需要停机!

pvmove的惟一要求是,VG 中连续空闲区段的数量必须等于要从 PV 中删除的区段数量。没有直接判断连续空闲 PE 的最大数量的简便方法,但是可以使用pvdisplay -m显示 PV 分配图:
清单 11. 显示 PV 分配图

#shows the allocation map
pvdisplay -m
  --- Physical volume ---
  PV Name               /dev/hda6
  VG Name               test-volume
  PV Size               4.91 GB / not usable 1.34 MB
  Allocatable           yes (but full)
  PE Size (KByte)       4096
  Total PE              1200
  Free PE               0
  Allocated PE          1200
  PV UUID               BA99ay-tOcn-Atmd-LTCZ-2KQr-b4Z0-CJ0FjO
  --- Physical Segments ---
  Physical extent 0 to 2367:
    Logical volume      /dev/test-volume/data
    Logical extents     5692 to 8059
  Physical extent 2368 to 2499:
    Logical volume      /dev/test-volume/data
    Logical extents     5560 to 5691
  --- Physical volume ---
  PV Name               /dev/hda7
  VG Name               test-volume
  PV Size               9.77 GB / not usable 1.37 MB
  Allocatable           yes
  PE Size (KByte)       4096
  Total PE              2500
  Free PE               1220
  Allocated PE          1280
  PV UUID               Es9jwb-IjiL-jtd5-TgBx-XSxK-Xshj-Wxnjni
  --- Physical Segments ---
  Physical extent 0 to 1279:
    Logical volume      /dev/test-volume/LV0
    Logical extents     0 to 1279
  Physical extent 1280 to 2499:
    FREE

清单 11 显示有 2,499-1,280 = 1,219 个连续空闲区段,这表示最多能够将 1,219 个区段从另一个 PV 转移到 /dev/hda7。

如果希望释放一个 PV 以便进行替换,那么最好禁止它的分配,这样就可以在从卷组中删除它之前确保它一直是空闲的。在转移数据之前,执行以下命令:
清单 12. 在释放之前禁止 PV 的分配

#Disable /dev/hda6 allocation
pvchange -xn /dev/hda6

释放之后,PV /dev/hda6 的大小为 1,200 个区段,没有空闲区段了。使用以下命令将数据转移出这个 PV:
清单 13. 从释放的 PV 移出数据

#Move allocated extents out of /dev/hda6
pvmove -i 10 /dev/hda6

清单 13 中的-i 10参数指示pvmove每 10 秒报告一次状态。根据要转移的数据量,这个操作可能要花费几分钟(甚至几小时)。还可以使用-b参数将这个操作转到后台执行。在后台执行的情况下,状态报告会发送到系统日志。

如果没有足以进行pvmove操作的连续空闲区段,那么可以在 VG 中添加 一个或多个磁盘/分区,从而形成pvmove所需的连续空间。

其他有用的 LVM 操作
关于下面这些 LVM 操作的细节,请查阅手册页:

  • pvresize:如果底层分区也已经扩展了,那么可以用这个操作扩展 PV;如果分配图允许的话,它也可以缩小 PV。
  • pvremove:销毁 PV(清空它的元数据)。只有在用vgreduce从 VG 中删除 PV 之后,才能使用这个操作。
  • vgreduce:从卷组中删除未分配的 PV,这会减小 VG。
  • vgmerge:将两个 VG 合并成一个。目标 VG 可以是在线的!
  • vgsplit:分割一个卷组。
  • vgchange:修改一个 VG 的属性和权限。
  • lvchange:修改一个 LV 的属性和权限。
  • lvconvert:在线性卷和镜像或快照之间进行转换。

用快照执行备份

如果在备份过程期间数据没有发生变化,那么就能够获得一致的备份。如果不在备份期间停止系统,就很难保证数据没有变化。

Linux LVM 实现了一种称为快照(Snapshot)的特性,它的作用就像是 “拍摄” 逻辑卷在某一时刻的照片。通过使用快照, 可以获得同一 LV 的两个拷贝 —— 一个可以用于备份,另一个继续用于日常操作。

快照有两大优点:

  1. 快照的创建非常快,不需要停止生产环境。
  2. 建立两个拷贝,但是它们的大小并不一样。快照使用的空间仅仅是存储两个 LV 之间的差异所需的空间。

快照由一个例外列表(exception list)来实现,每当 LV 之间出现差异时就会更新这个列表(正式的说法是 CoW,Copy-on-Write)。

创建新的快照

创建新的快照 LV 也是使用lvcreate命令,但是要指定-s参数和原来的 LV。在这种情况下,-L size指定例外列表的大小,这影响快照支持的最大差异量,如果差异超过这个量,就无法保持一致性。
清单 14. 建立快照

#create a Snapshot LV called 'snap' from origin LV 'test'
lvcreate -s -L 2G -n snap/dev/test-volume/test

可以使用lvdisplay查询特殊信息,比如 CoW 的大小和使用情况:
清单 15. CoW 的大小和使用情况

lvdisplay /dev/vg00/snap
  --- Logical volume ---
  LV Name                /dev/vg00/snap
  VG Name                vg00
  LV UUID                QHVJYh-PR3s-A4SG-s4Aa-MyWN-Ra7a-HL47KL
  LV Write Access        read/write
  LV snapshot status     active destination for /dev/vg00/test
  LV Status              available
  # open                 0
  LV Size                4.00 GB
  Current LE             1024
  COW-table size         2.00 GB
  COW-table LE           512
  Allocated to snapshot  54.16%
  Snapshot chunk size    8.00 KB
  Segments               1
  Allocation             inherit
  Read ahead sectors     0
  Block device           254:5

清单 15 表明这个 CoW 的大小为 2GB,其中的 54.16 % 已经使用了。

对于所有日常操作,快照看起来就是 原 LV 的一个拷贝。如果已经建立了文件系统的话,可以用以下命令挂载它:

#mount snapshot volume
mount -o ro /dev/test-volume/test /mnt/snap

在这个命令中,ro标志表示将它挂载为只读的。可以在lvcreate命令后面加上-p r,这样就在 LVM 级将它设置为只读的。

挂载文件系统之后,就可以用tarrsync或其他备份工具执行备份。如果 LV 不包含文件系统,或者需要原始备份,那么也可以在这个设备节点上直接使用dd

复制过程完成之后,就不需要快照了,这时只需用lvremove卸载并销毁它:

#remove snapshot
lvremove /dev/test-volume/snap

如果数据库建立在 LV 上,并且需要一个一致的备份,那么一定要刷新表并在获得读取锁(read-lock)的情况下建立快照卷(见下面的伪代码):

SQL> flush tables read lock
{create Snapshot}
SQL> release read lock
{start copy process from the snapshot LV}

 

备份脚本示例

清单 16 中的脚本直接取自我的笔记本电脑,我在这个脚本中使用rsync向一台远程服务器执行每日备份。这个脚本并不适合企业环境;在企业环境中,带历史记录的增量备份更合适,但概念是相同的。
清单 16. 简单的备份脚本示例

#!/bin/sh
# we need the dm-snapshot module
modprobe dm-snapshot
if [ -e /dev/vg00/home-snap ]
then
  # remove left-overs, if any
  umount -f /mnt/home-snap && true
  lvremove -f /dev/vg00/home-snap
fi
# create snapshot, 1GB CoW space
# that should be sufficient for accommodating changes during copy
lvcreate -vs -p r -n home-snap -L 1G /dev/vg00/home
mkdir -p /mnt/home-snap
# mount recently-created snapshot as read-only
mount -o ro /dev/vg00/home-snap /mnt/home-snap
# magical rsync command__rsync -avhzPCi --delete -e "ssh -i /home/klausk/.ssh/id_rsa" \
      --filter '- .Trash/' --filter '- *~' \
      --filter '- .local/share/Trash/' \
      --filter '- *.mp3' --filter '- *Cache*' --filter '- *cache*' \
      /mnt/home-snap/klausk klausk2@pokgsa.ibm.comThis e-mail address is being protected
      from spam bots, you need JavaScript enabled to view it :bkp/
# unmount and scrap snapshot LV
umount /mnt/home-snap
lvremove -f /dev/vg00/home-snap

在某些特殊情况下,无法估计备份周期或者复制过程很长,那么脚本可以用lvdisplay查询 Snapshot CoW 的使用情况并根据需要扩展这个 LV。在极端情况下, 可以让快照与原 LV 同样大 —— 这样就不需要执行查询,因为变化量不会比整个卷更大!

其他 LVM2 系统管理技巧

最后, 我要介绍一些可以用 LVM2 执行的系统管理任务,包括按需虚拟化、用镜像提高容错能力以及透明地对块设备执行加密。

快照和虚拟化

在使用 LVM2 时,快照可以不是只读的。这意味着,在创建快照之后, 可以像常规块设备一样挂载和读写快照。

因为流行的虚拟化系统(比如 Xen、VMWare、Qemu 和 KVM)可以将块设备用作 guest 映像,所以可以创建这些映像的完整拷贝,并根据需要使用它们,它们就像是内存占用量很低的虚拟机。这样做的好处是部署迅速(创建快照的时间常常不超过几秒)和节省空间(guest 共享原映像的大多数数据)。

设置的步骤如下:

  1. 为原映像创建一个逻辑卷。
  2. 使用这个 LV 作为磁盘映像安装 guest 虚拟机。
  3. 暂停这个虚拟机。内存映像可以是一个常规文件,所有其他快照都放在里面。
  4. 为原 LV 创建一个可读写的快照。
  5. 使用快照卷作为磁盘映像生成一个新的虚拟机。如果需要的话,要修改网络/控制台设置。
  6. 登录已经创建的虚拟机,修改网络设置/主机名。

完成这些步骤之后, 就可以让用户访问刚创建的虚拟机了。如果需要另一个虚拟机,那么只需重复步骤 4 到 6(所以不需要重新安装虚拟机)。还可以用一个脚本自动执行这些步骤。

在使用完虚拟机之后, 可以停止虚拟机并销毁快照。

更好的容错能力

最近的 LVM2 开发成果为逻辑卷提供了高可用性。逻辑卷可以有两个或更多的镜像,镜像可以放在不同的物理卷(或不同的设备)上。当在设备上发现 I/O 错误时,可以使用dmeventd让一个 PV 离线,而不会影响服务。更多信息请参考lvcreate(8)lvconvert(8)lvchange(8)手册页。

如果硬件能够支持的话,可以用dm_multipath通过不同的通道访问同一设备,这样的话在一个通道发生故障时,可以转移到另一个通道。更多细节请参考dm_multipathmultipathd的文档。

透明的设备加密

可以用dm_crypt对块设备或逻辑卷执行透明的加密。更多信息请参考dm_crypt的文档和cryptsetup(8)手册页。

参考资料

学习

关于作者

Klaus Heinrich Kiwi 于 2004 年从 Unicamp 毕业,获得计算机工程学位,之后一直从事 UNIX 环境中的系统开发和网络管理部署。从 2006 年开始,他在 IBM Linux Technology Center 的 Security Development 团队中担任软件工程师。

Written by qianxiong

August 25th, 2011 at 3:56 pm

Posted in 技术,转载

Tagged with ,

解锁Oracle表

without comments

两句话:

  1. 查询被锁表的相关信息
    select
      se.username, obj.object_name, obj.object_id, lk.session_id, se.serial#
    from
      user_objects obj,
      v$locked_object lk,
      v$session se
    where
      lk.object_id=obj.object_id
      and
      lk.session_id=se.sid;
  2. 解锁
    alter system kill session '{sid},{serial#}';

Written by qianxiong

July 29th, 2011 at 5:51 pm

在旧有项目上开发新功能

without comments

背景:

公司有一套项目的统一版本,一个由6个子项目组成的ejb项目,子系统分别是:commons, dao, bussiniselogic, ejbFacade, delegate, webapp。如果更改了一个jsp页面还好说,把jsp上传到服务器即可;但如果是更改了Action,那就麻烦了:重新打包、发布、重启服务器。项目大了打包发布就很耗时,如果大家很有时间,还打算利用这段时间喝杯咖啡,聊两句,那就无所谓了。如果很着急,或者急于看到自己的更改结果。那最好另想办法让你的程序编译-打包-发布过程快一点!

实践:

下面的方法经是我在经过实践证明可行的:

  1. 新建一个动态web项目。

    命名为webappNew。在build path(如src/main/java/)下对应建立相应项目的名称(commons/, dao/, bussiniselogic/, webapp/)。更改build path,将以上新建的几个子目录设置为build path。别忘了把该有的项目引用和依赖加入进来!

  2. 只在这个新项目下工作。

    开发过程中,将本该放在子项目中的新代码放在这些对应目录之下,如dao下要新增一个类com.maywide.oss.web.util.mkuser.java, 此代码本该新增在项目dao下,但是现在放在此web项目的dao/目录下。这样的目的是不再对dao项目重新编译打包,发布的时候只对此新项目编译、打包。这样能节约很多时间。

  3. 重构/集成

    项目开发完毕,需要对整个项目重构。有了前面新建的对应项目名称的目录,重构将非常简单。只需要将各个目录下的代码copy到对应项目中去就行了!然后删除在第1步中新建的web项目。

问题:

  1. 原web项目不参与编译,新页面怎么运行?

    原有的web项目中带有登录功能、js库、css库、框架设计、BaseAction、BaseAF等等基础内容。如果新建的项目中是完全干净的,所有代码都是新开发的,那没有了这些支撑新web页面/功能正常运行的基础设施,新的web项目是无法正常运行甚至不能运行的。所以,这里有了一个方案:

    1. 新建一个J2EE Utility Project,命名为webappU。将原web项目中的build path复制到webappU,并设置相同的build path,包括resource。
    2. 将webappU包含到webappNew的Java EE module Dependencies中。这样新开发的Action/AF/Bean就可以引用到原有的基础框架。
    3. 将css/、common/、images/、inc/、js/、WEB-INF/、index.jsp等顶层文件拷贝到webappNew中。其它功能按需添加。
  2. 如何进行团队协作?

    以前我们直接使用svn,但是现在webappNew是要被删除的,如何协作呢?Git是一个不错的选择。尤其对于连接服务器不方便但又需要团队协作的情况下!

 

Insert mode

Written by qianxiong

July 2nd, 2011 at 4:48 pm

使用jboss的minimal配置,加快服务器启动

without comments

jboss如果使用default配置,仅部署了一个只有index.jsp的war项目启动都要耗费17s,等待过程还是比较漫长的,加快服务器启动的方法就是采用minimal配置。

不过默认的minimal配置总是无法启动,这里摘抄一位外国友人的方法(原文)并翻译如下:

Step 1: Copy “Tomcat” Service to the Custom Configuration

cp -R server/default/deploy/jbossweb-tomcat50.sar server/custom/deploy/

Step 2: Modify Tomcat’s Configuration

找到jbossweb-tomcat50.sar/META-INF下的jboss-service.xml文件,将下面两行注释掉:
<depends optional-attribute-name="SecurityManagerService"
proxy-type="attribute">jboss.security:service=JaasSecurityManager
</depends>
<depends>jboss:service=TransactionManager</depends>

Step 3: Copy over some needed libraries

server/default/lib copy下列jar包到 server/custom/lib里:

javax.servlet.jar javax.servlet.jsp.jar jbosssx.jar jboss-j2ee.jar jboss.jar commons-logging.jar

Step 4: Try it out!

以上是以custom配置为例,其实对于minimal配置而言更广泛,同样实用。采用以上配置后,启动时间仅需3s!

Written by qianxiong

May 6th, 2011 at 1:08 am

Posted in 技术,笔记

Tagged with , ,

Javascript的对象与数组

without comments

Object:

  1. 当Object被当作函数而不是构造器被调用时,它将扮演类型转换器的角色。
    typeof 522  //"number"
    typeof Object(522) //522
    var a = Object(522);
    typeof a  //"object"
    

     

    1. Object ( [ value ] ):
      如果value为null,undefined或不提供,则创建并返回一个新的Object对象。这跟使用Object构造方法一样。
  2. Object 构造方法
  3. Object构造器的属性
    除了包含一个内部属性length(此值总是1)外,还包含: 

    1. Object.prototype
      包含三个属性:{DontEnum, DontDelete, ReadOnly}待补充
  4. Object的Prototype对象的属性
    1. Object.prototype.constructor:内建object构造器
    2. Object.prototype.toString ( ):待补充
    3. Object.prototype.toLocaleString ( ):待补充
    4. Object.prototype.valueOf ( )
    5. Object.prototype.hasOwnProperty (V):检查是否包含V属性。
      var obj = {name:"zhanglin", age:25};
      var p = obj.hasOwnProperty("name"); //p = true
      var q = obj.hasOwnProperty("sex"); //q = false
      
    6. Object.prototype.isPrototypeOf (V):待补充
    7. Object.prototype.propertyIsEnumerable (V):待补充

Array:

 

null:

undefined:

typeof:

 

Written by qianxiong

March 31st, 2011 at 1:34 am

Posted in 技术

Tagged with ,

命令行下批处理图片

without comments

每次拍完照片之后,因为照片太大无法直接上传到空间里面,需要将照片缩小。这时总是因为工作量太大而无法让人望而生畏。如果使用windows,而且手头又有photoshop,那很好。因为photoshop里面有个批处理功能叫Action,但是没有在windows下,更没有photoshop,有没有简便的方法可以做这种简单重复的事情呢?
答案是肯定的。那就是命令行的”imagemagick”!imagemagick是一组命令,包括convert, identify, mogrify, composite, montage, compare, stream, display, animate, import, conjure.
使用convert命令对原图做缩放处理,并将缩放后的图片放到resized/目录中:

find . -maxdepth 1 -name '*.JPG' -exec convert {} -resize '1024x1024' resized/{} \;

某些网站的图片上传功能要求后缀名小写,这时需要做一次转换。使用如下命令:

for f in *; do mv $f `echo $f | tr '[:upper:]' '[:lower:]'`; done

或者使用如下命令(更简单):

find resized -depth -exec rename 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;

详情请看这里这里

Written by qianxiong

July 28th, 2010 at 5:56 pm