redis基本理论,重点分析redis连接池的实现

2017-01-07 10:40:24来源:oschina作者:ljhlgj人点击



## 一、redis原理及实践
### 简介
redis是一个高性能的内存缓存key:value数据库,同时支持数据持久化,目前广泛使用作为缓存,来缓解db压力。其key支持string、byte[],value支持的数据类型较广泛,几乎包含java中所有的数据类型,有string、hash、list、set、zset **各种类型的操作命令自行查阅**。
redis启动如果不指定.conf文件,默认会使用redis内置的配置,建议启动redis时指定配置文件,可以更好的控制redis特性,通过```redis-server redis.conf```来指定,当然也可以通过命令修改内置配置,**各个配置含义自行查阅**。
### 二、数据持久化、备份与恢复
#### 数据持久化方案
1.snapshots/RDB(default)
redis默认将数据快照放在dump.rdb磁盘文件中,可以通过```rdbcompression yes```来关闭,通过配置可以设定redis的持久化策略、频率,如N秒超过M次更新,就将数据写入磁盘,或者手工调用save/bgsave命令来持久化,打开redis.conf可以看到redis默认的持久化策略:
```java
save 900 1 //900s后1key变化,就dump内存快照
save 300 10
save 60 10000
```
当条件达到时,操作系统fork调用创建一个子进程(父进程是redis服务进程),该子进程和redis服务进程共享内存空间,这样子进程就可以遍历整个内存空间来进行数据读取持久化(持久化条件到达前的所有数据),此时主进程可以继续提供服务,操作系统以内存页(page)为单位使用copy-on-write技术保证父子进程间互不影响,可以很大程度的避免主线程磁盘IO,子进程是通过将数据持久化到临时文件,然后替换老文件来完成的。
缺点:
1)数据丢失:进行持久化时服务挂掉,那么上次持久化到这次持久化间的数据就会丢失;持久化完成后服务挂掉,那么持久化之后存入的数据就会丢失。
2)性能较低:由于是fork子进程实现的,所以当数据集较大时,整个服务器可能会停顿几百毫秒,甚至是秒级。
优点:
1)RDB无论何种持久化策略,24小时持久化一次,还是一个月持久化一次,都只会保存一份.rdb备份文件,这样的备份方案,在系统出现故障时,很容易恢复。
2)由于持久化是由子进程处理的,所以在数据密集度不打的情况下,性能很好。
3)在数据密集度很大时,RDB启动效率较AOF高。
2.AOF(append-only mode)
snapshots并不健壮,当redis挂掉,很大可能造成数据丢失,对于要求数据高可用的系统来说,这是不可取的,可以使用aof方案。AOF方式类似于mysql的binlog方式,对数据的写操作,都会记录操作日志,一旦服务挂掉,可以通过操作日志对数据进行恢复,aof持久化的是操作日志(rdb持久化的是redis内存数据),默认appendonly是关闭的,修改.conf中```appendonly no```为```appendonle yes```来打开aof,aof有以下策略,可以通过```appendfsync```来确定使用的策略,默认是```appendfsync everysec```,效率较高:
1)appendfsync no:Redis不会主动调用fsync去将AOF日志内容同步到磁盘,这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。
2)appendfsync everysec(everysecond default advice):Redis默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘,该方案最大会丢失1s内的数据。当这次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒。也就是在两秒后再进行fsync,这次的fsync就不管会执行多长时间都会进行,在fsync时redis的写操作会被阻塞。
3)appendfsyn always:每一次写操作都会调用一次fsync,这时数据是最安全的,但性能也会受到影响。
缺点:
1)aof是通过一直append log实现的,久而久之,这个log文件会很大,那么当服务挂掉,数据恢复很慢,甚至几个小时,不是由于IO慢,而是log中的每条命令都要在内存中执行一次。
2)在将缓冲区的log写入磁盘时,redis服务器是不能进行操作的,但everysec的性能还是比较好的。
优点:
1)由于aof的log文件是append,那么一次fsync调用宕机,也不会影响已经存在的数据,即便是log append过程中宕机,也可以通过redis-check-aof工具来修复损坏的aof文件,解决数据一致性问题,执行```redis-check-aof --fix ```来修复数据。
2)aof log文件结构清晰,易于理解,可以通过该文件很容易的完成数据重建。
aof rewrite:aof文件中记录的是redis写的操作命令,但是不是完全按客户端的请求来记录日志的,每请求一次就生成一条日志,aof文件会很大,所以会进行aof重写,AOF重写是重新生成一份AOF文件,新的AOF文件中一条记录的操作只会有一次,而不像一份老文件那样,可能记录了对同一个值的多次操作log。其生成过程和rdb类似,也是fork一个进程,直接遍历数据,写入新的AOF临时文件。在写入新文件的过程中,可以进行写操作,并且所有的写操作日志还是会写到原来老的AOF文件中,同时还会记录在内存缓冲区中。当重写操作完成后,会将所有缓冲区中的日志一次性写入到临时文件中。然后调用原子性的rename命令用新的 AOF文件取代老的AOF文件,命令:BGREWRITEAOF重写aof。
#### 数据备份
在Redis中我们可以通过copy的方式在线备份正在运行的Redis数据文件。这是因为RDB文件一旦被生成之后就不会再被修改。Redis每次都是将最新的数据dump到一个临时文件中,之后在利用rename函数原子性的将临时文件改名为原有的数据文件名。因此我们可以说,在任意时刻copy数据文件都是安全的和一致的。鉴于此,我们就可以通过创建cron job的方式定时备份Redis的数据文件,并将备份文件copy到安全的磁盘介质中
#### 数据恢复
重启时加载AOF文件或rdb文件进行数据恢复,aof和rdb可以同时开启,数据恢复时优选aof,如果aof恢复时间太长,不能接受的话,可以考虑使用rdb,允许一部分数据丢失。在数据可用性要求不高的系统中,也可以不用持久化机制。
### 三、redis slowlog分析
#### slowlog简介
slowlog是redis记录慢日志的机制,通过```slowlog-log-slower-than 10000```设置定性为慢日志的条件,慢日志存储在内存中,通过```slowlog-max-len 1024```设置内存中慢日志的条数,```slowlog len```查看慢日志条数,```slowlog get n```查询慢日志详情,形如下:
```java
1) (integer) 1//编号
2) (integer) 1483671013//发生的时间戳
3) (integer) 96005//执行的时间
4) 1) "bgrewriteaof"//具体命令,包括参数
2) "args"//参数
```
#### slowlog内部机制分析
详见http://blog.sina.com.cn/s/blog_48c95a190101gebh.html
### 四、redis集群方案
#### Cluster
#### Sentinel
#### Codis
### 五、redis工作实践
#### jedis
#### jedisPool,借助apache common-pool实现
1.objectPool理解及相关概念:
java中object创建十分消耗资源,那么对于使用频繁的对象,创建出来后,我们可以将它放入池中,需要时从池中获取,而不用在重新创建,很大程度上提高了应用的性能,如数据库连接对象、线程对象等,主要有以下概念:
对象池ObjectPool:管理池中元素(池对象)的状态、分配、回收、销毁等,如线程池、数据库连接池等
池对象PooledObject:对象池中元素(对象),通常由对象工厂创建
池对象工厂PooledObjectFactory:创建池对象,放到对象池中
2.apache common-pool实现
1)GenericObjectPool(GenericObjectPoolConfig,PooledObjectFactory,PooledObject)
GenericObjectPoolConfig:对象池配置,用于设置一些对象池的相关属性,主要有:
maxTotal=30 最大数,当达到该数量时,不在创建新对象
maxIdle=10 最大空闲数
minIdle=5 最小空闲数
maxWait = 20 * 1000 最大等待时间,在时间内没有boorow到object,就报NoSuchElementException ,Timeout waiting for idle object
```java
public static void main(String[] args){
test1();
}
private static void test1() {
MyPooledObjectFactory factory = new MyPooledObjectFactory();
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(5);
config.setMaxIdle(3);
config.setMinIdle(1);
config.setMaxWaitMillis(2 * 1000);
ObjectPool pool = new GenericObjectPool<>(factory,config);
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new MyTask(pool,i));
thread.start();
}
}
private static class MyTask implements Runnable {
private ObjectPool pool;
private int i;
public MyTask(ObjectPool pool,int i) {
this.pool = pool;
this.i=i;
}
public void run() {
Person person =null;
try {
System.out.println(i+"==borrow");
person = (Person)pool.borrowObject();//借一个池对象
Thread.sleep(1000*5);//do something
} catch(Exception ex) {
System.out.println(ex);
} finally {
if (person != null) {
try {
pool.returnObject(person);//归还池对象
System.out.println(i+"==return");
} catch (Exception ex) {
}
}
}
}
}
public class MyPooledObjectFactory extends BasePooledObjectFactory{
//object在boorow对象时,如果pool中没有且pool中对象不超过最大个数,那么会调用crete方法创建一个池对象
@Override
public Person create() throws Exception {
System.out.println("==crate pool object");
return new Person("liu","jianhui");
}
@Override
public PooledObject wrap(Person person) {
return new DefaultPooledObject<>(person);
}
}
```
2)GenericKeyedObjectPool(GenericKeyedObjectPoolConfig,KeyedPooledObjectFactory,PooledObject)
GenericKeyedObjectPool和GenericObjectPool的区别是每个PooledObject都会和一个key关联,boorow对象时,key不存在就创建pooledObject,如果存在,就等待boorow,return时也要指定归还的pooledObject的key,coinfig的配置基本相同.
3.jedisPool借助common-pool实现
jedisPool使用common-pool管理jedis连接对象,很大程度上提高了jedis的连接性能,核心实现:
```java
public class JedisPool implements Closeable{
private ObjectPool pool;
public void JedisPool(GenericObjectPoolConfig config,String host,Integer port){
if (this.pool != null) {
this.closePool();
}
JedisFactory factory = new JedisFactory(host, port);
pool = new GenericObjectPool(factory,config);
}
public Jedis getResource(){
Jedis jedis=null;
try {
jedis =(Jedis) pool.borrowObject();
return jedis;
} catch (Exception e) {
returnResource(jedis);
throw new JedisConnectionException("Could not get a resource from the pool", e);
}
}
public void returnResource(Jedis jedis) {
try {
if(jedis!=null){
pool.invalidateObject(jedis);
}
}catch (Exception e){
throw new JedisException("Could not return the resource to the pool", e);
}
}
@Override
public void close() throws IOException {
}
private void closePool(){
try {
pool.close();
}catch (Exception e){
}
}
}
public class JedisFactory extends BasePooledObjectFactory {
private String host;
private Integer port;
public JedisFactory(String host, Integer port) {
this.host = host;
this.port = port;
}
@Override
public Jedis create() throws Exception {
return new Jedis(host,port);
}
@Override
public PooledObject wrap(Jedis obj) {
return new DefaultPooledObject<>(obj);
}
}
public class JedisPoolConfig extends GenericObjectPoolConfig {
//初始化一些自定义参数
}
```
#### spring redisTemplate

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台