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