Redis入门到入坑(一)

news2024/12/24 3:28:36

Redis入门到入坑(一)

  • Redis缓存入门
    • 简介
    • Redis初始操作
    • Redis数据存储操作
  • Redis常用数据类型
    • 简介
    • String类型操作实践
    • Hash类型应用实践
    • List类型应用实践
    • Set类型应用实践
  • Java中操作redis
    • 准备工作
    • Jedis的应用
    • 快速入门实现
    • RedisTemplate应用
    • 项目工程实践

Redis缓存入门

简介

背景

我们现在的项目架构中,基本上是Web服务器(Tomcat)和数据库独立部署,独占服务器资源,随着用户数的增长,并发读写数据库,会加大数据库访问压力,导致性能的下降,严重时直接导致系统宕机,例如:

在这里插入图片描述

此时,我们可以在Tomcat同服务器上中增加本地缓存,并在外部增加分布式缓存,缓存热门数据。也就是通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。例如:
在这里插入图片描述

基于这样的一种架构设计,于是类似redis的一些分布式数据库就诞生了。

Redis 概述

Redis是一个key-value存储系统(官网:http://redis.io),是一个分布式缓存数据库。在DB-Engines.com的数据库排行中, Redis上升排行第七,如图所示:

在这里插入图片描述

版本及参考说明

Redis的次版本号(第一个小数点后的数字)为偶数的版本是稳定版本(2.4、2.6等),奇数为非稳定版本(2.5、2.7),一般推荐在生产环境使用稳定版本。最新版本6.2.2,新增了stream的处理方式,性能更高。Redis官方是不支持windows平台的,windows版本是由微软自己建立的分支,基于官方的Redis源码上进行编译、发布、维护的,所以windows平台的Redis版本要略低于官方版本。

Redis 相关参考网址如下所示:

Bootnb 相关:https://www.runoob.com/redis/redis-tutorial.html
Redis 官网:https://redis.io/
源码地址:https://github.com/redis/redis
Redis 在线测试:http://try.redis.io/
Redis 命令参考:http://doc.redisfans.com/

Redis初始操作

启动redis服务

Docker 环境下的启动(docker环境启动多个需要运行多个容器):

docker start redis01 #底层也是通过redis-server启动,start单词后的redis01为容器名

docker 中查看redis 服务

docker ps

查看启动的redis进程信息

ps -ef | grep redis
root      3511     1  0 16:29 ?   00:00:01 redis-server *:6379
root      3515     1  0 16:29 ?   00:00:01 redis-server 127.0.0.1:6380

进入redis容器

docker exec -it redis01 bash    #redis01 为容器名

登陆redis服务

登陆本地redis

redis-cli
或者
redis-cli -p 6379
或者
redis-cli -p 6379 -a  password #-a后面为password,此操作需要开启redis.conf文件中的 requirepass选项

登陆远程redis

redis-cli  -h ip  -p 6379  -a  password

查看redis信息

首先登陆redis,然后输入info指令,例如

127.0.0.1:6379> info		#查看当前redis节点的详细配置信息

清空redis屏幕

清除redis屏幕内容

127.0.0.1:6379> clear

退出redis服务

退出redis服务,例如

127.0.0.1:6379> exit

关闭redis服务

关闭redis服务,例如:

127.0.0.1:6379> shutdown

也可以通过linux下的命令来杀死它:

在这里插入图片描述

系统帮助

可以基于hep指令查看相关指令帮助,例如

127.0.0.1:6379> help
redis-cli 2.8.19
Type: "help @<group>" to get a list of commands in <group>
      "help <command>" for help on <command>
      "help <tab>" to get a list of possible help topics
      "quit" to exit
127.0.0.1:6379> help type
TYPE key
summary: Determine the type stored at key
since: 1.0.0
group: generic

Redis数据存储操作

简易数据存取

基于查看redis中的key

127.0.0.1:6379> keys *
(empty list or set)

基于key/value形式存储数据

127.0.0.1:6379> set test1 123
OK
127.0.0.1:6379> set test2 ab
OK
127.0.0.1:6379> keys *
1) "test1"
2) "test2"

基于key获取redis中存储的数据

127.0.0.1:6379> get test1
"123"
127.0.0.1:6379> get test2
"ab"
127.0.0.1:6379> get test3
(nil)
127.0.0.1:6379>

清除redis中的数据

清除当前数据库数据

127.0.0.1:6379> flushdb
OK

清除所有数据库数据

127.0.0.1:6379> flushall
OK

选择数据库

127.0.0.1:6379> select 1  #默认为第0个数据库,总共有15个数据库

在这里插入图片描述

Key有效时间设计

实际工作中我们经常要控制redis中key的有效时长,例如秒杀操作的计时,缓存数据的有效时长等。

Expire (设置生效时长-单位秒)

语法:EXPIRE key seconds

127.0.0.1:6379> set bomb tnt
OK
127.0.0.1:6379> expire bomb 10
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) 5
127.0.0.1:6379> ttl bomb
(integer) 3
127.0.0.1:6379> ttl bomb
(integer) 3
127.0.0.1:6379> ttl bomb
(integer) 2
127.0.0.1:6379> ttl bomb
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) -2
127.0.0.1:6379> ttl bomb
(integer) -2

其中,TTL查看key的剩余时间,当返回值为-2时,表示键被删除。
当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。

Persist (取消时长设置)

通过persist让对特定key设置的生效时长失效。

语法:PERSIST key

127.0.0.1:6379> set bomb tnt
OK
127.0.0.1:6379> expire bomb 60
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) 49
127.0.0.1:6379> persist bomb
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) -1

其中,设置新的数据时需要重新设置该key的生存时间,重新设置值也会清除生存时间。

pexpire (单位毫秒)

pexpire 让key的生效时长以毫秒作为计量单位,这样可以做到更精确的时间控制。例如,可应用于秒杀场景。

语法:PEXPIRE key milliseconds

127.0.0.1:6379> set bomb tnt
OK
127.0.0.1:6379> pexpire bomb 10000
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) 6
127.0.0.1:6379> ttl bomb
(integer) 3
127.0.0.1:6379> ttl bomb
(integer) -2

Redis常用数据类型

简介

概述

Redis作为一种key/value结构的数据存储系统,为了便于对数据进行进行管理,提供了多种数据类型。然后,基于指定类型存储我们项目中产生的数据,例如用户的登陆信息,购物车信息,商品详情信息等等。

常用数据类型

Reids中基础数据结构包含字符串、散列,列表,集合,有序集合。工作中具体使用哪种类型要结合具体场景。

String类型操作实践

字符串类型是redis中最简单的数据类型,它存储的值可以是字符串,其最大字符串长度支持到512M。基于此类型,可以实现博客的字数统计,将日志不断追加到指定key,实现一个分布式自增iid,实现一个博客的的点赞操作

incr/incrby

