Redis知识体系总结(2021版,掌握这套精编Java高级面试题解析

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

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

3、Redis为何能解决高并发问题

  1. Redis是基于内存的,内存的读写速度非常快;

  2. Redis是单线程的,省去了很多上下文切换线程的时间;

  3. Redis使用多路复用技术,可以处理并发的连接。非IO内部实现采用epoll,采用了epoll自己实现的简单的事件框架。epoll的读写、关闭、连接都转化为事件,然后利用epoll的多路复用特性,绝不在IO上浪费一点时间。

Redis高并发总结

  1. Redis是纯内存数据库,一般都是简单存取操作,线程占用的时间很多,时间的花费主要集中在IO上,所以读取速度快;

  2. Redis使用的是非阻塞IO,IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成事件,减少了线程切换时上下文切换和竞争。

  3. Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。

  4. Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如跳表,使用有序的数据结构加快读写的速度。

  5. Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。

4、Redis的优劣势

(1)优势

  1. 代码更清晰,处理逻辑更简单

  2. 不用考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现死锁而导致的性能消耗

  3. 不存在多线程切换而消耗CPU

[](

)(2)劣势

无法发挥多核CPU性能优势,不过可以通过单击开多个Redis实例来完善。

二、Redis为什么是单线程的


1、官方答案

Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络宽带。既然单线程容易实现,而且CPU不会成为瓶颈,那么顺理成章的采用单线程的方案。

2、我的理解

(1)不需要各种锁的性能消耗

Redis的数据结构并不全是key-value形式的,还有list,hash等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash中添加或删除一个对象,这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。

总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现的死锁而导致的性能消耗。

(2)单线程多进程集群方案

单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。

所以单线程、多进程的集群不失为一个时髦的解决方案。

(3)CPU消耗

采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU。

但是如果CPU称为Redis的瓶颈,或者不想让服务器其它CPU核闲置,那怎么办?

可以考虑多起几个Redis进程,Redis是key-value数据库,不是关系型数据库,数据之间没有约束。只要客户端分清哪些key放在哪个Redis进程中就可以了。

三、Linux中安装Redis


1、Redis下载

2、上传、解压

Redis一般安装在Linux环境下,开启虚拟机,通过xftp将redis压缩包上传到Linux服务器,并进行解压。

修改redis.conf配置文件,使其在后台启动

四、Redis在Java Web中的应用


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

  • 存储缓存用的数据

  • 需要高速读写的场合

1、存储缓存用的数据

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

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

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

(1)业务数据常用吗?使用率如何?

如果使用率较低,就没必要写入缓存。

(2)该业务是读操作多,还是写操作多?

如果写操作多,频繁需要写入数据库,也没必要使用缓存。

(3)业务数据大小如何?

如果要存储几百兆字节的文件,会给缓存带来很大的压力,这样也没必要。

在考虑了这些问题之后,如果觉得有必要使用缓存,那么就使用它!

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

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

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

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

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

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

2、高速读写场合

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

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

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

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

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

五、Redis代码实例


1、Java整合Redis

(1)导入pom


<dependency>

    <groupId>redis.clients</groupId>

    <artifactId>jedis</artifactId>

    <version>3.2.0</version>

</dependency>

(2)编写Java主方法

调用Redis中的ping方法,惊现异常:

开始的时候以为是防火墙的问题,后来通过查看redis状态发现IP地址不对,不应该是127.0.0.1

修改redis.conf

注意:需要注意的是在修改redis.conf时,①注掉bind 127.0.0.1;②需要将本机访问保护模式设置为no;③此时可以配置多个ip

(3)再次执行主方法,执行成功!

2、五大数据类型代码实例


package com.guor.redis;

 

import redis.clients.jedis.Jedis;

 

import java.util.List;

import java.util.Set;

 

public class JedisTest01 {

    public static void main(String[] args) {

        test05();

    }

 

    private static void test01(){

        Jedis jedis = new Jedis("192.168.194.131", 6379);

        String value = jedis.ping();

        System.out.println(value);

        //添加

        jedis.set("name","GooReey");

        //获取

        String name = jedis.get("name");

        System.out.println(name);

 

        jedis.set("age","30");

        jedis.set("city","dalian");

        //获取全部的key

        Set<String> keys = jedis.keys("*");

        for(String key : keys){

            System.out.println(key+" --> "+jedis.get(key));

        }

 

        //加入多个key和value

        jedis.mset("name1","zs","name2","ls","name3","ww");

        List<String> mget = jedis.mget("name1", "name2");

        System.out.println(mget);//[zs, ls]

    }

 

    //list

    private static void test02(){

        Jedis jedis = new Jedis("192.168.194.131", 6379);

        jedis.lpush("key1","01","02","03");

        List<String> values = jedis.lrange("key1",0,-1);

        System.out.println(values);//[03, 02, 01]

    }

 

    //set

    private static void test03(){

        Jedis jedis = new Jedis("192.168.194.131", 6379);

        jedis.sadd("username","zs","ls","ww");

        Set<String> names = jedis.smembers("username");

        System.out.println(names);//[ww, zs, ls]

    }

 

    //hash

    private static void test04(){

        Jedis jedis = new Jedis("192.168.194.131", 6379);

        jedis.hset("users","age", "20");

        String hget = jedis.hget("users","age");

        System.out.println(hget);

    }

 

    //zset

    private static void test05(){

        Jedis jedis = new Jedis("192.168.194.131", 6379);

        jedis.zadd("china",100d,"shanghai");

        Set<String> names = jedis.zrange("china",0,-1);

        System.out.println(names);//[shanghai]

    }

}

3、手机验证码功能代码实例


package com.guor.redis;

 

import redis.clients.jedis.Jedis;

 

import java.util.Random;

 

public class PhoneCode {

    public static void main(String[] args) {

        verifyCode("10086");//795258

        getRedisCode("10086","795258");//success.

    }

 

    //1、生成6位数字验证码

    public static String getCode(){

        Random random = new Random();

        String code = "";

        for (int i = 0; i < 6; i++) {

            int rand = random.nextInt(10);

            code += rand;

        }

        return code;//849130

    }

 

    //2、每个手机每天只能发送三次,验证码放到redis中,设置过期时间

    public static void verifyCode(String phone){

        Jedis jedis = new Jedis("192.168.194.131", 6379);

        //拼接key

        //手机发送次数key

        String countKey = "VerifyCode" + phone + ":count";

        //验证码key

        String codeKey = "VerifyCode" + phone + ":code";

        //每个手机每天只能发送三次

        String count = jedis.get(countKey);

        if(count == null){

            //设置过期时间

            jedis.setex(countKey,24*60*60,"1");

        }else if(Integer.parseInt(count)<=2){

            //发送次数+1

            jedis.incr(countKey);

        }else if(Integer.parseInt(count)>2){

            System.out.println("今天的发送次数已经超过三次");

            jedis.close();

        }

 

        String vCode = getCode();

        jedis.setex(codeKey,120,vCode);

        jedis.close();

    }

 

    //3、验证码校验

    public static void getRedisCode(String phone, String code){

        //从redis中获取验证码

        Jedis jedis = new Jedis("192.168.194.131", 6379);

        //验证码key

        String codeKey = "VerifyCode" + phone + ":code";

        String redisCode = jedis.get(codeKey);

        if(redisCode.equals(code)){

            System.out.println("success.");

        }else{

            System.out.println("error");

        }

        jedis.close();

    }

}

当超过三次时:

4、SpringBoot整合Redis

(1)建工程,引入pom


<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-parent</artifactId>

        <version>2.2.1.RELEASE</version>

        <relativePath/> <!-- lookup parent from repository -->

    </parent>

    <groupId>com.guor</groupId>

    <artifactId>redisspringboot</artifactId>

    <version>0.0.1-SNAPSHOT</version>

    <name>redisspringboot</name>

    <description>Demo project for Spring Boot</description>

    <properties>

        <java.version>1.8</java.version>

    </properties>

    <dependencies>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

        </dependency>

 

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-test</artifactId>

            <scope>test</scope>

        </dependency>

 

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-data-redis</artifactId>

            <version>2.4.5</version>

        </dependency>

 

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->

        <dependency>

            <groupId>org.apache.commons</groupId>

            <artifactId>commons-pool2</artifactId>

            <version>2.9.0</version>

        </dependency>

 

    </dependencies>

 

    <build>

        <plugins>

            <plugin>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-maven-plugin</artifactId>

            </plugin>

        </plugins>

    </build>

 

</project>

(2)配置类

application.properties


# Redis数据库索引(默认为0)

spring.redis.database=0

# Redis服务器地址

spring.redis.host=192.168.194.131

# Redis服务器连接端口

spring.redis.port=6379

# Redis服务器连接密码(默认为空)

spring.redis.password=

# 连接池最大连接数(使用负值表示没有限制)



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值