[java]Redis

news2024/11/24 23:18:17

关于Redis

Redis是一款基于内存的,使用K-V结构存储数据的NoSQL非关系型数据库。

基于内存的:Redis读写数据时,都是在内存中进行读写的,所以,读写效率非常高!另外,Redis会自动的将所管理的数据同步到硬盘上,并且,在每次启动时也会自动从硬盘上将数据同步到内存中,所以,即使计算机重启,Redis中的数据基本不上会丢失

mysql是储存在磁盘上的,硬盘的特征是永久储存,不会因为断电而丢失数据,而内存会因为断电数据全部丢失。内存条是直接和cpu交换数据的。cpu是处理数据的一个硬件,负责运算,cpu的三大核心组成部分:第一,运算器,第二,控制器,第三,高速缓存。控制器是协调计算机的作用,高速缓存是内置的一个小小的空间,用来储存中间结果的。什么是中间结果?比如2+8+5=15,我们运算的时候会先用2+8=10,再用10+5=15,这个中间的10就是中间结果。运算器就是负责算术运算和逻辑运算,逻辑运算就是是和非,或者是和否、对和错这类运算,所以cpu是负责整个计算机的运算。那cpu的数据是从哪里来的?cpu是获取的是内存中的数据。如果磁盘中的数据需要被运算,则需要先加载到内存中,在被cpu处理。所以我们的程序当中需要使用流,流是连接了内存和内存以外数据的管道,比如我们读硬盘上的数据得用流,读网络上的数据得用流。所以流一边连接了内存,一边连接了硬盘和网络。因为cpu只能解取内存上的数据,以至于内存还有一个特点,它是整个计算机硬件系统里面,读写效率最高的存储设备。

使用K-V结构:在Redis中的数据,每个数据都有一个Key,则写入数据时需要指定数据的Key,读取数据时是根据Key找到对应的数据。

NoSQL:可以理解为No SQL,或No Operation SQL,总之,就是与SQL语句无关,因为只需要根据Key就可以访问数据。

数据库:数据的仓库,在没有明确的说明之前,通常,指的就是各种关系型数据库,例如MySQL等。

非关系型数据库:存储在Redis中的所有数据之间并没有任何关系。

什么是关系型数据库呢?例如像mysql这种,一张库里面可以有多张表,表是有表的结构的,每种数据有数据属性,我们都可以通过表把它表现出来。同时表和表之间有关联,存在数据与数据之间的关联,这种就称之为关系型数据库,

Redis的主要作用是缓存数据,通常的表现为:将关系型数据库(例如MySQL)中的数据取出,存入到Redis中,后续,当程序需要读取这些数据时,不再从关系型数据库中读取,而是优先从Redis中读取。

由于Redis是基于内存的,所以,读写效率远高于基于硬盘的关系型数据库,则Redis的查询效率非常高,单次查询耗时更短,就可以承受更多的查询访问量,并减少了对关系型数据库的访问,从而起到“保护”关系型数据库的作用!

因为数据库查询量太大也是会崩的,崩了不能读写,项目也就相当于废了,所以数据库不能崩,而redis可以分摊它的压力,从而起到保护作用。

Redis中的数据类型

Redis中的经典数据类型有5种,分别是:string(一般值,例如字符串、数值等可以字面表示的) / list / set / hash(对象,对应Java中的Map) / z-set

list就是列表,有序的。set就是相当于java中的set,也有无序性的特征,无序的同时效率也会更快。hash相当于是java中的对象,或者说对应Java中的Map。z-set是有序的set。注意在java中不是所有的set都是无序的,只有hashset是无序的,LinkedHashSet都是有序的。

另外,还有:bitmap / hyperloglog / Geo / Stream

 

 