当存储的字符串是整数时,redis提供了一个实用的命令 incr,其作用是让当前键值递增,并返回递增后的值。
incr key 按照默认步长(默认为1)进行递增
incrby key decrement 按照指定步长进行递增
语法:incr key

127.0.0.1:6379> set num 1
OK
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> keys *
1) "num"	
127.0.0.1:6379> incr num
(integer) 3

说明,如果num不存在,则自动会创建,如果存在自动+1。

指定增长系数

 
 

语法:INCRBY key increment

127.0.0.1:6379> incrby num 2
(integer) 5
127.0.0.1:6379> incrby num 2
(integer) 7
127.0.0.1:6379> incrby num 2
(integer) 9
decr/decrby

减少指定的整数
decr key 按照默认步长(默认为1)进行递减
decrby key decrement 按照指定步长进行递减

127.0.0.1:6379> incr num
(integer) 10
127.0.0.1:6379> decr num
(integer) 9
127.0.0.1:6379> decrby num 3
(integer) 6
append

向尾部追加值
如果键不存在,则创建该键,其值为写的value,即相当于set key value。
返回值是追加后字符串的总长度。
语法:append key value

127.0.0.1:6379> keys *
1) "test"
127.0.0.1:6379> get test
"123"
127.0.0.1:6379> append test "abc"
(integer) 6
127.0.0.1:6379> get test
"123abc"
strlen

字符串长度
返回数据的长度,如果键不存在则返回0。注意,如果键值为空串,返回值也是0。
语法:strlen key

127.0.0.1:6379> get test
"123abc"
127.0.0.1:6379> strlen test
(integer) 6
127.0.0.1:6379> strlen tnt
(integer) 0
127.0.0.1:6379> set tnt ""
OK
127.0.0.1:6379> strlen tnt
(integer) 0
127.0.0.1:6379> exists tnt
(integer) 1
mset/mget

同时设置/获取多个键值
语法:
mset key value
mget key

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> mset a 1 b 2 c 3
OK
127.0.0.1:6379> mget a b c
1) "1"
2) "2"
3) "3"
小节面试分析
  • 博客的字数统计如何实现?(strlen)
  • 如何将审计日志不断追加到指定key?(append)
  • 你如何实现一个分布式自增id?(incr-雪花算法)
  • 如何实现一个博客的的点赞操作?(incr,decr)

Hash类型应用实践

Redis散列类型相当于Java中的HashMap,实现原理跟HashMap一致,一般用于存储对象信息,存储了字段(field)和字段值的映射,一个散列类型可以包含最多232-1个字段。

hset/hget

赋值/取值

hset命令不区分插入和更新操作,当执行插入操作时hset命令返回1,当执行更新操作时返回0。

语法结构

hset key field value
hget key field
hmset key field value 
hmget key field 
hgetall key

hset 和 hget 赋值和取值

127.0.0.1:6379> hset user username chenchen
(integer) 1
127.0.0.1:6379> hget user username
"chenchen"
127.0.0.1:6379> hset user username chen
(integer) 0
127.0.0.1:6379> keys user
1) "user"
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
127.0.0.1:6379> 
127.0.0.1:6379> hset user age 18
(integer) 1
127.0.0.1:6379> hset user address "xi'an"
(integer) 1
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
3) "age"
4) "18"
3) "address"
4) "xi'an"

注意:上面 hmget 字段顺序可以自行定义

hincrby
127.0.0.1:6379> hdecrby article total 1		#执行会出错
127.0.0.1:6379> hincrby article total -1	#没有hdecrby自减命令
(integer) 1
127.0.0.1:6379> hget article total			#获取值
hexists

属性是否存在

127.0.0.1:6379> hexists killer
(error) ERR wrong number of arguments for 'hexists' command
127.0.0.1:6379> hexists killer a
(integer) 0
127.0.0.1:6379> hexists user username
(integer) 1
127.0.0.1:6379> hexists person age
(integer) 1
hdel

删除属性

127.0.0.1:6379> hdel user age
(integer) 1
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
127.0.0.1:6379> hgetall person
1) "username"
2) "tony"
3) "age"
4) "18"
hkeys/hvals

只获取字段名 hkeys 或字段值 hvals

127.0.0.1:6379> hkeys person
1) "username"
2) "age"
127.0.0.1:6379> hvals person
1) "tony"
2) "18"
hlen

获取元素个数

127.0.0.1:6379> hlen user
(integer) 1
127.0.0.1:6379> hlen person
(integer) 2
小节面试分析
  • 发布一篇博客需要写内存吗?(需要,hmset)
  • 浏览博客内容会怎么做?(hmget)
  • 如何判定一篇博客是否存在?(hexists)
  • 删除一篇博客如何实现?(hdel)
  • 分布式系统中你登录成功以后是如何存储用户信息的?(hmset)

List类型应用实践

Redis的list类型相当于java中的LinkedList,其原理就就是一个双向链表。支持正向、反向查找和遍历等操作,插入删除速度比较快。
经常用于实现热销榜,最新评论等的设计

List命令
序号命令作用示例
1lpush在头(左边)部添加多个key值lpush names ‘张三’,lpush names ‘李四’
2lrange获取指定范围的key的值lrange names 0 10
3blpop移除并获取第一个元素,并设置阻塞时间blpop names 10
4brpop移除并获取尾部元素,并设置阻塞时间brpop names 10
5lindex通过索引获取列表中的key值lindex names 1
6linsert在张三前|后插入tom值linsert names before|after ‘张三’ ‘tom’
7llen获取列表长度llen names
8lpop移出并获取第一个元素lpop names
9lpushx将多值插入到已存在的列表头部lpushx names lili
10lrem移除指定个数的值,count表示方向lrem ages 2 10
11lset修改指定索引的值lset ages 1 11
12ltrim保留修剪内部的数据ltrim ages 1 -1
13rpop移出尾部元素,并返回rpop ages
14rpush在尾(右边)部添加多个key值rpush ages 14 15
15rpushx将多值插入到已存在的列表头部rpushx names hahah
16rpoplpush将尾部的值移到新key中rpoplpush names newname
lpush

在 key 对应 list 的头部添加字符串元素

redis 127.0.0.1:6379> lpush mylist "world" "hello"
(integer) 2
redis 127.0.0.1:6379> lrange mylist 0 -1  #0表示第一个,-1表示最后一个
1) "hello"
2) "world"

其中,Redis Lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推

rpush

在 key 对应 list 的尾部添加字符串元素

redis 127.0.0.1:6379> rpush mylist2 "hello" "world"
(integer) 2
redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
2) "world"
del
redis 127.0.0.1:6379> del mylist

 
 
linsert

在 key 对应 list 的特定位置之前或之后添加字符串元素

redis 127.0.0.1:6379> rpush mylist3 "hello" "world"
(integer) 2
redis 127.0.0.1:6379> linsert mylist3 before "world" "there"
(integer) 3
redis 127.0.0.1:6379> lrange mylist3 0 -1
1) "hello"
2) "there"
3) "world"
lset

