redis pipeline

“redis pipeline” why pipeline ? Redis 客户端与 server 的请求/响应模型 前面的文章 Redis 底层协议RESP详解 ,介绍到 redis 客户端与 redis-server 交互通信,采用的 TCP 请求/响应模型; 我们通过 Redis 客户端执行命令,如set key value,客户端遵循RESP协议,将命令的协议串发送给redis-server执行,redis-server执行完成后再同步返回结果。 手写Redis客户端-实现自己的Jedis 对这一过程进行了重点分析,并遵循RESP实现了自己简易版的Redis客户端。 Redis客户端与server通信,使用的是客户端-服务器 (CS) 模式;每次交互,都是完整的请求/响应模式。 这意味着通常情况下一个请求会遵循以下步骤: 客户端连接服务端,基于特定的端口,发送一个命令,并监听Socket返回,通常是以阻塞模式,等待服务端响应。 服务端处理命令,并将结果返回给客户端。 很显然,我们使用jedis或lettuce执行Redis命令,每次都是建立socket连接,并等待返回。 每个命令底层建立TCP连接的时间是省不掉的,即使我们都是在内网使用Redis,内网快但请求/响应的往返时间是不会减少的。 当需要对一组kv进行批量操作时,这组命令的耗时=sum(N*(建立连接时间+发送命令、返回结果的往返时间RTT)),随批量操作的key越多,时间累加呈线性增长。 顺理成章的,就出现了像数据库连接池等池化思想的衍生,redis连接也进行“池化”,如JedisPool。 JedisPool就足够了? 池化connection后,每次执行命令都从池子里“借”,用完之后再将connection“还”到池子。只是节省了创建TCP连接的时间; 当需要对一组kv进行批量操作时,JedisPool池子里的connection连接、极端情况都被用完了,怎么办? ——需要等待JedisPool池里有可复用的connection才能继续执行; 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) 如果在指定的等待时间内没有等到idle空闲连接,就报异常了。 尽管使用了池化、将connection进行复用,但不可避免的带来其他问题: https://jjlu521016.github.io/2018/12/09/JedisPool常见问题.html 除了池化的connection会被瞬间用完,Redis官网还给出了另外一个性能损耗的原因: It’s not just a matter of RTT https://redis.io/topics/pipelining 虽然池化的connection,节省了建立连接的时间,但多条命令(发送命令到sever、server返回结果)分别执行多次socket网络IO,涉及到read()和write() syscall系统调用,这意味着从用户态到内核态。上下文切换是巨大的速度损失。 ...

2021-07-24 · 1 min · 184 words · -

redis scan

“redis scan” redis用scan代替keys 众所周知,当redis中key数量越大,keys 命令执行越慢,而且最重要的会阻塞服务器,对单线程的redis来说,简直是灾难,且在生产环境,keys命令一般是被禁止的。scan可用来替换keys请求。 所以说官方的建议是: 生产环境屏蔽掉 keys 命令。 在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 。 scan用法 SCAN cursor [MATCH pattern] [COUNT count] scan 0 match *news* count 3 scan是一个增量迭代式的命令,这意味着每次调用这个命令都会返回一个游标cursor,该游标用于下次查询。查询开始时,cursor值为0;当查询结束时,cursor的值也回归到0。 举个例子: 开始查询,scan cursor为0,返回的cursor为17 redis 127.0.0.1:6379> scan 0 “17” “key:12” “key:8” “key:4” “key:14” “key:16” “key:17” “key:15” “key:10” “key:3” “key:7” “key:1” 下一次查询,以上一次查询返回的cursor为起始位置 redis 127.0.0.1:6379> scan 17 查询返回cursor为0,标志查询结束 “0” “key:5” “key:18” “key:0” “key:2” “key:19” “key:13” “key:6” “key:9” “key:11” count count可理解为迭代过程中的步长,指每次调用scan时应执行的工作量,该值默认为10。每次调用count的值可以随意指定,只要下一次传递cursor是上一次调用返回的cursor就行。 match 需要注意的是,match操作时在元素被检出后执行的。假设redis中只有少量元素符合pattern条件,那么很可能在多次调用中scan返回的数据为空,例如: 查找key中包含11的键,因为这里没有指定count,所以默认为10 redis 127.0.0.1:6379> scan 0 MATCH 11 ...

