Redis有意思的一些特征
近些年,Redis得到大量的使用。比如使用Redis存储会话,缓存实体对象(用于缓解耗时的数据查询操作)以及一些需要频繁的操作的对象或数据。在使用redis的过程中,我看到很多人直接使用String存储对象,其实这并不是最佳的方案,其实使用哈希结构存放对象更方便操作,性能也更好。你可以直接修改哈希结构中某一个键,或者对其中的数字值键值进行加减操作,而无需对其进行解码。而string类型,你需要在json(你可以选择其他格式)与对象之间转换,操作更加复杂性。
上面提到了Redis中的哈希结构的一个有意思的特征,就是可以对其中的数字键值进行加减操作。一些我认为有意思的功能或操作,我列举在下面。
数据结构
redis常用的数据结构有字符串,列表,集合,哈希表,有序集合等。
字符串既可以存放字符数据,还可以存储二进制数据,比如图片,视频等。redis不仅可以对整个字符串进行操作,还可以对字符串中的某一字符进行操作,也可以对其中的某二进制位进行操作。如果Stirng存储的整数,浮点数,就可以对其中的整数,浮点数进行加减运算。
哈希表,存放的是一个键和值。最合适用于存放对象的属性值。这样就可以直接修改对象中的某一个属性,也就是修改某一个键对应的值,如果键对应的值是整数或浮点数,还可以对其进行加减运算,比如用户对象有关注人数,粉丝数等属性,当关注了新的用户时,只要将哈希表中相应的键值加一即可,如取消的某一用户的关注,则对应的键值减一就行,对于粉丝数也是如何操作。
有序集合,有序集合包括成员和分值,并且根据分值排序,有一个特别的特征时,当分值相等时,那么Redis将根据成员的字符串值排序,这个特征在某些情况下,相当有用。
事务
其实redis中的事务是通过MULTI,EXEC,CANCEL,WATCH,DISCARD等五个命令实现。Redis中的事务并不具备像数据库事务一样的原子性,持久性,完整性,隔离性等特征。redis的事务可以理解为一个乐观锁,先通过WATCH命令监视一个或多个数据结构,然后发送有MULTI,EXEC包裹的多条命令,如果监视的数据没有发生变化,那么MULTI,EXEC包裹的多条命令将会执行成功。如果监视的数据发生了变化,那么由MULTI,EXEC包裹的命令将不会执行,并返回错误信息给客户端,客户端可以根据情况选择重试,或直接放弃,或其他的操作。
交集 并集 差集
有序集合(ZSET)之间,集合(SET)之间,集合与有序集合之间可以进行交集,并集,差集运算。多个有序集合执行交集或并集时,如果有相同的成员,那么相同成员间的分值可以取和,也可以取最大值或最小值。比如现有有序集合A,包含成员X,它的分值为10,另外有序集合B,也包含成员X,它的分值为20,那么A,B进行交集运算的得到的新的有序集合也包含成员X,如果取和,那么分值就为30,如果取最大值,最小值,那么分别为20,10。
另外一个就是集合(SET)与有序结合(ZSET)之间的交集,并集,差集元素,我们都知道集合只有成员,并没有分值,那么Redis如何处理这种情况呢?其实Redis就把集合当成分值全为0的有序集合类处理。其实想想,也很容易理解。
那么这些操作有什么用呢?你可以想象一下,有一个这样的电子商务网站,系统想要记录所有商品的浏览次数。在这种情况,系统可以为每一个商品分类构建一个用来存放商品浏览次数的有序集合,成员为商品ID,分值为浏览次数。如果需要获取商品分类下的最多浏览次数的商品,只需通过ZREVRANGE命令获取前N商品即可。现在系统需要获取所有商品中最受欢迎的商品,那么该如何做呢?难道需要通过遍历所有的有序集合找出这样商品吗?没有必要,只需要求所有商品分类下的有序集合的并集,然后通过ZREVRANGE命令获取前N商品即可。这样是不是很方便呢!
分布式锁
redis并没有一个分布式锁,但是提供了一个机制,可以利用这个机制构建分布式锁。这个机制就是SET(key,value,NX),通过将第三个参数设置为NX,用于表示如果不存在此键值,这可以设置成功,如果存在此值,则设置失败。构建一个正确的分布式锁看似很容易,其实比较困难。要处理一些棘手的问题:
1.持有锁的进程因为操作时间过长而导致锁被自动释放,但进程本身并不知晓这一点,甚至还可能会错误地释放掉了其他进程持有的锁。
2.一个持有锁并打算执行长时间操作的进程已经崩溃,但其他想要获取锁的进程不知道哪个进程持有着锁,也无法检测出持有锁的进程已经崩溃,只能白白地浪费时间等待锁被释放。
3.在一个进程持有的锁过期之后,其他多个进程同时尝试去获取锁,并且都获得了锁。
4.上面提到的第一种情况和第三种情况同时出现,导致有多个进程获得了锁,而每个进程都以为自己是唯一一个获得锁的进程。
上面的一段文字摘自《Redis实战》。为了避免这些问题,一是客户端的操作线程设置的值都必须是唯一的。释放锁之前,必须校验是否是之前设置的值,用于防止释放掉其他线程持有的锁,用于避免第一问题。二是此分布式锁必须有一个过期时间,这样就能够解决第二个问题。三是获取的锁的操作作为一个单元执行,可以使用MULTI,EXEC两个命令来达到这个目的,另外也可以通过LUA脚本来实现。这样就可以解决第三个问题。当使用MULTI,EXEC或LUA脚本能够确保其中的命令在不被干扰的情况执行,也就解决了第四个问题。如果不是自己仔细看了《Redis实战》书中相关的章节,那么我实现分布式锁也会有所疏漏。
压缩列表
何为压缩列表,数据结构LIST,HASH,ZSET在元素所占空间较小,元素个数较少时,那么将会使用压缩列表存储元素。当使用压缩元素存储元素时,能够较大的减少数据结构的占用的内存空间的大小,就以列表为例,当列表使用双向链表存储数据元素时,那么列表至少需要指向前一个节点,后一个节点以及数据节点的指针,而使用压缩列表,这些空间都节省了,元素之间直接顺序存储,只在每个元素的开始位置多了长度的设置(这个长度用于表示前一个节点的内存的长度,用于列表从后向前遍历)。由于压缩列表时顺序存放元素,当插入元素时,那么插入点之后的元素都需要移动,所以压缩列表并不合适存在大量元素的情况,这样性能反而更差。你可以通过相关属性设置元素所占空间的大小和元素个数,当这两个设置值中的任何一个突破时,压缩列表将会转换为相应的数据结构,比如LIST将使用双向链表。
全文完。