Redis中的常用命令

 在终端窗口中,可以通过redis-cli命令登录Redis控制台,例如:

 当提示符变成 127.0.0.1:6379> 后,表示已经登录了Redis控制台,则可以使用Redis的相关命令:

  • set KEY VALUE:存入数据,例如:set username1 zhangsan,如果反复使用同一个KEY执行此命令,后续存入的值会覆盖前序存入的值,相当于“修改”,所以,此命令既是新增数据的命令,也是修改数据的命令

 

  • get KEY:取出数据,例如:get username1,如果KEY存在,则取出对应的数据,如果KEY不存在,则返回(nil),相当于Java中的null

 

  • keys PATTERN:根据模式(PATTERN)获取KEY,例如:keys username1,如果模式匹配的KEY是存在的,则返回匹配的KEY,如果不存在,则返回(empty list or set),在模式中,可以使用星号(*)作为通配符,例如:keys username*,将返回所有以username作为前缀的KEY的集合,甚至 ,你可以使用keys *匹配所有的KEY

    • 注意:在生产环境中,禁止使用此命令!

 

  • del KEY [KEY ...]:删除或批量删除数据,例如:del username1,或del username2 username3 username4,将返回成功删除的数据量

  • flushdb:清空当前数据库

 更多命令可参考:https://www.cnblogs.com/antLaddie/p/15362191.html

Redis中的List类型的数据

在Redis中,list类型的数据是一个先进后出、后进先出的栈结构的:

 

在学习Redis时,你应该把Redis中的list结构想像成一个在以上图例的基础上旋转了90度的栈!

在操作Redis中的list时,可以从左侧进行压栈或弹栈的操作,以压栈为例:

 还可以从右侧进行压栈或弹栈的操作,以压栈为例:

并且,从Redis中读取list数据时,都是从左至右读取,通常,为了更加迎合大多人使用列表数据的习惯,大多情况下会采取“从右侧压入数据”。

在Redis中的list数据,每个元素都同时拥有2个下标,一个是从左至右、从0开始顺序递增编号的,另一个是从右至左、从-1开始递减编号的,例如:

 

Redis编程

 在Spring Boot项目中,实现Redis编程需要添加spring-boot-starter-data-redis依赖项:

<!-- Spring Boot支持Redis编程的依赖项 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

在读写Redis中的数据时,需要使用RedisTemplate工具类的对象,通常,会使用配置类的@Bean方法来配置RedisTemplate,则后续可以任何组件类通过自动装配得到RedisTemplate,然后再调用相关API实现Redis中数据的读写!

在项目的根包下创建config.RedisConfiguration类,并配置RedisTemplate

@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(RedisSerializer.json());
        return redisTemplate;
    }

}

其中:

redisTemplate.setKeySerializer(RedisSerializer.string());//序列化为字符串
key的序列话器。在程序访问redis的数据的时候,程序在一台服务器上,redis在另外一台服务器上,中间通过网络传输的时候是通过二进制传输,所以需要设置序列化器来还原数据的类型。

 

redisTemplate.setValueSerializer(RedisSerializer.json()); //序列化为json

因为值的类型有很多,有可能是类,有可能是一个品牌,所以需要用json。实现一个对象通过json往redis里面存,redis在通过json取出来对应的对象。

redisTemplate.setConnectionFactory(redisConnectionFactory);//连接工厂

相当于我们再用数据库编程配置datasource:

在我们添加依赖的时候,这个依赖项自动帮我们配了一个这个连接工厂,RedisConnectionFactory redisConnectionFactory。所以我们所spring容器里面有,而当前的方法又是自动被spring容器调用的方法,就可以在参数里直接声明,这也是一种自动装配。回到之前说的什么是自动装配呢,当你的属性需要值,spring方法自动调用的参数需要值,就可以直接写进来,自动给值。

 

通过测试对redis做写读删等操作:

@SpringBootTest
public class RedisTests {

    // 如果操作与值相关,需要获取XxxOperations才能调用对应的API
    // -- 例如存入或取出字符串、对象类型的值时,需要先获取ValueOperations对象
    // 如果操作与值无关,直接调用RedisTemplate的API即可
    // -- 例如执行keys或delete时,直接调用RedisTemplate的API,并不需要事先获取XxxOperations对象
    @Autowired
    RedisTemplate<String, Serializable> redisTemplate;

