Redis快速入门攻略

前言:在我们日常的Java Web开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题。

NoSQL 技术

为了克服上述的问题,Java Web项目通常会引入NoSQL技术,这是一种基于内存的缓存数据库,并且提供一定的持久化功能。

NoSQL技术,泛指非关系型的数据库。随着大数据时代的到来,越来越多的网站、应用系统需要支撑海量数据存储,高并发请求、高可用、高可扩展性等特性要求,传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多能以克服的问题。由此,各种各样的NoSQL(Not Only SQL)数据库作为传统关系型数据的一个有力补充得到迅猛发展。

RedisMongoDB是当前使用最广泛的NoSQL数据库。而就Redis技术而言,它的性能十分优越,可以支持每秒十几万此的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配置,原则上可以无限扩展,让更多的数据存储在内存中,更让人欣慰的是它还支持一定的事务能力,这保证了高并发的场景下数据的安全和一致性。

Redis作为基于内存的缓存系统,常用来和服务端关系型数据库搭配使用,解决互联网应用高并发难题。

一、Redis简介

1、什么是Redis?

Redis是一个开源的底层使用C语言编写的高性能key-value存储数据库,基于内存并且可以持久化的缓存产品。Redis不仅仅支持简单的key-value类型的数据,还支持丰富的数据类型:string(字符串)、hash(哈希)、list(列表)、set(无序集合)、zset(sorted set:有序集合)。Redis性能极高 , Redisn能读的速度是110000次/s,写的速度是81000次/s 。

 Redis 是一个高性能的key-value数据库,底层数据结构是哈希表。和Memcached类似,它支持存储的value类型相对更多。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis的出现,很大程度补偿了这类key/value缓存产品memcached存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。

总结,Redis 与其他 key - value 缓存产品有以下三个特点

  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。

  • Redis支持数据的备份,即master-slave模式的数据备份。

Redis底层数据结构,建议看看:Redis底层数据结构---hash表

2、Redis缓存的作用,当数据继续增大我们需要利用主从复制/同步技术来达到读写分离

以前不使用Redis缓存框架的单节点服务器我们通常是这样的:

随着企业的发展、业务的扩展。面对海量的数据,直接使用MySql会导致性能下降,数据的读写也会非常慢。于是我们就可以搭配缓存来处理海量数据。于是现在我们是这样的:

数据库层直接与缓存进行交互,如果缓存中有数据直接返回客户端,如果没有才会从MySql中去查询并写入Redis中。从而减小了数据库的压力,提升了效率。

3、Redis为什么会这么快?

  • Redis是纯内存操作,需要的时候需要我们手动持久化到硬盘中

  • Redis是单线程,从而避开了多线程中上下文频繁切换的操作。

  • Redis数据结构简单、对数据的操作也比较简单

  • 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求

  • 使用多路I/O复用模型,非阻塞I/O

多路I/O复用的概念:

I/O 多路复用技术是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪,就是这个文件描述符进行读写操作之前),能够通知程序进行相应的读写操作

4、Redis支持主从同步/复制

数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。

5、Redis 其他功能使用场景

(1) 订阅-发布系统

Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在 Redis 中,你可以设定对某一个 key 值进行消息发布及消息订阅,当一个 key 值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

(2)事务——Transactions

谁说 NoSQL 都不支持事务,虽然 Redis 的 Transactions 提供的并不是严格的 ACID 的事务(比如一串用 EXEC 提交执行的命令,在执行中服务器宕机,那么会有一部分命令执行了,剩下的没执行),但是这个 Transactions 还是提供了基本的命令打包执行的功能(在服务器不出问题的情况下,可以保证一连串的命令是顺序在一起执行的,中间有会有其它客户端命令插进来执行)。Redis 还提供了一个 Watch 功能,你可以对一个 key 进行 Watch,然后再执行 Transactions,在这过程中,如果这个 Watched 的值进行了修改,那么这个 Transactions 会发现并拒绝执行。

二、Redis 在 Java Web 中的应用

  第二部分引用了简书博主@我没有三颗心脏  Redis【入门】就这一篇!谢谢这位大哥!

Redis 在 Java Web 主要有两个应用场景:

  • 存储 缓存 用的数据;

  • 需要高速读/写的场合使用它快速读/写

1、缓存的作用

在日常对数据库的访问中,读操作的次数远超写操作,比例大概在 1:9 到 3:7,所以需要读的可能性是比写的可能大得多的。当我们使用SQL语句去数据库进行读写操作时,数据库就会去磁盘把对应的数据索引取回来,这是一个相对较慢的过程。

