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