注册 登陆

    2019-10-01 10:58:56Redis中的批量操作Pipeline

    您现在的位置是: 首页 >  数据 >  Redis中的批量操作Pipeline

        Redis是一个开放源代码(BSD许可)的内存数据结构存储,用作数据库、缓存和消息代理。它支持字符串、哈希、列表、集合、带范围查询的排序集合、位图、超日志、带半径查询和流的地理空间索引等数据结构。Redis具有内置的复制、Lua脚本、LRU收回、事务和不同级别的磁盘上持久性,并通过Redis Sentinel和Redis群集的自动分区提供高可用性。

    为什么使用Redis
    1:性能(快)
    c语言实现,距离操作系统底层更近;
    数据存储在内存中,同时支持持久化;
    单线程、避免线程切换开销以及多线程的竞争问题;
    采用epoll,非阻塞I/O,不在网络上浪费时间;

    2、高并发
    服务在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库;

    3、丰富的数据类型
    Redis提供了一些丰富的数据结构,和对这些数据结构的丰富操作命令。
    string 存储整数、字符串、json、序列化数据
    hash 存储对象;类似mysql数据库的记录、一个string类型的field和value的映射表
    list 简单的字符串列表,按照插入顺序排序
    set 无序集合、唯一,适用唯一性场景,可以多个set进行操作计算
    sorted set 有序集合、适合排序类的业务场景;比如商品的销量
    bitmap 位图,可看做一个字符串或字符数组

    4.客户端语言支持丰富 https://redis.io/clients


    Redis的一些问题:

    1、缓存收益与成本的问题
    1.1、收益
    a、加速读写:通过缓存加速读写。
    b、降低服务器负载:Redis用来降低后端MySQL等数据库的负载。
    1.2、成本
    a、数据不一致:因为缓存层和数据层有时间窗口是不一致的,这和更新策略有关的。
    b、代码维护成本:业务代码一层缓存逻辑,增加开发和维护成本。
    c、服务器、流量成本

    怎么用Redis
    2、缓存更新的策略(怎么更新缓存)
    2.1、LRU、LFU、FIFO 算法策略。例如 maxmemory-policy,这是最大内存的策略,当 maxmemory 最大时,会优先删除过期数据。我们在控制最大内存,让它帮我们去删除数据。
    2.2、过期时间剔除,例如 expire。设置过期时间可以保证其性能,如果用户更新了重要信息,应该怎么办。所以这个时候就不适用了。
    2.3、主动更新,例如开发控制生命周期。

    3、缓存粒度问题 :一个商品对象有很多属性,写入缓存是全量属性,还是按照单个属性或者多个属性组合分片缓存?
    3.1、通用性:全量属性更好;
    3.2、占用空间:部分属性会更好。因为这样占用的空间是最小的;
    3.3、代码维护:表面上全量属性会更好。我们真的需要全量吗?其实我们在使用缓存的时候,优先考虑的是内存、网络传输,而不单单只是保证代码的扩展性。

    4、缓存穿透和雪崩问题
    4.1、空数据写null,空json串到缓存
    4.2、缓存过期时间长于缓存更新周期时间缓存穿透问题
    4.3、缓存不过期,由业务逻辑判断是否过期并执行重建缓存操作
    4.4、缓存时间随机,避免集中过期

    Redis管道

    由于redis是单线程的,下一次请求必须等待上一次请求执行完成后才能继续执行。然而使用Pipeline模式,客户端可以一次性的发送多个命令,无需等待服务端返回。这样就大大的减少了网络往返时间,提高了系统性能。

    下面用一个例子测试这两种模式在效率上的差别:

    public class PiplineTest {
        private static int count = 10000;
     
        public static void main(String[] args){
            useNormal();
            usePipeline();
        }
     
        public static void usePipeline(){
            ShardedJedis jedis = getShardedJedis();
            ShardedJedisPipeline pipeline = jedis.pipelined();
            long begin = System.currentTimeMillis();
            for(int i = 0;i<count;i++){
                pipeline.set("key_"+i,"value_"+i);
            }
            pipeline.sync();
            jedis.close();
            System.out.println("usePipeline total time:" + (System.currentTimeMillis() - begin));
        }
     
        public static void useNormal(){
            ShardedJedis jedis = getShardedJedis();
            long begin = System.currentTimeMillis();
            for(int i = 0;i<count;i++){
                jedis.set("key_"+i,"value_"+i);
            }
            jedis.close();
            System.out.println("useNormal total time:" + (System.currentTimeMillis() - begin));
        }
     
        public static ShardedJedis getShardedJedis(){
            JedisPoolConfig poolConfig = new JedisPoolConfig();
            poolConfig.setMaxTotal(2);
            poolConfig.setMaxIdle(1);
            poolConfig.setMaxWaitMillis(2000);
            poolConfig.setTestOnBorrow(false);
            poolConfig.setTestOnReturn(false);
            JedisShardInfo info1 = new JedisShardInfo("127.0.0.1",6379);
            JedisShardInfo info2 = new JedisShardInfo("127.0.0.1",6379);
            ShardedJedisPool pool = new ShardedJedisPool(poolConfig, Arrays.asList(info1,info2));
            return pool.getResource();
        }

    }  

     输出结果: 

    useNormal total time:772

    usePipeline total time:112 

     从测试的结果可以看出,使用pipeline的效率要远高于普通的访问方式。

     那么问题来了,在什么样的情景下适合使用pipeline呢?

     有些系统可能对可靠性要求很高,每次操作都需要立马知道这次操作是否成功,是否数据已经写进redis了,那这种场景就不适合。

    还有的系统,可能是批量的将数据写入redis,允许一定比例的写入失败,那么这种场景就可以使用了,比如10000条一下进入redis,可能失败了2条无所谓,后期有补偿机制就行了,比如短信群发这种场景,如果一下群发10000条,按照第一种模式去实现,那这个请求过来,要很久才能给客户端响应,这个延迟就太长了,如果客户端请求设置了超时时间5秒,那肯定就抛出异常了,而且本身群发短信要求实时性也没那么高,这时候用pipeline最好了。

关键字词: Redis中的批量操作Pipeline

0