第一次使用redis看的文章(转)

本文介绍了Redis的安装步骤及在Windows环境下的配置方法,并通过示例演示如何使用Java开发包Jedis进行基本操作,包括字符串、Map、List、Set等数据类型的增删改查。
摘要  redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从...

0.前言

    因为是初次使用,所以是在windows下进行安装和使用,参考了几篇博客,下面整理一下

1.安装Redis

官方网站:http://redis.io/

官方下载:http://redis.io/download 可以根据需要下载不同版本

windows版:https://github.com/MSOpenTech/redis

github的资源可以ZIP直接下载的(这个是给不知道的同学友情提示下)

下载完成后 可以右键解压到 某个硬盘下 比如D:\Redis\redis-2.6

在D:\Redis\redis-2.6\bin\release下 有两个zip包 一个32位一个64位 

根据自己windows的位数 解压到D:\Redis\redis-2.6 根目录下

2.启动Redis

进入redis目录后 开启服务  (注意加上redis.conf)

?
1
redis-server.exe redis.conf

这个窗口要保持开启  关闭时redis服务会自动关闭

redis会自动保存数据到硬盘 所以图中是我第二次开启时 多了一个 DB loaded from disk 

3.测试使用

另外开启一个命令行窗口 进入redis目录下 (注意修改自己的ip)

?
1
redis-cli.exe -h 192.168.10.61 -p 6379

4.Java开发包Jedis

Jedis :http://www.oschina.net/p/jedis (Redis的官方首选Java开发包)

?
1
2
3
4
5
6
7
8
<!--Redis -->
         < dependency >
             < groupId >redis.clients</ groupId >
             < artifactId >jedis</ artifactId >
             < version >2.0.0</ version >
             < type >jar</ type >
             < scope >compile</ scope >
         </ dependency >

测试例子原帖:http://flychao88.iteye.com/blog/1527163

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package com.lujianing.utils;
 
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
 
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
  * Created by lujianing on 14-2-28.
  */
public class JedisUtilTest {
 
 
 
         JedisPool pool;
         Jedis jedis;
         @Before
         public void setUp() {
             pool = new JedisPool( new JedisPoolConfig(), "192.168.10.61" );
 
             jedis = pool.getResource();
          //   jedis.auth("password");
         }
 
 
         @Test
         public void testGet(){
             System.out.println(jedis.get( "lu" ));
         }
 
         /**
          * Redis存储初级的字符串
          * CRUD
          */
         @Test
         public void testBasicString(){
             //-----添加数据----------
             jedis.set( "name" , "minxr" ); //向key-->name中放入了value-->minxr
             System.out.println(jedis.get( "name" )); //执行结果:minxr
 
             //-----修改数据-----------
             //1、在原来基础上修改
             jedis.append( "name" , "jarorwar" );   //很直观,类似map 将jarorwar append到已经有的value之后
             System.out.println(jedis.get( "name" )); //执行结果:minxrjarorwar
 
             //2、直接覆盖原来的数据
             jedis.set( "name" , "闵晓荣" );
             System.out.println(jedis.get( "name" )); //执行结果:闵晓荣
 
             //删除key对应的记录
             jedis.del( "name" );
             System.out.println(jedis.get( "name" )); //执行结果:null
 
             /**
              * mset相当于
              * jedis.set("name","minxr");
              * jedis.set("jarorwar","闵晓荣");
              */
             jedis.mset( "name" , "minxr" , "jarorwar" , "闵晓荣" );
             System.out.println(jedis.mget( "name" , "jarorwar" ));
 
         }
 
         /**
          * jedis操作Map
          */
         @Test
         public void testMap(){
             Map<String,String> user= new HashMap<String,String>();
             user.put( "name" , "minxr" );
             user.put( "pwd" , "password" );
             jedis.hmset( "user" ,user);
             //取出user中的name,执行结果:[minxr]-->注意结果是一个泛型的List
             //第一个参数是存入redis中map对象的key,后面跟的是放入map中的对象的key,后面的key可以跟多个,是可变参数
             List<String> rsmap = jedis.hmget( "user" , "name" );
             System.out.println(rsmap);
 
             //删除map中的某个键值
//        jedis.hdel("user","pwd");
             System.out.println(jedis.hmget( "user" , "pwd" )); //因为删除了,所以返回的是null
             System.out.println(jedis.hlen( "user" )); //返回key为user的键中存放的值的个数1
             System.out.println(jedis.exists( "user" )); //是否存在key为user的记录 返回true
             System.out.println(jedis.hkeys( "user" )); //返回map对象中的所有key  [pwd, name]
             System.out.println(jedis.hvals( "user" )); //返回map对象中的所有value  [minxr, password]
 
             Iterator<String> iter=jedis.hkeys( "user" ).iterator();
             while (iter.hasNext()){
                 String key = iter.next();
                 System.out.println(key+ ":" +jedis.hmget( "user" ,key));
             }
 
         }
 
