1<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3<html xmlns="http://www.w3.org/1999/xhtml"> 4 <head> 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 6 <title>Example Processing Loop</title> 7 <link rel="stylesheet" href="gettingStarted.css" type="text/css" /> 8 <meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /> 9 <link rel="start" href="index.html" title="Getting Started with Replicated Berkeley DB Applications" /> 10 <link rel="up" href="fwrkmasterreplica.html" title="Chapter��4.��Replica versus Master Processes" /> 11 <link rel="prev" href="processingloop.html" title="Processing Loop" /> 12 <link rel="next" href="addfeatures.html" title="Chapter��5.��Additional Features" /> 13 </head> 14 <body> 15 <div class="navheader"> 16 <table width="100%" summary="Navigation header"> 17 <tr> 18 <th colspan="3" align="center">Example Processing Loop</th> 19 </tr> 20 <tr> 21 <td width="20%" align="left"><a accesskey="p" href="processingloop.html">Prev</a>��</td> 22 <th width="60%" align="center">Chapter��4.��Replica versus Master Processes</th> 23 <td width="20%" align="right">��<a accesskey="n" href="addfeatures.html">Next</a></td> 24 </tr> 25 </table> 26 <hr /> 27 </div> 28 <div class="sect1" lang="en" xml:lang="en"> 29 <div class="titlepage"> 30 <div> 31 <div> 32 <h2 class="title" style="clear: both"><a id="exampledoloop"></a>Example Processing Loop</h2> 33 </div> 34 </div> 35 </div> 36 <div class="toc"> 37 <dl> 38 <dt> 39 <span class="sect2"> 40 <a href="exampledoloop.html#runningit">Running It</a> 41 </span> 42 </dt> 43 </dl> 44 </div> 45 <p> 46 In this section we take the example 47 processing loop that we presented in the 48 previous section and we flesh it out to 49 provide a more complete example. We do this 50 by updating the 51 <code class="function">doloop()</code> 52 function that our original transaction 53 application used 54 55 56 <span>(see <a class="xref" href="simpleprogramlisting.html#doloop_java" title="Method: SimpleTxn.doloop()">Method: SimpleTxn.doloop()</a>)</span> 57 to fully support our replicated application. 58 </p> 59 <p> 60 In the following example code, code that we 61 add to the original example is presented in 62 <strong class="userinput"><code>bold</code></strong>. 63 </p> 64 <p> 65 To begin, we must implement a way to track whether 66 our application is running as a master or a client. 67 There are many ways to do this, but in this case 68 what we will do is extend 69 <code class="classname">com.sleepycat.db.Environment</code> 70 to carry the information. We do this by creating 71 the <code class="classname">RepQuoteEnvironment</code> 72 class. 73 </p> 74 <pre class="programlisting"> 75 <strong class="userinput"> 76 <code>package db.repquote; 77 78import com.sleepycat.db.DatabaseException; 79import com.sleepycat.db.Environment; 80import com.sleepycat.db.EnvironmentConfig; 81 82public class RepQuoteEnvironment extends Environment 83{ 84 private boolean isMaster; 85 86 public RepQuoteEnvironment(final java.io.File host, 87 EnvironmentConfig config) 88 throws DatabaseException, java.io.FileNotFoundException 89 { 90 super(host, config); 91 isMaster = false; 92 } 93 94 boolean getIsMaster() 95 { 96 return isMaster; 97 } 98 99 public void setIsMaster(boolean isMaster) 100 { 101 this.isMaster = isMaster; 102 } 103} </code> 104 </strong> 105 </pre> 106 <p> 107 Next, we go to <code class="filename">RepQuoteExampleGSG.java</code> and 108 we include the 109 <code class="classname">RepQuoteEnvironment</code> class 110 as well as the 111 <code class="classname">EventHandler</code> class. We then 112 cause our <code class="classname">RepQuoteExampleGSG</code> 113 class to implement 114 <code class="classname">EventHandler</code>. We also 115 change our environment handle to be an instance 116 of <code class="classname">RepQuoteEnvironment</code> 117 instead of <code class="classname">Environment</code>. 118 </p> 119 <p> 120 Note that we also import the 121 <code class="classname">com.sleepycat.db.ReplicationHandleDeadException</code> 122 class. We will discuss what that exception is 123 used for a little later in this example. 124 </p> 125 <pre class="programlisting">package db.repquote; 126 127import java.io.FileNotFoundException; 128import java.io.BufferedReader; 129import java.io.InputStreamReader; 130import java.io.IOException; 131import java.io.UnsupportedEncodingException; 132import java.lang.Thread; 133import java.lang.InterruptedException; 134 135import com.sleepycat.db.Cursor; 136import com.sleepycat.db.Database; 137import com.sleepycat.db.DatabaseConfig; 138import com.sleepycat.db.DatabaseEntry; 139import com.sleepycat.db.DatabaseException; 140import com.sleepycat.db.DatabaseType; 141import com.sleepycat.db.EnvironmentConfig; 142<strong class="userinput"><code>import com.sleepycat.db.EventHandler;</code></strong> 143import com.sleepycat.db.LockMode; 144import com.sleepycat.db.OperationStatus; 145<strong class="userinput"><code>import com.sleepycat.db.ReplicationHandleDeadException;</code></strong> 146 147import db.repquote.RepConfig; 148<strong class="userinput"><code>import db.repquote.RepQuoteEnvironment</code></strong> 149 150public class RepQuoteExampleGSG <strong class="userinput"><code> implements EventHandler</code></strong> 151{ 152 private RepConfig repConfig; 153 private <strong class="userinput"><code>RepQuoteEnvironment</code></strong> dbenv; </pre> 154 <p> 155 That done, we can skip the 156 157 158 <span><code class="methodname">main()</code> method and 159 our class constructor, because they do not change.</span> 160 161 Instead, we skip down to our 162 163 164 <span><code class="methodname">init()</code> method 165 where we take care of opening our environment and setting 166 the event handler. </span> 167</p> 168 <p> 169 To update our <code class="methodname">init()</code> method, we only need 170 to do a couple of things. First, we identify the current class as 171 the event handler. Then, when we open our environment, we 172 instantiate a <code class="classname">RepQuoteEnvironment</code> 173 class instead of an <code class="classname">Environment</code> 174 class. 175</p> 176 <pre class="programlisting"> public int init(RepConfig config) 177 throws DatabaseException 178 { 179 int ret = 0; 180 repConfig = config; 181 EnvironmentConfig envConfig = new EnvironmentConfig(); 182 envConfig.setErrorStream(System.err); 183 envConfig.setErrorPrefix(RepConfig.progname); 184 185 envConfig.setReplicationManagerLocalSite(repConfig.getThisHost()); 186 for (ReplicationHostAddress host = repConfig.getFirstOtherHost(); 187 host != null; host = repConfig.getNextOtherHost()) 188 { 189 envConfig.replicationManagerAddRemoteSite(host); 190 } 191 192 if (appConfig.totalSites > 0) 193 envConfig.setReplicationNumSites(repConfig.totalSites); 194 envConfig.setReplicationPriority(repConfig.priority); 195 196 envConfig.setReplicationManagerAckPolicy( 197 ReplicationManagerAckPolicy.ALL); 198 envConfig.setCacheSize(RepConfig.CACHESIZE); 199 envConfig.setTxnNoSync(true); 200 201 <strong class="userinput"><code>envConfig.setEventHandler(this);</code></strong> 202 203 envConfig.setAllowCreate(true); 204 envConfig.setRunRecovery(true); 205 envConfig.setThreaded(true); 206 envConfig.setInitializeReplication(true); 207 envConfig.setInitializeLocking(true); 208 envConfig.setInitializeLogging(true); 209 envConfig.setInitializeCache(true); 210 envConfig.setTransactional(true); 211 envConfig.setVerboseReplication(appConfig.verbose); 212 try { 213 dbenv = new <strong class="userinput"><code>RepQuoteEnvironment</code></strong>(repConfig.getHome(), envConfig); 214 } catch(FileNotFoundException e) { 215 System.err.println("FileNotFound exception: " + e.toString()); 216 System.err.println( 217 "Ensure that the environment directory is pre-created."); 218 ret = 1; 219 } 220 221 // start Replication Manager 222 dbenv.replicationManagerStart(3, repConfig.startPolicy); 223 return ret; 224 } </pre> 225 <p> 226 That done, we need to implement the methods 227 required for responding to replication events. 228 These methods are required because we are now implementing 229 <code class="classname">com.sleepycat.db.EventHandler</code>. While 230 we are required to provide an implementation for all of these 231 methods, for our simple application we are really only interested 232 in these because they allow us to track whether a replication 233 instance is a master. 234</p> 235 <pre class="programlisting"> public void handleRepClientEvent() 236 { 237 dbenv.setIsMaster(false); 238 } 239 240 public void handleRepMasterEvent() 241 { 242 dbenv.setIsMaster(true); 243 } 244 245 public void handleRepNewMasterEvent(int envId) 246 { 247 // Ignored for now 248 } 249 250 public void handleWriteFailedEvent(int errorCode) 251 { 252 // Ignored for now 253 } 254 255 public void handleRepStartupDoneEvent() 256 { 257 // Ignored for now 258 } 259 260 public void handleRepPermFailedEvent() 261 { 262 // Ignored for now 263 } 264 265 public void handleRepElectedEvent() 266 { 267 // Safely ignored for Replication Manager applications. 268 } 269 270 public void handlePanicEvent() 271 { 272 System.err.println("Panic encountered!"); 273 System.err.println("Shutting down."); 274 System.err.println("You should restart, running recovery."); 275 try { 276 terminate(); 277 } catch (DatabaseException dbe) { 278 System.err.println("Caught an exception during " + 279 "termination in handlePanicEvent: " + dbe.toString()); 280 } 281 System.exit(-1); 282 } </pre> 283 <p> 284 That done, we need to update our 285 <code class="function">doloop()</code> 286 287 288 <span>method.</span> 289</p> 290 <p> 291 We begin by updating our <code class="classname">DatabaseConfig</code> 292 instance to determine which options to use, depending on whether the 293 application is running as a master. 294 </p> 295 <pre class="programlisting"> public int doloop() 296 throws DatabaseException 297 { 298 Database db = null; 299 300 for (;;) 301 { 302 if (db == null) { 303 DatabaseConfig dbconf = new DatabaseConfig(); 304 dbconf.setType(DatabaseType.BTREE); 305 <strong class="userinput"><code>if (dbenv.getIsMaster()) { 306 dbconf.setAllowCreate(true); 307 }</code></strong> 308 dbconf.setTransactional(true); </pre> 309 <p> 310 When we open the database, we modify our error handling to 311 account for the case where the database does not yet exist. This can 312 happen if our code is running as a replica and the Replication Manager has not 313 yet had a chance to create the databases for us. Recall that replicas never 314 write to their own databases directly, and so they cannot 315 create databases on their own. 316</p> 317 <p> 318 If we detect that the database does not yet exist, we simply 319 close the database handle, sleep for a short period of time 320 and then continue processing. This gives the Replication Manager a chance to 321 create the database so that our replica can continue 322 operations. 323</p> 324 <pre class="programlisting"> try { 325 db = dbenv.openDatabase 326 (null, RepConfig.progname, null, dbconf); 327 } catch (java.io.FileNotFoundException e) { 328 <strong class="userinput"><code>System.err.println("no stock database available yet."); 329 if (db != null) { 330 db.close(true); 331 db = null; 332 } 333 try { 334 Thread.sleep(RepConfig.SLEEPTIME); 335 } catch (InterruptedException ie) {} 336 continue;</code></strong> 337 } 338 } </pre> 339 <p> 340 Next we modify our prompt, so that if the local process is running 341 as a replica, we can tell from the shell that the prompt is for a 342 read-only process. 343 </p> 344 <pre class="programlisting"> BufferedReader stdin = 345 new BufferedReader(new InputStreamReader(System.in)); 346 347 // listen for input, and add it to the database. 348 System.out.print("QUOTESERVER"); 349 <strong class="userinput"><code>if (!dbenv.getIsMaster()) 350 System.out.print("(read-only)"); 351 System.out.print("> ");</code></strong> 352 System.out.flush(); 353 String nextline = null; 354 try { 355 nextline = stdin.readLine(); 356 } catch (IOException ioe) { 357 System.err.println("Unable to get data from stdin"); 358 break; 359 } 360 String[] words = nextline.split("\\s"); </pre> 361 <p> 362 When we collect data from the prompt, there is a case that says 363 if no data is entered then show the entire stocks database. 364 This display is performed by our 365 <code class="function">print_stocks()</code> 366 367 <span>method</span> 368 (which has not 369 required a modification since we first introduced it in 370 <a class="xref" href="simpleprogramlisting.html#printstocks_c" title="Method: SimpleTxn.printStocks()"> 371 372 373 <span>Method: SimpleTxn.printStocks()</span> 374 </a>). 375 </p> 376 <p> 377 When we call 378 379 <span><code class="function">printStocks()</code>, </span> 380 we check for a dead replication handle. Dead 381 replication handles happen whenever a replication election 382 results in a previously committed transaction becoming 383 invalid. This is an error scenario caused by a new master having a 384 slightly older version of the data than the original 385 master and so all replicas must modify their database(s) to 386 reflect that of the new master. In this situation, some 387 number of previously committed transactions may have to be 388 unrolled. From the replica's perspective, the database 389 handles should all be closed and then opened again. 390 </p> 391 <pre class="programlisting"> // A blank line causes the DB to be dumped to stdout. 392 if (words.length == 0 || 393 (words.length == 1 && words[0].length() == 0)) { 394 try { 395 printStocks(db); 396 <strong class="userinput"><code>} catch (DeadlockException de) { 397 continue; 398 // Dead replication handles are caused by an election 399 // resulting in a previously committing read becoming 400 // invalid. Close the db handle and reopen. 401 } catch (ReplicationHandleDeadException rhde) { 402 db.close(true); // close no sync. 403 db = null; 404 continue;</code></strong> 405 } catch (DatabaseException e) { 406 System.err.println("Got db exception reading " + 407 "replication DB: " + e.toString()); 408 break; 409 } 410 continue; 411 } 412 413 if (words.length == 1 && 414 (words[0].compareToIgnoreCase("quit") == 0 || 415 words[0].compareToIgnoreCase("exit") == 0)) { 416 break; 417 } else if (words.length != 2) { 418 System.err.println("Format: TICKER VALUE"); 419 continue; 420 } </pre> 421 <p> 422 That done, we need to add a little error checking to our 423 command prompt to make sure the user is not attempting to 424 modify the database at a replica. Remember, replicas must never 425 modify their local databases on their own. This guards against 426 that happening due to user input at the prompt. 427 </p> 428 <pre class="programlisting"> <strong class="userinput"><code>if (!dbenv.getIsMaster()) { 429 System.err.println("Can't update client."); 430 continue; 431 }</code></strong> 432 433 DatabaseEntry key = new DatabaseEntry(words[0].getBytes()); 434 DatabaseEntry data = new DatabaseEntry(words[1].getBytes()); 435 436 db.put(null, key, data); 437 } 438 if (db != null) 439 db.close(true); 440 return 0; 441 } </pre> 442 <p> 443 With that completed, we are all done updating our application 444 for replication. 445 446 The only remaining 447 448 449 <span>method, <code class="function">printStocks()</code>,</span> 450 451 is unmodified from when we 452 originally introduced it. For details on that function, see 453 <a class="xref" href="simpleprogramlisting.html#printstocks_c" title="Method: SimpleTxn.printStocks()"> 454 455 456 <span>Method: SimpleTxn.printStocks()</span> 457 </a>. 458</p> 459 <div class="sect2" lang="en" xml:lang="en"> 460 <div class="titlepage"> 461 <div> 462 <div> 463 <h3 class="title"><a id="runningit"></a>Running It</h3> 464 </div> 465 </div> 466 </div> 467 <p> 468 To run our replicated application, we need to make 469 sure each participating environment has its own unique 470 home directory. We can do this by running 471 each site on a separate networked machine, but that 472 is not strictly necessary; multiple instances of this 473 code can run on the same machine provided the 474 environment home restriction is observed. 475 </p> 476 <p> 477 To run a process, make sure the environment home 478 exists and then start the process using the 479 <code class="literal">-h</code> option to specify that 480 directory. You must also use the <code class="literal">-l</code> 481 option to identify the local host and port that this 482 process will use to listen for replication messages, the 483 <code class="literal">-n</code> option to specify the number of sites 484 in the replication group, and 485 the <code class="literal">-r</code> option to identify the other 486 processes in the replication group. Finally, use the 487 <code class="literal">-p</code> option to specify a priority. 488 The process that you designate to have the highest priority will become 489 the master. 490 </p> 491 <pre class="programlisting">> mkdir env1 492> java db.repquote.RepQuoteExampleGSG -h env1 -n 2 -l localhost:8080 \ 493-r localhost:8081 -p 10 494No stock database yet available. 495No stock database yet available. </pre> 496 <p> 497 Now, start another process. This time, change the environment 498 home to something else, use the <code class="literal">-l</code> flag to at 499 least change the port number the process is listening on, and 500 use the <code class="literal">-r</code> option to identify the host and 501 port of the other replication process: 502</p> 503 <pre class="programlisting">> mkdir env2 504> java db.repquote.RepQuoteExampleGSG -h env2 -n 2 -l localhost:8081 \ 505-r localhost:8080 -p 20</pre> 506 <p> 507 After a short pause, the second process should display the master 508 prompt: 509</p> 510 <pre class="programlisting"> 511QUOTESERVER > </pre> 512 <p> 513 And the first process should 514 display the read-only prompt: 515</p> 516 <pre class="programlisting"> 517QUOTESERVER (read-only)> </pre> 518 <p> 519 Now go to the master process and give it a couple of stocks and stock 520 prices: 521</p> 522 <pre class="programlisting">QUOTESERVER> FAKECO 9.87 523QUOTESERVER> NOINC .23 524QUOTESERVER> </pre> 525 <p> 526 Then, go to the replica and hit <strong class="userinput"><code>return</code></strong> at the prompt to 527 see the new values: 528</p> 529 <pre class="programlisting">QUOTESERVER (read-only)> 530 Symbol Price 531 ====== ===== 532 FAKECO 9.87 533 NOINC .23 534QUOTESERVER (read-only)> </pre> 535 <p> 536 Doing the same at the master results in the same thing: 537</p> 538 <pre class="programlisting">QUOTESERVER> 539 Symbol Price 540 ====== ===== 541 FAKECO 9.87 542 NOINC .23 543QUOTESERVER> </pre> 544 <p> 545 You can change a stock by simply entering the stock value and 546 new price at the master's prompt: 547</p> 548 <pre class="programlisting">QUOTESERVER> FAKECO 10.01 549QUOTESERVER> </pre> 550 <p> 551 Then, go to either the master or the replica to see the updated 552 database. On the master: 553</p> 554 <pre class="programlisting">QUOTESERVER> 555 Symbol Price 556 ====== ===== 557 FAKECO 10.01 558 NOINC .23 559QUOTESERVER> </pre> 560 <p> 561 And on the replica: 562</p> 563 <pre class="programlisting">QUOTESERVER (read-only)> 564 Symbol Price 565 ====== ===== 566 FAKECO 10.01 567 NOINC .23 568QUOTESERVER (read-only)> </pre> 569 <p> 570 Finally, to quit the applications, simply type 571 <code class="literal">quit</code> at both prompts. On the replica: 572</p> 573 <pre class="programlisting">QUOTESERVER (read-only)> quit 574> </pre> 575 <p> 576 And on the master as well: 577</p> 578 <pre class="programlisting">QUOTESERVER> quit 579> </pre> 580 </div> 581 </div> 582 <div class="navfooter"> 583 <hr /> 584 <table width="100%" summary="Navigation footer"> 585 <tr> 586 <td width="40%" align="left"><a accesskey="p" href="processingloop.html">Prev</a>��</td> 587 <td width="20%" align="center"> 588 <a accesskey="u" href="fwrkmasterreplica.html">Up</a> 589 </td> 590 <td width="40%" align="right">��<a accesskey="n" href="addfeatures.html">Next</a></td> 591 </tr> 592 <tr> 593 <td width="40%" align="left" valign="top">Processing Loop��</td> 594 <td width="20%" align="center"> 595 <a accesskey="h" href="index.html">Home</a> 596 </td> 597 <td width="40%" align="right" valign="top">��Chapter��5.��Additional Features</td> 598 </tr> 599 </table> 600 </div> 601 </body> 602</html> 603