如果我们把数据放在 Redis 中,也就是直接放在内存之中,让服务端直接去读取内存中的数据,那么这样速度明显就会快上不少,并且会极大减小数据库的压力,但是使用内存进行数据存储开销也是比较大的,限于成本的原因,一般我们只是使用 Redis 存储一些常用和主要的数据,比如用户登录的信息等。

一般而言在使用 Redis 进行存储的时候,我们需要从以下几个方面来考虑:

  • 业务数据常用吗?命中率如何?如果命中率很低,就没有必要写入缓存;

  • 该业务数据是读操作多,还是写操作多?如果写操作多,频繁需要写入数据库,也没有必要使用缓存;

  • 业务数据大小如何?如果要存储几百兆字节的文件,会给缓存带来很大的压力,这样也没有必要;

在考虑了这些问题之后,如果觉得有必要使用缓存,那么就使用它!使用 Redis 作为缓存的读取逻辑如下图所示:

从上图我们可以知道以下两点:

  1. 第一次读取数据的时候,读取 Redis 的数据就会失败,此时就会触发程序读取数据库,把数据读取出来,并且写入 Redis 中;

  2. 第二次以及以后需要读取数据时,就会直接读取 Redis,读到数据后就结束了流程,这样速度就大大提高了。

从上面的分析可以知道,读操作的可能性是远大于写操作的,所以使用 Redis 来处理日常中需要经常读取的数据,速度提升是显而易见的,同时也降低了对数据库的依赖,使得数据库的压力大大减少。

分析了读操作的逻辑,下面我们来看看写操作的流程

从流程可以看出,更新或者写入的操作,需要多个 Redis 的操作,如果业务数据写次数远大于读次数那么就没有必要使用 Redis。

2、高速读/写的场合

在如今的互联网中,越来越多的存在高并发的情况,比如天猫双11、抢红包、抢演唱会门票等,这些场合都是在某一个瞬间或者是某一个短暂的时刻有成千上万的请求到达服务器,如果单纯的使用数据库来进行处理,就算不崩,也会很慢的,轻则造成用户体验极差用户量流失重则数据库瘫痪,服务宕机,而这样的场合都是不允许的!

所以我们需要使用 Redis 来应对这样的高并发需求的场合,我们先来看看一次请求操作的流程图

我们来进一步阐述这个过程:

  1. 当一个请求到达服务器时,只是把业务数据在 Redis 上进行读写,而没有对数据库进行任何的操作,这样就能大大提高读写的速度,从而满足高速响应的需求

  2. 但是这些缓存的数据仍然需要持久化,也就是存入数据库之中,所以在一个请求操作完 Redis 的读/写之后,会去判断该高速读/写的业务是否结束,这个判断通常会在秒杀商品为0,红包金额为0时成立,如果不成立,则不会操作数据库;如果成立,则触发事件将 Redis 的缓存的数据以批量的形式一次性写入数据库,从而完成持久化的工作。

三、Redis的安装

Windows版访问地址:https://github.com/ServiceStack/redis-windows/tree/master/downloads

Redis 3.0版本后不需要配置环境变量之类的,把 redis-latest下载下来后找到一个合适的地方解压,就能得到如下图所示的目录(这里空格被替换成了%20...):


我们双击redis-server.exe文件,就能成功的看到 Redis 启动:

上图的提示信息告诉了我们:① Redis 当前的版本为 3.0.503;② Redis 运行在 6379 端口;③ Redis 进程的 PID 为 14748;④ 64 位。

我们可以打开同一个文件夹下的 redis-cli.exe 文件,这是 Redis 自带的一个客户端工具,它可以用来连接到我们当前的 Redis 服务器,可以看作一个shell程序,它可以发送命令对Redis进行操作。

我们做以下常用Redis命令测试:顾名思义,就不一一解释了

如此,我们便在 Windows 的环境下安装好了 Redis。

四、SpringBoot快速使用Redis

在 JavaWeb 中实现对 Redis 的操作,主要有两种方式:Jedis和SpringDataRedis。Jedis是Redis官方推荐的面向Java操作Redis的客户端开发Jar包;而spring-data-redis是Spring大家族的一部分,对Jedis API的进行了高度封装,提供了在Spring应用中通过简单的配置访问Redis服务。SpringDataRedis相对于Jedis来说可以方便地更换Redis的Java客户端,比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用比如SpringCache。它提供了使用Redis的模版RedisTemplate<K, V>接口或它的实现类StringRedisTemplateRedisTemplate。其中RedisTemplate可以支持Redis没有的缓存对象的操作,StringRedisTemplate用来存储字符串数据。