设置 list 中指定下标的元素值(一般用于修改操作)

redis 127.0.0.1:6379> rpush mylist4 "one"
(integer) 1
redis 127.0.0.1:6379> rpush mylist4 "two"
(integer) 2
redis 127.0.0.1:6379> rpush mylist4 "three"
(integer) 3
redis 127.0.0.1:6379> lset mylist4 0 "four"
OK
redis 127.0.0.1:6379> lset mylist4 -2 "five"
OK
redis 127.0.0.1:6379> lrange mylist4 0 -1
1) "four"
2) "five"
3) "three"
lrem

从 key 对应 list 中删除 count 个和 value 相同的元素

当count>0时,按从头到尾的顺序删除

redis 127.0.0.1:6379> rpush mylist5 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist5 "hello"
(integer) 2
redis 127.0.0.1:6379> rpush mylist5 "foo"
(integer) 3
redis 127.0.0.1:6379> rpush mylist5 "hello"
(integer) 4
redis 127.0.0.1:6379> lrem mylist5 2 "hello"
(integer) 2
redis 127.0.0.1:6379> lrange mylist5 0 -1
1) "foo"
2) "hello"

当count<0时,按从尾到头的顺序删除

redis 127.0.0.1:6379> rpush mylist6 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist6 "hello"
(integer) 2
redis 127.0.0.1:6379> rpush mylist6 "foo"
(integer) 3
redis 127.0.0.1:6379> rpush mylist6 "hello"
(integer) 4
redis 127.0.0.1:6379> lrem mylist6 -2 "hello"
(integer) 2
redis 127.0.0.1:6379> lrange mylist6 0 -1
1) "hello"
2) "foo"

当count=0时,删除全部

redis 127.0.0.1:6379> rpush mylist7 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist7 "hello"
(integer) 2
redis 127.0.0.1:6379> rpush mylist7 "foo"
(integer) 3
redis 127.0.0.1:6379> rpush mylist7 "hello"
(integer) 4
redis 127.0.0.1:6379> lrem mylist7 0 "hello"
(integer) 3
redis 127.0.0.1:6379> lrange mylist7 0 -1
1) "foo"
ltrim

保留指定 key 的值范围内的数据

redis 127.0.0.1:6379> rpush mylist8 "one"
(integer) 1
redis 127.0.0.1:6379> rpush mylist8 "two"
(integer) 2
redis 127.0.0.1:6379> rpush mylist8 "three"
(integer) 3
redis 127.0.0.1:6379> rpush mylist8 "four"
(integer) 4
redis 127.0.0.1:6379> ltrim mylist8 1 -1
OK
redis 127.0.0.1:6379> lrange mylist8 0 -1
1) "two"
2) "three"
3) "four"
lpop

从 list 的头部删除元素,并返回删除元素

redis 127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379> lpop mylist
"hello"
redis 127.0.0.1:6379> lrange mylist 0 -1
1) "world"
rpop

从 list 的尾部删除元素,并返回删除元素:

redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379> rpop mylist2
"world"
redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
llen

返回 key 对应 list 的长度:

redis 127.0.0.1:6379> llen mylist5
(integer) 2
lindex

返回名称为 key 的 list 中 index 位置的元素:

redis 127.0.0.1:6379> lrange mylist5 0 -1
1) "three"
2) "foo"
redis 127.0.0.1:6379> lindex mylist5 0
"three"
redis 127.0.0.1:6379> lindex mylist5 1
"foo"
rpoplpush

从第一个 list 的尾部移除元素并添加到第二个 list 的头部,最后返回被移除的元素值,整个操作是原子的.如果第一个 list 是空或者不存在返回 nil:
rpoplpush lst1 lst1
rpoplpush lst1 lst2

小节面试分析
  • 如何基于redis实现一个队列结构?(lpush/rpop)
  • 如何基于redis实现一个栈结构?(lpush/lpop)
  • 如何基于redis实现一个阻塞式队列?(lpush/brpop)
  • 如何实现秒杀活动的公平性?(先进先出-FIFO)
  • 通过list结构实现一个消息队列(顺序)吗?(可以,FIFO->lpush,rpop)
  • 用户注册时的邮件发送功能如何提高其效率?(邮件发送是要调用三方服务,底层通过队列优化其效率,队列一般是list结构)
  • 如何动态更新商品的销量列表?(卖的好的排名靠前一些,linsert)
  • 商家的粉丝列表使用什么结构实现呢?(若注意顺序,则使用list结构,若不注意顺序,则使用set结构)

Set类型应用实践

Redis的Set类似Java中的HashSet,是String类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis中Set集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

sadd

添加元素,重复元素添加失败,返回0

127.0.0.1:6379> sadd name tony
(integer) 1
127.0.0.1:6379> sadd name hellen
(integer) 1
127.0.0.1:6379> sadd name rose
(integer) 1
127.0.0.1:6379> sadd name rose
(integer) 0
smembers

获取内容

127.0.0.1:6379> smembers name
1) "hellen"
2) "rose"
3) "tony"
spop

移除并返回集合中的一个随机元素

127.0.0.1:6379> smembers set1
1) "amoeba"
2) "redis"
3) "rabbitmq"
4) "nginx"
127.0.0.1:6379> spop set1
"rabbitmq"
127.0.0.1:6379> spop set1
"nginx"
127.0.0.1:6379> smembers set1
1) "amoeba"
2) "redis"
scard

获取成员个数

127.0.0.1:6379> scard name
(integer) 3
smove

移动一个元素到另外一个集合中

127.0.0.1:6379> sadd set1 amoeba nginx redis
(integer) 3
127.0.0.1:6379> sadd set2 hadopp spark rabbitmq
(integer) 3
127.0.0.1:6379> smembers set1
1) "amoeba"
2) "redis"
3) "nginx"
127.0.0.1:6379> smembers set2
1) "hadopp"
2) "spark"
3) "rabbitmq"
127.0.0.1:6379> smove set2 set1 rabbitmq
(integer) 1
127.0.0.1:6379> smembers set1
1) "amoeba"
2) "redis"
3) "rabbitmq"
4) "nginx"
127.0.0.1:6379> smembers set2
1) "hadopp"
2) "spark"
sunion

两个集合的并集

127.0.0.1:6379> sunion set1 set2
1) "redis"
2) "nginx"
3) "rabbitmq"
4) "amoeba"
5) "hadopp"
6) "spark"
小节面试分析
  • 朋友圈的点赞功能你如何实现?(sadd,srem,smembers,scard)
  • 如何实现一个网站投票统计程序?
  • 你知道微博中的关注如何实现吗?

Java中操作redis

准备工作

创建工程

创建maven父工程,例如03-sca-redis,并在此工程下创建两个子工程,一个为sca-jedis,一个为sca-tempate,例如:

