001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 020package org.apache.oozie.service; 021 022import java.text.SimpleDateFormat; 023import java.util.Date; 024import java.util.concurrent.TimeUnit; 025 026import org.apache.curator.RetryPolicy; 027import org.apache.curator.framework.recipes.atomic.AtomicValue; 028import org.apache.curator.framework.recipes.atomic.DistributedAtomicLong; 029import org.apache.curator.framework.recipes.atomic.PromotedToLock; 030import org.apache.curator.retry.RetryNTimes; 031import org.apache.oozie.ErrorCode; 032import org.apache.oozie.lock.LockToken; 033import org.apache.oozie.util.XLog; 034import org.apache.oozie.util.ZKUtils; 035 036import com.google.common.annotations.VisibleForTesting; 037 038/** 039 * Service that provides distributed job id sequence via ZooKeeper. Requires that a ZooKeeper ensemble is available. The 040 * sequence path will be located under a ZNode named "job_id_sequence" under the namespace (see {@link ZKUtils}). The 041 * sequence will be reset to 0, once max is reached. 042 */ 043 044public class ZKUUIDService extends UUIDService { 045 046 public static final String CONF_PREFIX = Service.CONF_PREFIX + "ZKUUIDService."; 047 048 public static final String CONF_SEQUENCE_MAX = CONF_PREFIX + "jobid.sequence.max"; 049 public static final String LOCKS_NODE = "/SEQUENCE_LOCK"; 050 051 public static final String ZK_SEQUENCE_PATH = "/job_id_sequence"; 052 053 public static final long RESET_VALUE = 0L; 054 public static final int RETRY_COUNT = 3; 055 056 private final static XLog LOG = XLog.getLog(ZKUUIDService.class); 057 058 private ZKUtils zk; 059 private static Long maxSequence = 9999990L; 060 061 DistributedAtomicLong atomicIdGenerator; 062 063 public static final ThreadLocal<SimpleDateFormat> dt = new ThreadLocal<SimpleDateFormat>() { 064 @Override 065 protected SimpleDateFormat initialValue() { 066 return new SimpleDateFormat("yyMMddHHmmssSSS"); 067 } 068 }; 069 070 071 @Override 072 public void init(Services services) throws ServiceException { 073 074 super.init(services); 075 try { 076 zk = ZKUtils.register(this); 077 PromotedToLock.Builder lockBuilder = PromotedToLock.builder().lockPath(getPromotedLock()) 078 .retryPolicy(getRetryPolicy()).timeout(Service.lockTimeout, TimeUnit.MILLISECONDS); 079 atomicIdGenerator = new DistributedAtomicLong(zk.getClient(), ZK_SEQUENCE_PATH, getRetryPolicy(), 080 lockBuilder.build()); 081 082 } 083 catch (Exception ex) { 084 throw new ServiceException(ErrorCode.E1700, ex.getMessage(), ex); 085 } 086 087 } 088 089 /** 090 * Gets the unique id. 091 * 092 * @return the id 093 * @throws Exception the exception 094 */ 095 @Override 096 protected String createSequence() { 097 String localStartTime = super.startTime; 098 long id = 0L; 099 try { 100 id = getZKSequence(); 101 } 102 catch (Exception e) { 103 LOG.error("Error getting jobId, switching to old UUIDService", e); 104 id = super.getCounter(); 105 localStartTime = dt.get().format(new Date()); 106 } 107 return appendTimeToSequence(id, localStartTime); 108 } 109 110 protected synchronized long getZKSequence() throws Exception { 111 long id = getDistributedSequence(); 112 113 if (id >= maxSequence) { 114 resetSequence(); 115 id = getDistributedSequence(); 116 } 117 return id; 118 } 119 120 @SuppressWarnings("finally") 121 private long getDistributedSequence() throws Exception { 122 if (atomicIdGenerator == null) { 123 throw new Exception("Sequence generator can't be null. Path : " + ZK_SEQUENCE_PATH); 124 } 125 AtomicValue<Long> value = null; 126 try { 127 value = atomicIdGenerator.increment(); 128 } 129 catch (Exception e) { 130 throw new Exception("Exception incrementing UID for session ", e); 131 } 132 finally { 133 if (value != null && value.succeeded()) { 134 return value.preValue(); 135 } 136 else { 137 throw new Exception("Exception incrementing UID for session "); 138 } 139 } 140 } 141 142 /** 143 * Once sequence is reached limit, reset to 0. 144 * 145 * @throws Exception 146 */ 147 private void resetSequence() throws Exception { 148 for (int i = 0; i < RETRY_COUNT; i++) { 149 AtomicValue<Long> value = atomicIdGenerator.get(); 150 if (value.succeeded()) { 151 if (value.postValue() < maxSequence) { 152 return; 153 } 154 } 155 // Acquire ZK lock, so that other host doesn't reset sequence. 156 LockToken lock = null; 157 try { 158 lock = Services.get().get(MemoryLocksService.class) 159 .getWriteLock(ZKUUIDService.class.getName(), lockTimeout); 160 } 161 catch (InterruptedException e1) { 162 //ignore 163 } 164 try { 165 if (lock == null) { 166 LOG.info("Lock is held by other system, will sleep and try again"); 167 Thread.sleep(1000); 168 continue; 169 } 170 else { 171 value = atomicIdGenerator.get(); 172 if (value.succeeded()) { 173 if (value.postValue() < maxSequence) { 174 return; 175 } 176 } 177 try { 178 atomicIdGenerator.forceSet(RESET_VALUE); 179 } 180 catch (Exception e) { 181 LOG.info("Exception while resetting sequence, will try again"); 182 continue; 183 } 184 resetStartTime(); 185 return; 186 } 187 } 188 finally { 189 if (lock != null) { 190 lock.release(); 191 } 192 } 193 } 194 throw new Exception("Can't reset ID sequence in ZK. Retried " + RETRY_COUNT + " times"); 195 } 196 197 @Override 198 public void destroy() { 199 if (zk != null) { 200 zk.unregister(this); 201 } 202 zk = null; 203 super.destroy(); 204 } 205 206 public String getPromotedLock() { 207 if (ZKUtils.getZKNameSpace().startsWith("/")) { 208 return ZKUtils.getZKNameSpace() + LOCKS_NODE; 209 210 } 211 else { 212 return "/" + ZKUtils.getZKNameSpace() + LOCKS_NODE; 213 } 214 } 215 216 @VisibleForTesting 217 public void setMaxSequence(long sequence) { 218 maxSequence = sequence; 219 } 220 221 /** 222 * Retries 25 times with delay of 200ms 223 * 224 * @return RetryNTimes 225 */ 226 private static RetryPolicy getRetryPolicy() { 227 return new RetryNTimes(25, 200); 228 } 229 230}