Ubuntu 下安装 Docker

Ubuntu在14.02开始就已经集成了Docker,要安装很简单:

sudo apt-get update
sudo apt-get install docker.io
sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker
sudo sed -i '$acomplete -F _docker docker' /etc/bash_completion.d/docker.io

但是,这样安装的Docker会比较旧,如果想要安装最新版本的Docker还需要安装https支持:

sudo apt-get install apt-transport-https

然后把官方源添加进来:

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
sudo sh -c "echo deb https://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
sudo apt-get update

然后就可以通过下面的命令来安装最新版本的Docker了:

sudo apt-get install lxc-docker

在安装了Docker官方软件源后,若需要更新Docker软件版本,只需要执行以下命令即可升级:

sudo apt-get update lxc-docker

如果使用的是较低版本的Ubuntu系统,则需要先进行内核更新并重启系统后再进行安装:

sudo apt-get update
sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring
sudo reboot

重启后,重复和14.04一样的安装过程即可。

2015/06/03 10:11 am posted in  Docker

Docker的三大核心概念

最近开始学习Docker,不打算从介绍开始写Docker,虽然Docker从诞生(2014.6)到现在(2015.6)已经一年了,但到现在Docker在中国还处于科普阶段,但Docker 的强大随便一Google就能看到大把大把的。 现在我正在看的书是《Docker技术入门与实践》,号称中国第一本讲解Docker 的书籍,而我对Docker的学习也将围绕着这本书展开。

Docker镜像

Docker镜像(Image)类似与虚拟机的镜像,可以将他理解为一个面向Docker引擎的只读模板,包含了文件系统。 例如:一个镜像可以完全包含了Ubuntu操作系统环境,可以把它称作一个Ubuntu镜像。镜像也可以安装了Apache应用程序(或其他软件),可以把它称为一个Apache镜像。 镜像是创建Docker容器的基础,通过版本管理和增量的文件系统,Docker提供了一套十分简单的机制来创建和更新现有的镜像。 用户可以从网上下载一个已经做好的应用镜像,并通过命令直接使用。 总之,应用运行是需要环境的,而镜像就是来提供这种环境。  

Docker容器

Docker容器(Container)类似于一个轻量级的沙箱子(因为Docker是基于Linux内核的虚拟技术,所以消耗资源十分少),Docker利用容器来运行和隔离应用。 容器是从镜像创建的应用运行实例,可以将其启动、开始、停止、删除,而这些容器都是相互隔离、互不可见的。 可以吧每个容器看作一个简易版的Linux系统环境(包括了root用户权限、进程空间、用户空间和网络空间),以及与运行在其中的应用程序打包而成的应用盒子。 镜像自身是只读的。容器从镜像启动的时候,Docker会在镜像的最上层创建一个可写层,镜像本身将保持不变。就像用ISO装系统之后,ISO并没有什么变化一样。  

Docker仓库

Docker仓库(Repository)类似与代码仓库,是Docker集中存放镜像文件的场所。 有时候会看到有资料将Docker仓库和注册服务器(Registry)混为一谈,并不严格区分。实际上,注册服务器是存放仓库的地方,其上往往存放着多个仓库。每个仓库集中存放某一类镜像,往往包括多个镜像文件,通过不同的标签(tag)来进行区分。例如存放Ubuntu操作系统镜像的仓库,称为Ubuntu仓库,其中可能包括14.04,12.04等不同版本的镜像。 根据存储的镜像公开分享与否,Docker仓库分为公开仓库(Public)和私有仓库(Private)两种形式。 目前,最大的公开仓库是Docker Hub,存放了数量庞大的镜像供用户下载。国内的公开仓库包括Docker Pool等,可以提供稳定的国内访问。山东理工大学开源社区什么时候搞个这个啊,可以提上进程。 如果用户不希望公开分享自己的镜像文件,Docker也支持用户在本地网络内创建一个只能自己访问的私有仓库。 当用户创建了自己的镜像之后就可以使用push明亮将它上传到指定的公有或则私有仓库。这样用户下次在另一台机器上使用该镜像时,只需将其从仓库pull下来就可以了。 Docker利用仓库管理镜像的设计理念甚至命令和git非常相似,也就意味着非常好上手,尽管目前我git还不是很熟练。

2015/06/03 10:10 am posted in  Docker