    // 存入字符串类型的值
    @Test
    void setValue() {
        ValueOperations<String, Serializable> ops = redisTemplate.opsForValue();
        ops.set("username1", "张三");
        System.out.println("向Redis中存入数据,完成!");
    }

    // 取出字符串类型的值
    @Test
    void getValue() {
        ValueOperations<String, Serializable> ops = redisTemplate.opsForValue();
        String key = "username1";
        Serializable username1 = ops.get(key);
        System.out.println("根据Key=" + key + "取出数据:" + username1);
    }

    // 存入对象值
    @Test
    void setObjectValue() {
        ValueOperations<String, Serializable> ops = redisTemplate.opsForValue();
        Brand brand = new Brand();
        brand.setId(666L);
        brand.setName("华为");
        ops.set("brand666", brand);
        System.out.println("向Redis中存入数据,完成!");
    }

    // 取出对象值
    @Test
    void getObjectValue() {
        ValueOperations<String, Serializable> ops = redisTemplate.opsForValue();
        String key = "brand666";
        Serializable serializable = ops.get(key);
        Brand brand666 = (Brand) serializable;
        System.out.println("根据Key=" + key + "取出数据:" + brand666);
    }

    // 使用keys获取各个Key
    @Test
    void keys() {
        String pattern = "*";
        Set<String> keys = redisTemplate.keys(pattern);
        System.out.println(keys);
    }

    // 删除某个数据
    @Test
    void delete() {
        String key = "username1";
        Boolean delete = redisTemplate.delete(key);
        System.out.println("根据Key=" + key + "执行删除,结果:" + delete);
    }

    // 删除多个数据
    @Test
    void deleteBatch() {
        Set<String> keys = new HashSet<>();
        keys.add("username2");
        keys.add("username3");
        keys.add("username4");
        keys.add("username5");
        keys.add("username6");
        Long delete = redisTemplate.delete(keys);
        System.out.println("根据Keys=" + keys + "执行删除,结果:" + delete);
    }

    // 写入list数据
    @Test
    void rightPush() {
        List<Brand> brandList = new ArrayList<>();
        for (int i = 1; i <= 8; i++) {
            Brand brand = new Brand();
            brand.setId(i + 0L);
            brand.setName("测试品牌" + i);
            brandList.add(brand);
        }

        ListOperations<String, Serializable> ops = redisTemplate.opsForList();
        String key = "brandList";
        for (Brand brand : brandList) {
            ops.rightPush(key, brand);
        }
        System.out.println("存入list数据,完成!");
    }

    // 读取list数据
    // 起始位置和结束位置都可以使用正数的下标或负数的下标
    // 但是,必须保证起始位置对应的元素在结束位置的元素的左侧
    @Test
    void range() {
        String key = "brandList";
        long start = 0;
        long end = -1;
        ListOperations<String, Serializable> ops = redisTemplate.opsForList();
        List<Serializable> list = ops.range(key, start, end);
        System.out.println("从Redis中读取list完成,数据量:" + list.size());
        for (Serializable serializable : list) {
            System.out.println(serializable);
        }
    }

}

 

 

 控制台看序列化进去的json的数据比较乱:


可以用这个工具可视化查看redis里面的数据:

 

 json数据也可以很清楚的看见:

 

案例,将查询从数据库查改为从redis里面查(以下只为简单案例,时间开发为建立dao层来专门做redis的数据访问):

 

 

 

关于Key的格式

通常,在使用Redis时,建议使用多段名称组成的Key,并且,建议使用冒号(:)作为分隔符号!

在绝大部分Redis可视化管理工具(例如Another Redis Desktop Manager)中,默认根据冒号作为分隔符,将相同前缀的Key显示在同一个“文件夹”中。

注意:即使不使用冒号,改为使用其它符号,也是可以的,并且,大多软件可以设置分隔符号,例如:

 

 无论使用什么符号作为Key的多段名称中间的分隔符,对于读写Redis数据,并没有任何影响!

