Redis简介
Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
Redis优势
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s
- 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性
Redis集群服务器搭建
Spring集成JedisCluster
- maven依赖:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
- 增加spring配置
<bean name="genericObjectPoolConfig" class="org.apache.commons.pool2.impl.GenericObjectPoolConfig"> //对象池 <property name="maxWaitMillis" value="-1" /> //默认为-1 <property name="maxTotal" value="1000" /> //最大连接 <property name="minIdle" value="8" /> //最小空闲对象 <property name="maxIdle" value="100" /> //最大空闲对象 </bean> <bean id="jedisCluster" class="xxx.xxx.xxxxxx.infrastructure.util.redis.JedisClusterFactory"> <property name="addressConfig"> <value>file:/web/profile/beautiful/connect-redis.properties</value> //配置文件 </property> <property name="addressKeyPrefix" value="address" /> //属性文件里 key的前缀 <property name="timeout" value="300000" /> <property name="maxRedirections" value="6" /> <property name="genericObjectPoolConfig" ref="genericObjectPoolConfig" /> </bean>
- connect-redis.properties配置文件(6个节点)
address1=192.168.21.101:7000 address2=192.168.21.101:7001 address3=192.168.21.102:7002 address4=192.168.21.102:7003 address5=192.168.21.103:7004 address6=192.168.21.103:7005
- 增加JedisClusterFactory类
import java.util.HashSet; import java.util.Properties; import java.util.Set; import java.util.regex.Pattern; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; public class JedisClusterFactory implements FactoryBean<JedisCluster>, InitializingBean { private Resource addressConfig; private String addressKeyPrefix ; private JedisCluster jedisCluster; private Integer timeout; private Integer maxRedirections; private GenericObjectPoolConfig genericObjectPoolConfig; private Pattern p = Pattern.compile("^.+[:]\\d{1,5}\\s*$"); @Override public JedisCluster getObject() throws Exception { return jedisCluster; } @Override public Class<? extends JedisCluster> getObjectType() { return (this.jedisCluster != null ? this.jedisCluster.getClass() : JedisCluster.class); } @Override public boolean isSingleton() { return true; } private Set<HostAndPort> parseHostAndPort() throws Exception { try { Properties prop = new Properties(); prop.load(this.addressConfig.getInputStream()); Set<HostAndPort> haps = new HashSet<HostAndPort>(); for (Object key : prop.keySet()) { if (!((String) key).startsWith(addressKeyPrefix)) { continue; } String val = (String) prop.get(key); boolean isIpPort = p.matcher(val).matches(); if (!isIpPort) { throw new IllegalArgumentException("ip 或 port 不合法"); } String[] ipAndPort = val.split(":"); HostAndPort hap = new HostAndPort(ipAndPort[0], Integer.parseInt(ipAndPort[1])); haps.add(hap); } return haps; } catch (IllegalArgumentException ex) { throw ex; } catch (Exception ex) { throw new Exception("解析 jedis 配置文件失败", ex); } } @Override public void afterPropertiesSet() throws Exception { Set<HostAndPort> haps = this.parseHostAndPort(); jedisCluster = new JedisCluster(haps, timeout, maxRedirections,genericObjectPoolConfig); } public void setAddressConfig(Resource addressConfig) { this.addressConfig = addressConfig; } public void setTimeout(int timeout) { this.timeout = timeout; } public void setMaxRedirections(int maxRedirections) { this.maxRedirections = maxRedirections; } public void setAddressKeyPrefix(String addressKeyPrefix) { this.addressKeyPrefix = addressKeyPrefix; } public void setGenericObjectPoolConfig(GenericObjectPoolConfig genericObjectPoolConfig) { this.genericObjectPoolConfig = genericObjectPoolConfig; } }
- 编写redis工具类
import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisPool; import redis.clients.util.JedisClusterCRC16; import com.google.common.collect.Maps; @Component("redisTool") public class RedisTool { private static Logger logger = LoggerFactory.getLogger(RedisTool.class); /** * 集群客户端 */ private static JedisCluster jedisCluster; /** * redis哈希槽和ip:port的映射 */ private static TreeMap<Long, String> treeMap; private static Map<String, JedisPool> jedisMap; /** * 根据key获取对应的jedis实例 * @param key * @return */ public static Jedis getJedis(String key) { if(key == null) return null; if(treeMap == null) { synchronized (RedisTool.class) { if(treeMap == null){ parseAllSlotAndIport(); } } } //槽 int slot = JedisClusterCRC16.getSlot(key); //槽-端口映射 Entry<Long, String> entry = treeMap.lowerEntry(Long.valueOf(slot)); //槽对应的jedis Jedis jedis = jedisMap.get(entry.getValue()).getResource(); return jedis; } private static void parseAllSlotAndIport() { logger.info("redis开始哈希槽到ipport的映射"); jedisMap = jedisCluster.getClusterNodes(); treeMap = Maps.newTreeMap(); for(Entry<String, JedisPool> entry : jedisMap.entrySet()){ String ipAndPort = entry.getKey(); logger.info("redis服务器:" + ipAndPort + "哈希槽位映射"); TreeMap<Long, String> tree = parseSlotAndIport(ipAndPort); if(tree == null || tree.size() <= 0){ logger.info("redis服务器:" + ipAndPort + "哈希槽位映射异常,信息为空"); continue; } treeMap.putAll(tree); } logger.info("redis完成哈希槽到ipport的映射"); } private static TreeMap<Long, String> parseSlotAndIport(String ipAndPort) { TreeMap<Long, String> tree = new TreeMap<Long, String>(); String parts[] = ipAndPort.split(":"); HostAndPort anyHostAndPort = new HostAndPort(parts[0], Integer.parseInt(parts[1])); Jedis jedis = null; try{ jedis = new Jedis(anyHostAndPort.getHost(), anyHostAndPort.getPort()); List<Object> list = jedis.clusterSlots(); for (Object object : list) { List<Object> list1 = (List<Object>) object; List<Object> master = (List<Object>) list1.get(2); String hostAndPort = new String((byte[]) master.get(0)) + ":" + master.get(1); tree.put((Long) list1.get(0), hostAndPort); tree.put((Long) list1.get(1), hostAndPort); } }catch(Exception e){ logger.error("getSlotHostMap error",e); } return tree; }
@Autowiredpublic void setJedisCluster(JedisCluster jedisCluster){this.jedisCluster = jedisCluster;}public static void returnJedis(Jedis jedis) { jedis.close(); }
}public static JedisCluster getJedis() { return jedisCluster; }
- redis入库操作
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import com.ecpss.beautifulperson.domain.utils.Pager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cglib.beans.BeanMap; import org.springframework.stereotype.Component; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Pipeline; import redis.clients.jedis.Response; import com.ecpss.beautifulperson.domain.utils.StringUtils; import com.ecpss.beautifulperson.infrastructure.util.redis.RedisTool; import com.google.common.collect.Maps; @Component("redisUtil") public class RedisUtil { private static final Charset UTF_8 = Charset.forName("utf-8"); private static Logger logger = LoggerFactory.getLogger(RedisUtil.class); /************* STRING 操作 **************/ public boolean addString(String key, String value) { if (StringUtils.isBlank(key)) return false; logger.info("redis入库string, key:" + key + ",value:" + value); Jedis jedis = RedisTool.getJedis(key); String result = jedis.set(key, value); RedisTool.returnJedis(jedis); return "OK".equals(result); } public boolean addStringExipre(String key, String value, int second) { if (StringUtils.isBlank(key)) return false; logger.info("redis入库string, key:" + key + ",value:" + value); Jedis jedis = RedisTool.getJedis(key); String result = jedis.setex(key, second, value); RedisTool.returnJedis(jedis); return "OK".equals(result); } public String getString(String key) { if (StringUtils.isBlank(key)) return null; Jedis jedis = RedisTool.getJedis(key); String result = jedis.get(key); RedisTool.returnJedis(jedis); return result; } public Long increaseString(String key, int count) { if (StringUtils.isBlank(key)) { return null; } Jedis jedis = RedisTool.getJedis(key); Long result = jedis.incrBy(key, count); RedisTool.returnJedis(jedis); return result; } /***************** KEY操作 **************/ public boolean delKey(String key) { if (StringUtils.isBlank(key)) return true; logger.info("redis删除key, key:" + key ); Jedis jedis = RedisTool.getJedis(key); jedis.del(key); RedisTool.returnJedis(jedis); return true; } public boolean expire(String key, int second) { if (StringUtils.isBlank(key)) { return false; } logger.info("redis设置超时时间, key:" + key + ",second:" + second); Jedis jedis = RedisTool.getJedis(key); jedis.expire(key, second); RedisTool.returnJedis(jedis); return true; } public Set<String> keys(String pattern) { if (StringUtils.isBlank(pattern)) { return null; } Jedis jedis = RedisTool.getJedis(pattern); Set<String> set = jedis.keys(pattern); RedisTool.returnJedis(jedis); return set; } /****************** MAP 操作 ********************/ public void addMap(String key, Map<String, String> map) { if (StringUtils.isBlank(key) || map == null) return; logger.info("redis入库map, key:" + key + ",value:" + map); Jedis jedis = RedisTool.getJedis(key); jedis.hmset(key, map); RedisTool.returnJedis(jedis); } public boolean addMapVal(String key, String field, String value) { if (StringUtils.isBlank(key) || StringUtils.isBlank(key) || StringUtils.isBlank(key)) return false; logger.info("redis入库map单项值, key:" + key + ",field:" + field + ",value:" + value); Jedis jedis = RedisTool.getJedis(key); Long result = jedis.hsetnx(key, field, value); RedisTool.returnJedis(jedis); return result.intValue() ==1; } public boolean updateMapVal(String key, String field, String value){ if (StringUtils.isBlank(key) || StringUtils.isBlank(key) || StringUtils.isBlank(key)) return false; logger.info("redis更库map, key:" + key + ",field:" + field + ",value:" + value); Jedis jedis = RedisTool.getJedis(key); jedis.hset(key, field, value); RedisTool.returnJedis(jedis); return true; } public Map<String, String> getMap(String key) { if (StringUtils.isBlank(key)) return null; Jedis jedis = RedisTool.getJedis(key); Map<String, String> map = jedis.hgetAll(key); RedisTool.returnJedis(jedis); return map; } public String getMapField(String key, String field) { if(StringUtils.isBlank(key) || StringUtils.isBlank(field)) return null; Jedis jedis = RedisTool.getJedis(key); String result = jedis.hget(key, field); RedisTool.returnJedis(jedis); return result; } public List<String> getMapFields(String key, String... fields){ if(StringUtils.isBlank(key) || fields == null) return null; Jedis jedis = RedisTool.getJedis(key); List<String> result = jedis.hmget(key, fields); RedisTool.returnJedis(jedis); return result; } public Map<String, Map<String, String>> fetchGetMap(RedisKeyType keyType, String ...key) { if(key == null || keyType == null) return null; logger.info("redis管道入库map, key:" + key + ",keyType:" + keyType); Map<String, Response<Map<String, String>>> map = Maps.newHashMap(); Map<String, Map<String, String>> result = Maps.newHashMap(); Jedis jedis = RedisTool.getJedis(keyType.getText()); //管道批量操作 Pipeline pipeline = jedis.pipelined(); for (String string : key) { map.put(string, pipeline.hgetAll(RedisKeyUtil.getKeyByType(string, keyType))); } pipeline.sync(); for (Entry<String, Response<Map<String, String>>> entry : map.entrySet()) { result.put(entry.getKey(), entry.getValue().get()); } RedisTool.returnJedis(jedis); return result; } /*************** LIST 操作 ******************/ public void addList(String key, String... values) { if (StringUtils.isBlank(key) || values == null) { return; } logger.info("redis入库list, key:" + key + ",values:" + values); Jedis jedis = RedisTool.getJedis(key); jedis.rpush(key, values); RedisTool.returnJedis(jedis); } public List<String> getListVals(String key, int start, int end) { if (StringUtils.isBlank(key)) return null; Jedis jedis = RedisTool.getJedis(key); List<String> rtnList = jedis.lrange(key, start, end); RedisTool.returnJedis(jedis); return rtnList; } /******************* SET 操作 *********************/ public void addSet(String key, String... values) { if (StringUtils.isBlank(key) || values == null) { return; } logger.info("redis入库set, key:" + key + ",values:" + values); Jedis jedis = RedisTool.getJedis(key); jedis.sadd(key, values); RedisTool.returnJedis(jedis); } public void delSetVal(String key, String... fields) { if (StringUtils.isBlank(key)) { return; } logger.info("redis删除set, key:" + key + ",values:" + fields); Jedis jedis = RedisTool.getJedis(key); jedis.srem(key, fields); RedisTool.returnJedis(jedis); } public Set<String> getSetVals(String key) { if (StringUtils.isBlank(key)) { return null; } Jedis jedis = RedisTool.getJedis(key); Set<String> rtnSet = jedis.smembers(key); RedisTool.returnJedis(jedis); return rtnSet; } public boolean isSetContain(String key, String field) { if (StringUtils.isBlank(key) || StringUtils.isBlank(field)) { return false; } Jedis jedis = RedisTool.getJedis(key); boolean isContain = jedis.sismember(key, field); RedisTool.returnJedis(jedis); return isContain; } public Long getSetLength(String key) { if (null == key) { return 0L; } Jedis jedis = RedisTool.getJedis(key); Long length = jedis.scard(key); RedisTool.returnJedis(jedis); return length; } /********************* SORT SET 操作 *************************/ public static boolean addSortSet(String key, String member){ return addSortSet(key, System.currentTimeMillis(), member); } public static boolean addSortSet(String key, double score, String member) { if(StringUtils.isBlank(key) || StringUtils.isBlank(member)){ return false; } Jedis jedis = RedisTool.getJedis(key); Long result = jedis.zadd(key, score, member); RedisTool.returnJedis(jedis); if(result == null) return false; return result.intValue() > 0; } public boolean delSortSet(String key, String... member){ if(StringUtils.isBlank(key) || member == null){ return false; } Jedis jedis = RedisTool.getJedis(key); Long result = jedis.zrem(key, member); RedisTool.returnJedis(jedis); if(result == null) return false; return result.intValue() > 0; } public boolean delSortSetByScore(String key, Double score){ if(StringUtils.isBlank(key) || score == null){ return false; } Jedis jedis = RedisTool.getJedis(key); Long result = jedis.zremrangeByScore(key, score, score); RedisTool.returnJedis(jedis); if(result == null) return false; return result.intValue() > 0; } public boolean delSortSetAll(String key) { if (StringUtils.isBlank(key)) { return false; } Jedis jedis = RedisTool.getJedis(key); Long result = jedis.zremrangeByRank(key, 0, -1); RedisTool.returnJedis(jedis); if(result == null) return false; return result.intValue() > 0; } public boolean isSortSetContain(String key, String member) { if(StringUtils.isBlank(key) || StringUtils.isBlank(member)) return false; Jedis jedis = RedisTool.getJedis(key); Long result = jedis.zrank(key, member); RedisTool.returnJedis(jedis); if(result == null) return false; return result.intValue() >= 0; } public List<String> getSortSetValAsc(String key, int start, int end) { if(StringUtils.isBlank(key)) return null; Jedis jedis = RedisTool.getJedis(key); Set<String> sets = jedis.zrange(key, start, end); RedisTool.returnJedis(jedis); if(sets == null || sets.size() <= 0) return null; return new ArrayList<String>(sets); } public List<String> getSortSetValDesc(String key, int start, int end) { if(StringUtils.isBlank(key)) return null; Jedis jedis = RedisTool.getJedis(key); Set<String> sets = jedis.zrevrange(key, start, end); RedisTool.returnJedis(jedis); if(sets == null || sets.size() <= 0) return null; return new ArrayList<String>(sets); } /** * 根据score区间,获取分页String集合 * @param key * @param beginScore * @param endScore * @param pager pager为null则不分页 * @return */ public Long getSortSetCountByScore(String key, Double beginScore,Double endScore) { Set<String> sets; if(StringUtils.isBlank(key)) { return null; } Jedis jedis = RedisTool.getJedis(key); RedisTool.returnJedis(jedis); return jedis.zcount(key, beginScore, endScore); } /** * 根据score区间,获取分页String集合 * @param key * @param beginScore * @param endScore * @param pager pager为null则不分页 * @return */ public List<String> getSortSetValByScore(String key, Double beginScore,Double endScore,Pager pager) { Set<String> sets; if(StringUtils.isBlank(key)) { return null; } Jedis jedis = RedisTool.getJedis(key); if(pager==null) { sets = jedis.zrangeByScore(key, beginScore, endScore) ; } else { sets =jedis.zrangeByScore(key, beginScore, endScore, (pager.getPageNumber() - 1) * pager.getLimit(), pager.getLimit()); } RedisTool.returnJedis(jedis); if(sets == null || sets.size() <= 0) { return null; } return new ArrayList<String>(sets); } /** * 根据score区间,获取分页String倒序集合 * @param key * @param beginScore * @param endScore * @param pager pager为null则不分页 * @return */ public List<String> getSortSetValByScoreDesc(String key, Double beginScore,Double endScore,Pager pager) { Set<String> sets; if(StringUtils.isBlank(key)) { return null; } Jedis jedis = RedisTool.getJedis(key); if(pager==null) { sets = jedis.zrevrangeByScore(key, endScore, beginScore) ; } else { sets =jedis.zrevrangeByScore(key, endScore, beginScore,(pager.getPageNumber() - 1) * pager.getLimit(), pager.getLimit()); } RedisTool.returnJedis(jedis); if(sets == null || sets.size() <= 0) { return null; } return new ArrayList<String>(sets); } public static Long getSortSetLength(String key) { if(StringUtils.isBlank(key)) return 0l; Jedis jedis = RedisTool.getJedis(key); // Long result = jedis.zcount(key, "-inf", "+inf"); Long result = jedis.zcard(key); RedisTool.returnJedis(jedis); return result; } public boolean increaseSortSetScore(String key, double score, String member) { if (StringUtils.isBlank(key)) { return false; } Jedis jedis = RedisTool.getJedis(key); jedis.zincrby(key, score, member); RedisTool.returnJedis(jedis); return true; } /********************* JSON 操作 弃用 ************************/ public boolean addObejct(String key, Object obj) { if(StringUtils.isBlank(key) || obj == null){ return false; } logger.info("redis入库object, key:" + key + ",Object:" + obj); // String objString = gson.toJson(obj); Map map = convertMap(obj); Jedis jedis = RedisTool.getJedis(key); // String result = jedis.set(key, objString); String result = jedis.hmset(key, map); RedisTool.returnJedis(jedis); return "OK".equals(result); } private Map convertMap(Object obj) { if(obj == null) return null; Map map = Maps.newHashMap(); BeanMap beanMap = BeanMap.create(obj); for(Object key : beanMap.keySet()){ Object value = beanMap.get(key); if(value == null) continue; map.put(key + "", value.toString()); } return map; } public <T> T getObject(String key, Class<T> clazz){ if(StringUtils.isBlank(key) || clazz == null) return null; logger.info("redis获取object, key:" + key + ",clazz:" + clazz); Jedis jedis = RedisTool.getJedis(key); // String result = jedis.get(key); Map<String, String> map = jedis.hgetAll(key); RedisTool.returnJedis(jedis); if(map == null) return null; // T obj = gson.fromJson(result, clazz); try { T t = clazz.newInstance(); BeanMap beanMap = BeanMap.create(t); beanMap.putAll(beanMap); return t; } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /****************** 序列化操作 *************************/ /** * 禁止返回HIBERNATE对象 * @param key * @param tClass * @return */ public <T>T getObjectByKey(String key, Class<T> tClass) { Jedis jedis = RedisTool.getJedis(key); byte[] byt=jedis.get(key.getBytes()); Object obj= unserizlize(byt); RedisTool.returnJedis(jedis); if(obj!=null && obj.getClass().getName()==tClass.getName()){ return (T)obj; } return null; } public void saveObject(String key,Object Obj) { Jedis jedis = RedisTool.getJedis(key); jedis.set(key.getBytes(), serialize(Obj)); RedisTool.returnJedis(jedis); } //序列化 private static byte [] serialize(Object obj){ ObjectOutputStream obi=null; ByteArrayOutputStream bai=null; try { bai=new ByteArrayOutputStream(); obi=new ObjectOutputStream(bai); obi.writeObject(obj); byte[] byt=bai.toByteArray(); return byt; } catch (IOException e) { e.printStackTrace(); } return null; } //反序列化 private static Object unserizlize(byte[] byt){ ObjectInputStream oii=null; ByteArrayInputStream bis=null; bis=new ByteArrayInputStream(byt); try { oii=new ObjectInputStream(bis); Object obj=oii.readObject(); return obj; } catch (Exception e) { e.printStackTrace(); } return null; } }
Redis集群初始化、slot(槽)的分配
- 集群初始化: 在JedisClusterFactory类中它实现了InitializingBean接口,重写了afterPropertiesSet()方法,当JedisClusterFactory Bean被注册之后,此方法被调用,它里面调用parseHostAndPort()方法
- slot的分配: Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 取模,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。