添加项目依赖

sca-jedis 工程依赖

<dependency>
	<groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.5.2</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.6</version>
</dependency>

添加sca-template工程依赖

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>   
     
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

Jedis的应用

简介

Jedis是Java中操作redis的一个客户端,类似通过jdbc访问mysql数据库。

准备工作

第一步:从redis.io官方下载对应版本的redis.conf文件,地址如下:

https://redis.io/topics/config/

 
 

第二步:停止redis并删除挂载目录下(/usr/local/docker/redis01/conf)的redis.conf配置文件.-> (通过 docker inspect redis01 查看挂载目录)
第三步:将下载的redis.conf文件拷贝到redis挂载目录(/usr/local/docker/redis01/conf)
第四步:基于vim打开redis.conf文件,
注释 bind 127.0.0.1这一行,
在这里插入图片描述
并修改protected-mode的值修改为no或者直接注释掉.
在这里插入图片描述
第五步:重启redis服务,并检查启动日志(docker logs 容器id)

快速入门实现

在Jedis工程中定义单元测试类,在类中定义单元测试方法:

基础类型操作

在项目目录下创建单元测类,例如:

import com.google.gson.Gson;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class JedisTests {
    /**
     * 测试连接redis
     * 假如不能连通,要注释掉redis.conf中 bind 127.0.0.1,并将protected-mode的值修改为no,然后重启redis再试
     */
    @Test
    public void testGetConnection(){//JDK8
        Jedis jedis = new Jedis("192.168.126.129",6379);
        //jedis.auth("123456");假如在redis.conf中设置了密码,还需执行这条语句
        String ping = jedis.ping();
        System.out.println(ping);///PONG
    }
    
    /**基于junit+jedis对redis中的string类型进行测试*/
    @Test
    public void testStringOper() throws InterruptedException {
        //1.建立链接
        Jedis jedis = new Jedis("192.168.126.129",6379);
        //2.向redis中存储数据
        jedis.set("id", "1");
        jedis.set("name", "Toney");
        jedis.set("token", UUID.randomUUID().toString());
        jedis.expire("token", 2);
        //3.更新redis中指定数据
        jedis.set("id", "100");
        jedis.incr("id");
        jedis.incrBy("id", 2);
        jedis.decr("id");
        //4.删除redis中指定数据
        jedis.del("name");
        //5.查看redis中的数据
        String id = jedis.get("id");
        String name = jedis.get("name");
        String token = jedis.get("token");
        Long tokenStrLen = jedis.strlen("token");
        System.out.println("id = "+id);
        System.out.println("name = "+name);
        System.out.println("token = "+token);
        System.out.println("token.length = "+tokenStrLen);
        //Thread.sleep(2000);
        //TimeUnit是Java中枚举类型,SECONDS为枚举类型的实例,sleep底层会调用Thread.sleep()方法
        //TimeUnit.SECONDS.sleep(2);//休眠2秒
        TimeUnit.SECONDS.sleep(2);
        token = jedis.get("token");
        System.out.println("token = "+token);
        //6.释放资源
        jedis.close();
    }
    
    /**hash类型测试*/
    @Test
    public void testHashOper01(){
        //1.建立链接
        Jedis jedis = new Jedis("192.168.126.129",6379);
        //2.向redis中存储数据
        jedis.hset("user","id","100");
        jedis.hset("user", "name","jack");
        jedis.hset("user", "mobile","111111");
        //3.更新redis中指定数据
        jedis.hset("user", "id","101");
        //4.删除redis中指定数据
        jedis.hdel("user", "mobile");
        //5.查看redis中的数据
        String id = jedis.hget("user", "id");
        System.out.println("user id = "+id);
        Map<String, String> user = jedis.hgetAll("user");
        System.out.println("user = "+user);
        //6.释放资源
        jedis.close();
    }
    
    /**
     * 将对象转换为字符串,然后以字符串类型的方式存储到redis,
     * 这种的的最大缺陷:修改对象数据不方便
     */
    @Test
    public void testStringJsonOper(){
        //1.建立链接
        Jedis jedis = new Jedis("192.168.126.129",6379);
        //2.将对象转换为json字符串
        Map<String,Object> map = new HashMap<>();
        map.put("id", "101");
        map.put("name", "tony");
        Gson gson = new Gson();//Google公司提供
        String jsonStr = gson.toJson(map);//将map转换为json串
        System.out.println("jsonStr = "+jsonStr);
        //3.将json字符串存储到redis
        jedis.set("member",jsonStr);
        //4.取出member对象的值
        String member = jedis.get("member");
        System.out.println("redis.member = "+member);
        //5.将json字符串转换成map对象
        map = gson.fromJson(member, Map.class);
        System.out.println(map);
        //6.释放资源
        jedis.close();
    }
    
    /**直接存取map对象*/
    @Test
    public void testHashOper02() {
        //1.建立链接
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //2.存储一个map对象
        Map<String,String> map = new HashMap<>();
        map.put("x", "100");
        map.put("y", "200");
        jedis.hset("point", map);
        //3.读取一个map对象
        Map<String,String> point = jedis.hgetAll("point");
        System.out.println(point);//{x=100, y=200}
        //4.释放资源
        jedis.close();
    }
    
    /**测试redis中list类型的应用*/
    @Test
    public void testListOper01(){
        //1.建立链接
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //2.存储数据(list集合有顺序,允许重复)
        jedis.lpush("lst1", "A","B","C","C");
        //3.修改数据
        //3.1 首先获取要修改的元素的下标
        Long index = jedis.lpos("lst1", "A");
        //3.2 基于下标,修改元素内容
        jedis.lset("lst1", index,"D");
        //4.删除数据
        jedis.rpop("lst1",20);
        //5.查询数据
        List<String> lst1 = jedis.lrange("lst1", 0, -1);
        System.out.println(lst1);
        //6.释放资源
        jedis.close();
    }
    
    /**实现一个阻塞式队列*/
    @Test
    public void testListOper02() {
        //1.建立链接
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //2.存储数据(list集合有顺序,允许重复)
        jedis.lpush("lst2", "A","B","C");
        //3.基于阻塞式方式获取数据
        jedis.brpop(50, "lst2");
        jedis.brpop(50, "lst2");
        List<String> lst1 = jedis.brpop(50, "lst2");
        System.out.println(lst1);//[lst2, C] 输入的内容为key和pop处的值
        jedis.brpop(50, "lst2");
        //4.释放资源
        jedis.close();
    }
    
    @Test
    public void testSetOper01(){
        //1.建立链接
        Jedis jedis=new Jedis("192.168.126.129",6379);
        //2.存储数据(Set集合没有顺序,不允许重复)
        jedis.sadd("set1", "A","A","B","C","D");
        //3.修改数据
        //4.删除数据
        //jedis.spop("set1");
        jedis.srem("set1", "B");
        //5.查询数据
        Set<String> set1 = jedis.smembers("set1");
        System.out.println(set1);
        Long cards = jedis.scard("set1");
        System.out.println(cards);
        //5.释放资源
        jedis.close();
    }
}
连接池JedisPool应用

