redis怎样实现分页的 redis如何分页_ZSet

 

一,需求分析

如上图,app实现基金经理的查询,可根据年化回报率,综合得分,升序或降序排列,可下拉刷新,即实现分页效果。

生产数据24000左右,不算大,但是app查询要求响应速度,不能查询表,可以把数据放到缓存中,查询缓存。

二,背景

基金经理数据是买第三方的数据,每天更新一次,定时调第三方接口拉取数据,落库,写缓存。

拉取节奏,0-9点,过10min拉取。

redis怎样实现分页的 redis如何分页_List_02

三,怎么存,选择什么数据结构

ZADD — Redis 命令参考

ZSET

需要排序,可以选择有序集合zset,很重要的一点是,需要排序的字段,都需要分别缓存,即回报率和综合评分缓存两份数据,相互独立。

ZADD key score member [[score member] [score member] ...]

key:基金的key

sorce:回报率或综合评分

member:基金经理编码

除此之外,还可以通过区间查询实现分页

ZRANGE key start stop [WITHSCORES]

返回有序集 key 中,指定区间内的成员。

其中成员的位置按 score 值递增(从小到大)来排序。

ZREVRANGE key start stop [WITHSCORES]

返回有序集 key 中,指定区间内的成员。

其中成员的位置按 score 值递减(从大到小)来排列。

Hash

到此完成了,排序及分页,但是缺少基金的其他详细信息,姓名,学历,简介。这个数据也要存储,可以选择hash。

HSET key field value

将哈希表 key 中的域 field 的值设为 value 。

如果 key 不存在,一个新的哈希表被创建并进行 HSET 操作。

如果域 field 已经存在于哈希表中,旧值将被覆盖。

HGET key field

返回哈希表 key 中给定域 field 的值。

HLEN key

返回哈希表 key 中域的数量

如上,field:基金经理编码  value:基金经理详情。

查询的结果由两部分缓存结果,通过分页查询取排序数据,在通过HGet获取基金详细,数量总量,HLEN取得。

四,代码

1.定时落表,写缓存

@Scheduled(cron = "${managerlist.job.cron:0 10 */9 * * ?}")
    public void run() {
	String uniqueVal = UUID.randomUUID().toString();
	String lockKey = RedisKeyHelper.syncManagerListLockKey();
	try {
	    if (redisMgr.tryLock(lockKey, uniqueVal, LOCK_EXPIRE_TIME_SEC)) {
		process();
	    }
	} catch (Exception e) {
	    log.error("Fetch sync manager list exception", e);
	} finally {
	    redisMgr.releaseLock(lockKey, uniqueVal);
	}
	//等待1分钟后,执行刷新缓存操作,排序的缓存
	try {
	    long lateTime = 60000;
	    Thread.sleep(lateTime);//等待1分钟后
	    //刷入数据
	    refreshManagerCacheByProportion();//刷入收益排行榜
	    refreshManagerCacheByScore();//刷入分数排行
	} catch (Exception e) {
	    log.error("refresh manager list order exception", e);
	}
    }

    //刷新基金经理年回报率排行榜缓存
    private void refreshManagerCacheByProportion() {
	List<Manager> list = service.queryManagerListByProportion();
	if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(list)) {
	    for (Manager v : list) {
		String betaCode = v.getBetaCode();
		if (StringUtils.isNotBlank(betaCode)) {
		    Double proportion = v.getReturnProportion();
		    if (proportion == null) {
			proportion = 0.0;
		    }
		    redisMgr.hput(RedisKeyHelper.managerAllHInfoKey(), betaCode, JSON.toJSONString(v));
		    redisMgr.zadd(RedisKeyHelper.managerRankProporKey(), proportion, betaCode);
		}
	    }
	}
    }

    //刷新基金经理综合评分排行榜缓存
    private void refreshManagerCacheByScore() {
	List<Manager> list = service.queryManagerListByScore();
	if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(list)) {
	    for (Manager v : list) {
		String betaCode = v.getBetaCode();
		if (StringUtils.isNotBlank(betaCode)) {
		    Double score = v.getScore();
		    if (score == null) {
			score = 0.0;
		    }
		    redisMgr.hput(RedisKeyHelper.managerAllHInfoKey(), betaCode, JSON.toJSONString(v));
		    redisMgr.zadd(RedisKeyHelper.managerRankScoreKey(), score, betaCode);
		}
	    }
	}
    }

    public void process() {
	List<ManagerListDTO> dtos = managerApi.fetchManagerList();
	log.info("Need to sync manager list size:{}", dtos.size());
	if (CollectionUtils.isEmpty(dtos)) {
	    log.warn("No data to sync fund codes");
	    return;
	}
	service.clearManagerByDay();//首先清除当天历史数据

	List<List<ManagerListDTO>> pars = Lists.partition(dtos, BATCH_SIZE);
	pars.stream().forEach(l -> {
	    executor.execute(() -> {
		try {

		    long s = System.currentTimeMillis();
		    List<Manager> mlist = new ArrayList<>(l.size());
		    List<List<ManagerListDTO>> m = Lists.partition(l, 100);
		    m.stream().forEach(r -> mlist.addAll(converManager(r)));
		    List<ManagerEntity> dataList = dealManager(mlist);

		    StopWatch stopwatch = new StopWatch();
		    stopwatch.start("基金经理批量写入es");
		    esService.saveBatchManager(dataList);
		    stopwatch.stop();
		    log.info("BatchSaveManager spend:{}", stopwatch.getTotalTimeMillis());

		    service.saveManagerList(mlist);
		    long e = System.currentTimeMillis();
		    log.info("Take {}s to handle the size#{} data", (e - s) / 1000, l.size());
		} catch (Exception e) {
		    log.error("Sync manager list exception", e);
		}
	    });
	});

    }

2.查询排行

@Override
    public SearchResDTO searchAllManagerByOrder(String sortType, int sort, int page, int size) {
	log.info("searchAllManagerByOrder manager{} ,{} ,{}, {}", sortType, sort, page, size);
	int start = (page - 1) * size;
	String mainKey = null;
	if ("score".equals(sortType)) {//按照分数排行
	    mainKey = RedisKeyHelper.managerRankScoreKey();
	} else {//默认按照年化回报率排行
	    mainKey = RedisKeyHelper.managerRankProporKey();
	}
	List<String> list = new ArrayList<>();
	long end = start + size - 1;
	if (sort == -1) {//默认降序
	    Set<String> abc = redisMgr.zrevrange(mainKey, start, end);
	    if (abc != null) {
		list = new ArrayList<>(abc);
	    }
	} else {//其他升序
	    Set<String> abc = redisMgr.zrange(mainKey, start, end);
	    if (abc != null) {
		list = new ArrayList<>(abc);
	    }
	}
	List<FundManagerDTO> result = new ArrayList();
	int count = 0;
	if (list != null && list.size() > 0) {
	    String dictionaryKay = RedisKeyHelper.managerAllHInfoKey();
	    count = (int) redisMgr.hLen(dictionaryKay);
	    for (String code : list) {
		Object info = redisMgr.hget(dictionaryKay, code);
		if (info != null) {
		    FundManagerDTO node = JSONObject.parseObject(info.toString(), FundManagerDTO.class);
		    result.add(node);
		}
	    }
	}
	SearchResDTO searchResDTO = new SearchResDTO();
	searchResDTO.setSearchFundManagers(result);
	searchResDTO.setCount(count);
	return searchResDTO;
    }