Spring Framework 에서 Redis 연동 및 사용

redis 는 메모리DB로 NOSql 중 하나.

보통 이중화를 위해 redis를 여러개 띄워 놓고, 이를 sentinel 로 모니터링하는 식으로 사용.

 

sentinel 은 단어가 지니고 있는 뜻(감시자)처럼 redis 를 모니터링 하는 용도로 쓰인다.

보통 홀수개(3, 5...)로 sentinel 을 사용하고,

sentinel들은 redis master 서버가 죽었다고 판단 할 경우, 자체적으로 투표를 진행하여 redis slave를 redis master로 승격시킨다.

 

* Ubuntu에 redis를 설치하여 직접 사용해 보았으나 과정 중 캡쳐를 해놓는다거나 따로 정리하진 못했다.

* redis 자체가 워낙 많이 쓰이고 설치/사용이 어렵지 않아 구글링 조금만 하면 환경은 큰 어려움 없이 잡을 수 있다.

 

문제는 spring framework (jdk1.7)에서 sentinel 및 redis 사용법인데..

우리나라말로 설명이 잘 되어 있는 레퍼런스는 찾기 힘들고, 구글링도 쉽지 않다는 점..

 

현재 프로젝트에서 redis 를 사용하게 될 지 정확한 결론이 나지 않았지만

일단 짬짬히 시간날 때 마다 공부 좀 하고 소스 좀 짜며.. 모듈 구현을 해보았다.

 

* 환경 : 

server : jboss/wildfly 9

language : java

framework : spring 3.x

jdk : 1.7

sentinel 3대, redis 2대

 

***

기존 서비스 내의 채팅서버에서 사용하고 있는 redis서버를 사용했으며, 채팅서버는 vert.x로 구현이 되어 있는 상황.

vert.x 에서 redis 를 사용하기 위해 vert.x 에서 지원하는 lib 을 사용하게되면,

sentinel 에도 비밀번호 설정(sentinel configuration 파일의 require-pass 설정)이 필수적으로 되어 있어야 한다고 하는데, 

문제는 jdk 1.7 에 지원되는 spring redis(jedis) jar엔 sentinel 패스워드가 잡혀있는 환경에서 redis 를 사용할 수 있도록 지원하는 method 가 없는 듯 하다..   

(jedis jar 에 구현되어 있는 method 들(JedisSentinelPool 등..)의 인자값을 보면 password 를 받게 되있는 method 들이 몇개 있었으나, redis password만 인자값으로 받고있을 뿐, sentinel password를 인자값으로 받고 있는 오버로딩 된 생성자가 없다. 혹시나 하고 redis password 인자값 부분에 sentinel password를 넣고 connection을 시도해보았지만 역시나 되지 않음)

 

 

[ pom.xml ]

1
2
3
4
5
6
7
8
9
10
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.7.0.RELEASE</version>
        </dependency>
cs

 

lettuce 라는 녀석도 있었지만 jedis 만 sentinel 지원을 한다길래 jedis를 사용했다.

(lettuce 최신 버전은 지원하고 있는지 잘 모르겠다)

 

[ CustomJedisSentinelPool.java ]

sentinel 로부터 master redis를 가져오는 객체.

listener를 멀티스레드로 동작시켜, redis 서버 상태가 변할지라도 redis master를 가져올 수 있게 하는 객체.

 

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
package ~.utils;
 
 
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
 
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolAbstract;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
 
public class CustomJedisSentinelPool extends JedisPoolAbstract {
 
      protected GenericObjectPoolConfig poolConfig;
 
      protected int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
      protected int soTimeout = Protocol.DEFAULT_TIMEOUT;
 
      protected String password;
      protected String sentinelPassword;
      
      protected int database = Protocol.DEFAULT_DATABASE;
 
      protected String clientName;
 
      protected Set<MasterListener> masterListeners = new HashSet<MasterListener>();
 
      protected Logger log = LoggerFactory.getLogger(getClass().getName());
 
      private volatile JedisFactory factory;
      private volatile HostAndPort currentHostMaster;
      
      private final Object initPoolLock = new Object();
      
      public void setSentinelPassword(String sentinelPassword){
          this.sentinelPassword = sentinelPassword;
      }
      
