Redis

Redis

MySQL的数据是始终存在硬盘上的,对于我们的用户信息这种不需要经常发生修改的内容,使用MySQL存储固然可以,但是如果是快速更新或是频繁使用的数据,比如微博热搜,双十一秒杀,这些数据不仅要求服务器需要提供更高的响应速度,而且还需要面对短时间内上百万,甚至上千万次的询问,而MySQL的硬盘IO读写性能完全不能满足上面的要求,能搞满足上述需求的只有内存,因为速度远高于磁盘IO

因此,我们需要寻找一种更好的解决方案,来存储上述这类特殊资源,弥补MySQL的不足,以应对大数据时代的重重考验

NoSQL概论

NoSQL全称是 Not Only SQL , 它是一种非关系型数据库,相比传统 SQL 关系型数据库,它:

  • 不保证关系数据的ACID特性
  • 并不遵循SQL标准
  • 消除数据之间关联性

优势:

  • 远超传统关系型数据库的心梗
  • 非常易于扩展
  • 数据模型更加灵活
  • 高可用

因此,NoSQL非常适用于高并发海量数据

Redis是NoSQL数据库的一种,是键值存储数据库: 所有的数据都是以建值方式存储的,类似于我们之前学过的HashMap,使用起来非常方便,性能也非常高

Redis是一个开源的键值存储数据库,所有的数据全部存放在内存中,它的性能大大高于磁盘IO,并且也可以支持数据持久化,它还可以支持横向扩展,主从复制等

实际生产中,我们一般配合使用Redis和MySQL,发挥它们各自的优势,取长补短

Redis安装和部署

Redis下载: https://github.com/tporadowski/redis/releases

下载完成后,解压缩,双击即可打开Redis

在这里插入图片描述

在这里插入图片描述

基本操作

在我们之前使用MySQL时,我们需要先在数据库中创建一张表,并定义好表的每个字段内容,最后通过 insert 语句向表中添加数据

而Redis 并不具有MySQL那样的严格的表结构,Redis是一个键值数据库,因此,可以向Map一样的操作方式,通过键值对向Redis数据库中添加数据库(操作起来类似于向一个HashMap中存放数据)

在Redis下,数据库是由一个整数索引来表示,而不是由一个数据库名称

默认情况下,连接Redis之后,会使用0号数据库,我们可以通过Redis配置文件中的参数来修改数据库总数,默认是16个

我们可以通过select语句来进行切换:

select 序号;

数据操作

我们来看看,如何向Redis数据库中添加数据

set <key> <value>
-- 一次性多个
mset [<key> <value>]...

所有存入的数据默认会以字符串的形式保存,键值具有一定的命名规范,以方便我们可以快速定位我们的数据属于哪一个部分,比如用户的数据:

-- 使用冒号来进行板块分割,比如下面表示用户XXX的信息中的name属性,值为lbw
set user:info:用户ID:name lbw

我们可以通过键值获取存入的值

get <key>

Redis还支持数据的过期时间设定

set <key> <value> EX 秒
set <key> <value> PS 毫秒

当数据达到指定时间时就会被自动删除

我们也可以单独为其他的键值对设置过期时间

expire <key>

通过下面的命令来查询某个键值的过期时间还剩多少

ttl <key> //毫秒显示
persist <key> //将key转换为永久

那么当我们向直接删除这个数据时

del <key>...

当我们向查看数据库中所有的键值时

keys *

查询某个键是否存在

exists <key>...

随机拿一个键

randomkey

将一个数据库中的键移动到另一个数据库中

move <key> 数据库序号

修改一个键为另一个键

rename <key> <新的名称>
-- 下面这个会检查新的名称是否已经存在
renamex <key> <新的名称>

如果存放的数据是一个数字,我们还可以对其进行自增自减操作

incr <key> //++
decr <key> //--
incrby <key> b //key + b

数据类型介绍

一个键值对除了存储一个String类型的值以外,还支持多种常用的数据类型

Hash

这种数据类型本质上就是一个HashMap,也就是潜逃了一个HashMap罢了,在Java中就像这样:

#Redis默认存String类似于这样
Map<String,String> map = new HashMap<>();
#RedisHash就类似于这样:
Map<String,Map<String,String>> map = new HashMap<>();

它比较适合存储这样的数据,添加一个Hash类型的数据:

hset <key> [<字段> <>]...
hset person name lbw age 10

我们可以直接获取:

hget <key> [<字段> <>]
--如果想要一次性获取所有的字段和值
hgetall <key>

判断某个字段是否存在:

hexists <key> <字段>

删除Hash中的某个字段

hdel <key> [field] [field...]

我们发现,在操作一个Hash时,实际上就是我们普通操作命令前面添加一个h,这样就能以同样的方式去操作Hash里面存放的键值对了,之力就不意义列出所有操作了

我们来看看及格比较特殊的

我们现在想要知道Hash中一共存了多少个键值对

hlen <key>