2021-07-21 · 2 min · 315 words · -

redis list

redis list 在 Redis 中, List 类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。List中可以包含的最大元素数量是4294967295。 从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在常量时间内完成。然而需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效的。 Redis 的列表经常被用作队列(queue),用于在不同程序之间有序地交换消息(message)。 一个程序(称之为生产者,producer)通过LPUSH命令将消息放入队列中,而另一个程序(称之为消费者,consumer)通过RPOP命令取出队列中等待时间最长的消息。 不幸的是,在这个过程中,一个消费者可能在获得一个消息之后崩溃,而未执行完成的消息也因此丢失。 使用RPOPLPUSH命令可以解决这个问题,因为它在返回一个消息之余,还将该消息添加到另一个列表当中,另外的这个列表可以用作消息的备份表: 假如一切正常,当消费者完成该消息的处理之后,可以用LREM命令将该消息从备份表删除。 另一方面,助手(helper)程序可以通过监视备份表,将超过一定处理时限的消息重新放入队列中去(负责处理该消息的消费者可能已经崩溃),这样就不会丢失任何消息了。 头元素和尾元素 头元素指的是列表左端/前端第一个元素,尾元素指的是列表右端/后端第一个元素。 举个例子,列表list包含三个元素: x, y, z,其中x是头元素,而z则是尾元素。 空列表 指不包含任何元素的列表,Redis将不存在的key也视为空列表。 一个列表最多可以包含 232 – 1 个元素 (4294967295, 每个列表超过40亿个元素)。 相关命令 LPUSH LPUSH key value1 [value2] 将一个或多个值插入到列表头部 RPUSH RPUSH key value1 [value2] 将一个或多个值value插入到列表key的表尾。 LPOP key 移出并获取列表的第一个元素 RPOP key RPOP key 移除列表的最后一个元素,返回值为移除的元素。 LLEN key 获取列表长度 LRANGE key start stop LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 BLPOP BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 ...

2015-12-01 · 4 min · 771 words · -

redis hash

redis hash Redis hash 是一个 string 类型的 field和 value的映射表.一个 key可对应多个 field, 一个 field对应一个 value。将一个对象存储为 hash类型,较于每个字段都存储成string类型更能节省内存。新建一个 hash对象时开始是用 zipmap(又称为small hash)来存储的。这个 zipmap其实并不是 hash table,但是zipmap 相比正常的 hash实现可以节省不少 hash本身需要的一些元数据存储开销。尽管 zipmap的添加, 删除,查找都是O(n),但是由于一般对象的field数量都不太多。所以使用zipmap也是很快的,也就是说添加删除平均还是O(1)。如果field或者value的大小超出一定限制后,Redis会在内部自动将zipmap替换成正常的hash实现。 hash操作命令如下: 删除 key del key hset 向名称为 key 的 hash 中添加元素 hset key field value hget hget(key, field) 返回名称为key的hash中field对应的value hsetnx HSETNX key field value 将哈希表key中的域field的值设置为value,当且仅当域field不存在。若域field已经存在,该操作无效。 如果key不存在,一个新哈希表被创建并执行h#setnx命令。 hmget hmget(key, field1, …,field N) 返回名称为key的hash中field i对应的value hmset hmset(key, field1, value1,…,field N, value N) 向名称为key的hash中添加元素field i<—>value i ...

2015-07-28 · 1 min · 177 words · -

Redis

