title: Redis中zset类型数据的应用(实例+原理)
date: 2019-05-27
tags: [java,Redis]
项目需求
公司APP页面需要展示一个横轴为时间,纵轴为指定基金和沪深300指数(或者其他指数)的折线图。
折线图的范围是可选的(比如一个月内,三个月内,六个月内等等),并且由于每一支基金的净值公布节奏不同,同一个时间范围的实际首尾时间,以及具体哪些日期是有值也是不一样的。
还有一个特点是沪深300的值是相对固定的,每天只会更新一次。但是很多基金都会以沪深300作为比较基准,使得需要查询一个数据多次。
比如A基金(每天公布净值)的三个月范围需要查0201,0202,0203…… 0430这些日期对应的沪深300;但是B基金(每周公布净值)的三个月范围只需要查0202,0209,0216,0223……这些日期对应的沪深300。如果使用上面的步骤,势必会使得程序运行效率降低。
解决思路
对于这种类似于静态数据,或者说修改一次查询很多次的数据,使用缓存是最好的选择了。下面以Redis来说明解决方案。
Redis有五种数据存储格式,分别是String,List,Hash,Set,Zset。
除了存储常用的数据,Redis还可以用Set数据类型来进行去重工作,(后面有了布隆过滤器);还可以使用Redis 共享分布式系统中的session;使用Redis解决分布式锁的问题;Redis的List还能做消息队列和发布订阅模式,实现消息传递和进程间的通信。
扯远了,具体到眼前的实例,实现起来非常简单,只需要使用带分数的集合类型就能行了。具体是使用沪深300的时间作为 score
分数,将每天的收盘价作为value
,当确定时间范围之后取出对应时间范围的沪深300净值信息,即可拿到想要的数据。
代码实例
主要步骤
Set<Tuple> tuplesCsi300Set = RedisUtils.zrevrangeByScoreWithScores(csi300Key, dateParamMax, dateParamMin, 0, 1000);
if (tuplesCsi300Set != null && tuplesCsi300Set.size() > 0) {
compareGrowthList = getCompaFromRedis(param_csi300_arr, tuplesCsi300Set);
System.out.println("compareGrowthList:" + compareGrowthList);
} else {//先查,再存
//查询数据库全部沪深300的数据存入Redis中
JSONArray apiDataCsiRedis = getData();
Map<Double, String> csi300Map = new HashMap<>();
for (int i = 0; i < apiDataCsiRedis.size(); i++) {
JSONObject objecti = (JSONObject) apiDataCsiRedis.get(i);
csi300Map.put(Double.valueOf(objecti.getString("tDate")), objecti.getString("tClose"));
}
RedisUtils.addSortSetByScoreMap(csi300Key, csi300Map);
Set<Tuple> tuplesCsi300 = RedisUtils.zrevrangeByScoreWithScores(csi300Key, dateParamMax, dateParamMin, 0, 1000);
compareGrowthList = getCompaFromRedis(param_csi300_arr, tuplesCsi300);
System.out.println("compareGrowthList:" + compareGrowthList);
}
取数据:
/**
* 处理从redis中获取到的set
*
* @param param_csi300_arr
* @param csi3003YMap
* @return
*/
private LinkedList<Object> getCompaFromRedis(String[] param_csi300_arr, Set<Tuple> csi3003YMap) {
LinkedList<Object> compareGrowthList = new LinkedList<>();
compareGrowthList.addFirst(0.00);
TreeMap<String, String> treeMap = new TreeMap<>();
for (Iterator<Tuple> iterator = csi3003YMap.iterator(); iterator.hasNext(); ) {
Tuple next = iterator.next();
String score = Double.valueOf(next.getScore()).intValue() + "";
treeMap.put(score, next.getElement());
}
//最早时间的指数值 逻辑:(当前值/最早值) -1 *100
String s0 = treeMap.get(treeMap.firstKey());
for (int i = 1; i <= param_csi300_arr.length - 1; i++) {
//最早时间的指数值 逻辑:(当前值/最早值) -1 *100
if (treeMap.keySet().contains(param_csi300_arr[i])) { //判断第i个时间参数对应的取值结果是否存在
Double num = (Double.parseDouble(treeMap.get(param_csi300_arr[i])) / Double.parseDouble(s0) - 1) * 100;
compareGrowthList.add(Double.parseDouble(df_No_comma.format(num)));
} else {
compareGrowthList.add(BLANKVALUE);
}
}
return compareGrowthList;
}
主要代码逻辑还是比较简单的,拿到开始和结束时间后查询缓存,查到了就拿出区间内的数据进行后续处理,否则就从数据库(或者其他接口)查询并处理。
Redis有序集合实现原理
Redis的有序集合是使用跳表实现的。
相比红黑树,跳表的代码实现还是比较简单的,并且他也是一种支持动态扩容的数据结构。跳表的介绍不做多展开,具体参见数据结构章节的文章。
跳表这种结构实现类的类似二分查找的速度,查询的时间复杂度是O(logn),很高效。