最近已经入职了新的公司,不错的是公司给分配了配置还算不错的电脑。悲剧的是,操作系统是Windows7。之前使用Ubuntu的操作系统都已经快一年多了,都已经习惯了在Ubuntu下进行各个工作了,虽然在IM工具和Office工具上还是有些问题,但是影响还不至于很大。反倒是一下换回到了Windows环境下,反而有些不习惯了。
因为之前一直在从事云计算相关的开发与研究,所以会经常登录各种服务器,Ubuntu下的Terminal使用起来真的是很爽很方便。然而在Windows下,那个DOS Terminal一下子让我感受到了MS深深地恶意。没办法,只能自力更生,自己想办法解决了。之前是知道Cygwin的,而且也知道Cygwin有着些不足,并不是特别想用。于是在网上继续搜索着,终于找到一个叫做Babun 的软件。这东西是基于Cygwin做了层封装,但是做的还是挺好的,关键是居然还有一个Package Manager,这个真的是很难得的,更难得的是居然还有不少的软件可以安装。
首先是从Babun的官网下载Babun的安装包,Babun 默认安装在 %USER_HOME%.babun 目录,但是公司的电脑就C盘和D盘,而且C盘又小的可怜,没办法,只能将其安装到D盘了。解压Babun安装包,然后使用管理员权限打开cmd Dos界面,在安装包文件夹中执行以下命令即可:install.bat /t "D:\Babun"
等到命令执行完毕,Babun也就顺利安装完成了,看上去是不是很简单的。但是刚刚安装完毕的Babun的界面还是只是经典Dos风格的,看上去还是很丑。然后没办法,我只能祭出之前在Ubuntu上修改Terminator的办法,使用Solarized Dark配色的配置方法加上dircolors将其修改的有点Linux终端的味道了。最后就是使用私家的Vim配置 调整好Vim。整个过程折腾下来还是有些麻烦的,但是总算能够在Windows上有一个可用的Terminal了。
最后祭上最终的Babun的截图,看上是不是蛮不错的!
中午和老大请了个假,让后匆匆赶回学校。因为面试官下榻的宾馆离学校比较近,所以中午在宿舍磨蹭了一会儿,到下午1:20才出门。步行了2公里左右就到了面试地点(真的好近啊!!!)。由于提前到了,于是给面试官发了个短信,很快面试官就出来把我引入面试房间,说是面试房间,其实就是面试官的宾馆房间。面试官年龄看上去并不是很大,人很nice。等到都坐定了之后,就开始准备面试了。
其实,这次根本没有想到还会有面试的机会,当时相当于霸笔了,而且当时觉得卷子答的很是糟糕,就认定了是无望了。面试官开始翻阅之前的考卷,问我那个Linux下用宏计算结构体中变量偏移的题目现在会不会了,真心不知道那个宏究竟是什么,然后坦然告知还是不知道。于是,又接着问fopen()和open()的区别,这个考完后回来留心看了一下,然后解释了下主要是有cache缓存的区别就过去了。
以上算是开场白的话,后面面试官一边翻阅我的简历,一边让我自我介绍一下,这就算是正式开始了。首先是问了一下项目经历,问的并不是很细,只是粗略的了解了一下。由于两个在实验室的项目因为各种原因都是不了了之结束的,所以还特意的解释了一下原因。由于自己一直是在做云计算的东西,尤其是在学校这段时间,一直都是在搞OpenStack相关的东西。所以当我介绍之前搞得一个关于OpenStack
Neutron的项目的时候,面试官让详细介绍一下,似乎很是感兴趣(后面事实证明他在这方面应该也在做相应的研究和开发)。我向他介绍了一下Neutron的机制,整个网络架构和数据通信关系。然后还介绍一下Neutron现有的缺陷,说是存在单点故障的问题,而且网络节点承担整个集群的网络流量存在很大的问题。于是扯到社区上刚提出来的DVR的问题,并把DVR解释了一边。结果面试官问我DVR解决的是东西向流量问题还是南北向流量问题,想了一会儿觉得是外部流量问题,所以就回答了南北向,结果被告知是东西向的。回来仔细想想,其实这个问题并不是很好回答,因为DVR的确是解决了东西向流经网络节点的流量,但也是解决部分外部网络访问的流量问题,只不过像面试官所说的,解决方案上的问题还是比较多而已。
接下来,又问了一个系统设计的问题,针对neutron上的DHCP,面试官提问如果把DHCP服务集中到单一节点上服务,应该如何设计解决,想了一下后,就说要在每个计算节点上加入一个agent来监听每个ovs网桥,并在DHCP节点使用namespace隔离dhcp-server服务,并做好相应的持久化功能。对于这个答案,面试官也没有多说。然后又问了下OVS(OpenVSwitch)上的接口有多少中类型,只回答了两种。
算法题要求用python写一个字符串逆序的函数,不能用内置的reverse函数,然后就是一个二进制数的最大匹配,就是给定大于10w个(0, 2^32)的随机不重复的数,然后给定一个整数,求与其二进制匹配最长的数。先是给了一个求模递归的解法,效率较低,然后又改进使用异或方式给了个线性复杂度的解法。
在语言方面,由于本人的C功底不行,然后就只问了python中的类继承的问题,其实就是子类寻找父类的遍历方式,当时的答案被面试官说是错的,可是回来试试之后,觉得自己当时的回答应该是没错的,汗!!!
其中还穿插一些OpenStack的一些细节问题,一时也无法一一回想描述了。感觉这次面的一般般,不知道是不是要炮灰的节奏!
]]>最近因为工作的需要以及论文的方向,需要了解OpenStack监控方面的知识。所以深入看了一下OpenStack的Ceilometer,大致分析了一下Ceilometer的实现机制和工作流程,因此也就形成了本文的对Ceilometer的一个大致框架介绍。
Ceilometer的数据采集方式主要分为Poll和Push方式两种。
其中Push方式主要采集为OpenStack中各个组件模块中无法定时主动获取的事件消息,例如:虚拟机的创建,镜像的上传等等。该种方式的消息的采集依赖各个组件在事件发生时,依赖Ceilometer提供的消息机制将事件消息上报至消息队列当中。然后由Ceilometer-Collector中的notification-agent收集消息队列中的事件消息,然后交由指定的Pipeline将消息转换为指定的采样数据(Samples),转换之后的采样数据会被重新发送至消息队列当中,然后由Collector收集处理并存入数据库当中(MongoDB)。主要架构如下图:
Poll方式主要采集OpenStack中的各个组件的统计数据和计算节点中的实时数据(该数据也是可以被随时统计获取的)。
在Controller节点上,Poll方式主要是启动相应的轮询进程(Pollsters),依靠轮询进程定期调用组件模块的APIs获取各个组件的数据信息。然后将数据交由Pipeline进行处理,最后由Collector处理存储。此过程与上述Push方式一致。
在Compute节点上,Poll方式也是启动相应的轮询进程(Pollsters),依靠轮询进程定期查询相应的信息,只是在数据采集方式上,采用虚拟机的相关驱动获取虚拟机的信息,目前主要的部署方案都是采用KVM-QEMU实现虚拟化,因此,底层信息获取上,采用的为LibVirt操纵虚拟机,同时也是通过LibVirt获取虚拟机的相关信息。当数据被采集之后,其之后的处理流程与上述两种方式都是一致的。
前面的数据采集工作完成之后,采集来的数据会交由Pipeline进行数据处理,Pipeline主要实现的是一个数据处理链的功能。Pipeline会根据不同的配置将0个或一个或多个Transformers组装成为一条数据处理链,在这条数据处理链的末端,会被装配一个Publisher。当数据进入这条数据处理链后,会被Transformers加工处理,然后由Publisher发送至消息队列当中,由Collector收集。
Collector会时刻监听着消息队列,从消息队列中获取监控数据,然后将数据存入MongoDB中进行持久化。
1.OpenStack System Architecture
2.ceilometer的数据采集机制
3.OpenStack Ceilometer简介
很久之前写了一篇关于OpenStack Neutron解析的文章,那时只是粗略的写了一下把Neutorn的整体架构分析了一下,后来一直忙于其他事情,也就忘了去详细分析一下Neutron的架构。这次这篇算是完成未完之事,同时也是对之前的一个知识的总结及恢复。
OpenStack的Neutron自从由nova-network从Nova中分离出来之后,一直感觉十分的不稳定,而且初期其结构也是十分的复杂。很多人刚刚接触Neutron,甚至刚刚接触OpenStack的时候,都是被困在Neutron异常复杂的机制。尤其当我们部署了一套由Neutron管理网络的OpenStack环境时,会发现很多时候都是在解决各种莫名奇妙的问题,但我们纠察问题时,总是会涉及Neutron。所以,我总是一直认为在实际的生产环境中,如果不是对于网络真的有着很特殊的需求,直接部署OpenStack的Essex版本,别人问我,我也是如是的回答。但是,我们如果是想研究OpenStack的话,Neturon的可玩性还是很大的,尤其是其支持SDN等一些很前瞻性的特性。所以,对于Neturon我们有必要深入的研究一番。
接着上次的那篇文章,我们再来重新回顾一下Neutron的架构,从物理上划分的话,我们的Neutron主要部署在两类节点上:Compute节点和Network节点,而至于Controller节点,那不是主要的所在,因为几乎所有的组件都要在部署一个server服务在Controller节点上。从网络分层上来看,主要分为二层网络L2-Agent,三层网络L3-Agent,以及DHCP-Agent。借用官网上几张图片,按照物理划分的方式,大致分析一下Neutron架构。
这张图摘自官网,为Compute节点的网络架构及流程分析图。这张图中,我们可以清楚的看到网络相关的一些设备(其实就是一些进程或者系统接口)被分为四类:TAP device
,veth pair
,Linux Bridge
,Open vSwitch
,这里其实用的Open vSwitch的方式部署Neutron,而我之前也一直使用Open vSwitch部署的,但是这里却也是有着Linux Bridge的,正如之前文章所言,这个是为了实现安全组功能,但Open vSwitch暂不支持OpenStack的实现方式,所以只好用Linux Bridge实现qbr网桥作为一个折衷方案。接下来就是最为大头的Open vSwitch,它在Neutron中构建了一个虚拟的交换机,而这个虚拟交换机由L2-Agent控制着,最终所有Compute节点中的虚拟交换机统一构成一个巨大的虚拟交换机,统一控制虚拟机在二层网络的数据交换和接入功能。从图中我们可以看出每个Compute节点有两个Open vSwitch网桥,其实br-int
才是真正扮演交换机角色的,而br-eth1
则是通过GRE
通道在所有节点之间构成一个统一的通信层,实现各个Compute节点上虚拟机之间的通信。
接下来就是看看Network节点上的情况,下面的是Network节点的原理图:
这张图上,我们可以清楚看到Network节点被分为三大部分:Configured by L2-Agent
,Configured by L3-Agent
,Configured by DHCP-Agent
,而L2-Agent那部分和我们在Compute节点讨论的是一致的,所以此处就忽略了。接着我们看L3-Agent,在Neutron中,出现了私有网络这一概念,当然也是实际存在的。而依据以前的nova-network,是无法实现这一功能的,nova-network顶多能使用VLAN技术实现网络隔离,而无法实现真正的私有网络。在物理网络中,我们要想实现一个私有网络,那么就必须有个路由器才行,而L3-Agent正是Neutron中实现这个路由器而存在的(事实上,我们在Havana版本中部署的环境中,网络拓扑图中就很形象的显示了这些routers),L3-Agent的底层实现采用的是Linux系统自带的iptables技术,通过动态的生成配置iptables规则,实现网络的路由功能以及floatip功能。而每个用户都要一个私有网络的话,一个路由肯定是不够用的,接着就是Linux中namespace技术的用武之地了,事实上,Neutron为每个私有网络都配置一个router和dnsmasq,而这个就是依靠namespace进行规则和配置的隔离。如下图所示:
现在,每个私有网络可以建立了,但是我们总不能为每个虚拟机手动配置私有ip的,所以这时候DHCP-Agent就显示了其用处,DHCP-Agent通过控制dnsmasq实现了DHCP功能,这样每个虚拟机在启动的时候就可以动态获取私有ip了。
至此,我们就已经把OpenStack Neutron的整理结构分析清楚了,剩下就是我的上篇文章中的流程打通了。
个人而言,由于写的代码并不是很多,因而对于很多的设计模式,实际上大多都只是停留在纸面的理解上。但是设计模式中的很多思想,还是很是值得我们去好好去理解研读的,所以很多设计模式还是要去熟悉一下的。
今天被问到工厂模式与new的区别,脑袋就那么断线了,有工厂模式
这个概念,却把它的一些特性给忘了。充分暴露了学习不够深入,理解不够透彻的弊病了。所以就从工厂模式开始,慢慢复习一下设计模式吧。
先来回答上面那个问题吧,工厂模式往简单了说,其实就是把new一个对象的操作做了一些封装,往复杂的方向来说,则不仅仅是对new进行了封装,而且还将具体的实例化过程推迟到了具体的工厂子类中。
我们先从简单工厂
开始,图个方便,就以《Head First设计模式》上的Pizza例子解释吧。
好了,现在我们有一家Pizza店PizzaStore
,所有的Pizza都是PizzaStore
自己生产的,每当有一种新的口味流行时,我们就要在这个类中间增加一种Pizza制作方法。当我们的店面还只是一个的时候,这样的方式还是可以接受的。很快,我们的Pizza店由于广受欢迎,在这个A城市开了很多分店。然后,问题就出来,每次新的Pizza出来时,我们都要对每个店面进行修改,这样的弊病会随着店面数量的增加而不断加重。于是,我们对其结构进行一次改造,我们把Pizza的生产任务交给一个独立的工厂SimplePizzaFactory
,然后各个店面都从这个工厂中获取各种Pizza。这样,当每次出现新的Pizza的时候,我们就只需要更改一下SimplePizzaFactory
就行了。
我们可以把这个叫做简单工厂模式
,但事实上,简单工厂模式
算不得设计模式,只能说是个良好的设计习惯吧,上图就为我们展示了简单工厂模式
的类图结构。
过了好长时间,我们的Pizza店实在是太受欢迎了。于是乎我们开始在其他城市开设分店,可是问题也随之而来,我们发现其他城市的人们的口味对于同一种Pizza也是有所不同的,而我们原来的工厂只能统一生产同一口味的Pizza。于是,我们又把生产Pizza的权利转给Pizza店,让他们自己决定如何生产Pizza,而我们只需要提供一个统一的店面形象就行了,就像类图中描述的一样,而这个模式就是工厂模式
了。
OK!问题似乎解决了!可是,我们最初遇到的品种增多的问题似乎又回来了。每当新种类出来,每家店面又都忙着添加新Pizza,这看起来好麻烦啊!身为爱偷懒的程序猿,怎么能容忍这么麻烦的事出现。于是,我们又改造了我们的Pizza店。
这次,我们决定还是把Pizza生产的权利收回来。在前面简单工厂模式
的基础上,我们进一步改造,决定在每个城市建立一座Pizza工厂,当然,每个工厂由当地的城市去兴建,而我们只需要提供一个工厂的统一建设标准,至于每家工厂自己如何生产Pizza,我们就不再去关心了,而且,此后这个城市的Pizza分店都是从各自城市的Pizza工厂获取Pizza的,这既保证了Pizza的质量和品味,有保证了新产品增加的灵活性。其实,这个模式就是工厂模式
的一个升级了,我们称之为抽象工厂模式
,顾名思义,我们这次把工厂也给抽象化了。抽象工厂模式
的类图如下所示。
通过这个Pizza店面的例子,差不多已经把工厂模式
和抽象工厂模式
的主要思想已经讲解清楚了,至于具体实现的代码,可以参考《Head First设计模式》一书中的工厂模式章节。
由于我们的系统在很早之前就部署完成了。当时Ceilomter刚刚在Havana版本中发布,我们主要专注于网络和虚拟化部分,所以当时就没有在我们的Havana版本环境中安装Ceilomter,但是现在由于需要,开始研究OpenStack的监控功能, 所以就需要在我们现有的系统上补上Ceilomter了。利用了网上的各种教程,但是发现都有些问题,最终不得不依靠官方的Havana部署教程,利用其Ceilometer安装那一节的教程,完成了Ceilometer的完美部署。
我们的系统是依据OpenStack Havana版本部署的,整个系统采用网络集中式部署,也就是说我们的系统分为三个部分:控制节点,网络节点和计算节点。具体的系统相关信息可以参照该篇教程。
我们需要在所有的节点安装以下的安装包,这个是Ceilometer的实现基础:
sudo apt-get install python-ceilometer ceilometer-common
1.在控制节点安装以下的包:
sudo apt-get install ceilometer-api ceilometer-collector ceilometer-agent-central python-ceilometerclient
2.如此之后,控制节点上就已经完成了Ceilometer大部分安装了,接下来就是安装mogodb数据库,这个是Ceilometer默认的数据存储的仓库:
sudo apt-get install mongodb
3.接着我们开始配置mongodb监听所有的网络接口请求:
sudo sed -i 's/127.0.0.1/0.0.0.0/g' /etc/mongodb.conf
4.重启mongodb服务,让配置生效:
service mongodb restart
5.创建数据库ceilometer和对应的用户:
首先进入mongodb:
mongo
接着执行创建用户命令:
>use ceilometer >db.addUser( { user: "ceilometer", pwd: "CEILOMETER_DBPASS", roles: [ "readWrite", "dbAdmin" ]} )
6.编辑/etc/ceilometer/ceilometer.conf文件,配置数据库参数:1
2
3
4[database]
# The SQLAlchemy connection string used to connect to the
# database (string value)
connection = mongodb://ceilometer:CEILOMETER_DBPASS@controller:27017/ceilometer
7.利用openssl生成一个随机token密钥,该密钥用于Ceilometer各个组件之间通信加密使用:
openssl rand -hex 10 cefafd2288d0e4e43005 (注:这是命令生成的随机token)
编辑/etc/ceilometer/ceilometer.conf文件,修改中间的 [publisher_rpc]
选项,配置token
:1
2
3
4[publisher_rpc]
# Secret value for signing metering messages (string value)
metering_secret = cefafd2288d0e4e43005
...
8.编辑/etc/ceilometer/ceilometer.conf,修改RabbitMQ
配置选项:1
rabbit_host = controller_ip_address(自行修改)
9.编辑/etc/ceilometer/ceilometer.conf,修改日志打印目录:1
2[DEFAULT]
log_dir = /var/log/ceilometer
10.Keystone相关信息创建:
keystone user-create --name=ceilometer --pass=CEILOMETER_PASS --email=ceilometer@example.com keystone user-role-add --user=ceilometer --tenant=service --role=admin keystone service-create --name=ceilometer --type=metering --description="Ceilometer Telemetry Service" keystone endpoint-create --service-id=the_service_id_above --publicurl=http://controller_ip_address:8777 --internalurl=http://controller_ip_address:8777 --adminurl=http://controller_ip_address:8777
11.编辑/etc/ceilometer/ceilometer.conf,修改相关配置:1
2
3
4
5
6
7
8
9
10
11
12
13[keystone_authtoken]
auth_host = controller_ip_address
auth_port = 35357
auth_protocol = http
auth_uri = http://controller_ip_address:5000
admin_tenant_name = service
admin_user = ceilometer
admin_password = CEILOMETER_PASS
[service_credentials]
os_username = ceilometer
os_tenant_name = service
os_password = CEILOMETER_PASS
12.重启Ceilometer相关服务,使其生效:
service ceilometer-agent-central restart service ceilometer-api restart service ceilometer-collector restart
13.编辑/etc/glance/glance-api.conf,修改Glance
配置:1
2notifier_strategy = rabbit
rabbit_host = controller
重启相关服务:
service glance-registry restart service glance-api restart
14.编辑/etc/cinder/cinder.conf,修改Cinder
配置:1
2control_exchange = cinder
notification_driver = cinder.openstack.common.notifier.rpc_notifier
重启相关服务:
service cinder-volume restart service cinder-api restart
注:由于我们的环境中没有安装Swift,所以有关Swift配置的部分就省略了。需要的话,请查看本教程中的参考资料。
1.安装计算节点所需服务:
sudo apt-get install ceilometer-agent-compute
2.编辑 /etc/nova/nova.conf文件,配置Nova相关选项:1
2
3
4
5
6
7[DEFAULT]
...
instance_usage_audit = True
instance_usage_audit_period = hour
notify_on_state_change = vm_and_task_state
notification_driver = nova.openstack.common.notifier.rpc_notifier
notification_driver = ceilometer.compute.nova_notifier
3.编辑/etc/ceilometer/ceilometer.conf,配置计算节点上Ceilometer的选项:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23[publisher_rpc]
# Secret value for signing metering messages (string value)
metering_secret = cefafd2288d0e4e43005
[DEFAULT]
rabbit_host = controller
[keystone_authtoken]
auth_host = controller_ip_address
auth_port = 35357
auth_protocol = http
admin_tenant_name = service
admin_user = ceilometer
admin_password = CEILOMETER_PASS
[service_credentials]
os_auth_url = http://controller_ip_address:5000/v2.0
os_username = ceilometer
os_tenant_name = service
os_password = CEILOMETER_PASS
[DEFAULT]
log_dir = /var/log/ceilometer
4.重启服务使得配置生效:
service ceilometer-agent-compute restart
1.Install the Telemetry module - OpenStack Installation Guide for Ubuntu 12.04 (LTS) - havana
2.Ubuntu 12.04 Server OpenStack Havana多节点(OVS+GRE)安装
3.部署Ceilometer到已有环境中
每天打开电脑的第一件事就是开QQ、开微博(躺枪的举个手),然后就是打开自己的InoReader(前几天还是Digg Reader,但是嫌弃界面太简洁和功能太简单了,就换了)。每天都能看到不同的有趣的文章其实也是中享受的。
今天就在「伯乐在线」上看到一篇文章,其实算不上文章的,只是把StackOverFlow上面的一个问题给翻译了,但是内容本身还是值得探讨的。这个问题就是讨论指针的,没错,就是C/C++的指针的,估计很多人认为很简单了。但是说实话,我到现在还是不敢说自己已经完全了解指针的,当然也没有把指针玩的很溜的。
关于指针我记得有个经典的段子,形象的说明指针的优势和缺陷的,可惜已经记不太清楚了。个人认为,编程语言无非就是为我们提供一套操作内存与CPU的规则,而我们写出来的程序,就是我们利用这套规则编写的流程指导书而已。基于这种想法的话,我们会发现C语言提供的指针,真的是个非常美妙的工具。因为指针的存在,我们对于内存的操作,可以达到前所未有的灵活多变。当然,就像玩双节棍一样,在高手手中,是威力无穷的武器,但是到了低手手中,说不定就是自伤八百的凶器了。而低手就是要在这种近乎自残的锻炼方式中,才能慢慢成为高手。
记得当初学C语言的时候(相信大家都一样,大学学的第一门语言都是C语言),由于课时紧张,指针这个最重要的部分,直接就被授课老师给略掉了,所以后来一直对此耿耿于怀,因为自己走了好多弯路,才明白编程的意义所在。指针真的很重要,我认为只要我们真的理解指针的意义,对于编程来说,我们已经了解了一半了。然而,等我把指针弄懂了,其实指针本身也不难,但是却是难在如何使用上,对于指针的使用,其实就是我们对于内存的操作过程。当我们把指针调用来调用去的时候,很容易就把自己给绕晕了,最后连自己都不知道某个到底指到哪块内存了。所以,个人认为,使用指针最重要的原则就是,自己一定要有清晰的思路,能够明晰知道自己操作的指针的具体指向,否则就别用了吧。
说了这么多,很多其实都是废话了。其实,C中的指针对于我们来说,最终要的就是记住两个符号:*和&,这两个符号会一路伴随着我们的指针使用历程的。*表示取值,而&则表示取址,谨记它们的含义,那么我们的指针历程也就不会那么艰辛了。
关于单重指针,我们假设有以下的范例:
1 |
|
我们简单编译执行后,就有了以下的结果:
1 | i's address is: bfd42908 |
很明显啦,p就是指向i所在内存的一个指针,其实就是i的别名。回过头来我们在来看看*,其实*就是说明指针的符号,它表示p是一个指针变量,如果是两个**则是表示指针的指针,然后我们可以依次类推一下了。
我们再来看看二重指针,也就是那篇文章中讨论的问题。
1 | #include <stdio.h> |
这段代码执行完成后,我们会有以下的结果:
1 | i's address is: 22ff1c |
从上面的结果我们可以看到,**ipp是一个二重指针,它实际上是指向了一重指针*ip1的内存地址。也就是说ipp实际上指向指针ip1的指针。
没改变之前的最后的两行可以看到,**ipp的值为5,也就说,ipp最终指向的还是变量i,而*ipp也是存着ip1的地址值得,也就说ipp还是指向ip1的,而*ipp实际上指针ip1的别名罢了。
OK,接下来我们来改变一次:*ipp = ip2;
,可以发现ip1指向了j,而且*ipp和**ipp也都随着ip1的变化而变化了,这也再次证明了我们关于*ipp的说明:*ipp是ip1的别名。下面我们可以看看更为直观的图示:
因此,我们可以知晓多重指针中的*的作用,当某个指针**ipp的*被去除了一个再次操作(*ipp),其实就是对**ipp指向的那个内存中的数据进行操作,而*ipp也其实就是这块内存的另一个别名罢了。
]]>今天,实验室的一同学在登录自己的Ubuntu系统时,发现自己忘了登录密码。于是重启进入恢复模式
中,想要重置用户密码,结果执行相关命令后,得到以下错误提示:
root@username-PC:~# passwd usernameEnter new UNIX password:Retype new UNIX password:passwd: Authentication token manipulation errorpasswd: password unchanged
我刚开始以为是同我上次的系统循环登录的原因是类似的,于是按照我的教程操作,修改了相关文件的权限,重新在恢复模式
下重置密码,结果还是同样的错误。在伟大的Google的帮助下,找到了解决问题的办法,原因似乎是磁盘的根目录挂载出现了问题,执行以下命令:
mount -rw -o remount /
然后在恢复模式
中就可以重置密码了。
《Authentication token manipulation error》:http://askubuntu.com/questions/91188/authentication-token-manipulation-error
]]>Elementary OS自从出来之后,便声称是“最美的Linux”。我在很早之前看了相关介绍后,出于对于Elementary OS简洁精致的界面的喜爱,便很快安装了Luna版本的,这也是目前唯一的一个正式版本。
由于一直把安装了Elementary OS的电脑当作服务器使用,便一直没有关机(实验室的电脑,因此才可以不关机啊)。等到寒假放假回家,实验室要求断电,这才把电脑关机。可是关机的总是在界面无法关机,当时也没有太注意,直接就给强行关机了。等到前两天回到实验室,再次开机时,才发现已经无法通过图形界面登录进去了,也就是tty7无法登录。通过Google的帮助,弄了小半天,搞定了界面登录问题。(其实主要是Elementary OS太过于小众,没有多少资料,不过由于其是基于Ubuntu的,可以使用一部分Ubuntu的资料)
开机进入图形登录界面后,输入密码后系统开始确认,先是黑屏几秒,然后就直接跳回到登录界面,一直无法进入桌面。但是,使用Ctrl
+Alt
+F1
,可以进入tty1,通过命令行登录进去。
1.使用Ctrl
+Alt
+F1
,使用命令行界面登录系统。
2.执行命令sudo su
,切换至root
权限。
3.执行命令ls -lah
,可以看到:
-rw------- 1 root root 53 Nov 29 10:19 .Xauthority
4.执行命令chown username:username .Xauthority
,修改文件的用户和用户组,然后重启系统,就可以正常登录了。
《Ubuntu gets stuck in a Login Loop》:http://askubuntu.com/questions/223501/ubuntu-gets-stuck-in-a-login-loop
]]>其实身为一名将来要成为码农的软工master,学术活动基本上是与我们无关的,而与写paper也几乎是绝缘的。然而因为读书报告的缘故,也是寥寥看了几篇paper,尤其是Google的那三篇牛文,更是好好的拜读了一番。读完之后,除了对GFS
、MapReduce
、BigTable
有了些了解外,倒是对这些paper的行文风格甚是感兴趣,觉得这些paper的行文似乎都是一脉相承,但是其思路倒是很是清晰明了。
今天闲来无事,把之前从网络上淘来一份slide给看了,主题就是讨论如何写好一篇论文的。至此想起之前的那些牛文,顿时明白了那些行文风格是从何处而来的了。于是,也有了把这个slide写成blog的想法。
这篇文章主要是把slide中的我认为比较重要的部分提取出来予以整合以实现共享吧。
首先,我们明确一下写paper的目的,按照国内这种功利的学术氛围,paper无非是用来实现各种利益的工具而已。然而,paper的真正的目的应该是交流思想的,把我们的对于某一问题的思想传递给别人。正如slide中的作者所述:
The greatest ideas are (literally) worthless if
you keep them to yourself.
对于写paper的方式,原作者给出了两种方式:
1.Idea
-> Do Research
-> Write Paper
2.Idea
-> Write Paper
-> Do Research
而且,原作者似乎比较倾向于第二种方式,理由无外乎借助外部力量实现研究的明确性。但是,由于我没有写过paper的经历,总是觉得的第一种方式才是合乎逻辑的,或许,原作者的Write Paper只是编写paper吧,并不是将其正式的发表出来,暂时只能这样猜想了。
如上所说,paper的真正作用是传递思想,那么我们该怎么把我们的思想传递给读者呢?当然是要有合理明了的逻辑顺序。因此,我们有着以下的要点和行文思路:
好了,行文思路我们都有了,剩下来就是实现啦!OK,我们为我们的文章编写了以下的结构
:
关于摘要
,我们有下面的要求:
写完摘要
,我们就要写简介
了,简介
的主要要点如下:
接下来就是整个paper的主体了,问题
+我们的方案
+详细实现
:
写完整个paper的主要部分,剩下来的就不多了。对于同一个问题,我们给出了方案,别的人也说不定有其他的方案,那么,我们就要比较一下,以显出我们的优势。我们的方案肯定也是借助了外部的其他人的工作成果的,那么我们还要在相关工作
予以引用说明啊。
这个slide中大致给出了这些思路,若是想要看具体的实践,可以推荐Google的那些paper,这些paper都是很好遵循以上的原则的,值得一读。
PS:参考的slide可以到此处下载
]]>作为一名程序猿,懂得翻墙是十分必要的。由于种种原因,天朝通往世界的网络上,被人为的设置了一道「长城」。然而,「长城」之外的我们向往的先进的技术和知识,也被无辜的给拒之墙外了,所以,我们都在努力的「翻墙」,不为别的,只为能够获得最为直接的技术和知识。
关于翻墙,网络最多的就是利用如何使用GAE来翻墙,鉴于这样的教程实在是多如牛毛了,也就不再赘述了。关键是在于,我是在觉得GAE的配置比较烦人,最重要的是,访问的流量有限制和速度太慢。所以,一直以来还是习惯使用FreeGate(以下简称FG)了,虽然FG是免费的,而且在Windows下,配合Firefox使用十分方便,但是,FG的使用还是需要配置一些参数,否则用起来还有些不方便。
之前,我的电脑一直是把chrome设置为默认浏览器,而且chrome也没有和FG关联上,可是每次用chrome浏览网页总是出现图片模糊甚至不可见的状态。开始以为是浏览器的问题,重装了结果还是这样,后来才发现是开了FG的原因,原来,FG默认是将自己设置为IE代理的,所以,每次用chrome的时候,其实一直都是在用代理在访问国内的网站。为此,花了点时间,好好的将FG的配置修改了一番,终于可以在不影响系统和国内访问的情况下,自由的翻墙了。
现把一些配置要点总结如下:
1.浏览器设置成Firefox或者Chrome,不要用默认的那个IE浏览器设置。FG默认的是IE,一般来说,这个要设置成我们常用的浏览器。
2.设置中勾选以下选项
尤其是那个不设置IE代理的选项,一定要勾选上,否则FG就会成为系统默认浏览器的代理。但是,我们的chrome或者Firefox都是使用插件控制访问代理的,所以不需要设置IE代理。
3.在代理设置中选择国内网站直通,并制定自己的规则
这个其实只是为了方便直接访问国内的网站,而且浏览器上的插件也带有这些功能,所以设置一下只是为了更加方便些而已。
4.通道配置,切记选择代理模式并在设置中勾选不设置IE代理,否则会造成IE和默认浏览器访问一些网站出现问题。
这个必须选择代理模式,由于我们在第二步中选择不设置IE代理,所以其实这项没有全部起作用,但是,如果我们不选择代理模式的话,就没有办法使用第三步中的国内网站直通选项。
关于FG的使用,大致有这些总结,以后有新的发现会继续添加。
]]>自从开学以来,玩OpenStack
也已经3个月了,这段时间主要把精力投在了OpenStack
的安装部署和网络组件Neutron
的研究上了。这期间零零散散在安装部署和Neutron
运作原理上来回切换,有点在实践中学习的味道,虽然在安装部署的过程遇到了不少的问题,也一一都给解决了。然而,总是觉得对于Neutron
的机制理解的还是不够透彻。前一阵子刚刚部署好一套Havana
版本的系统,并开始投入使用。这阵子又开始投入OpenStack
的IPv6的试验中,因为需要对底层的路由机制进行彻底的了解,所以不得不又开始重新捋了一遍这方面的知识,在网上Google了一把资料,这次算是彻底的把Neutron
的运作机制彻底的理清楚了,之前那些半懂不懂的东西,现在也是觉得豁然开朗了。
这次在RDO找到了想要的资料,至少这篇资料对于之前的那些零散的知识做了一个很好的连贯,对于自己而言,算是把Neutron
讲解彻底明了,现在把那篇材料翻译下再糅合点自己的理解吧。
借用原资料上的一张架构图为开篇:
从这张架构图中,我们可以明显的看到有两个物理主机,计算节点和网络节点,这是因为采用了网络节点集中式的部署方式。至于为什么采用这种部署方式,那是因为自从E版之后,OpenStack
开始把network
功能从Nova
中分离出来,使之成为独立的Neutron
组件。而坑爹的是,分离后的版本,反而不支持网络分布式部署的特性了,所以目前的Grizzly
和Havana
版本都是只能使用网络集中式部署方案的,或者说,集群中只能存在一个部署网络功能的节点。
从图中看,这段网络包含了A、B、C这三段的流程。A就是虚拟机test0
的虚拟网卡,这块没什么好讲的。和它相连的B倒是值得好好讲一下。B是一个TAP
设备,通常是以tap
开头的一段名称,它挂载在Linux Bridge qbr
上面。那什么又是TAP
设备呢?Linux 中的虚拟网络中给出了这样的解释:
TAP是一个虚拟网络内核驱动,该驱动实现Ethernet设备,并在Ethernet框架级别操作。TAP驱动提供了Ethernet “tap”,访问Ethernet框架能够通过它进行通信。
总而言之,TAP
设备其实就是一个Linux内核虚拟化出来的一个网络接口。OK,我们明白了TAP
设备了,如果还是不明白可以查看TAP
的具体定义。接下来就是qbr
,这之前就说了,是一个Linux Bridge
,很是奇怪,我们在这个架构中,使用的OpenvSwitch
实现虚拟交换设备的,为什么会出现Linux Bridge
呢?OpenStack Networking Administration Guide给出了这样的解释:
Ideally, the TAP device vnet0 would be connected directly to the integration bridge, br-int. Unfortunately, this isn’t possible because of how OpenStack security groups are currently implemented. OpenStack uses iptables rules on the TAP devices such as vnet0 to implement security groups, and Open vSwitch is not compatible with iptables rules that are applied directly on TAP devices that are connected to an Open vSwitch port.
其实就是说,OpenvSwitch
不支持现在的OpenStack
的实现方式,因为OpenStack
是把iptables
规则丢在TAP
设备中,以此实现了安全组功能。没办法,所以用了一个折衷的方式,在中间加一层,用Linux Bridge
来实现吧,这样,就莫名其妙了多了一个qbr
网桥。在qbr
上面还存在另一个设备C,这也是一个TAP
设备。C通常以qvb
开头,C和br-int
上的D连接在一起,形成一个连接通道,使得qbr
和br-int
之间顺畅通信。
刚才说到D(这也是一个TAP
设备)在br-int上面,现在轮到br-int
出场了,br-int
是由OpenvSwitch
虚拟化出来的网桥,但事实上它已经充当了一个虚拟交换机的功能了。br-int
的主要职责就是把它所在的计算节点上的VM都连接到它这个虚拟交换机上面,然后利用下面要介绍的br-tun
的穿透功能,实现了不同计算节点上的VM连接在同一个逻辑上的虚拟交换机上面的功能。这个似乎有点拗口,其实就是在管理员看来,所有的VM都是连接在一个虚拟交换机上面的,但事实上这些VM又都是分布在不同的物理主机上面。OK,回到D上面,D通常以qvo开头。在上面还有另一个端口E,它总是以patch-tun
的形式出现,从字面就可以看出它是用来连接br-tun
的。
br-tun
在上面已经提及了,这同样是OpenvSwitch
虚拟化出来的网桥,但是它不是用来充当虚拟交换机的,它的存在只是用来充当一个通道层,通过它上面的设备G与其他物理机上的br-tun
通信,构成一个统一的通信层。这样的话,网络节点和计算节点、计算节点和计算节点这样就会点对点的形成一个以GRE
为基础的通信网络,互相之间通过这个网络进行大量的数据交换。这样,网络节点和计算节点之间的通信就此打通了。而图中的G、H正是描画这一通信。
正如前面所说,网络节点上也是存在一个br-tun
,它的作用同计算节点上的br-tun
如出一辙,都是为了在整个系统中构建一个统一的通信层而存在的。所以,这一部分的网络同计算节点上的网络的功能是一致的,因此,也就没有必要多说了。
网络节点上的br-int
也是起了交换机的作用,它通过I、J与br-tun
连接在一起。最终的要的是,在这个虚拟交换机上,还有其他两个重要的tap
设备M、O,它们分别同N、P相连,而N、P作为tap
设备则是分别归属两个namespace
router和dhcp,没错,正如这两个namespace
的名称所示,它们承担的就是router和dhcp的功能。这个router是由l3-agent
根据网络管理的需要而创建的,然后,该router就与特定一个子网绑定到一起,管理这个子网的路由功能。router实现路由功能,则是依靠在该namespace
中的iptables
实现的。dhcp则也是l3-agent
根据需要针对特定的子网创建的,在这个namespace
中,l3-agent
会启动一个dnsmasq
的进程,由它来实际掌管该子网的dhcp功能。由于这个两个namespace
都是针对特定的子网创建的,因而在现有的OpenStack
系统中,它们常常是成对出现的。
当数据从router中路由出来后,就会通过L、K传送到br-ex
这个虚拟网桥上,而br-ex
实际上是混杂模式加载在物理网卡上,实时接收着网络上的数据包。至此,我们的计算节点上的VM就可以与外部的网络进行自由的通信了。当然,前提是我们要给这个VM已经分配了float-ip
。
关于OpenStack Neutron的分析大致描述这些吧,后续的话会详细的写一些文章来详细介绍整个的详细流程和相关的实现方式。
]]>JUnit是由Erich Gamma和Kent Beck编写的一个开源的单元测试框架。它属于白盒测试,只要将待测类继承TestCase类,就可以利用JUnit的一系列机制进行便捷的自动测试了。
JUnit的设计精简,易学易用,但是功能却非常强大,这归因于它内部完善的代码结构。JUnit中深深渗透了扩展性优良的设计模式思想。JUnit提供的API既可以让您写出测试结果明确的可重用单元测试用例,也提供了单元测试用例成批运行的功能。在已经实现的框架中,用户可以选择三种方式来显示测试结果,并且 显示的方式本身也是可扩展的。
###JUnit系统架构
通过分析JUnit-3.8.1的源代码文件可以看到,JUnit的源码被分散在6个package中,这个6个package分别为:junit.awtui
、junit.swingui
、junit.textui
、junit.extensions
、junit.framework
、junit.runner
。具体的文件分布图如下:
通过对源码的分析,我们可以知道,其中junit.awtui
、junit.swingui
、junit.textui
这三个package是有关JUnit运行时的入口程序以及运行结果显示界面的,junit.runner中则包含了支持单元测试运行的一些基础类以及自己的类加载器,这四个package的运行逻辑对于JUnit的用户来说,都是透明无法感知的,用户只能感知到JUnit测试时的运行界面以及相关的运行结果的展示。junit.extensions、junit
.framework这两个package则是JUnit中的核心部分了,其中junit.framework 包含有编写一般JUnit单元测试类必须是用到的JUnit类;而junit.extensions则是对framework包在功能上的一些必要扩展以及为更多的功能扩展留下的接口。
通过对JUnit源码的package分析,可以得到以下三个package的类图:
1.junit.framework
的类图
2.junit.extensions
的类图
3.junit.runner
的类图
###JUnit工作流程解析
Junit的主要功能就是简化单元测试,由于其代码中使用了很多的设计模式,因此纯粹从源码的角度分析其工作流程相对比较复杂,因此此处使用一个简单的测试用例来描述Junit的工作流程,这样会更容易理解其流程时序。
这段代码为一个我们将要测试的类,其中只有一个简单的add()
方法,其功能是实现两个double类型的数值相加。
接下来我们将会对其编写对应的JUnit测试代码。
在上述测试代码中,我们的测试类TestCalculator
继承了TestCase
类(当然,我们还有一种编写方式不用继承TestCase
类,但是我们需要在每个测试方法上添加@Test的注解),在测试类中,我们编写了testAdd()
测试方法,同时一个必然失败的测试方法testFail()
,这样可以便于我们更全面的了解JUnit运行机制。
通过对上述测试代码的分析以及JUnit源码的分析,我们可以为此次的用例总结出以下的时序图,这张图为我们清楚的展示了JUnit实现单元测试的内部机制。
JUnit的完整生命周期分为3个阶段:初始化阶段、运行阶段和结果捕捉阶段。
1.初始化阶段:
通过分析源码,可以看到JUnit的入口点在junit.textui.TestRunner
的main
方法,在这个方法中,首先创建一个TestRunner
实例aTestRunner
,然后main
函数中主体工作函数为TestResult r = aTestRunner.start(args)
。此时TestRunner
实例存在并开始工作。接下来进入start()
方法中:
首先,对于参数的检查和解析。接下来通过Test suite= getTest(testCase)
将对testCase
持有的全限定名进行解析,并构造TestSuite
。进入getTest
方法中:
在这段代码中,TestSuite
实例开始被构造,而通过代码可以知道,构造方式分为:用户在测试类中通过声明Suite()
方法自定义TestSuite和JUnit自动判断并提取测试方法两种方式。接下来就进入TestSuite
类中构造方法public TestSuite(final Class theClass)
。
在这中间,可以可以看到测试用例由addTestMethod
方法全部添加到TestSuite
中,继续跟进addTestMethod
方法:
在这中间可以看到addTestMethod
的具体实现,主要的就是addTest
,其实这个方法只是简单封装了一个对全局的容器变量fTests
的添加值操作。最重要的还是那个createTest
方法,进入看看细节:
在这中间就可以看到各个测试用例方法通过反射机制:test= constructor.newInstance(new Object[]{name})
转化成一个个TestCase
实例。
至此,JUnit的初始化过程就结束了。
2.测试运行阶段:
在TestRunner
中的start()
方法中可以看到开始调用doRun()
方法开始执行测试了。
进入doRun()
方法中可以看到:
首先是利用createTestResult()
方法生成一个TestResult
实例,而createTestResult()
方法其实也就是new一个TestResult
实例,然后将junit.textui.TestRunner
的监听器fPrinter
加入到result 的监听器列表中。其中,fPrinter
是junit.textui.ResultPrinter
类的实例,该类提供了向控制台输出测试结果的一系列功能接口,输出的格式在类中定义。ResultPrinter
类实现了TestListener
接口,具体实现了addError
、addFailure
、endTest
和startTest
四个重要的方法,这种设计体现了Observer设计模式。而将ResultPrinter
对象加入到TestResult
对象的监听器列表中,因此实质上TestResult
对象可以有多个监听器显示测试结果。
接下来我们查看run方法,可以看到:
此处JUnit代码颇具说服力地说明了Composite模式的效力,run接口方法的抽象具有重大意义,它实现了客户代码与复杂对象容器结构的解耦,让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器。每次循环得到的节点test,都同result一起传递给runTest
方法,进行下一步更深入的运行。
在这样递归的运行之后, junit.framework.TestResult.run
相应的结果TestResult
实例中:
这中间实现了Protectable
接口的匿名类的实例,runProtected
则是实际把那个刚刚实现的匿名类实例中的runBare()
方法执行了。
在该方法中,最终的测试会传递给一个runTest
方法执行,注意此处的unTest
方法是无参的,注意与之前形似的方法区别。该方法中也出现了经典的setUp
方法和tearDown
方法,追溯代码可知它们的定义为空。用户可以覆盖两者,进行一些fixture的自定义和搭建。这中间其实就蕴含了模板模式的思想了。
其中,最为主要的还是runTest()
方法:
在这里,通过反射机制runMethod= getClass().getMethod(fName, null);
提取TestCase
中的方法,然后为每一个测试方法,创建一个方法对象unMethod
并调用runMethod.invoke(this, new Class[0]);
,至此,用户测试方法的代码才开始真正被运行起来。当然剩下就是些异常捕获和处理。
3.测试结果捕捉阶段:
运行已经完成了,现在就是看结果了,当一切都是OK,当然是没问题啦。
当出现错误了或者失败,我们就catch到相应的Error或者Fail,然后用addFailure
加入到监听器TestListener
中,当然为了区分不同,还可以用addError
添加。然后就可以由ResultPrinter
将结果输出了:
###主要设计模式
JUnit中间,使用的设计模式是比较多的,JUnit应该来说是一个学习设计模式的很好的范例。
在前面的JUnit工作流程分析的单元中,分析过程中,我们已经看到了组合模式(Composite Pattern):
其中的run接口方法的抽象实现了客户代码与复杂对象容器结构的解耦,让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器。
另外,TestCase
中runBare()
方法则是模板模式的典型范例:
这处则是充分体现了模板模式中将一些步骤延迟到子类中的思想。
而观察者模式则是体现在下方:TestResult
并不是一个记录测试结果的类,而是观察者模式中的主题角色。每个TestCase
运行的时候都是用TestResult
来运行的,这样才能通知每个观察者方法要开始运行了。在这里,观察者就是那些显示方式,比如Text,Swing,Awt那几种UI的显示类。
命令模式则是表现在编写测试用例时,JUnit只是一个测试用例的执行器和结果查看器,而与实际用例的编写没有关联,处于完全解耦的状态。
###参考资料
[1]《分析JUnit框架源代码》,何正华、徐晔,http://www.ibm.com/developerworks/cn/java/j-lo-junit-src/
[2]《JUnit源码分析》,匿名,http://blog.csdn.net/ai92/article/details/318318
[3]《HeadFirst设计模式》,Freeman,E.,O’Reilly Taiwan公司译,中国电力出版社
[4] JUnit-3.8.1源码
应用程序中使用日志功能能够方便的调试和跟踪应用程序任意时刻的行为和状态。在大规模的应用开发中尤其重要,毫不夸张的说,日志系统是不可或缺的重要组成部分。由于开源的广泛性,我们不需要再去重复造轮子,我们可以直接使用众多的开源日志系统来满足我们的开发需求。
目前使用最多的日志系统主要有slf4j、commons-logging这样的“门面”日志系统,也有log4j、logback这样的实际执行日志系统。slf4j、commons-logging这类系统它们本身并不是直接实现具体的日志打印逻辑,而只是作为一个代理系统,接收应用程序的日志打印请求,然后根据当前环境和配置,选取一个具体的日志实现系统,将真正的打印逻辑交给具体的日志实现系统,从而实现应用程序日志系统的“可插拔”,即可以通过配置或更换jar包来方便的更换底层日志实现系统,而不需要改变任何代码。
出于对日志系统的深入学习,我们这次对commons-logging进行源码分析。
我们对下载得到的commons-logging源码进行展开,很容得到commons-logging代码的主要代码结构。
其中的,部分代码文件已经作废,至于为什么没有删除掉,这就不得而知了。其中的LogSource.java文件已经作废,而其功能职责则由LogFactory.java代替。LogFactory.java实际只是一个实现接口,具体的实现则是由继承LogFactory.java的LogFactoryImpl.java来完成了。目录中众多其他实现文件则是继承自Log.java接口,对其实现了不同的功能,完成底层的对不同的实际执行日志系统的适配。主要是配有:AvalonLogger、Jdk13LumberjackLogger、Jdk14Logger、Log4JLogger、LogKitLogger、SimpleLog。还有一个ServletContextCleaner.java则是为了WebApp相关项目而准备,用于释放有关的内存。
在LogFactory中定义了一定的规则,从而根据当前的环境和配置取得特定的Log子类实例。
Commons Logging中默认实现的LogFactory(LogFactoryImpl类)查找具体Log实现类的逻辑如下:
1.查找在commons-logging.properties文件中是否定存在以org.apache.commons.logging.Log或org.apache.commons.logging.log(旧版本,不建议使用)为key定义的Log实现类,如果是,则使用该类。
2.否则,查找在系统属性中(-D方式启动参数)是否存在以org.apache.commons.logging.Log或org.apache.commons.logging.log(旧版本,不建议使用)为key定义的Log实现类,如果是,则使用该类。
3.否则,如果在classpath中存在Log4J的jar包,则使用Log4JLogger类。
4.否则,如果当前使用的JDK版本或等于1.4,则使用Jdk14Logger类。
5.否则,如果存在Lumberjack版本的Logging系统,则使用Jdk13LumberjackLogger类。
6.否则,如果可以正常初始化Commons Logging自身实现的SimpleLog实例,则使用该类。
7.最后,以上步骤都失败,则抛出LogConfigurationException。
我们在使用Commons-Logging的时候,一般都是通过LogFacotry获取Log实例的,然后调用Log接口中相应的方法,而这是一个典型的工厂设计模式。Commons-Logging的实现可以分成以下几个步骤:
1.LogFactory类初始化:
a.缓存加载LogFactory的ClassLoader(thisClassLoader字段),出于性能考虑。因为getClassLoader()方法以后会使用AccessController,因而缓存起来以提升性能。
b.初始化诊断流。读取系统属性org.apache.commons.logging.diagnostics.dest,若该属性的值为STDOUT、STDERR、文件名。则初始化诊断流字段(diagnosticStream),并初始化诊断消息的前缀(diagnosticPrefix),其格式为:“[LogFactory from ]”, 该前缀用于处理在同一个应用程序中可能会有多个ClassLoader加载LogFactory实例的问题。
c.如果配置了诊断流,则打印当前环境信息:java.ext.dir、java.class.path、ClassLoader以及ClassLoader层级关系信息。
d.初始化factories实例(Hashtable),用于缓存LogFactory(context-classloader –-< LogFactory instance)。如果系统属性org.apache.commons.logging.LogFactory.HashtableImpl存在,则使用该属性定义的Class作为factories Hashtable的实现类,否则,使用Common Logging实现的WeakHashtable。若初始化没有成功,则使用Hashtable类本身。使用WeakHashtable是为了处理在webapp中,当webapp被卸载是引起的内存泄露问题,即当webapp被卸载时,其ClassLoader的引用还存在,该ClassLoader不会被回收而引起内存泄露。因而当不支持WeakHashtable时,需要卸载webapp时,调用LogFactory.relase()方法。
e.最后,如果需要打印诊断信息,则打印“BOOTSTRAP COMPLETED”信息。
2.查找LogFactory类实现,并实例化:
当调用LogFactory.getLog()方法时,它首先会创建LogFactory实例(getFactory()),然后创建相应的Log实例。getFactory()方法不支持线程同步,因而多个线程可能会创建多个相同的LogFactory实例,由于创建多个LogFactory实例对系统并没有影响,因而可以不用实现同步机制。
a.获取context-classloader实例。
b.从factories Hashtable(缓存)中获取LogFactory实例。
c.读取commons-logging.properties配置文件(如果存在的话,如果存在多个,则可以定义priority属性值,取所有commons-logging.properties文件中priority数值最大的文件),如果设置use_tccl属性为false,则在类的加载过程中使用初始化cache的thisClassLoader字段,而不用context ClassLoader。
d.查找系统属性中是否存在org.apache.commons.logging.LogFactory值,若有,则使用该值作为LogFactory的实现类,并实例化该LogFactory实例。
e.使用service provider方法查找LogFactory的实现类,并实例化。对应Service ID是:META-INF/services/org.apache.commons.logging.LogFactory
f.查找commons-logging.properties文件中是否定义了LogFactory的实现类:org.apache.commons.logging.LogFactory,是则用该类实例化一个出LogFactory
g.否则,使用默认的LogFactory实现:LogFactoryImpl类。
h.缓存新创建的LogFactory实例,并将commons-logging.properties配置文件中所有的键值对加到LogFactory的属性集合中。
3.通过LogFactory实例查找Log实例(LogFactoryImpl实现):
使用LogFactory实例调用getInstance()方法取得Log实例。
a.如果缓存(instances字段,Hashtable)存在,则使用缓存中的值。
b.查找用户自定义的Log实例,即从先从commons-logging.properties配置文件中配置的org.apache.commons.logging.Log(org.apache.commons.logging.log,旧版本)类,若不存在,查找系统属性中配置的org.apache.commons.logging.Log(org.apache.commons.logging.log,旧版本)类。如果找到,实例化Log实例
c.遍历classesToDiscover数组,尝试创建该数组中定义的Log实例,并缓存Log类的Constructor实例,在下次创建Log实例是就不需要重新计算。在创建Log实例时,如果use_tccl属性设为false,则使用当前ClassLoader(加载当前LogFactory类的ClassLoader),否则尽量使用Context ClassLoader,一般来说Context ClassLoader和当前ClassLoader相同或者是当前ClassLoader的下层ClassLoader,然而在很多自定义ClassLoader系统中并没有设置正确的Context ClassLoader导致当前ClassLoader成了Context ClassLoader的下层,LogFactoryImpl默认处理这种情况,即使用当前ClassLoader。用户可以通过设置org.apache.commons.logging.Log.allowFlawedContext配置作为这个特性的开关。
d.如果Log类定义setLogFactory()方法,则调用该方法,将当前LogFactory实例传入。
e.将新创建的Log实例存入缓存中。
4.调用Log实例中相应的方法
通过对commons-logging的分析,可以发现其实现的思路相对来说还是比较简单的,源码的整体结构也非常清晰,代码的数量也不是很多,里面的注释也是十分的清晰明了。
]]>最近乘着开学的时候还没有什么事情,于是乎,开始学起了Python。Python作为脚本语言,最近一段时间真的是越来越火了,所以也有必要认真的学习一下的。毕竟多学一门语言也不是坏事,而且看上去,Python的学习门槛不是太高,只是在一些库上的学习和应用值得花些时间好好钻研一下。
不得不说Python由于去掉很多的数据类型,封装了很多的功能,加上那丰富的库,真的让开发变得很是容易,代码量更是大大的减少了。然而,并不是像很多其他人那样,总是推荐新手去学习Python,我个人认为如果只是玩玩编程而已,Python的确是个不错的选择,但是如果想要深入学习的话,还是拿C这类的语言来入门吧,虽然门槛要高一些,但是更能去理解程序的执行原理(当然,汇编之类的更是容易了解程序的执行原理,但大多数人还是愿意用稍微高级些的语言,毕竟我们不是机器人),而且以后学习其他的语言也是更容易一些入门。
废话不多说了,这些也就是顺便一提。
爬取人人网上的好友信息也就是看到别人有弄过,觉得也挺好玩的。之前从来没有使用开放平台的APIs
开发过的经验,于是也就是在人人上注册了开发者的账号,获取到accesskey
,然后拿着那刚开始入门的Python开始胡乱的写了起来。我的目的也是很简单的,就是写个简单的爬虫,爬点人人网上的好友信息玩玩。想来想去,也就好友之间的关系图看起来挺有意思的,于是找到相应的接口,胡乱的玩起来,而主要就是根据一个好友的id,然后搜索到其所有的好友id,然后是好友的好友的id,如此重复下去。理论上来说,这样一直爬下去的话,应该是可以爬完整个人人网上的id(那些僵尸账号是爬不到的,孤立在整个网络之外的孤点估计都没有办法爬到的),然后将好友写入文件或者数据库,有机会的话还打算用d3.js
。刚开始是用好友关系列表中获取到相应的好友id,当时只注意到这个API。今天突然发现还有个直接获取好友ID列表的API,很当然就用了这个接口。
目前的这个简单的爬虫很是粗糙,虽然暂时能爬取好友列表,但是暂时还是无法解决关系网中存在的回环问题,其实这个问题只要进行id去重就好,可是利用内存表去重的话,怕是到后来随着id表的规模的扩大导致内存吃不消,同时性能更是糟糕。其实,可以数据库来去重的,可惜暂时不打算使用数据库。利用文件存储也是可以的,但是怕到时候频繁的I/O造成性能下降。总之,还是等到后面再想办法吧。还有个就是万恶的人人网的访问请求限制,今天刚跑到1w的关系量就被禁止了。
多说也是无益啊,还是先贴上粗糙的代码吧!
1 | #name:renren-friend.py |
第一版的粗糙的代码暂时贴在这里吧,后面还是要花点时间继续改进,同时也要好好的学习Python的。
]]>在之前的公司里,项目组一直是用SVN来做相关的版本管理的,当然,我这种底层的开发小兵也不需要用到SVN的版本控制层面的功能的。我所能使用的,也就是提交代码,查看提交日志等一些一线开发人员常用的功能而已。当初记得,项目组的SE经常提出新的需求,同时,测试也在不停提出bug修改要求,所以总是把代码一份一份的拷贝到不同的文件夹中,然后对于不同文件夹的代码进行不同的代码功能修复或者开发,最后合并到最初的SVN源码文件夹中。不得不说,这是一种非常原始的方式,但是这是我当时能够最有效的代码控制的方式了,因为经过自己的实践证明,这种方式可以合理规避掉各种的代码覆盖冲突等问题。至今还对于用BeyondCompare一行行比对代码的事记忆犹新啊。
然而,随着了解Git的相关的一些知识后,蓦然发现,原来我使用过的那种原始代码控制方式,已经在Git中完美的实现了(唯一可能不满意就是Git中diff工具没有BeyondCompare那么强大吧,当年一直对于代码冲突导致的文件内容混乱非常反感)。看了不少的关于Git分支管理的布道文章后,加上自己的那套原始的代码控制方式,从而也对使用Git有效管理控制代码有了些自己的见解。
在我看来,我们clone完项目组的代码之后,此时的分支为master,那么我们应该一直保持此分支的干净整洁,不要在master分支上直接进行开发。我们应该在开始一项新的主功能的开发时,从master分支分离出一直develop分支,日常的开发应该在此分支之上。同时,根据的以后的附加功能的需要,我们应该考虑是从master分支分离分支开发还是从develop分支分离分支开发,对于bug的修复同样需要如此考虑。当我们开发完成该项主功能后,应该清理干净从develop分支上分离的各个分支,然后保证develop分支干净后,将其合入最新的master分支,最后将最新的master分支push到远程仓库中。另外,在整个开发过程中,对于master分支我们时常保持其最新程度,要时常更新master分支上的代码,这样可以便于我们同其他开发者的同步,也减小了覆盖别人代码的几率。
]]>「算法导论」中给出了「快速排序」以下的伪码实现,说实话,我并不是很喜欢这种写法,因为有时候这种方式很容易让人看晕了,还不如实际代码来的直接明了。
1 | PARTITION(A, p, r) |
接下来就是用Java实现了这种排序算法,当然,形式有些不同,或者说是有些优化或者简化吧,这种形式似乎是在严奶奶那本书中有记载。
1 | package com.test; |
代码是经过实际运行的,效果还是不错滴!
]]>今天才刚刚拿起来,直接忽略第一部分的所谓「基础知识」,其实是看到那么多非基础的知识一下在看不下去了。看了下「堆排序」的章节,顺便回忆了一下以前学习的「堆排序」的知识。
「堆」无外乎就是一个特殊的完全二叉树,其特殊性在于堆的每个子树的根节点的值为该子树中的最大值/最小值,对应的堆就定义为「大顶堆/小顶堆」。「堆排序」的时间复杂度为O(nlgn),这在排序算法中还是一个不错的时间复杂度的。接下来就是用代码实现了一下「堆排序」,这是不得不说下「算法导论」中的伪码还是不错的,至少比严奶奶的那本「数据结构」要强多了,但是这样的伪码有点不利于代码的实现,估计是太过于简要了吧。
我是用的Java实现,其实是找了网上的其他代码参照着书本的伪码改写的,其中还因为忘记了Java中没有引用这种东西的,导致调试代码调了好长时间。
1 | package com.test; |
车子自从寄回家后,一直也就没有拆封组装,前两天闲着没事才慢慢组装起来(PS:在这儿不得不抱怨下中通快递了,车子回来后也就看看有没有什么明显的伤痕,发现没什么事也就没管了,结果装车的时候才发现前轮的快拆轴给摔弯的不成样子,只好慢慢掰直了暂时先顶着。有了此次教训,以后寄车还是小心的为好了。),昨天乘着刚下过雨,天气荫凉的,同时也是在家闲的太过无聊了,骑着车出去转了一下。没有什么特别的感受,至少觉得安庆城还是挺小的,一个多小时就把老城区给转了一圈了。
骑行途中去了母校一中逛了一下,发现还是没怎么变化,只是前面的教学楼不知是翻新还是重建中。同时跑到长江边看看,只觉得江水真的不太干净啊,估计这几年的污染还是很严重啊。
「骑行路线」
「母校闲逛」
「江边一瞥」
]]>《Head First设计模式》
,所以有必要把自己学到的东西用blog的方式给记录下来,其中,大部分的源码和UML关系图均是来自于该书。在此,本系列的文章只是将学到的知识记录一下,必要的地方会加上个人的理解。##正文
在我们的开发过程中,难免会遇到一种情况:存在一个主要对象,其他的对象因为由于数据的关系,必须依赖于这个对象产生的数据,但这个对象的数据总是处于不断的话当中,因此,我们希望这个主要对象的数据一旦发生变化,那么就能够及时的通知到依赖于它的对象。而当我们取消了这种依赖之后,随之而来就是不用再去通知已经取消的对象。其实,这个很像现在的微信公众平台
,只要我们关注了某个公众账号后,公众账号就会不断的去推送最新的消息给关注者,而一旦取消关注后,这些推送也就中断了。在设计模式中,我们把这种行为称之为观察者模式(observer pattern)
。
对于观察者模式,我们有着这样的书面定义:
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
观察者有着如下的类图:
其实这么看的话,也是不清不楚的,这个只是给出了观察者模式的一种通用类图,而至于如何具体的运作,我们也是不甚了解。
以下就是以天气通知为例,利用具体代码具体说明整个观察者模式。
其中,Subject
为主题对象接口,也就是前面所说的数据变化的源头,其他对象都是它的观察者;Observer
为观察者接口;DisplayElement
为显示接口,这个由于每个观察者对于显示有着不同的要求,因此独立开辟这样一个接口。WeatherData
继承了Subject
接口,生成了具体的主题对象,而观察者真正观察到的数据,就是由该具体对象提供。CurrentConditonsDisplay
、ForecastDisplay
、StatisticsDisplay
、ThirdPartydDisplay
这几个则是真正的观察者,当主题对象的数据产生了变化之后,就会主动的一一通知到这些个观察者,然后由它们根据各自的显示需求显示相应的数据(由于显示要求的不同,因此它们也继承显示接口DisplayElement
)。
下面就让我们看看具体的运作代码。
1 | //观察对象接口 |
以上定义各个接口,以及其中需要继承的公用方法,下面就是各个具体对象的实现了。
1 | public class WeatherData implements Subject |
这个是主题对象的具体实现WeatherData
,它继承了Subject
接口,其中具体实现了各个方法,其中setMeasurements()
方法则是保证了在发生了变化时,第一时间能够将数据的变化通知到各个观察者。接下来,就是等着实现各个观察者了,这里只拿一个观察者CurrentConditonsDisplay
作为实现的案例。
1 | public class CurrentConditonsDisplay implements Observer, DisplayElement |
这段代码似乎没有什么好说道的,唯一的特殊的地方就是在构建方法中,我们调用Subject
对象,这在注册观察者时用到了,其实,在后期如果我们加上取消关注的时候,也是会用到这个对象的,所以从方便的角度考虑,我们在构造方法中调用了该对象。
剩下来就是个简单的测试代码了。
1 | public class WeatherStation() |
当我们理清了这些调用继承关系之后,豁然发现观察者模式其实挺简单的。而观察者模式的最重要的地方,就是在有一个主动数据源,多个被动读取数据的对象的情况下,让数据的操作变得单一简单,不会出现太多的对象操作数据的情况。
]]>