我们直接基于Jedis访问redis时,每次获取连接,释放连接会带来很大的性能开销,可以借助Jedis连接池,重用创建好的连接,来提高其性能,简易应用方式如下:

import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * 测试jedis连接池的一个基本应用
 */
public class JedisPoolTests {
    @Test
    public void testJedisPool(){
        //1.构建jedis连接池(JedisPool对象)
        //1.1定义连接池配置
        JedisPoolConfig config=new JedisPoolConfig();
        config.setMaxTotal(16);//最大连接数,默认为8
        config.setMaxIdle(60);//最大空闲时间(连接后续不用了,超出一定空闲时间要释放)
        //config.setxxxx
        //1.2定义连接的url和端口
        String host="192.168.126.129";
        int port=6379;
        //1.3创建连接池
        JedisPool jedisPool=
                new JedisPool(config,host,port);
        //2.从池中获取连接(jedis对象)
        Jedis resource = jedisPool.getResource();
        //3.执行redis操作
        resource.set("pool", "JedisPool");
        String pool = resource.get("pool");
        System.out.println(pool);
        //4.释放资源(不是关,而是将连接还回池中)
        resource.close();
        //5.关闭池(一般服务停止时关)
        //jedisPool.close();
    }
}

RedisTemplate应用

简介

RedisTemplate为SpringBoot工程中操作redis数据库的一个Java对象,此对象封装了对redis的一些基本操作。

准备工作

第一步:创建工程配置文件application.yml,其内容如下:

spring:
  redis:
    host: 192.168.126.129  #写自己的ip
    port: 6379

第二步:创建工程启动类,例如:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RedisApplication {
    public static void main(String[] args) {
        SpringApplication.run(RedisApplication.class,args);
    }
}
快速入门实现
StringRedisTemplate 应用

StringRedisTemplate 是一个专门用于操作redis字符串类型数据的一个对象,其应用方式如下:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@SpringBootTest
public class StringRedisTemplateTests {
    /**
     * 此对象为spring提供的一个用于操作redis数据库中的字符串的一个客户端对象,
     * 这个对象对key/value采用了字符串序列化(StringRedisSerializer)方式,进行redis数据的读写操作
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    @Test
    public void testGetConnection(){
        RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection();
        String ping = connection.ping();
        System.out.println(ping);//PONG
    }
    
    @Test
    public void testStringOper01(){
        //1.获取字符串操作对象(ValueOperations)
        ValueOperations<String, String> vo =
                stringRedisTemplate.opsForValue();
        //2.读写redis中的数据
        vo.set("x", "100");
        vo.increment("x");
        vo.set("y", "200", 1, TimeUnit.SECONDS);
        String x = vo.get("x");
        String y = vo.get("y");
        System.out.println("x = "+x+","+"y = "+y);
    }
    
    @Test
    public void testStringOper02() throws JsonProcessingException {
        //1.获取字符串操作对象(ValueOperations)
        ValueOperations<String, String> vo =
                stringRedisTemplate.opsForValue();
        //2.读写redis中的数据
        Map<String, String> map = new HashMap<>();
        map.put("id", "100");
        map.put("title", "StringRedisTemplate");
        //把对象转换为json字符串
        String jsonStr = new ObjectMapper().writeValueAsString(map);
        vo.set("blog", jsonStr);
        jsonStr = vo.get("blog");
        System.out.println(jsonStr);//{"id":"100","title":"StringRedisTemplate"}
        //把json字符串转换为对象
        map = new ObjectMapper().readValue(jsonStr,Map.class);
        System.out.println(map);//{id=100, title=StringRedisTemplate}
    }
    
    @Test
    public void testHashOper01(){
        //1.获取hash操作的对象
        HashOperations<String, Object, Object> vo = stringRedisTemplate.opsForHash();
        //2.读写redis中的数据
        //2.1 存储一个对象
        vo.put("use", "id", "100");
        vo.put("user", "username", "tony");
        vo.put("user", "status","1");
        //2.2获取一个对象
        //2.2.1获取对象的某个属性值
        Object status = vo.get("user", "status");
        System.out.println(status);
        //2.2.2获取对象某个key对应的某个所有值
        List<Object> user = vo.values("user");
        System.out.println(user);
    }
}
RedisTemplate 应用

RedisTemplate是一个专门用于实现对远端redis中复杂数据的操作的对应,应用案例如下:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.*;

@SpringBootTest
public class RedisTemplateTests {
    /**这个对象在springboot工程的RedisAutoConfiguration类中已经做了配置*/
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Test
    public void testGetConnection(){
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        String ping = connection.ping();
        System.out.println(ping);
    }
    
    /**测试字符串数据的存取*/
    @Test
    public void testStringOper01(){
        //自己指定key/value序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        ValueOperations vo = redisTemplate.opsForValue();
        //key和value默认会采用JDK的序列化方式进行存储
        vo.set("token", UUID.randomUUID().toString());
        Object token = vo.get("token");
        System.out.println(token);
    }
    
    /**测试hash数据的存取*/
    @Test
    public void testHashOper01(){
        //1.获取操作hash类型数据得操作对象
        HashOperations ho = redisTemplate.opsForHash();
        //2.读写redis数据hash类型数据
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", 100);
        map.put("title","spring boot");
        //直接存储一个map
        ho.putAll("blog", map);//hash 存储, 序列化
        //存储单个字段,值
        ho.put("blog", "content", "spring boot redis");
        //取blog对象中的id属性的值
        Object o = ho.get("blog", "id");
        System.out.println("id = "+o);
        //获取整个blog对象所有属性的值
        Map blog = ho.entries("blog");//反序列化
        System.out.println(blog);
    }
    
    /**测试List数据得的存取*/
    @Test
    public void testListOper01(){
        //1.获取操作list类型数据得操作对象
        ListOperations lo = redisTemplate.opsForList();
        //2.读写redis数据list类型数据
        //存数据
        lo.leftPush("lst1", "A");
        lo.leftPushAll("lst1", "B","C","C","D");
        //取数据
        lo.leftPop("lst1");
        List lst1 = lo.range("lst1", 0, -1);
        System.out.println(lst1);
    }
    
    /**测试Set数据得的存取*/
    @Test
    public void testSetOper01(){
        //1.获取操作set类型数据得操作对象
        SetOperations so = redisTemplate.opsForSet();
        //2.读写redis数据set类型数据
        so.add("set1", "A","B","C","C");//不允许重复
        Set set1 = so.members("set1");
        System.out.println(set1);
    }
}

项目工程实践

单点登陆
业务描述

