Redis入门(二)

上一篇我们主要讲Linxu环境下redis的简单安装,这篇我们讲讲redis的一些基本特性,主要包括五种数据结构,常用命令、客户端、及高可用等等。写这些博客,主要是对redis学习的练习及总结。

Redis可以做什么?

1、缓存
这种也是最常用的,相当于在应用和DB之间做了一个缓冲作用,缓存机制几乎在所有的大型网站都有使用, 合理地使用缓存不仅可以加快数据的访问速度, 而且能够有效地降低后端数据源的压力。 Redis提供了键值过期时间设置, 并且也提供了灵活控制最大内存和内存溢出后的淘汰策略。
2、排行榜系统
例如按照热度排名的排行榜, 按照发布时间的排行榜, 按照各种复杂维度计算出的排行榜, Redis提供了列表和有序集合数据结构, 合理地使用这些数据结构可以很方便地构建各种排行榜系统。
3、计数器应用
计数器在网站中的作用至关重要, 例如视频网站有播放数、 电商网站有浏览数, 为了保证数据的实时性, 每一次播放和浏览都要做加1的操作, 如果并发量很大对于传统关系型数据的性能是一种挑战。 Redis天然支持计数功能而且计数的性能也非常好, 可以说是计数器系统的重要选择。

Redis支持的数据结构

Redis支持五种数据结构,string( 字符串) 、 hash( 哈希) 、 list( 列表) 、 set( 集合) 、 zset( 有序集合) 。当然这只是Redis对外的数据结构,实际上每种数据结构都有自己底层的内部编码实现, 而且是多种实现,这样Redis会在合适的场景选择合适的内部编码。每种数据结构都有两种以上的内部编码实现, 例如list数据结构包含了linkedlist和ziplist两种内部编码。 同时有些内部编码, 例如ziplist,可以作为多种外部数据结构的内部实现。

Redis这样设计有两个好处: 第一, 可以改进内部编码, 而对外的数据结构和命令没有影响, 这样一旦开发出更优秀的内部编码, 无需改动外部数据结构和命令, 例如Redis3.2提供了quicklist, 结合了ziplist和linkedlist两者的优势, 为列表类型提供了一种更为优秀的内部编码实现, 而对外部用户来说基本感知不到。 第二, 多种内部编码实现可以在不同场景下发挥各自的优势, 例如ziplist比较节省内存, 但是在列表元素比较多的情况下, 性能会有所下降, 这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist。注意:这种转换是不可逆的,一旦从ziplist转换为linkedlist后,不会在转换成ziplist.

Redis常用命令

查询所有的键:key * (特别影响Redis性能,生产环境禁用)
查询键的总数:dbsize
检查键是否存在:exists key
删除键(所有类型的键):del key [key …]
设置键过期:expire key seconds
查看键过期时间:ttl key
查看键的数据结构:type key

Redis客户端jedis和jedis连接池

目前Redis最常用的客户端就是Jedis,不过一般都是通过连接池获取jedis来使用。客户端连接Redis使用的是TCP协议, 直连的方式每次需要建立TCP连接, 而连接池的方式是可以预先初始化好Jedis连接, 所以每次只需要从Jedis连接池借用即可, 而借用和归还操作是在本地进行的, 只有少量的并发同步开销, 远远小于新建TCP连接的开销。 另外直连的方式无法限制Jedis对象的个数, 在极端情况下可能会造成连接泄露, 而连接池的形式可以有效的保护和控制资源的使用。

// common-pool连接池配置
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
// 初始化Jedis连接池
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
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();
    }
}

客户端常见异常分析

1、无法从连接池获取到连接
JedisPool中的Jedis对象个数是有限的, 默认是8个。 这里假设使用的默认配置, 如果有8个Jedis对象被占用, 并且没有归还, 此时调用者还要从JedisPool中借用Jedis, 就需要进行等待( 例如设置了maxWaitMillis>0) , 如果在maxWaitMillis时间内仍然无法获取到Jedis对象就会抛出如下异常:

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource
from the pool
…
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.
java:449)

不过一般应用在使用时会自己设置配置参数。

还有一种情况, 就是设置了blockWhenExhausted=false, 那么调用者发现池子中没有资源时, 会立即抛出异常不进行等待, 下面的异常就是blockWhenExhausted=false时的效果:

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource
from the pool
…
Caused by: java.util.NoSuchElementException: Pool exhausted
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.
java:464)

为什么连接池没有资源了, 造成没有资源的原因非常多, 可能如下:

·客户端: 高并发下连接池设置过小, 出现供不应求, 所以会出现上面的错误, 但是正常情况下只要比默认的最大连接数( 8个) 多一些即可, 因为正常情况下JedisPool以及Jedis的处理效率足够高。

·客户端: 没有正确使用连接池, 比如没有进行释放, 例如没有再finally中使用jedis.close()将jedis对象归还给连接池。

·客户端: 存在慢查询操作, 这些慢查询持有的Jedis对象归还速度会比较慢, 造成池子满了。

·服务端: 客户端是正常的, 但是Redis服务端由于一些原因造成了客户端命令执行过程的阻塞, 也会使得客户端抛出这种异常。
2.客户端读写超时
Jedis在调用Redis时, 如果出现了读写超时后, 会出现下面的异常:

redis.clients.jedis.exceptions.JedisConnectionException:
java.net.SocketTimeoutException: Read timed out

造成该异常的原因也有以下几种:
·读写超时间设置得过短。
·命令本身就比较慢。
·客户端与服务端网络不正常。
·Redis自身发生阻塞。
3.客户端连接超时
Jedis在调用Redis时, 如果出现了连接超时后, 会出现下面的异常:

redis.clients.jedis.exceptions.JedisConnectionException:
java.net.SocketTimeoutException: connect timed out

造成该异常的原因也有以下几种:
1) 连接超时设置得过短, 可以通过下面代码进行设置:
// 毫秒
jedis.getClient().setConnectionTimeout(time);
2) Redis发生阻塞, 造成tcp-backlog已满, 造成新的连接失败。
3) 客户端与服务端网络不正常。

Redis为什么这么快

1、Redis是纯内存操作
2、Redis是单线程,避免了多线程的竞争问题
3、Redis采用了IO多路复用,减少了io操作的占用时间

Redis优化

1、尽量减少键值的长度
2、可以只存储必要的字段
3、可以将要存储的对象进行压缩(推荐使用Google的Snappy)
4、客户端避免进行append操作,append操作会占用多余的内存

参考文献《Redis开发与运维》

猜你喜欢

转载自blog.csdn.net/weixin_40096176/article/details/80279639