      public CustomJedisSentinelPool(String masterName, Set<String> sentinels,
          final GenericObjectPoolConfig poolConfig) {
        this(masterName, sentinels, poolConfig, Protocol.DEFAULT_TIMEOUT, nullnull,
            Protocol.DEFAULT_DATABASE);
      }
 
      public CustomJedisSentinelPool(String masterName, Set<String> sentinels) {
        this(masterName, sentinels, new GenericObjectPoolConfig(), Protocol.DEFAULT_TIMEOUT, nullnull,
            Protocol.DEFAULT_DATABASE);
      }
 
      public CustomJedisSentinelPool(String masterName, Set<String> sentinels, String password) {
        this(masterName, sentinels, new GenericObjectPoolConfig(), Protocol.DEFAULT_TIMEOUT, password);
      }
 
      public CustomJedisSentinelPool(String masterName, Set<String> sentinels,
          final GenericObjectPoolConfig poolConfig, int timeout, final String password) {
        this(masterName, sentinels, poolConfig, timeout, password, null, Protocol.DEFAULT_DATABASE);
      }
 
      public CustomJedisSentinelPool(String masterName, Set<String> sentinels,
          final GenericObjectPoolConfig poolConfig, final int timeout) {
        this(masterName, sentinels, poolConfig, timeout, nullnull, Protocol.DEFAULT_DATABASE);
      }
 
      public CustomJedisSentinelPool(String masterName, Set<String> sentinels,
          final GenericObjectPoolConfig poolConfig, final String password) {
        this(masterName, sentinels, poolConfig, Protocol.DEFAULT_TIMEOUT, password);
      }
 
      public CustomJedisSentinelPool(String masterName, Set<String> sentinels,
          final GenericObjectPoolConfig poolConfig, int timeout, final String password, final String sentinelPassword ,
          final int database) {
        this(masterName, sentinels, poolConfig, timeout, timeout, password, sentinelPassword, database);
      }
 
      public CustomJedisSentinelPool(String masterName, Set<String> sentinels,
          final GenericObjectPoolConfig poolConfig, int timeout, final String password, final String sentinelPassword,
          final int database, final String clientName) {
        this(masterName, sentinels, poolConfig, timeout, timeout, password, sentinelPassword, database, clientName);
      }
 
      public CustomJedisSentinelPool(String masterName, Set<String> sentinels,
          final GenericObjectPoolConfig poolConfig, final int timeout, final int soTimeout,
          final String password, final String sentinelPassword, final int database) {
        this(masterName, sentinels, poolConfig, timeout, soTimeout, password, sentinelPassword, database, null);
      }
 
      public CustomJedisSentinelPool(String masterName, Set<String> sentinels,
          final GenericObjectPoolConfig poolConfig, final int connectionTimeout, final int soTimeout,
          final String password, final String sentinelPassword, final int database, final String clientName) {
        this.poolConfig = poolConfig;
        this.connectionTimeout = connectionTimeout;
        this.soTimeout = soTimeout;
        this.password = password;
        this.sentinelPassword = sentinelPassword;
        this.database = database;
        this.clientName = clientName;
 
        HostAndPort master = initSentinels(sentinels, masterName);
        initPool(master);
      }
 
      @Override
      public void destroy() {
        for (MasterListener m : masterListeners) {
          m.shutdown();
        }
 
        super.destroy();
      }
 
      public HostAndPort getCurrentHostMaster() {
        return currentHostMaster;
      }
 
      private void initPool(HostAndPort master) {
        synchronized(initPoolLock){
          if (!master.equals(currentHostMaster)) {
            currentHostMaster = master;
            if (factory == null) {
              factory = new JedisFactory(master.getHost(), master.getPort(), connectionTimeout,
                  soTimeout, password, database, clientName);
              initPool(poolConfig, factory);
            } else {
              factory.setHostAndPort(currentHostMaster);
              // although we clear the pool, we still have to check the
              // returned object
              // in getResource, this call only clears idle instances, not
              // borrowed instances
              internalPool.clear();
            }
 
//            log.info("Created JedisPool to master at " + master);
          }
        }
      }
 