关于Key的定义,应该是多层级的,并且,应该保证同类的数据一定具有相同的组成部分,不同类的数据一定与其它数据能明确的区分开来!

例如:

  • 品牌数据详情:brand:item:1brand:item:2

  • 品牌列表:brand:list

  • 类别数据详情:category:item:1category:item:2

  • 类别列表:category:list

 

使用Redis时的数据一致性问题

 

在开发实践中,数据最终都是保存在关系型数据库(例如MySQL)中的,同时,为了提高查询效率、保护关系型数据库,通常会将某些数据从关系型数据库中取出并存入到缓存服务器(例如Redis服务器)中,后续,将优先从缓存服务器中读取数据!

由于在关系型数据库和缓存服务器上都存储了数据,如果某个数据发生了变化,通常是修改关系型数据库中的数据,此时,如果缓存中的数据没有及时更新,并仍从缓存中获取数据,则获取到的数据是不准确的!

如果出现了关系型数据库中的数据与缓存中的数据不同的问题,则称之为“数据一致性问题”,即2个或多个不同的存储位置中,本应该相同的数据并不相同。

其实,解决数据一致性问题的主要做法就是:更新关系型数据库中的数据时,一并更新缓存中的数据!

关于数据一致性问题:

  • 并不是所有数据都需要保证“实时一致性”,即:当关系型数据库中的数据发生变化后,并不一定需要马上更新缓存中的数据,此时,缓存中的数据是“不准确的”,但是,某些数据不并不需要完全准确

    • 例如:车票的余量(在列表中显示的值)

  • 某些数据的修改频率可能非常低,这类数据在绝大部分时间里都不会出现“数据一致性”问题

    • 例如:商品的类别

  • 某些数据的修改频率可能非常高,这类数据可能一开始就不会放在缓存中,也就没存在“数据一致性”问题

  • 某些数据的查询频率可能非常低,这类数据可能一开始就不会放在缓存中,也就没存在“数据一致性”问题

    • 例如:用户三年前的订单

 

关于数据一致性问题的解决方案:

  • 即时更新:更新关系型数据库中的数据的同时,也更新缓存中的数据

    • 可能需要考虑分布式事务

  • 周期性更新:更新关系型数据库中的数据时,不更新缓存中的数据,而是每隔一段时间更新一次缓存中的数据

  • 手动更新:更新关系型数据库中的数据时,不更新缓存中的数据,而是由管理人员明确执行“更新缓存”的操作时才更新缓存中的数据

 

 

使用ApplicationRunner实现缓存预热

当项目刚刚启动时,就直接从关系型数据库中读取数据并写入到缓存中,这种做法就称之为“缓存预热”。

在Spring Boot项目中,可以自定义组件类,实现ApplicationRunner接口,重写其中的run()方法,此方法会在项目启动后的第一时刻就自动执行!

例如:在项目的根包下创建preload.CachePreload类,在类上添加@Component注解,并实现ApplicationRunner接口后重写其中的方法:

@Component
public class CachePreload implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("CachePreload.run()");
    }
    
}

你可以在以上run()中编写任何你认为需要在项目启动后就自动执行的任务!同时,由于以上类是一个组件类,所以,在类中你可以按需自动装配任何在Spring容器中的对象!

计划任务

 计划任务指的是:在满足一定条件下,会周期性执行的任务,通常,可能是每过多长时间就执行一次,或到了某个特定的时间点就执行一次。

计划任务可能是比较耗时的,在Spring Boot项目中,默认不允许执行计划任务,需要在配置类上添加@EnableScheduling注解,以启用计划任务!

则在项目中的根包下创建config.ScheduleConfiguration类,在类上添加@Configuration注解,并添加@EnableScheduling注解:

@Configuration
@EnableScheduling
public class ScheduleConfiguration {
}

然后,在任何组件类中,自定义方法(公有的、void返回值类型、无参数列表),并在方法上添加@Scheduled注解,则此方法就是一个计划任务方法,然后,配置注解参数,以决定什么时候执行计划任务,例如:

