redis专题八:jedis简单概述

之前几节,通过命令,我们把数据存储到了redis上,但是最终我还是需要从程序上去对数据进行操作。可以连接redis进行操作的语言很多,Jedis 是Redis官方首选的 Java 客户端开发包。有了之前命令的基础,使用jedis就显得比较容易了。下面通过几个例子来说明下。

一、POM依赖

我们使用jedis 2.9.0的版本,并引入junit方便我们的测试。

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ljs</groupId>
    <artifactId>jedis-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>

二、基本操作

从代码中,我们演示了string和list两个操作,看操作的方法名,和我们redis的命令基本是一一对应的。所以其他操作类似,就不再说明。

package com.leijs.jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.List;

/**
 * JedisTest
 *
 * @author LeiJiSong
 * @date 2020/11/3
 */
public class JedisTest {

    @Test
    public void testJedis() {
        // 1. 连接redis
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        // 2. 操作redis
        jedis.set("name", "Bob");
        String name = jedis.get("name");
        System.out.println(name);

        jedis.lpush("list1", "a", "b", "c");
        List<String> list1 = jedis.lrange("list1", 0, -1);
        list1.forEach(u -> {
            System.out.println(u);
        });
        // 3. 关闭连接
        jedis.close();
    }
}

三、jedis的连接池

上述基本案例,我们每次操作都需要和redis建立连接,jedis连接资源的创建与销毁是很消耗程序性能,所以jedis为我们提供了jedis的池化技术,jedisPool在创建时初始化一些连接资源存储到连接池中,使用jedis连接资源时不需要创建,而是从连接池中获取一个资源进行redis的操作,使用完毕后,不需要销毁该jedis连接资源,而是将该资源归还给连接池,供其他请求使用。程序上可以如下实现:

首先,我们可以把基本的配置放在redis.properties中,如:

redis.maxTotal=30
redis.maxIdle=10
redis.host=127.0.0.1
redis.port=6379

接下来是工具类实现:

package com.redis.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ResourceBundle;

/**
 * Jedis工具类
 *
 * @author LeiJiSong
 * @date 2020/11/4
 */
public class JedisUtils {

    private static JedisPool jp = null;
    private static String host = null;
    private static int maxTotal;
    private static int maxIdle;
    private static int port;

    static {
        // 获取配置
        ResourceBundle rb = ResourceBundle.getBundle("redis");
        host = rb.getString("redis.host");

        port = Integer.parseInt(rb.getString("redis.port"));
        JedisPoolConfig jpc = new JedisPoolConfig();

        maxTotal = Integer.parseInt(rb.getString("redis.maxTotal"));
        jpc.setMaxTotal(maxTotal);

        maxIdle = Integer.parseInt(rb.getString("redis.maxIdle"));
        jpc.setMaxIdle(maxIdle);

        jp = new JedisPool(jpc, host, port);
    }

    /**
     * 获得连接
     * @return
     */
    public static Jedis getJedis() {
        return jp.getResource();
    }

    /**
     * 关闭连接
     * @param jedis
     */
    public static void close(Jedis jedis){
        if(jedis!=null){
            jedis.close();
        }
    }
}

核心参数详解:

 参数  说明
maxActive
控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;
如果赋值为-1,则表示不限制;
如果pool已经分配了maxActive个jedis实例,则此时pool的状态就成exhausted了
maxIdle
控制一个pool最多有多少个状态为idle的jedis实例
whenExhaustedAction
表示当pool中的jedis实例都被allocated完时,pool要采取的操作;
默认有三种WHEN_EXHAUSTED_FAIL(表示无jedis实例时,直接抛出NoSuchElementException)、
WHEN_EXHAUSTED_BLOCK(则表示阻塞住,或者达到maxWait时抛出JedisConnectionException)、
WHEN_EXHAUSTED_GROW(则表示新建一个jedis实例,也就说设置的maxActive无用)
maxWait

表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,

则直接抛出JedisConnectionException

 
testOnBorrow
在borrow一个jedis实例时,是否提前进行alidate操作;如果为true,则得到的jedis实例均是可用的
 
