精华内容
下载资源
问答
  • Redis 排序分页

    千次阅读 2018-10-19 11:23:35
    使用Redis实现分页出发点基础架构Redis Sort功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右...

    业务场景

    看到很多文章介绍的redis分页都是用list的lrange offset count 去查询id列表,然后根据id查到哈希对象,之后在代码中根据对象的某个字段做排序。

    虽然这样能够实现分页,但是排序的做法是错误的!因为这种排序不是对整个数据集进行排序,而是对你查出来的某一页的数据进行排序!

    事实上,redis提供了先排序,再分页的支持。

    Redis Sort

    如果你还不了解redis的基础数据结构,建议你先了解,再接着往下看:
    reids里面有一个非常有用的命令:sort,用来给list,set,sorted set进行排序

    SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination]
    
    参数 解释
    key redis中的键,可以是list,set,sorted set三种类型,按照key的值排序,取出key中的值
    BY pattern 匹配模式,将key放入pattern的号替换,并按照pattern的值排序,取出key中的值
    LIMIT 和mysql的limit一样,排序后,从第offset个元素开始,取出count个元素
    GET pattern 匹配模式,将key放入pattern的*号替换,取出pattern中的值,这个模式可以有多个,相当于sql中的join连接多个表
    ASC/DES 升降序
    ALPHA 是否按照字符串顺序,默认是按照数字排序,如果你用来排序的值(key,或pattern)是字符串,则必须使用ALPHA参数,否则报格式异常
    STORE 将排序结果放入另一个key中存储

    下面做出一些简单示例

    初步:List,Set排序

    创建一个list,升序排序

    127.0.0.1:6379> lpush ids 1 2 3 4
    (integer) 4
    127.0.0.1:6379> sort ids ASC
    1) "1"
    2) "2"
    3) "3"
    4) "4" 
    

    BY pattern,用pattern进行排序,并依次返回key的值

    127.0.0.1:6379> set user_age_1 10
    OK
    127.0.0.1:6379> set user_age_2 9
    OK
    127.0.0.1:6379> set user_age_3 8
    OK
    127.0.0.1:6379> set user_age_4 7
    OK
    127.0.0.1:6379> sort ids BY user_age_*
    1) "4"
    2) "3"
    3) "2"
    4) "1" 
    

    解释:这里pattern中的*,会用ids中的值替代;相当于遍历ids,然后取出每个元素,放入pattern中,如ids中的第一个元素,会替换成user_age_1,然后放入新的临时列表。遍历完成后,比较新的临时列表{user_age_1,user_age_2,user_age_3,user_age_4}的value的大小;

    python代码表示:

    def sort(keys,by_pattern): 
    	temp_dict = {} 
    	for key in keys:
    		replace_key = by_pattern.replace("*",key) #依次替换
    		replace_value = reids.get(replace_key) #获取replace_key的值
    		temp_dict[key] = replace_value #原key->replace_value
    	
    	#根据value排序,返回key
    	return [x[0] for x in sorted(d.items(),key=lambda x:x[1])]
    

    上面获得的是用户id,如果我想获取的是用户的年龄,而不是id,怎么办?

    GET pattern,返回(替换后)pattern的值,而不是key的值

    127.0.0.1:6379> sort ids BY user_age_* GET user_age_*
    1) "7"
    2) "8"
    3) "9"
    4) "10"
    

    GET可以设置多个,相当于sql中的join

    进阶:Hash排序

    在实际业务中,我们存储对象使用hash结构,比如:

    class User {
    	String id;
    	String name;
     	int age;
    }
    

    在redis里面一般是这么存储的:reids 的key是user:id :1,hash key是字段名,hash value是字段值

    127.0.0.1:6379> hset user:id:1 name Slbai
    (integer) 1
    127.0.0.1:6379> hset user:id:1 age 13
    (integer) 1
    127.0.0.1:6379> hgetall user:id:1
    1) "name"
    2) "Slbai"
    3) "age"
    4) "13"
    

    现在我创建了三个user

    ID name age
    1 Slbai 13
    2 Jack Ma 12
    3 Steve Jobs 11

    然后创建了一个list,存储用户id

    127.0.0.1:6379> lpush ids:user 1 2 3
    (integer) 3
    

    重点来了!!!
    BY pattern 和 GET pattern中,redis 的hash类型 也可以作为pattern,使用key->field 的格式来获取hash中的值

    查询用户id,根据年龄排序

    127.0.0.1:6379> sort ids:user BY user:id:*->age
    1) "3"
    2) "2"
    3) "1"
    

    查询用户年龄,根据年龄排序

    127.0.0.1:6379> sort ids:user BY user:id:*->age GET user:id:*->age
    1) "11"
    2) "12"
    3) "13"
    

    分页查询用户年龄,根据年龄排序

    127.0.0.1:6379> sort ids:user BY user:id:*->age GET user:id:*->age LIMIT 0 2
    1) "11"
    2) "12"
    

    性能优化:缓存排序结果

    redis的特点是高性能,而排序操作的时间复杂度和排序元素的个数正相关,对于一些元素较多的排序需求,我们可以加入缓存,sort给我们提供了这种便利。

    将查询结果存储到其他的key缓存,同时也可以设置他的过期时间,过期时间内,直接取key里面的值,优化性能

    127.0.0.1:6379> sort ids:user BY user:id:*->age STORE cache:ids:user
    (integer) 3
    127.0.0.1:6379> lrange cache:ids:user 0 -1
    1) "3"
    2) "2"
    3) "1"
    

    下面将通过一个项目进行演示

    实战

    项目 Value 版本
    语言 java jdk1.8
    框架 SpringBoot 2.0.4.RELEASE
    数据库 reids 4.x

    实际业务中,我们可能根据多种字段排序,比如搜索某件商品,可选根据 ‘价格’,‘距离’,‘评分’等分页+排序,而By pattern的hash形式很适合这种情况,如item:id:*->price,我们只需要将price替换为distance,就可以实现按照区域排序;
    刚才这种业务场景下,我们一般需要返回id,而不是其他字段,所以key是存储id列表的键。当然,如果你的业务需要查询单个或多个其他字段,可以添加一个或多个Get pattern。

        /**
         * 分页查询
         * @param key   一般是id列表
         * @param by_pattern 根据此pattern的value排序,除了常规的pattern,也可以接收hash的pattern
         * @param offset  偏移量
         * @param count  每次查询的条数
         * @return 返回分页后的id列表
         */
        public List<String> sort(String key, String by_pattern, Long offset, Long count){
            return redisTemplate.sort(
                    SortQueryBuilder
                            .sort(key)
                            .by(by_pattern)
                            .alphabetical(true)
                            .order(SortParameters.Order.DESC)
                            .limit(offset,count)
                            .build()
            );
        }
    

    使用

    redisService.sort("ids:user","user:id:*->age",0L,3L)
    

    在这之后,你也可以将id存储到新的key,实现缓存,减轻数据库压力;

    完整代码

    链接: https://github.com/slbai/example-redis.

    展开全文
  • redis实现排序分页

    千次阅读 2017-02-13 16:50:25
    要实现排序需要两个类型list,hash存放数据。 按排序好的数据id存放到list数据内,对应hash名称。list listSort:code 00003 00005 00008 00009 00017 00026 00084 00102 hash fund fund:00003 code name ...

    要实现排序需要两个类型list,hash存放数据。
    按排序好的数据id存放到list数据内,对应hash名称。

    list listSort:code

    00003
    00005
    00008
    00009
    00017
    00026
    00084
    00102

    hash fund

    fund:00003 code name
    fund:00003 00003 长城久利保本
    fund:00005 00005 长盛纯债C
    fund:00008 00008 博时基金
    fund:00009 00009 信诚基金
    fund:000093 000093 信诚新双盈B
    fund:000170 000170 泰达宏利收益增强B

    排序:

    sort listSort:code by fund:* get  fund:*->code get  fund:*->name limit 0 10
    展开全文
  • 主要介绍了如何在Redis中实现分页排序查询过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • redis 脚本 redis 分页

    2019-09-10 11:09:52
    无序: sadd: String script = "redis.call('sadd',KEYS[1],ARGV[1]) " + " if( redis.call('ttl',KEYS[1]) <= 0 ) " + " then redis.call('expire',KEYS[1],tonumbe...

    无序:
    sadd:
          String script = "redis.call('sadd',KEYS[1],ARGV[1]) " + " if( redis.call('ttl',KEYS[1]) <= 0 ) "
                                            + " then redis.call('expire',KEYS[1],tonumber(ARGV[2]) ) end";
         List<String> keys = new ArrayList<>(1);
         keys.add(key);
         List<String> args = new ArrayList<>(3);
         args.add(member);
          args.add(到期时间 秒);
           jedisCluster.evalscript keys ,args)
    有序:
    zadd: 
          Long zadd(String key, double score, String member);

          List<String> keys = new ArrayList<>(1);
         keys.add(key);
          List<String> args = new ArrayList<>(3);
         args.add(member);
         args.add(scope);
         args.add(到期时间 秒);
                script = " redis.call('zadd',KEYS[1],ARGV[2],ARGV[1]) " +
                        " if( redis.call('ttl',KEYS[1]) <= 0 ) " +
                        " then redis.call('expire',KEYS[1],tonumber(ARGV[3]) ) end ";
                jedisCluster.eval(script, keys, args);
    散列:
    hset:      Long hset(String key, String field, String value);
               String allScript = "redis.call('hset',KEYS[1],ARGV[1],ARGV[2])";
                List<String> allKeys = new ArrayList<>(1);
                allKeys.add(key);
                List<String> allArgs = new ArrayList<>(2);
                allArgs.add(filed);
                allArgs.add(value);
                jedisCluster.eval(allScript, allKeys, allArgs);
          或者:
     String allScript = "redis.call('hset',KEYS[1],ARGV[1],ARGV[2])";
                List<String> allKeys = new ArrayList<>(1);
                allKeys.add(key);
                List<String> allArgs = new ArrayList<>(2);
                allArgs.add(filed);
                allArgs.add(value);
             allArgs.add(到期时间 秒);
                jedisCluster.eval(allScript, allKeys, allArgs);
            String script = "redis.call('hset',KEYS[1],ARGV[1],ARGV[2]) " + " if( redis.call('ttl',KEYS[1]) <= 0 ) "
                        + " then redis.call('expire',KEYS[1],tonumber(ARGV[3]) ) end";
    hincrBy: 

    script = "redis.call('hincrBy',KEYS[1],ARGV[1],tonumber(ARGV[2])) " + " if( redis.call('ttl',KEYS[1]) <= 0 ) "
                    + " then redis.call('expire',KEYS[1],tonumber(ARGV[3]) ) end";
            List<String> keys = new ArrayList<>(1);
            keys.add(key);
            args = new ArrayList<>(3);
            //总车流量:1 +1
            args.add(filed);
            args.add(数字加多少);
            args.add(到期时间 秒);
            jedisCluster.eval(script, keys, args);
    llen:

      System.out.println(jedisCluster.lrange("HIGHT_ALARM_PUSHMSG",0,-1));
            List<String> keys = new ArrayList(1);
            keys.add("HIGHT_ALARM_PUSHMSG");
            List<String> args = new ArrayList(1);
            args.add("messss fffff2 ");
            String script = "local count = redis.call('llen',KEYS[1]) if(count >= 1) then redis.call('rpop',KEYS[1]) end redis.call('lpush',KEYS[1],ARGV[1])";
            jedisCluster.eval(script,keys,args);
            System.out.println(jedisCluster.lrange("HIGHT_ALARM_PUSHMSG",0,-1));
    或者;
         System.out.println(jedisCluster.lrange("HIGHT_ALARM_PUSHMSG",0,-1));
            List<String> keys = new ArrayList(1);
            keys.add("HIGHT_ALARM_PUSHMSG");
            List<String> args = new ArrayList(1);
            args.add("messss fffff6 ");
            String script = "local count = redis.call('llen',KEYS[1]) if(count >= 5) then redis.call('rpop',KEYS[1]) end redis.call('lpush',KEYS[1],ARGV[1])";
            System.out.println(jedisCluster.eval(script,keys,args));
            System.out.println(jedisCluster.lrange("HIGHT_ALARM_PUSHMSG",0,-1));
        测试结果:
    [messss fffff5 , messss fffff4 , messss fffff3 , messss fffff2 , messss fffff1 ]
    null
    [messss fffff6 , messss fffff5 , messss fffff4 , messss fffff3 , messss fffff2 ]

    get :
    String key = "key111";
            List<String> keys = new ArrayList<String>(1);
            keys.add(key);
            List<String> values = new ArrayList<String>(1);
            values.add("212112");
            String script = " local v = 0  if(redis.call('get',KEYS[1]) == 'dfaa') then v = 1 else v = 2 end return v";
            System.out.println(jedisCluster.eval(script,keys,values));
            System.out.println(jedisCluster.get("key111"));
    redis分页:
           long currMills = System.currentTimeMillis();
            long s = System.currentTimeMillis();

            Map<String, String> traceMap = new HashMap<>();
            if(StringUtils.isEmpty(vno)) {
                int fromIndex = page==null?0:(page.getPageNum() - 1) * page.getPageSize();
                int pageSize = page==null?15:page.getPageSize();
                Set<String> traceSet = redis.zrevrangeByScore(RedisKeyConsts.TRACE_ZSET+dateStr, max, min, fromIndex, pageSize);

                for (String traceStr : traceSet) {
                    String trace = redis.hget(RedisKeyConsts.TRACE + dateStr, traceStr);
                    traceMap.put(traceStr, trace);
                }
            }else{
                String cursor="0";
                ScanParams scanParams =new ScanParams();
                scanParams.count(fetchSize);
                scanParams.match("*"+vno+"*");
                do {
                    ScanResult<Map.Entry<String, String>> scanResult = redis.hscan(RedisKeyConsts.TRACE + dateStr, cursor, scanParams);
                    List<Map.Entry<String, String>> entryList = scanResult.getResult();
                    for (Map.Entry<String, String> entry : entryList) {
                        traceMap.put(entry.getKey(),entry.getValue());
                    }
                    cursor = scanResult.getStringCursor();
                } while (!cursor.equals("0"));
            }
      //不传参数
            if(StringUtils.isEmpty(vehicleNo)){
                pager = new PageUtils<>();
                pager.setPageNum(page.getPageNum());
                pager.setPageSize(page.getPageSize());
                pager.setTotal(redis.zcount(RedisKeyConsts.TRACE_ZSET+dateStr,min,max));
                pager.setDataList(vehicleSuperviseDtos);
            }else{
                pager = new PageUtils<>(page.getPageNum(), page.getPageSize(), vehicleSuperviseDtos);
            }


    //多线程
    //线程工厂
    public class ExecutorFactory {

        private static ExecutorService pool;

        static {
            initPool();
        }

        private static void initPool() {
            pool = Executors.newFixedThreadPool(50, new ThreadFactoryBuilder().setNameFormat("qd-pool-%d").build());
        }

        public static void execute(Runnable command) {
            pool.execute(command);
        }

        public static <T> Future<T> submit(Callable<T> task) {
            return pool.submit(task);
        }
    }

          s = System.currentTimeMillis();
            List<Map<String, String>> result = new ArrayList<>(traceMap.entrySet().size());
            List<Future<Map<String, String>>> futureList = new ArrayList<>(traceMap.entrySet().size());
            for (Map.Entry<String, String> entry : traceMap.entrySet()) {
                futureList.add(ExecutorFactory.submit(new HandleTraceTask(entry, vno, currMills)));
            }
            for (Future<Map<String, String>> future : futureList) {
                try {
                    Map<String, String> item = future.get();
                    if (item != null) {
                        result.add(item);
                    }
                } catch (Exception e) {
                    logger.error("跳过处理出错的轨迹点", e);
                }
            }


    private class HandleTraceTask implements Callable<Map<String, String>> {
            private Map.Entry<String, String> entry;
            private String vno;
            private long currMills;

            public HandleTraceTask(Map.Entry<String, String> entry, String vno, long currMills) {
                this.entry = entry;
                this.vno = vno;
                this.currMills = currMills;
            }

            @Override
            public Map<String, String> call() throws Exception {
                return handleTraceData(entry, vno, currMills);
            }
        }

        /**
         * 处理轨迹数据
         *
         * @param entry     原始数据
         * @param vno       车牌号
         * @param currMills 当前时间
         * @return map
         */
        private Map<String, String> handleTraceData(Map.Entry<String, String> entry, String vno, long currMills) {
            String[] vnoAndColor = entry.getKey().split("_");
            // lon@lat@gpsTime@direction@gpsSpeed@rtAreaCode
            String[] data = entry.getValue().split(SEP,8);
            if (isEmpty(data[0]) || isEmpty(data[1]) || isEmpty(data[2])) {
                return null;
            }
            if (vno != null && !vnoAndColor[0].contains(vno)) {
                return null;
            }
            Map<String, String> item = new HashMap<>();
            item.put("isOnline", isOnline(Long.parseLong(data[2]), currMills) ? "1" : "0");
            item.put("lat", data[1]);
            item.put("lon", data[0]);
            item.put("direction", data[3]);
            item.put("gpsTime", data[2]);
            item.put("gpsSpeed", String.valueOf(new BigDecimal(NumberUtils.toDouble(data[4], 0D)).setScale(1, RoundingMode.HALF_UP)));
            item.put("vno", vnoAndColor[0]);
            item.put("color", vnoAndColor[1]);
            if (data.length == 6) {
                item.put("area", data[5]);
            }
            return item;
        }

    list排序:
     List<VehicleSuperviseDto> vehicleSuperviseDtos = this.findList(vehicleNo, dateStr,page,min,max);
      vehicleSuperviseDtos.sort(Comparator.comparing(VehicleSuperviseDto::getIfOnline).reversed().thenComparing(VehicleSuperviseDto::getVehicleNo));

    展开全文
  • 感觉分析的很到位,学习学习分析方法 转载自(原文):...Redis是一个高效的内存数据库,它支持包括String、List、Set、Sort

    感觉分析的很到位,学习学习分析方法

    转载自(原文):http://liu-xin.me/2015/11/17/%E5%9C%A8Redis%E4%B8%AD%E8%BF%9B%E8%A1%8C%E5%88%86%E9%A1%B5%E6%8E%92%E5%BA%8F%E6%9F%A5%E8%AF%A2/

    Redis是一个高效的内存数据库,它支持包括String、List、Set、SortedSet和Hash等数据类型的存储,在Redis中通常根据数据的key查询其value值,Redis没有条件查询,在面对一些需要分页或排序的场景时(如评论,时间线),Redis就不太好不处理了。

    前段时间在项目中需要将每个主题下的用户的评论组装好写入Redis中,每个主题会有一个topicId,每一条评论会和topicId关联起来,得到大致的数据模型如下:

    {
    topicId: 'xxxxxxxx',
    comments: [
    {
    username: 'niuniu',
    createDate: 1447747334791,
    content: '在Redis中分页',
    commentId: 'xxxxxxx',
    reply: [
    {
    content: 'yyyyyy'
    username: 'niuniu'
    },
    ...
    ]
    },
    ...
    ]
    }


    将评论数据从MySQL查询出来组装好存到Redis后,以后每次就可以从Redis获取组装好的评论数据,从上面的数据模型可以看出数据都是key-value型数据,无疑要采用hash进行存储,但是每次拿取评论数据时需要分页而且还要按createDate字段进行排序,hash肯定是不能做到分页和排序的。

    那么,就挨个看一下Redis所支持的数据类型:

    String: 主要用于存储字符串,显然不支持分页和排序。
    Hash: 主要用于存储key-value型数据,评论模型中全是key-value型数据,所以在这里Hash无疑会用到。
    List: 主要用于存储一个列表,列表中的每一个元素按元素的插入时的顺序进行保存,如果我们将评论模型按createDate排好序后再插入List中,似乎就能做到排序了,而且再利用List中的LRANGE key start stop指令还能做到分页。嗯,到这里List似乎满足了我们分页和排序的要求,但是评论还会被删除,就需要更新Redis中的数据,如果每次删除评论后都将Redis中的数据全部重新写入一次,显然不够优雅,效率也会大打折扣,如果能删除指定的数据无疑会更好,而List中涉及到删除数据的就只有LPOP和RPOP这两条指令,但LPOP和RPOP只能删除列表头和列表尾的数据,不能删除指定位置的数据,所以List也不太适合。
    Set: 主要存储无序集合,无序!排除。
    SortedSet: 主要存储有序集合,SortedSet的添加元素指令ZADD key score member [[score,member]…]会给每个添加的元素member绑定一个用于排序的值score,SortedSet就会根据score值的大小对元素进行排序,在这里就可以将createDate当作score用于排序,SortedSet中的指令ZREVRANGE key start stop又可以返回指定区间内的成员,可以用来做分页,SortedSet的指令ZREM key member可以根据key移除指定的成员,能满足删评论的要求,所以,SortedSet在这里是最适合的。

    所以,我需要用到的数据类型有SortSet和Hash,SortSet用于做分页排序,Hash用于存储具体的键值对数据,我画出了如下的结构图:


    在上图的SortSet结构中将每个主题的topicId作为set的key,将与该主题关联的评论的createDate和commentId分别作为set的score和member,commentId的顺序就根据createDate的大小进行排列。
    当需要查询某个主题某一页的评论时,就可主题的topicId通过指令zrevrange topicId (page-1)×10 (page-1)×10+perPage这样就能找出某个主题下某一页的按时间排好顺序的所有评论的commintId。page为查询第几页的页码,perPage为每页显示的条数。
    当找到所有评论的commentId后,就可以把这些commentId作为key去Hash结构中去查询该条评论对应的内容。
    这样就利用SortSet和Hash两种结构在Redis中达到了分页和排序的目的。

    展开全文
  • 每个主题下的用户的评论组装好写入Redis中,每个主题会有一个topicId,每一条评论会和topicId关联起来,大致的数据模型如下: (推荐学习:Redis视频教程){ topicId: 'xxxxxxxx', comments: [ { username: 'niuniu', ...
  • 目标是能够以合理的性能对 Redis 中的 1,000,000 个实体进行排序分页。 方法一:设置和SORT 将 ID 存储在一个并使用进行排序分页。 这种方法使用较少的内存 (328MB) 但速度很慢 (30.24s)。 缓慢来自对内存中的 ...
  • redis分页 排序

    千次阅读 2018-03-26 15:27:08
    Redis是一个高效的内存数据库,它支持包括String、List、Set、SortedSet和Hash等数据类型的存储,在Redis中通常根据数据的key查询其value值,Redis没有条件查询,在面对一些需要分页排序的场景时(如评论,时间线...
  • 偶然在代码中发现一个接口,接口定义说是分页查询,但逻辑实现是Redis。不太理解,Redis怎么分页?后来看到一篇文章,然后了解了。 1、Zrevrange实现 通过SortedSet的zrevrange topicId (page-1)×10 (page-1)...
  • Redis 分页排序查询

    万次阅读 多人点赞 2016-05-23 18:45:08
    Redis是一个高效的内存数据库,它支持包括String、List、Set、SortedSet和Hash等数据类型的存储,在Redis中通常根据数据的key查询其value值,Redis没有条件查询,在面对一些需要分页排序的场景时(如评论,时间线...
  • redis做缓存 分页思路

    2019-01-16 11:04:22
    传统分页 一般分页做缓存都是直接查找出来,按页放到缓存里,但是这种缓存方式有很多缺点。 如缓存不能及时更新,一旦数据有变化,所有的之前的...2.把数据ID和排序打分存到Redis的skip list,即zset里; 3.当查...
  • 主要介绍了PHP操作redis实现的分页列表,新增,删除功能封装类与用法,结合实例形式分析了php针对redis数据库基本的连接、查询、添加、分页等操作封装与使用技巧,需要的朋友可以参考下
  • 使用Redis来进行分页查询

    万次阅读 2018-04-19 20:18:44
    在进行后台管理中,常常用到分页技术,每次数据从数据库中的读取都是非常耗时的,如果用redis来做缓存处理的话就会加快效率。 但是,需要实现分页的话需要用到sortset(zadd(),zrevrangebyscore()),hash(hset(),...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,546
精华内容 7,418
关键字:

redis排序加分页

redis 订阅