      private HostAndPort initSentinels(Set<String> sentinels, final String masterName) {
 
        HostAndPort master = null;
        boolean sentinelAvailable = false;
 
//        log.info("Trying to find master from available Sentinels...");
        
        for (String sentinel : sentinels) {
          final HostAndPort hap = HostAndPort.parseString(sentinel);
 
//          log.debug("Connecting to Sentinel {}", hap);
 
          Jedis jedis = null;
          try {
            jedis = new Jedis(hap);
            
            if(this.sentinelPassword != null && !this.sentinelPassword.isEmpty()){
                jedis.auth(this.sentinelPassword);
            }
            
            List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
 
            // connected to sentinel...
            sentinelAvailable = true;
 
            if (masterAddr == null || masterAddr.size() != 2) {
//              log.warn("Can not get master addr, master name: {}. Sentinel: {}", masterName, hap);
              continue;
            }
 
            master = toHostAndPort(masterAddr);
//            log.debug("Found Redis master at {}", master);
            break;
          } catch (JedisException e) {
            // resolves #1036, it should handle JedisException there's another chance
            // of raising JedisDataException
            log.warn(
              "Cannot get master address from sentinel running @ {}. Reason: {}. Trying next one.", hap,
              e.toString());
          } finally {
            if (jedis != null) {
              jedis.close();
            }
          }
        }
 
        if (master == null) {
          if (sentinelAvailable) {
            // can connect to sentinel, but master name seems to not
            // monitored
            throw new JedisException("Can connect to sentinel, but " + masterName
                + " seems to be not monitored...");
          } else {
            throw new JedisConnectionException("All sentinels down, cannot determine where is "
                + masterName + " master is running...");
          }
        }
 
//        log.info("Redis master running at " + master + ", starting Sentinel listeners...");
 
        for (String sentinel : sentinels) {
          final HostAndPort hap = HostAndPort.parseString(sentinel);
          MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort());
          // whether MasterListener threads are alive or not, process can be stopped
          masterListener.setDaemon(true);
          masterListeners.add(masterListener);
          masterListener.start();
        }
 
        return master;
      }
 
      private HostAndPort toHostAndPort(List<String> getMasterAddrByNameResult) {
        String host = getMasterAddrByNameResult.get(0);
        int port = Integer.parseInt(getMasterAddrByNameResult.get(1));
 
        return new HostAndPort(host, port);
      }
 
      @Override
      public Jedis getResource() {
        while (true) {
            try {
                Jedis jedis = super.getResource();
                jedis.setDataSource(this);
                
                // get a reference because it can change concurrently
                final HostAndPort master = currentHostMaster;
                final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(), jedis.getClient()
                        .getPort());
                
                if (master.equals(connection)) {
                    // connected to the correct master
                    return jedis;
                } else {
                    returnBrokenResource(jedis);
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw e;
            }
        }
      }
 
      @Override
      protected void returnBrokenResource(final Jedis resource) {
        if (resource != null) {
          returnBrokenResourceObject(resource);
        }
      }
 
      @Override
      protected void returnResource(final Jedis resource) {
        if (resource != null) {
          resource.resetState();
          returnResourceObject(resource);
        }
      }
 
      protected class MasterListener extends Thread {
 
        protected String masterName;
        protected String host;
        protected int port;
        protected long subscribeRetryWaitTimeMillis = 5000;
        protected volatile Jedis j;
        protected AtomicBoolean running = new AtomicBoolean(false);
 
        protected MasterListener() {
        }
 
        public MasterListener(String masterName, String host, int port) {
          super(String.format("MasterListener-%s-[%s:%d]", masterName, host, port));
          this.masterName = masterName;
          this.host = host;
          this.port = port;
        }
 
        public MasterListener(String masterName, String host, int port,
            long subscribeRetryWaitTimeMillis) {
          this(masterName, host, port);
          this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis;
        }
 
        @Override
        public void run() {
 
          running.set(true);
 
          while (running.get()) {
 
            j = new Jedis(host, port);
            if(sentinelPassword != null && !sentinelPassword.isEmpty()){
                j.auth(sentinelPassword);
            }
            try {
              // double check that it is not being shutdown
              if (!running.get()) {
                break;
              }
              
              /*
               * Added code for active refresh
               */
              List<String> masterAddr = j.sentinelGetMasterAddrByName(masterName);  
              if (masterAddr == null || masterAddr.size() != 2) {
                log.warn("Can not get master addr, master name: {}. Sentinel: {}:{}.",masterName,host,port);
              }else{
                  initPool(toHostAndPort(masterAddr)); 
              }
 
              j.subscribe(new JedisPubSub() {
                @Override
                public void onMessage(String channel, String message) {
                  log.debug("Sentinel {}:{} published: {}.", host, port, message);
 
                  String[] switchMasterMsg = message.split(" ");
 
                  if (switchMasterMsg.length > 3) {
 
                    if (masterName.equals(switchMasterMsg[0])) {
                      initPool(toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4])));
                    } else {
                      log.debug(
                        "Ignoring message on +switch-master for master name {}, our master name is {}",
                        switchMasterMsg[0], masterName);
                    }
 
                  } else {
                    log.error(
                      "Invalid message received on Sentinel {}:{} on channel +switch-master: {}", host,
                      port, message);
                  }
                }
              }, "+switch-master");
 
            } catch (JedisException e) {
 
              if (running.get()) {
                log.error("Lost connection to Sentinel at {}:{}. Sleeping 5000ms and retrying.", host,
                  port, e);
                try {
                  Thread.sleep(subscribeRetryWaitTimeMillis);
                } catch (InterruptedException e1) {
                  log.error("Sleep interrupted: ", e1);
                }
              } else {
                log.debug("Unsubscribing from Sentinel at {}:{}", host, port);
              }
            } finally {
              j.close();
            }
          }
        }
 
        public void shutdown() {
          try {
            log.debug("Shutting down listener on {}:{}", host, port);
            running.set(false);
            // This isn't good, the Jedis object is not thread safe
            if (j != null) {
              j.disconnect();
            }
          } catch (Exception e) {
            log.error("Caught exception while shutting down: ", e);
          }
        }
      }
    }