         /**
          * jedis操作List
          */
         @Test
         public void testList(){
             //开始前,先移除所有的内容
             jedis.del( "java framework" );
             System.out.println(jedis.lrange( "java framework" , 0 ,- 1 ));
             //先向key java framework中存放三条数据
             jedis.lpush( "java framework" , "spring" );
             jedis.lpush( "java framework" , "struts" );
             jedis.lpush( "java framework" , "hibernate" );
             //再取出所有数据jedis.lrange是按范围取出,
             // 第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有
             System.out.println(jedis.lrange( "java framework" , 0 ,- 1 ));
         }
 
         /**
          * jedis操作Set
          */
         @Test
         public void testSet(){
             //添加
             jedis.sadd( "sname" , "minxr" );
             jedis.sadd( "sname" , "jarorwar" );
             jedis.sadd( "sname" , "闵晓荣" );
             jedis.sadd( "sanme" , "noname" );
             //移除noname
             jedis.srem( "sname" , "noname" );
             System.out.println(jedis.smembers( "sname" )); //获取所有加入的value
             System.out.println(jedis.sismember( "sname" , "minxr" )); //判断 minxr 是否是sname集合的元素
             System.out.println(jedis.srandmember( "sname" ));
             System.out.println(jedis.scard( "sname" )); //返回集合的元素个数
         }
 
         @Test
         public void test() throws InterruptedException {
             //keys中传入的可以用通配符
             System.out.println(jedis.keys( "*" )); //返回当前库中所有的key  [sose, sanme, name, jarorwar, foo, sname, java framework, user, braand]
             System.out.println(jedis.keys( "*name" )); //返回的sname   [sname, name]
             System.out.println(jedis.del( "sanmdde" )); //删除key为sanmdde的对象  删除成功返回1 删除失败(或者不存在)返回 0
             System.out.println(jedis.ttl( "sname" )); //返回给定key的有效时间,如果是-1则表示永远有效
             jedis.setex( "timekey" , 10 , "min" ); //通过此方法,可以指定key的存活(有效时间) 时间为秒
             Thread.sleep( 5000 ); //睡眠5秒后,剩余时间将为<=5
             System.out.println(jedis.ttl( "timekey" ));   //输出结果为5
             jedis.setex( "timekey" , 1 , "min" );        //设为1后,下面再看剩余时间就是1了
             System.out.println(jedis.ttl( "timekey" ));  //输出结果为1
             System.out.println(jedis.exists( "key" )); //检查key是否存在
             System.out.println(jedis.rename( "timekey" , "time" ));
             System.out.println(jedis.get( "timekey" )); //因为移除,返回为null
             System.out.println(jedis.get( "time" )); //因为将timekey 重命名为time 所以可以取得值 min
 
             //jedis 排序
             //注意,此处的rpush和lpush是List的操作。是一个双向链表(但从表现来看的)
             jedis.del( "a" ); //先清除数据,再加入数据进行测试
             jedis.rpush( "a" , "1" );
             jedis.lpush( "a" , "6" );
             jedis.lpush( "a" , "3" );
             jedis.lpush( "a" , "9" );
             System.out.println(jedis.lrange( "a" , 0 ,- 1 )); // [9, 3, 6, 1]
             System.out.println(jedis.sort( "a" )); //[1, 3, 6, 9]  //输入排序后结果
             System.out.println(jedis.lrange( "a" , 0 ,- 1 ));
 
         }
 
 
}

Redis会定时 保存数据到硬盘上