Redis REmote DIctionary Server(Redis) 是一个由SalvatoreSanfilippo写的key-value存储系统。 Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash (哈希类型) 。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave (主从)同步。 Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。 Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。 redis的官网地址,非常好记,是redis.io。 (特意查了一下,域名后缀io属于国家域名,是british Indian Ocean territory,即英属印度洋领地) 目前,Vmware在资助着redis项目的开发和维护。redis[2] 的作者,叫Salvatore Sanfilippo,来自意大利的西西里岛,现在居住在卡塔尼亚。目前供职于Pivotal公司。他使用的网名是antirez。 Redis 4.0 之后的版本,情况就有了一些变动,新版的 Redis 服务在执行一些命令时就会使用『主处理线程』之外的其他线程,例如 UNLINK、FLUSHALL ASYNC, FLUSHDB ASYNC 等非阻塞的删除操作。 Redis 在较新的版本中引入了多线程,不过是在部分命令上引入的,其中包括非阻塞的删除操作,在整体的架构设计上,主处理程序还是单线程模型的 线程模型 使用单线程模型能带来更好的可维护性,方便开发和调试; 使用单线程模型也能并发的处理客户端的请求; Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU 6.0 Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂 I/O 多路复用的主要作用是让我们可以使用一个线程来监控多个连接是否可读或者可写,但是从网络另一头发的数据包需要先解序列化成 Redis 内部其他模块可以理解的命令,这个过程就是 Redis 6.0 引入多线程来并发处理的。 I/O 多路复用模块收到数据包之后将其丢给后面多个 I/O 线程进行解析,I/O 线程处理结束后,主线程会负责串行的执行这些命令,由于向客户端发回数据包的过程也是比较耗时的,所以执行之后的结果也会交给多个 I/O 线程发送回客户端。 AOF AOF 是 Redis 的一种持久化机制,它会在每次收到来自客户端的写请求时,将其记录到日志中,每次 Redis 服务器启动时都会重放 AOF 日志构建原始的数据集,保证数据的持久性。 Keyspace Notifications, 键空间通知 在Redis2.8.0版本的时候,推出 Keyspace Notifications future。 Keyspace Notifications 此特性允许客户端可以以 订阅/发布 (Sub/Pub) 模式,接收那些对数据库中的键和值有影响的操作事件。这些操作事件具体来说,就是 hash , del, expire , set , lpop 等。 那么你可能会问,redis keyspace 到底有啥用处? 简单说,对于我个人主要关注keyspace几个扩展场景: ...

2015-07-13 · 2 min · 357 words · -

Redis, Memcache, Guava, Ehcache 中的算法

