目录
- Redis
- 3.1 键值(Key-Value)存储数据库
- 3.2 列存储数据库 海量数据 大数据
- 3.3 文档型数据库
- 3.4 图形(Graph)数据库 (阿里云 OSS 对象存储 图片 视频 音频)
- 10.2 操作key相关API
- 10.3操作String相关API
- 10.4操作List相关API
- 10.5操作Set的相关API
- 10.6 操作ZSet相关API
- 10.7 操作Hash相关API
- 15.1 管理机制
- 15.2 开发Session管理
- 15.3.创建一个SpringBoot项目
- 15.4.项目连接Redis
- 15.5.使用redis管理session
- 15.6.tomcat集群测试
Redis
数据库:放数据 增删改查
特点:非关系型数据库 NoSql(没有sql) 内存型数据库 key,value
1. NoSQL的引言
NoSQL(Not Only SQL
),意即不仅仅是SQL, 泛指非关系型的数据库。Nosql这个技术门类,早期就有人提出,发展至2009年趋势越发高涨。
2. 为什么是NoSQL
随着互联网网站的兴起,传统的关系数据库在应付动态网站,特别是超大规模和高并发的纯动态网站已经显得力不从心,暴露了很多难以克服的问题。如商城网站中对商品数据频繁查询
、对热搜商品的排行统计
、订单超时问题
、以及微信朋友圈(音频,视频)存储等相关使用传统的关系型数据库实现就显得非常复杂,虽然能实现相应功能但是在性能上却不是那么乐观。nosql这个技术门类的出现,更好的解决了这些问题,它告诉了世界不仅仅是sql。
3. NoSQL的四大分类
3.1 键值(Key-Value)存储数据库
HashMap<Key,Value>
name:xiaoming
age:lalala
# 1.说明:
- 这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。
# 2.特点
- Key/value模型对于IT系统来说的优势在于简单、易部署。
- 但是如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了。
# 3.相关产品
- Tokyo Cabinet/Tyrant,
- Redis key value 内存
- SSDB key value 磁盘
- Voldemort
- Oracle BDB
3.2 列存储数据库 海量数据 大数据
# 1.说明
- 这部分数据库通常是用来应对分布式存储的海量数据。
# 2.特点
- 键仍然存在,但是它们的特点是指向了多个列。这些列是由列家族来安排的。
# 3.相关产品
- Cassandra、HBase(hadoop )、Riak.
3.3 文档型数据库
user 集合
{“id”:1,“name”:“xiaohei”,“bir”:2012-12-12}
{“id”:2,"“bir”:2012-12-12}
{“id”:3,“name”:“xiaohei”,“bir”:2012-12-12,“city”:“北京”}
# 1.说明
- 文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。文档型数据库可 以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高
# 2.特点
- 以文档形式存储
# 3.相关产品
- MongoDB、CouchDB、 MongoDb(4.x)(单条支持事务)
3.4 图形(Graph)数据库 (阿里云 OSS 对象存储 图片 视频 音频)
# 1.说明
- 图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。
- NoSQL数据库没有标准的查询语言(SQL),因此进行数据库查询需要制定数据模型。许多NoSQL数据库都有REST式的数据接口或者查询API。
# 2.特点
# 3.相关产品
- Neo4J、InfoGrid()、 Infinite Graph、
4. NoSQL应用场景
- 数据模型比较简单
- 需要灵活性更强的IT系统
- 对数据库性能要求较高 查询速度快
- 不需要高度的数据一致性 弱化事务 没有事务
5. 什么是Redis
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. 数据库服务
6. Redis特点
- Redis是一个高性能key/value内存型数据库
- Redis支持丰富的数据类型 key:String Value:数据(String,list,set,sortset,hash(key,value))
- Redis支持持久化 基于内存存储 断电即消失 不定期将内存中的数据持久化到硬盘中
- Redis单线程,单进程 线程安全 效率高 内存:快 加锁 解锁
7. Redis安装
1.准备环境
- vmware16.x+
- centos7.x+
2.下载redis源码包
3.下载完整源码包
redis-4.0.10.tar.gz
4.安装gcc运行环境
install gcc #redis是C语言写的
5.vim的安装
install -y vim* #//在线安装vim
6.上传redis的资料包
将下载redis资料包上传到Linux中
在CRT中 alt+p , 直接拖进
7.解压redis的压缩包
tar
8.安装包不能直接用,需要进入redis包执行指令编译redis
make
#如果遇到致命错误:执行
# zmalloc.h:50:31:致命错误:jemalloc/jemalloc.h:没有那个文件或目录
make MALLOC=libc
9.编译完成之后安装redis
make install PREFIX=/usr/redis
10.启动redis bin目录下执行
#默认执行配置文件 /path/to/redis.conf
#将配置文件拷贝到 /usr/redis目录 进入到家目录解压完成之后的redis中执行
cp redis.conf /usr/redis
#指定配置文件启动 usr/bin目录下执行
./redis-server ../redis.conf
11.修改启动端口
#将redis.conf中的默认端口修改为指定端口
#默认启动端口 port 6379 port 7000
12.使用redis的客户端连接redis服务 bin目录下
#默认连接
./redis-cli
#指定端口连接 指定IP连接
连接成功:出现上面界面连接成功
13.守护模式模式启动
#将redis.conf中的默认端口修改为指定端口
daemonize no 改为 daemonize yes
14.关闭防火墙
systemctl stop firewalld
15.查看进程
ps aux|grep redis
#杀死进程
kill
16.开启redis远程访问
修改redis.conf配置文件:
1.将 bind 127.0.0.1
改为 bind 0.0.0.0
2.重新加载配置文件启动redis
3.关闭防火墙
4.测试连接redis Desktop Manager
8. Redis数据库相关指令
8.1 数据库操作指令
# 1.Redis中库说明
- 使用redis的默认配置器动redis服务后,默认会存在16个库,编号从0-15 默认进入的是0号库
- 可以使用select 库的编号 来选择一个redis的库
# 2.Redis中操作库的指令
- 清空当前的库 FLUSHDB
- 清空全部的库 FLUSHALL
# 3.redis客户端显示中文
- ./redis-cli -p 7000 --raw
8.2 操作key相关指令
1.删除key
#删除给定的某个 key
DEL key
eg:127.0.0.1:6379> DEL name
eg:eg:(integer) 1
#删除给定的一多个 key
DEL [key ...]
eg:127.0.0.1:6379> DEL name type website
eg:(integer) 3
#返回值: 被删除 key 的数量。
#不存在的 key 会被忽略。
2.查看redis中的key
#查找所有符合给定模式 pattern 的 key 。
KEYS pattern
#匹配数据库中所有 key
keys *
eg:127.0.0.1:6379> keys *
eg:1) "age"
eg:2) "name"
#匹配 hello ,hallo 和 hxllo 等。
KEYS h?llo
# ?匹配一个任意字符
eg:127.0.0.1:6379> KEYS t??
eg:1) "two"
#匹配 hllo 和 heeeeello 等。
KEYS h*llo
# *匹配多个任意字符
eg:127.0.0.1:6379> KEYS *o*
eg:1) "four"
eg:2) "two"
eg:3) "one"
#匹配 hello 和 hallo ,但不匹配 hillo 。
KEYS h[ae]llo
## 匹配一个[]中包含的一个字符
eg:127.0.0.1:6379> KEYS t[w]*
eg:1) "two"
3.判断某个key是否存在
#判断某个key是否存在
EXISTS key
eg:127.0.0.1:6379> EXISTS name
eg:(integer) 1
#判断多个key是否存在
EXISTS [key ...]
eg:127.0.0.1:6379> EXISTS name age
eg:(integer) 2
#返回值: 若 key 存在,返回 1 ,否则返回 0
4.给key设置超时时间 秒
#为给定 key 设置生存时间,当 key 过期时 (生存时间为 0 ),它会被自动删除。
expire key seconds #时间单位为seconds
eg:127.0.0.1:6379> expire age 10
eg:(integer) 0
eg:127.0.0.1:6379> set age 18
eg:OK
eg:127.0.0.1:6379> expire age 10
eg:(integer) 1
#返回值: 设置成功,返回 1 ,否则返回 0
#注意:当 key 不存在或者不能为 key 设置生存时间时 (比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的
#生存时间),返回 0 。
eg:127.0.0.1:6379> expire age -1 //标识永不过期 默认
5.给key设置过期时间 毫秒
pexpire key milliseSeconds #时间单位为milliseSeconds
eg:127.0.0.1:6379> pexpire age 10000
eg:(integer) 1
6.查看key的过期时间 秒
ttl key 返回单位 秒
#返回值:
# 当key 不存在时,返回-2 。
# 当key 存在但没有设置剩余生存时间时,返回-1 。
# 否则,以秒为单位,返回key 的剩余生存时间。
eg:127.0.0.1:6379> ttl name
eg:(integer) -1
eg:127.0.0.1:6379> ttl hehe
eg:(integer) -2
eg:127.0.0.1:6379> ttl age
eg:(integer) 96
7.查看key的过期时间 毫秒
pttl key 返回单位 毫秒
eg:127.0.0.1:6379> pttl age
eg:(integer) 26301
8.移动key到指定db中
#将当前数据库的 key 移动到指定的数据库 db 当中。
MOVE key 库的编号
#如果当前数据库 (源数据库) 和给定数据库 (目标数据库) 有相同名字的给定 key ,或者 key 不存在于当前数据库,那么 MOVE 没有任何效果。
#因此,也可以利用这特性,将MOVE 当作锁 (locking) 原语 (primitive)
#返回值: 移动成功返回 1 ,失败则返回 0 。
9.切换库
select 库的编号
eg:127.0.0.1:6379> select 1
eg:OK
eg:127.0.0.1:[1]>
10.随机返回一个key
randomkey
#从当前数据库中随机返回(不删除) 一个key
#返回值:当数据库不为空时,返回一个key 。当数据库为空时,返回nil
eg:127.0.0.1:6379> randomkey
eg:"age"
eg:127.0.0.1:6379> randomkey
eg:"name"
11.修改key的名字
RENAME key newkey
#作用 : 将key 改名为newkey 。当key 和newkey 相同,或者key 不存在时,返回一个错误。当newkey 已经存在时,RENAME 命令将覆盖旧值。
#返回值: 改名成功时提示OK ,失败时候返回一个错误。
12.查看key对应值的类型
TYPE key
#作用 : 返回key 所储存的值的类型。
#返回值:
# none (key 不存在)
# string (字符串)
# list (列表)
# set (集合)
# zset (有序集)
# hash (哈希表)
8.3 String类型
2. 常用操作命令
命令 | 说明 |
set | 设置一个key/value |
get | 根据key获得对应的value |
mset | 一次设置多个key value |
mget | 一次获得多个key的value |
getset | 获得原始key的值,同时设置新值 |
strlen | 获得对应key存储value的长度 |
append | 为对应key的value追加内容 |
getrange 索引0开始 | 截取value的内容 |
setex | 设置一个key存活的有效期(秒) |
psetex | 设置一个key存活的有效期(毫秒) |
setnx | 存在不做任何操作,不存在添加 |
msetnx原子操作(只要有一个存在不做任何操作) | 可以同时设置多个key,只有有一个存在都不保存 |
decr | 进行数值类型的-1操作 |
decrby | 根据提供的数据进行减法操作 |
Incr | 进行数值类型的+1操作 |
incrby | 根据提供的数据进行加法操作 |
Incrbyfloat | 根据提供的数据加入浮点数 |
8.4 List类型
list类型 元素有序 且 可以重复
1.内存存储模型
key String
value list
2.常用操作指令
命令 | 说明 |
lpush | 将某个值加入到一个key列表头部 |
lpushx | 同lpush,但是必须要保证这个key存在 |
rpush | 将某个值加入到一个key列表末尾 |
rpushx | 同rpush,但是必须要保证这个key存在 |
lpop | 返回和移除列表左边的第一个元素 |
rpop | 返回和移除列表右边的第一个元素 |
lrange | 获取某一个下标区间内的元素 |
llen | 获取列表元素个数 |
lset | 设置某一个指定索引的值(索引必须存在) |
lindex | 获取某一个指定索引位置的元素 |
lrem | 删除重复元素 |
ltrim | 保留列表中特定区间内的元素 |
linsert | 在某一个元素之前,之后插入新元素 |
8.5 Set类型
key String
vlaue Set集合类型 元素不能存在重复 元素无序 String
特点: Set类型 Set集合 元素无序 不可以重复
1.内存存储模型
2.常用命令
命令 | 说明 |
sadd | 为集合添加元素 |
smembers | 显示集合中所有元素 无序 |
scard | 返回集合中元素的个数 |
spop | 随机返回一个元素 并将元素在集合中删除 |
smove | 从一个集合中向另一个集合移动元素 必须是同一种类型 smove ages(集合名) names(目标集合名) 22(数据) |
srem | 从集合中删除一个元素 |
sismember | 判断一个集合中是否含有这个元素 0:不存在 1:存在 |
srandmember | 随机返回元素 |
sdiff | 去掉第一个集合中其它集合含有的相同元素 (不会操作原始集合元素) |
sinter | 求交集 |
sunion | 求和集 |
8.6 ZSet类型 SortSet可排序SET
ZSET
特点: 1.不可重复 2.可排序的set集合 根据元素分数进行排序
key String
value ZSET
1.内存模型
2.常用命令
命令 | 说明 |
zadd | 添加一个有序集合元素 zadd 集合名 分数 数据 分数 数据 |
zcard | 返回集合的元素个数 |
zrange 升序 zrevrange 降序 | 返回一个范围内的元素 zrange 集合名 开始索引 结束索引 |
zrangebyscore | 按照分数查找一个范围内的元素 (实现分页) |
zrank | 返回排名升序排名 |
zrevrank | 倒序排名 |
zscore | 显示某一个元素的分数 |
zrem | 移除某一个元素 |
zincrby | 给某个特定元素加分 |
8.7 hash类型
key:String
value: Map<String,String>
特点: value 是一个map结构 存在key value key 无序的
KEY(String) hash(key,value)
1.内存模型
2.常用命令
命令 | 说明 |
hset | 设置一个key/value对 |
hget | 获得一个key对应的value |
hgetall | 获得所有的key/value对 |
hdel | 删除某一个key/value对 |
hexists | 判断一个key是否存在 |
hkeys | 获得所有的key |
hvals | 获得所有的value |
hmset | 设置多个key/value |
hmget | 获得多个key的value |
hsetnx | 设置一个不存在的key的值 |
hincrby | 为value进行加法运算 |
hincrbyfloat | 为value加入浮点值 |
9. 持久化机制
client redis[内存] -----> 内存数据- 数据持久化–>磁盘
Redis官方提供了两种不同的持久化方法来将数据存储到硬盘里面分别是:
- 快照(Snapshot)
- AOF (Append Only File) 只追加日志文件
9.1 快照(Snapshot)
1. 特点
这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是redis的默认开启持久化方式,保存的文件是以.rdb形式结尾的文件因此这种方式也称之为RDB方式。
2.快照生成方式
- 客户端方式: BGSAVE 和 SAVE指令
- 服务器配置自动触发
# 1.客户端方式之BGSAVE
- a.客户端可以使用BGSAVE命令来创建一个快照,当接收到客户端的BGSAVE命令时,redis会调用fork¹来创建一个子进程,然后子进程负责将快照写入磁盘中,而父进程则继续处理命令请求。
`名词解释: fork当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,在类unix系统中创建子进程的操作会进行优化:在刚开始的时候,父子进程共享相同内存,直到父进程或子进程对内存进行了写之后,对被写入的内存的共享才会结束服务`
# 2.客户端方式之SAVE
- b.客户端还可以使用SAVE命令来创建一个快照,接收到SAVE命令的redis服务器在快照创建完毕之前将不再响应任何其他的命令
-
- **注意: SAVE命令并不常用,使用SAVE命令在快照创建完毕之前,redis处于阻塞状态,无法对外服务**
```markdown
# 3.服务器配置方式之满足配置自动触发
- 如果用户在redis.conf中设置了save配置选项,redis会在save选项条件满足之后自动触发一次BGSAVE命令,如果设置多个save配置选项,当任意一个save配置选项条件满足,redis也会触发一次BGSAVE命令
# 4.服务器接收客户端shutdown指令
- 当redis通过shutdown指令接收到关闭服务器的请求时,会执行一个save命令,阻塞所有的客户端,不再执行客户端执行发送的任何命令,并且在save命令执行完毕之后关闭服务器
3.配置生成快照名称和位置
# 1.修改生成快照名称
- dbfilename dump.rdb
# 2.修改生成位置
- dir ./
4.存在的问题
redis在触发了一次快照之后,还没有到达下这一次触发时机,在这个过程中如果redis突然宕机这期间redis客户端执行的写操作将全部丢失
9.2 AOF 只追加日志文件
1.特点
这种方式可以将所有客户端执行的写命令记录到日志文件中,AOF持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发生的变化,因此只要redis从头到尾执行一次AOF文件所包含的所有写命令,就可以恢复AOF文件的记录的数据集.
2.开启AOF持久化
在redis的默认配置中AOF持久化机制是没有开启的,需要在配置中开启
# 1.开启AOF持久化
- a.修改 appendonly yes 开启持久化
- b.修改 appendfilename "appendonly.aof" 指定生成文件名称
3.日志追加频率
# 1.always 【谨慎使用】
- 说明: 每个redis写命令都要同步写入硬盘,严重降低redis速度
- 解释: 如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少;遗憾的是,因为这种同步策略需要对硬盘进行大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制;
- 注意: 转盘式硬盘在这种频率下200左右个命令/s ; 固态硬盘(SSD) 几百万个命令/s; 1k ssd
- 警告: 使用SSD用户请谨慎使用always选项,这种模式不断写入少量数据的做法有可能会引发严重的写入放大问题,导致将固态硬盘的寿命从原来的几年降低为几个月。
# 2.everysec 【推荐】
- 说明: 每秒执行一次同步将多个写命令同步到磁盘
- 解释: 为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据。
# 3.no 【不推荐】
- 说明: 由操作系统决定何时同步
- 解释:最后使用no选项,将完全有操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响但是系统崩溃时,会丢失不定数量的数据,另外如果用户硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘数据填满时,redis会处于阻塞状态,并导致redis的处理命令请求的速度变慢。
4.修改同步频率
# 1.修改日志同步频率
- 修改appendfsync everysec|always|no 指定
9.3 AOF文件的重写
1. AOF带来的问题
AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件Redis提供了AOF重写(ReWriter)机制。
2. AOF重写
用来在一定程度上减小AOF文件的体积
3. 触发重写方式
# 1.客户端方式触发重写
- 执行BGREWRITEAOF命令 不会阻塞redis的服务
# 2.服务器配置方式自动触发
- 配置redis.conf中的auto-aof-rewrite-percentage选项 参加下图↓↓↓
- 如果设置auto-aof-rewrite-percentage值为100和auto-aof-rewrite-min-size 64mb,并且启用的AOF持久化时,那么当AOF文件体积大于64M,并且AOF文件的体积比上一次重写之后体积大了至少一倍(100%)时,会自动触发,如果重写过于频繁,用户可以考虑将auto-aof-rewrite-percentage设置为更大
4. 重写原理
注意:重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件这点和快照有点类似。
# 重写流程
- 1. redis调用fork ,现在有父子两个进程 子进程根据内存中的数据库状态快照,往临时文件中写入重建数据库状态的命令
- 2. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
- 3. 当子进程把快照内容写入新文件已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
- 4. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
1.创建父子进程
2.子进程将当前的数据状态拍一个快照
3.将快照转化为写命令
4.父进程急需处理客户端写操作
5.继续将写命令向老的Aof文件中追加 同时再缓存一份 新的谢命令
6.子进程将快照写入临时文件之后通知父进程,父进程将缓存的新命令加入临时文件中
7.使用新的Aof文件替换老的Aof文件
9.4 持久化总结
两种持久化方案既可以同时使用(aof),又可以单独使用,在某种情况下也可以都不使用,具体使用那种持久化方案取决于用户的数据和应用决定。
无论使用AOF还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)。
10. java操作Redis
10.1 环境准备
1. 引入依赖
<!--引入jedis连接依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
2.创建jedis对象
public static void main(String[] args) {
//1.创建jedis对象
Jedis jedis = new Jedis("192.168.112.102", 6379);//1.redis服务必须关闭防火墙 2.redis服务必须开启远程连接
jedis.select(0);//选择操作的库默认0号库
//2.执行相关操作
//....
//3.释放资源
jedis.close();
}
10.2 操作key相关API
private Jedis jedis;
@Before
public void before(){
this.jedis = new Jedis("192.168.202.205", 7000);
}
@After
public void after(){
jedis.close();
}
//测试key相关
@Test
public void testKeys(){
//删除一个key
jedis.del("name");
//删除多个key
jedis.del("name","age");
//判断一个key是否存在exits
Boolean name = jedis.exists("name");
System.out.println(name);
//设置一个key超时时间 expire pexpire
Long age = jedis.expire("age", 100);
System.out.println(age);
//获取一个key超时时间 ttl
Long age1 = jedis.ttl("newage");
System.out.println(age1);
//随机获取一个key
String s = jedis.randomKey();
//修改key名称
jedis.rename("age","newage");
//查看可以对应值的类型
String name1 = jedis.type("name");
System.out.println(name1);
String maps = jedis.type("maps");
System.out.println(maps);
}
10.3操作String相关API
//测试String相关
@Test
public void testString(){
//set
jedis.set("name","小陈");
//get
String s = jedis.get("name");
System.out.println(s);
//mset
jedis.mset("content","好人","address","海淀区");
//mget
List<String> mget = jedis.mget("name", "content", "address");
mget.forEach(v-> System.out.println("v = " + v));
//getset
String set = jedis.getSet("name", "小明");
System.out.println(set);
//............
}
10.4操作List相关API
//测试List相关
@Test
public void testList(){
//lpush
jedis.lpush("names1","张三","王五","赵柳","win7");
//rpush
jedis.rpush("names1","xiaomingming");
//lrange
List<String> names1 = jedis.lrange("names1", 0, -1);
names1.forEach(name-> System.out.println("name = " + name));
//lpop rpop
String names11 = jedis.lpop("names1");
System.out.println(names11);
//llen
jedis.linsert("lists", BinaryClient.LIST_POSITION.BEFORE,"xiaohei","xiaobai");
//........
}
10.5操作Set的相关API
//测试SET相关
@Test
public void testSet(){
//sadd
jedis.sadd("names","zhangsan","lisi");
//smembers
jedis.smembers("names");
//sismember
jedis.sismember("names","xiaochen");
//...
}
10.6 操作ZSet相关API
//测试ZSET相关
@Test
public void testZset(){
//zadd
jedis.zadd("names",10,"张三");
//zrange
jedis.zrange("names",0,-1);
//zcard
jedis.zcard("names");
//zrangeByScore
jedis.zrangeByScore("names","0","100",0,5);
//..
}
10.7 操作Hash相关API
//测试HASH相关
@Test
public void testHash(){
//hset
jedis.hset("maps","name","zhangsan");
//hget
jedis.hget("maps","name");
//hgetall
jedis.hgetAll("mps");
//hkeys
jedis.hkeys("maps");
//hvals
jedis.hvals("maps");
//....
}
11.SpringBoot整合Redis
Spring Boot Data(数据) Redis 中提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是RedisTemplate的子类,两个方法基本一致,不同之处主要体现在操作的数据类型不同,RedisTemplate中的两个泛型都是Object,意味着存储的key和value都可以是一个对象,而StringRedisTemplate的两个泛型都是String,意味着StringRedisTemplate的key和value都只能是字符串。
存储手机验证码 redis 60s
获取验证码:15236674712 发送(fdsfs) key:15236674712(超时时间) value:fdsfs
登录验证 :15236674712 fdsfs value=get(15236674712)
list key value() cart book book book
user 购物车(一条一条的购物项) hash KEY(用户标识) key(商品id) value(购物项对象)
排行榜 zset key(排行榜标识) 搜索的关键字(评分 )
注意: 使用RedisTemplate默认是将对象序列化到Redis中,所以放入的对象必须实现对象序列化接口
11.1 环境准备
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置application.propertie
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0
11.2 StringRedisTemplate操作
1.key相关的操作
//key相关操作
@Test
public void testKey() {
//查看所有key
Set<String> keys = stringRedisTemplate.keys("*");
keys.forEach(key -> System.out.println(key));
//删除一个key //成功返回true 否则返回false
System.out.println("name是否删除成功: "+stringRedisTemplate.delete("name"));
//删除多个key //删除成功几个key返回就是数字几
System.out.println("成功删除了个数为: "+stringRedisTemplate.delete(Arrays.asList("name", "age")));
//判断某个key是否存在 //存在返回 true
System.out.println("maps是否存在: "+stringRedisTemplate.hasKey("maps"));
//设置key超时时间 参数:key,时间,时间单位
Boolean nsme = stringRedisTemplate.expire("nsme", 30, TimeUnit.SECONDS);
System.out.println("给nsme设置超时时间的状态:"+nsme);
//获取key超时时间 //返回的是存活时间 默认毫秒
System.out.println("name过期时间: "+stringRedisTemplate.getExpire("name")); //-2
//参数1:查看那个key超时时间 参数2:返回时间单位
System.out.println("lists超时时间: "+stringRedisTemplate.getExpire("lists", TimeUnit.SECONDS)); //-2
//移动key 到某一个库
System.out.println("是否移动成功: "+stringRedisTemplate.move("sets", 3));
//随机返回一个key
System.out.println("随机返回的key: "+stringRedisTemplate.randomKey());
}
2.String类型的操作
@Test
public void testString(){
//设置一个值 //永久设置一个key value
stringRedisTemplate.opsForValue().set("name","张三");
//设置一个key同时 指定key超时时间
stringRedisTemplate.opsForValue().set("age","19",10,TimeUnit.HOURS);
//在value后追加值
stringRedisTemplate.opsForValue().append("name"," 他是一个好人");
//给key对应数字类型value进行自减操作 -1
stringRedisTemplate.opsForValue().decrement("age");
//参数2:自减步长
stringRedisTemplate.opsForValue().decrement("age",2);
System.out.println(stringRedisTemplate.opsForValue().get("name"));
System.out.println(stringRedisTemplate.opsForValue().get("age"));
//获取value指定范围内结果
System.out.println(stringRedisTemplate.opsForValue().get("name", 0, -1));
//获取原始并设置新value
System.out.println(stringRedisTemplate.opsForValue().getAndSet("name", "小明明"));
//存在key不做任何操作不存在则设置value
stringRedisTemplate.opsForValue().setIfAbsent("aa","xiaoming");
//一次性设置多个key value 只要其中存在一个key 不做任何操作 原子性
//stringRedisTemplate.opsForValue().multiSetIfAbsent(map)
//获取key所对应值的长度
System.out.println(stringRedisTemplate.opsForValue().size("name"));
}
3.List类型的操作
@Test
public void testList(){
//获取list类型操作对象
ListOperations<String, String> listOperations = stringRedisTemplate.opsForList();
//创建lists列表并放入一个值
listOperations.leftPush("lists","zhangsan");
//创建lists列表并放入多个元素
listOperations.leftPushAll("names","张三","李四","王五");
listOperations.leftPushAll("ages",Arrays.asList("19","18","17"));
//获取一个列表
listOperations.range("names",0,-1).forEach(name-> System.out.println(name));
System.out.println("=================================");
//列表中某个元素之前 leftpush 之后 rightpush插入元素
listOperations.leftPush("names","李四","小陈");
listOperations.rightPush("names","李四","小明");
listOperations.range("names",0,-1).forEach(name-> System.out.println(name));
//获取指定下标的元素
System.out.println(listOperations.index("names", 1));
//向list放入一个值 保证list必须存在 不会创建新的列表
listOperations.leftPushIfPresent("names","xxx"); //names: xx 王五 ... ages: 小明 17 18 19
//针对于两个列表 说明:从参数1列表右边弹出一个元素并把弹出元素放入参数2列表左边 参数1: 列表 参数2:列表
System.out.println(listOperations.rightPopAndLeftPush("names","ages"));
//remove删除重复元素
listOperations.remove("names",2,"zhangsan");
//根据下标修改一个值
listOperations.set("names",2,"xiaochen");
//截取list列表中指定内容 注意:修改list内容
listOperations.trim("names",0,4);
//查询列表长度
System.out.println(listOperations.size("names"));
}
4.Set类型的操作
@Test
public void testSet(){
SetOperations<String, String> setOperations = stringRedisTemplate.opsForSet();
//向set集合添加一个元素
setOperations.add("sets","xxx","123","234");
//查看集合成员
setOperations.members("sets").forEach(val-> System.out.println(val));
System.out.println("===================================");
//从set集合随机返回指定元素
setOperations.distinctRandomMembers("sets",4).forEach( val-> System.out.println(val));
//查询集合元素个数
System.out.println(setOperations.size("sets"));
//从集合中返回并移除一个元素
System.out.println(setOperations.pop("sets"));
//从一个集合移动到另一个集合中 注意:必须是同一种类型
setOperations.move("sets","xxx","sets1");
//去掉第一个集合中其它集合含有的相同元素
setOperations.difference("sets","sets1");
setOperations.difference("sets",Arrays.asList("sets1","sets2","sets3"));
//求多个集合元素交集
setOperations.intersect("sets1","sets");
setOperations.intersect("sets1",Arrays.asList("sets","sets2"));
//求多个集合合集
setOperations.union("sets","sets1");
setOperations.union("sets",Arrays.asList("sets1","sets2"));
}
5.ZSet类型的操作
@Test
public void testZset(){
//获取Zset操作对象
ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
//创建一个zsets集合并放入一个元素
zSetOperations.add("zsets","张三",10.0);
HashSet<ZSetOperations.TypedTuple<String>> sets = new HashSet<>();
sets.add(new DefaultTypedTuple<>("小黑",8.0));
sets.add(new DefaultTypedTuple<>("小名",2.0));
sets.add(new DefaultTypedTuple<>("小网",6.0));
zSetOperations.add("zsets",sets);
//返回zsets集合中指定范围元素
zSetOperations.range("zsets", 0, -1).forEach(el-> System.out.println(el)); //all
System.out.println("========================"); //all score
zSetOperations.rangeWithScores("zsets", 0, -1).forEach(t-> System.out.println("el: "+t.getValue()+" score: "+t.getScore()));
System.out.println("========================");
//返回zsets集合中指定分数范围的元素 //小x
zSetOperations.rangeByScore("zsets",0.0,8.0).forEach(el-> System.out.println(el));
System.out.println("========================");
//分页返回zsets集合中指定分数范围的元素 //小名 小网
zSetOperations.rangeByScore("zsets",0.0,8.0,0,2).forEach(el-> System.out.println(el));
System.out.println("========================"); //小x score
zSetOperations.rangeByScoreWithScores("zsets",0.0,8.0).forEach(t-> System.out.println("el: "+t.getValue()+" score: "+t.getScore()));
//正向排名 升序
zSetOperations.rank("zsets","张三");
//反向排名 降序
zSetOperations.reverseRank("zsets","张三");
//返回指定元素分数
zSetOperations.score("zsets","小名");
//移除某一个元素
zSetOperations.remove("zsets","张三");
//给某个元素自增分数
zSetOperations.incrementScore("zsets","张三",2.0);
}
6.Hash类型的操作
@Test
public void testHash(){
//获取hash类型操作对象
HashOperations<String, Object, Object> hashOperations = stringRedisTemplate.opsForHash();
//hset
hashOperations.put("maps","name","张三");
//hmset
HashMap<String, String> m = new HashMap<>();
m.put("address","xxx");
m.put("content","xxx");
hashOperations.putAll("maps", m);
//hgetall
hashOperations.entries("maps").forEach((k,v)-> System.out.println("key: "+k +" value: "+v));
//hget
hashOperations.get("maps","name");
//hmget
hashOperations.multiGet("maps",Arrays.asList("name","addres","content")).forEach(val-> System.out.println(val));
//hdel
hashOperations.delete("maps","name");
//hexists
hashOperations.hasKey("maps","name");
//hkeys
hashOperations.keys("maps");
//hvals
hashOperations.values("maps");
//hsetnx
hashOperations.putIfAbsent("maps","name","xiaochen");
//hincrby
hashOperations.increment("maps","age",11.11);
}
11.3 使用StringRedisTemplate和RedisTemplate
- 两者的关系是StringRedisTemplate继承RedisTemplate。
- 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
- SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
@Autowired
private StringRedisTemplate stringRedisTemplate; //对字符串支持比较友好,不能存储对象
@Autowired
private RedisTemplate redisTemplate; //存储对象
@Test
public void testRedisTemplate(){
System.out.println(redisTemplate);
//设置redistemplate值使用对象序列化策略
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());//指定值使用对象序列化
//redisTemplate.opsForValue().set("user",new User("21","小黑",23,new Date()));
User user = (User) redisTemplate.opsForValue().get("user");
System.out.println(user);
}
11.4.opsForXXX和boundXXXOps区别
//key的绑定操作 如果日后对某一个key的操作及其频繁,可以将这个key绑定到对应redistemplate中,日后基于绑定操作都是操作这个key
//boundValueOps 用来对String值绑定key
//boundListOps 用来对List值绑定key
//boundSetOps 用来对Set值绑定key
//boundZsetOps 用来对Zset值绑定key
//boundHashOps 用来对Hash值绑定key
@Test
public void test(){
ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
valueOperations.set("name","zhangsan");
valueOperations.append("name","this is good man");
valueOperations.size("name");
//String
//stringRedisTemplate.boundValueOps()
BoundValueOperations<String, String> nameOperations = stringRedisTemplate.boundValueOps("name");
nameOperations.set("xiaochen");
nameOperations.append("this is good man");
nameOperations.size();
//list
ListOperations<String, String> listOperations = stringRedisTemplate.opsForList();
listOperations.leftPush("lists","zhangsan");
listOperations.size("lists");
listOperations.range("lists",0,-1);
BoundListOperations<String, String> listsOperations = stringRedisTemplate.boundListOps("lists");
listsOperations.leftPush("zhangsan");
listsOperations.leftPush("xiaozhang");
listsOperations.size();
listsOperations.range(0,-1);
//stringRedisTemplate.boundSetOps()
//stringRedisTemplate.boundZsetOps()
//stringRedisTemplate.boundHashOps()
}
12. Redis 主从复制
5.x版本之后不支持
12.1 主从复制
主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据
无法解决: 1.master节点出现故障的自动故障转移
12.2 主从复制架构图
12.3 搭建主从复制
这里给大家演示Redis伪分布式的搭建
**1.准备三台机器 **
2.准备三个配置文件
mkdir master slave1 slave2
3.复制三份配置文件到指定目录
cp redis-4.0.10/redis.conf master/
cp redis-4.0.10/redis.conf slave1/
cp redis-4.0.10/redis.conf slave2/
4.修改配置文件
vim master/redis.conf
master 修改以下配置:
port 7001 bind 0.0.0.0
vim slave1/redis.conf
slave1 修改以下配置:
port 7002 bind 0.0.0.0 slaveof 10.15.0.5 7001
vim slave2/redis.conf
slave2 修改以下配置:
port 7003 bind 0.0.0.0 slaveof 10.15.0.5 7001
# 5.启动3台机器进行测试
- cd /usr/redis/bin
- ./redis-server /root/master/redis.conf
- ./redis-server /root/slave1/redis.conf
- ./redis-server /root/slave2/redis.conf
13. Redis哨兵(Sentinel)机制
13.1 哨兵Sentinel机制
Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。简单的说哨兵就是带有自动故障转移功能的主从架构。
无法解决: 1.单节点并发压力问题 2.单节点内存和磁盘物理上限
13.2 哨兵架构原理
13.3 搭建哨兵架构
1.创建sentinel启动配置文件
mkdir sentinel
touch sentinel/sentinel.conf 新建sentinel.conf文件,名字绝对不能错;
2.配置哨兵,在sentinel.conf文件中填入内容:
在sentinel.conf中加入以下配置:
vim sentinel/sentinel.conf
sentinel monitor 被监控数据库名字(自己起名字) ip port 1
eg:
sentinel monitor aa 10.15.0.5 7001 1
说明:这个后面的数字2,是指当有两个及以上的sentinel服务检测到master宕机,才会去执行主从切换的功能。
3.将哨兵的启动脚本文件复制到redis目录下
cp redis-4.0.10/src/redis-sentinel /usr/redis/
4.进入redis目录指定配置文件启动rendis-sentinel
./redis-sentinel /root/sentinel/sentinel.conf
5.测试哨兵能否自动的作主节点故障转移
13.4 通过springboot操作哨兵
# redis sentinel 配置
# master书写是使用哨兵监听的那个名称
spring.redis.sentinel.master=mymaster
# 连接的不再是一个具体redis主机,书写的是多个哨兵节点
spring.redis.sentinel.nodes=192.168.202.206:26379
- 注意:如果连接过程中出现如下错误:RedisConnectionException: DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command ‘CONFIG SET protected-mode no’ from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2)
- 解决方案:在哨兵的配置文件中加入bind 0.0.0.0 开启远程连接权限
14. Redis集群
14.1 集群
Redis在3.0后开始支持Cluster(模式)模式,目前redis的集群支持节点的自动发现,支持slave-master选举和容错,支持在线分片(sharding shard )等特性。reshard
14.2 集群架构图
14.3 集群细节
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
- 节点的fail是通过集群中超过半数的节点检测失效时才生效.
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
14.4 集群搭建
判断一个是集群中的节点是否可用,是集群中的所用主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉了,所以搭建redis集群时建议节点数最好为奇数,搭建集群至少需要三个主节点,三个从节点,至少需要6个节点。
# 1.准备环境安装ruby以及redis集群依赖
- yum install -y ruby rubygems
- gem install redis-xxx.gem
# 2.在一台机器创建7个目录
# 3.每个目录复制一份配置文件
[root@localhost ~]# cp redis-4.0.10/redis.conf 7000/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7001/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7002/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7003/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7004/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7005/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7006/
# 4.修改不同目录配置文件
- port 6379 ..... //修改端口
- bind 0.0.0.0 //开启远程连接
- cluster-enabled yes //开启集群模式
- cluster-config-file nodes-port.conf //集群节点配置文件
- cluster-node-timeout 5000 //集群节点超时时间
- appendonly yes //开启AOF持久化
# 5.指定不同目录配置文件启动七个节点
- [root@localhost bin]# ./redis-server /root/7000/redis.conf
- [root@localhost bin]# ./redis-server /root/7001/redis.conf
- [root@localhost bin]# ./redis-server /root/7002/redis.conf
- [root@localhost bin]# ./redis-server /root/7003/redis.conf
- [root@localhost bin]# ./redis-server /root/7004/redis.conf
- [root@localhost bin]# ./redis-server /root/7005/redis.conf
- [root@localhost bin]# ./redis-server /root/7006/redis.conf
# 6.查看进程
- [root@localhost bin]# ps aux|grep redis
1.创建集群
# 1.复制集群操作脚本到bin目录中
- [root@localhost bin]# cp /root/redis-4.0.10/src/redis-trib.rb /usr/redis/bin
# 2.创建集群
- ./redis-trib.rb create --replicas 1 192.168.202.205:7000 192.168.202.205:7001 192.168.202.205:7002 192.168.202.205:7003 192.168.202.205:7004 192.168.202.205:7005
eg:
./redis-trib.rb create --replicas 192.168.112.102:7000 192.168.112.102:7001 192.168.112.102:7002 192.168.112.102:7003 192.168.112.102:7004 192.168.112.102:7005
./redis-trib.rb #启动的脚本
create --replicas 创建集群
1 一个主节点下有几个从节点
# 3.集群创建成功出现如下提示
2.查看集群状态
# 1.查看集群状态 check [原始集群中任意节点] [无]
- ./redis-trib.rb check 192.168.202.205:7000
# 2.集群节点状态说明
- 主节点
主节点存在hash slots,且主节点的hash slots 没有交叉
主节点不能删除
一个主节点可以有多个从节点
主节点宕机时多个副本之间自动选举主节点
- 从节点
从节点没有hash slots
从节点可以删除
从节点不负责数据的写,只负责数据的同步
3.添加主节点
# 1.添加主节点 add-node [新加入节点] [原始集群中任意节点]
- ./redis-trib.rb add-node 192.168.1.158:7006 192.168.1.158:7005
- 注意:
1.该节点必须以集群模式启动
2.默认情况下该节点就是以master节点形式添加
4.添加从节点
# 1.添加从节点 add-node --slave [新加入节点] [集群中任意节点]
- ./redis-trib.rb add-node --slave 192.168.1.158:7006 192.168.1.158:7000
- 注意:
当添加副本节点时没有指定主节点,redis会随机给副本节点较少的主节点添加当前副本节点
# 2.为确定的master节点添加主节点 add-node --slave --master-id master节点id [新加入节点] [集群任意节点]
- ./redis-trib.rb add-node --slave --master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7006 127.0.0.1:7000
./redis-trib.rb add-node --slave --master-id b75d75e28525e67152ae618245b33ca41e2f1c41 192.168.112.102:7003 192.168.112.102:7000
5.删除副本节点
# 1.删除节点 del-node [集群中任意节点] [删除节点id]
- ./redis-trib.rb del-node 127.0.0.1:7002 0ca3f102ecf0c888fc7a7ce43a13e9be9f6d3dd1
- 注意:
1.被删除的节点必须是从节点或没有被分配hash slots的节点
6.集群在线分片
# 1.在线分片 reshard [集群中任意节点] [无]
- ./redis-trib.rb reshard 192.168.1.158:7000
15.Redis实现分布式Session管理
15.1 管理机制
redis的session管理是利用spring提供的session管理解决方案,将一个应用session交给Redis存储,整个应用中所有session的请求都会去redis中获取对应的session数据。
15.2 开发Session管理
15.3.创建一个SpringBoot项目
1.导入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.配置项目配置
server:
port: 9696
servlet:
context-path:
3.开发controller测试
@Controller
@RequestMapping("test")
public class TestController {
@RequestMapping("session")
public void testSession(HttpServletRequest request, HttpServletResponse response) throws IOException {
//redis
ArrayList<String> list = (ArrayList<String>) request.getSession().getAttribute("list");
if(list==null){
list = new ArrayList<String>(); request.getSession().setAttribute("list",list);
}
list.add("hello redis session");
//响应
response.getWriter().print("<h1> sessionId: "+request.getSession().getId()+"</h1>");
response.getWriter().print("<h1> sise: "+list.size()+"</h1>");
}
}
4.启动项目测试session
15.4.项目连接Redis
注意:启动redis,并开启redis的远程连接(单节点)
1.导入redis的依赖
<!--导入连接redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置redis的连接
spring:
redis:
host: 192.168.112.102
port: 7000
database: 3
3. 测试redis的连接
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
void contextLoadss() {
//获取所有key 参数: * n?me n[as]me n*me
Set<String> keys = stringRedisTemplate.keys("*");
for (String key : keys) {
System.out.println("获取所有的key:"+key);
}
}
15.5.使用redis管理session
1. 引入依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2. 开发Session管理配置类
@Configuration //xml bean //@Component 创建单个对象 bean
@EnableRedisHttpSession
public class RedisSessionManager {
}
3.修改controller代码
@Controller
@RequestMapping("test")
public class TestController {
@RequestMapping("session")
public void testSession(HttpServletRequest request, HttpServletResponse response) throws IOException {
//redis
ArrayList<String> list = (ArrayList<String>) request.getSession().getAttribute("list");
//tomcat jvm session @101 list @102
//session list的地址
//list
if(list==null){
list = new ArrayList<String>();
}
list.add("hello redis session");
request.getSession().setAttribute("list",list);
//响应
response.getWriter().print("<h1> sessionId: "+request.getSession().getId()+"</h1>");
response.getWriter().print("<h1> sise: "+list.size()+"</h1>");
}
}
4.打包测试即可
注意:
1.war
…
15.6.tomcat集群测试
1.将项目的war包导入linux中的Tocmat集群启动tomcat
2.通过nginx访问测试session共享
16.使用redis实现缓存
感谢阅读~