• Redis入门完整教程:Java客户端Jedis


    Java有很多优秀的Redis客户端(详见:http://redis.io/clients#java),这
    里介绍使用较为广泛的客户端Jedis,本节将按照以下几个方面对Jedis进行
    介绍:
    ·获取Jedis
    ·Jedis的基本使用
    ·Jedis连接池使用
    ·Jedis中Pipeline使用
    ·Jedis的Lua脚本使用

    4.2.1 获取Jedis
    Jedis属于Java的第三方开发包,在Java中获取第三方开发包通常有两种
    方式:
    ·直接下载目标版本的Jedis-${version}.jar包加入到项目中。
    ·使用集成构建工具,例如maven、gradle等将Jedis目标版本的配置加入
    到项目中。
    通常在实际项目中使用第二种方式,但如果只是想测试一下Jedis,第
    一种方法也是可以的。在写本书时,Jedis最新发布的稳定版本2.8.2,以
    Maven为例子,在项目中加入下面的依赖即可:
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.8.2</version>
    </dependency>
    对于第三方开发包,版本的选择也是至关重要的,因为Redis更新速度
    比较快,如果客户端跟不上服务端的速度,有些特性和bug不能及时更新,
    不利于日常开发。通常来讲选取第三方开发包有如下两个策略:
    ·选择比较稳定的版本,也就是尽可能选择稳定的里程碑版本,这些版
    本已经经过多次alpha,beta的修复,基本算是稳定了。
    ·选择更新活跃的第三方开发包,例如Redis3.0有了Redis Cluster新特
    性,但是如果使用的客户端一直不支持,并且维护的人也比较少,这种就谨
    慎选择。
    本节介绍的Jedis基本满足上述两个特点,下面将对Jedis的基本使用方
    法进行介绍。


    4.2.2 Jedis的基本使用方法
    Jedis的使用方法非常简单,只要下面三行代码就可以实现get功能:
    # 1.  生成一个 Jedis 对象,这个对象负责和指定 Redis 实例进行通信
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    # 2. jedis 执行 set 操作
    jedis.set("hello", "world");
    # 3. jedis 执行 get 操作 , value="world"
    String value = jedis.get("hello");
    可以看到初始化Jedis需要两个参数:Redis实例的IP和端口,除了这两
    个参数外,还有一个包含了四个参数的构造函数是比较常用的:
    Jedis(final String host, final int port, final int connectionTimeout, final int
    soTimeout)
    参数说明:
    ·host:Redis实例的所在机器的IP。
    ·port:Redis实例的端口。
    ·connectionTimeout:客户端连接超时。
    ·soTimeout:客户端读写超时。
    如果想看一下执行结果:
    String setResult = jedis.set("hello", "world");
    String getResult = jedis.get("hello");
    System.out.println(setResult);
    System.out.println(getResult);

    输出结果为:
    OK
    world
    可以看到jedis.set的返回结果是OK,和redis-cli的执行效果是一样的,
    只不过结果类型变为了Java的数据类型。上面的这种写法只是为了演示使
    用,在实际项目中比较推荐使用try catch finally的形式来进行代码的书写:
    一方面可以在Jedis出现异常的时候(本身是网络操作),将异常进行捕获
    或者抛出;另一个方面无论执行成功或者失败,将Jedis连接关闭掉,在开
    发中关闭不用的连接资源是一种好的习惯,代码类似如下:
    Jedis jedis = null;
    try {
    jedis = new Jedis("127.0.0.1", 6379);
    jedis.get("hello");
    } catch (Exception e) {
    logger.error(e.getMessage(),e);
    } finally {
    if (jedis != null) {
    jedis.close();
    }
    }
    下面用一个例子说明Jedis对于Redis五种数据结构的操作,为了节省篇
    幅,所有返回结果放在注释中。
    // 1.string
    //  输出结果: OK
    jedis.set("hello", "world");
    //  输出结果: world
    jedis.get("hello");
    //  输出结果: 1
    jedis.incr("counter");
    // 2.hash
    jedis.hset("myhash", "f1", "v1");
    jedis.hset("myhash", "f2", "v2");
    //  输出结果: {f1=v1, f2=v2}
    jedis.hgetAll("myhash");
    // 3.list
    jedis.rpush("mylist", "1");
    jedis.rpush("mylist", "2");
    jedis.rpush("mylist", "3");
    //  输出结果: [1, 2, 3]
    jedis.lrange("mylist", 0, -1);
    // 4.set
    jedis.sadd("myset", "a");
    jedis.sadd("myset", "b");
    jedis.sadd("myset", "a");
    //  输出结果: [b, a]
    jedis.smembers("myset");
    // 5.zset
    jedis.zadd("myzset", 99, "tom");
    jedis.zadd("myzset", 66, "peter");
    jedis.zadd("myzset", 33, "james");
    //  输出结果: [[["james"],33.0], [["peter"],66.0], [["tom"],99.0]]
    jedis.zrangeWithScores("myzset", 0, -1);
    参数除了可以是字符串,Jedis还提供了字节数组的参数,例如:
    public String set(final String key, String value)
    public String set(final byte[] key, final byte[] value)
    public byte[] get(final byte[] key)
    public String get(final String key)
    有了这些API的支持,就可以将Java对象序列化为二进制,当应用需要
    获取Java对象时,使用get(final byte[]key)函数将字节数组取出,然后反序
    列化为Java对象即可。和很多NoSQL数据库(例如Memcache、Ehcache)的
    客户端不同,Jedis本身没有提供序列化的工具,也就是说开发者需要自己
    引入序列化的工具。序列化的工具有很多,例如XML、Json、谷歌的
    Protobuf、Facebook的Thrift等等,对于序列化工具的选择开发者可以根据自
    身需求决定,下面以protostuff(Protobuf的Java客户端)为例子进行说明。
    1)protostuff的Maven依赖:
    <protostuff.version>1.0.11</protostuff.version>
    <dependency>
    <groupId>com.dyuproject.protostuff</groupId>
    <artifactId>protostuff-runtime</artifactId>
    <version>${protostuff.version}</version>
    </dependency>
    <dependency>
    <groupId>com.dyuproject.protostuff</groupId>
    <artifactId>protostuff-core</artifactId>
    <version>${protostuff.version}</version>
    </dependency>

    2)定义实体类:
    //  俱乐部
    public class Club implements Serializable {
    private int id; // id
    private String name; //  名称
    private String info; //  描述
    private Date createDate; //  创建日期
    private int rank; //  排名
    //  相应的 getter setter 不占用篇幅
    }
    3)序列化工具类ProtostuffSerializer提供了序列化和反序列化方法:
     

    1. package com.sohu.tv.serializer;
    2. import com.dyuproject.protostuff.LinkedBuffer;
    3. import com.dyuproject.protostuff.ProtostuffIOUtil;
    4. import com.dyuproject.protostuff.Schema;
    5. import com.dyuproject.protostuff.runtime.RuntimeSchema;
    6. import java.util.concurrent.ConcurrentHashMap;
    7. // 序列化工具
    8. public class ProtostuffSerializer {
    9. private Schema<Club> schema = RuntimeSchema.createFrom(Club.class);
    10. public byte[] serialize(final Club club) {
    11. final LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_
    12. BUFFER_SIZE);
    13. try {
    14. return serializeInternal(club, schema, buffer);
    15. } catch (final Exception e) {
    16. throw new IllegalStateException(e.getMessage(), e);
    17. } finally {
    18. buffer.clear();
    19. }
    20. }
    21. public Club deserialize(final byte[] bytes) {
    22. try {
    23. Club club = deserializeInternal(bytes, schema.newMessage(), schema);
    24. if (club != null ) {
    25. return club;
    26. }
    27. } catch (final Exception e) {
    28. throw new IllegalStateException(e.getMessage(), e);
    29. }
    30. return null;
    31. }
    32. private <T> byte[] serializeInternal(final T source, final Schema<T>
    33. schema, final LinkedBuffer buffer) {
    34. return ProtostuffIOUtil.toByteArray(source, schema, buffer);
    35. }
    36. private <T> T deserializeInternal(final byte[] bytes, final T result, final
    37. Schema<T> schema) {
    38. ProtostuffIOUtil.mergeFrom(bytes, result, schema);
    39. return result;
    40. }
    41. }

    4)测试。
    生成序列化工具类:
    ProtostuffSerializer protostuffSerializer = new ProtostuffSerializer();
    生成Jedis对象:
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    序列化:
    String key = "club:1";
    //  定义实体对象
    Club club = new Club(1, "AC", " 米兰 ", new Date(), 1);
    //  序列化
    byte[] clubBtyes = protostuffSerializer.serialize(club);
    jedis.set(key.getBytes(), clubBtyes);
    反序列化:
    byte[] resultBtyes = jedis.get(key.getBytes());
    //  反序列化 [id=1, clubName=AC, clubInfo= 米兰 , createDate=Tue Sep 15 09:53:18 CST
    // 2015, rank=1]
    Club resultClub = protostuffSerializer.deserialize(resultBtyes);

    4.2.3 Jedis连接池的使用方法
    4.2.2节介绍的是Jedis的直连方式,所谓直连是指Jedis每次都会新建TCP
    连接,使用后再断开连接,对于频繁访问Redis的场景显然不是高效的使用
    方式,如图4-3所示。

     

    因此生产环境中一般使用连接池的方式对Jedis连接进行管理,如图4-4
    所示,所有Jedis对象预先放在池子中(JedisPool),每次要连接Redis,只
    需要在池子中借,用完了在归还给池子。

     

    客户端连接Redis使用的是TCP协议,直连的方式每次需要建立TCP连
    接,而连接池的方式是可以预先初始化好Jedis连接,所以每次只需要从
    Jedis连接池借用即可,而借用和归还操作是在本地进行的,只有少量的并
    发同步开销,远远小于新建TCP连接的开销。另外直连的方式无法限制Jedis
    对象的个数,在极端情况下可能会造成连接泄露,而连接池的形式可以有效
    的保护和控制资源的使用。但是直连的方式也并不是一无是处,表4-1给出
    两种方式各自的优劣势。

     

    Jedis提供了JedisPool这个类作为对Jedis的连接池,同时使用了Apache的
    通用对象池工具common-pool作为资源的管理工具,下面是使用JedisPool操
    作Redis的代码示例:
    1)Jedis连接池(通常JedisPool是单例的):
    // common-pool 连接池配置,这里使用默认配置,后面小节会介绍具体配置说明
    GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
    //  初始化 Jedis 连接池
    JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
    2)获取Jedis对象不再是直接生成一个Jedis对象进行直连,而是从连接
    池直接获取,代码如下:
    Jedis jedis = null;
    try {
    // 1.  从连接池获取 jedis 对象
    jedis = jedisPool.getResource();
    // 2.  执行操作
    jedis.get("hello");
    } catch (Exception e) {
    logger.error(e.getMessage(),e);
    } finally {
    if (jedis != null) {
    //  如果使用 JedisPool , close 操作不是关闭连接,代表归还连接池
    jedis.close();
    }
    }
    这里可以看到在finally中依然是jedis.close()操作,为什么会把连接关
    闭呢,这不和连接池的原则违背了吗?但实际上Jedis的close()实现方式
    如下:
    public void close() {
    //  使用 Jedis 连接池
    if (dataSource != null) {
    if (client.isBroken()) {
    this.dataSource.returnBrokenResource(this);
    } else {
    this.dataSource.returnResource(this);
    }
    //  直连
    } else {
    client.close();
    }

    参数说明:
    ·dataSource!=null代表使用的是连接池,所以jedis.close()代表归还
    连接给连接池,而且Jedis会判断当前连接是否已经断开。
    ·dataSource=null代表直连,jedis.close()代表关闭连接。
    前面GenericObjectPoolConfig使用的是默认配置,实际它提供有很多参
    数,例如池子中最大连接数、最大空闲连接数、最小空闲连接数、连接活性
    检测,等等,例如下面代码:
    GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
    //  设置最大连接数为默认值的 5 倍
    poolConfig.setMaxTotal(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL * 5);
    //  设置最大空闲连接数为默认值的 3 倍
    poolConfig.setMaxIdle(GenericObjectPoolConfig.DEFAULT_MAX_IDLE * 3);
    //  设置最小空闲连接数为默认值的 2 倍
    poolConfig.setMinIdle(GenericObjectPoolConfig.DEFAULT_MIN_IDLE * 2);
    //  设置开启 jmx 功能
    poolConfig.setJmxEnabled(true);
    //  设置连接池没有连接后客户端的最大等待时间 ( 单位为毫秒 )
    poolConfig.setMaxWaitMillis(3000);
    上面几个是GenericObjectPoolConfig几个比较常用的属性,表4-2给出了
    Generic-ObjectPoolConfig其他属性及其含义解释。


    表4-2 GenericObjectPoolConfig的重要属性

     

    4.2.4 Redis中Pipeline的使用方法
    3.3节介绍了Pipeline的基本原理,Jedis支持Pipeline特性,我们知道
    Redis提供了mget、mset方法,但是并没有提供mdel方法,如果想实现这个功
    能,可以借助Pipeline来模拟批量删除,虽然不会像mget和mset那样是一个原
    子命令,但是在绝大数场景下可以使用。下面代码是mdel删除的实现过程。
    注意
    这里为了节省篇幅,没有写try catch finally,没有关闭jedis。
    public void mdel(List<String> keys) {
    Jedis jedis = new Jedis("127.0.0.1");
    // 1) 生成 pipeline 对象
    Pipeline pipeline = jedis.pipelined();
    // 2)pipeline 执行命令,注意此时命令并未真正执行
    for (String key : keys) {
    pipeline.del(key);
    }
    // 3) 执行命令
    pipeline.sync();
    }
    说明如下:
    ·利用jedis对象生成一个pipeline对象,直接可以调用
    jedis.pipelined()。
    ·将del命令封装到pipeline中,可以调用pipeline.del(String key),这个
    方法和jedis.del(String key)的写法是完全一致的,只不过此时不会真正的
    执行命令。
    ·使用pipeline.sync()完成此次pipeline对象的调用。
    267
    除了pipeline.sync(),还可以使用pipeline.syncAndReturnAll()将
    pipeline的命令进行返回,例如下面代码将set和incr做了一次pipeline操作,
    并顺序打印了两个命令的结果:
    Jedis jedis = new Jedis("127.0.0.1");
    Pipeline pipeline = jedis.pipelined();
    pipeline.set("hello", "world");
    pipeline.incr("counter");
    List<Object> resultList = pipeline.syncAndReturnAll();
    for (Object object : resultList) {
    System.out.println(object);
    }
    输出结果为:
    OK
    1

    4.2.5 Jedis的Lua脚本
    Jedis中执行Lua脚本和redis-cli十分类似,Jedis提供了三个重要的函数实
    现Lua脚本的执行:
    Object eval(String script, int keyCount, String... params)
    Object evalsha(String sha1, int keyCount, String... params)
    String scriptLoad(String script)
    eval函数有三个参数,分别是:
    ·script:Lua脚本内容。
    ·keyCount:键的个数。
    ·params:相关参数KEYS和ARGV。
    以一个最简单的Lua脚本为例子进行说明:
    return redis.call('get',KEYS[1])
    在redis-cli中执行上面的Lua脚本,方法如下:
    127.0.0.1:6379> eval "return redis.call('get',KEYS[1])" 1 hello
    "world"
    在Jedis中执行,方法如下:
    String key = "hello";
    String script = "return redis.call('get',KEYS[1])";
    Object result = jedis.eval(script, 1, key);
    //  打印结果为 world
    System.out.println(result)

    scriptLoad和evalsha函数要一起使用,首先使用scriptLoad将脚本加载到
    Redis中,代码如下:
    String scriptSha = jedis.scriptLoad(script);
    evalsha函数用来执行脚本的SHA1校验和,它需要三个参数:
    ·scriptSha:脚本的SHA1。
    ·keyCount:键的个数。
    ·params:相关参数KEYS和ARGV。
    执行效果如下:
    Stirng key = "hello";
    Object result = jedis.evalsha(scriptSha, 1, key);
    //  打印结果为 world
    System.out.println(result);
    总体来说,Jedis的使用还是比较简单的,重点注意以下几点即可:
    1)Jedis操作放在try catch finally里更加合理。
    2)区分直连和连接池两种实现方式优缺点。
    3)jedis.close()方法的两种实现方式。
    4)Jedis依赖了common-pool,有关common-pool的参数需要根据不同的
    使用场景,各不相同,需要具体问题具体分析。
    5)如果key和value涉及了字节数组,需要自己选择适合的序列化方法。

    4.3 Python客户端redis-py
    因为本书主要使用Java语言作为编程语言,所以对Python的客户端redis-
    py不会太详细介绍,主要介绍以下几个方面:
    ·获取redis-py。
    ·redis-py的基本使用方法。
    ·redis-py的Pipeline的使用。
    ·redis-py的Lua脚本使用。

    4.3.1 获取redis-py
    Redis官网提供了很多Python语言的客户端
    (http://redis.io/clients#python),但最被广泛认可的客户端是redis-py。
    redis-py需要Python2.7以上版本,有关Python的安装本书不会介绍,主要介绍
    一下如何获取安装redis-py,方法有三种:
    第一,使用pip进行安装:
    pip install redis
    第二,使用easy_install进行安装:
    easy_install redis
    第三,使用源码安装:以2.10.5版本为例子进行说明,只需要如下四
    步:
    wget https:// github.com/andymccurdy/redis-py/archive/2.10.5.zip
    unzip redis-2.10.5.zip
    cd redis-2.10.5
    # 安装 redis-py
    python setup.py install

    4.3.2 redis-py的基本使用方法
    redis-py的使用方法也比较简单,下面将逐步骤介绍。
    1)导入依赖库:
    import redis
    2)生成客户端连接:需要Redis的实例IP和端口两个参数:
    client = redis.StrictRedis(host='127.0.0.1', port=6379)
    3)执行命令:redis-py的API保留了Redis API的原始风格,所以使用起
    来不会有不习惯的感觉:
    # True
    client.set(key, "python-redis")
    # world
    client.get(key)
    整个实例代码如下:
    import redis
    client = redis.StrictRedis(host='127.0.0.1', port=6379)
    key = "hello"
    setResult = client.set(key, "python-redis")
    print setResult
    value = client.get(key)
    print "key:" + key + ", value:" + value
    输出结果为:
    True
    key:hello, value:python-redis

    下面代码给出redis-py操作Redis五种数据结构的示例,输出结果写在注
    释中:

    1. #1.string
    2. # 输出结果: True
    3. client.set("hello","world")
    4. # 输出结果: world
    5. client.get("hello")
    6. # 输出结果: 1
    7. client.incr("counter")
    8. #2.hash
    9. client.hset("myhash","f1","v1")
    10. client.hset("myhash","f2","v2")
    11. # 输出结果: {'f1': 'v1', 'f2': 'v2'}
    12. client.hgetall("myhash")
    13. #3.list
    14. client.rpush("mylist","1")
    15. client.rpush("mylist","2")
    16. client.rpush("mylist","3")
    17. # 输出结果: ['1', '2', '3']
    18. client.lrange("mylist", 0, -1)
    19. #4.set
    20. client.sadd("myset","a")
    21. client.sadd("myset","b")
    22. client.sadd("myset","a")
    23. # 输出结果: set(['a', 'b'])
    24. client.smembers("myset")
    25. #5.zset
    26. client.zadd("myzset","99","tom")
    27. client.zadd("myzset","66","peter")
    28. client.zadd("myzset","33","james")
    29. # 输出结果: [('james', 33.0), ('peter', 66.0), ('tom', 99.0)]
    30. client.zrange("myzset", 0, -1, withscores=True)

    4.3.3 redis-py中Pipeline的使用方法
    redis-py支持Redis的Pipeline功能,下面用一个简单的示例进行说明。
    1)引入依赖,生成客户端连接:
    import redis
    client = redis.StrictRedis(host='127.0.0.1', port=6379)
    2)生成Pipeline:注意client.pipeline包含了一个参数,如果
    transaction=False代表不使用事务:
    pipeline = client.pipeline(transaction=False)
    3)将命令封装到Pipeline中,此时命令并没有真正执行:
    pipeline.set("hello","world")
    pipeline.incr("counter")
    4)执行Pipeline:
    #[True, 3]
    result = pipeline.execute()
    和4.2.4小节一样,将用redis-py的Pipeline实现mdel功能:
    import redis
    def mdel( keys ):
    client = redis.StrictRedis(host='127.0.0.1', port=6379)
    pipeline = client.pipeline(transaction=False)
    for key in keys:
    print pipeline.delete(key)
    return pipeline.execute();

    4.3.4 redis-py中的Lua脚本使用方法
    redis-py中执行Lua脚本和redis-cli十分类似,redis-py提供了三个重要的
    函数实现Lua脚本的执行:
    eval(String script, int keyCount, String... params)
    script_load(String script)
    evalsha(String sha1, int keyCount, String... params :
    eval函数有三个参数,分别是:
    ·script:Lua脚本内容。
    ·keyCount:键的个数。
    ·params:相关参数KEYS和ARGV。
    以一个最简单的Lua脚本为例进行说明:
    return redis.call('get',KEYS[1])
    在redis-py中执行,方法如下:
    import redis
    client = redis.StrictRedis(host='127.0.0.1', port=6379)
    script = "return redis.call('get',KEYS[1])"
    # 输出结果为 world
    print client.eval(script,1,"hello")
    script_load和evalsha函数要一起使用,首先使用script_load将脚本加载到
    Redis中,代码如下:

    scriptSha = client.script_load(script)
    evalsha函数用来执行脚本的哈希值,它需要三个参数:
    ·scriptSha:脚本的SHA1。
    ·keyCount:键的个数。
    ·params:相关参数KEYS和ARGV。
    执行效果如下:
    print jedis.evalsha(scriptSha, 1, "hello");
    完整代码如下:
    import redis
    client = redis.StrictRedis(host='127.0.0.1', port=6379)
    script = "return redis.call('get',KEYS[1])"
    scriptSha = client.script_load(script)
    print client.evalsha(scriptSha, 1, "hello");

  • 相关阅读:
    Object转List<>,转List<Map<>>
    pdf转换器是什么东西?看这篇就懂了!
    Java常用类String
    AcWing.505 火柴排队(离散化&逆序对)
    openssl之中文手册
    git快速上手指南
    【系统架构设计师考试大纲】
    显示控件——半圆进度条
    Linux 4T硬盘parted分区
    Lingolingo
  • 原文地址:https://blog.csdn.net/tysonchiu/article/details/125609626