Redis入门及在商城案例中的使用
2017-07-30 18:19
661 查看
自学那么多月以来学到的知识点挺多几乎每天都在接受新东西,接受的多忘的也多,想回头再去找也不知道从哪里找了,所以决定执行好几个月前就决定的事情-写博客,用来记录自己每次所学习到的东西。
由于自己实习的时候,自己做的项目的数据库就是用的MySql跟Redis。所以先写下以前在做商城案例的时候用的redis。
redis属于NoSql分类,它把数据都是缓存在内存中的,我们都知道内存的读写效率跟硬盘不是一个级别的,最后redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。既然用redis读取效率那么高,最后内容也会添加到磁盘那么我们就当然要使用它了。
对于redis的安装我自己也是网上找的。建议自己使用的时候最好linux下安装一个,windows下装一个桌面版的。
一、redis的基本操作
redis是一个key-value存储系统。它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sortedset–有序集合)和hash(哈希类型)五种数据类型,存储形式均为字符串。
启动redis
启动redis服务:[root@itheima32 bin]# ./redis-server redis.conf
查看进程:[root@itheima32 bin]# ps aux|grep redis
root 1793 0.3 0.0 34992 1772 ? Ssl 10:29 0:00 ./redis-server *:6379
root 1797 0.0 0.0 5532 812 pts/0 S+ 10:29 0:00 grep redis
表示启动成功。
启动客户端:[root@itheima32 bin]# ./redis-cli
127.0.0.1:6379>
其中6379表示本机的6379端口服务。
连接到该linux:ctrl+c退出,接着切换
[root@itheima32 bin]# ./redis-cli -h 192.168.25.128 -p 6379
192.168.25.128:6379>
这就切换成功了。接下来进行操作。
1.1、String类型
为了看的更直观,这里就直接展示操作内容。
存储:set key value
取值:get key
删除:del key
查看所有键:keys *
示例:
增1:incr key
减1:decr key
注意, 虽然redis存储形式都是字符串,但是自增减的时候要求value必须能解析成数值类型,比如你的value是”1ad”那就不行,就拿商城案例来说,可以用来生成订单号(要求绝对不要重复),或者添加,减少商品数量时候使用。
示例:先添加键值对 str1 3,再自增就成了4
1.2、Hash类型
相当于一个key对于一个map,map中还有key-value
存储:hset key field value
取值:hget key field
查看某个键对应的map里面的所有key:hkeys key
查看某个键对应的map里面的所有的value:hvals key
查看某个键的map:hgetall key
示例:
1.3、List类型
存储:push,分为lpush list v1 v2 v3 v4 …(左边添加),rpush list v1 v2 v3 v4 …(右边添加)
取值:pop,分为lpop lpop list(左边取,移除list最左边的值) ,rpop rpop list(右边取,移除list最右边的值)
查看list:lrange key 0 -1(0 -1表示查看所有)
存储,取值操作跟栈的存储,取值方法是一样的,而不是add,get,存储的值有序可以重复。用pop取值取完后该值就从list中移除了。
示例:
1.4、Set类型
set中的元素是无序不重复的,出现重复会覆盖
存储:sadd key v1 v2 v3 …
移除:srem key v
查看set集合: smembers key
另外还提供了差集,交集,并集操作
差集:sdiff seta setb(seta中有setb中没有的元素)
交集:sinter seta setb
并集:sunion seta setb
1.5、SortedSet,有序Set
存储:存储的时候要求对set进行排序,需要对存储的每个value值进行打分,默认排序是分数由低到高。zadd key 分数1 v1 分数2 v2 分数3 v3…
取值:取指定的值 zrem key value
取所有的值(不包括分数):zrange key 0 -1,降序取值用zrevrange key 0 -1
取所有的值(带分数):zrange(zrevrange) key 0 -1 withscores
示例:
1.6、key命令
由于redis是内存存储数据,所以不能够存储过大的数据量,所以存储在redis中的数据,在不再需要的时候应该清除掉。比如,用户买完东西下订单,生成的订单信息存储了在redis中,但是用户一直没支付那么存储在redis中的订单信息就应该清除掉,这个时候就可以通过设置redis的过期时间来完成,一旦达到了过期时间就清除该信息。
设置key的过期时间:expired key 过期时间(秒)
查看key的有效剩余时间:ttl key
清除key的过期时间,持久化该key:persist key
-1:表示持久化
-2: 表示该key不存在
示例:
对与上面的基本操作,就我个人在案例中以及实习的真实项目中来说,至少得要掌握String类型,Hash类型以及key命令。
二、商城中使用redis
2.1、使用jedis操作redis(单机版)测试
进行测试之前需要引入依赖
单机版测试:
这里只测试了String类型,Jedis提供了与redis操作对应的方法来操作redis。
操作hash类型主要有:hset(…),hget(…),hdel(…),hexist(…)
key命令:persist(key),expire(key,seconds)
具体的根据提示来选择自己需要的。
单机版测试,从连接池获取jedis:
这个就跟从数据库连接池获取连接道理是一样的,需要连接的时候从连接池中取获取一个连接就行,使用完后就放回连接池,而不用重新去创建一个连接,这项技术能大大提高操作redis的性能。
2.2、使用JedisClient操作redis (单机版)
测试的时候我们发现,每次都要自己创建关闭连接,频繁使用的话会显得很繁琐。所以我们可以一开始就定义好对应方法来帮我们关闭连接。使用的时候调用自己的方法即可。
其实项目上线后如果网站做大了,那么用就一个redis肯定是不够的,万一总挂对网站影响就大了,所以最后redis需要进行集群,这在后面会讲到。但是开发阶段使用单机版就行了,不管是单机还是集群最后用到的jedis方法还是一样的,只是实现不一样。那么我们可以写个接口,定义好两者公共的方法,然后只要分别提供两个实现类来实现这个接口就行。这个接口的名称在这里叫JedisClient(由自己定义,当然也可以叫别的名字)。
接口如下:
这里定义的方法主要是针对String类型,Hash类型,key命令。
比如String类型,Hash类型的存储、获取、删除、是否存在指定key,设置过期时间,自增,自间,有效时间等。
单机版实现类如下:
其实这些方法主要还是调用了jedis的方法,主要是帮我们创建、关闭了连接然后进行封装,从而在项目中使用可以简化操作。
集群版这里就不贴出来了,后面讲集群的时候会给出。
2.3、从Spring容器中获取JedisClient
在案例中,JedisClient是与Spring整合的。不然每次都要自己创建JedisClient对象,使用Spring那么就不用我们自己创建对象了。
单机版JedisClient与spring整合:
这样初始化Spring容器的时候就会创建单机版JedisClient了
测试:
到这里就可以大胆的在项目中使用JedisClient来操作redis了。
2.4、实现redis在商城案例中进行缓存
2.4.1、商城首页轮播图使用redis
需求:
在商城首页,有个轮播图:
图种轮播的内容是8个,暂时都是伪造的。
而轮播展示的内容按照以往学习的经验来说就应该是从数据库里面查出来的。这些内容是属于项目的内容管理模块,所轮播的内容是同属一个内容分类id,也就是说,代码实现是根据内容分类id查找到内容信息(4个)然后在页面显示,查找到的这个内容分类id我们可以在项目中自己设置,比如就把这个id放在配置文件里面。需要改变轮播的内容时候就去改一下配置文件里面的内容分类id。然后会根据这个分类id去数据库中查询对应的内容。所涉及的两张表一个是content_category,一个是content(外键为content_category的id)。为了更直观,表也给出来了。
两张表如下:
分类id为89分类下有4条内容记录。所以实际上首页轮播图展示的内容应该是4个。
不使用NoSql存在的问题:
对于需要的信息如果每次都去数据库查,如果人少倒没事,但如果并发很高的情况那么数据库压力就大了严重还可能会宕机,很影响消费体验。这个时候就可以用redis了来提高效率了。
处理过程:当访问商城首页的时候,调用服务,查看缓存里面是否有分类id(比如这里分类id是89)对应的内容(这里共4个),如果有的话,直接从缓存取出来显示到页面。如果没有则查询数据库,查出来的数据需要存入到redis中,再显示到页面。
商城首页表现层工程(e3-portal-web)
内容服务层工程(e3-content-service):
对于redis中用哪种类型的value来存放内容的List集合呢,如果用String类型的话,那么键就是内容分类id,值就是List集合其中存放的是该分类id下的内容对象,但是考虑到该分类id也对应了其它信息,也要存放在redis中,那么一个分类id对应两个不同的查询集合肯定是不行的。当然可以使用分类id加不同的前缀后缀来加以区分。
在这里为了更直观而又不冲突,可以考虑用Hash来存储。key的话根据实际需要自己定义,比如这里叫CONTENT_LIST,对应的map中的key为内容分类id,值为内容列表。
用try,catch对redis的操作包裹起来是因为redis出现的问题是不应该影响正常业务逻辑的,否则出现异常会出现事务回滚的问题。
测试:
访问商城首页显示如下。
第一次访问是从数据库中查的,查询出来之后放到redis中,后续则都是从redis中查询出来。如果通过修改属性配置文件中的内容分类id,那么则在redis中不存在,还会查询数据库。
查看客户端redis,(可以通过去客户端使用命令去查询),建议下载个桌面版的redis连接到linux上安装好的redis。那么在桌面版的redis中也能查看linux上redis中存储的信息了。
发现原本redis中不存在该信息,但是现在却有了。
Redis实现缓存同步
考虑这么个问题,如果在后台我们在内容分类id(比如为89)下面添加了一个内容或者减少了一个内容。当再次访问首页的时候,还是根据分类id为89先去redis中查,发现redis中有数据,按照上面来说就是4条数据,那么首页轮播图就是4个。但是实际上数据库中已经不是4个了,而是5个或者3个或者其它。这就存在信息不同步问题了。这里解决的办法是,每次添加内容或者减少内容等凡是对内容进行了修改操作,那么就需要清空redis中的信息。当下次访问首页的时候会去数据库中查询最新的内容信息,存放到redis中,从而实现了缓存同步,代码如下。
商品管理表现层(e3-manager-web)
工具类(E3Result)
内容管理服务层:
删除内容跟修改内容就不再写了,同样都是清除内容分类id对应的缓存数据。
至此商城首页轮播图使用redis来缓存内容信息就做完了。
2.4.2、在商品详情中使用redis
在商城的其他地方,比如查询到商品,点击该商品会进入到商品详情列表。这里也使用了redis,比如在促销日双11双12之类,有些商品很畅销,那么单位时间的浏览量就会很高,对于这种情况我们可以将商品信息添加到redis中。
注意:虽然逻辑跟首页轮播类似,点击商品的时候会根据商品id去redis中查询是否存在该商品信息,有的话直接响应,如果没有那么就去查数据库,查出来的数据存到redis中再做响应。
但是跟首页轮播中有一点不同的是,商品信息不能一直在redis中存放,商品种类过多的话非常耗费redis的存储空间,所以需要设置一下过期时间,如果这段时间该商品没人访问的话就应该将该商品信息从redis中清除来释放空间,如果有人访问的话那么就重新设置回原来的过期时间。
商品详情表现层工程(e3-item-web)
商品详情服务层工程(e3-manager-service)
这里使用的是String类型,写成 前缀:d:后缀,这种形式在桌面版redis中文件目录会有层次结构。
注:对上面的代码不必深究,也不能单独拿那些代码进行测试,主要是知道redis的使用。只要会使用jedis的方法,那么自己进行封装就很容易,主要还是理解业务需求,理清了需求redis部分的代码实现就不难了。
由于自己实习的时候,自己做的项目的数据库就是用的MySql跟Redis。所以先写下以前在做商城案例的时候用的redis。
redis属于NoSql分类,它把数据都是缓存在内存中的,我们都知道内存的读写效率跟硬盘不是一个级别的,最后redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。既然用redis读取效率那么高,最后内容也会添加到磁盘那么我们就当然要使用它了。
对于redis的安装我自己也是网上找的。建议自己使用的时候最好linux下安装一个,windows下装一个桌面版的。
一、redis的基本操作
redis是一个key-value存储系统。它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sortedset–有序集合)和hash(哈希类型)五种数据类型,存储形式均为字符串。
启动redis
启动redis服务:[root@itheima32 bin]# ./redis-server redis.conf
查看进程:[root@itheima32 bin]# ps aux|grep redis
root 1793 0.3 0.0 34992 1772 ? Ssl 10:29 0:00 ./redis-server *:6379
root 1797 0.0 0.0 5532 812 pts/0 S+ 10:29 0:00 grep redis
表示启动成功。
启动客户端:[root@itheima32 bin]# ./redis-cli
127.0.0.1:6379>
其中6379表示本机的6379端口服务。
连接到该linux:ctrl+c退出,接着切换
[root@itheima32 bin]# ./redis-cli -h 192.168.25.128 -p 6379
192.168.25.128:6379>
这就切换成功了。接下来进行操作。
1.1、String类型
为了看的更直观,这里就直接展示操作内容。
存储:set key value
取值:get key
删除:del key
查看所有键:keys *
示例:
192.168.25.128:6379> set str1 123 OK 192.168.25.128:6379> set str2 abc OK 192.168.25.128:6379> set str3 xixi OK 192.168.25.128:6379> get str1 "123" 192.168.25.128:6379> get str2 "abc" 192.168.25.128:6379> get str3 "xixi" 192.168.25.128:6379> del str1 (integer) 1 192.168.25.128:6379> keys * 1) "str2" 2) "str3"
增1:incr key
减1:decr key
注意, 虽然redis存储形式都是字符串,但是自增减的时候要求value必须能解析成数值类型,比如你的value是”1ad”那就不行,就拿商城案例来说,可以用来生成订单号(要求绝对不要重复),或者添加,减少商品数量时候使用。
示例:先添加键值对 str1 3,再自增就成了4
192.168.25.128:6379> set str1 3 OK 192.168.25.128:6379> incr str1 (integer) 4
1.2、Hash类型
相当于一个key对于一个map,map中还有key-value
存储:hset key field value
取值:hget key field
查看某个键对应的map里面的所有key:hkeys key
查看某个键对应的map里面的所有的value:hvals key
查看某个键的map:hgetall key
示例:
192.168.25.128:6379> hset hone field1 123 (integer) 1 192.168.25.128:6379> hset hone field2 abc (integer) 1 192.168.25.128:6379> hset hone field3 haha (integer) 1 192.168.25.128:6379> hget hone field1 "123" 192.168.25.128:6379> hget hone field2 "abc" 192.168.25.128:6379> hget hone field3 "haha" 192.168.25.128:6379> hkeys hone 1) "field1" 2) "field2" 3) "field3" 192.168.25.128:6379> hvals hone 1) "123" 2) "abc" 3) "haha" 192.168.25.128:6379> hgetall hone 1) "field1" 2) "123" 3) "field2" 4) "abc" 5) "field3" 6) "haha"
1.3、List类型
存储:push,分为lpush list v1 v2 v3 v4 …(左边添加),rpush list v1 v2 v3 v4 …(右边添加)
取值:pop,分为lpop lpop list(左边取,移除list最左边的值) ,rpop rpop list(右边取,移除list最右边的值)
查看list:lrange key 0 -1(0 -1表示查看所有)
存储,取值操作跟栈的存储,取值方法是一样的,而不是add,get,存储的值有序可以重复。用pop取值取完后该值就从list中移除了。
示例:
``` 192.168.25.128:6379> lpush list1 1 2 3 4 5 6 (integer) 6 192.168.25.128:6379> rpush list1 a b c d e (integer) 11 192.168.25.128:6379> lrange list1 0 -1 1) "6" 2) "5" 3) "4" 4) "3" 5) "2" 6) "1" 7) "a" 8) "b" 9) "c" 10) "d" 11) "e" 192.168.25.128:6379> lpop list1 "6" 192.168.25.128:6379> lrange list1 0 -1 1) "5" 2) "4" 3) "3" 4) "2" 5) "1" 6) "a" 7) "b" 8) "c" 9) "d" 10) "e" 192.168.25.128:6379> rpop list1 "e" 192.168.25.128:6379> lrange list1 0 -1 1) "5" 2) "4" 3) "3" 4) "2" 5) "1" 6) "a" 7) "b" 8) "c" 9) "d"
1.4、Set类型
set中的元素是无序不重复的,出现重复会覆盖
存储:sadd key v1 v2 v3 …
移除:srem key v
查看set集合: smembers key
另外还提供了差集,交集,并集操作
差集:sdiff seta setb(seta中有setb中没有的元素)
交集:sinter seta setb
并集:sunion seta setb
192.168.25.128:6379> sadd set a b a b c d (integer) 4 192.168.25.128:6379> srem set a (integer) 1 192.168.25.128:6379> smembers set 1) "d" 2) "b" 3) "c" 192.168.25.128:6379> sadd seta a b c d e (integer) 5 192.168.25.128:6379> sadd setb c d e f g (integer) 5 192.168.25.128:6379> sdiff seta setb(差集,seta有setb没有) 1) "a" 2) "b" 192.168.25.128:6379> sdiff setb seta (差集,setb有seta没有) 1) "g" 2) "f" 192.168.25.128:6379> sinter seta setb(交集) 1) "d" 2) "e" 3) "c" 192.168.25.128:6379> sunion seta setb(并集) 1) "a" 2) "b" 3) "d" 4) "g" 5) "f" 6) "e" 7) "c"
1.5、SortedSet,有序Set
存储:存储的时候要求对set进行排序,需要对存储的每个value值进行打分,默认排序是分数由低到高。zadd key 分数1 v1 分数2 v2 分数3 v3…
取值:取指定的值 zrem key value
取所有的值(不包括分数):zrange key 0 -1,降序取值用zrevrange key 0 -1
取所有的值(带分数):zrange(zrevrange) key 0 -1 withscores
示例:
192.168.25.128:6379> zadd zset1 1 a 3 b 2 c 5 d(要求给定分数,从而达到排序效果,默认升序) (integer) 4 192.168.25.128:6379> zrange zset1 0 -1 1) "a" 2) "c" 3) "b" 4) "d" 192.168.25.128:6379> zrem zset1 a (integer) 1 192.168.25.128:6379> zrange zset1 0 -1 1) "c" 2) "b" 3) "d" 192.168.25.128:6379> zrevrange zset1 0 -1(按分数降序排) 1) "d" 2) "b" 3) "c" 192.168.25.128:6379> zrevrange zset1 0 -1 withscores 1) "d" 2) "5" 3) "b" 4) "3" 5) "c" 6) "2"
1.6、key命令
由于redis是内存存储数据,所以不能够存储过大的数据量,所以存储在redis中的数据,在不再需要的时候应该清除掉。比如,用户买完东西下订单,生成的订单信息存储了在redis中,但是用户一直没支付那么存储在redis中的订单信息就应该清除掉,这个时候就可以通过设置redis的过期时间来完成,一旦达到了过期时间就清除该信息。
设置key的过期时间:expired key 过期时间(秒)
查看key的有效剩余时间:ttl key
清除key的过期时间,持久化该key:persist key
-1:表示持久化
-2: 表示该key不存在
示例:
192.168.25.128:6379> expire zone 60 (integer) 1 192.168.25.128:6379> ttl zone (integer) 55 192.168.25.128:6379> ttl zone (integer) 51 192.168.25.128:6379> ttl zone (integer) 48 192.168.25.128:6379> ttl zone (integer) 37 192.168.25.128:6379> ttl zone (integer) 16 192.168.25.128:6379> ttl zone (integer) -2 -->(该key已经不存在) 192.168.25.128:6379> expire sone 30 (integer) 1 192.168.25.128:6379> ttl sone (integer) 22 192.168.25.128:6379> persist sone (integer) 1 192.168.25.128:6379> ttl sone (integer) -1 -->(该key是持久化的)
对与上面的基本操作,就我个人在案例中以及实习的真实项目中来说,至少得要掌握String类型,Hash类型以及key命令。
二、商城中使用redis
2.1、使用jedis操作redis(单机版)测试
进行测试之前需要引入依赖
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
单机版测试:
@Test public void testJedis() throws Exception{ //创建一个连接jedis对象,参数:host,port Jedis jedis = new Jedis("192.168.25.128", 6379); //直接使用jedis来操作redis,所有jedis的命令都对应一个方法 jedis.set("test123", "my first jedis test"); String s = jedis.get("test123"); System.out.println(s); //关闭连接 jedis.close(); } 输出结果:my first jedis test 去客户端查看: 192.168.25.128:6379> get test123 "my first jedis test"
这里只测试了String类型,Jedis提供了与redis操作对应的方法来操作redis。
操作hash类型主要有:hset(…),hget(…),hdel(…),hexist(…)
key命令:persist(key),expire(key,seconds)
具体的根据提示来选择自己需要的。
单机版测试,从连接池获取jedis:
@Test//连接单机版 public void testJedisPool() throws Exception{ JedisPool jedisPool = new JedisPool("192.168.25.128", 6379); //从连接池获得一个连接,就是一个jedis对象 Jedis jedis = jedisPool.getResource(); //操作redis String s = jedis.get("test123"); System.out.println(s); //关闭连接,每次使用完毕后关闭连接,连接池回收资源 jedis.close(); //关闭连接池 jedisPool.close(); }
这个就跟从数据库连接池获取连接道理是一样的,需要连接的时候从连接池中取获取一个连接就行,使用完后就放回连接池,而不用重新去创建一个连接,这项技术能大大提高操作redis的性能。
2.2、使用JedisClient操作redis (单机版)
测试的时候我们发现,每次都要自己创建关闭连接,频繁使用的话会显得很繁琐。所以我们可以一开始就定义好对应方法来帮我们关闭连接。使用的时候调用自己的方法即可。
其实项目上线后如果网站做大了,那么用就一个redis肯定是不够的,万一总挂对网站影响就大了,所以最后redis需要进行集群,这在后面会讲到。但是开发阶段使用单机版就行了,不管是单机还是集群最后用到的jedis方法还是一样的,只是实现不一样。那么我们可以写个接口,定义好两者公共的方法,然后只要分别提供两个实现类来实现这个接口就行。这个接口的名称在这里叫JedisClient(由自己定义,当然也可以叫别的名字)。
接口如下:
public interface JedisClient { String set(String key, String value); String get(String key); Boolean exists(String key); Long expire(String key, int seconds); Long ttl(String key); Long incr(String key); Long hset(String key, String field, String value); String hget(String key, String field); Long hdel(String key, String... field); Boolean hexists(String key, String field); List<String> hvals(String key); Long del(String key); }
这里定义的方法主要是针对String类型,Hash类型,key命令。
比如String类型,Hash类型的存储、获取、删除、是否存在指定key,设置过期时间,自增,自间,有效时间等。
单机版实现类如下:
public class JedisClientPool implements JedisClient { private JedisPool jedisPool; public JedisPool getJedisPool() { return jedisPool; } public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool; } public String set(String key, String value) { Jedis jedis = jedisPool.getResource(); String result = jedis.set(key, value); jedis.close(); return result; } public String get(String key) { Jedis jedis = jedisPool.getResource(); String result = jedis.get(key); jedis.close(); return result; } public Boolean exists(String key) { Jedis jedis = jedisPool.getResource(); Boolean result = jedis.exists(key); jedis.close(); return result; } public Long expire(String key, int seconds) { Jedis jedis = jedisPool.getResource(); Long result = jedis.expire(key, seconds); jedis.close(); return result; } public Long ttl(String key) { Jedis jedis = jedisPool.getResource(); Long result = jedis.ttl(key); jedis.close(); return result; } public Long incr(String key) { Jedis jedis = jedisPool.getResource(); Long result = jedis.incr(key); jedis.close(); return result; } public Long hset(String key, String field, String value) { Jedis jedis = jedisPool.getResource(); Long result = jedis.hset(key, field, value); jedis.close(); return result; } public String hget(String key, String field) { Jedis jedis = jedisPool.getResource(); String result = jedis.hget(key, field); jedis.close(); return result; } public Long hdel(String key, String... field) { Jedis jedis = jedisPool.getResource(); Long result = jedis.hdel(key, field); jedis.close(); return result; } public Boolean hexists(String key, String field) { Jedis jedis = jedisPool.getResource(); Boolean result = jedis.hexists(key, field); jedis.close(); return result; } public List<String> hvals(String key) { Jedis jedis = jedisPool.getResource(); List<String> result = jedis.hvals(key); jedis.close(); return result; } public Long del(String key) { Jedis jedis = jedisPool.getResource(); Long result = jedis.del(key); jedis.close(); return result; } }
其实这些方法主要还是调用了jedis的方法,主要是帮我们创建、关闭了连接然后进行封装,从而在项目中使用可以简化操作。
集群版这里就不贴出来了,后面讲集群的时候会给出。
2.3、从Spring容器中获取JedisClient
在案例中,JedisClient是与Spring整合的。不然每次都要自己创建JedisClient对象,使用Spring那么就不用我们自己创建对象了。
单机版JedisClient与spring整合:
<!-- 连接单机版--> <bean class="cn.e3mall.jedis.JedisClientPool"> <property name="jedisPool" ref="jedisPool"/> </bean> 配置单机版jedis连接池 <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <constructor-arg name="host" value="192.168.25.128"/> <constructor-arg name="port" value="6379"/> </bean>
这样初始化Spring容器的时候就会创建单机版JedisClient了
测试:
public void testJedisClient() throws Exception{ ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-redis.xml"); //从容器中拿到JedisClient对象 JedisClient jc = ac.getBean(JedisClient.class); jc.set("mytest", "jedisClient"); String s = jc.get("mytest"); System.out.println(s); } 输出:jedisClient
到这里就可以大胆的在项目中使用JedisClient来操作redis了。
2.4、实现redis在商城案例中进行缓存
2.4.1、商城首页轮播图使用redis
需求:
在商城首页,有个轮播图:
图种轮播的内容是8个,暂时都是伪造的。
而轮播展示的内容按照以往学习的经验来说就应该是从数据库里面查出来的。这些内容是属于项目的内容管理模块,所轮播的内容是同属一个内容分类id,也就是说,代码实现是根据内容分类id查找到内容信息(4个)然后在页面显示,查找到的这个内容分类id我们可以在项目中自己设置,比如就把这个id放在配置文件里面。需要改变轮播的内容时候就去改一下配置文件里面的内容分类id。然后会根据这个分类id去数据库中查询对应的内容。所涉及的两张表一个是content_category,一个是content(外键为content_category的id)。为了更直观,表也给出来了。
两张表如下:
分类id为89分类下有4条内容记录。所以实际上首页轮播图展示的内容应该是4个。
不使用NoSql存在的问题:
对于需要的信息如果每次都去数据库查,如果人少倒没事,但如果并发很高的情况那么数据库压力就大了严重还可能会宕机,很影响消费体验。这个时候就可以用redis了来提高效率了。
处理过程:当访问商城首页的时候,调用服务,查看缓存里面是否有分类id(比如这里分类id是89)对应的内容(这里共4个),如果有的话,直接从缓存取出来显示到页面。如果没有则查询数据库,查出来的数据需要存入到redis中,再显示到页面。
商城首页表现层工程(e3-portal-web)
public class IndexController { @Autowired private ContentService contentService; @Value("${CONTENT_LUNBO_ID}") private Long CONTENT_LUNBO_ID; @RequestMapping("/index") public String showIndex(Model model){ //根据分类id查询对应内容列表 List<TbContent> ad1List = contentService.getContentListByCid(CONTENT_LUNBO_ID); //将结果传递给页面 model.addAttribute("ad1List", ad1List); return "index"; } } 属性配置文件中:CONTENT_LUNBO_ID=89
内容服务层工程(e3-content-service):
对于redis中用哪种类型的value来存放内容的List集合呢,如果用String类型的话,那么键就是内容分类id,值就是List集合其中存放的是该分类id下的内容对象,但是考虑到该分类id也对应了其它信息,也要存放在redis中,那么一个分类id对应两个不同的查询集合肯定是不行的。当然可以使用分类id加不同的前缀后缀来加以区分。
在这里为了更直观而又不冲突,可以考虑用Hash来存储。key的话根据实际需要自己定义,比如这里叫CONTENT_LIST,对应的map中的key为内容分类id,值为内容列表。
@Service public class ContentServiceImpl implements ContentService{ @Autowired private TbContentMapper contentMapper; @Autowired private JedisClient jedisClient; @Value("${CONTENT_LIST}") private String CONTENT_LIST; /* * 根据内容分类id查询内容列表 */ public List<TbContent> getContentListByCid(Long cid) { try { //如果缓存中有直接响应结果 String json = jedisClient.hget(CONTENT_LIST, cid+""); if(StringUtils.isNotBlank(json)){ List<TbContent> list = JsonUtils.jsonToList(json, TbContent.class); return list; } //如果没有查询数据库 } catch (Exception e) { e.printStackTrace(); } //------------------------- TbContentExample example = new TbContentExample(); Criteria criteria = example.createCriteria(); //设置查询条件 criteria.andCategoryIdEqualTo(cid); //执行查询,withBlogs()表示 包含大文本 List<TbContent> list = contentMapper.selectByExampleWithBLOBs(example); //---------------------------- //把结果添加到缓存 try { jedisClient.hset(CONTENT_LIST, cid + "",JsonUtils.objectToJson(list)); } catch (Exception e) { e.printStackTrace(); } return list; } } 属性配置文件中:CONTENT_LIST=CONTENT_LIST
用try,catch对redis的操作包裹起来是因为redis出现的问题是不应该影响正常业务逻辑的,否则出现异常会出现事务回滚的问题。
测试:
访问商城首页显示如下。
第一次访问是从数据库中查的,查询出来之后放到redis中,后续则都是从redis中查询出来。如果通过修改属性配置文件中的内容分类id,那么则在redis中不存在,还会查询数据库。
查看客户端redis,(可以通过去客户端使用命令去查询),建议下载个桌面版的redis连接到linux上安装好的redis。那么在桌面版的redis中也能查看linux上redis中存储的信息了。
发现原本redis中不存在该信息,但是现在却有了。
Redis实现缓存同步
考虑这么个问题,如果在后台我们在内容分类id(比如为89)下面添加了一个内容或者减少了一个内容。当再次访问首页的时候,还是根据分类id为89先去redis中查,发现redis中有数据,按照上面来说就是4条数据,那么首页轮播图就是4个。但是实际上数据库中已经不是4个了,而是5个或者3个或者其它。这就存在信息不同步问题了。这里解决的办法是,每次添加内容或者减少内容等凡是对内容进行了修改操作,那么就需要清空redis中的信息。当下次访问首页的时候会去数据库中查询最新的内容信息,存放到redis中,从而实现了缓存同步,代码如下。
商品管理表现层(e3-manager-web)
/* * 内容管理controller */ @Controller public class ContentController { @Autowired private ContentService contentService; @RequestMapping(value="/content/save") @ResponseBody public E3Result addContent(TbContent tbContent){ //调用服务把内容数据保存到数据库 E3Result result = contentService.addContent(tbContent); return result; } }
工具类(E3Result)
/** * 自定义响应结构 */ public class E3Result implements Serializable{ // 定义jackson对象 private static final ObjectMapper MAPPER = new ObjectMapper(); // 响应业务状态 private Integer status; // 响应消息 private String msg; // 响应中的数据 private Object data; public static E3Result build(Integer status, String msg, Object data) { return new E3Result(status, msg, data); } public static E3Result ok(Object data) { return new E3Result(data); } public static E3Result ok() { return new E3Result(null); } public E3Result() { } public static E3Result build(Integer status, String msg) { return new E3Result(status, msg, null); } public E3Result(Integer status, String msg, Object data) { this.status = status; this.msg = msg; this.data = data; } public E3Result(Object data) { this.status = 200; this.msg = "OK"; this.data = data; } get,set方法 }
内容管理服务层:
@Service public class ContentServiceImpl implements ContentService{ @Autowired private TbContentMapper contentMapper; @Autowired private JedisClient jedisClient; @Value("${CONTENT_LIST}") private String CONTENT_LIST; /* * 将内容数据插入到内容表 */ public E3Result addContent(TbContent content) { content.setCreated(new Date()); content.setUpdated(new Date()); //插入到数据库 contentMapper.insert(content); //------------------- //缓存同步,删除缓存中对应的数据 jedisClient.hdel(CONTENT_LIST, content.getCategoryId().toString()); return E3Result.ok(); } /* * 根据内容分类id查询内容列表 */ 同上 }
删除内容跟修改内容就不再写了,同样都是清除内容分类id对应的缓存数据。
至此商城首页轮播图使用redis来缓存内容信息就做完了。
2.4.2、在商品详情中使用redis
在商城的其他地方,比如查询到商品,点击该商品会进入到商品详情列表。这里也使用了redis,比如在促销日双11双12之类,有些商品很畅销,那么单位时间的浏览量就会很高,对于这种情况我们可以将商品信息添加到redis中。
注意:虽然逻辑跟首页轮播类似,点击商品的时候会根据商品id去redis中查询是否存在该商品信息,有的话直接响应,如果没有那么就去查数据库,查出来的数据存到redis中再做响应。
但是跟首页轮播中有一点不同的是,商品信息不能一直在redis中存放,商品种类过多的话非常耗费redis的存储空间,所以需要设置一下过期时间,如果这段时间该商品没人访问的话就应该将该商品信息从redis中清除来释放空间,如果有人访问的话那么就重新设置回原来的过期时间。
商品详情表现层工程(e3-item-web)
/* * 商品详情页面展示Controller */ @Controller public class ItemController { @Autowired private ItemService itemService; @RequestMapping("/item/{itemId}") public String showItemInfo(@PathVariable Long itemId, Model model){ //调用服务取商品基本信息 TbItem tbItem = itemService.getItemById(itemId); Item item = new Item(tbItem); //取商品描述信息 TbItemDesc tbItemDesc = itemService.getItemDescById(itemId); model.addAttribute("item", item); model.addAttribute("itemDesc", tbItemDesc); //返回逻辑shitu return "item"; } }
商品详情服务层工程(e3-manager-service)
@Service public class ItemServiceImpl implements ItemService{ @Autowired private TbItemMapper itemMapper; @Autowired private TbItemDescMapper descMapper; //@Autowired消息队列的时候使用 //private JmsTemplate jmsTemplate; //@Resource //private Destination topicDestination; @Autowired private JedisClient jedisClient; @Value("${REDIS_ITEM_PRE}") private String REDIS_ITEM_PRE; @Value("${ITEM_CACHE_EXPIRE}") private Integer ITEM_CACHE_EXPIRE; public TbItem getItemById(Long itemId) { //查询缓存 try { String string = jedisClient.get(REDIS_ITEM_PRE+":"+itemId+":BASE"); if(StringUtils.isNotBlank(string)){ TbItem tbItem = JsonUtils.jsonToPojo(string, TbItem.class); return tbItem; } } catch (Exception e) { e.printStackTrace(); } //缓存中没有,查询数据库 //设置查询条件 TbItemExample example = new TbItemExample(); Criteria criteria = example.createCriteria(); criteria.andIdEqualTo(itemId); //执行查询 List<TbItem> list = itemMapper.selectByExample(example); if(list != null && list.size()>0){ //把结果添加到缓存 try { jedisClient.set(REDIS_ITEM_PRE+":"+itemId+":BASE", JsonUtils.objectToJson(list.get(0))); //设置过期时间 jedisClient.expire(REDIS_ITEM_PRE+":"+itemId+":BASE", ITEM_CACHE_EXPIRE); } catch (Exception e) { e.printStackTrace(); } return list.get(0); }else{ return null; } } /* * 根据商品id取商品描述 */ public TbItemDesc getItemDescById(long itemId) { //查询缓存 try { String string = jedisClient.get(REDIS_ITEM_PRE+":"+itemId+":DESC"); if(StringUtils.isNotBlank(string)){ TbItemDesc tbItemDesc = JsonUtils.jsonToPojo(string, TbItemDesc.class); return tbItemDesc; } } catch (Exception e) { e.printStackTrace(); } //缓存中没有,查询数据库 TbItemDesc itemDesc = descMapper.selectByPrimaryKey(itemId); //把结果添加到缓存 try { jedisClient.set(REDIS_ITEM_PRE+":"+itemId+":DESC", JsonUtils.objectToJson(itemDesc)); //设置过期时间 jedisClient.expire(REDIS_ITEM_PRE+":"+itemId+":DESC", ITEM_CACHE_EXPIRE); } catch (Exception e) { e.printStackTrace(); } return itemDesc; } 属性描述文件中 REDIS_ITEM_PRE=ITEM_INFO ITEM_CACHE_EXPIRE=3600
这里使用的是String类型,写成 前缀:d:后缀,这种形式在桌面版redis中文件目录会有层次结构。
注:对上面的代码不必深究,也不能单独拿那些代码进行测试,主要是知道redis的使用。只要会使用jedis的方法,那么自己进行封装就很容易,主要还是理解业务需求,理清了需求redis部分的代码实现就不难了。
相关文章推荐
- Redis入门及在商城案例中的使用
- redis在php中基本使用案例
- Redis使用入门
- 【炼数成金 NOSQL引航 三】 Redis使用场景与案例分析
- NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例
- _00017 Kafka的体系结构介绍以及Kafka入门案例(初级案例+Java API的使用)
- android入门-------案例九(下拉选择spinner的使用)
- redis在PHP中的基本使用案例
- redis在PHP中的基本使用案例
- Redis快速入门、熟悉和使用
- 【转】NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例
- redis在PHP中的基本使用案例
- redis在PHP中的基本使用案例
- Ubuntu环境下的Redis 配置与C++使用入门
- 第4周 Redis使用场景与案例分析
- redis使用案例
- redis 入门 使用方法简单介绍
- redis在PHP中的基本使用案例
- 使用Ajax技术的一个入门案例
- 《mysql使用hibernate入门案例》