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