百度指数: http://index.baidu.com/v2/index.html#/
Redis
# key 命令
Redis 支持 5 中数据类型:
- string(字符串)
- hash(哈希)
- list(列表)
- set(集合)
- zset(sorted set:有序集合)
- 等(新版会增加类型,如 5.0,后面会讲到)
常用命令 key 管理
keys * :返回满足的所有键,可以模糊匹配,比如 keys abc* 代表 abc 开头的 key
exists key : 是否存在指定的 key,存在返回1 , 不存在返回 0
expire key second : 设置 某个 key 的过期时间 时间单位 秒
del key : 删除某个 key
ttl key : 查看剩余时间,
当 key 不存在时,返回 -2 ;
存在但没有设置剩余生存时间时,返回 -1 ,
否则,以秒单位返回 key 的剩余生存时间
persist key : 取消过时时间
PEXPIRE key milliseconds : 修改 key 的过期时间为毫秒
select : 选择数据库 数据库为 0~15(默认一共16个数据库)
设计成多个数据库实际上是为了数据库安全和备份
move key dbindex : 将当前数据库中的 key 转移到其他数据库
randomkey:随机返回一个key
rename key key2 : 重命名 key
echo : 打印命令
dbsize : 查看数据库的 key 数量
info: 查看数据库信息
config get * : 实时传存储收到的请求,返回相关的配置
flushdb : 清空当前数据库
flushall:清空所有数据库
应用场景
EXPIRE key seconds、EXISTS key
- 显示的优惠活动信息
- 网站数据存储(对于一些需要定时更新的数据,如:积分排行榜)
- 手机验证码
- 限制网站访客访问频率(例如:1 分钟最多访问 10 次)(流量攻击)
ip
得到 ==> 计数 key 的生存时间为 1 秒
# key 命令规范(建议)
为什么要有命名规范?
非关系数据库(Redis):数据与数据之间没有关联关系。
避免回看时候的混乱
命名规范:
-
<mark>key 不要太长</mark>,尽量不要超过 1024 字节,这不仅消耗内存,而且会<mark>降低查找的效率</mark>;
redis 单个 key 允许存入 512M 大小 -
<mark>key 也不要太短</mark>,太短的话,key 的<mark>可读性差</mark>
-
在一个项目中,key 最好<mark>使用同一的命名模式</mark>。
例如:user:123:password -
key 名称<mark>区分大小写</mark>
比较好行的命名模式 ⇒ 冒号模式分割
user:id:name
如
user:1:zhangshang
user:2:lisi
不建议:
user_id_name(某些情况无法区分)
另外,如果遵循
user:id:name
的命名规范,上面的可视化工具会自动帮我们分组管理
# 数据类型
String 类型
String 类型是 Redis 最基本的数据类型,一个键最大存储 512MB
String 数据结构是简单的 key-value 类型,<mark>value 其不仅是 string、也可以是数字,是包含很多种类型额的特殊类型</mark>
<mark>string 类型是二进制安全的,意思是 redis 的 string 可以包含任何数据</mark>
比如序列化的对象进行存储,比如一张图片进行二进制存储,比如简单的字符串,数值等等。
String 命令
!赋值语法
------------------------------------------------------------------
SET KEY value
多次设置KEY的VALUE会覆盖
(即如果KEY已经存储值,SET就会覆写旧值,且无视类型)
MSET K1 V1 K2 V2 ...
批量写入
SETNX KEY value
(NX = not exists)
如果KEY不存在,则设值,并且返回 1
如果KEY存在,则不设值,并且返回 0
(解决分布式锁 方案之一, 只有在KEY不存在时设置KEY的值。)
(类似 MYSQL 的 SET if not exists)
SETEX KEY time value
(字符串类型专用过,设置过期时间)
SETRANGE KEY offset value
替换字符串
如:
set name hhhh
SETRANGE name 0 11
get name
得到:11hh
SETBIT KEY offset value
设置指定 key 对应偏移量上的 bit 值,value 只能是1或者0
(String 作为 Bitmaps 类型的 api,后面讲bitmaps时再提)
!取值语法
------------------------------------------------------------------
GET KEY
如果KEY不存在,则返回 nil
如果KEY存储的值不是字符串类型,返回一个错误
GET K1 K2 K3 ...
批量读
GETRANGE KEY offset end
字符量截取
(包括offset和end)
如:
set name 123456
GETRANGE name 1 3
得到:23
GETBIT KEY offset
对KEY所存储字符串,获取指定的偏移量上的位(bit)
如:value=2 => (bit)10 => 1(offset=0),0(offset=1)
GETSET KEY value
设置KEY上新的值value,并返回旧的值
当key不存在时,依然存入新的值,并返回nil
STRLEN KEY
返回KEY所存储的字符串的长度
!删除语法
------------------------------------------------------------------
DEL KEY
删除指定的KEY,如果存在,返回数字类型
!自增、自减
------------------------------------------------------------------
INCR KEY
自增
如果KEY不存在,那么结果为 1 (KEY会先被初始化为 0,然后马上自增为 1)
如果类型不对:("ERR value is not an integer or out of range")
INCRBY KEY increment
指定增量值
DECR KEY
自减
如果KEY不存在,那么结果为 -1 (KEY会先被初始化为 0,然后马上自减为 -1)
如果类型不对:("ERR value is not an integer or out of range")
DECRBY KEY decrement
指定减量值
APPEND KEY VALUE
字符串拼接
(追加至末尾)
String 应用场景
- String 通常用于保存单个字符串或 JSON 字符串数据
- 因为 String 是二进制安全的,所以你完全可以把一个图片文件的内容作为字符串来存储
- 计数器(常规 Key-valu 缓存应用。常规计数器:微博数、粉丝数)
INCR 等指令本身就具有原子操作的特性
<mark>所以我们完全可以利用 redis 的 INCR、INCRBY、DECR、DECRBY 等指令来实现原子计数的效果</mark>
(简单理解原子性)假如:
- 在某种场景下有 3 个客户端同时读取了
mynum
的值(值为 2)- 然后对其同时进行了 加 1 的操作。
- 那么,最后
mynum
的值一定是 6
Hash 类型 = Object (Map)
- <mark>Hash 类型是 String 类型的 field 和 value 的映射表,或者说是一个 String 的集合。</mark>
- <mark>相比于 String,hash 存储对象所占空间比 String 类型少,hash 特别适合于存储对象</mark>
- <mark>相比于 json , hash 存储在磁盘中的空间也很小</mark>
(Redis 中,每个 hash 可以存储 2 的 32 次方-1 键值对 = 40 多亿)
一句话: 特别适合存储对象 Object
Hash 命令
!赋值语法
------------------------------------------------------------------
HSET KEY FIELD value
存储单个属性
如:
hset user:1 id 1
Json 视角:
'user:1' : {
id:1
}
HMSET KEY FIELD value
存储多个属性(批量存储属性)
如:
hmset user:1 id 1 name alan age 18
Json 视角:
'user:1' : {
id:1 ,
name : "alan" ,
age : 18
}
!取值语法
------------------------------------------------------------------
HGET KEY FIELD
获取 KEY 对象的 FIELD 属性值
HMGET KEY F1 F2 ...
批量获取 KEY 对象的 FIELD 属性值
HGETALL KEY
获取KEY中全部属性值
HKEYS KEY
获取KEY对象的所有属性名
HLEN KEY
获取KEY对象属性的个数
!删除语法
------------------------------------------------------------------
HDEL KEY F1 F2 ...
删除一个或多个KEY对象的属性
!其他语法
------------------------------------------------------------------
HSETNX KEY FIELD value
只有在字段FIELD不存在时,设置值
HINCRBY KEY FIELD increment
为KEY对象(整数类型的)FIELD属性设置自增量
HINCRBYLOAT KEY FIELD increment
为KEY对象(浮点类型的)FIELD属性设置自增量
HEXISTS KEY FIELD
查看KEY对象的FIELD是否存在
Hash 应用场景
常用语存储一个对象
为什么不用 String 存储一个对象?
- <mark>Hash 是最接近关系数据库结构的数据结构</mark>,可以将数据库一条记录或者程序中一个对象转换成hashmap存放在redis中。
<mark>可以理解成:用hash类型存储的,直接就是对象</mark> - 而用 String 类型存储对象,有两种方案:
- 对象系列化成String存储
<mark>这样添加了序列化、反序列化的开销</mark>
进而导致在修<mark>改时候需要整个对象</mark>
再进而导致需要<mark>对并发保护</mark>,和引入 CAS 等复杂问题。 - Key=用户id+属性名,value=属性值 方式存储
这方案问题很明显,<mark>浪费内存,效率低</mark>
假如:一个用户10个属性,创建10个键值对。
10个用户100个键值对
<mark>内存消耗成几何倍数增长</mark>
- 对象系列化成String存储
List 类型
特点:
- 有序
- 可重复
L 开头
命令
如:
rpush key value1 value2 ...
从列表右端插入N个数据
linsert
lrem
ltrim
保留1到4,其他删除
blpop、brpop
应用场景
微博
整理
- LPUSH + LPOP (后进先出) = Stack 栈结构
- LPUSH + RPOP (先进先出)= Queue 队列结构
- LPUSH + LTRIM = Capped Collection 限制大小的集合
- LPUSH + BRPOP = Message Queue 消息队列
Set 类型
和 Java 一样
- 元素无序(不记录插入顺序)、唯一
在 Redis 中 set 是通过 hashtable 实现的
- 集合中最大的成员数 2的32次方 -1 个(40多亿)
- 类似于 Hashtable 集合
命令
赋值:
SADD KEY member1 member2 ...
向集合添加一个或多个成员
取值:
SCARD KEY
获取集合的成员数
SMEMBERS KEY
返回集合中的所有成员
SISMEMBER KEY member
判断 member 元素是否是集合 KEY 的成员
(开发中,用于判断是否存在)
SRANDMEMBER key [count]
返回集合中一个或多个随机数
删除:
SREM KEY member1 member2 ...
移除集合中一个或多个成员
SPOP KEY [count]
移除并返回集合中的一个或多个随机元素
SMOVE source destination member
将 member 元素从 source 集合移动到 destination 集合
差集:
SDIFF key1 key2 ...
返回给定所有集合的差集(左侧)
SDIFFSTORE destination key1 key2 ...
返回给定所有集合的差集,并存储在 destination 中
交集:
SINTER key1 key2 ...
返回给定所有集合的交集(共有数据)
并集:
SUNION key1 key2 ...
返回所有给定集合的并集
SUNIONSTORE destination key1 key2 ...
所有给定集合的并集存储在 destination 集合中
应用场景
常应用于:对两个集合间的数据(计算)进行交集、并集、差集运算
- 共同关注、共同喜好、二度好友等功能。同时还能生成新的集合
- 判断唯一性,可以统计网站的所有独立IP、存取当天[或某天]的活跃用户列表
ZSet 类型
有序集合 (sorted set)
简介
- 不允许重复
- 不同的是,每个元素都会关联一个
double类型
的分数。
<mark>redis 正是通过分数来为集合中的成员进行从小到大的排序。</mark> - 有序集合的成员是唯一的,但分数(score)却可以重复
- 集合是通过hashtable 实现的。
最大成员数 2的32次方-1 (40多亿)
<mark>在redis中,有序集合相关的操作命令都是z开头的</mark>
很多时候,也会将redis中的 有序集合
叫做 zsets
命令
赋值:
ZADD KEY score1 member1 score2 member2 ....
向有序集合添加一个或多个成员
或更新已存在成员的分数
取值:
ZCARD KEY
获取有序集合的成员数
ZCOUNT KEY min max
计算在有序集合中,指定取件分数的成员数
ZRANK KEY member
返回有序集合中,指定成员的索引
ZRANGE KEY start stop [WITHSCORES]
通过索引区间,返回有序集合指定区间内的成员
(低到高)
ZREVRANGE KEY start stop [WITHSCORES]
通过索引区间,返回有序集合指定区间内的成员
(高到低)
ZRANGEBYSCORE KEY min max [WITHSOCRES] [LIMIT]
通过分数,返回有序集合指定区间内的成员
(低到高)
ZREVRANGEBYSCORE KEY max min [WITHSCORES]
同上
(高到低)
删除:
DEL KEY
移除集合
ZREM KEY member member ...
移除有序集合中一个或多个成员
ZREMRANGEBYSCORE KEY start stop
移除有序集合中给定的排名区间的所有成员
(第一名是0)
(低到高排序)
ZRERANGEBYSCORE KEY min max
移除有序集合中,给定的分数区间的所有成员
ZINCBY KEY increment member
增加member元素的分数increment,返回值是更改后的分数
应用场景
- 排行榜
- 带权重的队列(重要的任务先执行)
Bitmaps 类型
存储需求:存储位的状态
应用场景:统计有/无两种状态的数据
如:
- 某电影一周内是否有没点播
- 某账号一年内是否有被使用
- …
这个类型实际上就是 String,不过我们会操作 String 的作为 bitmaps 的 api
GETBIT KEY offset
获取指定KEY对应偏移量上的bit值
SETBIT KEY offset value
设置指定KEY对应偏移量上的bit值,value只能是1或者0
bitop op destkey key1 key2 ...
# op可以是:
# and 交
# not 非
# or 并
# xor 异或
对指定的KEY按位进行交、并、非、异或操作,并将结果保存到 destKey 中
bitcount KEY [start end]
统计指定KEY中1的数量
例子
HyperLogLog 类型
存储需求: 独立信息统计(基数统计)
应用场景:
如:
统计独立UV
- 原始方案:
set
(存储吗每个用户的id
字符串) - 改进方案:
bitmaps
(存储每个用户状态 bit) - 全新方案:
Hyperloglog
(存储基数)
特点:<mark>高效、节省空间</mark>
必要说明:
-
用于进行基数统计,不是集合,不保存数据,只记录数量而不是统计数据
-
核心是基数估算算法,最终数值存在一定误差
-
耗费空间小,每个hyperloglog key 最大占用了 12k (<mark>最大值不随统计数量变化</mark>)的内存用于标记基数
(也就是说,实际数据远大于12k时,才考虑使用) -
pfadd 命令不是一次性分配 12k 内存,会随基数的增加内存逐渐增大
-
pfmerge 命令合并后占用的存储空间为12k,无论合并之前数据是多少
原理:
- HyperLogLog 是用来做基数统计的,运用了loglog算法
- 基数:是数据集去重后元素个数
loglog
算法:(<mark>懵逼就对了</mark>,看 专门的文章 了解吧)
简介
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,<mark>计算基数所需的空间总是固定的,并且是很小的</mark>。
- 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,可以计算接近 2的64次方个不同元素的基数。
<mark>这和计算基数时,元素越多耗费内存养护耳朵的集合形成鲜明对比</mark>。- 但是,因为 HyperLogLog 只会根据输入的元素来计算基数,而不会存储输入元素本身,所以,<mark>HyperLogLog 不能像集合那样,返回输入的各个元素</mark>
基数
一堆数据中,不重复的数的个数
如: [1,2,3,4,5,6,7,8,1]
==>
不重复: [1,2,3,4,5,6,7,8]
基数:8
基数估计
在误差可接受范围内,快速计算基数。
常用命令
PFADD KEY element element ...
添加指定元素到 HyperLogLog 中
PFCOUNT key1 key2 ...
返回给定 HyperLogLog 的基数估算值
PFMERGE destkey sourcekey sourcekey ...
将多个 HyperLogLog 合并为一个 HyperLogLog
应用场景
- 基数不大,数据量不大就用不上,会有点大材小用浪费空间
- 有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么
- 统计注册 IP 数
- 统计每日访问 IP 数
- 统计页面实时 UV 数
- 统计在线用户数
- 统计用户每天搜索不同词条的个数
- 统计真实文章阅读数
- …
GEO
静止地球轨道(Geostationary Earth Orbit,GEO)
<mark>专门存储和处理地理位置信息</mark>
- 经度纬度
应用场景
生活服务类软件
基本操作
geoadd key longitude latitude member [longitude latitude member ...]
添加坐标点
geopos key member [member ... ]
获取坐标点
geodist key member1 member2 [unit]
计算坐标点距离
georaidus key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
根据坐标求范围内的数据
georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
根据点求范围内数据
geohash key member [member...]
获取指定点对应的坐标hash值
# redis客户端对比(Jedis、Redisson、lettuce)
<mark>在 Spring Boot 2.0 之前,主流的,对 redis 连接的支持,有以下多种:</mark>
- Jedis api 官网 http://tool.oschina.net/uploads/apidocs/redis/clients/jedis/Jedis.html
- redisson 官网 https://redisson.org/
- redisson github https://github.com/redisson/redisson
- lettuce 官网 https://lettuce.io/
- lettuce github https://github.com/lettuce-io/lettuce-core
<mark>spring boot 2.0 之后,(默认)采用了 lettuce 对 redis 做支持</mark>
概念 :
Jedis
:老牌的 Redis 的 Java 实现客户端,提供了比较全面的 Redis 命令支持。Redisson
:实现了可<mark>分布式和可扩展</mark>的Java数据结构Lettuce
:<mark>高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器</mark>
优点 :
Jedis
:比较全面的提供了 Redis 的操作特性Redisson
:促使使用者对Redis的关注分离,提供很多分布式相关操作服务
例如:<mark>分布式锁、分布式集合、可通过Redis支持延迟队列</mark>Lettuce
: <mark>基于 Netty 框架的事件驱动的通信层,其方法调用时异步的。Lettuce 的 API 是线程安全的。所以可以是作单个 Lettuce 连接来完成各种操作</mark>
三方对比:可伸缩性:
-
Jedis
:
使用阻塞的I/O,且其 <mark>方法调用都是同步的</mark>,程序流需要等到sockets处理完I/O才能执行
不支持异步。
Jedis客户端实例不是线程安全的,所以<mark>需要通过连接池来使用Jedis</mark>。 -
Redisson
:
<mark>基于Netty</mark>框架的事件驱动的通信层,其方法调用是<mark>异步</mark>的
其连接实例(StatefulRedisConnection)可以在多个线程间并发访问。
Redisson的API是<mark>线程安全</mark>的,所以可以操作单个Redisson连接来完成各种操作
<mark>同时,当一个连接实例不够的情况下,可以按需增加连接实例。(体现了可伸缩的设计)</mark>
但和 Jedis 相比,功能较为简单,<mark>不支持字符串操作,不支持排序、事务、管道、分区等Redis特性</mark>
因为其宗旨是促进使用者对Redis的关注分离,从而将精力放在业务逻辑处理上 -
Lettuce
:
<mark>基于Netty</mark>框架的事件驱动的通信层,其方法调用是异步的。
Lettuce的API是<mark>线程安全</mark>的,所以可以操作单个Lettuce连接来完成各种操作
lettuce 能够支持 redis4,需要 java8 以上。
总结
- redisson 分布式
- lettuce 对字符串支持好,线程安全
- jedis 对字符串支持好(功能全),<mark>但线程不安全</mark>
- <mark>开发中,优先使用 Lettuce,如果需要分布式锁,分布式集合等分布式特性,添加Redisson结合使用</mark>
(不单独使用 Redisson ,因为redisson 本身对字符串的操作支持很差)
下面,分别使用 Jedis 和 Lettuce 作为演示案例
SpringBoot 整合 Jedis
新建springboot项目
# 创建 JedisPool
引入依赖
SpringBoot 内默认引入了Jedis版本,所以这里不需要写版本
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
application.yml
spring:
redis:
port: 6379
host: 192.168.64.33
password: root
jedis:
pool:
max-idle: 6 #最大空闲数
min-idle: 2 #最小空闲数
max-active: 10 #最大连接数
timeout: 2000 #连接超时
编写Config
package com.example.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/** * @author alan smith * @version 1.0 * @date 2020/3/26 8:57 */
@Slf4j
@Configuration // 相当于一个XML
public class JedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.jedis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.jedis.pool.min-idle}")
private int minIdle;
// SpringBoot2.2.2版本
@Bean
public JedisPool jedisPool() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setMaxTotal(maxActive);
JedisPool pool = new JedisPool(config, host, port, timeout, password);
log.info("JedisPool连接成功:{}\t{}", host, port);
return pool;
}
}
启动
# 测试String类型操作
接口
package com.example.service;
/** * @author alan smith * @version 1.0 * @date 2020/3/26 9:56 */
public interface UserService {
/** * Redis String 类型 * 需求:用户输入一个key,先判断Redis中是否存在该数据 * 如果存在,在Redis中进行查询,并返回 * 如果不存在,在MySQL数据库查询,将结果交给Redis,并返回 */
String getString(String key);
/** * 清空缓存 */
void clear();
}
接口实例
package com.example.service.impl;
import com.example.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/** * @author alan smith * @version 1.0 * @date 2020/3/26 9:59 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Autowired
private JedisPool jedisPool;
@Override
public String getString(String key) {
String val = null;
// 得到Jedis对象
Jedis jedis = jedisPool.getResource();
// 判断key是否存在
if (jedis.exists(key)) {
log.info("查询Redis中的数据");
val = jedis.get(key);
} else {
// 如果不存在,
log.info("查询的是MySQL数据库");
val = "key from MySQL";
jedis.set(key, val);
}
// 关闭连接
jedis.close();
return val;
}
@Override
public void clear() {
Jedis jedis = jedisPool.getResource();
jedis.flushDB();
log.info("清空当前数据库");
jedis.close();
}
}
测试类
package com.example;
import com.example.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import redis.clients.jedis.JedisPool;
@Slf4j
@SpringBootTest
class DemoBootJedisApplicationTests {
@Autowired
private JedisPool jedisPool;
@Autowired
private UserService userService;
@Test
void contextLoads() {
String result = null;
log.info("jedisPool:{}", jedisPool.toString());
userService.clear();
result = userService.getString("name");
log.info("第一次:{}", result);
result = userService.getString("name");
log.info("第二次:{}", result);
}
}
# 测试Hash类型操作
编写PO类
package com.example.po;
import lombok.*;
import java.io.Serializable;
/** * @author alan smith * @version 1.0 * @date 2020/3/26 8:35 */
@ToString
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private String id;
private String name;
private Integer age;
}
接口添加抽象方法
/** * 获取一个对象 * 需求:传入一个ID,根据ID查询对象 * 如果Redis中存在,直接返回给用户结果 * 如果Redis中不存在,查询MySQL,结果存入Redis并返回 */
public User selectById(String id);
实例添加方法实现
@Override
public User selectById(String id) {
String key = "user:" + id;
Jedis jedis = jedisPool.getResource();
//实现业务逻辑
User user = null;
if (jedis.exists(key)) {
log.info("查询的是Redis的数据");
Map<String, String> map = jedis.hgetAll(key);
// 可以引入工具类简化
user = new User();
user.setId(map.get("id"));
user.setName(map.get("name"));
user.setAge(Integer.valueOf(map.get("age")));
} else {
log.info("查询的是MySQL的数据");
user = new User();
user.setId(id);
user.setName("我妻由乃");
user.setAge(16);
//存入redis
Map map = new HashMap();
map.put("id", user.getId());
map.put("name", user.getName());
map.put("age", user.getAge());
jedis.hmset(key, map);
}
jedis.close();
return user;
}
SpringBoot 整合 Lettuce
编写缓存配置类 RedisConfig 用于调优缓存默认配置,RedisTemplate 类型 <mark>兼容性更高</mark>
大家可以看到 redisTemplate() 这个方法中用
JacksonJsonRedisSerializer
换掉了 Redis 默认的序列化方法:JdkSerializationRedisSerializer
类比理解
- jdbcTemplate 是对 JDBC 的进一步封装
- RedisTemplate 是对 Redis 进行了进一步封装(
早期:jedis,如今:lettuce)…
Spring-data-redis 中序列化类有以下几个:
- GenericToStringSerializer:可以将 <mark>任何对象</mark> 泛化为字符串并 <mark>序列化</mark>
- Jackson2JsonRedisSerializer:序列化 <mark>Object对象</mark> 为 <mark>Json字符串</mark> (与jacksonJsonRedisSerializer相同)
- JdkSerializationRedisSerilizer:序列化 <mark>java对象</mark>
被序列化对象必须实现 Serializable 接口,被序列化除属性内容还有其他内容,长度长且不容易阅读- StringRedisSerializer:<mark>简单的字符串序列化</mark>
# RedisConfig 配置类
新建springboot项目
依赖
<!--默认是lettuce客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redis依赖commons-pool,这个依赖一定要添加,否则会丢失资源-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<mark>spring-boot-starter-data-redis 即 Spring Data</mark>
.
Spring Data概述:
Spring Data 是 Spreing 的一个子项目,<mark>用于简化数据库访问</mark>,支持NoSQL和关系数据库存储
.
封装链:(后者对前者封装)
hibernate ⇒ JPA ⇒ SpringData
application.yml
spring:
redis:
port: 6379
password: root
host: 192.168.64.33
lettuce:
pool:
max-active: 8 #连接池最大连接数(使用负值表示没有限制)
max-idle: 8 #连接池中最大空闲连接
min-idle: 0 #连接池中最小空闲连接
max-wait: 1000 #连接池最大阻塞等待时间(使用负值表示没有限制)
shutdown-timeout: 100 #关闭超时时间
RedisConfig
RedisTemplate 介绍
Spring Data 提供了 RedisTemplate 模板
.
封装了 Redis 连接池管理的逻辑,业务代码无需关心获取和释放的连接逻辑。
.
Spring Redis 同时支持了 Jedis、Jredis、rjc 客户端操作
在RedisTemplate 中提供了几个常用的接口方法的使用,分别是:
- valueOps
- listOps
- setOps
- zSetOps
- list
package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.lang.reflect.Method;
/** * Redis的配置类 * <p> * 暂时只需要 RestTemplate * <p> * “非必要” 和继承 CachingConfigurerSupport内容后面讲 * * @author alan smith * @version 1.0 * @date 2020/3/26 14:26 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
/** * (非必要) * 自定义缓存key的生成策略。 * 默认的生成策略是看不懂的(乱码内容) * 通过Spring的依赖注入特性,进行自定义的配置,并且此类是一个配置类,可以更多程度的自定义配置 * * @return */
@Bean
@Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
// 类名+方法名+参数1+参数2+...
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
// 类名
sb.append(target.getClass().getName());
// 类名+方法名
sb.append(method.getName());
// 类名+方法名+参数1+参数2+...
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
// redis的startter提供
@Autowired
private LettuceConnectionFactory factory;
/** * (非必要) * 缓存配置管理器 * * @return */
@Override
public CacheManager cacheManager() {
// 以锁写入的方式创建RedisCacheWriter对象
RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
// 创建默认缓存配置对象
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheManager cacheManager = new RedisCacheManager(writer, config);
return cacheManager;
}
/** * 把任何数据保存到redis中时,都需要进行序列化 * 默认使用 JdkSerializationRedisSerializer 进行数据序列化。 * 所有的key和value还有hashkey和hashvalue的原始支付前,都加了一串字符。 * <p> * 而下面改为 Jackson2JsonRedisSerializer * * @return */
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// (下面)在使用注解@Bean返回RedisTemplate的时候,同时配置hashKey与hashValue的序列化方式
/*可忽略(start)*/
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// 需要依赖:jackson-databind
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// value序列化采用 jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
/*可忽略(end)*/
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
# 测试 String 类型操作
沿用 Jedis 的 service 类
这里直接写实例了
实例
package com.example.service.impl;
import com.example.po.User;
import com.example.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Set;
/** * @author alan smith * @version 1.0 * @date 2020/3/26 15:24 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public String getString(String key) {
log.info("RedisTemplate测试:");
if (redisTemplate.hasKey(key)) {
return (String) redisTemplate.opsForValue().get(key);
} else {
String val = "RedisTemplate模板学习 lettuce 客户端";
log.info("MySQL中查询出来的:{}", val);
log.info("结果存入redis");
// ops operation
// value 就是 String
redisTemplate.opsForValue().set(key, val);
return val;
}
}
@Override
public void clear() {
Set<String> keys = redisTemplate.keys("*");
redisTemplate.delete(keys);
}
@Override
public User selectById(String id) {
return null;
}
}
测试类
package com.example;
import com.example.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@Slf4j
@SpringBootTest
class DemoBootLuttuceApplicationTests {
@Autowired
private UserService userService;
@Test
void contextLoads() {
userService.clear();
log.info("清空数据");
String val1 = userService.getString("name");
log.info("val1={}", val1);
String val2 = userService.getString("name");
log.info("val2={}", val2);
}
}
# 测试Hash类型操作
同样,沿用上面 User 实体类
和 service 接口 的 select 方法
直接添加实例方法
@Override
public User selectById(String id) {
//redisTemplate.hasKey(key) 判断整个key是否存在
//如 user:id , user:1 , user:2
if(redisTemplate.opsForHash().hasKey("user", id )) {
return (User) redisTemplate.opsForHash().get("user", id);
}else {
// 查询数据库
log.info("查询MySQL数据库");
User u = new User();
u.setId(id);
u.setName("三上悠亚");
u.setAge(18);
// 存入缓存
/* h 用户实体 "user" hk 用户主键 id hv 整个对象 u */
redisTemplate.opsForHash().put("user", id , u);
return u ;
}
}
测试
@Test
void testHash() {
userService.clear();
log.info("清空数据");
User user1 = userService.selectById("0010");
log.info("user1={}", user1);
User user2 = userService.selectById("0010");
log.info("user2={}", user2);
}
<mark>注意,这里 key、value 之所以是“明文”存储</mark>
是因为我们配置类里面重新制定了 序列化的引擎: Jackson2JsonRedisSerializer
案例
# 限制登录功能
需求描述:
- 用户在2分钟内,仅允许输入错误密码5次
- 如果超过次数,限制登录1小时(需要每登录失败,都给相应的提示)