里氏替换原则

在面向对象的语言中,无论是C++还是Java,继承是常见的、优秀的语言机制之一。继承的优秀,带来了很多好处,比如代码重用,提高代码拓展性等等。在学C++的时候,刚刚接触继承,便被它的神奇震惊了。既然可以实现父类全部的属性和方法,那么只需要找到几个类的共性岂不是可以少写很多代码? 但是在实际用起来却不是这样,继承中很多不方便或者很多不得不接受的东西,比如如果要继承,你不得不接受父类所有的属性和方法,哪怕有相当一部分是用不到的。还有,如果本来在接受一些用不到的属性和方法的子类突然变了需求,那么如果重构的话,将花费更大的力气。 Java采用了单继承的方式,相对C++比,更少了一些灵活,甚至让人感觉弱化了继承,但我也和大多数人一样认为,利大于弊。但是只是靠这些并不能减少更多的弊端,怎么让利起到更多的作用,而减少弊端?这就是引入里氏替换原则(LSP)的原因。 里氏替换原则有两种定义:

  • 第一种定义:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都带换为o2时,程序P的行为没有发生变化,那么类型S就是类型T的子类型。
  • 第二种定义:所有引用基类的地方必须能透明地使用其子类对象。

其实说的通俗一点,就是只要父类出现的地方子类一定就可以出现,而且替换为子类也不会产生任何错误或异常。 为了让良好的继承定义了一个规范,里氏替换原则一句简单的定义包含了4层含义。

1.子类必须完全实现父类的方法

如果在做系统设计的时候,定义了一个接口或者抽象类,然后编码实现,调用类则直接传入接口或抽象类,其实这里已经使用了里氏替换原则。 如果我们定义了一个枪的抽象类:

abstract class AbstractGun{
	public abstract void shoot();
}

再定义一个士兵类,和他使用枪支的方法:

class Soldier{
	private AbstractGun gun;
	//获得一把枪
	public void setGun(AbstractGun gun){
		this.gun = gun;
	}
	public void killEnemy(){
		System.out.println("士兵开始杀人...");
		gun.shoot();
	}
}

如果我们需要多种枪支,只不过是需要分别继承于枪支抽象类:

class Handgun extends AbstractGun{

	@Override
	public void shoot() {
		System.out.println("手枪射击...");
	}
}

class Rifle extends AbstractGun{

	@Override
	public void shoot() {
		System.out.println("步枪射击...");
	}
}

我们在使用枪*支的时候不需要做什么区分,直接声明出来给士兵就够了:

public void Test(){
	Soldier sanmaoSoldier = new Soldier();
	sanmaoSoldier.setGun(new Handgun());
	sanmaoSoldier.killEnemy();

	sanmaoSoldier.setGun(new Rifle());
	sanmaoSoldier.killEnemy();
}

在这样的程序中,哪怕程序中枪支或士兵的需求发生变化,也不过只是需要修改相应的部分就可以。 在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。 如果在增加一个玩具枪会怎么样?

class Toygun extends AbstractGun{

	@Override
	public void shoot() {
		//玩具枪不能射击,这个方法无法实现
	}
}

那么把玩具枪传给士兵会怎么样?因为玩具枪无法射击,那么这个类并没有达到预期的效果,也就是说发生了子类替换父类之后出现错误的情况。 应该怎么解决这个问题,有两种解决方式:

  1. 在士兵类中增加instanceof判断,如果是玩具枪,就不用来kill。
  2. 让玩具枪与抽象枪类脱离关系,建立一个独立的父类,为了实现代码复用,可以与抽象枪类建立关联委托关系。

第一种方法明显的不好,首先他是修改了和枪类无关的士兵了,让需要重写的范围增大了,还有,最要紧的就是如果增加一个需求就要修改一次的花,如果再加几次需求,任务量将比较大。 虽然按道理来说玩具枪继承抽象枪是完全没有问题的,但是要根据实际情况来,如果继承了,就出现了子类不能完整的实现父类的业务的情况。 如果子类不能完整的实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”。则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。

2.子类可以有自己的个性

子类当然可以有自己独特的属性和方法。但是,里氏替换原则可以正着用,但不能反过来,也就是说,有父类的地方一定可以用子类代替,但有子类的地方不一定可以能用父类替换。 比如在步枪类下有两种枪,一个是AK47,一个是狙击步枪AWP:

