Redis【入门】

Redis 概述

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

NoSQL 技术

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

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

Redis 在 Java Web 中的应用

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

  • 存储 缓存 用的数据;
  • 须要高速读/写的场合使用它快速读/写

缓存

在平常对数据库的访问中,读操做的次数远超写操做,比例大概在 1:93:7,因此须要读的可能性是比写的可能大得多的。当咱们使用SQL语句去数据库进行读写操做时,数据库就会去磁盘把对应的数据索引取回来,这是一个相对较慢的过程。spring

若是咱们把数据放在 Redis 中,也就是直接放在内存之中,让服务端直接去读取内存中的数据,那么这样速度明显就会快上很多,而且会极大减少数据库的压力,可是使用内存进行数据存储开销也是比较大的,限于成本的缘由,通常咱们只是使用 Redis 存储一些经常使用和主要的数据,好比用户登陆的信息等。数据库

通常而言在使用 Redis 进行存储的时候,咱们须要从如下几个方面来考虑:windows

  • 业务数据经常使用吗?命中率如何?若是命中率很低,就没有必要写入缓存;
  • 该业务数据是读操做多,仍是写操做多?若是写操做多,频繁须要写入数据库,也没有必要使用缓存;
  • 业务数据大小如何?若是要存储几百兆字节的文件,会给缓存带来很大的压力,这样也没有必要;

在考虑了这些问题以后,若是以为有必要使用缓存,那么就使用它!使用 Redis 做为缓存的读取逻辑以下图所示:缓存

从上图咱们能够知道如下两点:安全

  1. 第一次读取数据的时候,读取 Redis 的数据就会失败,此时就会触发程序读取数据库,把数据读取出来,而且写入 Redis 中;
  2. 第二次以及之后须要读取数据时,就会直接读取 Redis,读到数据后就结束了流程,这样速度就大大提升了。

从上面的分析能够知道,读操做的可能性是远大于写操做的,因此使用 Redis 来处理平常中须要常常读取的数据,速度提高是显而易见的,同时也下降了对数据库的依赖,使得数据库的压力大大减小。springboot

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

从流程能够看出,更新或者写入的操做,须要多个 Redis 的操做,若是业务数据写次数远大于读次数那么就没有必要使用 Redis。

关于使用内存存储数据,我知道谷歌好像就是把全部互联网的数据都存储在内存条的,因此才会有如此高质量、高效的搜索,但它毕竟是谷歌...

高速读/写的场合

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

因此咱们须要使用 Redis 来应对这样的高并发需求的场合,咱们先来看看一次请求操做的流程图

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

  1. 当一个请求到达服务器时,只是把业务数据在 Redis 上进行读写,而没有对数据库进行任何的操做,这样就能大大提升读写的速度,从而知足高速响应的需求
  2. 可是这些缓存的数据仍然须要持久化,也就是存入数据库之中,因此在一个请求操做完 Redis 的读/写以后,会去判断该高速读/写的业务是否结束,这个判断一般会在秒杀商品为0,红包金额为0时成立,若是不成立,则不会操做数据库;若是成立,则触发事件将 Redis 的缓存的数据以批量的形式一次性写入数据库,从而完成持久化的工做。

Redis 的安装

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

把 Redis 下载下来后找到一个合适的地方解压,就能获得以下图所示的目录(这里空格被替换成了%20...):

为了方便启动,咱们在该目录下新建一个 startup.cmd 的文件,而后将如下内容写入文件:

redis-server redis.windows.conf

这个命令其实就是在调用 redis-server.exe 命令来读取 redis.window.conf 的内容,咱们双击刚才建立好的 startup.cmd 文件,就能成功的看到 Redis 启动:

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

咱们能够打开同一个文件夹下的 redis-cli.exe 文件,这是 Redis 自带的一个客户端工具,它能够用来链接到咱们当前的 Redis 服务器,咱们作如下测试:

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


在 Java 中使用 Redis

第一步:添加 Jedis 依赖

想要在 Java 中使用 Redis 缓存,须要添加相关的Jar包依赖,打开Maven仓库的网站:https://mvnrepository.com/ ,搜索Jedis:

把它导入工程中去就能够啦,下面咱们来对Redis的写入性能作一下测试:

@Test
public void redisTester() {
    Jedis jedis = new Jedis("localhost", 6379, 100000);
    int i = 0;
    try {
        long start = System.currentTimeMillis();// 开始毫秒数
        while (true) {
            long end = System.currentTimeMillis();
            if (end - start >= 1000) {// 当大于等于1000毫秒(至关于1秒)时,结束操做
                break;
            }
            i++;
            jedis.set("test" + i, i + "");
        }
    } finally {// 关闭链接
        jedis.close();
    }
    // 打印1秒内对Redis的操做次数
    System.out.println("redis每秒操做:" + i + "次");
}
-----------测试结果-----------
redis每秒操做:10734次

