??xml version="1.0" encoding="utf-8" standalone="yes"?>
无论是什么样的解x案,一定要牢记我们要解决的问题是什么,切不能将解决Ҏ当做问题本n。具体到q程改进Q不是何种方式的改q,它们所要解决的问题永远只有一个:~短从品想法到可用软g之间的时间周?/strong>。自动化发布正是如此Q如果Y件发布只做一ơ,我们说根本不需要自动化Q但如果三次以上Q那么Y件开发的黄金法则DRY必遵守,让时间真正用到开发当中去?/span>
二?/strong>与发布相关的问题
所谓自动化只不q是原先手工做的工作谦让给机器做,所以自动化之前一定要先清楚与发布相关的问题有哪些Q即使不自动化,q些工作也一个也不能:
三?/strong>我们的方?/span>
李安的少q?/span>Pi正在狂刷房Q我们的自动化发布方案也要跟上潮:Puppet+CI我们的少q?/span>Pi?/span>
Ø 使用CI自动化打包,q踪每个发布包的SVN版本Q?/span>
Ø 使用Puppet理发布包、目标机器环境、应用程序配|信息以及应用程序线上生命周期;
Ø 使用伽利略系l提供应用程序的命名服务和进行流量切换?/span>
现在应用E序的发布需要两步:CI一键打包?/span>puppet指定应用E序版本SVN提交?/span>
四?/strong>具体Ҏ
具体Ҏ也就是如何解决与发布相关八个问题的过E?/span>
1. 如何安装、升U和卸蝲应用E序
我们使用操作pȝ原生包管理系l来安装、升U和卸蝲应用E序Q我们的应用E序打出RPM二进制包。免安装Q所有机器自带,l色的,有机的?/span>
打包Q?/span>rpm -ba ./team_member-1.spec
安装Q?/span>rpm –ivh team_ member-2.0.1-48.x86_64.rpm
升Q?/span>rpm –U team_ member-2.0.1-49.x86_64.rpm
卸蝲Q?/span>rpm –e team_ member-2.0.1-48.x86_64.rpm
E序升前要停旧版本服务怎么办?旧版本数据要做处理怎么办?RPM已经帮我们料理好q一切,只要写出spec文gQ剩下的交给我们。尽情的插入吧:
2. 如何理目标机器环境和应用配|信?/span>
应用E序已经打好rpm包了Q但q还不够Q应用程序发布到哪台机器上?应用E序对目标机器有什么要求?发布旉要修改哪些配|和参数Q实际发布如何执行,N需要登陆到每台目标机器q行rpm命o吗?
我们使用Puppet来搞定这一切,Puppet是现在应用第一?/span>devops工具Q它通过master/agent的工作模式管理机器。我们通过声明来控制我们的机器辑ֈ目标状态。同Ӟ所?/span>puppet文g全部?/span>SVN里,所有对机器的修改全?/span>codereview和可审计?/span>
如何理应用E序发布到哪台机器上Q在回答q个问题前我们必d应用E序在线上的生命周期再进行一ơ封装?/span>
应用E序TeamMember被我们封装成一?/span>puppet moduleQ配|文件和参数被封装在对应templates?/span>files里,每次发布前都要修攚w|文件和传递不同的参数Q?/span>out了吧Q?/span>puppet帮你传参搞定:
Teammember.conf文g内容Q?/span>
装完成后的效果是这LQ?/span>
最后在理部v?/span>site.pp文g里声明一下,应用E序TeamMember?/span>2146版本p自动部v?/span>10.128.34.141.test.back.shequq台机器上了Q我们后l的工作也就是维护这?/span>site.pp文g了,所有应用程序的部v信息都在SVN被集中管理v来:
登陆到每台目标机器运?/span>rpm命oQ?/span>No!现在TeamMember已经被封装,我们修改完毕site.ppq提交后Q?/span>puppetp动执行命令了Q要不怎么说是自动化呢。(现在puppet默认?/span>agent每半时同步一ơ,但同时支持马上触发执行)?/span>
3. 如何q踪每次发布?/span>SVN版本?/span>
我们使用CIq行应用E序的打包,?/span>build号作为包命名的一部分Q?/span>
4. 如何在发布过E中切换量
q是另外一个很大的话题Q参见伽利略计划?/span>
五?/strong>下一步工?/span>
使用CI环境的自动化部|与自动化测试串联v来,搭徏h个研发流E自动化q_Q?/span>
六?/strong>结
没有银弹Q自动化所做的只是之前手工工作交l计机完成Q需要做的工作一个都不能,此外Q我们还要多做一些封装或脚本工作Q但是,当我们需要重复做q些事情的时候,价值就出现了。我们的目标永远是羃短从产品x到可用Y件之间的旉周期。让旉真正用到开发当中去?/span>
http://localhost:80/content/section/news.json展现资源信息的JSON描述Q?br />
http://localhost:80/content/section/news.pdf展现|页的PDF?br />
服务器端的无状?/strong>
通过JS获取当前用户信息q缓存在客户端?br />
2?strong style="font-weight: bold; ">pȝ分层
pȝ分ؓ四层QJS、Servlet、Domain Model和JCR?br />
因ؓJCR提供了一套节Ҏ型,所以Domain Model是在节点模型上的行ؓ增强Q例如所有对囄节点的操作我们封装在Asset领域模型里?br />
3、程序划?/strong>
E序分ؓ两个大的模块Q?strong style="font-weight: bold; ">Migration和Bundles。ؓ什么叫BundlesQ因为Sling使用了OSGI框架Felix?br />
Migration负责导入客户的遗留数据到新系l。之前客LCMSq行已有10多年的时_U篏有大量数据。主要是各种cd的报表和囄?br />
Bundles实现pȝ功能。主要包括了定义报表模板、定义报表各U所见即所得的展现lg、实现对囄的管理、搜索(包括Z囄的可视化搜烦Q和其他七七八八?br />
三、测试实?br />
1、Migration的测?br />
自动化测?/strong>
对MigrationQ我们采用了TDD的方式?br />
输入是客户实际提供的xml文gQ输出是JCR里的节点。测试环境的搭徏主要是在本地建立起Jackrabbit实例。我们的工作方式是这P每天早上领到一张migration故事卡,然后先写一个xml到jcr节点的集成测试描q出该类型报表的功能需求,接下来就是让q个试通过。经q开始阶D늚熟悉q程Q我们的速度保持在一对Pair一天一U报表类型的导入?br />
手工试
xml文g内容正常解析q导入JCR只是W一步,W二步我们需要在Bundles里ؓ该类型的报表~写模板使之正常展现。由于涉及到报表样式Q这个测试我们采用手工测试,q个工作也是QA工作的重要一部分?br />
在最开始的开发中Q我们没有导入所有报表数据进行测试。这带来了问题,׃客户遗留数据跨越10多年Q各U数据Ş式都可能存在Q特别是04q以前数据,lUI带来了很大挑战)Q而最开始的开发中Q我们只是用了部分数据Q?9?0q数据)q行试Q这个导致了建立UAT环境时程序的很多q工以及QA的测试压力?br />
2、Bundles的测?br />
自动化测?/strong>
寚w域模型,我们采用了TDD的方式进行单元测试;在本地Jackrabbit实例里徏立数据,领域模型装数据试行ؓ?br />
对servletQ我们采用了TDD的方式进行集成测试(同时试了servlet和领域模型)Q在试中对request和responseq行mockQ?br />
对JSQ我们用数据驱动的selenium功能试q行覆盖?/p>
我们最后的自动化测试结构:
手工试
手工试内容主要是功能测试?br />
自动化测试hg的部分我们采用手工测试,q部分内容包括报表模板,相对独立Q内容不多,一ơ测试处处通过Q?br />
自动化测试成本高的部分我们采用手工测试,q部分内容包括报表展现组件的~辑功能Q因为采用了Ext JSQ所以自动化试困难Q?br />
无法自动化的试Q报表在U生成PDFQ报表样式,WEBDEV扚w上传囄{?
我们与QA的约定:
每完成一个用h事,我们会与QA、BA一起mini showcaseQ?br />
QA验收完成后编写功能测试用例;
我们对QA~写的功能测试用例进行自动化Q共同维护一个功能测试列表;
对于不能自动化或自动化hg高的试用例QAl箋使用手工试?br />
四、我们感受到的自动化试价?/strong>
1、通过自动化功能测试,我们使得需求对客户可视化;
2、QA的回归测试成本降低,管目前频繁的向客户实际使用环境部vQ但QA每次部v只需要做一些简单的冒烟试Q?br />
3、测试即需求,q点在TDD的开发过E中感觉非常明显Q今天开发的目的是使这个测试通过Q避免了频繁部v到应用中q行试Q最快的甉|不是速度最快的甉|Q而是中间停的楼层最的甉|Q?br />
4、与持箋集成一P及时反馈。这点在q行JS代码~写Ӟ心理上都非常依赖于selenium试Q对于没有测试覆盖的地方Q没有安全感Q?br />
5、够的单元、集成测试保证了频繁重构Q没有h愿意引入BUGQ没有够的试Q没人愿意重构;
6、测试即文档Q良好的试和命名,使得新加入的成员非常Ҏ理解当前代码的功能?br />
五、思考和讨论
1、自动化功能试做到多少才合适?
当然是越多越合适,问题在于自动化功能测试成本要大大高于单元试和集成测试,q些成本反映在测试环境的搭徏、数据的准备Q需要准备其他很多关联数据例如用户信息和权限信息、自动化功能试的运行时间长、稳定性(随机成功/p|Q、编写等{,需要权衡成本与收益?br />
个h认ؓQ自动化功能试需要考虑的着重点包括Q页面是否包含大量功能交互性JSQ与展现性JS相对Q?当前功能是否与其他功能共享一些代码?即独立性,独立性越低越需要着重覆盖(q里又涉及到另外一个问题,即从各个模块里重构出q代码是否L合适?Q。QA每次冒烟试时是否需要重复回归(重复回归ơ数多Q自动化有价|Q经常失败的试一定比不失败的试价值更高?或者从未失败过的测试就没有价|
2、单元测试?功能单元试Q?/strong>
TDD的测试粒度多大才合适?从我个h而言Q几乎天然的反对mockQؓ了满x试覆盖率的追求,强制两个联pd紧密的类分开Q做出各式各样mockQ这真让人气馁;stub也不是什么好东西Q在一个曾l的spring目里,在测试目录里Q一堆一堆的试xml配置文g让h有种呕吐的感觉。那做集成试吧,两个cdpd好,那么整体测试它们的输入和输出,看v来很不错Q功能实CQ测试也没多写,也不用准备太多其他东西,但是打开实现代码Q你发现那个丑陋,到处是复制和_脓Q是啊,不管黑猫白猫抓住老鼠是好猫Q只x了当前结果,完全失去了TDD驱动单设计的好处?br />
3、测试的重点是测试用例的设计Q它反映出对需求的理解
那么l论很明显Q我们如果连需求就没有理解Q如何进行实玎ͼ所以测试驱动是很自然的?/p>
]]>
cacheManager负责对ehcacheq行理Q初始化、启动、停止?br />
resourceCacheBackend负责实际执行~存操作Qput 、get、remove?br />
resourceCache实现h业务语义的业务应用层面的~存操作Q内部调用resourceCacheBackend操作?br />
现在采用memcached?br />
关于客户端,采用文初装的客LQ地址?a mce_>http://code.google.com/p/memcache-client-forjava/?br />
使用spring的FactoryBeanq行二次装。同理:
memcachedManager负责对memcachedq行理Q初始化、启动、停止?br />
代码Q?/p>
配置Q?/p>
resourceCacheBackend负责实际执行~存操作Qput 、get、remove?br /> 代码Q?/p>
配置Q?/p>
二?Session失效的处?/strong>
采用memcached作ؓhttpsession的存储,q不直接保存httpsession对象Q自定义SessionMapQSessionMap直接l承HashMapQ保存SessionMap?br />
会话胶粘Q未p|转发的情况下没必要在memcached保存的SessionMap和httpsession之间复制来复制去Q眉来眼厅R?br />
利用memcached计数器保存在Uh数?br />
pȝ权限采用了acegiQ在acegi的拦截器N配置snaFilter
注意需要配|在W一个?br />
snaFilter的职责:
1?没有HttpSessionӞ创徏HttpSession;
2?创徏Cookie保存HttpSession id;
3?如果Cookie保存的HttpSession id与当前HttpSession id一_说明是正常请求;
4?如果Cookie保存的HttpSession id与当前HttpSession id不一_说明是失败{发;p|转发的处理:
4.1、根据Cookie保存的HttpSession id从memcached获取SessionMapQ?br />
4.2、SessionMap属性复制到当前HttpSessionQ?br />
4.3、memcached删除SessionMap?br />
5?判断当前hurl是否是登出urlQ是则删除SessionMapQ在Uh数减1.
代码Q?/p>
和缓存类|采用集中式的文g服务。对于linuxQ采用nfs。参考文?a mce_>http://linux.vbird.org/linux_server/0330nfs.php#What_NFS_perm。关键在于对权限的分配?br /> 应用E序本n不用修改?/p>
?/span>httpsession的处?/span>
?/span>httpsession的处理最为重要,因ؓ?/span>WEBE序而言Q?/span>httpsession无疑是最重要的全局资源Q它需要被多个web服务器所׃n?/span>
无共享的集群架构Q?/span>SNAQ,在这L集群中,每个节点具备完全相同的功能,q且不需要知道其他节点存在与否。每个节?/span>JVMq程不保持全局状态,才能够保?/span>n?/span>JVM节点的幂{性,那些所有涉及到全局状态的Q必L?/span>JVMq程之外Q例如用?/span>ID可以使用cookieQ?/span>session可以攑օ数据库(qƈ不是一个好的选择Q,文g可以攑֜׃n存储pȝ中?/span>
也就是说httpsession的信息需要被保存?/span>JVMq程之外Q例如分布式~存、数据库?/span>
q里是方案:
1、用会话cookie保存web服务器生的sessionid
下面Ҏwebh的过E分情况讨论该方案:
A、登?/span>
Ҏh?/span>url判断是否是登录请?/span>
在线人数保存?/span>memcached?/span>
B?nbsp;正常h
C?nbsp;p|转发
D?/span>d
Ҏh?/span>url判断是否是登?/span>
E、HttpSessionq期
?/span>hack memcachedQ?/span>HttpSessionListenerQ?/span>sessionDestroyed事g时根?/span>sessionid删除memcached里的sessionMapQ如果存在)
关于在线人数的统计:在线人数存储?/span>memcached里,在UhCsessionMapl定Q往memcached里增?/span>sessionMap时在Uh?/span>+1Q删除时-1.
实战LuceneQ第 1 部分: 初识 LuceneQ?a >http://www.ibm.com/developerworks/cn/java/j-lo-lucene1/
?/span>Lucene加速Web搜烦应用E序的开发:http://www.ibm.com/developerworks/cn/web/wa-lucene2/
一?nbsp;solr介绍
solr是基于Lucene Java搜烦库的企业U全文搜索引擎,目前是apache的一个项目。它的官方网址?a >http://lucene.apache.org/solr/ 。solr需要运行在一?/span>servlet 容器里,例如tomcat5.5?/span>solr?/span>lucene的上层提供了一个基?/span>HTTP/XML?/span>Web ServicesQ我们的应用需要通过q个服务?/span>solrq行交互?/span>
二?nbsp;solr安装和配|?/span>
关于solr的安装和配置Q这里也有两非常好的文档,作者同时也?/span> Lucene Java 目的提交h和发a人:
使用Apache Solr实现更加灵y的搜索:http://www.ibm.com/developerworks/cn/java/j-solr1/index.html
http://www.ibm.com/developerworks/cn/java/j-solr2/index.html
下面主要说说需要注意的地方?/span>
Solr的安装非常简单,下蝲solr?/span>zip包后解压~将dist目录下的war文g改名?/span>solr.war直接复制?/span>tomcat5.5?/span>webapps目录卛_。注意一定要讄solr的主位置。有三种Ҏ。我采用的是在tomcat里配|java:comp/env/solr/home的一个JNDI指向solr的主目录Qexample目录下)Q徏?tomcat55/conf/Catalina/localhost/solr.xml文g?/span>
观察q个指定的solrM|,里面存在两个文g夹:conf和data。其中conf里存放了对solr而言最为重要的两个配置文gschema.xml和solrconfig.xml。data则用于存攄引文件?/span>
schema.xml主要包括types?/span>fields和其他的一些缺省设|?/span>
solrconfig.xml用来配置Solr的一些系l属性,例如与烦引和查询处理有关的一些常见的配置选项Q以及缓存、扩展等{?/span>
上面的文档对q两个文件有比较详细的说明,非常Ҏ上手。注意到schema.xml里有一?/span>
的配|,q里?/span>url字段作ؓ索引文档的唯一标识W,非常重要?/span>
三?nbsp;加入中文分词
对全文检索而言Q中文分词非常的重要Q这里采用了qieqie庖丁分词Q非怸错:Q)。集成非常的ҎQ我下蝲的是2.0.4-alpha2版本Q其中它支持最多切分和按最大切分。创q一个中文TokenizerFactoryl承自solr的BaseTokenizerFactory?/span>
在schema.xml的字Dtext配置里加入该分词器?/span>
完成后重启tomcatQ即可在http://localhost:8080/solr/admin/analysis.jsp
体验到庖丁的中文分词。注意要paoding-analysis.jar复制到solr的lib下,注意修改jar包里字典的home?/span>
四?nbsp;与自己应用进行集?/span>
Solr安装完毕Q现在可以将自己的应用与solr集成。其实过E非常的单,应用增加数据-->Ҏ配置的字D|建add的xml文档-->post至solr/update?/span>
应用删除数据àҎ配置的烦引文档唯一标识W构建delete的xml文档-->post至solr/update?/span>
索数?/span>à构徏查询xml?gt;get?solr/select/-->对solrq回的xmlq行处理-->面展现?/span>
具体的xml格式可以在solr|站扑ֈ。另外就是solr支持高亮昄Q非常方ѝ?/span>
关于中文Qsolr内核支持UTF-8~码Q所以在tomcat里的server.xml需要进行配|?/span>
另外Q向solr Posth的时候需要{为utf-8~码。对solr q回的查询结果也需要进行一ơutf-8的{码。检索数据时Ҏ询的关键字也需要{码,然后?#8220;+”q接?/span>
2. 解压~?jbpm-3.0.zip ?'temp' 目录
3. 使用 eclipse, ?'temp\jbpm-3.0' 作ؓ an existing project into workspace 导入
配置q接 MySQL
1. ?'jbpm-3.0\lib' 目录?创徏 'mysql' 目录
2. ?mysql数据库驱?(mysql-connector-java-3.1.7-bin.jar) 拯?'mysql' 目录
3. ?mysql 中创Z个数据库Q数据库名字
5. 把两个配|文?(create.db.hibernate.properties, identity.db.xml) ?'hsqldb' 目录?拯?'mysql' 目录
6. 按下面所C编?'create.db.hibernate.properties' 文g: hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class=com.mysql.jdbc.Driver
hibernate.connection.url=jdbc:mysql://localhost:3306/
hibernate.connection.username=
hibernate.show_sql=true hibernate.query.substitutions=true 1, false 0
hibernate.c3p0.min_size=1 hibernate.c3p0.max_size=3
7. 另一个文?'identity.db.xml'不做改动
8. ?'jbpm-3.0' 根目? ~辑ANT的脚?'build.deploy.xml' 扑ֈ target name="create.db", 删除 db.start, db.stop 在这个目标块中将所有的'hsqldb' 替换?'mysql'
9. q行ANT ant create.db -buildfile build.deploy.xml q行完毕后就会发现mysql中多出很多表Q这是jbpm保持状态用?
创徏 jbpm.war 使其在tomcat中运?/STRONG>
默认的打war包时Q掉了一些库文g
1. ?eclipse? ~辑ant脚本 'build.deploy.xml' 在目标块 target name="build.webapp" 中在
<copy todir="build/jbpm.war.dir/WEB-INF/lib">
<fileset dir="build" includes="jbpm-webapp-${jbpm.version}.jar" />
<fileset dir="build" includes="jbpm*.jar" />
<fileset dir="lib/hibernate" includes="*.jar" />
<fileset dir="lib/bsh" includes="*.jar" />
3. 打开源文?JbpmSessionFactory.java, ?getInstance() Ҏ? 删除下面代码
InitialContext initialContext = new InitialContext(); Object o = initialContext.lookup(jndiName);
下面这?
instance = (JbpmSessionFactory) PortableRemoteObject.narrow
(o, JbpmSessionFactory.class);
替换?instance = (JbpmSessionFactory) PortableRemoteObject.narrow
(new JbpmSessionFactory(createConfiguration()), JbpmSessionFactory.class);
4.?createConfiguration(String configResource) Ҏ? 注释掉这D代?
String hibernatePropertiesResource = JbpmConfiguration.getString("jbpm.hibernate.properties");
if (hibernatePropertiesResource!=null) { Properties hibernateProperties =
new Properties();
try { hibernateProperties.load( ClassLoaderUtil.getStream(hibernatePropertiesResource) ); }
catch (IOException e) {
e.printStackTrace();
throw new RuntimeException
("couldn't load the hibernate properties from resource '"hibernatePropertiesResource"'", e);
}
log.debug("overriding hibernate properties with "+ hibernateProperties); configuration.setProperties(hibernateProperties);
}
同时加入下面的代?
configuration.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
configuration.setProperty("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
configuration.setProperty("hibernate.connection.url", "jdbc:mysql://localhost:3306/
configuration.setProperty("hibernate.connection.username", "
configuration.setProperty("hibernate.connection.password", "
configuration.setProperty("hibernate.connection.pool_size", "15");
5. q行脚本命o ant build ant build.webapp -buildfile build.deploy.xml
6. jbpm.war ?'jbpm-3.0\build' 下拷贝到 'tomcat.home\webapps'
7. 启动 tomcat
8. 打开览?'http://localhost:8080/jbpm'