class AK47 extends Rifle{

	public void shoot() {
		System.out.println("AK射击...");
	}
}

class AWP extends Rifle{

	public void zoomOut(){
		System.out.println("通过望远镜观察敌人...");
	}

	public void shoot() {
		System.out.println("AWP射击...");
	}
}

我们还可以定义一个狙击手类:

class Snipper{
	private void killEnemy(AWP awp){
		awp.zoomOut();
		awp.shoot();
	}
}

在这个类下,狙击手可以使用狙击枪,进行观察和射击,而之前定义的士兵类也可以使用狙击枪射击,但不能观察。这就说明有父类的地方完全可以用子类替换,当然,士兵类使用狙击枪是不会观察的,但依然可以保证枪能用。 但是如果把一个步枪传递给狙击手会怎么样?

public void Test(){
	Snipper sanmaoSnipper = new Snipper();
	sanmaoSnipper.setGun((AWP)new Rifle());
	sanmaoSnipper.killEnemy();
}

因为步枪没有狙击镜,不能观察,所以在向下转型是不安全的。从里氏替换原则看,就是有子类出现的地方父类未必就可以出现。

3.覆盖或实现父类的方法时输入参数可以被放大

如果我们需要调用子类时是使用父类的方法,但是还要有子类独特的方法存在,也就是说子类不能覆写父类的方法,因为在一些情况下需要调用父类的方法,但是子类还需要有自己的方法,因为在某些情况下子类还需要调用自己的方法。 其实很简单也不过就是重载么,重载判断的特征值是参数,所以在子类重载父类的方法的时候需要把参数的范围放大,举个例子,有这么一个父类:

class Father{
	public Collection soSomething(HashMap map){
		System.out.println("父类被执行...");
		return map.values();
	}
}

然后有一个子类继承了他:

class Son extends Father{
	public Collection doSomething(Map map){
		System.out.println("子类被执行...");
		return map.values();
	}
}

当我们执行父类的方法的时候,

public void text(){
	Father father = new Father();
	HashMap map = new HashMap<>();
	father.soSomething(map);
}

可以很明确,就是父类的方法得到了执行,而且,如果换做子类执行:

public void text(){
	Son son = new Son();
	HashMap map = new HashMap<>();
	son.soSomething(map);
}

因为重载的优先级问题,子类还是会执行父类的方法,而如果子类想执行自己的方法的时候,只需要吧HashMap换做Map。 如果没有按照放大的规则来,

class Father{
	public Collection soSomething(hMap map){
		System.out.println("父类被执行...");
		return map.values();
	}
}
class Son extends Father{
	public Collection doSomething(HashMap map){
		System.out.println("子类被执行...");
		return map.values();
	}
}

那么在把HashMap传递给父类的时候执行的父类的方法,而传递给子类的时候执行的却是子类的方法。而我们的本意却是执行父类继承来的方法。所以子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或更宽松

4.覆写或实现父类的方法时输出结果可以被缩小

第三点讲的主要是重载,而已经明确子类是要覆写父类地方方法,假设父类的一个方法返回值是T,子类的同名方法返回值是S,父类和子类的同名方法的输入参数应该是相同的,两个方法的范围之S(子类)小于等于T。这是对覆写的要求,是重中之重。这样的目的是为了保证有父类的地方子类一定适用。 采用里氏替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性,即使增加子类,原有的子类还可以继续运行。在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑。 在项目中,应该尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当作父类使用,子类的“个性”被抹杀;把子类单独作为一个业务使用,则会让代码间的耦合关系变的扑朔迷离(缺乏类替换的标准)。

2015/05/21 10:07 am posted in  设计模式

单一职责原则

写出符合规范的代码能够快速、直观的解决实际问题,但写出优美的代码,能够具有更好的可拓展性,让你当发生需求变化的时候不需要大范围的重构。 而所谓的优美,并不是说有一个漂亮的排版,而是从项目的设计层次上更加科学。符合科学的设计原则,能让代码更加健壮、可拓展。 单一设计原则(SRP),是一个本着单一原则,对接口和对象进行设计,以达到功能分离,从而达到当需求变更时能更少的重构的目的。 在做用户、机构、角色管理这些模块的时候,使用的基本都是RBAC(Role-Based Access Control,基于角色的访问控制,通过分配和取消角色来完成用户权限的授予和取消,使动作主体(用户)与资源行为(权限)分离)。说白了,就是让业务对象(被操作者)和业务逻辑(操作方法)分离。 如果有这么一个接口:

