Redis
NoSQL的简介
什么是NoSQL
NoSQL 是 Not Only SQL 的缩写,意即"不仅仅是SQL"的意思,泛指非关系型的数据库。强调Key-Value Stores和文档数据库的优点。
NoSQL产品是传统关系型数据库的功能阉割版本,通过减少用不到或很少用的功能,来大幅度提高产品性能。
NoSQL数据库类型
① 键值(Key-Value)数据库 [Redis/Memcached]
适用场景:
储存用户信息,比如会话、配置文件、参数、购物车等等。这些信息一般都和ID(键)挂钩,这种情景下键值数据库是个很好的选择。
不适用场景:
1.取代通过键查询,而是通过值来查询。Key-Value数据库中根本没有通过值查询的途径
2.需要储存数据之间的关系。在Key-Value数据库中不能通过两个或以上的键来关联数据。
3.事务的支持。在Key-Value数据库中故障产生时不可以进行回滚。
② 面向文档(Document-Oriented)数据库 [MongoDB]
数据可以使用XML、JSON或者JSONB等多种形式存储。
适用场景:1.日志 2.分析
不适用场景:不支持事务
③ 列存储(Wide Column Store/Column-Family)数据库 [HBASE]
列存储数据库将数据储存在列族(column family)中,一个列族存储经常被一起查询的相关数据。举个例子,如果我们有一个Person类,我们通常会一起查询他们的姓名和年龄而不是薪资。这种情况下,姓名和年龄就会被放入一个列族中,而薪资则在另一个列族中。
适用场景:1.日志 2.博客平台,我们储存每个信息到不同的列族中。举个例子,标签可以储存在一个,类别可以在一个,而文章则在另一个。
不适用场景:1.ACID事务 2.原型设计。在模型设计之初,我们根本不可能去预测它的查询方式,而一旦查询方式改变,我们就必须重新设计列族。
④ 图(Graph-Oriented)数据库 [Neo4J]
适用范围很小,主要用用网络拓扑分析 如脉脉的人员关系图等
传统RDBMS VS NOSQL
RDBMS
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
NoSQL
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键 - 值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID【原子,一致,隔离,持久】属性
- 非结构化和不可预知的数据
- CAP定理【一致性,可用性,容错性】
- 高性能,高可用性和可伸缩性
Redis
Redis简介
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI C语言编写的key-value存储系统(区别于MySQL的二维表格的形式存储。)。和Memcache类似,但很大程度补偿了Memcache的不足。和Memcache一样,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失。所以Memcache的应用场景适用于缓存无需持久化的数据。而Redis不同的是它会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据的持久化。
Redis的特点
-
Redis读取的速度是110000次/s,写的速度是81000次/s
-
原子性。Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
-
支持多种数据结构:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)
-
持久化,主从复制(集群)
-
支持过期时间,支持事务,消息订阅。
-
官方不支持window,但是又第三方版本。
Redis的应用场景
- 数据缓存(提高访问性能)
将一些数据在短时间之内不会发生变化,而且它们还要被频繁访问,为了提高用户的请求速度和降低网站的负载,降低数据库的读写次数,就把这些数据放到缓存中。 - 会话缓存
(session cache,保存web会话信息) - 排行榜/计数器
(nginx + lua + redis计数器进行IP自动封禁) - 消息队列
(构建实时消息系统,聊天,群聊)
Redis的安装、启动及停止
下载地址:http://redis.io/download ,这里以【redis-4.0.14.tar.gz】为例
下载完成后,把压缩包copy到centos系统下的【/root/software】下面。
开始安装
1. 安装gcc 目地是编译软件
yum install gcc-c++
2. 解压
tar -zxvf redis-4.0.14.tar.gz
3. 把解压的文件copy到/usr/local/src里面
cp -r /root/software/redis-4.0.14 /usr/local/src/redis
4. 打开 /usr/local/src/redis/deps 进行编译依赖项
cd /usr/local/src/redis/deps
make hiredis lua jemalloc linenoise
5. 打开 /usr/local/src/redis 进行编译
cd /usr/local/src/redis
make
6. 在上面的Redis目录安装把它安装到 /usr/local/redis 里面
mkdir /usr/local/redis
make install PREFIX=/usr/local/redis
7. 验证安装是否成功
cd /usr/local/redis/bin
ls
看到如下启动文件就可以了
使用which命令查看系统里面是否有redis的服务
which redis-server
显示为没有
8. 把配置文件移动到/usr/myredis目录(目录可以自定义])
使用which命令查看系统里面是否有redis的服务
mkdir /usr/myredis
cp /usr/local/src/redis/redis.conf /usr/myredis
9. 启动Redis
cd /usr/local/redis/bin
./redis-server /usr/myredis/redis.conf
10. 默认情况,Redis不是在后台运行,我们需要把redis放在后台运行
vim /usr/myredis/redis.conf
11. 再次启动查看进程
./redis-server /usr/myredis/redis.conf
#查看进程
ps -ef|grep redis
可以看到在6379端口号已启动了redis
修改redis密码:
config set requirepass '123456'
12. 客户端链接和退出
cd /usr/local/redis/bin #进入到redis中的bin目录下
#连接
./redis-cli 默认是-h 127.0.0.1 -p 6379
#退出
quit
ping
PONG
13. 停止redis
cd /usr/local/redis/bin
./redis-cli shutdown
#或者
pkill redis-server
#再次查看进程
ps -ef|grep redis
14. 开机自启Redis的配置
vim /etc/rc.local
加入
/usr/local/redis/bin/redis-server /usr/myredis/redis-conf
bin目录的文件说明
redis-benchmark:redis性能测试工具
redis-check-aof:检查aof日志的工具
redis-check-dump:检查rdb日志的工具
redis-cli:连接用的客户端
redis-server:redis服务进程
redis配置
daemonize 如需要在后台运行,把该项的值改为yes
pdifile 把pid文件放在/var/run/redis.pid,可以配置到其他地址
bind 指定redis只接收来自该IP的请求,如果不设置,那么将处理所有请求,在生产环节中最好设置该项
port 监听端口,默认为6379
timeout 设置客户端连接时的超时时间,单位为秒
loglevel 等级分为4级,debug,revbose,notice和warning。生产环境下一般开启notice
logfile 配置log文件地址,默认使用标准输出,即打印在命令行终端的端口上
database 设置数据库的个数,默认使用的数据库是0
save 设置redis进行数据库镜像的频率
rdbcompression 在进行镜像备份时,是否进行压缩
dbfilename 镜像备份文件的文件名
dir 数据库镜像备份的文件放置的路径
slaveof 设置该数据库为其他数据库的从数据库
masterauth 当主数据库连接需要密码验证时,在这里设定
requirepass 设置客户端连接后进行任何其他指定前需要使用的密码
maxclients 限制同时连接的客户端数量
maxmemory 设置redis能够使用的最大内存
appendonly 开启appendonly模式后,redis会把每一次所接收到的写操作都追加到appendonly.aof文件中,当redis重新启动时,会从该文件恢复出之前的状态
appendfsync 设置appendonly.aof文件进行同步的频率
vm_enabled 是否开启虚拟内存支持
vm_swap_file 设置虚拟内存的交换文件的路径
vm_max_momery 设置开启虚拟内存后,redis将使用的最大物理内存的大小,默认为0
vm_page_size 设置虚拟内存页的大小
vm_pages 设置交换文件的总的page数量
vm_max_thrrads 设置vm IO同时使用的线程数量
Redis 数据类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
1. 字符串类型Map<String,String>
字符串类型是编程语言和应用程序中最常见和最有用的数据类型,也是Redis的基本数据类型之一,事实上,Redis中所有键都必须是字符串。
2. list数据类型Map<String,List<Object>
>
列表是应用我只是应该程序开发中非常有用的数据类型之一,列表能存在一组对象,因此它也可以被用于栈或者队列,在Redis中,与键相关的联的值可以是字符串组成的列表,Redis中的列表更像是数据结构中的双向链表。
3. hash数据类型Map<String,Map<Object,Object>>
哈希表示字段和值之间的映射关系,与JAVA中的Map类似,Redis数据集本身就可以看做一个哈希,其中字符串类型的键关联到如字符串和列表之类的数据对象,而Reidis的数据对象也可以再次使用哈希,其字段和值必须 是字符串。
4. set数据类型Map<String,Set<Object,Object>>
集合类型是由唯一,无序对象组成的集合(collection).它经常用于测试某个成员是集合中,重复项删除和集合运算(求并,交,差集),Redis的值对象可以是字符串集合。
5. zset(sortset)数据类型
有序集合是一个类似于set但是更复杂的数据类型,单词sorted意为着这种集合中的每个元素都有一个可用于排序的权重,并且我们可以按顺序从集合中得到元素在某些需要一个保持数据有序的场景中,使用这种原生的序的特性是很方便的。
redis相关命令及详解
redis命令查询网站:http://www.redis.net.cn/order/ 或者:http://redisdoc.com/
Redis 服务器 命令
命令 | 介绍 |
---|---|
Flushdb | 删除当前数据库的所有key |
Flushal l | 删除所有数据库的所有key |
Dbsize | 返回当前数据库的 key 的数量 |
Info | 获取 Redis 服务器的各种信息和统计数值 |
Slaveof ip port | 主从切换 |
Redis 连接 命令
命令 | 介绍 |
---|---|
Echo | 打印字符串 例:ECHO "Hello World" |
Select index | 切换到指定的数据库 |
Ping | 查看服务是否运行 |
Quit | 关闭当前连接 |
Auth | 验证密码是否正确 |
Redis 键(key) 命令
命令 | 介绍 |
---|---|
Type key | 返回 key 所储存的值的类型 |
Rename key newkey | 修改 key 的名称 |
TTL key | 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live) -1代表永不过期 |
Pttl key | 返回 key 的剩余的过期时间(单位:毫秒) |
Expire key seconds | 为给定 key 设置过期时间(单位:秒) |
PERSIST key | 移除 key 的过期时间,key 将持久保持 |
Keys pattern | 如key * 查找所有符合给定模式( pattern)的 key |
Move key indexdb | 将当前数据库的 key 移动到给定的数据库 db 当中 |
RANDOMKEY | 从当前数据库中随机返回一个 key |
DEL key | 用于在 key 存在是删除 key |
EXISTS key | 检查给定 key 是否存在 1 :true 0:false |
Redis 字符串(String) 命令
命令 | 介绍 |
---|---|
SET key value | 设置指定 key 的值。 |
Get key | 获取指定 key 的值。 |
Getset key newValue | 修改指定key的值(value),并返回key的旧值。 |
Mset | 同时设置一个或多个 key-value 对。 例:MSET key1 "Hello" key2 "World" |
Mget | 获取所有(一个或多个)给定 key 的值。 例:MGET key1 key2 |
Getrange | 返回 key 中字符串值的子字符。 |-- Getrange key 0 -1 取出所有 |-- Getrange key 0 10 取出key 的第一个到第11元素 |
Setrange | 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 |-- setrange key 0 value 代表从第一个字符开始替换 |
Incr | 将 key 中储存的数字值增一。 例:set num 1 INCR num |
Decr | 将 key 中储存的数字值减一。 例:DECR num |
Incrby | 将 key 所储存的值加上给定的增量值(步长)。 例:INCEBY num 10 |
Incrbyfloat | 将 key 所储存的值加上给定的浮点增量值(步长)。 例:INCRBYFLOAT num 2.5 |
Decrby | 将 key 所储存的值减去给定的减量值(步长)。 例:DECRBY num 6 |-- value 必须为整数 |
Strlen | 返回 key 所储存的字符串值的长度。 |
Msetnx | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 |
Setnx | 只有在 key 不存在时设置 key 的值。 |
Append | 如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。 |
objectEncoding key | 得到key 的类型 string里面有三种编码: int 用于能够副作用64位有符号整数表示的字符串 embstr 用于长度小于或等于44字节 Redis3.x中是39字节,这种类型的编码在内存使用时性能更好 raw 用于长度大于44字节的 |
Redis 列表(List) 命令
命令 | 介绍 |
---|---|
Lpush | 将一个或多个值插入到列表头部。 例:lpush mylist a b c d |
Rpush | 在列表中从右边添加一个或多个值。 例:rpush mylist a b c d e |
Lrange | 获取列表指定范围内的元素。 例:lrange mylist 0 -1(-1表示所有) |
Lpop | 移除并获取列表的第一个元素。 例:lpop mylist |
Rpop | 移除并获取列表最后一个元素。 例:rpop mylist |
Lindex | 通过索引获取列表中的元素。 例:lindex mylist 0 |
Lrem | 移除列表元素。 例:lrem mylist count value 从左边移除count个value |
Llen | 获取列表长度。 例:llen mylist 返回列表长度 |
Ltrim | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 例:LTRIM mylist 0 2 从左边截取index从 0-2的元素 |
Lpushx | 将一个或多个值插入到已存在的列表头部 |
Rpushx | 为已存在的列表添加值。 例:lpushx mylist a b c d e 如果redis里面没有mylist的key那么这个命令无效 |
Linsert | 在列表的元素前或者后插入元素。 例:LINSERT mylist after/before c bb 在mylist列表里面c元素的后面/前面插入元素 |
Lset | 通过索引设置列表元素的值。 例:lset mylist 0 aaa 把0位置的元素变成aaa |
Rpoplpush | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。 例:RPOPLPUSH srouce des |
Blpop | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 例:blpop mylist 60 (单位:秒) |
Brpop | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 例:brpop mylist 60 (单位:秒) |
Redis 哈希(Hash) 命令
命令 | 介绍 |
---|---|
Hset | 将哈希表 key 中的字段 field 的值设为 value。 例:HSET myhash id 1 向key=myhash 里面放一个map 这个map的key id value 1 |
Hget | 获取存储在哈希表中指定字段的值。 例:hget myhash id |
Hmset | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 例:HMSET myhash address wh sex man |
Hmget | 获取所有给定字段的值。 例:HMGET myhash id name address sex age |
Hgetall | 获取在哈希表中指定 key 的所有字段和值。 例:HGETALL myhash |
Hexists | 查看哈希表 key 中,指定的字段是否存在。(true=1,false=0) 例:HEXISTS myhsh id |
Hincrby | 为哈希表 key 中的指定字段的整数值加上增量 increment 。 例:HINCRBY myhash age 20 |
Hlen | 获取哈希表中字段的数量。 例:HLEN myhash |
Hdel | 删除一个或多个哈希表字段。 例:HDEL myhash age |
Hincrbyfloat | 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 例:HINCRBYFLOAT myhash age 0.5 |
Hvals | 获取哈希表中所有值。 例:HVALS myhash |
Hkeys | 获取所有哈希表中的字段。 例: HKEYS myhash |
Hsetnx | 只有在字段 field 不存在时,设置哈希表字段的值。 |
Redis 集合(Set) 命令
命令 | 介绍 |
---|---|
Sadd | 向集合添加一个或多个成员。 例:SADD myset a b c d e f g |
Spop | 移除并返回集合中的一个随机元素。 例:SPOP myset |
Scard | 获取集合的成员数。 |
Srandmember | 返回集合中一个或多个随机数。 例:SRANDMEMBER myset -1 |
Smembers | 返回集合中的所有成员。 例:SMEMBERS myset |
Srem | 移除集合中一个或多个成员。 例:SREM myset a c |
Smove | 将 member 元素从 source 集合移动到 destination 集合。 例:SMOVE myset myset2 c |
Sismember | 判断 member 元素是否是集合 key 的成员。 例:SISMEMBER myset b |
Sinter | 返回给定所有集合的交集。 例:SINTER myset1 myset2 myset3 |
Sinterstore | 返回给定所有集合的交集并存储在 destination 中。 例:SINTERSTORE myset4 |
Sunion | 返回所有给定集合的并集。 例:SUNION myset1 myset2 myset3 |
Sunionstore | 所有给定集合的并集存储在 destination 集合中。 例:SUNIONSTORE myset4 myset1 myset3 myset3 |
Sdiff | 返回给定所有集合的差集。 例:SDIFF myset1 myset2 |
Sdiffstore | 返回给定所有集合的差集并存储在 destination 中。 |
Sscan | 迭代集合中的元素。 例:SSCAN myset 0 match d* |
Redis 有序集合(sorted set) 命令
命令 | 介绍 |
---|---|
Zadd | 向有序集合添加一个或多个成员,或者更新已存在成员的分数。 例:ZADD myzset 1 zhangsan |
Zrange | 通过索引区间返回有序集合成指定区间内的成员。 例:ZRANGE myzset 0 1 |
Zcount | 计算在有序集合中指定区间分数的成员数。 例:ZCOUNT myzset 0 3 |
Zrevrange | 返回有序集中指定区间内的成员,通过索引,分数从高到底。 例:ZREVRANGE myzset 0 |
Zcard | 获取有序集合的成员数。 例:ZCARD myzset |
Zscore | 返回有序集中,成员的分数值。 例:ZSCORE myzset lisi |
Zrem | 移除有序集合中的一个或多个成员。 例:ZREM myzset lisi |
Zrank | 返回有序集合中指定成员的索引。 例:ZRANK myzent zhangsan |
Zincrby | 有序集合中对指定成员的分数加上增量 increment。 例:ZINCRBY myzset 10 zhaoliu |
Zrangebyscore | 通过分数返回有序集合指定区间内的成员。 例:ZRANGEBYSCORE myzset 0 10 |
Redis配置文件解析
Redis配置文件
Units 单位
-
配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit。
-
对大小写不敏感。
INCLUDES 包含
和spring配置文件类似,可以通过includes包含,redis.conf可以作为总闸,包含其他。
NETWORK 通用
默认情况下,如果没有指定“bind”配置指令,则Redis侦听用于连接服务器上所有可用的网络接口。
可以只监听一个或多个选择的接口 "bind"配置指令,后面跟着一个或多个IP地址。
-
bind
默认情况下,redis 在 server 上所有有效的网络接口上监听客户端连接。如果只想让它在一个或多个网络接口上监听,那你就绑定一个IP或者多个IP。多个ip空格分隔即可。
- prot运行端口
-
Tcp-backlog
设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。
在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值来达到想要的效果
- timeout
当客户端闲置多少秒后关闭连接,如果设置为0表示关闭该功能。
-
tcp-keepalive
单位:秒,表示将周期性的使用SO_KEEPALIVE检测客户端是否还处于健康状态,避免服务器一直阻塞,官方给出的建议值是300S,但建议设置成60。
GENERAL 通用
daemonize no :是否以守护模式启动,默认为no,配置为yes时以守护模式启动,这时redis instance会将进程号pid写入默认文件/var/run/redis.pid。
supervised no :可以通过upstart和systemd管理Redis守护进程,这个参数是和具体的操作系统相关的。
pidfile /var/run/redis_6379.pid :配置pid文件路径。当redis以守护模式启动时,如果没有配置pidfile,pidfile默认值是/var/run/redis.pid 。
loglevel notice :日志级别。可选项有:debug(记录大量日志信息,适用于开发、测试阶段); verbose(较多日志信息); notice(适量日志信息,使用于生产环境);warning(仅有部分重要、关键信息才会被记录)。
logfile "" :日志文件的位置,当指定为空字符串时,为标准输出,如果redis已守护进程模式运行,那么日志将会输出到 /dev/null 。
syslog-enabled no :是否把日志记录到系统日志。
syslog-ident : 设置系统日志的id 如 syslog-ident redis
databases 16 :设置数据库的数目。默认的数据库是DB 0 ,可以在每个连接上使用select
always-show-logo yes :是否一直显示日志
SNAPSHOTTING 快照
save 保存数据到磁盘。格式是:save
如: save 900 1 900秒有1条数据改变就保存
save 300 10 300秒有10条数据改变就保存
save 60 10000 600秒有10000条数据改变就保存
stop-writes-on-bgsave-error yes 默认情况下,如果 redis 最后一次的后台保存失败,redis 将停止接受写操作,这样以一种强硬的方式让用户知道数据不能正确的持久化到磁盘, 否则就会没人注意到灾难的发生。 如果后台保存进程重新启动工作了,redis 也将自动的允许写操作。然而你要是安装了靠谱的监控,你可能不希望 redis 这样做,那你就改成 no 好了。
rdbcompression yes 是否在dump .rdb数据库的时候压缩字符串,默认设置为yes。如果你想节约一些cpu资源的话,可以把它设置为no,这样的话数据集就可能会比较大。
rdbchecksum yes 是否CRC64校验rdb文件,会有一定的性能损失(大概10%)
dbfilename dump.rdb rdb文件的名字。
dir ./ 数据文件保存路径指redis.conf配置文件所在的路径。
Clients 客户端配置
SECURITY 密码配置
默认是没有密码的,设置密码需要放开注释。
LIMITS 限制
maxclients 10000
设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。当你无法设置进程文件句柄限制时,redis会设置为当前的文件句柄限制值减去32,因为redis会为自身内部处理逻辑留一些句柄出来。如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
maxmemory
设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素
maxmemory-policy
(1)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key,这种情况一般是把redis既当做缓存,又做持久化存储的时候才使用。
(2)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key,不推荐。
(3)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key,推荐使用。
(4)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,不推荐使用。
(5)volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key。
(6)noeviction:当内存不足以容纳新写入数据时,新的写入操作会报错,基本不用。
maxmemory-samples
设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。
常见配置 redis.conf 介绍
前20个是最长用的哦!!!
redis.conf 配置项参数说明:
1,daemonize no
Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程。
2,pidfile /var/run/redis.pid
当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定。
3,port 6379
指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字。
4,bind 127.0.0.1
绑定的主机地址。
5,timeout 300
当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能。
6,loglevel notice
指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice。
7,logfile stdout
日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null。
8,databases 16
设置数据库的数量,默认数据库为0,可以使用SELECT
9,save <seconds>
<changes>
指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合。
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
10,rdbcompression yes
指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大。
11,dbfilename dump.rdb
指定本地数据库文件名,默认值为dump.rdb。
12,dir ./
指定本地数据库存放目录。
13,slaveof <masterip>
<masterport>
设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步。
14,masterauth <master-password>
当master服务设置了密码保护时,slav服务连接master的密码。
15,requirepass foobared
设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH
16,maxclients 128
设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息。
17,maxmemory <bytes>
指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区。
18,appendonly no
指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no。
19,appendfilename appendonly.aof
指定更新日志文件名,默认为appendonly.aof。
20,appendfsync everysec
指定更新日志条件,共有3个可选值:
**no**:表示等操作系统进行数据缓存同步到磁盘(快) 。
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) 。
**everysec**:表示每秒同步一次(折衷,默认值)。
21,vm-enabled no
指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)。
22,vm-swap-file /tmp/redis.swap
虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享。
23, vm-max-memory 0
将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0。
24,vm-page-size 32
Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值。
25,vm-pages 134217728
设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
26,vm-max-threads 4
设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4。
27,glueoutputbuf yes
设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启。
28,hash-max-zipmap-entries 64 hash-max-zipmap-value 512
指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法。
29,activerehashing yes
指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)。
30,include /path/to/local.conf
指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件。
Redis的持久化
redis官方持久化网址:https://redis.io/topics/persistence
什么是FORK
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
RDB保存位置及配置位置
Rdb 保存的是dump.rdb文件
推荐博文:https://www.cnblogs.com/ysocean/p/9114268.html
优点
-
适合大规模的数据恢复
-
对数据完整性和一致性要求不高
缺点
- 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改
- Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
AOF
原理
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
保存位置及位置配置
Aof保存的是appendonly.aof文件
AOF启动/修复/恢复
正常恢复
启动:设置Yes 修改默认的appendonly no,改为yes
将有数据的aof文件复制一份保存到对应目录(config get dir)
恢复:重启redis然后重新加载
异常恢复
启动:设置Yes 修改默认的appendonly no,改为yes
备份被写坏的AOF文件
修复: Redis-check-aof --fix 进行修复
恢复:重启redis然后重新加载
优势
每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好
每秒同步:appendfsync everysec 异步操作,每秒记录 如果一秒内宕机,有数据丢失
不同步:appendfsync no 从不同步
劣势
相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
Aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
用到的命令
Config get dir 得到redis的启动路径 也就是rda aof存储路径
Config set dir “” 设置redis的启动路径 也就是rda aof存储路径
Config get requirepass 得到密码
Config set requirepass “123456” 设置密码
Config set requirepass “” 取消密码
Redis的事务
什么是Redis事务
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。
**官网说明:**https://redis.io/topics/transactions
一个队列中,一次性、顺序性、排它性的执行一系列命令。
常用命令
情况1:正常执行
情况2:放弃事务
情况3:全体连坐【语法错】
情况4:冤头债主(语法正确,执行报错)
情况5:watch监控
悲观锁/乐观锁
悲观锁(Pessimistic Lock):顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁 select * from sys_user for update
乐观锁(Optimistic Lock):顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,
乐观锁策略:提交版本必须大于记录当前版本才能执行更新。
初始化信用卡可用余额和欠额
无加塞篡改
先监控再开启multi, 保证两笔金额变动在同一个事务内
有加塞篡改
监控了key,如果key被修改了,后面一个事务的执行失效
unwatch
一旦执行了exec之前加的监控锁都会被取消掉了
小结:
Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变, 比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。
通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化, EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。
阶段
**开启:**以MULTI开始一个事务
**入队:**将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
**执行:**由EXEC命令触发事务
特性
单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行, 也就不存在”事务内要看到其它事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题。
不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。
Redis的主从复制(Master/Slave)
行话
行话:也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slave机制,Master以写为主,Slave以读为主。
官方说明:
作用
读写分离
容灾恢复
使用
准备三个redis.conf
redis6379.conf
port 6379
daemonize yes
pidfile /var/run/redis_6379.pid
logfile “redis6379.log”
dbfilename dump6379.rdb
redis6380.conf
port 6380
daemonize yes
pidfile /var/run/redis_6380.pid
logfile “redis6380.log”
dbfilename dump6380.rdb
redis6381.conf
port 6381
daemonize yes
pidfile /var/run/redis_6381.pid
logfile “redis6381.log”
dbfilename dump6381.rdb
命令info replication 查看当前redis的状态
配从不配主
配置命令 【SLAVEOF 主库IP 主库端口】
每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件
Info replication 可以查看
详细操作
①拷贝多个redis.conf文件
②开启daemonize yes
③Pid文件名字
④指定端口
⑤Log文件名字
⑥Dump.rdb名字
见准备工作
进入6380命令:
注意:不加 -h -p 默认会直接进入6379端口。
./redis-cli -h 127.0.0.1 -p 6380
一主二仆
演示问题
-
切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的123是否也可以复制
|--第一次配置主从之后从机会全量复制 主机时面的数据
|---当全量复制完成之后如果主机的数据再发生变化,那么从机就会使用增量复制
-
从机不可写入数据,不可使用set。
-
主机shutdown后情况如何?从机是上位还是原地待命
|-- 原地待命 可以反客为主成为主机。但是其它的从现不能跟着
-
主机又回来了后,主机新增记录,从机还能否顺利复制?
|-- 可以
-
其中一台从机down后情况如何?依照原有它能跟上大部队吗?
|-- 如果在redis.conf里面没有配置主从,那么必须使用slaveof 主机IP 主机端口才能跟上
薪火相传
上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,中途变更转向:会清除之前的数据,重新建立拷贝最新的。
Slaveof 新主库IP 新主库端口。
例:slaveof 127.0.0.1 6380
反客为主
SLAVEOF no one
使当前数据库停止与其他数据库的同步,转成主数据库。
复制的原理
Slave 启动成功连接到master后会发送一个sync命令。
Master 接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步,但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。
以上配置存在问题
因为当主机挂了,从机需要手动反客为主。那么样就是运维24 小时能操作redis ,如果半夜出现主机挂了,所以每出问题都及时并手动去处理,所以不爽
以上的配置是主从复制的手动版
哨兵模式
什么是哨兵模式
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
使用步骤
调整结构,6379带着80、81
自定义的/redis-ms目录下新建sentinel.conf文件,名字绝不能错
配置哨兵,填写内容
sentinel monitor 被监控主机名字(自己起名字) 127.0.0.1 6379 1 如:sentinel monitor hos6379 127.0.0.1 6379 1
上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机
启动哨兵 ./redis-sentinel /root/master-slaver/sentinel.conf
正常主从演示
原有的master挂了
投票新选
重新主从继续开工,info replication查查看
一组sentinel(哨兵)能同时监控多个Master
复制的缺点
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
高可用高并发集群配置
中心化和去中心化
中心化
所有的节点都要有一个主节点
缺点:中心挂了,服务就挂了
中心处理数据的能力有限,不能把节点性能发挥到最大
特点:就是一个路由作用
![](https://img2020.cnblogs.com/blog/1782005/202107/1782005-20210729141500723-1332921411.png)
去中心化
特点:去掉路由,我自己来路由
![](https://img2020.cnblogs.com/blog/1782005/202107/1782005-20210729141516280-1267886703.png)
以上通俗的就是:
中心化:几个经过认证的嘉宾在‘讲话’,所有其他人在听。
去中心化:每个人都可以‘讲话’,每个人都可以选择听或者讲。
Redis集群的执行流程分析
2.1,哈希槽说明
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
当你往Redis Cluster中加入一个Key时,会根据crc16(key) mod 16384计算这个key应该分布到哪个hash slot中,一个hash slot中会有很多key和value。你可以理解成表的分区,使用单节点时的redis时只有一个表,所有的key都放在这个表里;改用Redis Cluster以后会自动为你生成16384个分区表,你insert数据时会根据上面的简单算法来决定你的key应该存在哪个分区,每个分区里有很多key。
2.2,执行流程分析
假如redis集群里面能存放90个key,那么redis集群把90key平分到3个主机
redis对每个主机里面30个存储位置都编号,当应用连接到主机1上面时,应该发送一个写的命令
主机使用crc16算出槽号
如果槽号在1-30 可以直接操作主机1
如果槽号在31-60那么redis会转发到主机2
如果应该再发一个命令set age 22
那么主机2使用crc16再算槽号再转发
Redis集群的搭建(Cluster)
文档
http://redis.cn/topics/cluster-tutorial.html
原理:去中心化
集群规则
机器编号 | ip | port |
---|---|---|
1 | 192.168.18.130 | 7000 |
2 | 192.168.18.130 | 7001 |
3 | 192.168.18.130 | 7002 |
4 | 192.168.18.130 | 7003 |
5 | 192.168.18.130 | 7004 |
6 | 192.168.18.130 | 7005 |
搭建过程
1. 新建文件夹
2. 准备一个服务端程序
3. 准备6个redis的配置文件并修改
修改redis.conf配置文件
Redis-1
bind 0.0.0.0 69行
port 7000 92行
daemonize yes 136行
# 打开aof 持久化
appendonly yes 672行
# 开启集群
cluster-enabled yes 814行
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7000.conf 822行
# 集群的超时时间
cluster-node-timeout 5000 828行
------------------------------------
Redis-2
daemonize yes
bind 0.0.0.0
port 7001
# 打开aof 持久化
appendonly yes
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7001.conf
# 集群的超时时间
cluster-node-timeout 5000
-------------------------------------
Redis-3
daemonize yes
bind 0.0.0.0
port 7002
# 打开aof 持久化
appendonly yes
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7002.conf
# 集群的超时时间
cluster-node-timeout 5000
------------------------------------
Redis-4
daemonize yes
bind 0.0.0.0
port 7003
# 打开aof 持久化
appendonly yes
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7004.conf
# 集群的超时时间
cluster-node-timeout 5000
-------------------------------------
Redis-5
daemonize yes
bind 0.0.0.0
port 7004
# 打开aof 持久化
appendonly yes
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7005.conf
# 集群的超时时间
cluster-node-timeout 5000
--------------------------------------
Redis-6
daemonize yes
bind 0.0.0.0
port 7005
# 打开aof 持久化
appendonly yes
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7006.conf
# 集群的超时时间
cluster-node-timeout 5000
4. 同时启动所有的redis
5. 使用脚本创建集群(分配槽)
找到集群脚本,在src/src/redis-trib.rb 要安装Ruby的环境【不推荐】
6. 使用docker 下载redis-trib的镜像运行【推荐】
安装Docker
yum install docker
启动docker
systemctl start docker
下载镜像
docker pull inem0o/redis-trib
docker run -it --net host inem0o/redis-trib create --replicas 1
192.168.120.129:7000 192.168.120.129:7001
192.168.120.129:7002 192.168.120.129:7003
192.168.120.129:7004 192.168.120.129:7005
-it 是为了可以输入
--net host 是为了上docker容器能连接上本地的宿主机
7. 测试集群环境
-c 表示连接集群
到此集群搭建完成!
java连接redis
创建maven项目
加入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
测试单机连接
测试集群连接
JedisPool
为什么要使用JedisPool
1,获取**jedis**实例需要从**JedisPool**中获取。
2,用完jedis实例需要返还给JedisPool。
3,如果jedis在使用过程中出错,则也需要还给JedisPool。
测试
spring里面使用redis并实现缓存
Spring里面使用Redis
修改 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sxt</groupId>
<artifactId>redis-spring</artifactId>
<version>1.0</version>
<name>redis-spring</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<spring.version>4.3.24.RELEASE</spring.version>
<jedis.version>3.1.0</jedis.version>
</properties>
<dependencies>
<!-- 导入spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
</dependencies>
<build>
</build>
</project>
创建 applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--声明jdesiPool的配置对象-->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!--最大的连接数据-->
<property name="maxTotal" value="10000"></property>
<!--最大的活动连接数-->
<property name="maxIdle" value="100"></property>
<!--最小的活动连接数-->
<property name="minIdle" value="10"></property>
</bean>
<!--声明jedisPool 只能使用构造方法注入-->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg>
<constructor-arg name="host" value="192.168.18.130"></constructor-arg>
<constructor-arg name="port" value="6379"></constructor-arg>
<constructor-arg name="timeout" value="5000"></constructor-arg>
<constructor-arg name="password" value="123456"></constructor-arg>
</bean>
</beans>
测试
package com.sxt;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
* Hello world!
*/
public class TestRedisConnection
{
public static void main( String[] args )
{
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
JedisPool bean = context.getBean(JedisPool.class);
Jedis jedis = bean.getResource();
String set = jedis.set("key1", "value1");
String key1 = jedis.get("key1");
System.out.println(key1);
}
}
Spring里面使用Redis、缓存 实现所有学生数据
原理
查询时先查询缓存 ,如果缓存有,就直接返回出去;如果缓存没有,就先查询数据 ,再放入缓存,再返回
使用切面+mybatis实现。
创建项目
修改pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sxt</groupId>
<artifactId>redis_spring-cache</artifactId>
<version>1.0-SNAPSHOT</version>
<name>redis_spring-cache</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<mybatis.version>3.5.2</mybatis.version>
<mybatis-spring.version>2.0.2</mybatis-spring.version>
<spring.version>4.3.24.RELEASE</spring.version>
<druid.version>1.0.18</druid.version>
<mysql.version>8.0.17</mysql.version>
<fastjson.version>1.2.59</fastjson.version>
<!-- 注意只能使用2.0以下的版本 -->
<log4j.version>1.2.17</log4j.version>
<jedis.version>3.1.0</jedis.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- 导入spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- mysql数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
</dependencies>
<build>
</build>
</project>
创建application-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加载db.properties -->
<context:property-placeholder location="classpath*:db.properties" system-properties-mode="FALLBACK"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
<!-- 声明数据对象 -->
<bean id="configuration" class="org.apache.ibatis.session.Configuration">
<!-- 在控制台输出sql -->
<property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/>
</bean>
<!-- 声明sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
<!--加入配置文件-->
<!--<property name="configLocation" value="classpath*:mybatis.cfg.xml"></property>-->
<property name="configuration" ref="configuration"/>
<!--配置mppaer.xml的扫描-->
<property name="mapperLocations" value="classpath*:mapper/*Mapper.xml"/>
</bean>
<!-- 配置mapper接口扫描 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.lyang.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>
创建application-redis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 声明jedisPool的配置对象 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大连接数据 -->
<property name="maxTotal" value="10000"/>
<!-- 最大活动连接数 -->
<property name="maxIdle" value="100"/>
<!-- 最小多动连接数 -->
<property name="minIdle" value="10"/>
</bean>
<!--声明jedisPool 只能使用构造方法注入-->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="poolConfig" ref="jedisPoolConfig"/>
<constructor-arg name="host" value="192.168.18.130"/>
<constructor-arg name="port" value="6379"/>
<constructor-arg name="timeout" value="5000"/>
<constructor-arg name="password" value="123456"/>
</bean>
</beans>
创建application-service.xm
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描service.impl-->
<context:component-scan base-package="com.lyang.service.impl"/>
<aop:aspectj-autoproxy/>
<!--扫描缓存类-->
<context:component-scan base-package="com.lyang.cache"/>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务的传播特性-->
<tx:advice id="advise" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="change*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<!--<tx:method name="get*" read-only="true"/>-->
<!--<tx:method name="load*" read-only="true"/>-->
<!--<tx:method name="query*" read-only="true"/>-->
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
<!--声明切面-->
<aop:pointcut id="pc" expression="execution(* com.lyang.service.impl.*.*(..))"/>
<!--织入-->
<aop:advisor advice-ref="advise" pointcut-ref="pc"/>
</aop:config>
</beans>
创建applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="classpath*:application-dao.xml"/>
<import resource="classpath*:application-service.xml"/>
<import resource="classpath*:application-redis.xml"/>
</beans>
创建db.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username=root
password=root
创建log4j.properties
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
使用插件生成Student相关业务
Student
StudentMapper
StudentMapper.xml
StudentService
StudentServiceImpl
创建ChcheAspect
完成添加、修改、删除的缓存。
package com.lyang.cache;
import com.alibaba.fastjson.JSON;
import com.lyang.domain.Student;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import javax.annotation.Resource;
import java.util.List;
@Aspect
@Component
@EnableAspectJAutoProxy
public class CacheAspect {
// 声明切面
private static final String ALL_STUDENT = "execution(* com.lyang.service.impl.StudentServiceImpl.queryAllStudent(..))";
private static final String ONE_STUDENT = "execution(* com.lyang.service.impl.StudentServiceImpl.selectByPrimaryKey(..))";
private static final String ADD_STUDENT = "execution(* com.lyang.service.impl.StudentServiceImpl.insert(..))";
private static final String UPDATE_STUDENT = "execution(* com.lyang.service.impl.StudentServiceImpl.updateByPrimaryKey(..))";
private static final String DELETE_STUDENT = "execution(* com.lyang.service.impl.StudentServiceImpl.deleteByPrimaryKey(..))";
private static final String KEY = "student_all";
private static final String PREFIX = "student";
@Resource
private JedisPool jedisPool;
/**
* 全部缓存
* @param joinPoint 连接点对象
* @return
* @throws Throwable
*/
@Around(ALL_STUDENT)
public Object cacheAllStudent(ProceedingJoinPoint joinPoint) throws Throwable {
// 从redis中获取
Jedis jedis = jedisPool.getResource();
String str = jedis.get(KEY);
if (null != str) {
// 有返回 把redis里面取出的字符串转换成List对象
List<Student> students = JSON.parseArray(str, Student.class);
jedis.close();
return students;
} else {
// 没有 查询放入缓存在返回
Object proceed = joinPoint.proceed();
// 放入缓存
jedis.set(KEY, JSON.toJSONString(proceed));
return proceed;
}
}
/**
* 添加缓存
* @param joinPoint 连接点对象
* @return
* @throws Throwable
*/
@Around(ADD_STUDENT)
public Object cacheAddStudent(ProceedingJoinPoint joinPoint) throws Throwable {
// 执行添加的方法,把数据保存到数据库中
Object proceed = joinPoint.proceed();
// 得到添加的参数对象
Student student = (Student) joinPoint.getArgs()[0];
// 先从redis中获取
Jedis jedis = jedisPool.getResource();
// 把student对象放入redis
jedis.set(PREFIX + ":" + student.getId(), JSON.toJSONString(student));
jedis.close();
return proceed;
}
/**
* 删除缓存
* @param joinPoint 连接点对象
* @return
* @throws Throwable
*/
@Around(DELETE_STUDENT)
public Object cacheUpdateStudent(ProceedingJoinPoint joinPoint) throws Throwable {
// 删除数据库里面的数据
Object proceed = joinPoint.proceed();
// 的到要删除的ID
Integer id = (Integer) joinPoint.getArgs()[0];
// 先从redis里面取
Jedis jedis = jedisPool.getResource();
// 根据id删除redis中指定的缓存
jedis.del(PREFIX + ":" + id);
jedis.close();
return proceed;
}
/**
* 查询一个
* @param joinPoint
* @return
* @throws Throwable
*/
@Around(ONE_STUDENT)
public Object cacheOneStudent(ProceedingJoinPoint joinPoint) throws Throwable {
// 得到要查询的数据ID
Integer id = (Integer) joinPoint.getArgs()[0];
// 先从redis里面查询
Jedis jedis = jedisPool.getResource();
String json = jedis.get(PREFIX + ":" + id);
if (null != json) {
Student student = JSON.parseObject(json, Student.class);
jedis.close();
return student;
} else {
Student student = (Student) joinPoint.proceed();
if (null != student) {
// 存入redis
jedis.set(PREFIX + ":" + student.getId(), JSON.toJSONString(student));
}
jedis.close();
return student;
}
}
}
测试
package com.lyang;
import com.lyang.domain.Student;
import com.lyang.service.StudentService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Date;
import java.util.List;
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
StudentService studentService = context.getBean(StudentService.class);
List<Student> students = studentService.queryAllStudent();
for (Student student : students) {
System.out.println(student);
}
/* Student student = studentService.selectByPrimaryKey(1);
System.out.println(student);
int i = studentService.updateByPrimaryKey(new Student(4, "lyang", 23, "高三(4)班", new Date()));
System.out.println(i);
studentService.deleteByPrimaryKey(5);*/
}
}
springboot中使用redis
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
原理说明
因为springboot里面那124个自动配置类里面有一个RedisAutoConfiguration,所以我们要让RedisAutoConfiguration生效。
配置yml文件
application.yml
#redis的配置
spring:
redis:
host: 192.168.149.130
password: 123456
port: 6379
StringRedisTemplate使用最多的
这里是说当前StringRedisTemplate类只能存放key=string value=String 的值。
List --- String values ={“123”}
可以看到StringRedisTemplate初始化时设置了key value hashKey hashValue的序列化方式全部都为RedisSerializer.string()
package com.sxt;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
@SpringBootTest
class RedisSpringbootApplicationTests {
@Autowired
private StringRedisTemplate redisTemplate;
@Test
void contextLoads() {
System.out.println(redisTemplate);
}
@Test
void redisTemplateMethod(){
redisTemplate.opsForValue();//返回的对象是操作String
redisTemplate.opsForCluster();//返回的对象用来操作集群的
redisTemplate.opsForHash();//返回的对象用来操作hash
redisTemplate.opsForList();//返回的对象用来操作 list
redisTemplate.opsForSet();//返回的对象用来操作 set
redisTemplate.opsForZSet();//返回的对象用来操作zset
redisTemplate.delete("key");//根据key删除某个值
redisTemplate.hasKey("");//判断是否有某一个key
// redisTemplate.countExistingKeys(Conllection keys );//判断存在的key的数量
// redisTemplate.type() 判断key的类型
redisTemplate.slaveOfNoOne();//反客为主
// redisTemplate.slaveOf();
redisTemplate.randomKey();//返回随机key
// redisTemplate.keys()
redisTemplate.discard();//放弃事务
// redisTemplate.rename(); 修改key名
redisTemplate.multi();//开启事务
redisTemplate.exec();//提交事务
// redisTemplate.watch();
redisTemplate.unwatch();
// redisTemplate.move()
redisTemplate.getExpire("");//得到key的过期时间
// redisTemplate.set
// redisTemplate.expire() 设置过期时间
}
@Test
void redisTemplateFroValue(){
//返回的对象是操作String
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
//操作String的数据类型
opsForValue.set("name","leige");
//operations里面的方法和redisTemplate这个对象的方法差多
RedisOperations<String, String> operations = opsForValue.getOperations();
}
}
存在问题:
无法直接存自定义对象
RedisTemplate一定要掌握的
原理说明
上面的StringRedisTeampate它继承了RedisTemplate,并且定死了序列化方式。
存在问题
如果想存放自定义数据就不可以,
可以使用RedisTemplate来完成,
默认使用jdk的序列化方式。
编写测试
-
使用jdk默认序列化方式
-
一般使用时key都是使用String
-
Value一般自定义
情况1:key –string value 默认
情况2:key –string value
使用jackson,【GenericJackson2JsonRedisSerializer】 类。
总结
推荐使用jackson的序列化方式。因为同样的东西,jackson之后的数据更小。
*springboot使用redis注解做缓存
相关注解
@EnableCaching
在启动类上加上注解,启动缓存
@Cacheable
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
例:@Cacheable(key="#id",cacheNames="com.sxt.service.impl.MenuServiceImpl")
@Cacheable 作用和配置方法:
参数 | 解释 | example |
---|---|---|
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @Cacheable(value=”mycache”) @Cacheable(value= |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | @Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
@Cacheput(解决脏读)
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用。
@CachePut 这个注解可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。
@CachePut 作用和配置方法:
参数 | 解释 | example |
---|---|---|
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | @CachePut(value=”my cache”) |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | @CachePut(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | @CachePut(value=”testcache”,condition=”#userName.length()>2”) |
@CachEvict(解决脏读)
主要针对方法配置,能够根据一定的条件对缓存进行清空。
@CacheEvict 作用和配置方法:
参数 | 解释 | example |
---|---|---|
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | @CacheEvict(value=”my cache”) |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | @CacheEvict(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | @CacheEvict(value=”testcache”,condition=”#userName.length()>2”) |
allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | @CachEvict(value=”testcache”,beforeInvocation=true) |
@Cacheconfig(全局的配置缓存)
一个类中可能会有多个缓存操作,而这些缓存操作可能是重复的。这个时候可以使用@CacheConfig。
@CacheConfig是一个类级别的注解,允许共享缓存的名称、KeyGenerator、CacheManager 和CacheResolver。
该操作会被覆盖。
相关概念
1,脏读:
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
例如:
张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。
与此同时,事务B正在读取张三的工资,读取到张三的工资为8000。
随后,事务A发生异常,而回滚了事务。张三的工资又回滚为5000。
最后,事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。
2,不可重复读:
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
例如:
在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
与此同时,事务B把张三的工资改为8000,并提交了事务。
随后,在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。
3,幻读:
是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
例如:
目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。
此时,事务B插入一条工资也为5000的记录。
这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
项目创建
修改 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lyang</groupId>
<artifactId>redis_springboot-annotation</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis_springboot-annotation</name>
<description>Spring Boot集成redis使用注解</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建 application.yml
server:
port: 8080
spring:
datasource:
type: org.springframework.jdbc.datasource.DriverManagerDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
# redis的配置
redis:
host: 192.168.18.130
port: 6379
password: 123456
jackson:
date-format: yyyy-MM-dd HH:mm:ss
mvc:
date-format: yyyy-MM-dd HH:mm:ss
#mybatisplus的配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: available #哪一个字段做逻辑删除
logic-not-delete-value: 1 #不删除字段的值
logic-delete-value: 0 #已删除的字段值为0
# id-type: auto #如果你的数据库本身是有自增的,那么可以选择这 自动适配数据库规则
生成Student、StudentMapper、StudentMapper.xml、StudentService、StudentServiceImpl
修改StudentService
package com.lyang.service;
import com.lyang.domain.Student;
import com.baomidou.mybatisplus.extension.service.IService;
public interface StudentService extends IService<Student>{
/**
* 修改学生信息
* @param entity
* @return
*/
Student updateStudent(Student entity);
/**
* 添加学生信息
* @param entity
* @return
*/
Student addStudent(Student entity);
}
修改StudentServiceImpl
package com.lyang.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lyang.mapper.StudentMapper;
import com.lyang.domain.Student;
import com.lyang.service.StudentService;
import java.io.Serializable;
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService{
/**
* 它存放的对象是当前方法返回的对象
* @param entity
* @return
*/
// 在Redis里面com.sxt.service.impl.UserServiceImpl:用户id 它存放的对象是当前方法返回的对象
@CachePut(cacheNames = "com.lyang.service.impl.StudentServiceImpl", key = "#entity.id")
public Student updateStudent(Student entity) {
// 先查再改是为了学生部分修改的时候redis里面会出现空值的问题
Student student = baseMapper.selectById(entity.getId());
BeanUtil.copyProperties(entity, student, CopyOptions.create().setIgnoreNullValue(true));
baseMapper.updateById(student);
return entity;
}
@Override
@CachePut(cacheNames = "com.lyang.service.impl.StudentServiceImpl", key = "#entity.id")
public Student addStudent(Student entity) {
baseMapper.insert(entity);
return entity;
}
@CacheEvict(cacheNames = "com.lyang.service.impl.StudentServiceImpl", key="#id")
public boolean removeById(Serializable id) {
baseMapper.deleteById(id);
return true;
}
/**
* 存放的对象是当前方法返回的对象
* @param id
* @return
*/
@Override
@Cacheable(cacheNames = "com.sxt.service.impl.UserServiceImpl" ,key ="#id")
public Student getById(Serializable id) {
return baseMapper.selectById(id);
}
}
创建StudentController
package com.lyang.controller;
import com.lyang.domain.Student;
import com.lyang.service.StudentService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("student")
public class StudentController {
@Resource
private StudentService studentService;
@RequestMapping("addStudent")
public String addStudent() {
studentService.addStudent(new Student(6, "喜羊羊", 14, "大草原", new Date()));
return "ADD SUCCESS";
}
@RequestMapping("updateStudent")
public String updateStudent(Integer id) {
Student student = new Student(id, "喜羊羊" + id, 14 + id, "大草原" + id, new Date());
studentService.updateStudent(student);
return "UPDATE SUCCESS";
}
@RequestMapping("updateStudent2")
public String updateStudent(Integer id, String name) {
Student student = new Student();
student.setId(id);
student.setName(name);
studentService.updateStudent(student);
return "UPDATE SUCCESS";
}
@RequestMapping("deleteStudent")
public String deleteStudent(Integer id) {
studentService.removeById(id);
return "DELETE SUCCESS";
}
@RequestMapping("getOne")
public Student getOne(Integer id) {
return this.studentService.getById(id);
}
@RequestMapping("getAll")
public List<Student> getAll(Integer id) {
return this.studentService.list();
}
}
创建配置类修改序列化方式
package com.lyang.config;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
/**
* redis序列化方式配置
*/
@Configuration
public class RedisConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
CacheProperties.Redis redis = cacheProperties.getRedis();
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig();
// 设置redis序列化方式
cacheConfig.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// .fromSerializer(RedisSerializer.json());
if (redis.getTimeToLive() != null) {
cacheConfig = cacheConfig.entryTtl(redis.getTimeToLive());
}
if (redis.getKeyPrefix() != null) {
cacheConfig = cacheConfig.prefixKeysWith(redis.getKeyPrefix());
}
if (!redis.isCacheNullValues()) {
cacheConfig = cacheConfig.disableCachingNullValues();
}
if (!redis.isUseKeyPrefix()) {
cacheConfig = cacheConfig.disableKeyPrefix();
}
return cacheConfig;
}
}
创建启动类
package com.lyang;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
@MapperScan(basePackages = {"com.lyang.mapper"})
public class RedisSpringbootAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(RedisSpringbootAnnotationApplication.class, args);
}
}
Redis键和值的设计原则
使用【表名:数据ID】,这样可以更好的区分不同的数据和层级关系。如:User:1、User:2、Dept:1
cacheNames=com.sxt.service.impl.UserServiceImpl keys=”#id” // id代表是方式的形式参数名
keys =”result.id” // result 代表方法的返回值对象
redis value 值格式
在Java常规开发中,我们需要有面向对象的思想,相对于对象来说,比较常用且能快速转换的格式就是 JSON 了;比较常用的Java处理JSON数据有三个比较流行的类库FastJSON、Gson和Jackson
上面提到了JSON,这是因为在Redis的存储中,我们使用它来存储value值,为什么要这样做呢?主要是因为json格式有如下几种好处:
标准,主流数据交换格式
简单,结构清晰,相对于XML来说更加的轻量级,易于解析
语言无关,任何语言都能轻松搞它
类型安全,值是有类型的,比如整数、字符串、布尔等
代码中redis value在存储前我们对其做了一次转换,将对象V转换为json对象后存储,也就是一个key对应一个json串。
redis key 键格式
上面讲了简单的key存储,如 xdd的存储,此时普通的需求可以满足;然而在实际业务中,往往key键的存储会非常的复杂,比如我们现在有一个需求:
需求:根据基础数据系统中的数据字典类型查询对应的字典集合
这时,我们需要关注的业务就变得复杂了,就不能使用常规的key键存储方式,上面的需求大致可以拆分为:
系统:基础数据系统 user:sex:1=男 user:sex:0= 女
模块:数据字典 sys:available:1=true sys:available:0=false
方法:根据数据字典类型查询
参数:字典类型
为什么要这样拆分呢?为了可读性;也为了抽象出key存储规则;因为业务复杂情况下,我们定义的key键太多时就不便于管理,也不便于查找,以 系统-模块-方法-参数 这样的规则定义,我们可以很清晰的了解redis key存储的值是做了什么事情。
common:sys:sex:1 男
common:sys:sex:0 女
common:sys:flag:1 是
common:sys:flag:0 否
common:page:title 欢迎使用XX管理系统
user:1 {id:1.name:小明}
user:2 {id:2.name:张三}
这个在使用工具去查看的时候就可以看出层级关系啦!
面试中要知道的
1. Redis支持的数据类型?
String(字符串类型)
String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; 常规计数:微博数,粉丝数等。
常用命令:
set,get,incr,decr,mget 等:set key value 设置值、 get key 获取值、 incr key 加一、 decr key 减一
hash(哈希)
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。
常用命令: set,get,decr,incr,mget 等:
hset key field value 设置值
hget key field 获取值
hincrby key field num 设置增数量
list(列表)
Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。
可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。
常用命令: lpush,rpush,lpop,rpop,lrange等:
lpush list a b c d (从list左边添加元素)、 rpush list 1 2 3 4 (从list右边添加元素)
lrange list 0 -1(从0 到 -1 元素查看:也就表示查看所有)
lpop list (从list左边取,删除)、 rpop list (从list右边取,删除)
set(集合)
Redis的Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
常用命令: sadd,spop,smembers,sunion 等:
sadd set1 a b c d d (向set1中添加元素) 元素不重复
smembers set1(查询元素)、 srem set1 a(删除元素)
sorted set(zset,有序集合)
和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
例:在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。
常用命令: zadd,zrange,zrem,zcard等:
zadd zset1 1 a 2 b 3 c (添加元素 zadd key score member,这里添加元素a:1分、元素b:2分、元素c:3分 )
zrange zset1 0 -1 (查看zset1的所有元素,默认从小到大)
zrange zset1 0 -1 withscores (查看zset1的所有元素,包括分数score)
zrevrange zset1 0 -1 (查看zset1的所有元素,从大到小)
zincrby zset1 5 a (对zset1的a元素增加5分)
2. 什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
(Redis 数据都放在内存中。如果机器挂掉,内存的数据就不存在。所以需要做持久化,将内存中的数据保存在磁盘,下一次启动的时候就可以恢复数据到内存中。)
Redis 提供了两种持久化方式:RDB(默认) 和AOF 。
RDB (快照):
Redis可以通过创建快照来 获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。
快照持久化是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
AOF(只追加文件):
与快照持久化相比,AOF持久化的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:appendonly yes
开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。
在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步
为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
RDB (快照):快照形式 ,定期将当前时刻的数据保存磁盘中。会产生一个dump.rdb文件。
特点:性能较好,数据备份。但可能会存在数据丢失。
AOF(只追加文件) :append only file (所有对redis的操作命令记录在aof文件中),恢复数据,重新执行一遍即可。
特点:每秒保存,数据比较完整。但耗费性能。
【注】如果两个都配了优先加载AOF。(同时开启两个持久化方案,则按照 AOF的持久化放案恢复数据。)
3,Redis 有哪些架构模式?讲讲各自的特点?
主从模式(redis2.8版本之前的模式)、哨兵(sentinel)模式(redis2.8及之后的模式)、redis cluster模式(redis3.0版本之后)。
布隆过滤器【缓存穿透,雪崩,击穿】
缓存穿透、缓存雪崩、缓存击穿
前提
因为redis是一个缓存数据库。它里面的数据应该是mysql数据的子集
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
-
对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
-
对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤
-
也可以使用流行的bloom filter布隆过滤器
布隆过滤器
相关博文:https://www.cnblogs.com/CodeBear/p/10911177.html
布隆过滤器是一个叫“布隆”的人提出的,它本身是一个很长的二进制向量,既然是二进制的向量,那么显而易见的,存放的不是0,就是1。
下面以8个长度为例:
现在需要添加一个数据:
我们通过某种计算方式,比如Hash1,计算出了Hash1(数据)=5,我们就把下标为5的格子改成1,就像下面这样:
布隆过滤器的优缺点:
- 优点:由于存放的不是完整的数据,所以占用的内存很少,而且新增,查询速度够快;
- 缺点: 随着数据的增加,误判率随之增加;无法做到删除数据;只能判断数据是否一定不存在,而无法判断数据是否一定存在。
在实际开发中,如果你要添加大量的数据,仅仅8位是远远不够的,为了让误判率降低,我们还可以用更多的随机映射函数、更长的二进制向量去计算位置。
hash环
Hash环介绍:https://www.cnblogs.com/iwenwen/p/10997372.html
缓存雪崩
产生情况
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
大量的key过期
解决方法:每个key设置随机时间
Redis宕机
解决方法:集群
缓存击穿
产生情况
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案:
- 设置热点数据永远不过期。
- 加互斥锁
docker部署redis
#docker安装redis
docker pull redis
#docker部署redis
docker run -d --name myredis -p 6380:6379 redis --requirepass "123456"
#docker运行redis
docker exec -it f374bf68977 redis-cli
#docker停止redis
docker stop myredis
#删除redis镜像
docker rm redis
注意:如果在云服务器里面使用redis,不要开放6379端口。(开启6379端口服务器容易遭受攻击)
redis实现秒杀
package com.lyang;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class TestRedisMiaoSha {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
JedisPool bean = context.getBean(JedisPool.class);
Jedis jedis = bean.getResource();
jedis.flushDB();
String key = "product";
// 初始化10个商品待抢
for (int i = 1; i <= 10; i++) {
jedis.lpush(key, "商品" + i);
}
System.out.println("10个商品初始化完成");
jedis.close();
for (int i = 1; i <= 100; i++) {
new Thread("用户" + i) {
@Override
public void run() {
Jedis resource = bean.getResource();
String pro = resource.lpop(key);
Thread thread = Thread.currentThread();
if (null != pro) {
System.out.println(thread.getName() + "抢到了" + pro + " 的商品");
//发到家到MQ生成订单
} else {
System.err.println(thread.getName() + " 对不起,商品已抢完");
}
resource.close();
}
}.start();
}
}
}