<think>我们面对的问题是如何使用Redis缓存来实现分页查询,特别是当用户请求第二页数据时,不再查询数据库,而是直接从Redis缓存中获取。根据引用内容,我们可以知道使用RedisList数据结构可以加速分页查询。然而,引用[1]和引用[4]都提到了使用List的range操作来获取分页数据。同时,引用[3]指出了传统分页缓存的缺点:一旦数据有变化,所有分页缓存都失效。因此,我们需要考虑缓存更新的问题,但用户当前的问题重点是如何避免第二次查询数据库。解决方案:1.将查询结果集按照页码(或者偏移量)缓存到Redis中。但是这种方法(如引用[3]所说)在数据更新时会导致所有分页缓存失效,不够灵活。2.另一种方案(如引用[4]所述):使用一个List来存储整个数据集(或者某个查询条件对应的全部数据),然后通过LRANGE命令来获取分页数据。这样,当请求第二页时,我们可以直接从Redis的List中通过range取出相应的数据,而不用查询数据库。但是,引用[4]同时指出,如果整个数据集很大,那么第一次查询时可能会将大量数据加载到缓存中,并且当缓存失效时,并发写缓存可能会导致重复数据。但是,如果我们的数据量不是特别巨大,且对实时性要求不是特别高,这种方法是可以接受的。具体实现步骤:假设我们有一个查询条件,例如按照时间倒序排列的评论列表,我们将其对应的所有主键ID(或者序列化后的整个对象)存储在一个RedisList中。步骤:a.当第一次查询时(无论是第一页还是其他页),先检查Redis中是否存在这个List的key(例如:根据查询条件生成的唯一key)。b.如果存在,则使用LRANGE命令从List中获取指定范围的数据(例如:第一页是0到9,第二页是10到19)。c.如果不存在,则从数据库中查询满足条件的所有主键ID(注意:这里是一次性查询全部,所以如果数据量极大,可能不适合),然后保存到Redis的List中,并设置一个合理的过期时间(以防数据更新)。然后再用LRANGE取出当前页的数据。d.当数据更新时,我们需要更新Redis中对应的List(或者直接删除缓存,等待下次查询重新加载)。但是,用户特别提到要避免第二页查询数据库,那么按照上述方法,只要这个List已经缓存了,那么无论是第几页,都不会再查询数据库。问题:如果数据更新频繁,每次更新都导致整个List重新加载,那么性能可能不佳。因此,我们需要根据实际业务场景权衡。另一种优化方法(针对大数据量):我们可以使用有序集合(SortedSet)来存储,并给每个元素设置一个分数(例如时间戳),这样我们可以按照分数范围来分页。但是,引用[2]提到Redis没有模糊条件查询,所以如果需要条件查询,这种方式可能不够灵活。鉴于用户的问题,我们采用List的方式来实现,因为直接且简单。示例流程(以评论分页为例,每页10条):1.用户查询第一页:page=1,size=10-根据查询条件(如文章ID)生成key:例如,comment:article:123-检查Redis中是否存在key-不存在:从数据库查询该文章下的所有评论ID(注意:这里只查ID,避免数据量大时传输过慢),然后使用RPUSH(或LPUSH,根据排序需求)将ID列表存入Redis的List,并设置过期时间(比如10分钟)。-然后使用LRANGEkey09获取第一页的10条ID。-再根据这些ID去数据库查询对应的评论详情(这里也可以考虑将评论对象本身缓存到Redis的Hash中,这样下一步就不用查数据库了,但注意这又是一层优化)。-但是,为了避免N+1查询,我们也可以一次性将整个评论对象缓存到List中(直接存储序列化的对象字符串),这样我们就可以直接用LRANGE获取到数据,而不需要再查数据库了。2.用户查询第二页:page=2,size=10-同样的key,我们直接使用LRANGEkey1019来获取第二页的数据(假设List中有数据)。-因为缓存已经存在,所以我们不需要访问数据库。注意:这种方案在数据更新时需要更新缓存。比如新增评论,我们需要在List的头部(或尾部,根据排序)插入新的评论ID,并删除旧的缓存或者更新整个List。如果更新操作频繁,缓存维护的复杂度会增加。考虑到引用[4]中提到的缓存失效时并发写缓存可能导致重复数据,我们可以使用分布式锁(如Redis的setnx)来避免并发重建缓存的问题。总结:使用RedisList存储整个数据集(或ID列表),通过LRANGE分页,可以避免第二页查询数据库。但是,对于数据量非常大的情况,一次性加载全部数据到内存可能不现实。这时候,我们可以考虑使用其他方案,如只缓存前N页(比如前10页)的数据,或者使用SortedSet分页。由于用户的问题是如何避免第二页查询数据库,我们采用List方案,前提是数据量不是特别大。代码示例(伪代码):Stringkey="comment:article:"+articleId;List<String>pageData=redis.lrange(key,(page-1)*size,page*size-1);if(pageData==null||pageData.isEmpty()){//缓存未命中,从数据库获取全部评论的ID列表//加锁,防止并发重建缓存if(tryLock(key)){//再次检查缓存,因为可能在等待锁的过程中,其他线程已经重建了pageData=redis.lrange(key,(page-1)*size,page*size-1);if(pageData==null||pageData.isEmpty()){List<Long>commentIds=getCommentIdsFromDB(articleId);//假设返回的是评论ID列表//将整个ID列表存入Redis的Listredis.rpush(key,commentIds);//这里注意,如果排序是倒序,可能要用lpushredis.expire(key,600);//设置10分钟过期//重新分页pageData=redis.lrange(key,(page-1)*size,Math.min(commentIds.size(),page*size)-1);}releaseLock(key);}else{//未获得锁,可以选择等待重试或者从数据库分页//简单处理:等待一下再重试,或者直接查数据库(不建议)}}//如果pageData存储的是评论ID,那么还需要根据ID从数据库或者缓存中获取评论详情//如果pageData存储的是评论对象的序列化字符串,那么直接反序列化即可另一种做法:我们并不在List中存储整个评论对象,而是存储ID,然后使用Redis的Hash结构来缓存每个评论对象。这样在分页获取ID列表后,再用这些ID去Hash中获取对象。注意:这里我们只讨论了分页查询ID列表,然后获取对象详情。但详情可能也在缓存中,所以后续获取详情时可能也不需要查数据库,实现完全脱离数据库。但是,如果数据量巨大,这种方案不可行,因为一次性加载全部ID到RedisList中,内存占用大,而且可能超过Redis内存限制。因此,在实际项目中,需要根据数据量、更新频率、业务要求等权衡。最后,根据引用[2],Redis本身不支持条件查询,所以如果我们的分页需要带条件查询(比如同时需要时间范围等),那么这种方案就不适用了。针对用户的问题,我们推荐使用List+LRANGE的方案,前提是数据量不大,且分页条件固定。如果数据量大,我们可以考虑只缓存前几页的数据(例如前10页),而后续的页码则走数据库查询。这样也能满足用户访问前几页(包括第二页)时快速响应。总结:避免第二页查询数据库的核心是提前将整个数据集(或ID集)缓存到RedisList中,通过LRANGE进行分页。下面生成相关问题:§§相关问题§§1.当数据量很大时,使用RedisList缓存整个数据集有什么缺点?如何改进?2.如何使用Redis有序集合(SortedSet)实现分页查询?3.如何更新Redis中缓存的分页数据?4.Redis缓存分页数据时,如何避免缓存失效时并发重建导致的数据重复问题?5.除了List和SortedSet,Redis还有哪些数据结构适合实现分页查询?</think> ### Redis分页缓存实现方案 根据引用的资料和您的需求,可以通过以下方案实现Redis分页缓存,确保第二页查询不访问数据库: #### ✅ 核心方案:使用Redis List存储完整数据集 1. **缓存结构设计** - 使用**单个List键**存储整个查询结果集(如`comments:article123`) - 存储顺序需与数据库排序一致(如时间倒序) - 建议存储ID而非完整数据(节省内存,通过ID二次查询详情) 2. **数据读取流程** ```python # 伪代码实现 def get_page(page=1, size=10): key = "comments:article123" # 计算分页偏移量 start = (page - 1) * size # 第二页: (2-1)*10=10 end = start + size - 1 # 10 + 10 -1 =19 # 从Redis读取分页数据 ids = redis.lrange(key, start, end) # 关键点:直接按偏移量获取 if not ids: # 缓存穿透处理(省略) else: # 根据IDs获取详情(HGETALL或数据库查询) return load_details(ids) ``` #### ⚡ 性能优化要点 1. **首次加载策略** - 首次查询时加载**全量数据**到List: ```python if not redis.exists(key): all_ids = db.query("SELECT id FROM comments ORDER BY time DESC") redis.rpush(key, *all_ids) # 按顺序插入 redis.expire(key, 600) # 设置过期时间() ``` 2. **防缓存穿透** ```python # 特殊值标记空结果集 if not all_ids: redis.rpush(key, "__EMPTY__") ``` 3. **缓存更新策略** - 新增数据时:`redis.lpush(key, new_id)`(新评论插到头部) - 删除数据时:`redis.lrem(key, 0, deleted_id)`(需定位删除) - 修改时:只需更新Hash中的详情,ID顺序不变则无需处理List #### 🛡️ 并发控制(引用[4]问题解决方案) ```python # 使用Redis分布式锁防止缓存击穿 lock = redis.lock("lock:" + key, timeout=3) if lock.acquire(): try: if not redis.exists(key): data = db.query(...) redis.rpush(key, *data) finally: lock.release() ``` #### 💡 方案优势 1. **第二页免DB查询**:通过`LRANGE`直接获取分片数据 2. **内存高效**:仅存储ID(每ID约20字节) 3. **排序一致**:List天然保持插入顺序 4. **更新灵活**:独立维护数据和顺序 > **数据量警告**:当总数据量>10万时,需改用Sorted Set分桶方案(参考引用[3]的顶次数场景) ### 📚 相关拓展问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值