在分布式系统中,通过会有多个服务,我们登录了一个服务以后,再访问其它服务时,不想再登录,就需要有一套单独的认证系统,我们通常会称之为单点登录系统,在这套系统中提供一个认证服务器,服务完成用户身份认证,在一些中小型分布式系统中中,我们通常会借助redis存储用户的认证信息,例如:
在这里插入图片描述

关键代码实现

SSODemo01.java

package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.UUID;

/**
 * SSO(单点登录系统) 案例演示:
 * 		1)访问资源(假如没有登录,要提示先登录,如何判定是否登录了)
 * 		2)执行登录(登录成功,存储用户登录信息)
 * 		3)访问资源(已登录)
 * 解决方案
 * 		1)方案1:SpringSecurity + jwt + oauth2 (并发比较大)
 * 		2)方案2:SpringSecurity + redis + oauth2 (中小型并发)
 *
 */
public class SSODemo01 {
    /**认证中心的登录设计*/
    public static String doLogin(String username,String password){
        //1.执行用户身份校验
        if(!"jack".equals(username))//将来这个jack来自数据库
            throw new IllegalArgumentException("这个用户不存在");     
        //2.用户存在并且密码正确,表示用户是系统的合法用户
        if(!"123456".equals(password))
            throw new IllegalArgumentException("密码不正确");            
        //3.将合法用户信息存储到redis
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        String token = UUID.randomUUID().toString();
        jedis.set(token,username);
        jedis.expire(token, 2);
        jedis.close();
        return token;//token
    } 
    
    /**获取资源服务中的资源*/
    public static Object doGetResource(String token){
        //1.检查用户是否已经登录
        if(token == null || "".equals(token))
            throw new IllegalArgumentException("请先登录");
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        String username = jedis.get(token);
        jedis.close();        
        //2.假如没有登录,则提示先登录
        if(username == null)
            throw new RuntimeException("登录超时或token无效,请重新登录");            
        //3.已登录则可以访问资源
        System.out.println("继续访问资源");
        //.....
        return "the resource of user";
    }    
    
    //假设这里的main方法为客户端
    public static void main(String[] args) throws InterruptedException {
        String token = null;
        //第一次访问资源
        // doGetResource(token);
        //执行登录操作(将来认证要在认证中心实现)
        token = doLogin("jack", "123456");
        //第二次访问资源
        doGetResource(token);
    }
}

SSODemo02.java

package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.Map;
import java.util.UUID;

/**
 * SSO(单点登录系统) 案例演示:
 * 		1)访问资源(假如没有登录,要提示先登录,如何判定是否登录了)
 * 		2)执行登录(登录成功,存储用户登录信息)
 * 		3)访问资源(已登录)
 * 解决方案
 * 		1)方案1:SpringSecurity + jwt + oauth2 (并发比较大)
 * 		2)方案2:SpringSecurity + redis + oauth2 (中小型并发)
 *
 */
public class SSODemo02 {
    private static String token;
    
    /**认证中心的登录设计*/
    public static String doLogin(String username,String password){
        //1.执行用户身份校验
        if(!"jack".equals(username))//将来这个jack来自数据库
            throw new IllegalArgumentException("这个用户不存在");            
        //2.用户存在并且密码正确,表示用户是系统的合法用户
        if(!"123456".equals(password))
            throw new IllegalArgumentException("密码不正确");            
        //3.将合法用户信息存储到redis
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        String token = UUID.randomUUID().toString();
        jedis.hset(token,"username",username);
        jedis.hset(token,"status","1");
        //...
        jedis.expire(token, 2);
        jedis.close();
        return token;//token
    }
    
    /**获取资源服务中的资源*/
    public static Object doGetResource(String token){
        //1.检查用户是否已经登录
        if(token == null || "".equals(token))
            throw new IllegalArgumentException("请先登录");
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        Map<String, String> map = jedis.hgetAll(token);
        jedis.close();        
        //2.假如没有登录,则提示先登录
        System.out.println("map = "+map);//map = {username=jack, status=1}
        if(map.isEmpty())
            throw new RuntimeException("登录超时或token无效,请重新登录");            
        //3.已登录则可以访问资源
        System.out.println("继续访问资源");
        //.....
        return "the resource of user";
    }
    
    //假设这里的main方法为客户端
    public static void main(String[] args) throws InterruptedException {
        //第一次访问资源
        // doGetResource(token);
        //执行登录操作(将来认证要在认证中心实现)
        token = doLogin("jack", "123456");
        //第二次访问资源
        //Thread.sleep(2000);
        doGetResource(token);
    }
}
投票系统
业务描述

在很多系统中设计中,都会有一个活动设计,开启一个活动之前,可以对这个活动的支持力度先进行一个调查,例如基于这个活动设计一个投票系统,例如:
在这里插入图片描述

关键代码实现
package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.Set;

/** redis Key/Value (string、hash、list、set)
 *
 * 基于某个活动的投票系统的建议设计
 * 需求:
 *  1.基于用户id对活动投票
 *  2.一个用户id只能参与一次投票
 *  3.可以查询投票总数以及参与投票的用户id
 * 设计:
 *  1.数据存储结构:set结构(不允许重复)
 */
public class VoteDemo01 {
    static final String IP = "192.168.126.129";
    static final int PORT = 6379;
    
    /**查看用户是否参与过此活动的投票*/
    public static Boolean isVote(String activityId,String userId){
        //1.创建Jedis对象
        Jedis jedis = new Jedis(IP,PORT);
        //2.检查是否投票
        Boolean flag = jedis.sismember(activityId, userId);
        //3.释放资源
        jedis.close();
        //4.返回结果
        return flag;
    }
    
    /**执行投票操作*/
    public static void doVote(String activityId,String userId){
        //1.创建Jedis对象
        Jedis jedis = new Jedis(IP,PORT);
        //2.执行投票
        jedis.sadd(activityId, userId);
        //3.释放资源
        jedis.close();
    }
    
    /**查看活动的总投票数*/
    public static Long doGetVote(String activityId){
        //1.创建Jedis对象
        Jedis jedis = new Jedis(IP,PORT);
        //2.查看活动的投票数
        Long count = jedis.scard(activityId);
        //3.释放资源
        jedis.close();
        //4.返回投票数量
        return count;
    }
    
    /**获取参与投票当前活动的用户*/
    public static Set<String> doGetUser(String activityId){
        //1.创建Jedis对象
        Jedis jedis = new Jedis(IP,PORT);
        //2.查看活动的投票数
        Set<String> smembers = jedis.smembers(activityId);
        //3.释放资源
        jedis.close();
        //4.返回投票数量
        return smembers;
    }
    