cs

*

jedis jar의 JedisSentinelPool.class 를 열어보면 아래 소스와 거의 일치한다..

그럼에도 불구하고 기존 구현된 jedis jar의 JedisSentinelPool 을 사용하지 않고 굳이 JedisPoolAbstract을 상속받아 구현하게 된 이유는, Jedis jar의 JedisSentinelPool은 Sentinel에 패스워드 설정이 잡혀있는 경우, initSentinel(..) method내에서 sentinel 로 부터 redis master 정보를 가져오는 jedis.sentinelGetMasterAddrByName(..) method 에서 Exception 이 나기 때문.. (auth fail exception)

 

실질적으로 수정한 부분은 jedis 객체로부터 master 정보를 가져오는 sentinelGetMasterAddrByName(..)메소드를 호출하기 전에 jedis객체에 sentinel 패스워드를 set해준게 전부..

(163~165 line, 298~301 line)

 

 

[ JedisFactory.java ]

CustomJedisSentinelPool.java 에서 사용하는 객체로, 

Jedis jar 에 구현되어 있지만 접근제한자가 default이므로 동일한 package가 아닌 CustomJedisSentinelPool에선 해당 객체를 생성할 수 없어 CustomJedisSentinelPool과 동일한 package 밑에 JedisFactory를 만들어 줌.

* Jedis jar 내의 JedisFactory.class 와 동일한 소스

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
package ~.utils;
 
import java.net.URI;
import java.util.concurrent.atomic.AtomicReference;
 
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocketFactory;
 
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
 
import redis.clients.jedis.BinaryJedis;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.InvalidURIException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.util.JedisURIHelper;
 
/**
 * PoolableObjectFactory custom impl.
 */
class JedisFactory implements PooledObjectFactory<Jedis> {
  private final AtomicReference<HostAndPort> hostAndPort = new AtomicReference<HostAndPort>();
  private final int connectionTimeout;
  private final int soTimeout;
  private final String password;
  private final int database;
  private final String clientName;
  private final boolean ssl;
  private final SSLSocketFactory sslSocketFactory;
  private final SSLParameters sslParameters;
  private final HostnameVerifier hostnameVerifier;
 
