【Redis】redis基础知识

版权声明:本文为博主原创文章,未经博主允许不得转载。如文章内容有问题,请不吝指教。 https://blog.csdn.net/hangdianfengzhizi/article/details/81950097

Redis

Redis 是一个开源的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件

  • 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
  • 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
  • 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  • 使用多路I/O复用模型(同一个线程,通过selector模型遍历多个网络连接),非阻塞IO;

应用案例

  • twitter
  • weibo
  • stackoverflow
  • github

应用场景

  • session缓存,例如购物车
  • 队列 list 的 push/pop
  • 计数器(调用访问次数,incrby原子操作)
  • TopN (sorted set)
  • 分布式锁

命令

Redis命令十分丰富,包括的命令组有Cluster、Connection、Geo、Hashes、HyperLogLog、Keys、Lists、Pub/Sub、Scripting、Server、Sets、Sorted Sets、Strings、Transactions一共14个redis命令组两百多个redis命令
值得注意的是,redis提供的命令均是原子性的

redis命令

数据类型

Redis的key是二进制的安全的字符串,例如简单的字符串或者图片。key的长度在保证可读性的前提下尽量短,减少存储消耗。命名常用冒号分割
Redis的value可以为以下几种
- String 值的长度不能超过512 MB(常用命令 get set mget mset getset incr )
- List 基于Linked Lists实现 写快,随机读慢(常用命令 lpush rpush lrange lpop rpop ltrim rtrim)应用场景评论列表(brpop blpop 可以阻塞读列表直到获取到数据,超时仍无数据则返回null)
- Set (smembers sadd spop scard)
- Sorted Set (zadd zrange zrevrange )
- Hash (hget hset …)
- Bit Array
- HyperLogLogs

持久化

redis持久化会在子线程中执行,不会阻塞命令的执行
aof 追加到命令日志中,在执行写命令时,将被执行的写命令复制到硬盘里面
rdb 每隔一定时间将数据集导出到磁盘(快照)

事务

MULTI 命令用于开启一个事务,它总是返回 OK 。 MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC命令被调用时, 所有队列中的命令才会被执行。

DISCARD , 客户端可以清空事务队列, 并放弃执行事务。

EXEC 命令负责触发并执行事务中的所有命令:
如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。

当multi指令与exec指令之间出现了错误
1)如果命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。那么exec之后会执行discard取消事务中的指令(2.6.5版本之后)。
2)如果命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。那么redis会执行所有的命令,只是出错的指令结果为失败。

使用 check-and-set 操作实现乐观锁
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。

被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败。程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。

Redis 脚本和事务
从定义上来说, Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。

分布式锁

只需具备3个特性就可以实现一个最低保障的分布式锁。

  • 安全属性(Safety property): 独享(相互排斥)。在任意一个时刻,只有一个客户端持有锁。
  • 活性A(Liveness property A): 无死锁。即便持有锁的客户端崩溃(crashed)或者网络被分裂(gets partitioned),锁仍然可以被获取。
  • 活性B(Liveness property B): 容错。 只要大部分Redis节点都活着,客户端就可以获取和释放锁.

单Redis实例实现分布式锁的正确方法
在尝试克服上述单实例设置的限制之前,让我们先讨论一下在这种简单情况下实现分布式锁的正确做法,实际上这是一种可行的方案,尽管存在竞态,结果仍然是可接受的,另外,这里讨论的单实例加锁方法也是分布式加锁算法的基础。

获取锁使用命令:

SET resource_name my_random_value NX PX 30000

这个命令仅在不存在key的时候才能被执行成功(NX选项),并且这个key有一个30秒的自动失效时间(PX属性)。这个key的值是“my_random_value”(一个随机值),这个值在所有的客户端必须是唯一的,所有同一key的获取者(竞争者)这个值都不能一样。

value的值必须是随机数主要是为了更安全的释放锁,释放锁的时候使用脚本告诉Redis:只有key存在并且存储的值和我指定的值一样才能告诉我删除成功。可以通过以下Lua脚本实现:

if redis.call(“get”,KEYS[1]) == ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end
使用这种方式释放锁可以避免删除别的客户端获取成功的锁。

Redlock算法
在Redis的分布式环境中,我们假设有N个Redis master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。之前我们已经描述了在Redis单实例下怎么安全地获取和释放锁。我们确保将在每(N)个实例上使用此方法获取和释放锁。在这个样例中,我们假设有5个Redis master节点,这是一个比较合理的设置,所以我们需要在5台机器上面或者5台虚拟机上面运行这些实例,这样保证他们不会同时都宕掉。

为了取到锁,客户端应该执行以下操作:

获取当前Unix时间,以毫秒为单位。
依次尝试从N个实例,使用相同的key和随机值获取锁。在步骤2,当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis实例。
客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点,相似原理参考paxos算法)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。

参考文献
www.redis.cn/topics/distlock.html

猜你喜欢

转载自blog.csdn.net/hangdianfengzhizi/article/details/81950097