java redis 通用組建

前言

redis 是個幹嗎的 ? 看官網:http://redis.io/java

一句話,這裏redis當作緩存(或者原本就是), 利用java寫一個jedis的讀寫的組建redis

1. 組建代碼

no bb, review codespring

package com.mushroom.hui.common.cache;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by july on 16/4/1.
 */
public class CacheWrapper implements InitializingBean {
    private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CacheWrapper.class);

    private JedisPool masterJedis;
    private List<JedisPool> slaveJedisList;

    private String masterConf;
    private String slaveConf;
    private int maxIdle;
    private int minIdle;
    private int maxTotal;
    private int timeout;
    private int database;

    public String getMasterConf() {
        return masterConf;
    }

    public void setMasterConf(String masterConf) {
        this.masterConf = masterConf;
    }

    public String getSlaveConf() {
        return slaveConf;
    }

    public void setSlaveConf(String slaveConf) {
        this.slaveConf = slaveConf;
    }

    public int getMaxIdle() {
        return maxIdle;
    }

    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
    }

    public int getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

    public int getMaxTotal() {
        return maxTotal;
    }

    public void setMaxTotal(int maxTotal) {
        this.maxTotal = maxTotal;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public int getDatabase() {
        return database;
    }

    public void setDatabase(int database) {
        this.database = database;
    }

    private class ConfAddress {
        private String ip;
        private int port;

        public String getIp() {
            return ip;
        }

        public void setIp(String ip) {
            this.ip = ip;
        }

        public int getPort() {
            return port;
        }

        public void setPort(int port) {
            this.port = port;
        }

        public ConfAddress(String conf) {
            init(conf);
        }

        private void init(String conf) {
            if (!StringUtils.contains(conf, ":")) {
                return;
            }

            String[] pair = StringUtils.split(conf, ":");
            if (pair == null || pair.length != 2) {
                return;
            }

            this.ip = pair[0];
            this.port = Integer.parseInt(pair[1]);
        }

        public boolean isIllegal() {
            return StringUtils.isBlank(ip) || port <= 0;
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // 池基本配置
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal <= 0 ? 300 : maxTotal);
        config.setMaxIdle(maxIdle <= 0 ? 10 : maxIdle);
        config.setMinIdle(minIdle <= 0 ? 3 : minIdle);
        config.setMaxWaitMillis(timeout <= 0 ? 1000 : timeout);
        config.setTestOnBorrow(false);


        // init master jedis
        ConfAddress masterAddr = new ConfAddress(masterConf);
        if (masterAddr.isIllegal()) {
            throw new JedisException("master jedis conf is error!");
        }
        masterJedis = new JedisPool(config, masterAddr.getIp(), masterAddr.getPort(), this.timeout, null, this.database);


        // init slave jedis
        String[] slaveConfs = StringUtils.split(slaveConf, ",");
        if (slaveConfs == null || slaveConfs.length == 0) {
            slaveJedisList = Collections.emptyList();
        }
        slaveJedisList = new ArrayList<>(slaveConfs.length);
        ConfAddress slaveTmpAddr;
        for (String conf: slaveConfs) {
            slaveTmpAddr = new ConfAddress(conf);
            if(slaveTmpAddr.isIllegal()) {
                continue;
            }
            JedisPool slaveJedis = new JedisPool(config, slaveTmpAddr.getIp(), slaveTmpAddr.getPort(),
                    this.timeout, null, this.database);
            slaveJedisList.add(slaveJedis);
        }
    }




    final int MASTER_JEDIS = 0;
    final int SLAVE_JEIDS = 1;
    // 保證線程安全的自動技術器
    private AtomicInteger chooseCounter = new AtomicInteger();

    /**
     * 獲取使用的jedis,這裏採用標準的一主多備模式
     * @param type
     * @return
     */
    public JedisPool getJedisPool(int type) {
        if (type == MASTER_JEDIS) {
            return masterJedis;
        }

        if (CollectionUtils.isEmpty(slaveJedisList)) {
            return masterJedis;
        }


        final int chooseIndex = this.chooseCounter.incrementAndGet();
        final int index = chooseIndex % slaveJedisList.size();
        return slaveJedisList.get(index);
    }


    public String get(String key) {
        if(StringUtils.isBlank(key)) {
            throw new IllegalArgumentException("key is null!");
        }

        Jedis jedis = null;
        JedisPool pool = getJedisPool(SLAVE_JEIDS);
        try {
            jedis = pool.getResource();
            String ans = jedis.get(key);
            return ans;
        } catch (Exception e) {
            logger.error("get string from cache error!");
            logger.error("Exception: {}", e);
            return null;
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 採用fastJson做爲序列化工具
     * @param key cache key
     * @param clz object class
     * @param <T> type
     * @return object in cache!
     */
    public <T> T getObject(String key, Class<T> clz) {
        if(StringUtils.isBlank(key)) {
            throw new IllegalArgumentException("key is null");
        }

        Jedis jedis = null;
        JedisPool pool = getJedisPool(SLAVE_JEIDS);
        try {
            jedis = pool.getResource();
            String ans = jedis.get(key);
            if (StringUtils.isBlank(ans) || "nil".equals(ans)) {
                return null;
            }

            T obj = JSON.parseObject(ans, clz);
            return obj;
        } catch (Exception e) {
            logger.error("get object from cache error!");
            logger.error("Exception: {}", e);
            return null;
        } finally {
            if(jedis != null) {
                jedis.close();
            }
        }
    }


    public boolean set(String key, String value, int expire) {
        if(StringUtils.isBlank(key) || StringUtils.isBlank(value) || expire <= 0) {
            throw new IllegalArgumentException("key || value || expire are illegal");
        }

        Jedis jedis = null;
        JedisPool pool = getJedisPool(MASTER_JEDIS);
        try {
            jedis = pool.getResource();
            String ans = jedis.setex(key, expire, value);
        } catch (Exception e) {
            logger.error("set string into cache error!");
            logger.error("Exception: {}", e);
            return false;
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return true;
    }


    public boolean setObject(String key, Object value, int expire) {
        if (StringUtils.isBlank(key) || value == null || expire <= 0) {
            throw new IllegalArgumentException("key value expire are illegal!");
        }

        Jedis jedis = null;
        JedisPool pool = getJedisPool(MASTER_JEDIS);
        try {
            jedis = pool.getResource();
            String data = JSON.toJSONString(value);
            jedis.setex(key, expire, data);
        } catch (Exception e) {
            logger.error("set object into cache error!");;
            logger.error("Exception: {}", e);
            return false;
        } finally {
            if(jedis != null) {
                jedis.close();
            }
        }
        return true;
    }

}

配置文件 cache.xmlapache

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:beans="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">

    <!-- 下面的這個掃描很重要,用於創建uri與controller指尖的映射 -->
    <context:component-scan base-package="com.mushroom.hui"/>
    <context:annotation-config/>
    <beans:annotation-driven/>

    <bean id="cachePropertyConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="order" value="2" />
        <property name="ignoreUnresolvablePlaceholders" value="true" />
        <property name="locations">
            <list>
                <value>classpath*:conf/cache.properties</value>
            </list>
        </property>
    </bean>

    <bean id="cacheWrapper" class="com.mushroom.hui.common.cache.CacheWrapper">
        <property name="masterConf" value="${cache.masterConfig}" />
        <property name="slaveConf" value="${cache.slaveConfigs}" />
        <property name="timeout" value="${cache.timeout}" />
        <property name="database" value="${cache.database}" />
        <property name="maxTotal" value="${cache.maxTotal}" />
        <property name="maxIdle" value="${cache.maxIdle}" />
        <property name="minIdle" value="${cache.minIdle}" />
    </bean>
</beans>

cache.propertiesjson

cache.masterConfig=127.0.0.1:6379
cache.slaveConfigs=127.0.0.1:6379
cache.passwd=123
cache.timeout=60
cache.database=5
cache.maxTotal=10
cache.maxIdle=4
cache.minIdle=2

test測試文件緩存

package com.mushroom.hui.test;

import com.mushroom.hui.common.cache.CacheWrapper;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by yihui on 16/4/2.
 */
public class CacheWrapperTest {

    private final static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CacheWrapperTest.class);

    private CacheWrapper cacheWrapper;

    @Before
    public void init() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:spring/*.xml");
        cacheWrapper = applicationContext.getBean("cacheWrapper", CacheWrapper.class);
    }

    private class Point {
        String name;
        Float x;
        float y;

        public Point(String name, Float x, float y) {
            this.name = name;
            this.x = x;
            this.y = y;
        }

        public float getY() {
            return y;
        }

        public void setY(float y) {
            this.y = y;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Float getX() {
            return x;
        }

        public void setX(Float x) {
            this.x = x;
        }
    }

    @Test
    public void cacheTest() {
        String key = "hello_001";
        String value = "woca";

        boolean ans = cacheWrapper.set(key, value, 600);
        logger.info("The result is: {}", ans);

        Object obj = cacheWrapper.get(key);
        logger.info("The obj is {}", obj);


        Map<String, List<Point>> map = new HashMap<>(2);
        List<Point> pointList = new ArrayList<>();
        pointList.add(new Point("a1", 123f, 123));
        pointList.add(new Point("a2", 10f, 20f));
        map.put("a", pointList);

        List<Point> pointList2 = new ArrayList<>();
        pointList2.add(new Point("b1", 10f, 110f));
        pointList2.add(new Point("b2", -10f, -20f));
        pointList2.add(new Point("b3", -100f, -200f));
        map.put("b2", pointList2);

        String key2 = "world_001";
        boolean ans2 = cacheWrapper.setObject(key2, map, 600);
        logger.info("The ans2 is  {}", ans2);
        Object result = cacheWrapper.getObject(key2, map.getClass());
        logger.info("Thre result2 is {}", result);
    }
}

2. 代碼解析

因爲項目工程是機遇spring框架的,因此上面的代碼中能夠很清晰的看到soring bean的相關內容安全

若是不瞭解spring,也不想使用spring相關的東西,能夠無視上面的xml, properties 文件,直接用上面的java類便可(附件給出相關代碼)併發

- 一主多備模式

上面的組建支持一主多備的使用方式,寫maser,讀slaveapp

- 代碼解析

  1. 初始話redis相關的配置參數
  1. 初始化 JedisPool 實例框架

  2. get/set設計與實現

  3. 參數初始化 redis相關的經常使用參數:JedisPoolConfig, (masterAddr, port), (slaveAddr, port), timeout

maxTotal 最多的jedis實例
  maxIdel, minIdel 最大最小的空閒jedis實例
  masterConfig: master redis的ip(or域名)和端口號
  slaveConfig: slave redis的ip(or域名)和端口號
  timeout: 連接超時時間(大於這個時間則拋鏈接超時異常)

這些參數的初始化是由spring框架完成的,在bean的聲明處,即將properties中的相關參數注入到CacheWrapper中的成員變量中

why redisPool not jedis? - 併發量大時(qps高),耽擱jedis處理不過來!!! - 使用pool減小了jedis實例的頻繁銷燬和新建的開銷

  1. JedisPool 初始化 聚焦 afterPropertiesSet方法,jedisPool的初始化主要調用的是 public JedisPool(final GenericObjectPoolConfig poolConfig, final String host, int port, int timeout, final String password, final int database);

這段代碼邏輯也很簡單,稍稍注意一下slaveJedisPool 是一個列表(一主多備嘛)

  1. get/set 方法 在聚焦get/set方法以前須要先關注一下 getJedisPool()方法, why? 代碼中有一個masterPool和多個slavePool,那麼選擇哪個來使用,也是一個問題
  • getJedisPool 邏輯 :

    • 寫redis,選用 masterPool
    • 讀redis,輪詢(利用了線程安全的AutomicInteger)使用slavePool,沒有salvePool時,讀masterPool
  • get(key) / getObject(key, clz) 從redis讀

    • 獲取jedisPool
    • get jedis實例
    • 從redis中獲取存儲對象
    • 處理對象(如getObject中使用fastJson反序列化),返回
    • 關閉jedis實例(實際上是扔到了pool)
  • set(key, value, expire) / setObject(key, object, expire) 寫入redis

    • 獲取jedisPool
    • get jedis實例
    • 處理object(fastJson序列化)
    • 塞入redis
    • 關閉redis實例

基本流程完結

相關文章
相關標籤/搜索