Redis数据结构--链表( linked-list)

 概述

  1. 链表结构是 Redis 中一个常用的结构,可以存储多个字符串
  2. 有序
  3. 能够存储2的32次方减一个节点(超过 40 亿个节点)
  4. Redis 链表是双向的,因此即可以从左到右,也可以从右到左遍历存储的节点
  5. 链表结构查找性能不佳,但 插入和删除速度很快

    链表结构的优势在于插入和删除的便利 ,因为链表的数据节点是分配在不同的内存区域的,并不连续,只是根据上一个节点保存下一个节点的顺序来索引而己,无需移动元素。

     

  

       

  对于很多个节点同时操作的,需要考虑其花费的时间,链表数据结构对于查找而言并不适合于大数据。需要考虑插入和删除内容的大小,因为这将是十分消耗性能的命令,会导致 Redis 服务器的卡顿 。对于不允许卡顿的一些服务器,可以进行分批次操作,以避免出现卡顿。

  

  

  Spring操作redis链表

package com.smart;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import java.io.UnsupportedEncodingException;
import java.util.*;

public class RedisStringDemo {

    public static void main(String[] args) throws UnsupportedEncodingException {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:redis/spring-redis.xml");

        // redisTemplate.opsForValue()所返回的对象可以操作简单的键值对,可以是字符串,也可以是对象,具体依据所配置的序列化方案
        // 在spring-redis-string.xml中key和value是指定的 stringRedisSerializer
        RedisTemplate redisTemplate= (RedisTemplate<String, String>) context.getBean("redisTemplate");

        flushDb(redisTemplate);
        String key="list";
        System.out.println(redisTemplate.opsForList().leftPush(key,"node3"));

        List<String> nodeList = new ArrayList<>();
        for(int i=2;i>=1;i--){
            nodeList.add("node"+i);
        }
        System.out.println(redisTemplate.opsForList().leftPushAll(key,nodeList));

        System.out.println(redisTemplate.opsForList().rightPush(key,"node4"));

        String node = (String) redisTemplate.opsForList().index(key, 0);
        System.out.println("第一个节点:"+node);

        System.out.println(key + "中的总数为:" + redisTemplate.opsForList().size(key));

        String leftPopNode = (String) redisTemplate.opsForList().leftPop(key);
        System.out.println("leftPopNode:" + leftPopNode);

        String rightPopNode = (String) redisTemplate.opsForList().rightPop(key);
        System.out.println("rightPopNode:" + rightPopNode);

        Long aLong = redisTemplate.getRequiredConnectionFactory().getConnection()
                .lInsert(key.getBytes("utf-8"), RedisListCommands.Position.BEFORE, "node2".getBytes("utf-8"), "before_node".getBytes("utf-8"));
        System.out.println(aLong);

        Long aLong1 = redisTemplate.getConnectionFactory().getConnection()
                .lInsert(key.getBytes("utf-8"), RedisListCommands.Position.AFTER, "node2".getBytes("utf-8"), "after_node".getBytes("utf-8"));
        System.out.println(aLong1);

        System.out.println(redisTemplate.opsForList().leftPushIfPresent(key,"head"));

        System.out.println(redisTemplate.opsForList().rightPushIfPresent(key,"end"));

        List<String> list = redisTemplate.opsForList().range(key, 0, 10);
        for(String str : list){
            System.out.println(str);
        }

        nodeList.clear();
        for(int i=0;i<3;i++){
            nodeList.add("node");
        }
        System.out.println(redisTemplate.opsForList().leftPushAll(key,nodeList));

        System.out.println(redisTemplate.opsForList().remove(key, 3, "node"));

        redisTemplate.opsForList().set(key,0,"new_head_value");

        list = redisTemplate.opsForList().range(key, 0, 10);
        for (String string : list) {
            System.out.println("节点:" + string);
        }
    }

    public static String flushDb(RedisTemplate<String,String> redisTemplate){
      return redisTemplate.execute(new RedisCallback<String>() {
          @Override
          public String doInRedis(RedisConnection connection) throws DataAccessException {
              connection.flushDb();
              return "ok";
          }
      });
    }

}
View Code

  有些命令 Spring 所提供的 RedisTemplate 并不能支持,比如 linsert 命令。可以使用更为底层的方法去操作 ,如下