听说 Redis 的性能能达到十万级别,我不敢相信个人台式机电脑只有十分之一不到的性能,虽说这里不是流水线的操做,会形成必定的影响,但我仍是不信邪,我查到了官方的性能测试方法:

首先在Redis根目录下召唤Cmd:具体方法是按住【Shift】点击右键

而后输入命令:【redis-benchmark -n 100000 -q】:来同时执行10万个请求测试性能

好吧,我同时在个人笔记本上测试了一下,结果更加惨淡...low啊low...

第二步:使用 Redis 链接池

跟数据库链接池相同,Java Redis也一样提供了类redis.clients.jedis.JedisPool来管理咱们的Reids链接池对象,而且咱们可使用redis.clients.jedis.JedisPoolConfig来对链接池进行配置,代码以下:

JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大空闲数
poolConfig.setMaxIdle(50);
// 最大链接数
poolConfig.setMaxTotal(100);
// 最大等待毫秒数
poolConfig.setMaxWaitMillis(20000);
// 使用配置建立链接池
JedisPool pool = new JedisPool(poolConfig, "localhost");
// 从链接池中获取单个链接
Jedis jedis = pool.getResource();
// 若是须要密码
//jedis.auth("password");

Redis 只能支持六种数据类型(string/hash/list/set/zset/hyperloglog)的操做,但在 Java 中咱们却一般以类对象为主,因此在须要 Redis 存储的五中数据类型与 Java 对象之间进行转换,若是本身编写一些工具类,好比一个角色对象的转换,仍是比较容易的,可是涉及到许多对象的时候,这其中不管工做量仍是工做难度都是很大的,因此整体来讲,就操做对象而言,使用 Redis 仍是挺难的,好在 Spring 对这些进行了封装和支持。

第三步:在 Spring 中使用 Redis

上面说到了 Redis 没法操做对象的问题,没法在那些基础类型和 Java 对象之间方便的转换,可是在 Spring 中,这些问题均可以经过使用RedisTemplate获得解决!

想要达到这样的效果,除了 Jedis 包之外还须要在 Spring 引入 spring-data-redis 包:https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis

这里把2.0.7最新版本标红的意思是:别老想着使用最新的Jar包,特别是涉及到框架的一些东西,笔者用实际的操做体验告诉大家,引入该版本的包是会致使Jar包冲突的(也就是莫名其妙的错误),我乖乖换回了1.7.2的版本,代码就通了...咱们来看看怎么作吧:

(1)第一步:使用Spring配置JedisPoolConfig对象

大部分的状况下,咱们仍是会用到链接池的,因而先用 Spring 配置一个 JedisPoolConfig 对象:

<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <!--最大空闲数-->
    <property name="maxIdle" value="50"/>
    <!--最大链接数-->
    <property name="maxTotal" value="100"/>
    <!--最大等待时间-->
    <property name="maxWaitMillis" value="20000"/>
</bean>

(2)第二步:为链接池配置工厂模型

好了,咱们如今配置好了链接池的相关属性,那么具体使用哪一种工厂实现呢?在Spring Data Redis中有四种可供咱们选择的工厂模型,它们分别是:

  • JredisConnectionFactory
  • JedisConnectionFactory
  • LettuceConnectionFactory
  • SrpConnectionFactory

咱们这里就简单配置成JedisConnectionFactory:

<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <!--Redis服务地址-->
    <property name="hostName" value="localhost"/>
    <!--端口号-->
    <property name="port" value="6379"/>
    <!--若是有密码则须要配置密码-->
    <!--<property name="password" value="password"/>-->
    <!--链接池配置-->
    <property name="poolConfig" ref="poolConfig"/>
</bean>

(3)第三步:配置RedisTemplate

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

RedisTemplate能够帮助咱们完成这份工做,它会找到对应的序列化器去转换Redis的键值:

<bean id="redisTemplate"
      class="org.springframework.data.redis.core.RedisTemplate"
      p:connection-factory-ref="connectionFactory"/>

笔者从《JavaEE互联网轻量级框架整合开发》中了解到,这一步须要配置单独的序列化器去支撑这一步的工做,可是本身在测试当中,发现只要咱们的POJO类实现了Serializable接口,就不会出现问题...因此我直接省略掉了配置序列化器这一步...

(4)第四步:编写测试

首先编写好支持咱们测试的POJO类:

/**
 * @author: @我没有三颗心脏
 * @create: 2018-05-30-下午 22:31
 */
public class Student implements Serializable{

    private String name;
    private int age;