Redis, Memcache, Guava, Ehcache 中的算法 缓存那些事,一是内存爆了要用LRU(最近最少使用, Least Recently Used)、LFU(最少访问次数, Least Frequently Used)、FIFO的算法清理一些;二是设置了超时时间的键过期便要删除,用主动或惰性的方法。 在看所有的细节之前,可以看一篇相当专业的《缓存算法》,世界真宽阔,算法真奇妙。 LRU 简单粗暴的Redis 今天看Redis3.0的发行通告里说,LRU算法大幅提升了,就翻开源码来八卦一下,结果哭笑不得,这旧版的"近似LRU"算法,实在太简单,太偷懒,太Redis了。 在Github的Redis项目里搜索lru,找到代码在redis.c的freeMemoryIfNeeded()函数里。 先看2.6版的代码: 竟然就是随机找三条记录出来,比较哪条空闲时间最长就删哪条,然后再随机三条出来,一直删到内存足够放下新记录为止…….可怜我看配置文档后的想象,一直以为它会帮我在整个Redis里找空闲时间最长的,哪想到我有一百万条记录的时候,它随便找三条就开始删了。 好,收拾心情再看3.0版的改进: 现在每次随机五条记录出来,插入到一个长度为十六的按空闲时间排序的队列里,然后把排头的那条删掉,然后再找五条出来,继续尝试插入队列………嗯,好了一点点吧,起码每次随机多了两条,起码不只在一次随机的五条里面找最久那条,会连同之前的一起做比较…… 中规中矩的Memcached 相比之下,Memcached实现的是再标准不过的LRU算法,专门使用了一个教科书式的双向链表来存储slab内的LRU关系,代码在item.c里,详见memcached源码分析–LRU队列与item结构体,元素插入时把自己放到列头,删除时把自己的前后两个元素对接起来,更新时先做删除再做插入。 分配内存超限时,很自然就会从LRU的队尾开始清理。 同样中规中矩的Guava Cache Guava Cache同样做了一个双向的Queue,见LocalCache中的AccessQueue类,也会在超限时从Queue的队尾清理,见evictEntries()函数。 和Redis旧版一样的Ehcache/Hazelcast 看文档,居然和Redis2.6一样,直接随机8条记录,找出最旧那条,刷到磁盘里,再看代码,Eviction类 和 OnHeapStore的evict()函数。 再看Hazelcast,几乎一样,随机取25条。 这种算法,切换到LFU也非常简单。 小结 不过后来再想想,也许Redis本来就不是主打做Cache的,这种内存爆了需要通过LRU删掉一些元素不是它的主要功能,默认设置都是noeviction——内存不够直接报错的,所以就懒得建个双向链表,而且每次访问时都要更新它了,看Google Group里长长的讨论,新版算法也是社区智慧的结晶。何况,Ehcache和Hazelcast也是和它的旧版一样的算法,Redis的新版还比这两者强了。 后来,根据@刘少壮同学的提示,JBoss的InfiniSpan里还实现了比LRU更高级的LIRS算法,可以避免一些冷数据因为某个原因被大量访问后,把热数据挤占掉。 过期键删除 如果能为每一个设置了过期的元素启动一个Timer,一到时间就触发把它删掉,那无疑是能最快删除过期键最省空间的,在Java里用一条DeplayQueue存着,开条线程不断的读取就能做到。但因为该线程消耗CPU较多,在内存不紧张时有点浪费,似乎大家都不用这个方法。 所以有了惰性检查,就是每次元素被访问时,才去检查它是否已经超时了,这个各家都一样。但如果那个元素后来都没再被访问呢,会永远占着位子吗?所以各家都再提供了一个定期主动删除的方式。 Redis 代码在redis.c的activeExpireCycle()里,看过文档的人都知道,它会在主线程里,每100毫秒执行一次,每次随机抽20条Key检查,如果有1/4的键过期了,证明此时过期的键可能比较多,就不等100毫秒,立刻开始下一轮的检查。不过为免把CPU时间都占了,又限定每轮的总执行时间不超过1毫秒。 Memcached Memcached里有个文不对题的LRU爬虫线程,利用了之前那条LRU的队列,可以设置多久跑一次(默认也是100毫秒),沿着列尾一直检查过去,每次检查LRU队列中的N条数据。虽然每条Key设置的过期时间可能不一样,但怎么说命中率也比Redis的随机选择N条数据好一点,但它没有Redis那种过期的多了立马展开下一轮检查的功能,所以每秒最多只能检查10N条数据,需要自己自己权衡N的设置。 Guava Cache 在Guava Cache里,同一个Cache里所有元素的过期时间是一样的,所以它比Memached更方便,顺着之前那条LRU的Queue检查超时,不限定个数,直到不超时为止。而且它这个检查的调用时机并不是100毫秒什么的,而是每次各种写入数据时的preWriteCleanup()方法中都会调用。 吐槽一句,Guava的Localcache类里面已经4872行了,一点都不轻量了。 Ehcache Ehcache更乱,首先它的内存存储中只有惰性检查,没有主动检查过期的,只会在内存超限时不断用近似LRU算法(见上)把内存中的元素刷到磁盘中,在文件存储中才有超时检查的线程,FAQ里专门解释了原因。 然后磁盘存储那有一条8小时左右跑一次的线程,每次遍历所有元素…..见DiskStorageFactory里的DiskExpiryTask。 一圈看下来,Ehcache的实现最弱。 文章持续修改,转载时请保留原链接: http://calvin1978.blogcn.com/articles/lru.html

2015-06-28 · 1 min · 56 words · -