001package gu.dtalk.redis; 002 003import static com.google.common.base.Preconditions.*; 004 005import java.net.URI; 006import java.util.Iterator; 007import java.util.Map; 008import java.util.ServiceLoader; 009 010import com.google.common.base.Optional; 011import com.google.common.base.Predicate; 012import com.google.common.base.Strings; 013import com.google.common.collect.ImmutableMap; 014import com.google.common.collect.Iterators; 015import com.google.common.net.HostAndPort; 016 017import gu.simplemq.IMQConnParameterSupplier; 018import gu.simplemq.MessageQueueType; 019import gu.simplemq.exceptions.SmqNotFoundConnectionException; 020import gu.simplemq.redis.JedisPoolLazy.PropName; 021import gu.simplemq.redis.JedisUtils; 022 023/** 024 * redis连接配置参数 025 * @author guyadong 026 * 027 */ 028public enum RedisConfigType implements IMQConnParameterSupplier{ 029 /** 自定义配置 */CUSTOM(new DefaultCustomRedisConfigProvider()) 030 /** 局域网配置 */,LAN(new DefaultLocalRedisConfigProvider()) 031 /** 公有云配置 */,CLOUD(new DefaultCloudRedisConfigProvider()) 032 /** 本机配置(仅用于测试) */,LOCALHOST(new DefaultLocalhostRedisConfigProvider()); 033 /** 034 * 接口实例的默认实现 035 */ 036 private final RedisConfigProvider defImpl; 037 /** 038 * 接口实例 039 */ 040 private volatile RedisConfigProvider instance; 041 /** 042 * redis连接配置参数 043 */ 044 private volatile Map<PropName, Object> parameters; 045 /** 046 * 当前配置是否可连接 047 */ 048 private volatile boolean connectable = false; 049 private RedisConfigType(){ 050 this(null); 051 } 052 private RedisConfigType(RedisConfigProvider defImpl) { 053 this.defImpl = defImpl; 054 } 055 /** 056 * SPI(Server Load Interface)方式加载当前类型的{@link RedisConfigProvider}实例, 057 * 没找到则用默认{@link #defImpl}实例代替 058 * @return 059 */ 060 private RedisConfigProvider findConfigProvider(){ 061 // double checking 062 if(instance == null){ 063 synchronized (this) { 064 if(instance == null){ 065 ServiceLoader<RedisConfigProvider> providers = ServiceLoader.load(RedisConfigProvider.class); 066 Iterator<RedisConfigProvider> itor = providers.iterator(); 067 Optional<RedisConfigProvider> find = Iterators.tryFind(itor, new Predicate<RedisConfigProvider>() { 068 069 @Override 070 public boolean apply(RedisConfigProvider input) { 071 return input.type() == RedisConfigType.this; 072 } 073 }); 074 instance = find.isPresent() ? find.get() : this.defImpl; 075 // host uri都为null则 instance无效设置为null 076 if(null != instance && instance.getHost()==null && instance.getURI() == null){ 077 instance = null; 078 } 079 } 080 } 081 } 082 return instance; 083 } 084 @Override 085 public HostAndPort getHostAndPort(){ 086 return JedisUtils.getHostAndPort(parameters); 087 } 088 private static Map<PropName,Object> asRedisParameters(RedisConfigProvider config){ 089 if(config == null){ 090 return null; 091 } 092 ImmutableMap.Builder<PropName, Object> builder = ImmutableMap.builder(); 093 URI uri = config.getURI(); 094 if(uri != null){ 095 builder.put(PropName.uri, uri); 096 }else{ 097 String host = config.getHost(); 098 int port =config.getPort(); 099 String password = config.getPassword(); 100 int database = config.getDatabase(); 101 checkArgument(!Strings.isNullOrEmpty(host),"INVALID REDIS HOST"); 102 103 builder.put(PropName.host, host); 104 if(port >0){ 105 builder.put(PropName.port, port); 106 } 107 if(!Strings.isNullOrEmpty(password)){ 108 builder.put(PropName.password, password); 109 } 110 if(database > 0){ 111 builder.put(PropName.database, database); 112 } 113 } 114 int timeout = config.getTimeout(); 115 if(timeout >0){ 116 builder.put(PropName.timeout, timeout); 117 } 118 return builder.build(); 119 120 } 121 /** 122 * 根据SPI加载的{@link RedisConfigProvider}实例提供的参数创建Redis连接参数<br> 123 * 如果{@link #findConfigProvider()}返回{@code null}则返回{@code null} 124 * @return Redis连接参数 125 */ 126 public Map<PropName, Object> readRedisParam(){ 127 // double checking 128 if(parameters == null){ 129 synchronized (this) { 130 if(parameters == null){ 131 RedisConfigProvider config = findConfigProvider(); 132 parameters = asRedisParameters(config); 133 } 134 } 135 } 136 return parameters; 137 } 138 139 @Override 140 public Map<String, Object> getMQConnParameters(){ 141 return JedisUtils.asMqParameters(readRedisParam()); 142 } 143 @Override 144 public final MessageQueueType getImplType() { 145 return MessageQueueType.REDIS; 146 } 147 /** 148 * 保存redis参数到当前类型对应的{@link RedisConfigProvider}实例 149 * @param param redis参数 150 */ 151 public synchronized void saveRedisParam(Map<PropName, Object> param){ 152 RedisConfigProvider config = findConfigProvider(); 153 Object host = param.get(PropName.host); 154 if(host instanceof String){ 155 config.setHost((String) host); 156 } 157 Object port = param.get(PropName.port); 158 if(port instanceof Number){ 159 config.setPort(((Number) port).intValue()); 160 } 161 Object password = param.get(PropName.password); 162 if(password instanceof String){ 163 config.setPassword((String) password); 164 } 165 Object database = param.get(PropName.database); 166 if(database instanceof Number){ 167 config.setDatabase(((Number) database).intValue()); 168 } 169 Object timeout = param.get(PropName.timeout); 170 if(timeout instanceof Number){ 171 config.setTimeout(((Number) timeout).intValue()); 172 } 173 Object uri = param.get(PropName.uri); 174 if(uri instanceof URI){ 175 config.setURI( (URI) uri); 176 } 177 } 178 /** 179 * 当前可用的的配置类型 180 */ 181 private static volatile RedisConfigType activeConfigType = null; 182 /** 183 * 复位{@link #activeConfigType}为{@code null}<br> 184 * 为避免{@link #lookupConnect(Long)}方法被多次执行, 185 * 当{@link #activeConfigType}不为{@code null}时直接返回{@link #activeConfigType}的值, 186 * 如果希望再次执行{@link #lookupConnect(Long)}方法,可先调用此方法设置{@link #activeConfigType}为{@code null} 187 */ 188 public static void resetActiveConfigType(){ 189 activeConfigType = null; 190 } 191 /** 192 * 测试redis连接 193 * @param timeoutMills 连接超时(毫秒),为{@code null}或小于等于0使用默认值 194 * @return 连接成功返回{@code true},否则返回{@code false} 195 */ 196 public synchronized boolean testConnect(Integer timeoutMills){ 197 Map<PropName, Object> props = readRedisParam(); 198 connectable = false; 199 if(props != null && !props.isEmpty()){ 200// System.out.printf("try to connect %s...\n", this); 201 try{ 202 if(timeoutMills != null && timeoutMills > 0){ 203 props = ImmutableMap.<PropName, Object>builder() 204 .putAll(props).put(PropName.timeout, timeoutMills) 205 .build(); 206 } 207 connectable = JedisUtils.testConnect(props); 208 }catch (Throwable e) { 209 } 210// if(connectable){ 211// System.out.println(toString() + " connect OK\n"); 212// } 213// System.out.printf("%s connect %s\n",this.toString(),connectable?"OK":"FAIL"); 214 } 215 return connectable; 216 } 217 218 /** 219 * 按照如下优先顺序测试配置的redis连接,返回第一个能建立有效连接的配置,否则抛出异常<br> 220 * <ul> 221 * <li>{@link RedisConfigType#CUSTOM}</li> 222 * <li>{@link RedisConfigType#LAN}</li> 223 * <li>{@link RedisConfigType#CLOUD}</li> 224 * <li>{@link RedisConfigType#LOCALHOST}</li> 225 * </ul> 226 * @param timeoutMills 连接超时(毫秒),为{@code null}或小于等于0使用默认值 227 * @return {@link #activeConfigType}不为{@code null}时直接返回{@link #activeConfigType}的值 228 * @throws SmqNotFoundConnectionException 没有找到有效redis连接 229 */ 230 public static RedisConfigType lookupConnect(final Integer timeoutMills) throws SmqNotFoundConnectionException{ 231 if(activeConfigType == null){ 232 synchronized(RedisConfigType.class){ 233 if(activeConfigType == null){ 234 // 并发执行连接测试,以减少等待时间 235 Thread[] threads = new Thread[values().length]; 236 int index = 0; 237 for (final RedisConfigType type : values()) { 238 threads[index] = new Thread(){ 239 240 @Override 241 public void run() { 242 type.testConnect(timeoutMills); 243 } 244 245 }; 246 threads[index].start(); 247 index++; 248 } 249 // 等待所有子线程结束 250 // 以枚举变量定义的顺序为优先级查找第一个connectable为true的对象返回 251 // 都为false则抛出异常 252 try { 253 for(int i =0;i<threads.length;++i){ 254 threads[i].join(); 255 RedisConfigType type = values()[i]; 256 if(type.connectable){ 257 return type; 258 } 259 } 260 } catch (InterruptedException e) { 261 } 262 throw new SmqNotFoundConnectionException("NOT FOUND VALID REDIS SERVER"); 263 } 264 } 265 } 266 return activeConfigType; 267 } 268 269 /** 270 * 与{@link #lookupConnect(Long)}功能相似,不同的时当没有找到有效redis连接时,不抛出异常,返回{@code null} 271 * @param timeoutMills 连接超时(毫秒),为{@code null}或小于等于0使用默认值 272 * @return 返回第一个能建立有效连接的配置,否则返回{@code null} 273 */ 274 public static RedisConfigType lookupConnectUnchecked(Integer timeoutMills) { 275 try { 276 return lookupConnect(timeoutMills); 277 } catch (SmqNotFoundConnectionException e) { 278 return null; 279 } 280 } 281 @Override 282 public String toString(){ 283 StringBuffer buffer = new StringBuffer(); 284 buffer.append(getClass().getSimpleName()).append(":"); 285 buffer.append(name()); 286 Map<PropName, Object> param = readRedisParam(); 287 if(param==null){ 288 buffer.append("(UNDEFINED)"); 289 }else{ 290 buffer.append("(").append(JedisUtils.getCanonicalURI(param).toString()).append(")"); 291 } 292 return buffer.toString(); 293 } 294}