Redis 管道(Pipelining)

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。

 这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。

 Redis很早就支持管道(pipelining)技术,因此无论你运行的是什么版本,你都可以使用管道(pipelining)操作Redis。下面是一个使用的例子:

>$ (printf “PING\r\nPING\r\nPING\r\n”; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG

 我们没有为每个命令都花费了RTT开销,而是只用了一个命令的开销时间。

 使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令,等等。这样速度几乎是相同的,但是在回复这10k命令队列需要非常大量的内存用来组织返回数据内容。

使用Luke协议

使用正常模式的Redis 客户端执行大量数据插入不是一个好主意:因为一个个的插入会有大量的时间浪费在每一个命令往返时间上。使用管道(pipelining)是一种可行的办法,但是在大量插入数据的同时又需要执行其他新命令时,这时读取数据的同时需要确保请可能快的的写入数据。

只有一小部分的客户端支持非阻塞输入/输出(non-blocking I/O),并且并不是所有客户端能以最大限度的提高吞吐量的高效的方式来分析答复。

例如,如果我们需要生成一个10亿的`keyN -> ValueN’的大数据集,我们会创建一个如下的redis命令集的文件:

SET Key0 Value0
SET Key1 Value1

SET KeyN ValueN

一旦创建了这个文件,其余的就是让Redis尽可能快的执行。在以前我们会用如下的netcat命令执行:

(cat data.txt; sleep 10) | nc localhost 6379 > /dev/null

然而这并不是一个非常可靠的方式,因为用netcat进行大规模插入时不能检查错误。从Redis 2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。

使用pipe mode模式的执行命令如下:

cat data.txt | redis-cli –pipe

这将产生类似如下的输出:


All data transferred. Waiting for the last reply…
Last reply received from server.
errors: 0, replies: 1000000

使用redis-cli将有效的确保错误输出到Redis实例的标准输出里面。

Redis浅显的理解

Redis与其他key-value存储有什么不同?

主要有以下两个方面:

  • Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
  • Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存。在内存数据库方面的另一个优点是, 相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。 同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

Redis是什么?

 Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

Redis 事务

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。
  • 命令入队。
  • 执行事务。

几个常用命令:

  • Redis Multi 命令用于标记一个事务块的开始。
    事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。
  • Redis Discard 命令用于取消事务,放弃执行事务块内的所有命令。
  • Redis Exec 命令用于执行所有事务块内的命令。
  • Redis Watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
  • Redis Unwatch 命令用于取消 WATCH 命令对所有 key 的监视。

Redis有什么特点?

 1). 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

 2). 支持丰富数据类型,支持string,list,set,sorted set,hash

 3). 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

 4). 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

Redis常见性能问题和解决方案

1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。

2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。

4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。

Redis是单进程单线程的

redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销

为什么Redis需要把所有数据放到内存中?

 Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。

 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。   

Redis的并发竞争问题如何解决?

 Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是
由于客户端连接混乱造成。

对此有2种解决方法:

  1.客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。

  2.服务器角度,利用setnx实现锁。

  注:对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;第二种需要用到Redis的setnx命令,但是需要注意一些问题。

Redis持久化的几种方式

 1、快照(snapshots)

  缺省情况情况下,Redis把数据快照存放在磁盘上的二进制文件中,文件名为dump.rdb。你可以配置Redis的持久化策略,例如数据集中每N秒钟有超过M次更新,就将数据写入磁盘;或者你可以手工调用命令SAVE或BGSAVE。


工作原理

  • Redis forks

  • 子进程开始将数据写到临时RDB文件中。

  • 当子进程完成写RDB文件,用新文件替换老文件。

  • 这种方式可以使Redis使用copy-on-write技术。

  2、AOF

  快照模式并不十分健壮,当系统停止,或者无意中Redis被kill掉,最后写入Redis的数据就会丢失。这对某些应用也许不是大问题,但对于要求高可靠性的应用来说,

  Redis就不是一个合适的选择。

  Append-only文件模式是另一种选择。

  你可以在配置文件中打开AOF模式

  3、虚拟内存方式

  当你的key很小而value很大时,使用VM的效果会比较好.因为这样节约的内存比较大.

  当你的key不小时,可以考虑使用一些非常方法将很大的key变成很大的value,比如你可以考虑将key,value组合成一个新的value.

  vm-max-threads这个参数,可以设置访问swap文件的线程数,设置最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的.可能会造成比较长时间的延迟,但是对数据完整性有很好的保证.

  如果数据量很大,可以考虑分布式或者其他数据库

RedisTemplate几个方法的用法

  • RedisTemplate封装了lettuce
  • Redis支持五种数据类型:

    1.String(字符串)
    2.Hash(哈希)
    3.List(列表)
    4.Set(集合)
    5.zset(有序集合)
  • spring集成redis的RedisTemplate,也分别提供的对这些数据类型的操作。

    spring引用redis需要添加依赖,在项目根目录下的pom.xml里加入下方代码,版本号需要自己检查。

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>
    spring-boot-starter-data-redis
    </artifactId>
    <version>2.0.4.RELEASE</version>
    </dependency>


    导入依赖后需要在application.properties里加入redis的配置信息,格式像这样按实际情况修改
    spring.redis.host=localhost
    spring.redis.port=6379
    spring.redis.database=0

    测试连通性
    @Component
    public class RedisTemplateTest {
    @Autowired
    RedisTemplate redisTemplate;
    @PostConstruct
    public void get() {
    redisTemplate.opsForValue().set(“ping”,”pong”);
    System.out.println(redisTemplate.opsForValue().get(“ping”));
    }
    }
  • RedisTemplate中对几种redis的数据类型的常用操作

    redisTemplate.opsForValue()
    redisTemplate.opsForHash();//操作hash
    redisTemplate.opsForList();//操作list
    redisTemplate.opsForSet();//操作set
    redisTemplate.opsForZSet();//操作有序set

    redisTemplate.opsForValue().set("name","tom");
    redisTemplate.opsForValue().get("name");  
    

    //输出结果为tom

    redisTemplate.opsForValue().set("name","tom",10,TimeUnit.SECONDS);
    redisTemplate.opsForValue().get("name");
    

    //由于设置的是10秒失效,十秒之内查询有结果,十秒之后返回为null

    redisTemplate.opsForValue().set("key1","hello world");
    redisTemplate.opsForValue().set("key1","redis", 6);
    System.out.println(redisTemplate.opsForValue().get("key1"));
    

    //结果:hello redis

    System.out.println(redisTemplate.opsForValue().setIfAbsent("key1","value1"));
    

    //false key1之前已经存在

    System.out.println(redisTemplate.opsForValue().setIfAbsent("key2","value2"));
    

    //true key2之前不存在

    Map<String,String> maps = new HashMap<String, String>();
    maps.put("key1","value1");
    maps.put("key2","value2");
    maps.put("key3","value3");
    redisTemplate.opsForValue().multiSet(maps);
    List<String> keys = new ArrayList<String>();
    keys.add("key1");
    keys.add("key2");
    keys.add("key3");
    System.out.println(redisTemplate.opsForValue().multiGet(keys));
    redisTemplate.opsForValue().set("key1","value");
    System.out.println(redisTemplate.opsForValue().getAndSet("key1","value2"));
    System.out.println(redisTemplate.opsForValue().get("key1"));
    long stringValueLong = redisTemplate.opsForValue().increment("longValue",1);   
    System.out.println("通过increment(K key, long delta)方法以增量方式存储long值:" + stringValueLong);  
    

    //key不存在则创建key并自增增量,已存在则直接自增增量

  • redisTemplate.opsForValue().increment(key,value)可以实现类似浏览数记录、点赞数记录的功能。key不存在则创建key并自增增量,已存在则直接自增增量.

  • redisTemplate.opsForValue().set(key,value,超时时长, 超时时长单位)这个方法则可以用来实现期限优惠券、设置token过期时间等功能。
  • redis对过期键的三种清除策略
    惰性删除:当读写到一个过期key时自动删除,可能会导致内存被大量过期key占满溢出
    定时删除:为key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除,可能会消耗大量CPU资源
    定期删除:每隔一段时间执行一次或内存占用达到设置的值时定期操作,随机扫描一定数量的Key,删除其中的过期数据。这种方法对资源消耗较少同时也保证了redis内存占用合理。
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×