    /**
     * 给该类一个服务类用于测试
     */
    public void service() {
        System.out.println("学生名字为:" + name);
        System.out.println("学生年龄为:" + age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

而后编写测试类:

@Test
public void test() {
    ApplicationContext context =
            new ClassPathXmlApplicationContext("applicationContext.xml");
    RedisTemplate redisTemplate = context.getBean(RedisTemplate.class);
    Student student = new Student();
    student.setName("我没有三颗心脏");
    student.setAge(21);
    redisTemplate.opsForValue().set("student_1", student);
    Student student1 = (Student) redisTemplate.opsForValue().get("student_1");
    student1.service();
}

运行能够成功看到结果:

第四步:在 SpringBoot 中使用 Redis

(1)在SpringBoot中添加Redis依赖:

<!-- Radis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)添加配置文件:

在SpringBoot中使用.properties或者.yml均可以,这里给出.properties的例子,由于本身的.yml文件看上去感受乱糟糟的:

# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器链接端口
spring.redis.port=6379
# Redis服务器链接密码(默认为空)
spring.redis.password=
# 链接池最大链接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 链接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 链接池中的最大空闲链接
spring.redis.pool.max-idle=8
# 链接池中的最小空闲链接
spring.redis.pool.min-idle=0
# 链接超时时间(毫秒)
spring.redis.timeout=0

(3)测试访问:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest()
public class ApplicationTests {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void test() throws Exception {

        // 保存字符串
        stringRedisTemplate.opsForValue().set("aaa", "111");
        Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa"));

    }
}

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

运行测试,若是一切成功则不会报错,若是咱们没有拿到或者拿到的数不是咱们想要的 “111” ,那么则会报错,这是使用Assert的好处(下面是我改为112以后运行报错的结果):

(4)存储对象:

这一步跟上面使用Spring同样,只须要将POJO类实现Serializable接口就能够了,我这里就贴一下测试代码:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest()
public class ApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void test() throws Exception {

        User user = new User();
        user.setName("我没有三颗心脏");
        user.setAge(21);

        redisTemplate.opsForValue().set("user_1", user);
        User user1 = (User) redisTemplate.opsForValue().get("user_1");

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

仍然没有任何问题:

参考文章:
1.http://www.javashuo.com/article/p-azjgnmws-go.html
2.http://blog.didispace.com/springbootredis/


在Redis中操做集合

引用文章:https://www.jianshu.com/p/29aaac3172b5

直接黏上两段简单的示例代码:

在Redis中操做List

// list数据类型适合于消息队列的场景:好比12306并发量过高,而同一时间段内只能处理指定数量的数据!必须知足先进先出的原则,其他数据处于等待
@Test
public void listPushResitTest() {
    // leftPush依次由右边添加
    stringRedisTemplate.opsForList().rightPush("myList", "1");
    stringRedisTemplate.opsForList().rightPush("myList", "2");
    stringRedisTemplate.opsForList().rightPush("myList", "A");
    stringRedisTemplate.opsForList().rightPush("myList", "B");
    // leftPush依次由左边添加
    stringRedisTemplate.opsForList().leftPush("myList", "0");
}

@Test
public void listGetListResitTest() {
    // 查询类别全部元素
    List<String> listAll = stringRedisTemplate.opsForList().range("myList", 0, -1);
    logger.info("list all {}", listAll);
    // 查询前3个元素
    List<String> list = stringRedisTemplate.opsForList().range("myList", 0, 3);
    logger.info("list limit {}", list);
}

@Test
public void listRemoveOneResitTest() {
    // 删除先进入的B元素
    stringRedisTemplate.opsForList().remove("myList", 1, "B");
}

@Test
public void listRemoveAllResitTest() {
    // 删除全部A元素
    stringRedisTemplate.opsForList().remove("myList", 0, "A");
}

在Redis中操做Hash

@Test
public void hashPutResitTest() {
    // map的key值相同,后添加的覆盖原有的
    stringRedisTemplate.opsForHash().put("banks:12600000", "a", "b");
}

@Test
public void hashGetEntiresResitTest() {
    // 获取map对象
    Map<Object, Object> map = stringRedisTemplate.opsForHash().entries("banks:12600000");
    logger.info("objects:{}", map);
}

@Test
public void hashGeDeleteResitTest() {
    // 根据map的key删除这个元素
    stringRedisTemplate.opsForHash().delete("banks:12600000", "c");
}

@Test
public void hashGetKeysResitTest() {
    // 得到map的key集合
    Set<Object> objects = stringRedisTemplate.opsForHash().keys("banks:12600000");
    logger.info("objects:{}", objects);
}

@Test
public void hashGetValueListResitTest() {
    // 得到map的value列表
    List<Object> objects = stringRedisTemplate.opsForHash().values("banks:12600000");
    logger.info("objects:{}", objects);
}

@Test
public void hashSize() { // 获取map对象大小
    long size = stringRedisTemplate.opsForHash().size("banks:12600000");
    logger.info("size:{}", size);
}

总结

在网上看到了关于MySQL的性能测试,读写操做大概就每秒1000如下的样子,并且这还和引擎相关,因此能够看出Redis确实能在性能方面帮助许多,此博客是转载保存,之后方便查看,感谢做者:我没有三颗心脏

相关文章
相关标签/搜索