再谈redis

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/GoSaint/article/details/84890920

         1 Redis String和Hash的区别

         笔者之前面试被问到一个问题,就是说:Redis的String和Hash有什么区别。当时我回答到String类型可以保存简单的key-value形式的数据。对于对象的保存一般使用hash存储。这个回答我自己都不满意,因为我回答的没有错,但是我没有再往下详谈。因为平时可能就是这么使用的,但是没有深究其理。当然现在也有很多人使用string保存对象,只不过需要序列化对象以及反序列化的操作而已。对于查找和删除的时间复杂度而言都是O(1)。hash在存储对象方面占用内存较少。前期使用的是zipmap来操作的。而且hash对数据的保存更稳直观,并且可以部分修改数据。下面的代码演示的是hash对于数据的部分修改。

80:0>HMSET runoobkey name "redis tutorial" description "redis basic commands for caching" likes 20 visitors 23000
"OK"
80:0>HGETALL runoobkey
 1)  "name"
 2)  "redis tutorial"
 3)  "description"
 4)  "redis basic commands for caching"
 5)  "likes"
 6)  "20"
 7)  "visitors"
 8)  "23000"
80:0>HMSET runoobkey name "redis tutorial" description "redis basic commands for caching" likes 20 visitors 23004
"OK"
80:0>hgetall runoobkey
 1)  "name"
 2)  "redis tutorial"
 3)  "description"
 4)  "redis basic commands for caching"
 5)  "likes"
 6)  "20"
 7)  "visitors"
 8)  "23004"
80:0>

       而String set会进行数据的覆盖。并且hash一次性可以保存多个数据,这个string不能具备。当时面试官给我给了一个场景:就是在删除数据的时候谁更加有优势。这里我想说的是String很容易误删数据,假设当key的前几位都相同的时候,就比较容易删错。HDEL key field1 [field2] 。这是hash的删除命令。尽管key的前面相同,但是还有后续的字段做保证。假设String有下面的组合:

 key=user001 value="user1"

 key=user002 value="user2"

删除命令:del user001。但是不小心删除了002。你看key的差别是很小的。hash是不存在这样的问题的。

     2 Redis 和 memcached相比具有哪些优势?

      对于这个问题,其实还是比较简单的。首先二者均为缓存使用的始终实现方式。不同的是支持数据的类型memcached逊色点。memcache只支持简单的key-valeu形式。而redis支持string,list,hash,set、sortedSet,pub/sub(发布订阅)。更加高级的还有Redis Module,像BloomFilter,RedisSearch,Redis-ML等。BloomFilter这个在实际开发场景中对于防止缓存击穿是一种比较高效的选择。Redis支持久化,有AOF和RDB。memcache不支持持久化,单纯的内存数据库。在速度方面redis更快。

     3 Redis 是单进程单线程的,为什么还那么快。

      首先我们应该明白一点的是,Redis的性能瓶颈永远不是CPU。因为它是基于内存操作的NOSQL数据库。

    (1)Redis完全基于内存的操作。get每秒110000.set每秒80000次的并发。

    (2)采用单进程单线程。避免了多线程的上下文切换和竞争条件。也没有多进程或者多线程下的切换导致CPU性能消耗。没有加锁或者释放锁,不存在出现死锁产生的性能消耗。

    (3)使用多路复用IO模型。这里的多路指的是网络连接,复用指的是复用同一个线程。还是那句话,Redis操作CPU不是性能瓶颈,最有可能成为Redis性能瓶颈的就是网络和内存。知识Redis官方给出的比较含糊的回答。单线程速度很快,那么就没必要使用多线程啦。多线程的管理毕竟还是比较麻烦。在生产中我们可以使用多个Redis实例处理数据。值得注意的是Redis的单线程是指的网络连接的请求,而不是Redis server.Redis server在实际处理数据的时候还是使用了多线程的。比如一个线程读取数据,一个线程异步的持久化操作等。

 4 Redis分布式锁

    对于分布式锁的实现,Redis可以使用setnx命令。(set if not exists)。当key不存在时设置值为valle,返回1,否则返回0。   

80:0>setnx keys value
"1"
80:0>setnx keys value1
"0"
80:0>

在实际开发阶段,这里的使用还是要比较谨慎。

      错误1:直接使用setnx,返回1之后为key设置过期时间。这里假设setnx成功,但是此时Redis Server挂了之后怎么办,由于没有为key设置过期时间,那么此时锁得不到释放,造成死锁的产生。

      错误2:setnx(key,expire)。这种实现方式看似没有问题,但是没有请求标识符,也就是说谁都可以解锁。更为要命的是,我们需要在代码中判断key的过期时间,那么必然就要求系统时间一致,因此这也是一个问题。保证系统时间的一致。

      关于如何正确的使用redis分布式锁。有一个命令set(key,reuqestId,NX,PX,expire);这个是符合redis分布式锁的最佳实践的。

 5 热点数据问题1

      场景介绍:假设redis中有1亿个key,10w个key都是以相同的前缀开头的,如何查找出来?

       在这里我先设置一些具有相同前缀的key。

80:0>mset 20181208user001 user001 20181208user002 user002 20181208user003 user003 20181208user004 user004
"OK"

    在redis中,可以使用keys做正则匹配查询,但是存在性能问题。假设我们redis正在线程运行,使用keys会造成redis的阻塞。直到查询结束恢复正常。这个时候我们可以使用scan命令,scan可以获取指定格式的key列表,单数存在一定的重复,我们在客户端只需要做一次去重就OK啦。下面分别是使用keys和sacn查询key.

80:0>keys 20181208user*
 1)  "20181208user003"
 2)  "20181208user002"
 3)  "20181208user001"
 4)  "20181208user004"

scan命令:游标迭代器

80:0>scan 0 match 20181208user*
 1)  "25"
 2)    1)   "ID22_lock"
  2)   "20181208user001"
  3)   "caozg"
  4)   "ID39_lock"
  5)   "comment"
  6)   "ID28_lock"
  7)   "ID21_lock"
  8)   "\xAC\xED\x00\x05t\x00\x04USER"
  9)   "20181208user003"
  10)   "20181208user002"
  11)   "ID17_lock"

80:0>scan 25 match 20181208user*
 1)  "23"
 2)    1)   "20181208user004"

80:0>scan 23 match 20181208user*
 1)  "0"
 2)    1)   "123bh"
  2)   "ID24_lock"
  3)   "ID20_lock"
  4)   "ID14_lock"

80:0>

第一次迭代scan 0。只有user001 user002 user003。然后第二次迭代,选择上一次的游标,scan 25 ,迭代出user004。第三次迭代,scan 23。返回游标0.表示迭代结束,我们在客户端需要做一次帅选即可。

猜你喜欢

转载自blog.csdn.net/GoSaint/article/details/84890920