我们也可以一次性获取所有字段的值

hvals <key>

唯一需要质疑的是,Hash中只能存放字符串值,不允许出现嵌套的情况

List

像Java中的List,支持随机访问,双端操作,很像LinkedList

--向列表头部添加元素
lpush <key> <element>...
--向列表尾部添加元素
rpush <key> <element>...
--在指定元素前面/后面插入元素
linsert <key> before/after <指定元素> <element>

同样的,获取元素也非常简单

--根据下标获取元素
lindex <key> <下标>
--获取并移除头部元素
lpop <key>
--获取并移除尾部元素
rpop <key>
--获取指定范围的
lrange <key> start stop

注意,下标可以使用负数来表示从后到前数的数字

--获取列表a中的所有元素
lrange a 0 -1

push和pop还可以连着用

--从前一个数组的最后取一个出来放到另一个数据的头部,并返回元素
rpoplpush 当前数组 目标数组

Set和SortedSet

Set集合其实就像Java中的HashSet一样(HashSet本质上是利用了一个HashMap,但是Value是固定对象,仅仅是key不同)它不允许出现重复元素,不支持随机访问,但是能够利用hash表提供极高的查找效率

向Set中添加一个或多个值

sadd <key> <value>...

查看Set集合中有多少个值

scard <key>

判断集合中是否包含:

-- 是否包含指定值
sismember <key> <value>
-- 列出所有值
smembers <key>
-- 那些是集合a中有b中没有的
sdiff a b
-- 那些是集合a和集合b都有的
sinter a b 
-- 集合ab的并集
sunion a b
-- 将集合的差集存到目标集合上
sdiffstore target a b

移动指定值到另一个集合中

smove <key> 目标 value

移除操作

srem <key> <value>...

那么如果我们要求Set集合中的数据按照我们指定的顺序进行排序怎么办呢?这时可以使用SortedSet,它支持我们为每个值设定一个分数,分数的大小决定了值的位置,所以它是有序的

我们可以添加一个带分数的值:

zadd <key> [<score> <value>]...

同样的:

-- 查询有多少个值
zcard <key>
-- 移除
zrem <key> <value>...
-- 获得区间内所有
zrange <key> start stop [with scores]
-- 获得成绩区间
zrangebyscore <key> min max [withscores]
-- 统计分数段内的数量
zcount <key> start stop
-- 根据分数获取指定值的排名
zrank key value

https://www.jianshu.com/p/32b9fe8c20e1

持久化

我们知道,Redis数据库中的数据都是存放在内存中,虽然很高效,但是这样存在一个非常严重的问题,如果突然停电,那我们的数据不就全部丢失了吗?不像硬盘上的数据,断电依然能够保存

这个时候我们就需要持久化,我们需要将我们的数据备份到硬盘上,防止断电或是机器故障导致的数据丢失

持久化有两种方案:

  • 保存已存储的数据,相当于复制内存中的数据到硬盘上,需要恢复数据时直接读取即可
  • 存储我们存放数据的所有过程,需要恢复数据时,只需要重复过程即可

RDB

就是我们说的第一套方案

save
-- 注意上面这个命令是直接保存,会占用一定的时间,也可以单独开一个子进程后台执行保存
bgsave

执行后,会在服务端目录下生成一个dumb.rdb文件,而这个文件中就保存了内存中存放的资源,当服务器重启后,会自动加载里面的内容

保存后我们可以关闭服务器

shutdown

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们重启客户端后数据依然存在

我们建议隔一段时间就进行保存(最好是自动的)

我们可以在配置文件上设置自动保存,并设定一段时间内写入多少数据时,执行一次保存操作

save 300 10 # 300秒(5分钟) 内有10个写入
save 60 10000 # 60s内有10000个写入

在这里插入图片描述

配置的save使用的都是bgsave后台保存

AOF

存储过程

但是我们多久写一次日志?我们有三种策略:

  • always: 每次执行写操作都会保存一次
  • everysec: 每秒保存一次
  • no: 看系统心情保存

在这里插入图片描述
在这里插入图片描述

默认是关闭的

开启后默认是第二种策略

我们可以输入命令手动执行重写操作

bgrewriteaof

在配置文件中自动配置重写

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
  • AOF 存储速度快,消耗资源少,支持实时存储,加载速度慢,数据体积大
  • RDB加载速度快,数据体积小,存储速度慢,大量消耗资源,会产生数据丢失

事务和锁机制

和MySQL一样,在Redis中也有事务机制

-- 打开事务
multi
-- 提交事务
exec
-- 取消事务
discard

实际上整个事务是创建了一个命令队列,它不像MySQL那种在事务中也能单独取得结果,而是统一执行

Redis的锁机制是乐观锁

  • 悲观锁:禁止一切外来访问
  • 乐观锁:直接对数据进行操作,在操作时判断是否有其他人抢占资源

Redis中可以使用watch来监视一个目标,如果执行事务之前被监视目标发生了修改,则取消本次事务