redisTemplate.getConnectionFactory().getConnection()
                    .lInsert(key.getBytes("utf-8"), RedisListCommands.Position.AFTER, "node2".getBytes("utf-8"), "after_node".getBytes("utf-8"));

  链表的阻塞命令

  上面的这些操作链表的命令都是进程不安全的,因为 当操作这些命令的时候,其他 Redis 的客户端也可能操作同一个链表,就会造成并发数据安全和一致性的问题,尤其是当操作一个数据量不小的链表结构时,常常会遇到这样的问题 。 Redis 提供链表的阻塞命令,在运行的时候 , 会给链表加锁,以保证操作链表的命令安全性.

  

  当使用这些命令时, Redis 就会对对应的链表加锁,加锁的结果就是其他的进程不能再读取或者写入该链表,只能等待命令结束 。 加锁的好处可以保证在多线程并发环境中数据的一致性,保证一些重要数据的一致性,比如账户的金额 、 商品的数量。不过在保证这些的同时也要付出其他线程等待、线程环境切换等代价。

  

  实际的项目中 , 虽然阻塞可以有效保证数据的一致性,但是阻塞就意味着其他进程的等待, CPU 需要给其他线程挂起、恢复等操作,更多的时候希望的并不是阻塞的处理请求,所以这些命令在实际中使用得并不多.。

  

package com.smart;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class RedisStringDemo {

    public static void main(String[] args) throws UnsupportedEncodingException {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:redis/spring-redis.xml");

        // redisTemplate.opsForValue()所返回的对象可以操作简单的键值对,可以是字符串,也可以是对象,具体依据所配置的序列化方案
        // 在spring-redis-string.xml中key和value是指定的 stringRedisSerializer
        RedisTemplate redisTemplate= (RedisTemplate<String, String>) context.getBean("redisTemplate");

        flushDb(redisTemplate);

        List<String> list = new ArrayList<String>();
        for (int i = 1; i <= 5; i++) {
            list.add("node" + i);
        }

        String KEY1="list1";
        String KEY2="list2";
        redisTemplate.opsForList().leftPushAll(KEY1, list);
        printList(redisTemplate, KEY1, 0, 4);

        // Spring 使用参数超时时间作为阻塞命令区分,等价于 blpop 命令,并且可以设置时间参数
        String leftPopNode = (String) redisTemplate.opsForList().leftPop(KEY1, 2, TimeUnit.SECONDS);
        System.out.println("leftPopNode:"+leftPopNode);

        // Spring 使用参数超时时间作为阻塞命令区分,等价于 brpop 命令,并且可以设置时间参数
        System.out.println("rightPopNode:"+redisTemplate.opsForList().rightPop(KEY1,3,TimeUnit.SECONDS));

        // 相当于 rpoplpush 命令,弹出 list1最右边的节点,插入到 list2 最左边
        String val = (String) redisTemplate.opsForList().rightPopAndLeftPush(KEY1, KEY2);
        System.out.println("rightPopAndLeftPush:" + val);

        // 相当于 brpoplpush 命令,注意在 Spring 中使用超时参数区分
        String value3 = (String) redisTemplate.opsForList().rightPopAndLeftPush(KEY1, KEY2, 3, TimeUnit.SECONDS);
        System.out.println("rightPopAndLeftPush:" + value3);

        printList(redisTemplate, KEY2, 0, 10);
    }

    public static void printList(RedisTemplate redisTemplate,String key,int begin,int end){
        List<String> data = redisTemplate.opsForList().range(key, begin, end);
        for(String str: data){
            System.out.println(str);
        }
    }

    public static String flushDb(RedisTemplate<String,String> redisTemplate){
      return redisTemplate.execute(new RedisCallback<String>() {
          @Override
          public String doInRedis(RedisConnection connection) throws DataAccessException {
              connection.flushDb();
              return "ok";
          }
      });
    }

}

  在 Spring 中它和非阻塞命令的方法是一致的,只是它会通过超时参数进行区分,而且我们还可以通过方法设置时间的单位。 注意,它是阻塞的命令,在多线程的环境中,它能在一定程度上保证数据 的一致而性能却不佳。

   在实际使用过程中,blpop/brpop 可能会产生假死现象,就是当没有数据的时候通过blpop/brpop操作进入阻塞休眠状态,当再次有数据进来后,blpop/brpop操作并没有被唤醒继续执行pop, 这是为什么呢?

 

  什么问题? —— 空闲连接的问题。

 

  如果线程一直阻塞在哪里,Redis的客户端连接就成了闲置连接,闲置过久,服务器一般会主动断开连接,减少闲置资源占用。这个时候blpop/brpop会抛出异常来。所以编写客户端消费者的时候要小心,注意捕获异常,还要重试。

  

 


参考
原文:https://artisan.blog.csdn.net/article/details/82792672

posted on 2019-02-24 23:19  溪水静幽  阅读(558)  评论(0)    收藏  举报