Redis批量操作--增加,删除,模糊查询

需求来源

虽然说redis是纯内存操作,效率非常高,但是一次插入或者删除千万级或者亿级的操作,如果采用单条处理的api,整体处理效率还是很低的;另外,如果处理的数据量过大,稍有不慎可能就会导致client端的内存溢出或者服务端的负载过高,基于此,下面提供几种优雅的批量操作的jedis api供批量操作场景使用

具体实现

下面将从批量添加,批量查询以及批量删除三个方面提供例子:

批量添加

批量添加有两种方式,即使用jedis自带的api进行操作,以及使用jedis的Pipeline api来进行操作

jedis自带api:

可以使用jedis自带的zadd(zset),sadd(set)和hmset(hash)方法来进行批量操作处理,其中sadd方法value可以传入一个可变长度的string,具体实现的时候可以传一个数组,通过List的toArray方法实现,zadd方法传入一个存放Key以及Score方法的map,hmset方法传入一个存放KV的map即可,但是上述方法有一个问题需要注意,需要控制数组或者map的大小,如果是循环插入数据,防止数据过大导致client端OOM

sadd:

List<String> list = new ArrayList<>(100);
for (int i = 0; i < 100; i++) {
  list.add(i+"");
}
jedis.sadd("testSet",list.toArray(new String[list.size()]));

zadd:

Map<String,Double> map = new HashMap<>();
for (int i = 0; i < 100; i++) {
  map.put(i+"",Double.valueOf(i));
}
jedis.zadd("testZSet",map);

hmset:

Map<String,String> map = new HashMap<>();
for (int i = 0; i < 100; i++) {
  map.put(i+"",i+"");
}
jedis.hmset("testHash",map);

Pipeline:

redis的管道(Pipelining)操作是一种异步的访问模式,一次发送多个指令,不同步等待其返回结果。这样可以取得非常好的执行效率,具体代码如下:

jedis = jedispool.getResource();
Pipeline pip = jedis.pipelined();
long startTime = System.currentTimeMillis();
for (int i = start; i < end; i++) {
  pip.set((10000000 + i) + "", (10000000 + i) + "");
}
pip.sync();

批量删除

批量删除即使用jedis自带的api操作即可(zset按照score来删除使用专用的api,不在此文讨论范围之内),只是value参数传递一个可变长度的字符串来处理,具体实现的时候可以传一个数组,通过List的toArray方法实现,同样需要控制数组或者map的大小,如果是循环插入数据,防止数据过大导致client端OOM,具体代码如下:

//kv
jedis.del(list.toArray(new String[list.size()]));
//hash
jedis.hdel("testHash",list.toArray(new String[list.size()]));
//set
jedis.srem("testSet",list.toArray(new String[list.size()]));
//zset
jedis.zrem("testZSet",list.toArray(new String[list.size()]));

模糊查询

理论上来说有两种方式可以实现模糊查询,一种是jedis的keys方法,另外一种是使用scan方式来迭代处理模糊查询,但是keys方法只是理论上的实现方式,无法在生产环境的大数据量场景下使用,所以scan就成了唯一通用的靠谱选择:

jedis的keys(不建议使用):

该方法返回所有匹配表达式的key:

KEYS * 匹配数据库中所有 key 。
KEYS j?va 匹配单一字符:如 java , jeva 和 juva 等。
KEYS j*va 匹配0-那个字符:如 jva 和 jaaaaava 等。
KEYS j[ae]va 匹配给定的单个字符:如 java 和 jeva ,但不匹配 juva 。

功能看起来比较丰富,但是实际应用中有时候会出现需要遍历redis中的所有键值的需求,比如清理没用的键等等。但是keys这个命令性能真的很差,而且会使相关数据加锁,从而影响其他操作,按照redis官方的说法:

Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don’t use KEYS in your regular application code. If you’re looking for a way to find keys in a subset of your keyspace, consider using SCAN or sets.

由于执行keys命令,redis会锁定数据,如果数据庞大的话可能需要几秒或更长,对于生产服务器上锁定几秒这绝对是灾难了,所以不建议在生产服务器上使用keys命令,尤其是大数据量的场景下。

scan(建议使用):

说完不建议使用的,再来说说建议使用的scan,从redis的官方文档上看,2.8版本之后SCAN命令已经可用,允许使用游标从keyspace中检索键。对比KEYS命令,虽然SCAN无法一次性返回所有匹配结果,但是却规避了阻塞系统这个高风险,从而也让一些操作可以放在server节点上执行。

需要注意的是,SCAN 命令是一个基于游标的迭代器。SCAN 命令每次被调用之后, 都会向用户返回一个新的游标,用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。同时,使用SCAN,用户还可以使用keyname模式和count选项对命令进行调整。SCAN相关命令还包括SSCAN 命令、HSCAN 命令和 ZSCAN 命令,分别用于集合、哈希键及有续集等。

下面是样例代码:

String cursor = ScanParams.SCAN_POINTER_START;
ScanParams scanParams = new ScanParams();
scanParams.match(prefix);
scanParams.count(10000);
while (true) {
  //使用scan命令获取数据,使用cursor游标记录位置,下次循环使用
  ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
  cursor = scanResult.getStringCursor();// 返回0 说明遍历完成
  List<String> list = scanResult.getResult();
  for(String s : list){
    //TODO 业务逻辑
  }
  if ("0".equals(cursor)) {
    break;
  }
}

总结

虽然redis是一个基于内存的缓存神器,操作速度奇快,但是单次操作奇快也架不住数以千万甚至亿级的操作,此时如果还是用迭代的单条操作可能带来灾难性的后果,所以除了单条操作外,建议在实现中尽量使用批量操作。至于这几种批量操作叠加,如批量删除某些模糊匹配的key(数据量是千万级或者亿级)等相关场景,后续我会单独写一篇文章来说明该问题,敬请期待!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值