Copyright © 2020 Powered by MWeb, Theme used GitHub CSS.
最近开始学习Docker,不打算从介绍开始写Docker,虽然Docker从诞生(2014.6)到现在(2015.6)已经一年了,但到现在Docker在中国还处于科普阶段,但Docker 的强大随便一Google就能看到大把大把的。 现在我正在看的书是《Docker技术入门与实践》,号称中国第一本讲解Docker 的书籍,而我对Docker的学习也将围绕着这本书展开。
Docker镜像(Image)类似与虚拟机的镜像,可以将他理解为一个面向Docker引擎的只读模板,包含了文件系统。 例如:一个镜像可以完全包含了Ubuntu操作系统环境,可以把它称作一个Ubuntu镜像。镜像也可以安装了Apache应用程序(或其他软件),可以把它称为一个Apache镜像。 镜像是创建Docker容器的基础,通过版本管理和增量的文件系统,Docker提供了一套十分简单的机制来创建和更新现有的镜像。 用户可以从网上下载一个已经做好的应用镜像,并通过命令直接使用。 总之,应用运行是需要环境的,而镜像就是来提供这种环境。
Docker容器(Container)类似于一个轻量级的沙箱子(因为Docker是基于Linux内核的虚拟技术,所以消耗资源十分少),Docker利用容器来运行和隔离应用。 容器是从镜像创建的应用运行实例,可以将其启动、开始、停止、删除,而这些容器都是相互隔离、互不可见的。 可以吧每个容器看作一个简易版的Linux系统环境(包括了root用户权限、进程空间、用户空间和网络空间),以及与运行在其中的应用程序打包而成的应用盒子。 镜像自身是只读的。容器从镜像启动的时候,Docker会在镜像的最上层创建一个可写层,镜像本身将保持不变。就像用ISO装系统之后,ISO并没有什么变化一样。
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还不是很熟练。
在面向对象的语言中,无论是C++还是Java,继承是常见的、优秀的语言机制之一。继承的优秀,带来了很多好处,比如代码重用,提高代码拓展性等等。在学C++的时候,刚刚接触继承,便被它的神奇震惊了。既然可以实现父类全部的属性和方法,那么只需要找到几个类的共性岂不是可以少写很多代码? 但是在实际用起来却不是这样,继承中很多不方便或者很多不得不接受的东西,比如如果要继承,你不得不接受父类所有的属性和方法,哪怕有相当一部分是用不到的。还有,如果本来在接受一些用不到的属性和方法的子类突然变了需求,那么如果重构的话,将花费更大的力气。 Java采用了单继承的方式,相对C++比,更少了一些灵活,甚至让人感觉弱化了继承,但我也和大多数人一样认为,利大于弊。但是只是靠这些并不能减少更多的弊端,怎么让利起到更多的作用,而减少弊端?这就是引入里氏替换原则(LSP)的原因。 里氏替换原则有两种定义:
其实说的通俗一点,就是只要父类出现的地方子类一定就可以出现,而且替换为子类也不会产生任何错误或异常。 为了让良好的继承定义了一个规范,里氏替换原则一句简单的定义包含了4层含义。
如果在做系统设计的时候,定义了一个接口或者抽象类,然后编码实现,调用类则直接传入接口或抽象类,其实这里已经使用了里氏替换原则。 如果我们定义了一个枪的抽象类:
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() {
//玩具枪不能射击,这个方法无法实现
}
}
那么把玩具枪传给士兵会怎么样?因为玩具枪无法射击,那么这个类并没有达到预期的效果,也就是说发生了子类替换父类之后出现错误的情况。 应该怎么解决这个问题,有两种解决方式:
第一种方法明显的不好,首先他是修改了和枪类无关的士兵了,让需要重写的范围增大了,还有,最要紧的就是如果增加一个需求就要修改一次的花,如果再加几次需求,任务量将比较大。 虽然按道理来说玩具枪继承抽象枪是完全没有问题的,但是要根据实际情况来,如果继承了,就出现了子类不能完整的实现父类的业务的情况。 如果子类不能完整的实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”。则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。
子类当然可以有自己独特的属性和方法。但是,里氏替换原则可以正着用,但不能反过来,也就是说,有父类的地方一定可以用子类代替,但有子类的地方不一定可以能用父类替换。 比如在步枪类下有两种枪,一个是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();
}
因为步枪没有狙击镜,不能观察,所以在向下转型是不安全的。从里氏替换原则看,就是有子类出现的地方父类未必就可以出现。
如果我们需要调用子类时是使用父类的方法,但是还要有子类独特的方法存在,也就是说子类不能覆写父类的方法,因为在一些情况下需要调用父类的方法,但是子类还需要有自己的方法,因为在某些情况下子类还需要调用自己的方法。 其实很简单也不过就是重载么,重载判断的特征值是参数,所以在子类重载父类的方法的时候需要把参数的范围放大,举个例子,有这么一个父类:
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传递给父类的时候执行的父类的方法,而传递给子类的时候执行的却是子类的方法。而我们的本意却是执行父类继承来的方法。所以子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或更宽松。
第三点讲的主要是重载,而已经明确子类是要覆写父类地方方法,假设父类的一个方法返回值是T,子类的同名方法返回值是S,父类和子类的同名方法的输入参数应该是相同的,两个方法的范围之S(子类)小于等于T。这是对覆写的要求,是重中之重。这样的目的是为了保证有父类的地方子类一定适用。 采用里氏替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性,即使增加子类,原有的子类还可以继续运行。在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑。 在项目中,应该尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当作父类使用,子类的“个性”被抹杀;把子类单独作为一个业务使用,则会让代码间的耦合关系变的扑朔迷离(缺乏类替换的标准)。
我的电脑是两块硬盘,一块是三星的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压力和扩容的目的。 经过一个下午的数据倒换和整理,吃晚饭后,终于可以进行了。
首先要做的就是换个能让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
然后就完成了。。。
Copyright © 2020 Powered by MWeb, Theme used GitHub CSS.