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实例

基本流程完结