watch <key>

解决aba问题,不是根据值,而是根据版本号

使用Java和Redis进行交互

既然了解了如何通过命令窗口操作Redis数据库,那么我们如何使用Java来操作呢?

这里我们需要使用到Jedis框架,它能够实现Java和Redis数据库的交互 依赖:

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.0.0</version>
</dependency>

连接Redis数据库

public static void main(String[] args) {
        //创建Jedis对象
        Jedis jedis = new Jedis("127.0.0.1",6379);
    
    	//使用之后关闭连接
        jedis.close();
    }

通过jedis对象,我们就可以直接调用命令的同名方法来执行Redis命令了

记得开始运行程序前打开redis-server .exe 就是打开服务器

//创建Jedis对象
        Jedis jedis = new Jedis("127.0.0.1",6379);
        jedis.set("test","frank");//set test frank
        System.out.println(jedis.get("test"));//get test
        jedis.close();

在这里插入图片描述

在这里插入图片描述

得到这些,Java就成功连接到了Redis

Hash类型的数据也是这样:

 public static void main(String[] args) {
        //创建Jedis对象
        Jedis jedis = new Jedis("127.0.0.1",6379);
        /*jedis.set("test","frank");//set test frank
        System.out.println(jedis.get("test"));//get test*/
        jedis.hset("user","name","francis");
        jedis.hset("user","id","1");
        jedis.hgetAll("user").forEach((k,v)-> System.out.println(k + " : " + v));
        jedis.save();
        jedis.close();
    }

列表操作:

jedis.lpush("myList","33","22","11");
        List<String> myList = jedis.lrange("myList", 0, -1);
        System.out.println(myList);
        jedis.save();
        jedis.close();

在这里插入图片描述

SpringBoot,整合Redis

我们接着来看如何在SpringBoot项目中整合Redis操作框架,只需要一个starter即可,但是它底层没有用Jedis,而是Lettuce

<!--配置Redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

或者直接用SpringBoot加载器,更方便

在这里插入图片描述

创建好项目,默认连接了本地Redis,端口号6379,0号数据库

等同于

在这里插入图片描述

等于说这里写了跟没写效果一样

starter已经给我们提供了两个默认的模板类

package org.springframework.boot.autoconfigure.data.redis;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}

类中有许多封装好的方法

在这里插入图片描述

在这里插入图片描述

得到1000后,说明已正确连接Redis

那么如何去使用这两个模板类呢?我们可以直接注入StringRedisTemplate来使用模板

@SpringBootTest
class RedisSpringBootApplicationTests {

    @Autowired
    StringRedisTemplate template;

    @Test
    void contextLoads() {
        ValueOperations<String, String> operations = template.opsForValue();
        operations.set("c","xxxxx");//设置值
        System.out.println(operations.get("c"));
        System.out.println(template.delete("c"));//删除值
        System.out.println(template.hasKey("c"));//判断是否含键
    }

}

实际上所有的值的操作都被封装到了ValueOperations对象中,而普通的键操作直接通过模板对象就可以使用了,大致使用方式其实和Jedis一致

Redis与分布式

我们学习下Redis在分布式开发场景下的应用

主从复制

在分布式场景下,我们考虑让Redis实现主从模式

主从模式,是指将一台Redis服务器的数据复制到其他服务器,前者称为主节点,后者称为从节点

数据的复制是单向的,由主到从

  • 实现了读写分离,提高了性能
  • 在写少读多的情况下,我们甚至可以安排很多从节点,这样能够大幅度分担压力,并且就算挂掉几个,其他的也能使用

输入指令,看到端口号为6001的服务器的主从状态

在这里插入图片描述

输入指令,让6002为从,6001为主

在这里插入图片描述

在这里插入图片描述

我们发现从属数量变为1

在这里插入图片描述

主从偏移量相等,说明数据转移完全

输入命令:

replicaof no one

解除6002的所有主从关系

``

实际上所有的值的操作都被封装到了ValueOperations对象中,而普通的键操作直接通过模板对象就可以使用了,大致使用方式其实和Jedis一致

Redis与分布式

我们学习下Redis在分布式开发场景下的应用

主从复制

在分布式场景下,我们考虑让Redis实现主从模式

主从模式,是指将一台Redis服务器的数据复制到其他服务器,前者称为主节点,后者称为从节点

数据的复制是单向的,由主到从

  • 实现了读写分离,提高了性能
  • 在写少读多的情况下,我们甚至可以安排很多从节点,这样能够大幅度分担压力,并且就算挂掉几个,其他的也能使用

输入指令,看到端口号为6001的服务器的主从状态
在这里插入图片描述
输入指令,让6002为从,6001为主
在这里插入图片描述
在这里插入图片描述

我们发现从属数量变为1
在这里插入图片描述

主从偏移量相等,说明数据转移完全
输入命令:

replicaof no one

解除6002的所有主从关系

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值