    public static void main(String[] args) {
        //1.定义活动id
        String activityId = "1001";
        String user1 = "301";
        String user2 = "302";
        //2.进行投票
        doVote(activityId, user1);
        //3.进行投票检查
        Boolean flag = isVote(activityId, user1);
        Boolean flag1 = isVote(activityId, user2);
        //4.获取投票的总数
        Long aLong = doGetVote(activityId);
        System.out.println(activityId + " 的总投票数为 " + aLong);
        //5.获取参与投票的用户
        Set<String> users = doGetUser(activityId);
        System.out.println(users);
        //6.检查用户是否投过票
        System.out.println(user1 + ":" + (flag?"已投过票":"还没投票"));
        System.out.println(user2 + ":" + (flag1?"已投过票":"还没投票"));
    }
}
秒杀队列
业务描述

在设计一个秒杀或抢购系统时,为了提高系统的响应速度,通常会将用户的秒杀或抢购请求先存储到一个redis队列,这里我们就基于redis实现一个先进先出队列,例如:
在这里插入图片描述

关键代码实现
package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.List;

/**
 * 演示抢购活动中的秒杀队列
 * 数据存储结构:list
 * 算法:先进先出(FIFO)-体现公平性
 * 数据存储结构:redis中的list类型进行存储
 *
 * 在抢购活动中会执行这样的操作:
 *    1)生产者(购买商品的用户):创建请求并将请求存储到队列
 *    2)消费者(处理购买请求的底层对象):从队列去请求,然后处理
 */
public class SecondsKillDemo01 {
    /**入队操作*/
    public static void enque(String request){
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //jedis.auth("123456");若有密码,则写此语句
        jedis.lpush("queue-1",request);
        jedis.close();
    }
    
    /**出队操作*/
    public static String deque(){
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //非阻塞式取数据
        //return jedis.rpop("queue-1");
        //阻塞式取数据
        List<String> list = jedis.brpop(60, "queue-1");
        jedis.close();
        return list!=null?list.get(1):null;//0为key,1为value
    }
    
    public static void main(String[] args) {
        //1.构建生产者对象(购买商品的用户)
        Thread t1 = new Thread(){
            @Override
            public void run() {
                int i = 1;
                for(;;){
                    enque("request-1"+i++);
                    try { Thread.sleep(1000);
                    }catch (Exception e){}
                }
            }
        };
        //2.构建消费者对象(处理请求的对象)
        Thread t2 = new Thread(){
            @Override
            public void run() {
                for(;;){
                    String request = deque();
                    if(request == null) continue;
                    System.out.println("process"+request);
                }
            }
        };
        //3.开启抢购活动
        t1.start();
        t2.start();
    }
}
分布式id
业务描述

在分布式系统中,数据量将越来越大时,就需要对数据进行分表操作,但是,分表后,每个表中的数据都会按自己的节奏进行自增,很有可能出现ID冲突。这时就需要一个单独的机制来负责生成唯一ID,生成出来的ID也可以叫做 分布式ID,这里我们借助redis实现一个简易的分布式id进行实现,当然还有一些第三方的系统,可以帮你生成这样的id,可以自己进行拓展学习.

关键代码实现
package com.jt.redis;
import redis.clients.jedis.Jedis;

/**
 * 分布式id生成策略
 * 需求:生成一个分布递增的id
 * 多张表基于这个方法中生成的id作为主键id值(分布式环境不会采用数据库表中自带的自增策略-auto_increment)
 */
public class IdGenerator {
    /**
     * 编写一个方法,每次调用此方法,外界都能够获取一个唯一的一个递增整数值。
     */
    public static Long getId(){
        Jedis jedis=new Jedis("192.168.126.129", 6379);
        //jedis.auth("123456");//密码
        //incr方法用于对指定key的值进行递增,假如key不存在则创建
        long id = jedis.incr("incrementId");
        jedis.close();
        return id;
    }
    
    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            new Thread(){
                @Override
                public void run() {
                    System.out.println(IdGenerator.getId());
                }
            }.start();
        }
    }
}
购物车简易实现
业务描述

简易购物车业务设计如图所示:
在这里插入图片描述
基础指令操作,例如:

1)向购物车添加商品
hset cart:101 2001 1
hset cart:101 2002 1
hset cart:101 2003 2
2)查看购物车商品
hgetall cart:101
3)删除购物车商品
hdel cart:101 2003
4)改变购物车某个商品的购买数量
hincrby cart:101 2002 2
hincrby cart:101 2002 -2
关键代码实现
package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.Map;

/**
 * 购物车系统简易实现
 * 需求:
 *  1.向购物车添加商品
 *  2.修改购物车商品数量
 *  3.查看购物车商品
 *  4.清空购物车商品
 * 存储设计:
 *  1.mysql(优势是持久性)
 *  2.redis(优势是性能,持久性相对于mysql会差一点,假如性能要求高于数据的存储安全,可以存储到redis)
 */
public class CartDemo01 {
    static final String IP = "192.168.126.129";
    static final int PORT = 6379;

    /**向购物车添加商品或修改商品数量*/
    public static void doAddCart(String userId,String productId,int num){
        //1.创建Jedis对象
        Jedis jedis = new Jedis(IP,PORT);
        //2.基于hash结构存储商品信息
        //hincrBy这个函数在key不存在时会自动创建key
        jedis.hincrBy("cart:"+userId, productId, num);
        //3.释放资源
        jedis.close();
    }

    /**查看购物车信息*/
    public static Map<String, String> doViewCart(String userId){
        //1.创建Jedis对象
        Jedis jedis = new Jedis(IP,PORT);
        //2.产看购物车信息
        Map<String, String> cart = jedis.hgetAll("cart:" + userId);
        //3.释放资源
        jedis.close();
        //4.返回购物车商品信息
        return cart;
    }

    /**删除购物车某个商品*/
    public static void doClearCart(String userId,String... productIds){
        //1.创建Jedis对象
        Jedis jedis = new Jedis(IP,PORT);
        //2.删除指定商品
        jedis.hdel("cart:"+userId,productIds);
        //3.释放资源
        jedis.close();
    }