@Slf4j
@Component
public class CacheSchedule {

    // fixedRate:执行频率,以[上一次执行开始时的时间]来计算一次的执行时间,以毫秒为单位
    // fixedDelay: 执行间隔,从[上一次执行结束时的时间]来计算下次的执行时间,以毫秒为单位
    @Scheduled(fixedRate = 5 * 1000)
    public void xxx() {
        log.debug("CacheSchedule.xxx()");
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/701774.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mmdetection自定义数据集训练

目录 1. 源码下载&#xff0c;本文基于v3.0版本 2.选模型训练 2.1 先生成后面用于编译的配置文件 2.2.1 修改coco.py 2.2.2 修改class_names.py 3. 训练配置 4. 训练过程展示 1. 源码下载&#xff0c;本文基于v3.0版本 GitHub - open-mmlab/mmdetection: OpenMMLab Detec…

UART-GD32

UART-GD32 通信的概念 同步通信和异步通信 数据帧格式 波特率 使用步骤 引脚分布

图-深度优先搜索与广度优先搜索

图 在现实生活中&#xff0c;有许多应用场景会包含很多点以及点点之间的连接&#xff0c;而这些应用场景我们都可以用即将要学习的图 这种数据结构去解决 地图 我们生活中经常使用的地图&#xff0c;基本上是由城市以及连接城市的道路组成&#xff0c;如果我们把城市看做是一…

西安石油大学 C++期末考试 重点知识点+题目复习(上)

第一章 “const”关键字的用法 当使用 const 修饰变量、函数参数、成员函数以及指针时&#xff0c;以下是一些代码示例&#xff1a; 声明只读变量&#xff1a; const int MAX_VALUE 100; const float PI 3.14;保护函数参数&#xff1a; void printArray(const int arr[]…

解决vmware虚拟机,克隆修改ip后,xshell连接不上问题

1、查看网卡 ifconfig2、修改网卡配置 vim /etc/sysconfig/network-scripts/ifcfg-ens32 改成与上图一样 修改后 3、重启reboot则解决

STM32F407 串口配置步骤

介绍STM32F407串口配置步骤&#xff0c;完成串口的数据发送与接收、实现中断接收&#xff0c;支持printf重定向。 STM32F407 串口配置说明 STM32F4 的串口资源相当丰富的&#xff0c;功能也相当强劲&#xff0c;STM32F407ZGT6 最多可提供 6 路串口&#xff0c;有分数波特率发…

【Junit 单元测试】

Junit 单元测试 笔记记录 1. Junit介绍2. 使用Junit3. 常用注解4. 断言使用 1. Junit介绍 2. 使用Junit 1.导入依赖 <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>tes…

C语言-基础语法学习-4 字符串

目录 字符串C语言字符串及相关函数定义字符数组和字符串的区别sizeof()和strlen()的区别动态开辟字符串野指针常用字符串函数 字符串 C语言字符串及相关函数 在C语言中&#xff0c;字符串是由字符数组表示的一系列字符序列。C语言提供了一些函数来处理字符串&#xff0c;使我…

centos磁盘扩容

解释 PE - 物理块&#xff08;Physical Extent&#xff09; 硬盘上有很多实际物理存在的存储块PV - 物理卷 &#xff08;Physical Volume&#xff09; 物理卷处于最底层&#xff0c;它可以是实际物理硬盘上的分区&#xff0c;也可以是整个物理硬盘(相当于单独做一个分区)&…

CommonJS 和 ES6 module

本文主要自己觉得要记录的点记录下来,不耽误大家时间&#xff0c;会持续更新。 Module对象 Module {id: xxx/demo/1.js, //加载文件的绝对路径path: xxx/demo,// 加载文件所在目录的绝对路径exports: [Function (anonymous)],filename: xxx/demo/1.js,加载文件的绝对路径load…

B/S和C/S详解(嵌入式学习)

B/S和C/S详解 1. C/S1.1 概念1.2 C/S架构的特点 2. B/S2.1 概念2.2 B/S架构的特点2.3 相对于传统的C/S架构的优势 3. B/S架构详解4. B/S架构怎么用&#xff08;CGI、Lighttpd&#xff09; 1. C/S 1.1 概念 C/S&#xff08;Client/Server&#xff09;是一种计算机网络架构模式…

【C++】 Qt-页面布局

文章目录 布局组件和布局按钮练习-用户信息页面布局准备工作设置性别设置年龄设置生日设置邮箱后缀 设置头像创建文件写入文件清空表单信息Buddy&#xff08;伙伴&#xff09;关系Tab顺序 布局组件和布局按钮 Qt的UI设计器中提供了丰富的布局管理功能&#xff0c;组件面板里有…

高效简单解决滑动验证码

前言 做爬虫总会遇到各种各样的反爬限制&#xff0c;其中移动验证码是很重要且常见的一环&#xff0c;今天总结下如何高效破解他的方法&#xff0c;例如下图&#xff1a; 解决思路与方法 首先先来分析下&#xff0c;核心问题其实是要怎么样找到目标缺口的位置&#xff0c;一…

Windows服务启动exe无界面终极解决方案

1、前言 我这个方案&#xff08;C#操作&#xff09;是彻底解决【从Windows服务启动程序exe&#xff0c;程序无界面】问题的终极解决方案&#xff0c;终极方案&#xff0c;绝对的终极方案&#xff0c;本来打算收钱的&#xff0c;还是算了&#xff0c;你们也不容易&#xff0c;关…

网络安全(自学笔记)

如果你真的想通过自学的方式入门web安全的话&#xff0c;那建议你看看下面这个学习路线图&#xff0c;具体到每个知识点学多久&#xff0c;怎么学&#xff0c;自学时间共计半年左右&#xff0c;亲测有效&#xff08;文末有惊喜&#xff09;&#xff1a; 1、Web安全相关概念&am…

Redis 性能管理/优化 缓存雪崩/击穿/穿透

---------------------- Redis 性能管理 ---------------------------------------- ----- 查看Redis内存使用 ----- info memoryredis-cli -a abc123 info memory ----- 内存碎片率 ----- used_memory_rss&#xff1a;是Redis向操作系统申请的内存。used_memory&#xff1a;是…

缺失concrt140.dll下载,找不到concrt140.dll的解决方法

我们平时在打开 Adobe 应用程序、Halo、Forza Horizon 5 等时&#xff0c;可能会遇到找不到 concrt140.dll。因此&#xff0c;这不是特定于某个应用程序的问题。如果没有安装正确的 DLL&#xff0c;应用程序将无法正常工作&#xff0c;因为它们的代码依赖于这些库中编写的代码。…

井盖异动监测传感器:井盖的安全守护者

随着城市化进程的不断加速&#xff0c;城市道路、人行道上的井盖扮演着重要的角色。然而&#xff0c;由于各种因素&#xff0c;如车辆冲击、材料老化等&#xff0c;井盖常常会出现异动情况&#xff0c;井盖异动不仅对行车和行人的安全构成威胁&#xff0c;还给城市基础设施的维…

C#扩展——Visual Studio 代码提示/智能提示字体大小更改方法.

声明&#xff1a;本文为个人笔记&#xff0c;用于学习研究使用非商用&#xff0c;内容为个人研究及综合整理所得&#xff0c;若有违规&#xff0c;请联系&#xff0c;违规必改。 C#扩展——Visual Studio 代码提示/智能提示字体大小更改方法. 文章目录 C#扩展——Visual Studio…

【期末总复习】医学影像学(第2版)周翔平

【选择】 1、垂体腺瘤大小范围这么界定的&#xff1f; P66&#xff0c;肿瘤直径<1cm称为垂体微腺瘤&#xff0c;>1cm称为垂体大腺瘤&#xff0c;>4cm成为巨大腺瘤。 2、当一个车祸伤病人&#xff0c;怀疑长骨骨折首选什么检查方法&#xff1f; P387 X线平片 3、…