Redis数据结构--链表( linked-list)
概述
- 链表结构是 Redis 中一个常用的结构,可以存储多个字符串
- 是有序的
- 能够存储2的32次方减一个节点(超过 40 亿个节点)
- Redis 链表是双向的,因此即可以从左到右,也可以从右到左遍历存储的节点
- 链表结构查找性能不佳,但 插入和删除速度很快
链表结构的优势在于插入和删除的便利 ,因为链表的数据节点是分配在不同的内存区域的,并不连续,只是根据上一个节点保存下一个节点的顺序来索引而己,无需移动元素。
对于很多个节点同时操作的,需要考虑其花费的时间,链表数据结构对于查找而言并不适合于大数据。需要考虑插入和删除内容的大小,因为这将是十分消耗性能的命令,会导致 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"; } }); } }
有些命令 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