  JedisFactory(final String host, final int port, final int connectionTimeout,
      final int soTimeout, final String password, final int database, final String clientName) {
    this(host, port, connectionTimeout, soTimeout, password, database, clientName,
        falsenullnullnull);
  }
 
  JedisFactory(final String host, final int port, final int connectionTimeout,
      final int soTimeout, final String password, final int database, final String clientName,
      final boolean ssl, final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
      final HostnameVerifier hostnameVerifier) {
    this.hostAndPort.set(new HostAndPort(host, port));
    this.connectionTimeout = connectionTimeout;
    this.soTimeout = soTimeout;
    this.password = password;
    this.database = database;
    this.clientName = clientName;
    this.ssl = ssl;
    this.sslSocketFactory = sslSocketFactory;
    this.sslParameters = sslParameters;
    this.hostnameVerifier = hostnameVerifier;
  }
 
  JedisFactory(final URI uri, final int connectionTimeout, final int soTimeout,
      final String clientName) {
    this(uri, connectionTimeout, soTimeout, clientName, nullnullnull);
  }
 
  JedisFactory(final URI uri, final int connectionTimeout, final int soTimeout,
      final String clientName, final SSLSocketFactory sslSocketFactory,
      final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {
    if (!JedisURIHelper.isValid(uri)) {
      throw new InvalidURIException(String.format(
        "Cannot open Redis connection due invalid URI. %s", uri.toString()));
    }
 
    this.hostAndPort.set(new HostAndPort(uri.getHost(), uri.getPort()));
    this.connectionTimeout = connectionTimeout;
    this.soTimeout = soTimeout;
    this.password = JedisURIHelper.getPassword(uri);
    this.database = JedisURIHelper.getDBIndex(uri);
    this.clientName = clientName;
    this.ssl = JedisURIHelper.isRedisSSLScheme(uri);
    this.sslSocketFactory = sslSocketFactory;
    this.sslParameters = sslParameters;
    this.hostnameVerifier = hostnameVerifier;
  }
 
  public void setHostAndPort(final HostAndPort hostAndPort) {
    this.hostAndPort.set(hostAndPort);
  }
 
  @Override
  public void activateObject(PooledObject<Jedis> pooledJedis) throws Exception {
    final BinaryJedis jedis = pooledJedis.getObject();
    if (jedis.getDB() != database) {
      jedis.select(database);
    }
 
  }
 
  @Override
  public void destroyObject(PooledObject<Jedis> pooledJedis) throws Exception {
    final BinaryJedis jedis = pooledJedis.getObject();
    if (jedis.isConnected()) {
      try {
        try {
          jedis.quit();
        } catch (Exception e) {
        }
        jedis.disconnect();
      } catch (Exception e) {
 
      }
    }
 
  }
 
  @Override
  public PooledObject<Jedis> makeObject() throws Exception {
    final HostAndPort hostAndPort = this.hostAndPort.get();
    final Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout,
        soTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
 
    try {
      jedis.connect();
      if (password != null) {
        jedis.auth(password);
      }
      if (database != 0) {
        jedis.select(database);
      }
      if (clientName != null) {
        jedis.clientSetname(clientName);
      }
    } catch (JedisException je) {
      jedis.close();
      throw je;
    }
 
    return new DefaultPooledObject<Jedis>(jedis);
 
  }
 
  @Override
  public void passivateObject(PooledObject<Jedis> pooledJedis) throws Exception {
    // TODO maybe should select db 0? Not sure right now.
  }
 
  @Override
  public boolean validateObject(PooledObject<Jedis> pooledJedis) {
    final BinaryJedis jedis = pooledJedis.getObject();
    try {
      HostAndPort hostAndPort = this.hostAndPort.get();
 
      String connectionHost = jedis.getClient().getHost();
      int connectionPort = jedis.getClient().getPort();
 
      return hostAndPort.getHost().equals(connectionHost)
          && hostAndPort.getPort() == connectionPort && jedis.isConnected()
          && jedis.ping().equals("PONG");
    } catch (final Exception e) {
      return false;
    }
  }
}
 
cs


[ RedisUtil.java ]

CustomJedisSentinelPool 을 사용하여 redis master 객체를 얻어오고, redis에 값을 넣고 빼는 등 실질적으로 redis를 사용하는 클래스

 

별도의 property로 sentinel ip:port, sentinel password, redis password, database number를 관리.

* sentinel 로 부터 redis 객체를 가져오기 때문에 redis ip:port 는 별도로 관리해줄 필요가 없음.

 

getConnection() : pool 객체 생성(내부적으로 listener 생성 및 동작)

getValue(String key) : key 기준으로 값을 가져오는 메소드

setValue(String key, String value, int sec) : key, value 를 특정시간(sec)동안 redis 에 저장하는 메소드

disConnection(..) : 사용한 redis 와의 접속 해제 및 자원 반납

                        (* 자원 반납 제대로 하지 않을 경우 server exhausted Exception 발생)

 

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
package ~.utils;
 
import java.util.HashSet;
import java.util.Set;
 
import javax.annotation.PostConstruct;
 
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
 
import redis.clients.jedis.Jedis;
 
import ~.APIException;
 
@Component("RedisUtil")
public class RedisUtil {
    
    @Value("#{properties['sentinel.Addr']}")
    private String SENTINEL_ADDR;
    
    @Value("#{properties['sentinel.master']}")
    private String MASTER_NAME;
    
    @Value("#{properties['sentinel.pa']}")
    private String SENTINEL_P;
    
    @Value("#{properties['sentinel.redis.pa']}")
    private String REDIS_P;
    
    @Value("#{properties['sentinel.db']}")
    private int DATABASE;
 
    private Set<String> sentinels;
    
    private final String deli = "\\^";
    
    private CustomJedisSentinelPool pool;
    
    protected static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);
    
    @PostConstruct
    private void getConnection(){
        
        if(pool != null){
            return;
        }
        
        
        if(sentinels == null){ 
        
            sentinels = new HashSet<String>();
        }
        
        if(sentinels.isEmpty()){
        
            String[] sentinelArr = this.SENTINEL_ADDR.split(this.deli);
            
            for(String sentinelNode : sentinelArr ){
        
                sentinels.add(sentinelNode);
            }
        }
        
        logger.debug("redis master name : " + this.MASTER_NAME);
        logger.debug("redis redis p : " + this.REDIS_P);
        logger.debug("redis sentinel p : " + this.SENTINEL_P);
        logger.debug("redis database : " + this.DATABASE);
        
        GenericObjectPoolConfig gopc = new GenericObjectPoolConfig();
        gopc.setMaxWaitMillis(1000);
        gopc.setBlockWhenExhausted(false);
        pool = new CustomJedisSentinelPool(this.MASTER_NAME, 
                sentinels, gopc, 500this.REDIS_P, this.SENTINEL_P, this.DATABASE);
        
        logger.info("get Connection 3");
    }
    
    public String getValue(String key){
        String result="";
 
        Jedis jedis = null;
        
        
        try{
            jedis = this.pool.getResource();
        
        } catch (Exception e){
            logger.error("redis connection fail : " + e);
        }
        
        result = jedis.get(key);
        logger.debug("retrived value : " + result);
        
        
        this.disconnection(jedis);
        
        return result;
    }
    
    public boolean setValue(String key, String value, int sec){
        boolean result=true;
        Jedis jedis = null;
        
 
        try{
            jedis = this.pool.getResource();
            String setexResult = jedis.setex(key, sec, value);
        
        
            if(!"OK".equalsIgnoreCase(setexResult)){
                throw new APIException();
            }
            
        } catch (APIException ae){
            logger.warn("set redis data fail : " + ae);
            result = false;
        } catch (Exception e){
            logger.error("exception in set value : " + e);
            result = false;
        }
        logger.info("set value 3");
        this.disconnection(jedis);
        
        return result;
    }
        
    private void disconnection(Jedis jedis){
        try{
 
                if(jedis != null){
                    jedis.disconnect();
                    jedis.close();
                    jedis = null;
                }
 
        } catch (Exception e){
            logger.error("redis disconnection fail : {}" , e);
        }
    }
 
    
}
 
cs

 

RedisTemplate 객체를 사용할 경우 값을 다양한 방법으로 넣고 뺄 수 있던데,

해당 부분은 나중에 공부를 더 해보는걸로..

 

반응형

+ Recent posts