testOnReturn
在return给pool时,是否提前进行validate操作
testWhileIdle
如果为true,表示有一个idle object evitor线程对idle object进行扫描,
如果validate失败,此object会被从pool中drop掉;
这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;
timeBetweenEvictionRunsMillis
表示idle object evitor两次扫描之间要sleep的毫秒数
numTestsPerEvictionRun
表示idle object evitor每次扫描的最多的对象数
minEvictableIdleTimeMillis

表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;

这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义

softMinEvictableIdleTimeMillis

在minEvictableIdleTimeMillis基础上,加入了至少minIdle个对象已经在pool里面了。

如果为-1,evicted不会根据idle time驱逐任何对象。

如果minEvictableIdleTimeMillis>0,则此项设置无意义,

且只有在timeBetweenEvictionRunsMillis大于0时才有意义

 lifo

borrowObject返回对象时,是采用DEFAULT_LIFO(last in first out,即类似cache的最频繁使用队列),

如果为False,则表示FIFO队列

四、一个简单实际场景的应用

场景:比方说百度网盘,用过的都知道,在下载文件的时候,普通用户体验只有一分钟,然后就提示到时间,想用就要去充值了,VIP客户可以不限制时间,但是一个月或者半年的时间限制。

针对这种场景,我们进行稍微变形来模拟这样一种场景:普通用户对我们下载提速的接口:一天只能调用一次,VIP则可以调用30次;

接口的调用内容我们采取打印,对于超过次数的调用采用异常提示的方式,普通用户和VIP的调用采用两个线程来模拟。简单实现如下:

业务接口:

package com.redis.service;

import com.redis.util.JedisUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisDataException;

import java.util.Objects;

/**
 * 提供业务服务模拟增值调用
 *
 * @author LeiJiSong
 * @date 2020/11/4
 */
public class BusinessService {
    private String userId;
    private int count;
    public BusinessService(String userId, int count) {
        this.userId = userId;
        this.count = count;
    }

    /**
     * 业务控制单元
     */
    public void service() {
        Jedis jedis = JedisUtils.getJedis();
        String accountId = "accountId:" + userId;
        String value = jedis.get(accountId);
        try {
            if (Objects.isNull(value)) {
                // 不存在就设值
                // 这里用达到增长上限值来模拟达到最高次数
                jedis.setex(accountId, 25, Long.MAX_VALUE - count + "");
            } else {
                // 存在就自增
                Long currentValue = jedis.incr(accountId);
                business(userId, count - (Long.MAX_VALUE - currentValue));
            }
        } catch (JedisDataException e) {
            System.out.println("使用已经达到上限了,可以升级会员处理");
            return;
        } finally {
            jedis.close();
        }
    }

    public void business(String id, Long value) {
        System.out.println("用户" + id + "业务执行第" + value + "次操作");
    }
}

这里设计调用次数的值,setex方法,我们采用了之前章节说到的redis string自增的最大值,当达到最大值的时候,就会抛出JedisDataException异常。

用户线程:

package com.redis.service;

/**
 * 模拟线程
 *
 * @author LeiJiSong
 * @date 2020/11/4
 */
public class UserThread implements Runnable {
    BusinessService businessService;
    public UserThread(String id, int count) {
        this.businessService = new BusinessService(id ,count);
    }

    @Override
    public void run() {
        while(true) {
            businessService.service();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

主线程:

传入用户类型和可调用的次数,方便模拟,我们改了下key的失效时间和调用次数。

package com.redis.service;

/**
 * 主程序
 *
 * @author LeiJiSong
 * @date 2020/11/4
 */
public class Main {

    public static void main(String[] args) {
        Thread t1 = new Thread(new UserThread("base", 10));
        Thread t2 = new Thread(new UserThread("vip", 20));
        t1.start();
        t2.start();
    }

}

程序刚开始运行:

redis专题八:jedis简单概述

 在中间时间:

redis专题八:jedis简单概述

达到新的可以操作的周期,又可以调用:

redis专题八:jedis简单概述

程序的整体实现比较简陋, 比方说写死了失效的时间,以后想扩展超级VIP不限制次数,嗯呢,这些都是可以实现的。

嗯,简单介绍到这里。下一篇,聊一聊redis的持久化。

上一篇:数据结构实验——散列表的查找算法及其应用


下一篇:MySQL内存管理机制