存取数据时,是键值对的形式key-value
- 比如存一个用户信息时,key = 1001,value 可以为 用户的信息,是用json数据存的
SQL | NoSQL |
---|---|
结构化:(修改起来很麻烦) | 非结构化(健值,文档型,列表,Graph) |
关系型的,需要维护 | 无关联 |
sql查询 | 非sql |
ACID(原子性,一致性,隔离性,持久性) | BASE |
磁盘存储 | 内存存储(速度快) |
垂直 | 水平 |
键值型:value支持多种不用的数据结构
单线程,每个命令具有原子性
低延迟,速度快(基于内存,io多路复用,良好的编码)
支持数据持久化,定期存磁盘中
支持主从集群,分片集群
支持多语言客户端
基本类型:不能重复
string
hash
list
set
sortedSet
特殊类型
GEO
Bitmap
Hyperlog
还有些其他类型
keys 模糊查询
del 删除命令
exists 判断key是否存在
expire 设置有效期
ttl 查看key剩余的有效期
基本操作
set :添加或者修改
get:获取值
exists:判断key是否存在
strlen:获取长度
批量设置
计数器
incr :自增+1
incrby:设置自增的步长
derc:自减-1
decby:设置自减的步长
incrbyfloat :必须设置指定增长的步长
缓存对象
常规计数
以为redis处理命令是单线程的,所以执行命令的过程是原子性的。因此 String 数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。
# 初始化文章的阅读量
> SET aritcle:readcount:1001 0
OK
#阅读量+1
> INCR aritcle:readcount:1001
(integer) 1
#阅读量+1
> INCR aritcle:readcount:1001
(integer) 2
#阅读量+1
> INCR aritcle:readcount:1001
(integer) 3
# 获取对应文章的阅读量
> GET aritcle:readcount:1001
"3"
分布式锁
key和string没啥变化,主要是value是一个无序的字典,是松散的
hash结构将,String中的json变成键值类型
key | field | value |
---|---|---|
heima:user:1 | name | jack |
age | 21 |
类似一中双向链表,支持正向检索和反向检索
特征:
常用于存一些有序的数据
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。
可排序
元素不可重复
查询速度快
通常用于实现排行榜功能
注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可
用idea创建一个java的maven项目
引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
建立链接
//建立链接
JedisPool pool = new JedisPool("服务器地址", 6379);
Jedis jedis = pool.getResource();
//输入密码
jedis.auth("0806");
//选择库
jedis.select(0);
执行redis操作
//进行操作
jedis.set("name","lmg");
jedis.set("age", String.valueOf(12)); //默认填字符串要转换
System.out.println(jedis.get("name"));
System.out.println(jedis.get("age"));
Map<String,String> hash = new HashMap<>();
hash.put("name", "John");
hash.put("surname", "Smith");
hash.put("company", "Redis");
hash.put("age", "29");
jedis.hset("book:1",hash);
System.out.println(jedis.hgetAll("book:1"));
//关闭jedis
if (jedis !=null){
jedis.close();
}
建立一个工具类
public class JedisConnetionFactory {
private static final JedisPool jedisPool;
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//最大链接数
jedisPoolConfig.setMaxTotal(8);
//最大空闲链接
jedisPoolConfig.setMaxIdle(8);
//最小空闲链接
jedisPoolConfig.setMinIdle(8);
//设置最长等待时间,5s
jedisPoolConfig.setMaxWait(Duration.ofSeconds(5));
jedisPool = new JedisPool(jedisPoolConfig,"服务器地址",6379,1000,"0806");
}
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
main函数中使用工具类创建jedis
Jedis jedis = JedisConnetionFactory.getJedis();
System.out.println(jedis.get("name"));
jedis.close();
支持的功能
提供了对不同Redis客户端的整合(Lettuce和Jedis)
提供了RedisTemplate统一API来操作Redis
支持Redis的发布订阅模型
支持Redis哨兵和Redis集群
支持基于Lettuce的响应式编程
支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
支持基于Redis的JDKCollection实现
1、引入依赖
Spring-redis依赖
<!-- redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- pool的依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、配置redis的信息
spring:
# redis 配置
redis:
# 地址
host: .服务器地址
# 端口,默认为6379
port: 6379
# 密码,密码用双引号括起来,血与泪的排查(重置服务器的代价)
password: "0806"
# 连接超时时
timeout: 5200
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
未修改时
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
//创建RedisTemplate对象
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
//创建链接工厂
redisTemplate.setConnectionFactory(connectionFactory);
//创建序列化工具,这个是value的
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
//设置key 的序列化
redisTemplate.setKeySerializer(RedisSerializer.string());
//设置value的序列化
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashValueSerializer(serializer);
//返回
return redisTemplate;
}
}
为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
写入redis序列化将对象为json
读出数据时,将json转换为对象
User user = new User("xxl",20);
//对象转json
String jsonString = JSONObject.toJSONString(user);
stringRedisTemplate.opsForValue().set("user:11",jsonString);
//json转对象
String jsonUser = stringRedisTemplate.opsForValue().get("user:11");
User user1 = Objects.requireNonNull(JSONObject.parseObject(jsonUser)).toJavaObject(User.class);
System.out.println(user1);
验证验证码的接口
/**
* 发送手机验证码,控制层
*/
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
return userService.sendCode(phone,session);
}
//业务逻辑
@Override
public Result sendCode(String phone, HttpSession session) {
//校验手机号,用hutool中的工具
boolean isValid = PhoneUtil.isMobile(phone);
//不符合返回错误
if (!isValid){
return Result.fail("手机号码格式错误");
}
//符合生成验证码
String str = RandomUtil.randomNumbers(6);
//保存用户到redis,加个前缀有助于区分,时间为两分钟
String s = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
if (s == null) {
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,str,2, TimeUnit.MINUTES);
}
//发送验证码
log.debug("发送验证码成功,验证码:{}",str);
//返回ok
return Result.ok("ok");
}
登录接口的实现
/**
* 登录功能
* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
return userService.login(loginForm,session);
}
@GetMapping("/me")
public Result me(){
UserDTO userDTO = UserHolder.getUser();
return Result.ok(userDTO);
}
//业务层
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
String phone = loginForm.getPhone();
String code = loginForm.getCode();
//1、验证手机号
if (!PhoneUtil.isMobile(phone)){
return Result.fail("手机号码格式错误");
}
//2、验证验证码,在redis中验证
String flag = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
if (flag == null || !flag.equals(code)) {
return Result.fail("验证码错误");
}
//3、判断用户是否存在
User user = this.query().eq("phone", phone).one();
//4、不存在创建一个新用户
if (user == null){
user = createUser(phone);
}
//5、保存用户数据到redis中,用hash存,token用uuid生成,
String token = UUID.randomUUID().toString(true);
//6、将user对象转化为hashmap存,因为在转map的方法中默认两个类型都是String所以需要转化
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
//法1、高级
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
CopyOptions.create().setIgnoreNullValue(true)
.setFieldValueEditor((fileldName,fieldVaule) -> fieldVaule.toString()));
//法2、笨方法
// HashMap<String,String> map = new HashMap<>();
// map.put("id",userDTO.getId().toString());
// map.put("nickName",userDTO.getNickName());
// map.put("icon",userDTO.getIcon());
//7、value用来保存用户信息
stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token,userMap);//键是uuid+token
//设置有效期,长时间未操作,清除
stringRedisTemplate.expire(LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES);
//返回token信息
return Result.ok(token);
}
private User createUser(String phone) {
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
this.save(user);
return user;
}
登录校验,保证用户一直在登录状态
使用拦截器,对请求进行拦截。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
//order为 0 的拦截所有地址,用于做token的更新和保存数据到ThreadLocal,并对redis的token有效期刷新
//order为 1 的拦截要登录才能使用的功能,判断是否登录
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/shop/**",
"/voucher/**",
"/upload/**",
"/shop-type/**",
"/user/code",
"/blog/hot",
"/user/login"
).order(1);
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);
}
}
public class RefreshTokenInterceptor implements HandlerInterceptor {
//无法通过注解进行构建,因为这个类是我们自己构建的,不是Spring构建的,可以通过构造器注入
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1、根据前端的请求头获取token
String token = request.getHeader("authorization");
if (CharSequenceUtil.isBlank(token)){
return true;
}
//2、通过token获取用户信息
Map<Object, Object> map = stringRedisTemplate.opsForHash()
.entries(LOGIN_USER_KEY + token);
//3、判断用户是否存在
if (map.isEmpty()){
return true;
}
//4、将map转换为UserDTO,hutool里面的工具
UserDTO userDTO = BeanUtil.fillBeanWithMap(map, new UserDTO(), false);
//5、存在保存信息到ThreadLocal
UserHolder.saveUser(userDTO);
//6、刷新token的有效期,达到用户一直访问,一直有效
stringRedisTemplate.expire(LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
//放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1、判断是否要拦截
if (UserHolder.getUser() == null){
response.setStatus(401);
return false;
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}