目录
一、引言
1.1 数据库压力过大
由于用户量增大,请求数量也随之增大,数据压力过大
1.2 数据不同步
多台服务器之间,数据不同步
1.3 传统锁失效
多台服务器之间的锁,已经不存在互斥性了。
二、Redis介绍
2.1 NoSQL
NoSQL,泛指非关系型的数据库,NoSQL即Not-Only SQL,它可以作为关系型数据库的良好补充。随着互联网web2.0网站的兴起,非关系型的数据库现在成了一个极其热门的新领域,非关系数据库产品的发展非常迅速
而传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,例如:
1、High performance -
对数据库高并发读写的需求
web2.0网站要根据用户个性化信息来实时生成动态页面和提供动态信息,所以基本上无法使用动态页面静态化技术,因此数据库并发负载非常高,往往要达到每秒上万次读写请求。
关系数据库应付上万次SQL查询还勉强顶得住,但是应付上万次SQL写数据请求,硬盘IO就已经无法承受了。
其实对于普通的BBS网站,往往也存在对高并发写请求的需求,例如网站的实时统计在线用户状态,记录热门帖子的点击次数,投票计数等,因此这是一个相当普遍的需求。2、Huge Storage -
对海量数据的高效率存储和访问的需求
类似Facebook,twitter,Friendfeed这样的SNS网站,每天用户产生海量的用户动态,以Friendfeed为例,一个月就达到了2.5亿条用户动态,对于关系数据库来说,
在一张2.5亿条记录的表里面进行SQL查询,效率是极其低下乃至不可忍受的。再例如大型web网站的用户登录系统,例如腾讯,盛大,动辄数以亿计的帐号,
关系数据库也很难应付。3、High Scalability && High Availability-
对数据库的高可扩展性和高可用性的需求,在基于web的架构当中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,
你的数据库却没有办法像web server和app server那样简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供24小时不间断服务的网站来说,
对数据库系统进行升级和扩展是非常痛苦的事情,往往需要停机维护和数据迁移,为什么数据库不能通过不断的添加服务器节点来实现扩展呢?NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题
2.2 NoSQL的类别
2.2.1键值(Key-Value)存储数据库
相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
数据模型: 一系列键值对
优势: 快速查询
劣势: 存储的数据缺少结构化
2.2.2 列存储数据库
相关产品:Cassandra, HBase, Riak
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限
2.2.3 文档型数据库
相关产品:CouchDB、MongoDB
典型应用:Web应用(与Key-Value类似,Value是结构化的)
数据模型: 一系列键值对
优势:数据结构要求不严格
劣势: 查询性能不高,而且缺乏统一的查询语法
2.2.4 图形(Graph)数据库
相关数据库:Neo4J、InfoGrid、Infinite Graph
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
2.3 Redis是什么
2008年,意大利的一家创业公司Merzia推出了一款基于MySQL的网站实时统计系统LLOOGG,然而没过多久该公司的创始人 Salvatore Sanfilippo便对MySQL的性能感到失望,
于是他决定亲自为LLOOGG量身定做一个数据库,并于2009年开发完成,这个数据库就是Redis。不过Salvatore Sanfilippo并不满足只将Redis用于LLOOGG这一款产品,
而是希望更多的人使用它,于是在同一年Salvatore Sanfilippo将Redis开源发布,并开始和Redis的另一名主要的代码贡献者Pieter Noordhuis一起继续着Redis的开发,
直到今天。Salvatore Sanfilippo自己也没有想到,短短的几年时间,Redis就拥有了庞大的用户群体。Hacker News在2012年发布了一份数据库的使用情况调查,
结果显示有近12%的公司在使用Redis。国内如新浪微博、街旁网、知乎网,国外如GitHub、Stack Overflow、Flickr等都是Redis的用户。
VMware公司从2010年开始赞助Redis的开发, Salvatore Sanfilippo和Pieter Noordhuis也分别在3月和5月加入VMware,全职开发Redis。Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库。它通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:
字符串类型
散列类型
列表类型
集合类型
有序集合类型Redis 与其他 key - value 缓存产品有以下三个特点:
Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
Redis支持数据的备份,即master-slave模式的数据备份。
Redis 提供的API支持:C、C++、C#、Clojure、Java、JavaScript、Lua、PHP、Python、Ruby、Go、Scala、Perl等多种语言。
2.4 Redis的应用场景
目前全球最大的Redis用户是新浪微博,在新浪有200多台物理机,400多个端口正在运行Redis,有+4G的数据在Redis上来为微博用户提供服务
- 取最新的N个数据(取最新文档、排行榜等)
- 需要精确设定过期时间的应用
- 计数器应用
- 实时性要求的高并发读写
- 消息系统Pub/Sub
- 构建队列
- 缓存
2.5 Redis优缺点
2.5.1 Redis 优势
对数据高并发读写(基于内存)
对海量数据的高效率存储和访问(基于内存)
对数据的可扩展性和高可用性
垂直扩展:提升硬件
水平扩展:集群
2.5.2 Redis 缺点
redis(ACID处理非常简单)无法做到太复杂的关系数据库模型
2.6 Redis面向互联网的解决方案
- 主从:一主多从,主机可写,从机备份。类似于Mysql的读写分离,存在问题是一但主节点down掉,整个Redis不可用。
- 哨兵(2.x):启用一个哨兵程序(节点),监控其余节点的状态,根据选举策略,进行主从切换。
缺点:每个节点的数据依旧是一致的,仍无法实现分布式的数据库。- 集群(3.x):结合上述两种模式,多主多从,实现高可用、分布式数据存储
三、Redis安装
3.1 安装Redis
在https://redis.io/下载并解压即可
下载
wget http://download.redis.io/releases/redis-4.0.6.tar.gz
解压
tar -zxvf redis-4.0.6.tar.gz
安装gcc依赖
yum install gcc
跳转到解压目录
cd redis-4.0.6
编译安装
cd src && make install
3.2 启动并使用redis-cli连接Redis
有两种启动方式:直接启动和以服务的方式启动
直接启动,这种启动方式需要一直打开窗口,不能进行其他操作,不太方便
./redis-server
以服务的方式启动:(推荐)
先修改redis.conf文件,将daemonize no 修改为 daemonize yes
指定启动文件并启动
./redis-server /usr/local/redis-4.0.6/redis.conf
指定启动文件并指定端口号启动
./redis-server /usr/local/redis-4.0.6/redis.conf --port 6380
使用客户端redis-cli连接
./redis-cli
远程连接:
默认不允许远程连接,需要修改一下信息才可以进行修改
将redis.conf中的bind 127.0.0.1进行注释
vim /usr/local/redis/conf/redis.conf 编辑配置文件
/usr/local/redis/bin/redis-server ../conf/redis.conf 启动redis
/usr/local/redis/bin/redis-cli 打开客户端,连接成功,再进行下一步
config set requirepass lx 设置密码
quit 退出客户端
/usr/local/redis/bin/redis-cli 打开客户端
auth lx 输入密码
注意:
windows版本的redis服务器启动,直接在命令行中输入:
redis-server.exe redis.windows.conf
客户端启动:
直接双击redis-cli.exe
3.3 使用图形化界面连接Redis
傻瓜式安装
RedisDesktopManager |
---|
3.4 redis.conf配置文件详解
1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no
2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid
3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379
4. 绑定的主机地址
bind 127.0.0.1
5.当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
loglevel verbose
7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile stdout
8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
databases 16
9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save <seconds> <changes>
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
11. 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
12. 指定本地数据库存放目录
dir ./
13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
slaveof <masterip> <masterport>
14. 当master服务设置了密码保护时,slav服务连接master的密码
masterauth <master-password>
15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭
requirepass foobared
16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128
17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory <bytes>
18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no
19. 指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof
20. 指定更新日志条件,共有3个可选值:
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec
21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
vm-enabled no
22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap
23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0
24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
vm-page-size 32
25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
vm-pages 134217728
26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4
27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes
28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes
30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
四、Redis常用命令【重点
】
4.1 Redis存储数据的结构
常用的5种数据结构:
- key-string:一个key对应一个值。
- key-hash:一个key对应一个Map。
- key-list:一个key对应一个列表。
- key-set:一个key对应一个集合。
- key-zset:一个key对应一个有序的集合。
另外三种数据结构:
- HyperLogLog:计算近似值的。
- GEO:地理位置。
- BIT:一般存储的也是一个字符串,存储的是一个byte[]。
五种常用的存储数据结构图 |
---|
key-string:最常用的,一般用于存储一个值。
key-hash:存储一个对象数据的。
key-list:使用list结构实现栈和队列结构。
key-set:交集,差集和并集的操作。
key-zset:排行榜,积分存储等操作。
4.2 string常用命令
string常用操作命令
#1. 添加值
set key value
#2. 取值
get key
#3. 批量操作
mset key value [key value...]
mget key [key...]
#4. 自增命令(自增1)
incr key
#5. 自减命令(自减1)
decr key
#6. 自增或自减指定数量
incrby key increment
decrby key increment
#7. 设置值的同时,指定生存时间(每次向Redis中添加数据时,尽量都设置上生存时间)
setex key second value
#8. 设置值,如果当前key不存在的话(如果这个key存在,什么事都不做,如果这个key不存在,和set命令一样)
setnx key value
#9. 在key对应的value后,追加内容
append key value
#10. 查看value字符串的长度
strlen key
4.3 hash常用命令
hash常用命令
#1. 存储数据
hset key field value
#2. 获取数据
hget key field
#3. 批量操作
hmset key field value [field value ...]
hmget key field [field ...]
#4. 自增(指定自增的值)
hincrby key field increment
#5. 设置值(如果key-field不存在,那么就正常添加,如果存在,什么事都不做)
hsetnx key field value
#6. 检查field是否存在
hexists key field
#7. 删除key对应的field,可以删除多个
hdel key field [field ...]
#8. 获取当前hash结构中的全部field和value
hgetall key
#9. 获取当前hash结构中的全部field
hkeys key
#10. 获取当前hash结构中的全部value
hvals key
#11. 获取当前hash结构中field的数量
hlen key
4.4 list常用命令
list常用命令
#1. 存储数据(从左侧插入数据,从右侧插入数据)
lpush key value [value ...]
rpush key value [value ...]
#2. 存储数据(如果key不存在,什么事都不做,如果key存在,但是不是list结构,什么都不做)
lpushx key value
rpushx key value
#3. 修改数据(在存储数据时,指定好你的索引位置,覆盖之前索引位置的数据,index超出整个列表的长度,也会失败)
lset key index value
#4. 弹栈方式获取数据(左侧弹出数据,从右侧弹出数据)
lpop key
rpop key
#5. 获取指定索引范围的数据(start从0开始,stop输入-1,代表最后一个,-2代表倒数第二个)
lrange key start stop
#6. 获取指定索引位置的数据
lindex key index
#7. 获取整个列表的长度
llen key
#8. 删除列表中的数据(他是删除当前列表中的count个value值,count > 0从左侧向右侧删除,count < 0从右侧向左侧删除,count == 0删除列表中全部的value)
lrem key count value
#9. 保留列表中的数据(保留你指定索引范围内的数据,超过整个索引范围被移除掉)
ltrim key start stop
#10. 将一个列表中最后的一个数据,插入到另外一个列表的头部位置
rpoplpush list1 list2
4.5 set常用命令
set常用命令
#1. 存储数据
sadd key member [member ...]
#2. 获取数据(获取全部数据)
smembers key
#3. 随机获取一个数据(获取的同时,移除数据,count默认为1,代表弹出数据的数量)
spop key [count]
#4. 交集(取多个set集合交集)
sinter set1 set2 ...
#5. 并集(获取全部集合中的数据)
sunion set1 set2 ...
#6. 差集(获取多个集合中不一样的数据)
sdiff set1 set2 ...
# 7. 删除数据
srem key member [member ...]
# 8. 查看当前的set集合中是否包含这个值
sismember key member
4.6 zset的常用命令
zset常用命令
#1. 添加数据(score必须是数值。member不允许重复的。)
zadd key score member [score member ...]
#2. 修改member的分数(如果member是存在于key中的,正常增加分数,如果memeber不存在,这个命令就相当于zadd)
zincrby key increment member
#3. 查看指定的member的分数
zscore key member
#4. 获取zset中数据的数量
zcard key
#5. 根据score的范围查询member数量
zcount key min max
#6. 删除zset中的成员
zrem key member [member...]
#7. 根据分数从小到大排序,获取指定范围内的数据(withscores如果添加这个参数,那么会返回member对应的分数)
zrange key start stop [withscores]
#8. 根据分数从大到小排序,获取指定范围内的数据(withscores如果添加这个参数,那么会返回member对应的分数)
zrevrange key start stop [withscores]
#9. 根据分数的返回去获取member(withscores代表同时返回score,添加limit,就和MySQL中一样,如果不希望等于min或者max的值被查询出来可以采用 ‘(分数’ 相当于 < 但是不等于的方式,最大值和最小值使用+inf和-inf来标识)
zrangebyscore key min max [withscores] [limit offset count]
4.7 key常用命令
key常用命令
#1. 查看Redis中的全部的key(pattern:* ,xxx*,*xxx)
keys pattern
#2. 查看某一个key是否存在(1 - key存在,0 - key不存在)
exists key
#3. 删除key
del key [key ...]
#4. 设置key的生存时间,单位为秒,单位为毫秒,设置还能活多久
expire key second
pexpire key milliseconds
#5. 设置key的生存时间,单位为秒,单位为毫秒,设置能活到什么时间点
expireat key timestamp
pexpireat key milliseconds
#6. 查看key的剩余生存时间,单位为秒,单位为毫秒(-2 - 当前key不存在,-1 - 当前key没有设置生存时间,具体剩余的生存时间)
ttl key
pttl key
#7. 移除key的生存时间(1 - 移除成功,0 - key不存在生存时间,key不存在)
persist key
#8. 选择操作的库
select 0~15
#9. 移动key到另外一个库中
move key db
#10. 登录
auth password
4.8 库的常用命令
db常用命令
#1. 清空当前所在的数据库
flushdb
#2. 清空全部数据库
flushall
#3. 查看当前数据库中有多少个key
dbsize
#4. 查看最后一次操作的时间
lastsave
#5. 实时监控Redis服务接收到的命令
monitor
五、Java连接Redis【重点
】
5.1 Jedis连接Redis
5.1.1 创建Maven工程
idea创建
5.1.2 导入需要的依赖
<dependencies>
<!-- 1、 Jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.1</version>
</dependency>
<!-- 2、 Junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 3、 Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
</dependencies>
5.1.3 测试
public class Demo1 {
@Test
public void set(){
//1. 连接Redis
Jedis jedis = new Jedis("localhost",6379);
//2. 操作Redis - 因为Redis的命令是什么,Jedis的方法就是什么
jedis.set("name","李四");
//3. 释放资源
jedis.close();
}
@Test
public void get(){
//1. 连接Redis
Jedis jedis = new Jedis("localhost",6379);
//2. 操作Redis - 因为Redis的命令是什么,Jedis的方法就是什么
String value = jedis.get("name");
System.out.println(value);
//3. 释放资源
jedis.close();
}
}
具体操作内容:
Jedis jedis = new Jedis("localhost", 6379); //默认端口6379 可以省略
jedis.auth("123456"); //无密码此步可省略
//String操作
jedis.set("abc", "test1"); // 存数据
System.out.println("String:" + jedis.get("abc")); // 取数据
//hash操作
jedis.hset("hash1", "name", "zhangan");
jedis.hset("hash1", "pass", "123456");
Map<String, String> hash1 = jedis.hgetAll("hash1");
for(String s:hash1.keySet()){
System.out.println("hash:"+s+"--->"+hash1.get(s));
}
//list操作
jedis.lpush("list1", "Java");
jedis.lpush("list1", "Html5");
jedis.lpush("list1", "Python");
// 获取存储的数据并输出
List<String> list = jedis.lrange("list1", 0, -1);
for (int i = 0; i < list.size(); i++) {
System.err.println("列表项为: " + list.get(i));
}
//set操作
jedis.sadd("set1", "MySQL");
jedis.sadd("set1", "Redis");
Set<String> set1 = jedis.smembers("set1");
for (String s : set1) {
System.err.println("set:" + s);
}
//zset操作
jedis.zadd("zset1",1.2,"java");
jedis.zadd("zset1",2.2,"html");
Set<String> set2=jedis.zrange("zset1",0,3);
for(String s :set2){
System.err.println("zset:" + s);
}
//系统命令
Set<String> keys = jedis.keys("*");
Iterator<String> it = keys.iterator();
while (it.hasNext()) {
String key = it.next();
System.err.println(key);
}
5.2 Jedis存储一个对象到Redis以byte[]的形式
5.2.1 准备一个User实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private Integer id;
private String name;
private Date birthday;
}
5.2.2 导入spring-context依赖
<!-- 4. 导入spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
5.2.3 创建Demo测试类,编写内容
public class Demo2 {
// 存储对象 - 以byte[]形式存储在Redis中
@Test
public void setByteArray(){
//1. 连接Redis服务
Jedis jedis = new Jedis("localhost",6379);
//------------------------------------------------
//2.1 准备key(String)-value(User)
String key = "user";
User value = new User(1,"张三",new Date());
//2.2 将key和value转换为byte[]
byte[] byteKey = SerializationUtils.serialize(key);
byte[] byteValue = SerializationUtils.serialize(value);
//2.3 将key和value存储到Redis
jedis.set(byteKey,byteValue);
//------------------------------------------------
//3. 释放资源
jedis.close();
}
// 获取对象 - 以byte[]形式在Redis中获取
@Test
public void getByteArray(){
//1. 连接Redis服务
Jedis jedis = new Jedis("localhost",6379);
//------------------------------------------------
//2.1 准备key
String key = "user";
//2.2 将key转换为byte[]
byte[] byteKey = SerializationUtils.serialize(key);
//2.3 jedis去Redis中获取value
byte[] value = jedis.get(byteKey);
//2.4 将value反序列化为User对象
User user = (User) SerializationUtils.deserialize(value);
//2.5 输出
System.out.println("user:" + user);
//------------------------------------------------
//3. 释放资源
jedis.close();
}
}
5.3 Jedis存储一个对象到Redis以String的形式
5.3.1 导入依赖
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
5.3.2 测试
public class Demo3 {
// 存储对象 - 以String形式存储
@Test
public void setString(){
//1. 连接Redis
Jedis jedis = new Jedis("localhost",6379);
//2. 操作Redis - 因为Redis的命令是什么,Jedis的方法就是什么
String key = "user1";
User user = new User(1, "李四", new Date());
String value = new Gson().toJson(user);
jedis.set(key, value);
//3. 释放资源
jedis.close();
}
// 获取对象 - 以String形式获取
@Test
public void getString(){
//1. 连接Redis
Jedis jedis = new Jedis("localhost",6379);
//2. 操作Redis - 因为Redis的命令是什么,Jedis的方法就是什么
String key = "user1";
String s = jedis.get(key);
User user = new Gson().fromJson(s, User.class);
System.out.println(user);
//3. 释放资源
jedis.close();
}
}
5.4 Jedis连接池的操作
使用连接池操作Redis,避免频繁创建和销毁链接对象消耗资源
@Test
public void pool2(){
//1. 创建连接池配置信息
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(100); // 连接池中最大的活跃数
poolConfig.setMaxIdle(10); // 最大空闲数
poolConfig.setMinIdle(5); // 最小空闲数
poolConfig.setMaxWaitMillis(3000); // 当连接池空了之后,多久没获取到Jedis对象,就超时
//2. 创建连接池
JedisPool pool = new JedisPool(poolConfig,"localhost",6379);
//3. 通过连接池获取jedis对象
Jedis jedis = pool.getResource();
//4. 操作
String value = jedis.get("user");
System.out.println("user:" + value);
//5. 释放资源
jedis.close();
}
5.5 SSM框架基于Redis实现缓存
在之前的回购项目的基础上进行修改:
添加redis需要的包,注意之前应该已经导入了json的包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.18.RELEASE</version>
</dependency>
新增配置文件:
redis.properties Redis数据库的配置文件
#访问地址
redis.host=localhost
#访问端口
redis.port=6379
#注意,如果没有password,此处不设置值,但这一项要保留
redis.password=123456
#最大空闲数,数据库连接的最大空闲时间。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制。
redis.maxIdle=300
#连接池的最大数据库连接数。设为0表示无限制
redis.maxActive=600
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
redis.maxWait=1000
#在borrow一个jedis实例时,是否提前进行alidate操作;如果为true,则得到的jedis实例均是可用的;
redis.testOnBorrow=true
在原spring.xml中添加redis的配置
<!--此处修改为读取多个文件-->
<context:property-placeholder location="classpath:*.properties"></context:property-placeholder>
<bean id="jedisConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxActive}"></property>
<property name="maxIdle" value="${redis.maxIdle}"></property>
<property name="maxWaitMillis" value="${redis.maxWait}"></property>
<property name="testOnBorrow" value="${redis.testOnBorrow}"></property>
</bean>
<!-- 2、redis连接工厂 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}"></property>
<property name="port" value="${redis.port}"></property>
<property name="password" value="${redis.password}"></property>
<property name="poolConfig" ref="jedisConfig"></property>
</bean>
<!-- 3、redis操作模板,这里采用尽量面向对象的模板 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="keySerializer" >
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer" >
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
</property>
<!--开启事务-->
<property name="enableTransactionSupport" value="true"/>
</bean>
1.2.3 java代码
@Service
public class RoleService {
@Resource
private RoleDAO roleDAO;
@Resource
private RedisTemplate<String, String> redisTemplate;
public List<Roles> findAll(){
return roleDAO.findAll();
}
public List<AuthTreeNode> findAllCheckedModule(String roleCode)throws Exception{
// 查询缓存中是否存在该内容
// 得到Json处理的对象
ObjectMapper mapper = new ObjectMapper();
// 得到字符串类型的操作方式
BoundValueOperations<String, String> operations = redisTemplate.boundValueOps("wxb_find_all_auth_tree");
// 获取缓存数据
String s = operations.get();
List<AuthTreeNode> treeNodeList = null;
if (s == null || s.trim().length() == 0){
System.out.println("===============在数据库中获取==================");
// 查询数据库得到所有权限
treeNodeList = roleDAO.findAllModule("0101");
// 将数据保存到redis缓存中
if (treeNodeList != null && treeNodeList.size() > 0){
String value = mapper.writeValueAsString(treeNodeList);
operations.set(value);
}
}else{
System.out.println("===============redis中获取==================");
// 将缓存中的字符串结果转换成集合对象
JavaType javaType = mapper.getTypeFactory().constructParametricType(
List.class, AuthTreeNode.class);
treeNodeList = mapper.readValue(s, javaType);
}
// 得到选择角色的权限code
List<String> strings = roleDAO.findAllModuleCodeByRoleCode(roleCode);
// 判断当前所有的树节点中哪些节点是该角色拥有的权限节点
for (AuthTreeNode authTreeNode : treeNodeList) {
List<AuthTreeNode> children = authTreeNode.getChildren();
if (children != null && children.size() > 0){
for (AuthTreeNode child : children) {
if (strings.contains(child.getId())){
child.setChecked(true);
}
}
}
}
return treeNodeList;
}
public void updateAuth(TreeModuleVo vo){
// 删除原来的数据
roleDAO.deleteAllFuncByRoleId(vo.getRoleId());
// 添加现有的权限数据
roleDAO.saveAllFunc(vo);
}
}
测试结果缓存是否起作用
六、Redis其他配置及集群【重点
】
6.1 Redis的AUTH
方式一:通过修改Redis的配置文件,实现Redis的密码校验
# redis.conf
requirepass 密码
三种客户端的连接方式
redis-cli:在输入正常命令之前,先输入auth 密码即可。
图形化界面:在连接Redis的信息中添加上验证的密码。
Jedis客户端:
- jedis.auth(password);
使用JedisPool的方式
// 使用当前有参构造设置密码
public JedisPool(final GenericObjectPoolConfig poolConfig, final String host, int port,int timeout, final String password)
方式二:在不修改redis.conf文件的前提下,在第一次链接Redis时,输入命令:Config set requirepass 密码
后续向再次操作Redis时,需要先AUTH做一下校验。
6.2 Redis的事务
Redis的事务:一次事务操作,该成功的成功,该失败的失败。
先开启事务,执行一些列的命令,但是命令不会立即执行,会被放在一个队列中,如果你执行事务,那么这个队列中的命令全部执行,如果取消了事务,一个队列中的命令全部作废。
- 开启事务:multi
- 输入要执行的命令:被放入到一个队列中
- 执行事务:exec
- 取消事务:discard
Redis的事务向发挥功能,需要配置watch监听机制
在开启事务之前,先通过watch命令去监听一个或多个key,在开启事务之后,如果有其他客户端修改了我监听的key,事务会自动取消。
如果执行了事务,或者取消了事务,watch监听自动消除,一般不需要手动执行unwatch。
注意:redis事务不支持回滚:
Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。鉴于没有任何机制能避免程序员自己造成的错误, 并且这类错误通常不会在生产环境中出现, 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。
6.3 Redis持久化、复制、哨兵和集群介绍
Redis持久化、复制、哨兵和集群,其主要作用和解决的问题是:
- 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
- 复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
- 哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。
- 集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。
6.4 Redis持久化
6.3.1 RDB
RDB(redis database)是Redis默认的持久化机制
RDB持久化文件,速度比较快,而且存储的是一个二进制的文件,传输起来很方便。
RDB持久化的时机:
save 900 1:在900秒内,有1个key改变了,就执行RDB持久化。
save 300 10:在300秒内,有10个key改变了,就执行RDB持久化。
save 60 10000:在60秒内,有10000个key改变了,就执行RDB持久化。
RDB无法保证数据的绝对安全。
6.3.2 AOF
AOF(append of file)持久化机制默认是关闭的,Redis官方推荐同时开启RDB和AOF持久化,更安全,避免数据丢失。
AOF持久化的速度,相对RDB较慢的,存储的是一个文本文件,到了后期文件会比较大,传输困难。
AOF持久化时机。
appendfsync always:每执行一个写操作,立即持久化到AOF文件中,性能比较低。
appendfsync everysec:每秒执行一次持久化。
appendfsync no:会根据你的操作系统不同,环境的不同,在一定时间内执行一次持久化。AOF相对RDB更安全,推荐同时开启AOF和RDB。
6.3.3 注意事项
同时开启RDB和AOF的注意事项:
如果同时开启了AOF和RDB持久化,那么在Redis宕机重启之后,需要加载一个持久化文件,优先选择AOF文件。
如果先开启了RDB,再次开启AOF,如果RDB执行了持久化,那么RDB文件中的内容会被AOF覆盖掉。
6.5 Redis的主从架构
单机版 Redis存在读写瓶颈的问题
主从架构 |
---|
复制3个并修改redis.conf文件
# 一主多从
# 结构
master 6379
slave 6380 -> 6379
slave 6381 -> 6379
# 主节点 master 6379 复制redis.conf为redis6379.conf
port 6379
protected-mode no
daemonize yes
logfile "6379.log"
dbfilename "dump-6379.rdb"
# 从节点6380 复制redis.conf为redis6380.conf
port 6380
protected-mode no
daemonize yes
slaveof localhost 6379
logfile "6380.log"
dbfilename "dump-6380.rdb"
# 从节点6381 复制redis.conf为redis6381.conf
port 6381
protected-mode no
daemonize yes
slaveof localhost 6379
logfile "6381.log"
dbfilename "dump-6381.rdb"
#依次启动3台redis
./redis-server conf/redis6379.conf
./redis-server conf/redis6380.conf
./redis-server conf/redis6381.conf
# 查看进程是否启动
ps -ef | grep redis
# 登录主节点
./redis-cli -p 6379
# 查看结构
127.0.0.1:6379> info Replication
6.6 哨兵
哨兵可以帮助我们解决主从架构中的单点故障问题
添加哨兵 |
---|
复制并修改sentinel.conf 准备哨兵的配置文件,并且在容器内部手动启动哨兵即可
# sentinel-26379.conf
port 26379
# 哨兵需要后台启动
daemonize yes
# 指定Master节点的ip和端口(主)
sentinel monitor mymaster localhost 6379 2
# 指定Master节点的ip和端口(从)
# sentinel monitor master master 6379 2
# 哨兵每隔多久监听一次redis架构
# sentinel down-after-milliseconds mymaster 10000
logfile "26379.log"
依次启动3台sentinel即可
./redis-sentinel conf/sentinel26379.conf
./redis-sentinel conf/sentinel26380.conf
./redis-sentinel conf/sentinel26381.conf
# 查看进程是否启动
ps -ef | grep redis
# 登录哨兵
./redis-cli -p 26379
# 查看结构
127.0.0.1:26379> info sentinel
演示故障转移
# 停止redis的master节点6379
kill -9 pid
# 登录哨兵查看是否选举了原来的从节点变成主节点
./redis-cli -p 26379
127.0.0.1:26379> info sentinel
# 登录现在的主节点,查看结构
./redis-cli -p 6380
127.0.0.1:6380> info Replication
# 再次启动6379节点
./redis-server conf/redis6379.conf
# 登录现在的主节点,查看结构,看看原来的6379主节点是否变成从节点
./redis-cli -p 6380
127.0.0.1:6380> info Replication
6.7 Redis的集群
先登录之前任意一个redis,使用info server命令查看redis_mode,发现都是standalone,接下来配置集群。
Redis集群在保证主从加哨兵的基本功能之外,还能够提升Redis存储数据的能力。
Redis集群架构图 |
---|
6.7.1 集群配置文件修改和启动
先清理一下原来的redis中的内容,flushall。
配置6个文件,端口号分别为主7000/7001/7002,从8000/8001/8002
# 以redis7000.conf为例
# 指定redis的端口号
port 7000
protected-mode no
# 日志文件
logfile "log-7000.log"
# 数据库文件
dbfilename "dump-7000.rdb"
# 后台执行
daemonize yes
# 开启Redis集群
cluster-enabled yes
# 集群信息的文件
cluster-config-file nodes-7000.conf
# 集群的对外ip地址
#cluster-announce-ip 192.168.199.109
# 集群的对外port
#cluster-announce-port 7000
# 集群的总线端口
#cluster-announce-bus-port 17000
分别启动了6个Redis的节点。
./redis-server conf/redis7000.conf
./redis-server conf/redis7001conf
./redis-server conf/redis7002.conf
./redis-server conf/redis8000.conf
./redis-server conf/redis8001.conf
./redis-server conf/redis8002.conf
使用./redis-cli -p 7000登录后使用info server命令,查看redis_mode,已经显示是cluster,说明已经是集群
6.7.2 节点握手
登录后使用cluster nodes 命令查看,发现只显示当前一台redis,说明6台redis之间没有任何关联。节点启动以后是相互独立的,并不知道其他节点存在;需要进行节点握手,将独立的节点组成一个网络。节点握手使用cluster meet {ip} {port}命令实现。
127.0.0.1:7000> cluster meet 127.0.0.1 7001 # 注意,不能使用localhost,应该使用ip,此处只是单机测试,所以使用127.0.0.1
127.0.0.1:7000> cluster meet 127.0.0.1 7002
127.0.0.1:7000> cluster meet 127.0.0.1 8000
127.0.0.1:7000> cluster meet 127.0.0.1 8001
127.0.0.1:7000> cluster meet 127.0.0.1 8002
只需要7000与其他的全部握手后,所有的redis之间就关联了。再次cluster nodes查看,发现显示了所有6台redis。
6.7.3 分配槽
在Redis集群中,借助槽实现数据分区。集群有16384个槽,槽是数据管理和迁移的基本单位。当数据库中的16384个槽都分配了节点时,集群处于上线状态(ok);如果有任意一个槽没有分配节点,则集群处于下线状态(fail)。使用cluster info命令查看。
127.0.0.1:7000> cluster info
会发现cluster_state会显示fail。说明处于下线状态,接下来配置槽(注意只需要主机配置槽即可,从机不用分配)
redis-cli -p 7000 cluster addslots {0..5461}
redis-cli -p 7001 cluster addslots {5462..10922}
redis-cli -p 7002 cluster addslots {10923..16383}
再使用cluster info命令查看,会发现cluster_state显示ok,说明配置成功。
6.7.4 指定主从关系
集群中指定主从关系不再使用slaveof命令,而是使用cluster replicate命令;参数使用节点id。
通过cluster nodes获得几个主节点的节点id后,执行下面的命令为每个从节点指定主节点:
redis-cli -p 8000 cluster replicate be816eba968bc16c884b963d768c945e86ac51ae
redis-cli -p 8001 cluster replicate 788b361563acb175ce8232569347812a12f1fdb4
redis-cli -p 8002 cluster replicate a26f1624a3da3e5197dde267de683d61bb2dcbf1
此时执行cluster nodes查看各个节点的状态,可以看到主从关系已经建立。
集群搭建完毕。
七、Redis常见问题【重点
】
7.1 key的生存时间到了,Redis会立即删除吗?
不会立即删除。
定期删除:Redis每隔一段时间就去会去查看Redis设置了过期时间的key,会再100ms的间隔中默认查看3个key。
惰性删除:如果当你去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,是否已经到了,直接删除当前key,并且给用户返回一个空值。
7.2 Redis的淘汰机制
标签:常用,key,Redis,redis,value,jedis,conf,安装 From: https://www.cnblogs.com/Fanxc/p/18008519在Redis内存已经满的时候,添加了一个新的数据,执行淘汰机制。
- volatile-lru:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少使用的key。
- allkeys-lru:在内存不足时,Redis会再全部的key中干掉一个最近最少使用的key。
- volatile-lfu:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少频次使用的key。
- allkeys-lfu:在内存不足时,Redis会再全部的key中干掉一个最近最少频次使用的key。
- volatile-random:在内存不足时,Redis会再设置过了生存时间的key中随机干掉一个。
- allkeys-random:在内存不足时,Redis会再全部的key中随机干掉一个。
- volatile-ttl:在内存不足时,Redis会再设置过了生存时间的key中干掉一个剩余生存时间最少的key。
- noeviction:(默认)在内存不足时,直接报错。