一, Redis 数据类型
1.string
string 是 Redis 的最基本数据类型, 一个 key 对应一个 value, 每个 value 最大可存储 512M.string 一半用来存图片或者序列化的数据.
2.hash
相当于一个 string 类型的映射表. 特别适合用来存储对象. 例如可以存储用户信息, 用户 ID 作为 hash 类型里的每一个 key.
案例: 我们这边需要对接微信粉丝的数据到我们自己的平台上, 但微信提供的接口只支持单天查询, 那么如果我们想要查看最近一个月微信粉丝的状况, 就需要循环 30 次调用微信的接口. 一个月勉强还可以接受, 那么如果想要查半年, 甚至一年呢? 那么我们的接口里就需要循环 365 次调微信的接口, 这就会使我们的接口变得非常慢, 甚至超时. 还有这些数据, 比如单天新增粉丝数, 是不会变得, 而且每天都有一个数据, 这样就特别适合存在 Redis 的 hash 类型里, 以日期 (2018-10-10) 作为 hash 的 key.
3.list
list 类型是简单的字符串列表, 每个列表可以存储 232 - 1 个值. 可以从头部或者尾部顺序插入数据. list 类型可以用来做电商里的秒杀营销系统或关注列表.
4.set
set 是 string 类型的无序集合. 该集合是通过哈希实现的, 添加, 删除的复杂度都是 O(1), 所以查找非常快.
案例: 我们这边是以手机号为唯一标示符, 防止重复用户注册, 会判断该手机号有没有注册过, 那么如果用 set 类型存储注册过的用户手机号, 就会很快判断出该用户是否注册过, 而不用去查数据库了.
5.zset
和 set 一样, 但 zset 多了一个 score 来让 set 变得有序, 且不允许有重复的成员.
案例: 我们这边有一个账户记录需要按记录时间排序, 那么就可以将时间戳当作 score 存储 zset 中.
二, Redis 分布式锁
网上很多 Redis 分布式锁的实现方式不能说错误的, 但至少不够严谨, 在某些极端情况下是会出问题的. 一旦出现问题, 还是挺麻烦的事情, 所以我们要知道 Redis 分布式锁的正确姿势.
其实很简单, 利用 Redis 的原子性. 关于原子性, 官方的一段描述为:
大概意思就是 Redis 执行 lua 脚本的时候, 会被当成一条命令执行, 在此期间, 不会执行其他命令, 所以 lua 脚本尽量是快脚本而不是慢脚本.
所以, 正确的姿势是:
- public function getDistributeLock($Redis, string $key, int $userId, int $expire)
- {
- $luaScript = <<<LUA
- if (Redis.call('exists', KEYS[1]) == 0) and Redis.call('setex', KEYS[1], ARGV[1], ARGV[2])
- then
- return 1
- else
- return 0
- end
- LUA;
- return $Redis->eval($luaScript, 1, $key, $expire, $userId)> 0 ? true : false;
- }
这里我们把一段 lua 脚本放到 Redis 的 eval 方法里执行, 这样就可以保证这一段命令的原子性.
那么如果不用 lua 脚本, 姿势应该是这样的:
- public function wrongGetDistributeLock($Redis, string $key, int $userId, int $expire)
- {
- // 若锁不存在, 则加锁
- if(!$Redis->exists($key) && ($Redis->setex($key, $expire, $userId) == 'OK')) {
- return true;
- }
- return false;
- }
前面提到过, 这种姿势在某些情况下会出问题: 如果同时好几个客户端同时请求, 同时通过了上面 if 条件的第一层, 那么这时候就会出现多个同时拿到锁, 并且前面人的锁会被覆盖.
然后, 正确的解锁姿势是:
- public function releaseDistributeLock($Redis, string $key, int $userId)
- {
- $luaScript = <<<LUA
- if Redis.call('get', KEYS[1]) == ARGV[1]
- then
- return Redis.call('del', KEYS[1])
- else
- return 0
- end
- LUA;
- $Redis->eval($luaScript, 1, $key, $userId);
- }
同样需要使用 lua 脚本来达到原子性. 那么如果不使用 lua 脚本的姿势是:
- public function wrongReleaseLock($Redis, string $key, int $userId)
- {
- if($userId == $Redis->get($key)) {
- $Redis->del($key);
- }
- }
会有这样一种情况: A 请求通过 if 语句后, 这时候这个 Redis 的 key 刚好过期了, 然后 B 客户端加锁成功, 这时候 A 请求就会把客户端 B 刚加的锁给解除了.
虽然我上面提到的两种情况都是很极端, 很少出现的. 但如果可以用很简单的方法避免掉, so why not?
上面提到的 Redis 分布式锁, 满足了三个特性:
互斥性. 同时只能又一个客户端拥有锁
不会发生死锁.
加锁和解锁的必须是同一个客户端.
童鞋们, 有什么疑问, 可以在地下留言哦.
来源: https://www.cnblogs.com/johnson108178/p/9819113.html