    public static void main(String args[]){
        String userId = "101";
        //1.购买商品时,将商品信息添加到购物车
        doAddCart(userId,"2001",1);
        doAddCart(userId,"2002",1);
        doAddCart(userId,"2003",1);
        //2.修改购物车商品的数量
        doAddCart(userId, "2002", 1);
        doAddCart(userId, "2003", 2);
        //3.查看购物车商品信息
        Map<String, String> cart = doViewCart(userId);
        System.out.println(cart);
        //4.清空购物车
        doClearCart(userId,"2001","2002","2003");
        cart = doViewCart(userId);
        System.out.println(cart);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1536056.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LeetCode: 433. 最小基因变化 + BFS】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

YOLOV5 部署:cuda和cuDNN安装

1、前言 TensorRT 的安装需要配合cuda的使用,所以这里需要安装cuda和cudnn用于加速推理 TensorRT 就是神经网络专门用来加速的框架 之前训练yolov5项目的时候,我们只是配置了torch的GPU环境,没有专门安装cuda和cudnn,因为简单的训练、推理没必要cuda加速。 torch的GPU配置…

利用matplot绘制折线图(详细版-有示例数据)

对于五组数据&#xff0c;绘制折线图&#xff0c;添加有图例、不同折线的颜色等&#xff0c;如下图所示&#xff1a; python代码&#xff1a; import matplotlib.pyplot as plt import numpy as np# 定义数据 data [[1, 2, 3, 4, 5, 6, 7, 8], # 数据1[2, 2, 4, 4, 5, 5, 6,…

Spring Web MVC入门(6)

应用分层 在开发的过程中, 我们会发现, 程序的代码有时会很"杂乱", 如果后面的项目更大了, 那就会更加地杂乱无章(文件乱, 代码内容乱). 也基于此, 接下来让我们来学习一下应用分层. 也类似于公司的组织架构 公司初创阶段, 一个人身兼数职, 既做财务, 又做人事,还有…

十四、ReadWriteLock

ReadWriteLock 读写锁 又叫排他锁 如果使用互斥锁&#xff0c;一个线程在读&#xff0c;其他线程也不能读也不能写 换成读写锁的时候&#xff0c;读线程是读锁&#xff0c;写线程是写锁&#xff0c;写锁是排他的 在多线程大大提高效率&#xff0c;当一个线程在读的时候&…

当内外网的域名相同时,如何在外网解析同域名的网址

当内部网络和外部网络存在相同的域名&#xff0c;并且希望内部用户通过内部DNS服务器解析到外部网络上的该域名对应的公网IP地址时&#xff0c;需要在内部DNS服务器上采取一些特殊配置策略来实现这一目标。以下是一种通用的解决方案&#xff1a; 条件转发&#xff08;Condition…

MT1490 修改字符串

原题链接:https://www.matiji.net/exam/brushquestion/490/778/B3FCFEC101BD05189BB74D522E019504 输入1个字符串, 如果其中小写字符多于大写字符&#xff0c;则将其全部转换为小写字符&#xff0c;如果大写字符多于小写字符&#xff0c;则全部转换为大写字符。 输入格式&…

Oracle数据库 利用RMAN备份

利用RMAN备份讨论 运用RMAN进行备份 运用RMAN进行还原与恢复 RMAN的日常管理 RMAN的恢复目录 一、RMAN的概念与体系结构 Recovery Manager&#xff08;RMAN&#xff09;是一种用于备份(backup)、还原(restore)和恢复(recover) 数据库的 Oracle 工具。RMAN只能用于ORACLE…

重磅|国家能源局开展配电网安全风险管控重点行动

据国家能源局3月21日消息&#xff0c;为紧扣新形势下电力保供和转型目标&#xff0c;聚焦配电网安全运行、供电保障、防灾减灾和坚强可靠等方面安全风险&#xff0c;推动解决城乡配电网发展薄弱等问题&#xff0c;全面提升配电网供电保障和综合承载能力&#xff0c;国家能源局决…

Linux东方通下载及使用

解压文件 mkdir /usr/local/java 加压包拖进去 vi /etc/profile 复制路径修改路径 export JAVA_HOME/usr/local/java/jdk1.8.0_151 export CLASSPATH.:${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar export PATH$PATH:${JAVA_HOME}/bin 进入…

✅让我们用AI制作一个漂亮的头像吧

头像 头像是指在互联网上用于代表个人或实体的图像。在社区中&#xff0c;头像通常用于标识和区分用户&#xff0c;是用户身份的象征。 社区的头像有多种意义&#xff0c;不限于以下几点: 身份标识&#xff1a;社区头像可以让用户在互联网上更好地代表自己&#xff0c;帮助用…

Mamba 基础讲解【SSM,LSSL,S4,S5,Mamba】

文章目录 Mamba的提出动机TransformerRNN Mama的提出背景状态空间模型 (The State Space Model, SSM)线性状态空间层 (Linear State-Space Layer, LSSL)结构化序列空间模型 &#xff08;Structured State Spaces for Sequences, S4&#xff09; Mamba的介绍Mamba的特性一&#…

java分割等和子集(力扣Leetcode416)

分割等和子集 力扣原题链接 给你一个只包含正整数的非空数组nums。请你判断是否可以将这个数组分割成两个子集&#xff0c;使得两个子集的元素和相等。 示例 1&#xff1a; 输入&#xff1a;nums [1,5,11,5] 输出&#xff1a;true 解释&#xff1a;数组可以分割成 [1, 5, 5] …

外包干了6天,技术明显进步。。。

我是一名大专生&#xff0c;自19年通过校招进入湖南某软件公司以来&#xff0c;便扎根于功能测试岗位&#xff0c;一晃便是近四年的光阴。今年8月&#xff0c;我如梦初醒&#xff0c;意识到长时间待在舒适的环境中&#xff0c;已让我变得不思进取&#xff0c;技术停滞不前。更令…

面试算法-73-二叉树的最小深度

题目 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a;叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;2 解 class Solu…

TC(totalCommander)配置快捷键

totalCommander 是一个非常流行的资源管理工具&#xff0c;如果我们有一些便捷的脚本&#xff0c;那么如何在totalCommander 中通过快捷键快速的调用/运行这个脚本呢&#xff1f;本文本就以 ssh工具 python脚本 为例&#xff0c;说明如何在totalCommander中配置快捷键&#xff…

如何使用好用便签管理生活内容或工作事项?

在快节奏的现代生活中&#xff0c;我们常常被各种琐事和工作压力所困扰&#xff0c;仿佛身陷一片混沌之中&#xff0c;焦虑不已。面对这些繁杂的事项&#xff0c;我们往往感到无从下手&#xff0c;甚至有时会忘记一些重要的事情。然而&#xff0c;自从我发现了敬业签这个神奇的…

openEuler 22.03(华为欧拉)一键安装 Oracle 19C RAC(19.22) 数据库

前言 Oracle 一键安装脚本&#xff0c;演示 openEuler 22.03 一键安装 Oracle 19C RAC 过程&#xff08;全程无需人工干预&#xff09;&#xff1a;&#xff08;脚本包括 ORALCE PSU/OJVM 等补丁自动安装&#xff09; ⭐️ 脚本下载地址&#xff1a;Shell脚本安装Oracle数据库…

[HackMyVM]靶场 Nebula

kali:192.168.56.104 靶机:192.168.56.125 端口扫描 # nmap 192.168.56.125 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-03-22 12:35 CST Nmap scan report for 192.168.56.125 Host is up (0.00051s latency). Not shown: 998 filtered tcp ports (no-response) …

【爬取网易财经文章】

引言 在信息爆炸的时代&#xff0c;获取实时的财经资讯对于投资者和金融从业者来说至关重要。然而&#xff0c;手动浏览网页收集财经文章耗时费力&#xff0c;为了解决这一问题&#xff0c;本文将介绍如何使用Python编写一个爬虫程序来自动爬取网易财经下关于财经的文章 1. 爬…