SpringBoot使用StringRedisTemplate对Redis缓存数据库进行操作:

1、pom.xml添加redis的起步依赖

<!-- SpringBoot整合Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、application.properties配置文件中添加redis的连接信息

#Redis
spring.redis.host=127.0.0.1
spring.redis.port=6379

3、Redis使用测试代码

package test;

import com.hs.springbootdemo.SpringbootdemoApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@RunWith(SpringRunner.class)//SpringBoot 2.X 默认使用Junit4
@SpringBootTest(classes = SpringbootdemoApplication.class)
public class RedisTest {

    //如果不能找到RedisTemplate,就使用@Resource试试
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void test() throws Exception
    {
        // 保存字符串
        stringRedisTemplate.opsForValue().set("username", "redis!!!");
        Logger logger = LoggerFactory.getLogger(RedisTest.class);
        String str = stringRedisTemplate.opsForValue().get("username");
        logger.warn(str);

    }
}

4、打开Redis目录下的redis-server,启动Redis

5、IDEA运行RedisTest单元测试

通过上面这段极为简单的测试案例演示了如何通过Spring自动配置的StringRedisTemplate对象进行Redis的读写操作,该对象从命名中就可注意到支持的是String类型。原本是RedisTemplate<K, V>接口,StringRedisTemplate就相当于RedisTemplate<String, String>的实现类。

6、Redis通过RedisTemplate存储对象

Redis 只能支持六种数据类型(string/hash/list/set/zset/hyperloglog)的操作,但在 Java 中我们却通常以类对象为主,所以在需要 Redis 存储的五中数据类型与 Java 对象之间进行转换,如果自己编写一些工具类,比如一个角色对象的转换,还是比较容易的,但是涉及到许多对象的时候,这其中无论工作量还是工作难度都是很大的,所以总体来说,就操作对象而言,使用 Redis 还是挺难的。

好在 Spring框架中的spring-data-redis模块对Redis API进行了高度封装并且支持Redis缓存对象。上面说到了 Redis 无法操作对象的问题,无法在那些基础类型和 Java 对象之间方便的转换,但是在 Spring 应用中,这些问题都可以通过spring-data-redis模块提供的RedisTemplate对象模板类得到解决!

普通的连接根本没有办法直接将对象直接存入 Redis 内存中,我们需要替代的方案:将对象序列化(可以简单的理解为继承Serializable接口)。我们可以把对象序列化之后存入Redis缓存中,然后在取出的时候又通过转换器,将序列化之后的对象反序列化回对象,这样就完成了我们的要求:

RedisTemplate可以帮助我们完成这份工作,它会找到对应的序列化器去转换Redis的键值。

a、POJO类需要实现Serializable接口:

package com.hs.springbootdemo.dao.entity;

import java.io.Serializable;

/**
*   用户实体类
 */

public class UserEntity implements Serializable {

    //属性名必须与数据库表的字段名一致,以便Mybatis直接把实体类映射成数据库记录
    private Integer uid; //用户id
    private String username;//用户名
    private String password;//用户密码

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

b、通过RedisTemplate存储对象到Redis中

package test;

import com.hs.springbootdemo.SpringbootdemoApplication;
import com.hs.springbootdemo.dao.entity.UserEntity;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@RunWith(SpringRunner.class)//SpringBoot 2.X 默认使用Junit4
@SpringBootTest(classes = SpringbootdemoApplication.class)
public class RedisTest {

    //如果无法注入RedisTemplate,就使用@Resource试试
    @Autowired
    RedisTemplate redisTemplate;//自带的对象模板类,用于缓存对象
    @Autowired
    StringRedisTemplate stringRedisTemplate;//自带的字符串模板类,用于缓存字符串

    @Test
    public void test() throws Exception
    {
        // 保存字符串
        stringRedisTemplate.opsForValue().set("username", "redis!!!");
        Logger logger = LoggerFactory.getLogger(RedisTest.class);
        String str = stringRedisTemplate.opsForValue().get("username");
        logger.warn(str);
    }

    @Test
    public void test1() throws Exception {

        UserEntity user = new UserEntity();
        user.setUsername("hello");
        user.setPassword("12345");

        //保存对象
        redisTemplate.opsForValue().set("user_1", user);
        UserEntity user1 = (UserEntity) redisTemplate.opsForValue().get("user_1");

        System.out.println(user1.getUsername());
    }
}

运行结果:


 

参考链接:

Redis【入门】就这一篇!

SpringBoot使用Redis

Redis和编程语言的纠缠

猜你喜欢

转载自blog.csdn.net/CSDN2497242041/article/details/102656541
今日推荐