28
28
import java .util .concurrent .locks .LockSupport ;
29
29
import java .util .concurrent .locks .ReadWriteLock ;
30
30
import java .util .concurrent .locks .ReentrantReadWriteLock ;
31
+ import java .util .regex .Matcher ;
32
+ import java .util .regex .Pattern ;
31
33
34
+ import org .apache .accumulo .core .Constants ;
32
35
import org .apache .zookeeper .KeeperException ;
33
36
import org .apache .zookeeper .KeeperException .Code ;
34
37
import org .apache .zookeeper .WatchedEvent ;
47
50
public class ZooCache {
48
51
private static final Logger log = LoggerFactory .getLogger (ZooCache .class );
49
52
53
+ public final static Pattern TABLE_SETTING_CONFIG_PATTERN =
54
+ Pattern .compile ("(/accumulo/[0-9a-z-]+)(" + Constants .ZTABLES + ")(/.*)("
55
+ + Constants .ZTABLE_CONF + ")/(table.*|tserver.*)" );
56
+
57
+ public final static Pattern ZNODE_PATTERN = Pattern .compile ("(/accumulo/[0-9a-z-]+)/.*" );
58
+
50
59
private final ZCacheWatcher watcher = new ZCacheWatcher ();
51
60
private final Watcher externalWatcher ;
52
61
@@ -62,6 +71,8 @@ public class ZooCache {
62
71
private final SecureRandom secureRandom = new SecureRandom ();
63
72
64
73
private volatile boolean closed = false ;
74
+ private static boolean tableConfigWatcherSet = false ;
75
+ private static int lastTableConfigVersion = 0 ;
65
76
66
77
public static class ZcStat {
67
78
private long ephemeralOwner ;
@@ -148,14 +159,20 @@ private ZooKeeper getZooKeeper() {
148
159
}
149
160
150
161
private class ZCacheWatcher implements Watcher {
162
+
151
163
@ Override
152
164
public void process (WatchedEvent event ) {
153
165
if (log .isTraceEnabled ()) {
154
166
log .trace ("{}" , event );
155
167
}
168
+ setWatcherForTableConfigVersion (event );
156
169
157
170
switch (event .getType ()) {
158
171
case NodeDataChanged :
172
+ if (event .getPath ().endsWith (Constants .ZTABLE_CONFIG_VERSION )) {
173
+ processTableConfigurationItem (event );
174
+ break ;
175
+ }
159
176
case NodeChildrenChanged :
160
177
case NodeCreated :
161
178
case NodeDeleted :
@@ -191,6 +208,158 @@ public void process(WatchedEvent event) {
191
208
externalWatcher .process (event );
192
209
}
193
210
}
211
+ /*
212
+ * The processTableConfigurationItem function is only called when there is a NodeDataChanged
213
+ * event on the "table_config_version" znode. It resets the watcher on the
214
+ * "table_config_version" znode and extracts the last modified table config path from the
215
+ * table_config_version znode. If the version of the table_config_version znode is what this
216
+ * process expects to be the next one we simply emulate what we used to do and remove that
217
+ * actual table configuration property's zpath from the ZooCache. If the version is not what we
218
+ * expect to be the next then we remove all the table config properties from the ZooCache as
219
+ * specified in Accumulo Issue #1225. We gain a little efficiency in ZooKeeper by trying to keep
220
+ * track of the version of the table_config_version znode which Zookeeper generates on its own.
221
+ * It would be unwise to try to calculate the version on our own since many processes on
222
+ * different machines will be setting table configuration properties.
223
+ */
224
+
225
+ private synchronized void processTableConfigurationItem (WatchedEvent event ) {
226
+
227
+ if (event .getPath () == null || event .getPath ().isEmpty ())
228
+ return ;
229
+
230
+ try {
231
+ Stat versionStat = getZooKeeper ().exists (event .getPath (), watcher );
232
+ if (versionStat == null ) {
233
+ return ;
234
+ }
235
+
236
+ byte [] configToRefresh = getZooKeeper ().getData (event .getPath (), true , versionStat );
237
+
238
+ if (configToRefresh == null ) {
239
+ return ;
240
+ }
241
+
242
+ if ((versionStat != null ) && (versionStat .getVersion () - lastTableConfigVersion == 1 )) {
243
+ lastTableConfigVersion = versionStat .getVersion ();
244
+ refreshTableConfig (configToRefresh );
245
+ if (log .isTraceEnabled ()) {
246
+ log .trace (
247
+ "Successfully refreshed table single table config: " + new String (configToRefresh ));
248
+ }
249
+ } else {
250
+
251
+ if (log .isTraceEnabled ()) {
252
+ log .trace ("We have to update all table configs." );
253
+ }
254
+
255
+ if (versionStat != null ) {
256
+ lastTableConfigVersion = versionStat .getVersion ();
257
+ updateAllTableConfigurations ();
258
+ }
259
+ }
260
+
261
+ } catch (Exception e ) {
262
+ log .error ("Error getting data from TABLE_CONFIGS zoonode " + e .getMessage ());
263
+ }
264
+
265
+ return ;
266
+ }
267
+
268
+ /**
269
+ * Remove all table configuration items for all tables from the cache.
270
+ */
271
+
272
+ private synchronized void updateAllTableConfigurations () {
273
+
274
+ for (String cachedItem : cache .keySet ()) {
275
+ if (cachedItem == null || cachedItem .isEmpty ())
276
+ continue ;
277
+
278
+ Matcher tableConfigMatcher = TABLE_SETTING_CONFIG_PATTERN .matcher (cachedItem );
279
+ if (tableConfigMatcher .matches ()) {
280
+ remove (cachedItem );
281
+ }
282
+ }
283
+
284
+ }
285
+
286
+ /***
287
+ * Create the first watcher for the /accumulo/{InstanceID/table-config-version znode. It only
288
+ * operates on the table-config-version znode. Watchers need to be set on a node if a watch
289
+ * event is ever to be triggered. Watch events are triggered by a data change, addition or
290
+ * deletion of a znode. Watches, once triggered need to be reset. (The reset of the watcher set
291
+ * here will be done in the ZCacheWatcher.processTableConfigurationItem function.) The change to
292
+ * the table-config-version znode occurs during setTableProperty and removeTableProperty calls
293
+ * in the TablePropUtil class. During setTableProperty and removeTableProperty calls, the data
294
+ * value of the table-config-version znode will also be changed in addition to the actual table
295
+ * configuration property (that will no longer be watched). Everytime the data in the
296
+ * table-config-version znode changes the internal "data version" tracked by Zookeeper will be
297
+ * automatically incremented. Accumulo does not have to compute what that version is. Zookeeper
298
+ * does that by itself. The ZooCache object will track this version and use it to maintain the
299
+ * cache. Many processes will be using it so when the "lastTableConfigVersion member" does not
300
+ * match the actual version of the table-config-version znode we just remove all the table
301
+ * configurations (in the particular ZooCache instance) as was specified in the Accumulo Issue
302
+ * #1225. What I found is that we can usually rely on events coming to the ZCacheWatcher in
303
+ * order and we only need to remove one table configuration from the ZooCache. The one that just
304
+ * got changed, added, or deleted. This should lead to far few calls to getData on Zookeeper
305
+ * during runtime. This is accomplished by setting the data of the table-config-version Znode to
306
+ * the ZooPath of the table configuration that was modified. This zPath with be removed from the
307
+ * ZooCache in the ZCacheWathcer.process(). * It will be restored in the ZooCache the next time
308
+ * ZooCache.get(String) is called for that table configuration path. This was how ZooCached
309
+ * worked when we watched all table configurations and we are just emulating it now using only
310
+ * one watched node for all the configuration items - the "table-config-version node".
311
+ */
312
+ private synchronized void setWatcherForTableConfigVersion (WatchedEvent event ) {
313
+
314
+ if (event .getPath () == null || event .getPath ().isEmpty ())
315
+ return ;
316
+
317
+ if (!tableConfigWatcherSet ) {
318
+ Matcher znodeMatcher = ZNODE_PATTERN .matcher (event .getPath ());
319
+ if (znodeMatcher .matches ()) {
320
+ String pathPrefix = znodeMatcher .group (1 );
321
+ try {
322
+
323
+ Stat versionStat =
324
+ getZooKeeper ().exists (pathPrefix + Constants .ZTABLE_CONFIG_VERSION , watcher );
325
+ if (versionStat != null ) {
326
+ lastTableConfigVersion = versionStat .getVersion ();
327
+ tableConfigWatcherSet = true ;
328
+ if (log .isTraceEnabled ())
329
+ log .trace ("Successfully set table_config watcher" );
330
+ }
331
+
332
+ } catch (KeeperException ke ) {
333
+ log .error ("Could not set watcher on " + pathPrefix + Constants .ZTABLE_CONFIG_VERSION
334
+ + " will retry later " + ke .getMessage ());
335
+ } catch (InterruptedException ie ) {
336
+ log .error ("Could not set watcher on " + pathPrefix + Constants .ZTABLE_CONFIG_VERSION
337
+ + " will retry later " + ie .getMessage ());
338
+ }
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ private void refreshTableConfig (byte [] configToRefresh ) {
345
+ if (configToRefresh == null )
346
+ return ;
347
+
348
+ String strConfigToRefresh = new String (configToRefresh );
349
+ if (strConfigToRefresh .isEmpty ())
350
+ return ;
351
+
352
+ Matcher tableConfigMatcher = TABLE_SETTING_CONFIG_PATTERN .matcher (strConfigToRefresh );
353
+ if (!tableConfigMatcher .matches ())
354
+ return ;
355
+
356
+ if (log .isTraceEnabled ()) {
357
+ log .trace ("NodeDataChanged called refreshTableConfig and refreshed table config: "
358
+ + new String (configToRefresh ));
359
+ }
360
+
361
+ remove (strConfigToRefresh );
362
+
194
363
}
195
364
196
365
/**
@@ -402,18 +571,22 @@ public byte[] run() throws KeeperException, InterruptedException {
402
571
* a special case that looks for Code.NONODE in the KeeperException, then non-existence can
403
572
* not be cached.
404
573
*/
574
+
575
+ Matcher configMatcher = TABLE_SETTING_CONFIG_PATTERN .matcher (zPath );
576
+ boolean createWatch = !configMatcher .matches ();
577
+
405
578
cacheWriteLock .lock ();
406
579
try {
407
580
final ZooKeeper zooKeeper = getZooKeeper ();
408
- Stat stat = zooKeeper .exists (zPath , watcher );
581
+ Stat stat = zooKeeper .exists (zPath , createWatch ? watcher : null );
409
582
byte [] data = null ;
410
583
if (stat == null ) {
411
584
if (log .isTraceEnabled ()) {
412
585
log .trace ("zookeeper did not contain {}" , zPath );
413
586
}
414
587
} else {
415
588
try {
416
- data = zooKeeper .getData (zPath , watcher , stat );
589
+ data = zooKeeper .getData (zPath , createWatch ? watcher : null , stat );
417
590
zstat = new ZcStat (stat );
418
591
} catch (KeeperException .BadVersionException | KeeperException .NoNodeException e1 ) {
419
592
throw new ConcurrentModificationException ();
@@ -546,6 +719,7 @@ boolean childrenCached(String zPath) {
546
719
* path of top node
547
720
*/
548
721
public void clear (String zPath ) {
722
+
549
723
Preconditions .checkState (!closed );
550
724
cacheWriteLock .lock ();
551
725
try {
0 commit comments