interface UserInfo{
    void setUserID(String ID);
    String getUserID();
    void setPassword(String password);
    String getPassword();
    void setUserName(String name);
    String getUserName();
    boolean changePassword(String oldPassword);
    boolean deleteUser();
    void mapUser();
    boolean addOrg(int orgID);
    boolean addRole(int roleID);
}

我们叫它牛类,自己独占实现了很多功能,的确能减少类和接口的数量,方便管理,作为单纯实现功能的话还是不错的。但这样科学吗?显然不可取。如果无论是用户模块需求发生变化,还是对管理的规则改变,都将导致对这个接口以及实现这个接口的类重写。 这显然没有符合单一设计原则,而应该做的就是把用户信息抽取成一个业务对象(Business Object,BO),而把行为抽取成为一个业务逻辑(Business Logic,Biz)。 那么就会把上述接口拆分成两个:UserBo和UserBiz,分别负责收集和反馈用户的属性信息和负责用户的行为,完成用户信息的维护和变更。

abstract class UserInfo implements UserBiz,UserBo{

}

interface UserBo{
    void setUserID(String ID);
    String getUserID();
    void setPassword(String password);
    String getPassword();
    void setUserName(String name);
    String getUserName();
}

interface UserBiz{
    boolean changePassword(String oldPassword);
    boolean deleteUser();
    void mapUser();
    boolean addOrg(int orgID);
    boolean addRole(int roleID);
}
class IUserInfo extends UserInfo{

    ....
}

因为是面向接口的编程,因此,产生IUserInfo对象之后,可以用UserBo接口使用,也可以用UserBiz接口使用。那么,获得用户就可以直接使用UserBo接口,而管理用户,维护用户信息则使用UserBiz接口:

UserInfo user = new IUserInfo();

UserBo userBo = (UserBo)user;
//就认为他是一个纯粹的BO
userBo.setPassword("admin");

UserBiz userBiz = (UserBiz)user;
//就认为他是一个纯粹的BIZ
userBiz.deleteUser();

这样,即使当用户或用户管理模块发生变动,则只需要修改变动的模块就可以,而没有必要将使用其模块的相关模块修改。那么总结一下,如果要依照单一职责原则,应该让类有且仅有一个原因使其变更。 也就是说,如果两个职责的变化不相互影响,就不应该放在同一个接口中实现,比如:

interface Phone{
	//打电话
	void dial(String phoneNumber);
	//通话
	void chat(Object o);
	//通话完毕,挂断电话
	void hangup();
}

这样的接口是否合理,是否符合单一原则? 那么做个假设,之前手机只能打电话,现在需求变了,要求手机还可以上网,那么会不会引起这个对象以及相关类的修改呢?必须会啊,那就说明这个接口还是存在问题的。 那么应该再把这个接口拆分,拆分为协议管理和数据传送,那么一个管理连接问题,一个管理数据传送问题各司其职。那么如果要添加上网功能的话,也不过就是新实现一个数据传输接口的类。 那么使用单一职责原则的好处也很明显了:

  • 降低类的复杂性,实现什么职责都有清晰明确的定义
  • 可读性提高
  • 可维护性提高
  • 变更引起的风险更低,一个接口只对相应的实现类有影响,对其他的接口无影响。

单一原则提出一个写程序的标准,用“职责“或变化原因来衡量接口或类设计的是否优良,但是”职责“和”变化原因“都是不可度量的,因项目而异,因环境而异。 对于接口,在设计的时候一定要做到单一,但对于实现类要考虑的方面将要更多,如果对单一原则生搬硬套,则实现类的数量将会剧增,反而人为的增加了系统的复杂性。规则是死的,人是活的,这些都可以灵活使用起来。 那么对于单一职责原则:接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化(不会有多个导致其重构的原因)

2015/05/20 10:06 am posted in  设计模式

挂载新硬盘扩容/home

