Nikita Salnikov Tarnovski是plumbr的高级开发者,也是一位应用性能调优的专家,他拥有多年的性能调优经验。近日,Tarnovski撰文谈到了如何通过异步Servlet来改进常见的Java
Web应用的性能问题。
众所周知,Servlet 3.0标准已经发布了很长一段时间,相较于之前的2.5版的标准,新标准增加了很多特性,比如说以注解形式配置Servlet、web.xml片段、异步处理支持、文件上传支持等。虽然说现在的很多Java Web项目并不会直接使用Servlet进行开发,而是通过如Spring MVC、Struts2等框架来实现,不过这些Java Web框架本质上还是基于传统的JSP与Servlet进行设计的,因此Servlet依然是最基础、最重要的标准和组件。在Servlet 3.0标准新增的诸多特性中,异步处理支持是令开发者最为关注的一个特性,本文就将详细对比传统的Servlet与异步Servlet在开发上、使用上、以及最终实现上的差别,分析异步Servlet为何会提升Java Web应用的性能。
本文主要介绍的是能够解决现代Web应用常见性能问题的一种性能优化技术。当今的应用已经不仅仅是被动地等待浏览器来发起请求,而是由应用自身发起通信。典型的示例有聊天应用、拍卖系统等等,实际情况是大多数时间与浏览器的连接都是空闲的,等待着某个事件来触发。
这种类型的应用自身存在着一个问题,特别是在高负载的情况下问题会变得更为严重。典型的症状有线程饥饿、影响用户交互等等。根据近一段时间的经验,我认为可以通过一种相对比较简单的方案来解决这个问题。在Servlet API 3.0实现成为主流后,解决方案就变得更加简单、标准化且优雅了。
在开始介绍解决方案前,我们应该更深入地理解问题的细节。还有什么比看源代码更直接的呢,下面就来看看下面这段代码:
@WebServlet(urlPatterns = "/BlockingServlet") public class BlockingServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { long start = System.currentTimeMillis(); Thread.sleep(2000); String name = Thread.currentThread().getName(); long duration = System.currentTimeMillis() - start; response.getWriter().printf("Thread %s completed the task in %d ms.", name, duration); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } |
上面这个Servlet主要完成以下事情:
请求到达,表示开始监控某些事件。
线程被阻塞,直到事件发生为止。
在接收到事件后,编辑响应然后将其发回给客户端。
为了简化,代码中将等待部分替换为一个Thread.sleep()调用。
现在,你可能会觉得这就是一个挺不错的Servlet。在很多情况下,你的理解都是正确的,上述代码并没有什么问题,不过当应用的负载变大后就不是这么回事了。
为了模拟负载,我通过JMeter创建了一个简单的
测试,我会启动2,000个线程,每个线程运行10次,每次都会向/BlockedServlet这个地址发出请求。将这个Servlet部署在Tomcat 7.0.42中然后运行测试,得到如下结果:
平均响应时间:19,324ms
最快响应时间:2,000ms
最慢响应时间:21,869ms
吞吐量:97个请求/秒
默认的Tomcat配置有200个
工作线程,此外再加上模拟的工作由2,000ms的睡眠时间来表示,这就能比较好地解释最快与最慢的响应时间了,每个线程都会睡眠2秒钟。再加上上下文切换的代价,因此97个请求/秒的吞吐量基本上是符合我们的预期的。
对于绝大多数的应用来说,这个吞吐量还算是可以接受的。重点来看看最慢的响应时间与平均响应时间,问题就变得有些严重了。经过20秒而不是期待的2秒才能得到响应显然会让用户感到非常不爽。 下面我们来看看另外一种实现,利用Servlet API 3.0的异步支持:
@WebServlet(asyncSupported = true, value = "/AsyncServlet") public class AsyncServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Work.add(request.startAsync()); } } public class Work implements ServletContextListener { private static final BlockingQueue queue = new LinkedBlockingQueue(); private volatile Thread thread; public static void add(AsyncContext c) { queue.add(c); } @Override public void contextInitialized(ServletContextEvent servletContextEvent) { thread = new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(2000); AsyncContext context; while ((context = queue.poll()) != null) { try { ServletResponse response = context.getResponse(); response.setContentType("text/plain"); PrintWriter out = response.getWriter(); out.printf("Thread %s completed the task", Thread.currentThread().getName()); out.flush(); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { context.complete(); } } } catch (InterruptedException e) { return; } } } }); thread.start(); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { thread.interrupt(); } } |
上面的代码看起来有点复杂,因此在开始分析这个解决方案的细节信息之前,我先来概述一下这个方案:速度上提升了75倍,吞吐量提升了20倍。看到这个结果,你肯定迫不及待地想知道这个示例是如何做到的吧。
这个Servlet本身是非常简单的。需要注意两点,首先是声明Servlet支持异步方法调用:
@WebServlet(asyncSupported = true, value = "/AsyncServlet")
其次,重要的部分实际上是隐藏在下面这行代码调用中的。
Work.add(request.startAsync());
整个请求处理都被委托给了Work类。请求上下文是通过AsyncContext实例来保存的,它持有容器提供的请求与响应对象。
现在来看看第2个,也是更加复杂的类,Work类实现了ServletContextListener接口。进来的请求会在该实现中排队等待通知,通知可能是上面提到的拍卖中的竞标价,或是所有请求都在等待的群组聊天中的下一条消息。
当通知到达时,我们这里依然是通过Thread.sleep()让线程睡眠2,000ms,队列中所有被阻塞的任务都是由一个工作线程来处理的,该线程负责编辑与发送响应。相对于阻塞成百上千个线程以等待外部通知,我们通过一种更加简单且干净的方式达成所愿,通过批处理在单独的线程中处理请求。
还是让结果来说话吧,测试配置与方才的示例一样,依然使用Tomcat 7.0.24的默认配置,测试结果如下所示:
平均响应时间:265ms
最快响应时间:6ms
最慢响应时间:2,058ms
吞吐量:1,965个请求/秒
虽然说这个示例很简单,不过对于实际项目来说通过这种方式依然能获得类似的结果。
在将所有的Servlet改写为异步Servlet前,请容许我多说几句。该解决方案非常适合于某些应用场景,比如说群组通知与拍卖价格通知等。不过,对于等待数据库查询完成的请求来说,这种方式就没有什么必要了。像往常一样,我必须得重申一下——请通过实验进行度量,而不是瞎猜。
对于那些不适合于这种解决方案的场景来说,我还是要说一下这种方式的好处。除了在吞吐量与延迟方面带来的显而易见的改进外,这种方式还可以在大负载的情况下优雅地避免可能出现的线程饥饿问题。
另一个重要的方面,这种异步处理请求的方式已经是标准化的了。它不依赖于你所使用的Servlet API 3.0,兼容于各种应用服务器,如Tomcat 7、JBoss 6或是Jetty 8等,在这些服务器上这种方式都可以正常使用。你不必再面对各种不同的Comet实现或是依赖于平台的解决方案了,比如说Weblogic FutureResponseServlet。
就如本文一开始所提的那样,现在的Java Web项目很少会直接使用Servlet API进行开发了,不过诸多的Web MVC框架都是基于Servlet与JSP标准实现的,那么在你的日常开发中,是否使用过出现多年的Servlet API 3.0,使用了它的哪些特性与API呢?
一.粗放式分析
1.存储过程的运行是否正常:编译成功,能运行,且有结果。
2.存储过程的参数调用正确:能运行成功。
3.存储过程的返回结果正确:查询,更新或删除的结果是否正确,且返回正常。
4.存储过程的性能是否达到要求:在可接受的时间内能运行完成并成功。
二.抽离式分析
1. 外模式:基于业务场景的分析
多种业务场景可以映射到一个程序中的等价类逻辑上(即内模式)。
所以,挑选比较特殊的业务场景,从结果表和程序逻辑层面去印证。
2. 概念模式:基于结果表的分析
结果表是对业务场景的概念化和集合化。从结果表数据的类型分析可以延伸出各种业务场景(即外模式)。
所以,从结果表数据的分类可以反推出对业务场景的覆盖率。
3. 内模式:基于程序实现的分析
程序内部实现的逻辑是各种业务场景的抽象,最终形成结果表(即概念模式)。
所以,对程序逻辑分支的分析可以评估对业务场景的抽象覆盖程度。
这里有两种方式在Jmeter中调用Selenium测试用例。可以使用 Jmeter
JUnit sampler 或者 BeanShell sampler,后一种方式包含了Selenium client 和
server,因此不需要单独启动Server。
方法一
将文件selenium-server-standalone-2.*.jar拷贝到JMeter类库安装目录%JMETER_HOME%/lib/下,手动启动Selenium server。
Jmeter JUnit sampler
将Selenium测试用例打包成.jar文件,并拷贝到%JMETER_HOME%/lib/junit/目录下,注意:
测试用例应该继承TestCase or SeleniumTestCase类,从而允许JMeter获取到该测试用例,且测试方法名称应该以
test开头。
在Jmeter中创建test group和JUnit sampler测试计划,并在JUnit sampler中选择测试用例的名称,选择测试方法并运行。当然还可以根据需求设置其他参数。
一个测试用例例子:
package com.example.tests; import com.thoughtworks.selenium.*; import org.junit.Before; import org.junit.Test; import org.junit.After; public class selenium extends SeleneseTestCase { @Before public void setUp() throws Exception { . . . } @Test public void testSelenium_test() throws Exception { . . . } @After public void tearDown() throws Exception { selenium.stop(); } } |
方法二
准备工作:
将文件selenium-server-standalone-2.*.jar拷贝到JMeter类库安装目录%JMETER_HOME%/lib/下,不需要启动Selenium server.
Jmeter JUnit sampler
将Selenium测试用例打包成.jar文件,并拷贝到%JMETER_HOME%/lib/junit/目录下,注意:测试用例应该继承TestCase or SeleniumTestCase类,从而允许JMeter获取到该测试用例,且测试方法名称应该以test开头。
在Jmeter中创建test group和JUnit sampler测试计划,并在JUnit sampler中选择测试用例的名称,选择测试方法并运行。当然还可以根据需求设置其他参数。
一个测试用例例子:
import junit.framework.TestCase; import org.openqa.selenium.*; import org.junit.Before; import org.junit.Test; import org.junit.After; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxProfile; public class selenium extends TestCase { WebDriver driver; @Before public void setUp() { FirefoxProfile profile = new FirefoxProfile(); . . . driver = new FirefoxDriver(profile); } @Test public void testSelenium_test() throws Exception { . . . } @After public void tearDown() { driver.quit(); } } |
要求:
JDK1.5以上(因为Junit4是用注解来实现的)
需要的包
spring-2.5.jar
junit-4.4.jar
spring-test.jar
package user; import static org.junit.Assert.fail; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.TransactionConfiguration; import com.sample.model.user.User; import com.sample.service.user.IUserService; /** 设置要加载的配置文件 */ @ContextConfiguration( locations={ "classpath:spring/persistenceContext.xml", "classpath:spring/aopContext.xml", "classpath:spring/daoContext.xml", "classpath:spring/serviceContext.xml" } ) /** 设置是否回滚数据 */ @TransactionConfiguration(defaultRollback = false) public class UserTest extends AbstractTransactionalJUnit4SpringContextTests{ /** 设置自动注入的属性 */ @Autowired private IUserService userService; @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { } @Test @Rollback(false) public void testSaveUser() { User user=new User(); user.setUsername("zhoujun"); user.setCreatetime(new Date()); userService.saveUser(user); } @Test public void testGetUserById() { User user=userService.getUserById("1"); System.out.println(user.getUsername()); System.out.println(user.getCreatetime()); } } |
有关Junit4中注解的说明如下:
@ContextConfiguration 用来指定加载的Spring配置文件的位置,会加载默认配置文件
例如下例会加载:classpath:/com/example/MyTest-context.xml文件
package com.example; @ContextConfiguration public class MyTest { // class body... } @ContextConfiguration 注解有以下两个常用的属性: locations:可以通过该属性手工指定 Spring 配置文件所在的位置,可以指定一个或多个 Spring 配置文件。如下所示: @ContextConfiguration(locations={“xx/yy/beans1.xml”,” xx/yy/beans2.xml”}) inheritLocations:是否要继承父测试用例类中的 Spring 配置文件,默认为 true。如下面的例子: @ContextConfiguration(locations={"base-context.xml"}) public class BaseTest { // ... } @ContextConfiguration(locations={"extended-context.xml"}) public class ExtendedTest extends BaseTest { // ... } |
如果 inheritLocations 设置为 false,则 ExtendedTest 仅会使用 extended-context.xml 配置文件,否则将使用 base-context.xml 和 extended-context.xml 这两个配置文件。
在使用所有注释前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境
Spring框架在org.springframework.test.annotation 包中提供了常用的Spring特定的注解集,如果你在Java5或以上版本开发,可以在测试中使用它。
@IfProfileValue
提示一下,注解测试只针对特定的测试环境。 如果配置的ProfileValueSource类返回对应的提供者的名称值, 这个测试就可以启动。这个注解可以应用到一个类或者单独的方法。
@IfProfileValue(name=”java.vendor”, value=”Sun Microsystems Inc.”)
public void testProcessWhichRunsOnlyOnSunJvm() {
// some logic that should run only on Java VMs from Sun Microsystems
}
同时@IfProfileValue可配置一个值列表 (使用OR 语义) 来在JUnit环境中获得TestNG的测试组支持。 看下面的例子:
@IfProfileValue(name=”test-groups”, values={”unit-tests”, “integration-tests”})
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
// some logic that should run only for unit and integration test groups
}
@ProfileValueSourceConfiguration
类级别注解用来指定当通过@IfProfileValue注解获取已配置的profile值时使用何种ProfileValueSource。 如果@ProfileValueSourceConfiguration没有在测试中声明,将默认使用SystemProfileValueSource。
@ProfileValueSourceConfiguration(CustomProfileValueSource.class)
public class CustomProfileValueSourceTests {
// class body…
}
@DirtiesContext
在测试方法上出现这个注解时,表明底层Spring容器在该方法的执行中被“污染”,从而必须在方法执行结束后重新创建(无论该测试是否通过)。
@DirtiesContext
public void testProcessWhichDirtiesAppCtx() {
// some logic that results in the Spring container being dirtied
}
@ExpectedException
表明被注解方法预期在执行中抛出一个异常。预期异常的类型在注解中给定。如果该异常的实例在测试方法执行中被抛出, 则测试通过。同样的如果该异常实例没有在测试方法执行时抛出,则测试失败。
@ExpectedException(SomeBusinessException.class)
public void testProcessRainyDayScenario() {
// some logic that should result in an Exception being thrown
}
@Timed
表明被注解的测试方法必须在规定的时间区间内执行完成(以毫秒记)。如果测试执行时间超过了规定的时间区间,测试就失败了。
注意该时间区间包括测试方法本身的执行,任何重复测试(参见 @Repeat),还有任何测试fixture的set up或tear down时间。
Spring的@Timed注解与JUnit 4的@Test(timeout=...)支持具有不同的语义。 特别地,鉴于JUnit 4处理测试执行超时(如通过在一个单独的线程中执行测试方法)的方式, 我们不可能在一个事务上下文中的测试方法上使用JUnit的@Test(timeout=...)配置。因此, 如果你想将一个测试方法配置成计时且具事务性的, 你就必须联合使用Spring的@Timed及@Transactional注解。 还值得注意的是@Test(timeout=...)只管测试方法本身执行的次数,如果超出的话立刻就会失败; 然而,@Timed关注的是测试执行的总时间(包括建立和销毁操作以及重复),并且不会令测试失败。
@Timed(millis=1000)
public void testProcessWithOneSecondTimeout() {
// some logic that should not take longer than 1 second to execute
}
@Repeat
表明被注解的测试方法必须重复执行。执行的次数在注解中声明。
注意重复执行范围包括包括测试方法本身的执行,以及任何测试fixture的set up或tear down。
@Repeat(10)
public void testProcessRepeatedly() {
// …
}
@Rollback
表明被注解方法的事务在完成后是否需要被回滚。 如果true,事务将被回滚,否则事务将被提交。 使用@Rollback接口来在类级别覆写配置的默认回滚标志。
@Rollback(false) public void testProcessWithoutRollback() { // … } @NotTransactional 出现该注解表明测试方法必须不在事务中执行。 @NotTransactional public void testProcessWithoutTransaction() { // … } Spring TestContext Framework还支持下面这些非特定于测试的注解,并且保持其语义不变。 @Autowired @Qualifier @Resource (javax.annotation)如果JSR-250可用 @PersistenceContext (javax.persistence)如果JPA可用 @PersistenceUnit (javax.persistence)如果JPA可用 @Required @Transactional @TestExecutionListeners |
定义类级别的元数据,TestExecutionListeners会使用TestContextManager进行注册。 通常,@TestExecutionListeners与@ContextConfiguration会搭配使用。
@ContextConfiguration @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) public class CustomTestExecutionListenerTests { // class body... } |
@TransactionConfiguration
为配置事务性测试定义了类级别的元数据。特别地,如果需要的PlatformTransactionManager不是“transactionManager”的话, 那么可以显式配置驱动事务的PlatformTransactionManager的bean名字。此外, 可以将defaultRollback标志改为false。通常, @TransactionConfiguration与@ContextConfiguration搭配使用。
@ContextConfiguration @TransactionConfiguration(transactionManager="txMgr", defaultRollback=false) public class CustomConfiguredTransactionalTests { // class body... } @BeforeTransaction 表明被注解的public void方法应该在测试方法的事务开始之前执行, 该事务是通过@Transactional注解来配置的。 @BeforeTransaction public void beforeTransaction() { // logic to be executed before a transaction is started } @AfterTransaction 表明被注解的public void方法应该在测试方法的事务结束之后执行, 该事务是通过@Transactional注解来配置的。 @AfterTransaction public void afterTransaction() { // logic to be executed after a transaction has ended } |
1.什么是库
在windows平台和linux平台下都大量存在着库。
本质上来说库是一种可执行代码的二进制形式,可以被
操作系统载入内存执行。
由于windows和linux的本质不同,因此二者库的二进制是不兼容的。
本文仅限于介绍linux下的库。
2.库的种类
linux下的库有两种:静态库和共享库(动态库)。
二者的不同点在于代码被载入的时刻不同。
静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。
共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。
3.库存在的意义
库是别人写好的现有的,成熟的,可以复用的代码,你可以使用但要记得遵守许可协议。
现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
4.库文件是如何产生的
在linux下,静态库的后缀是.a,它的产生分两步:
Step 1.由源文件编译生成一堆.o,每个.o里都包含这个编译单元的符号表。
Step 2.ar命令将很多.o转换成.a,成为静态库。
动态库的后缀是.so,它由gcc加特定参数编译产生。
例如:
$ gcc -fPIC -c *.c
$ gcc -shared -Wl,-soname, libfoo.so.1 -o libfoo.so.1.0 *.
5.库文件是如何命名的,有没有什么规范
在linux下,库文件一般放在/usr/lib 下,
静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称
动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号。
6.如何知道一个可执行程序依赖哪些库
ldd命令可以查看一个可执行程序依赖的共享库,
例如# ldd /bin/lnlibc.so.6
=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
=> /lib/ld- linux.so.2 (0×40000000)
可以看到ln命令依赖于libc库和ld-linux库
7.可执行程序在执行的时候如何定位共享库文件
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径
此时就需要系统动态载入器(dynamic linker/loader)。
对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的 DT_RPATH段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目录找到库文件后将其载入内存。第5步:由.o文件创建动态库文件
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc来创建动态库。
在系统提示符下键入以下命令得到动态库文件libmyhello.so。
# gcc -shared -fPCI -o libmyhello.so hello.o
#
我们照样使用ls命令看看动态库文件是否生成。
# ls
hello.c hello.h hello.o libmyhello.so main.c
#
第6步:在程序中使用动态库
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。我们先运行gcc命令生成目标文件,再运行它看看结果。
# gcc -o hello main.c -L. -l myhello
# ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shared object
file: No such file or directory
#
哦!出错了。快看看错误提示,原来是找不到动态库文件libmyhello.so。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件libmyhello.so复制到目录/usr/lib中,再试试。
# mv libmyhello.so /usr/lib
# ./hello
./hello: error while loading shared libraries: /usr/lib/libhello.so: cannot restore segment
prot after reloc: Permission denied
由于SELinux引起,
# chcon -t texrel_shlib_t /usr/lib/libhello.so
# ./hello
Hello everyone!
#
成功了。这也进一步说明了动态库在程序运行时是需要的。
我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的gcc命令完全一样,那当静态库和动态库同名时,gcc命令会使用哪个库文件呢?抱着对问题必究到底的心情,来试试看。
先删除 除.c和.h外的 所有文件,恢复成我们刚刚编辑完举例程序状态。
# rm -f hello hello.o /usr/lib/libmyhello.so
# ls
hello.c hello.h main.c
#
在来创建静态库文件libmyhello.a和动态库文件libmyhello.so。
# gcc -c hello.c
# ar cr libmyhello.a hello.o
# gcc -shared -fPCI -o libmyhello.so hello.o
# ls
hello.c hello.h hello.o libmyhello.a libmyhello.so main.c
#
通过上述最后一条ls命令,可以发现静态库文件libmyhello.a和动态库文件libmyhello.so都已经生成,并都在当前目录中。然后,我们运行gcc命令来使用函数库myhello生成目标文件hello,并运行程序hello。
# gcc -o hello main.c -L. -lmyhello
# ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shared object
file: No such file or directory
#
从程序hello运行的结果中很容易知道,当静态库和动态库同名时, gcc命令将优先使用动态库。
DB2分区
数据库备份映象文件是通过联机备份产生的,在DB2分区数据库恢复操作结束后,数据库将处于前滚暂挂状态,必须通过前滚操作前滚归档日志,以取消前滚暂挂状态,使数据库最终可用。
对DB2分区数据库的前滚操作:
由于新数据库 SAMPNEW 的日志路径下不包含源 SAMPLE 数据库日志路径下的归档日志文件,所以在前滚操作之前,需要将 SAMPLE 的归档日志文件复制到一个特定的路径下,然后在发出前滚命令时,使用 OVERFLOW 选项来指定该路径,以替代 SAMPNEW 的数据恢复日志路径来提供前滚操作要使用的归档日志文件。
为查找源 SAMPLE 数据库归档日志存放的路径,可利用如下命令:
db2_all "db2 get db cfg for sample" | grep "Path" Path to log files = /home/db2inst1/db2inst1/NODE0000/SQL00001/SQLOGDIR/ Path to log files = /home/db2inst1/db2inst1/NODE0001/SQL00001/SQLOGDIR/ Path to log files = /home/db2inst1/db2inst1/NODE0002/SQL00001/SQLOGDIR/ Path to log files = /home/db2inst1/db2inst1/NODE0003/SQL00001/SQLOGDIR/ |
这里假设将各分区的归档日志文件对应复制到 /sampnew 下,因前滚命令仅可在编目分区上执行,所以在编目分区上前滚到日硬盘数据恢复志文件尾并结束前滚状态的命令应写为:
db2 "rollforward db sampnew to end of logs and complete overflow log path (/sampnew/NODE0000/SQL00001/SQLOGDIR, /sampnew/NODE0001/SQL00001/SQLOGDIR on dbpartitionnum 1, /sampnew/NODE0002/SQL00001/SQLOGDIR on dbpartitionnum 2, /sampnew/NODE0003/SQL00001/SQLOGDIR on dbpartitionnum 3, )" |
注:对于0号分区,在 OVERFLOW 选项中不能使用“ON DBPARTITIONNUM 0”的子句,否则会遇到:
SQL0104N An unexpected token "on" was found following "<identifier>". Expected tokens may include: ")". SQLSTATE=42601的报错,表明命令语法不正确。
组合和继承都允许在新的类中放置子对象,组合是显示的这样做,而继承则是隐式的做。
组合技术通常用于想在新类中使用现有类的功能而非他的接口。在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是新类所定义的接口,而非所嵌入对象的接口。为了取得这样效果,需要在新类中嵌入一个现有类的parivate对象。
有时,允许类的用户直接访问新类中的组合成分是极具意义的;也就是说,将成员对象声明为public。如果成员对象自身都隐藏了具体实现,那么这种做法是安全的。当用户能够了解到你正在组装一组部件,会使得端口更加易于理解。Car对象就是一个很好的例子: