背景:不久前單位上線一款應用,上了生產環境之後,沒過多久,便吃掉了伺服器所有的記憶體,最後導致網站服務掛了。
在解決了這一問題之後,我發現這其實是典型的一單例模式,現分享一下。
之前存在問題的老程式碼如下:
這是導致問題所在的那個關鍵方法
public synchronized static JedisCluster getJedisCluster() { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(MAX_ACTIVE); config.setMaxIdle(MAX_IDLE); config.setMaxWaitMillis(MAX_WAIT); config.setTestOnBorrow(TEST_ON_BORROW); // 叢集模式 JedisPoolConfig poolConfig = new JedisPoolConfig(); Set<HostAndPort> nodes = new HashSet<HostAndPort>(); HostAndPort hostAndPort1 = new HostAndPort("伺服器地址1", 埠1); HostAndPort hostAndPort2 = new HostAndPort("伺服器地址2", 埠2); HostAndPort hostAndPort3 = new HostAndPort("伺服器地址3", 埠3); nodes.add(hostAndPort1); nodes.add(hostAndPort2); nodes.add(hostAndPort3); JedisCluster jedisCluster = new JedisCluster(nodes, poolConfig); return jedisCluster; }
以上這段程式碼是有問題的,大家看出來了嗎?
問題在於,雖然方法宣告為synchronized static,但是在併發多執行緒的情況下,並不能保證每個使用者執行緒只生成一個JedisCluster的例項。
這樣就會導致每個執行緒都會建立jedisCluster的例項,就會消耗記憶體,而且這塊記憶體又沒有被及時地釋放掉,導致多使用者併發以後,快速吃光了伺服器的記憶體。
解決方法就是使用單例模式,把JedisCluster作為static的類成員,且使用懶漢單例模式,程式碼如下:
public class OuterClass{ ... private static JedisCluster jedisCluster = null; ... public synchronized static JedisCluster getJedisCluster() { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(MAX_ACTIVE); config.setMaxIdle(MAX_IDLE); config.setMaxWaitMillis(MAX_WAIT); config.setTestOnBorrow(TEST_ON_BORROW); // 叢集模式 JedisPoolConfig poolConfig = new JedisPoolConfig(); Set<HostAndPort> nodes = new HashSet<HostAndPort>(); HostAndPort hostAndPort1 = new HostAndPort("伺服器地址1", 埠1); HostAndPort hostAndPort2 = new HostAndPort("伺服器地址2", 埠2); HostAndPort hostAndPort3 = new HostAndPort("伺服器地址3", 埠3); nodes.add(hostAndPort1); nodes.add(hostAndPort2); nodes.add(hostAndPort3); // 只有當jedisCluster為空時才例項化 if (jedisCluster == null) { jedisCluster = new JedisCluster(nodes, poolConfig); } return jedisCluster; } }
這樣就會保證即使在高併發的環境下,所有使用者執行緒還是隻會擁有一個JedisCluster的例項。