我的电脑是两块硬盘,一块是三星的120G固态,一块是希捷的750G的机械。之前是主要用Windows系统,就把系统放在SSD里,机械作为补充。因为自从退队(应该是退队前一个月)就无情的抛弃的视窗(23333),然后转身投到Linux的怀抱(233333)。 当时没有顾虑太多,依然是按照Win的类似的分区方式,加上机械里面满满的东西,也就不打算动它。之前的分区方式:

Device     Boot    Start       End   Sectors  Size Id Type
/dev/sdb1  *        2048  80001023  79998976 38.2G 83 Linux
/dev/sdb2       80003070 234440703 154437634 73.7G  5 Extended
/dev/sdb5       80003072  84000767   3997696  1.9G 82 Linux swap / Solaris
/dev/sdb6       84002816 234440703 150437888 71.8G 83 Linux

一SSD为主,/和home都在里面,而机械纯粹作为数据仓库使用,当时就感觉可能会遇到home不够用的情况,但为了速度,也没做很多东西,直到今天,我看到这个:

/dev/sdb6       73907416  61589776   8540312   88% /home

这就呵呵, 然后我就决定把原有的NTFS格式的机械挂在到home中以到达缓解home压力和扩容的目的。 经过一个下午的数据倒换和整理,吃晚饭后,终于可以进行了。

1.重新分区(格式化)

首先要做的就是换个能让linux完美兼容的格式,本来想直接格式化,想想不如直接重新分区了。 首先要解除挂载:

sudo umount /dev/sda1

然后通过fdisk命令删除分区并新建分区

hypochen@HypoChen-TP:~$ sudo fdisk /dev/sda

删除:d命令 新建:n命令 之后新建了四个分区: p命令可以查看:

Device     Boot      Start        End   Sectors  Size Id Type
/dev/sda1             2048  419432447 419430400  200G 83 Linux
/dev/sda2        419432448  838862847 419430400  200G 83 Linux
/dev/sda3        838862848 1258293247 419430400  200G 83 Linux
/dev/sda4       1258293248 1465149167 206855920 98.7G 83 Linux

然后格式化,

sudo mkfs -t ext3 /dev/sdaX(X为序号)

然后就是挂载操作了,不能直接将磁盘挂载到home下,因为挂载之后无法获得写入权限,只能先挂载到mnt下:

hypochen@HypoChen-TP:~$sudo mkdir -p /mnt/VirtualBox
hypochen@HypoChen-TP:~$sudo mkdir -p /mnt/Data
hypochen@HypoChen-TP:~$sudo mkdir -p /mnt/Video
hypochen@HypoChen-TP:~$sudo mkdir -p /mnt/Project

在mnt下新建几个个文件夹,然后挂载:

hypochen@HypoChen-TP:~$ sudo mount /dev/sda1 /mnt/VirtualBox/
hypochen@HypoChen-TP:~$ sudo mount /dev/sda2 /mnt/Data/
hypochen@HypoChen-TP:~$ sudo mount /dev/sda3 /mnt/Video/
hypochen@HypoChen-TP:~$ sudo mount /dev/sda4 /mnt/Project/

修改新建文件夹的权限:

hypochen@HypoChen-TP:~$ sudo chmod 777 /mnt/Data/
hypochen@HypoChen-TP:~$ sudo chmod 777 /mnt/VirtualBox/
hypochen@HypoChen-TP:~$ sudo chmod 777 /mnt/Video/
hypochen@HypoChen-TP:~$ sudo chmod 777 /mnt/Priject/

然后修改fstab文件:

sudo gedit /etc/fstab

在文件的后面添加:

/dev/sda1 /mnt/VirtualBox  ext3  relatime    0  2
/dev/sda2 /mnt/Data  ext3  relatime    0  2
/dev/sda3 /mnt/Video  ext3  relatime    0  2
/dev/sda4 /mnt/Project  ext3  relatime    0  2

保存文件之后使用命令:

sudo mount -a

使得挂载生效。 最后做一个软链接:

hypochen@HypoChen-TP:~$ ln -s /mnt/VirtualBox/ VirtualBox
hypochen@HypoChen-TP:~$ ln -s /mnt/Data/ Data
hypochen@HypoChen-TP:~$ sudo ln -s /mnt/Video/ Video
hypochen@HypoChen-TP:~$ sudo ln -s /mnt/Project/ Project

然后就完成了。。。

2015/05/18 10:40 am posted in  Linux