/
advanced-topics.xml
1044 lines (1012 loc) · 59.4 KB
/
advanced-topics.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?>
<chapter version="5.0" xml:id="advanced-topics" xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:ns="http://docbook.org/ns/docbook"
xmlns:mml="http://www.w3.org/1998/Math/MathML"
xmlns:html="http://www.w3.org/1999/xhtml">
<title>Advanced Topics</title>
<para>
This chapter focuses on a variety of topics, including deployment options, jobs, clustering, encryptions, synchronization control,
and configuration of SymmetricDS.
</para>
<section id="advanced-sync">
<title>Advanced Synchronization</title>
<section id="bi-direction-sync">
<title>Bi-Directional Synchronization</title>
<para>
SymmetricDS allows tables to be synchronized bi-directionally. Note that an outgoing
synchronization does not process changes during an incoming synchronization on the same node unless the trigger
was created with the <literal>sync_on_incoming_batch</literal> flag set. If the <literal>sync_on_incoming_batch</literal> flag
is set, then update loops are prevented by a feature that is available in most database dialects.
More specifically, during an incoming synchronization the source <literal>node_id</literal> is put into a database session variable that is
available to the database trigger. Data events are not generated if the target <literal>node_id</literal>
on an outgoing synchronization is equal to the source <literal>node_id</literal>.
</para>
<para>
By default, only the columns that changed will be updated in the target system.
</para>
<para>
Conflict resolution strategies can be configured for specific links and/or sets of tables.
</para>
</section>
<section id="multi-tier">
<title>Multi-Tiered Synchronization</title>
<para>
As shown in <xref linkend="organizing-nodes"/>, there may be
scenarios where data needs to flow through multiple tiers of nodes that
are organized in a tree-like network with each tier requiring a different subset of data. For example,
you may have a system where the lowest tier may by a computer or device located in a store. Those devices
may connect to a server located physically at that store. Then the store server may communicate with
a corporate server for example. In this case, the three tiers would be device, store, and corporate.
Each tier is typically represented by a node group. Each node in
the tier would belong to the node group representing that tier.
</para>
<para>
A node will always push and pull data to other node groups according to the node group link configuration.
A node can only pull and push data to other nodes that are represented <literal>node</literal> table in its database and
having <literal>sync_enabled = 1</literal>.
Because of this, a tree-like
hierarchy of nodes can be created by having only a subset of nodes belonging to the same node group represented at the different branches of the tree.
</para>
<para>
If auto registration is turned <emphasis>off</emphasis>, then this setup must occur manually by opening registration
for the desired nodes at the desired parent node and by configuring each node's <literal>registration.url</literal>
to be the parent node's URL.
The parent node is always tracked by the setting of the parent's <literal>node_id</literal> in the <literal>created_at_node_id</literal> column of the new node.
When a node registers and downloads its configuration it is always provided the configuration for nodes
that might register with the node itself based on the Node Group Links defined in the parent node.
</para>
<section id='registration-redirect'>
<title>Registration Redirect</title>
<para>
When deploying a multi-tiered system it may be advantageous to have only one registration server, even though the parent node of a registering node
could be any of a number of nodes in the system. In SymmetricDS the parent node is always the node that a child registers with. The
<xref linkend="table_registration_redirect" xrefstyle="table"/> table allows a single node, usually the root server in the network, to
redirect registering nodes to their true parents. It does so based on a mapping found in the table of the external id (<literal>registrant_external_id</literal>) to the parent's node
id (<literal>registration_node_id</literal>).
</para>
<para>
For example, if it is desired to have a series of regional servers that workstations at retail stores get assigned to based on their <literal>external_id</literal>, the store number, then
you might insert into <xref linkend="table_registration_redirect" xrefstyle="table"/> the store number as the <literal>registrant_external_id</literal> and the <literal>node_id</literal> of
the assigned region as the <literal>registration_node_id</literal>. When a workstation at the store registers, the root server send an HTTP redirect to the <literal>sync_url</literal> of the node
that matches the <literal>registration_node_id</literal>.
</para>
<important>
<para>Please see <xref linkend="configuration-initial-load" /> for important details around initial loads
and registration when using registration redirect.
</para>
</important>
</section>
</section>
</section>
<!-- Jobs? -->
<section id="jobs">
<title>Jobs</title>
<para>
The SymmetricDS software allows for outgoing and incoming changes to be synchronized
to/from other databases. The node that initiates a synchronization connection is the client, and the
node receiving a connection is the host. Because synchronization is configurable to
push or pull in either direction, the same node can act as either a client or a host
in different circumstances.
</para>
<para>
The SymmetricDS software consists of a series of background jobs, managers, Servlets, and services wired together
via dependency injection using the <ulink url="http://springframework.org">Spring Framework</ulink>.
</para>
<para>
As a client, the node runs the router job, push job and pull job on a timer thread.
The router job uses services to create batches that are targeted at certain nodes.
The push job uses services to extract and stream data to
another node (that is, it pushes data). The response from a push is a list
of batch acknowlegements to indicate that data was loaded.
The pull job uses services to load data that is streamed from another node
(<emphasis>i.e.</emphasis>, it pulls data). After loading data, a second connection is made to send a list
of batch acknowlegements.
</para>
<para>
As a host, the node waits for incoming connections that pull, push, or acknowledge data changes.
The push Servlet uses services to load data that is pushed from a client node.
After loading data, it responds with a list of batch acknowledgements.
The pull Servlet uses services to extract, and stream data back to the client node.
The ack Servlet uses services to update the status of data that was loaded at a client node. The router
job batches and routes data.
</para>
<para>
By default, data is extracted from the source database into memory until a threshold size is reached.
If the threshold size is reached, data is streamed to a temporary file in the JVM's default temporary
directory. Next, the data is streamed to the target node across the transport layer. The receiving
node will cache the data in memory until the threshold size is reached, writing to a temporary file if
necessary. At last, the data is loaded into the target database by the data loader. This step by step
approach allows for extract time, transport time, and load time to all be measured independently. It
also allows database resources to be used most optimally.
</para>
<para>
The transport manager handles the incoming and outgoing streams of data between nodes.
The default transport is based on a simple implementation over HTTP. An internal transport
is also provided. It is possible to add other implementations, such as a socket-based transport manager.
</para>
<para>
Node communication over HTTP is represented in the following figure.
</para>
<para>
<figure>
<title>Node Communication</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/seq-node-communication.gif" format="GIF" />
</imageobject>
</mediaobject>
</figure>
</para>
<para>
The <literal>StandaloneSymmetricEngine</literal> is wrapper API that can be used to directly start the client services only. The
<literal>SymmetricWebServer</literal> is a wrapper API that can be used to directly start <emphasis>both</emphasis> the
client and host services inside a Jetty web container. The <literal>SymmetricLauncher</literal> provides command line tools to work
with and start SymmetricDS.
</para>
<!-- TODO
<section id="ch04-components">
<title>Software Components</title>
<para>
<figure>
<title>Startup Sequence Diagram</title>
<mediaobject>
<imageobject>
<imagedata fileref="../images/seq-startup.gif" format="GIF" />
</imageobject>
</mediaobject>
</figure>
</para>
<para>
<figure>
<title>Client Side Pull Sequence Diagram</title>
<mediaobject>
<imageobject>
<imagedata fileref="../images/seq-pull-client.gif" format="GIF" />
</imageobject>
</mediaobject>
</figure>
</para>
<para>
<figure>
<title>Host Side Pull Sequence Diagram</title>
<mediaobject>
<imageobject>
<imagedata fileref="../images/seq-pull-host.gif" format="GIF" />
</imageobject>
</mediaobject>
</figure>
</para>
<para>
<figure>
<title>Client Side Push Sequence Diagram</title>
<mediaobject>
<imageobject>
<imagedata fileref="../images/seq-push-client.gif" format="GIF" />
</imageobject>
</mediaobject>
</figure>
</para>
<para>
<figure>
<title>Host Side Push Sequence Diagram</title>
<mediaobject>
<imageobject>
<imagedata fileref="../images/seq-push-host.gif" format="GIF" />
</imageobject>
</mediaobject>
</figure>
</para>
</section>
-->
<section id="routing-job">
<title>Route Job</title>
<section id="routing-job-overview">
<title>Overview</title>
<para>
The SymmetricDS-created database triggers cause data to be capture in the <xref linkend="table_data" xrefstyle="table"/> table.
The next step in the synchronization process is to process the change data to determine which nodes, if any, the data should
be routed to. This step is performed by the <emphasis>Route Job</emphasis>. In addition to determining which nodes data will
be sent to, the Route Job is also responsible for determing how much data will be batched together for transport.
It is a single background task that inserts into <xref linkend="table_data_event" xrefstyle="table"/>
and <xref linkend="table_outgoing_batch" xrefstyle="table"/>.
</para>
<para>
At a high level, the Route Job is straightforward. It collects a list of data ids from <xref linkend="table_data" xrefstyle="table"/>
which haven't yet been routed (see <xref linkend="data-gaps" /> for much more detail about this step),
one channel at a time, up to a limit specified by the channel configuration
(<literal>max_data_to_route</literal>, on <xref linkend="table_channel" xrefstyle="table"/>).
The data is then batched based on the <literal>batch_algorithm</literal> defined for the channel and as documented in
<xref linkend="configuration-channel"/>. Note that, for the default batching algorithm, there may actually be more than <literal>max_data_to_route</literal> included depending
on the transaction boundaries. The mapping of data to specific nodes, organized into batches, is then
recorded in <xref linkend="table_outgoing_batch" xrefstyle="table"/> with a status of "RT" in each case (representing the
fact that the Route Job is still running).
Once the routing algorithms and batching are completed, the batches are organized with their corresponding data ids
and saved in <xref linkend="table_data_event" xrefstyle="table"/>. Once <xref linkend="table_data_event" xrefstyle="table"/> is
updated, the rows in <xref linkend="table_outgoing_batch" xrefstyle="table"/> are updated to a status of New "NE".
</para>
</section>
<section id="data-gaps">
<title>Data Gaps</title>
<para>
On the surface, the first Route Job step of collecting unrouted data ids seems simple: assign
sequential data ids for each data row as it's inserted and keep track of which data id was last routed and start from there. The difficulty arises, however, due to the fact that
there can be multiple transactions inserting into <xref linkend="table_data" xrefstyle="table"/> simultaneously.
As such, a given section of rows in the <xref linkend="table_data" xrefstyle="table"/> table may actually contain "gaps" in the data ids when the Route Job is executing.
Most of these gaps are only temporarily and fill in at some point after routing and need to be picked up with the next run of the Route Job. Thus, the Route Job needs to
remember to route the filled-in gaps. Worse yet, some of these gaps are actually permanent and result from
a transaction that is rolled back for some reason. In this case, the Route Job must continue to watch
for the gap to fill in and, at some point, eventually gives up and assumes the gap is permanent and can be skipped. All of this
must be done in some fashion that guarantees that gaps are routed when they fill in while also keeping
routing as efficient as possible.
</para>
<para>
SymmetricDS handles the issue of data gaps by making use of a table, <xref linkend="table_data_gap" xrefstyle="table"/>, to record
gaps found in the data ids. In fact, this table completely defines the entire range of data tha can be routed at any point in time.
For a brand new instance of SymmetricDS, this table is empty
and SymmetricDS creates a gap starting from data id of zero and ending with a very large number (defined by <literal>routing.largest.gap.size</literal>).
At the start of a Route Job, the list of valid gaps (gaps with status of 'GP') is collected, and each gap is evaluated in turn.
If a gap is sufficiently old (as defined by <literal>routing.stale.dataid.gap.time.ms</literal>, the gap
is marked as skipped (status of 'SK') and will no longer be evaluated in future Route Jobs (note that the 'last' gap (the one with the highest starting data id) is never
skipped). If not skipped, then <xref linkend="table_data_event" xrefstyle="table"/> is searched for data ids present in the gap.
If one or more data ids is found in <xref linkend="table_data_event" xrefstyle="table"/>, then the current gap is marked with a status
of OK, and new gap(s) are created to represent the data ids still missing in the gap's range. This process is done for
all gaps. If the very last gap contained data, a new gap starting from the highest data id and ending at (highest data id + <literal>routing.largest.gap.size</literal>) is then created.
This process has resulted in an updated list of gaps which may contain new data to be routed.
</para>
</section>
</section>
<section id='controlling-synchronization'>
<title>Synchronization Frequency</title>
<para>
The frequency of data synchronization is controlled by the coordination of a series of asynchronous events.
</para>
<para>
The route job determines which nodes
data will be sent to, and batches it together for transport. When the <literal>start.route.job</literal> SymmetricDS property is set to
<literal>true</literal>, the frequency that routing occurs is controlled by the <literal>job.routing.period.time.ms</literal>.
</para>
<para>
After data is routed, it awaits transport to the target nodes. Transport can occur when a client node is configured to pull data or when the host node is configured to push data. These
events are controlled by the <emphasis>push</emphasis> and the <emphasis>pull jobs</emphasis>. When the <literal>start.pull.job</literal> SymmetricDS property is set to
<literal>true</literal>, the frequency that data is pulled is controlled by the <literal>job.pull.period.time.ms</literal>. When the <literal>start.push.job</literal> SymmetricDS property is set to
<literal>true</literal>, the frequency that data is pushed is controlled by the <literal>job.push.period.time.ms</literal>. Data is extracted by channel from the source database's
<xref linkend="table_data" xrefstyle="table"/> table at an interval controlled by the <literal>extract_period_millis</literal> column on the
<xref linkend="table_channel" xrefstyle="table"/> table. The <literal>last_extract_time</literal> is
always recorded, by channel, on the <xref linkend="table_node_channel_ctl" xrefstyle="table"/> table for the host node's id. When the Pull and Push Job run, if the extract period
has not passed according to the last extract time, then the channel will be skipped for this run. If the <literal>extract_period_millis</literal> is set to zero, data extraction will happen every time the jobs run.
</para>
<para>
Both the push and pull jobs can be configured to push and pull from multiple nodes in parallel. In order to take advantage of this the
<literal>pull.thread.per.server.count</literal> or <literal>push.thread.per.server.count</literal> should be adjusted (from their default value of 10) to the number
to the number of concurrent push/pulls you want to occur per period on each SymmetricDS instance. Push and pull activity is recorded in the
<xref linkend="table_node_communication" xrefstyle="table"/> table. This table is also used to lock push and pull activity across
multiple servers in a cluster.
</para>
<para>
SymmetricDS also provides the ability to configure windows of time when synchronization is allowed. This is done using the
<xref linkend="table_node_group_channel_window" xrefstyle="table"/>
table. A list of allowed time windows can be specified for a node group and a channel. If one or more windows exist, then data will only be extracted and transported if the time
of day falls within the window of time specified. The configured times are always for the target node's local time. If the <literal>start_time</literal> is greater than the <literal>end_time</literal>, then the window crosses
over to the next day.
</para>
<para>
All data loading may be disabled by setting the <literal>dataloader.enable</literal> property to false. This has the effect of not allowing incoming synchronizations, while allowing outgoing
synchronizations. All data extractions may be disabled by setting the <literal>dataextractor.enable</literal> property to false. These properties can be controlled by inserting into the
root server's <xref linkend="table_parameter" xrefstyle="table"/> table. These properties affect every channel with the exception of the 'config' channel.
</para>
</section>
<section id="sync-triggers">
<title>Sync Triggers Job</title>
<para>
SymmetricDS examines the current configuration, corresponding database triggers,
and the underlying tables to determine if database triggers need created or updated.
The change activity is recorded on the <xref linkend="table_trigger_hist" xrefstyle="table"/> table with a reason for the
change. The following reasons for a change are possible:
<itemizedlist>
<listitem>
<para>N - New trigger that has not been created before</para>
</listitem>
<listitem>
<para>S - Schema changes in the table were detected</para>
</listitem>
<listitem>
<para>C - Configuration changes in Trigger</para>
</listitem>
<listitem>
<para>T - Trigger was missing</para>
</listitem>
</itemizedlist>
A configuration entry in Trigger without any history in Trigger Hist results in a new
trigger being created (N). The Trigger Hist stores a hash of the underlying table, so
any alteration to the table causes the trigger to be rebuilt (S). When the
<literal>last_update_time</literal> is changed on the Trigger entry, the configuration change causes
the trigger to be rebuilt (C). If an entry in Trigger Hist is missing the
corresponding database trigger, the trigger is created (T).
</para>
<para>
The process of examining triggers and rebuilding them is automatically run during startup and
each night by the SyncTriggersJob. The user can also manually run the process at any time
by invoking the <literal>syncTriggers()</literal> method over JMX. The SyncTriggersJob is enabled by default
to run at 15 minutes past midnight. If SymmetricDS is being run from a collection of servers
(multiple instances of the same Node running against the same database), then locking
should be enable to prevent database contention. The following runtime properties
control the behavior of the process.
<variablelist>
<varlistentry>
<term>
<command>start.synctriggers.job</command>
</term>
<listitem>
<para>
Whether the sync triggers job is enabled for this node.
[ Default: true ]
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>job.synctriggers.aftermidnight.minutes</command>
</term>
<listitem>
<para>
If scheduled, the sync triggers job will run nightly. This is how long after midnight
that job will run. [ Default: 15 ]
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>cluster.lock.during.sync.triggers</command>
</term>
<listitem>
<para>
Indicate if the sync triggers job is clustered and requires a lock before running.
[ Default: false ]
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</section>
</section>
<section id='jms-publishing'>
<title>JMS Publishing</title>
<para>
With the proper configuration SymmetricDS can publish XML messages of captured data changes to
JMS during routing or transactionally while data loading synchronized data into a target database.
The following explains how to publish to JMS during synchronization to the target database.
</para>
<para>
The XmlPublisherDatabaseWriterFilter is a
<xref linkend="extensions-data-loader-filter" xrefstyle="table"/> that may be configured to
publish specific tables as an XML message to a JMS provider.
See <xref linkend="extensions"/> for information on how
to configure an extension point. If the publish to JMS fails, the batch will be marked in error,
the loaded data for the batch will be rolled back
and the batch will be retried during the next synchronization run.
</para>
<para>
The following is an example extension point configuration that will publish four tables in XML with a root
tag of <emphasis>'sale'</emphasis>. Each XML message will be grouped by the batch and the column names identified by
the groupByColumnNames property which have the same values.
<programlisting><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="configuration-publishingFilter"
class="org.jumpmind.symmetric.integrate.XmlPublisherDatabaseWriterFilter">
<property name="xmlTagNameToUseForGroup" value="sale"/>
<property name="tableNamesToPublishAsGroup">
<list>
<value>SALE_TX</value>
<value>SALE_LINE_ITEM</value>
<value>SALE_TAX</value>
<value>SALE_TOTAL</value>
</list>
</property>
<property name="groupByColumnNames">
<list>
<value>STORE_ID</value>
<value>BUSINESS_DAY</value>
<value>WORKSTATION_ID</value>
<value>TRANSACTION_ID</value>
</list>
</property>
<property name="publisher">
<bean class="org.jumpmind.symmetric.integrate.SimpleJmsPublisher">
<property name="jmsTemplate" ref="definedSpringJmsTemplate"/>
</bean>
</property>
</bean>
</beans>]]></programlisting>
</para>
<para>
The publisher property on the XmlPublisherDatabaseWriterFilter takes an interface of type IPublisher. The implementation
demonstrated here is an implementation that publishes to JMS using Spring's
<ulink url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/jms.html#jms-jmstemplate">JMS template</ulink>.
Other implementations of IPublisher could easily publish the XML to other targets like an HTTP server, the file system or secure copy it to another server.
</para>
<para>
The above configuration will publish XML similiar to the following:
<programlisting><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<sale xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
id="0012010-01-220031234" nodeid="00001" time="1264187704155">
<row entity="SALE_TX" dml="I">
<data key="STORE_ID">001</data>
<data key="BUSINESS_DAY">2010-01-22</data>
<data key="WORKSTATION_ID">003</data>
<data key="TRANSACTION_ID">1234</data>
<data key="CASHIER_ID">010110</data>
</row>
<row entity="SALE_LINE_ITEM" dml="I">
<data key="STORE_ID">001</data>
<data key="BUSINESS_DAY">2010-01-22</data>
<data key="WORKSTATION_ID">003</data>
<data key="TRANSACTION_ID">1234</data>
<data key="SKU">9999999</data>
<data key="PRICE">10.00</data>
<data key="DESC" xsi:nil="true"/>
</row>
<row entity="SALE_LINE_ITEM" dml="I">
<data key="STORE_ID">001</data>
<data key="BUSINESS_DAY">2010-01-22</data>
<data key="WORKSTATION_ID">003</data>
<data key="TRANSACTION_ID">1234</data>
<data key="SKU">9999999</data>
<data key="PRICE">10.00</data>
<data key="DESC" xsi:nil="true"/>
</row>
<row entity="SALE_TAX" dml="I">
<data key="STORE_ID">001</data>
<data key="BUSINESS_DAY">2010-01-22</data>
<data key="WORKSTATION_ID">003</data>
<data key="TRANSACTION_ID">1234</data>
<data key="AMOUNT">1.33</data>
</row>
<row entity="SALE_TOTAL" dml="I">
<data key="STORE_ID">001</data>
<data key="BUSINESS_DAY">2010-01-22</data>
<data key="WORKSTATION_ID">003</data>
<data key="TRANSACTION_ID">1234</data>
<data key="AMOUNT">21.33</data>
</row>
</sale>
]]></programlisting>
To publish JMS messages during routing
the same pattern is valid, with the exception that the extension point would be the XmlPublisherDataRouter and the router
would be configured by setting the <literal>router_type</literal> of a <xref linkend="table_router" xrefstyle="table"/> to the Spring bean
name of the registered extension point. Of course, the router would need to be linked through <xref linkend="table_trigger_router" xrefstyle="table"/>s
to each <xref linkend="table_trigger" xrefstyle="table"/> table that needs published.
</para>
</section>
<section id="deployment-options">
<title>Deployment Options</title>
<para>An instance of SymmetricDS can be deployed in several ways:</para>
<itemizedlist>
<listitem>
<para>Web application archive (WAR) deployed to an application server</para>
<para>
This option means packaging a WAR file and deploying to your favorite
web server, like Apache Tomcat. It's a little more work, but you
can configure the web server to do whatever you need. SymmetricDS can also
be embedded in an existing web application, if desired.
</para>
</listitem>
<listitem>
<para>Standalone service that embeds Jetty web server</para>
<para>
This option means running the <emphasis>sym</emphasis> command line, which launches the built-in Jetty web server.
This is a simple option because it is already provided, but you lose the flexibility to configure
the web server any further.
</para>
</listitem>
<listitem>
<para>Embedded as a Java library in an application</para>
<para>
This option means you must write a wrapper Java program that runs
SymmetricDS. You would probably use Jetty web server, which is also embeddable.
You could bring up an embedded database like Derby or H2. You could configure the
web server, database, or SymmetricDS to do whatever you needed, but it's also
the most work of the three options discussed thus far.
</para>
</listitem>
</itemizedlist>
<para>
The deployment model you choose depends on how much flexibility you need versus how easy you
want it to be. Both Jetty and Tomcat are excellent, scalable web servers that
compete with each other and have great performance. Most people choose either
the <emphasis>Standalone</emphasis> or <emphasis>Web Archive</emphasis> with Tomcat 5.5 or 6. Deploying to Tomcat
is a good middle-of-the-road decision that requires a little more work for more flexibility.
</para>
<para>Next, we will go into a little more detail on the first three deployment options listed above.</para>
<section id="deployment-options-web-archive">
<title>Web Archive (WAR)</title>
<para>
As a web application archive, a WAR is deployed to an application server,
such as Tomcat, Jetty, or JBoss. The structure of the archive will have a <literal>web.xml</literal>
file in the <literal>WEB-INF</literal> folder, an appropriately configured <filename>symmetric.properties</filename> file in the <literal>WEB-INF/classes</literal> folder,
and the required JAR files in the <literal>WEB-INF/lib</literal> folder.
</para>
<mediaobject>
<imageobject>
<imagedata fileref="images/symmetric_war.gif" format="GIF" />
</imageobject>
</mediaobject>
<para>
A war file can be generated using the standalone installation's <literal>symadmin</literal> utility and the
<literal>create-war</literal> subcommand. The command requires the name of the war file to generate. It
essentially packages up the web directory, the conf directory and includes an optional
properties file. Note that if a properties file is included, it will be copied to
WEB-INF/classes/symmetric.properties. This is the same location conf/symmetric.properties
would have been copied to. The generated war distribution uses the same web.xml as the standalone
deployment.
</para>
<para>
<command>../bin/symadmin -p my-symmetric-ds.properties create-war /some/path/to/symmetric-ds.war</command>
</para>
<para>
The <literal>web.base.servlet.path</literal> property in <filename>symmetric.properties</filename> can be set if the SymmetricServlet needs to
coexist with other Servlets. By default, the value is blank. If you set it to, say, <literal>web.base.servlet.path=sync</literal> for exmaple,
<literal>registration.url</literal> would be <literal>http://server:port/sync</literal>.
</para>
</section>
<section id="deployment-options-standalone">
<title>Standalone</title>
<para>
A standalone service can use the <literal>sym</literal> command line options to start
a server. An embedded instance of Jetty is used to service web
requests for all the servlets.
</para>
<programlisting><![CDATA[/symmetric/bin/sym --properties root.properties --port 8080 --server
]]></programlisting>
<para>
This example starts the SymmetricDS server on port 8080 with the startup
properties found in the <filename>root.properties</filename> file.
</para>
</section>
<section id="deployment-options-embedded">
<title>Embedded</title>
<para>
A Java application with the SymmetricDS Java Archive (JAR) library on its
classpath can use the <literal>SymmetricWebServer</literal> to start the server.
</para>
<programlisting><![CDATA[
import org.jumpmind.symmetric.SymmetricWebServer;
public class StartSymmetricEngine {
public static void main(String[] args) throws Exception {
SymmetricWebServer node = new SymmetricWebServer(
"classpath://my-application.properties", "conf/web_dir");
// this will create the database, sync triggers, start jobs running
node.start(8080);
// this will stop the node
node.stop();
}
}]]></programlisting>
<para>
This example starts the SymmetricDS server on port 8080.
The configuration properties file, <filename>my-application.properties</filename>,
is packaged in the application to provide properties that override the SymmetricDS
default values. The second parameter to the constructor points to the web directory.
The default location is <code>../web</code>. In this example the web directory is located
at conf/web_dir. The web.xml is expected to be found at conf/web_dir/WEB-INF/web.xml.
</para>
</section>
</section>
<section id="running-service">
<title>Running SymmetricDS as a Service</title>
<para>
SymmetricDS can be configured to start and run as a service in both Windows and *nix platforms.
</para>
<section id="running-service-windows">
<title>Running as a Windows Service</title>
<para>
SymmetricDS uses the
<ulink url="http://wrapper.tanukisoftware.org/">Java Service Wrapper</ulink>
product from Tanuki Software to run in the background as a Windows system service.
The Java Service Wrapper executable is named <filename class="file">sym_service.exe</filename>
so it can be easily identified from a list of running processes.
To install the service, use the provided script:
<programlisting>bin\install_service.bat</programlisting>
</para>
<para>
The service configuration is found in <filename class="filename">conf/sym_service.conf</filename>.
Edit this file if you want to change the default port number (8080), initial memory size
(256 MB), log file size (10 MB), or other settings.
When started, the server will look in the <filename class="directory">conf</filename> directory
for the <filename class="filename">symmetric.properties</filename> file
and the <filename class="filename">log4j.xml</filename> file.
Logging for standard out, error, and application are written to the
<filename class="directory">logs</filename> directory.
</para>
<para>
Most configuration changes do not require the service to be re-installed.
To un-install the service, use the provided script:
<programlisting>bin\uninstall_service.bat</programlisting>
</para>
<para>
Use the <command>net</command> command to start and stop the service:
<programlisting>net start symmetricds
net stop symmetricds</programlisting>
</para>
</section>
<section id="running-service-unix">
<title>Running as a *nix Service</title>
<para>
SymmetricDS uses the
<ulink url="http://wrapper.tanukisoftware.org/">Java Service Wrapper</ulink>
product from Tanuki Software to run in the background as a Unix system service.
The Java Service Wrapper executable is named <filename class="file">sym_service</filename>
so it can be easily identified from a list of running processes.
The service configuration is found in <filename class="filename">conf/sym_service.conf</filename>.
Edit this file if you want to change the default port number (8080), initial memory size
(256 MB), log file size (10 MB), or other settings.
</para>
<para>
An init script is provided to work with standard Unix run configuration levels.
The <filename class="file">sym_service.initd</filename> file follows the
Linux Standard Base specification, which should work on many systems, including
Fedora and Debian-based distributions.
To install the script, copy it into the system init directory:
</para>
<para>
<programlisting>cp bin/sym_service.initd /etc/init.d/sym_service</programlisting>
</para>
<para>
Edit the init script to set the SYM_HOME variable to the directory
where SymmetricDS is located. The init script calls the
<filename class="file">sym_service</filename> executable.
</para>
<para>Enabling the service varies based on the version of Linux in use. Three possible approaches are listed below:</para>
<orderedlist>
<listitem>Using <literal>chkconfig</literal> command:
<para>
To enable the service to run automatically when the system is started:
<programlisting>/sbin/chkconfig --add sym_service</programlisting>
</para>
<para>
To disable the service from running automatically:
<programlisting>/sbin/chkconfig --del sym_service</programlisting>
</para>
</listitem>
<listitem>Using <literal>install_initd</literal> command (Suse Linux):
<para>
On Suse Linux install the service by calling:
<programlisting>/usr/lib/lsb/install_initd sym_service</programlisting>
Remove the service by calling:
<programlisting>/usr/lib/lsb/remove_initd sym_service</programlisting>
</para></listitem>
<listitem>
Using <literal>sysv-rc-conf</literal> command (Ubuntu Linux):
<para>
On Ubuntu Linux, you might need to use sysv-rc-conf instead of chkconfig.
Try running sys-rc-conf as a super user (consider utilizing apt-get to install sysv-rc-conf
if it is not present: <literal>sudo apt-get install sysv-rc-conf</literal>).
Run sysv-rc-conf with the following command:
<programlisting>sudo sysv-rc-conf </programlisting>
You should see a list of the scripts residing in your /etc/init.d folder.
Use control-N to navigate through the list to locate sym_service, then activate the service
for the desired run-levels (most likely 2-5).
</para>
</listitem>
</orderedlist>
<para>
Finally, you can use the <command>service</command> command to start, stop, and query
the status of the service:
<programlisting>/sbin/service sym_service start
/sbin/service sym_service stop
/sbin/service sym_service status</programlisting>
</para>
<para>
Alternatively, call the init.d script directly:
<programlisting>/etc/init.d/sym_service start
/etc/init.d/sym_service stop
/etc/init.d/sym_service status
</programlisting>
</para>
</section>
</section>
<section id="clustering">
<title>Clustering</title>
<para>
A single SymmetricDS node may be clustered across a series of instances, creating a web farm. A node might be clustered to provide load balancing and failover, for example.
</para>
<para>
When clustered, a hardware load balancer is typically used
to round robin client requests to the cluster. The load balancer should be configured for stateless connections.
Also, the <literal>sync.url</literal> (discussed in <xref linkend="configuration-node-properties"/>)
SymmetricDS property should be set to the URL of the load balancer.
</para>
<para>
If the cluster will be running any of the SymmetricDS jobs, then the <literal>cluster.lock.enabled</literal> property should be set to <literal>true</literal>.
By setting this property to true, SymmetricDS will use a row in the <xref linkend="table_lock" xrefstyle="table"/> table as a semaphore to make sure that only one instance at a time
runs a job. When a lock is acquired, a row is updated in the lock table with the time of the lock and the server id of the locking job. The lock time is set back to null
when the job is finished running. Another instance of SymmetricDS cannot aquire a lock until the locking instance (according to the server id) releases the lock. If an
instance is terminated while the lock is still held, an instance with the same server id is allowed to reaquire the lock. If the locking instance remains down, the lock can be
broken after a period of time, specified by the <literal>cluster.lock.timeout.ms</literal> property, has expired. Note that if the job is still running and the lock
expires, two jobs could be running at the same time which could cause database deadlocks.
</para>
<para>
By default, the locking server id is the hostname of the server. If two clustered instances are running on the same server, then the <literal>cluster.server.id</literal> property
may be set to indicate the name that the instance should use for its server id.
</para>
<para>
When deploying SymmetricDS to an application server like Tomcat or JBoss, no special session clustering needs to be configured for the application server.
</para>
</section>
<section id="encrypted-passwords">
<title>Encrypted Passwords</title>
<para>
The <literal>db.user</literal> and <literal>db.password</literal> properties will accept encrypted text, which protects
against casual observation. The text is prefixed with <literal>enc:</literal> to indicate
that it is encrypted. To encrypt text, use the following command:
<programlisting>sym -e secret</programlisting>
The text is encrypted using a secret key named "sym.secret" that is retrieved from a keystore file.
By default, the keystore is located in <filename class="filename">security/keystore</filename>.
The location and filename of the keystore can be overridden by setting the "sym.keystore.file" system property.
If the secret key is not found, the system will generate and install a secret key for use with Triple DES cipher.
</para>
<para>
Generate a new secret key for encryption using the <literal>keytool</literal>
command that comes with the JRE. If there is an existing key in the keystore, first remove it:
<programlisting>keytool -keystore keystore -storepass changeit -storetype jceks \
-alias sym.secret -delete</programlisting>
Then generate a secret key, specifying a cipher algorithm and key size.
Commonly used algorithms that are supported include aes, blowfish, desede, and rc4.
<programlisting>keytool -keystore keystore -storepass changeit -storetype jceks \
-alias sym.secret -genseckey -keyalg aes -keysize 128</programlisting>
If using an alternative provider, place the provider JAR file in the SymmetricDS <filename class="directory">lib</filename> folder.
The provider class name should be installed in the JRE security properties or specified on the command line.
To install in the JRE, edit the JRE <filename class="filename">lib/security/java.security</filename> file
and set a <literal>security.provider.i</literal> property for the provider class name.
Or, the provider can be specified on the command line instead.
Both <literal>keytool</literal> and <literal>sym</literal> accept command line arguments for the provider class name.
For example, using the Bouncy Castle provider, the command line options would look like:
<programlisting>keytool -keystore keystore -storepass changeit -storetype jceks \
-alias sym.secret -genseckey -keyalg idea -keysize 56 \
-providerClass org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerPath ..\lib\bcprov-ext.jar</programlisting>
<programlisting>symadmin -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -e secret</programlisting>
To customize the encryption, write a Java class that implements the ISecurityService or extends the default SecurityService, and place
the class on the classpath in either <filename class="directory">lib</filename> or
<filename class="directory">web/WEB-INF/lib</filename> folders.
Then, in the <filename class="filename">symmetric.properties</filename> specify your class name for the security service.
<programlisting>security.service.class.name=org.jumpmind.security.SecurityService</programlisting>
Remember to specify your properties file when encrypting passwords, so it will use your custom ISecurityService.
<programlisting>symadmin -p symmetric.properties -e secret</programlisting>
</para>
</section>
<section id="secure-transport">
<title>Secure Transport</title>
<para>
By specifying the "https" protocol for a URL, SymmetricDS will communicate over
Secure Sockets Layer (SSL) for an encrypted transport. The following properties
need to be set with "https" in the URL:
<variablelist>
<varlistentry>
<term>
<command>sync.url</command>
</term>
<listitem>
<para>
This is the URL of the current node, so if you want to force other
nodes to communicate over SSL with this node, you specify "https" in the URL.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>registration.url</command>
</term>
<listitem>
<para>
This is the URL where the node will connect for registration when it
first starts up. To protect the registration with SSL, you specify
"https" in the URL.
</para>
</listitem>
</varlistentry>
</variablelist>
For incoming HTTPS connections, SymmetricDS depends on the webserver where
it is deployed, so the webserver must be configured for HTTPS.
As a standalone deployment, the "sym" launcher command provides options for
enabling HTTPS support.
</para>
<section id="secure-transport-sym">
<title>Sym Launcher</title>
<para>
The "sym" launch command uses Jetty as an embedded web server.
Using command line options, the web server can be told to listen for
HTTP, HTTPS, or both.
</para>
<para>
<command>sym --port 8080 --server</command>
</para>
<para>
<command>sym --secure-port 8443 --secure-server</command>
</para>
<para>
<command>sym --port 8080 --secure-port 8443 --mixed-server</command>
</para>
</section>
<section id="secure-transport-tomcat">
<title>Tomcat</title>
<para>
If you deploy SymmetricDS to Apache Tomcat, it can be secured by editing the
<filename class="filename">TOMCAT_HOME/conf/server.xml</filename>
configuration file. There is already a line that can be uncommented
and changed to the following:
<programlisting>
<![CDATA[<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="/symmetric-ds-1.x.x/security/keystore" />]]></programlisting>
</para>
</section>
<section id="secure-transport-keystore">
<title>Keystores</title>
<para>
When SymmetricDS connects to a URL with HTTPS, Java checks the validity of the
certificate using the built-in trusted keystore located at
<filename class="filename">JRE_HOME/lib/security/cacerts</filename>.
The "sym" launcher command overrides the trusted keystore to use its own
trusted keystore instead, which is located at
<filename class="filename">security/cacerts</filename>.
This keystore contains the certificate aliased as "sym" for use in testing
and easing deployments.
The trusted keystore can be overridden
by specifying the <literal>javax.net.ssl.trustStore</literal> system property.
</para>
<para>
When SymmetricDS is run as a secure server with the "sym" launcher,
it accepts incoming requests using the key installed in the keystore
located at
<filename class="filename">security/keystore</filename>.
The default key is provided for convenience of testing, but should be
re-generated for security.
</para>
</section>
<section id="secure-transport-keys">
<title>Generating Keys</title>
<para>
To generate new keys and install a server certificate, use the
following steps:
</para>
<procedure>
<step>
<para>
Open a command prompt and navigate to the
<filename class="directory">security</filename>
subdirectory of your SymmetricDS installation on the server to which
communication will be secured (typically the "root" or "central office" server).
</para>
</step>
<step>
<para>Delete the old key pair and certificate.</para>
<para>
<command>keytool -keystore keystore -delete -alias sym</command>
</para>
<para>
<command>keytool -keystore cacerts -delete -alias sym</command>
</para>
<programlisting><![CDATA[Enter keystore password: changeit]]></programlisting>
</step>
<step>
<para>Generate a new key pair. Note that the first name/last name (the "CN") must match
the fully qualified hostname the client will be using to communcate to the server.</para>
<para>
<command>keytool -keystore keystore -alias sym -genkey -keyalg RSA -validity 10950</command>
</para>
<programlisting>
<![CDATA[Enter keystore password: changeit
What is your first and last name?
[Unknown]: localhost
What is the name of your organizational unit?
[Unknown]: SymmetricDS
What is the name of your organization?
[Unknown]: JumpMind
What is the name of your City or Locality?
[Unknown]:
What is the name of your State or Province?
[Unknown]:
What is the two-letter country code for this unit?
[Unknown]:
Is CN=localhost, OU=SymmetricDS, O=JumpMind, L=Unknown, ST=Unknown, C=Unknown
correct?
[no]: yes
Enter key password for <sym>
(RETURN if same as keystore password):]]></programlisting>
</step>
<step>
<para>Export the certificate from the private keystore.</para>
<para>
<command>keytool -keystore keystore -export -alias sym -rfc -file sym.cer</command>
</para>
</step>
<step>
<para>Install the certificate in the trusted keystore.</para>
<para>
<command>keytool -keystore cacerts -import -alias sym -file sym.cer</command>
</para>
</step>
<step>
<para>Copy the cacerts file that is generated by this process to
the <filename class="directory">security</filename> directory of each client's SymmetricDS installation.</para>
</step>
</procedure>
</section>
</section>
<section id="basic-auth">
<title>Basic Authentication</title>
<para>
SymmetricDS supports basic authentication for client and server nodes.
</para>
<para>
To configure a client node to use basic authentication when communicating with a server node,
specify the following startup parameters:
</para>
<variablelist>
<varlistentry>
<term>
<command>http.basic.auth.username</command>
</term>
<listitem>
<para>
username for client node basic authentication.
[ Default: ]
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>http.basic.auth.password</command>