什么时候需要进行需要原子操作?
很常见的例子, 就是利用 Redis 实现分布式锁.
实现锁需要哪些条件?
我们知道要实现锁, 就需要一个改变锁状态的方法. 这个方法能原子地对锁的状态进行检查并修改. 如果修改成功, 则意味着获得了锁. 对于硬件, 就是它提供的就是 test-and-set,compare-and-swap 等原语.
Redis 有没有提供类似的原语呢?
有的. Redis 有提供 setnx(), 它会提供这样的原子操作: 如果 key 没有值, 则将值设置进去, 如果已有值就不做处理, 提示失败.
这样就可以基于这个原语来实现锁, 简单原理就是: key 就是对应的锁, 如果 key 有值就说明锁被占用. 删除值代表释放锁. 如果插入值成功, 则代表获得锁. 再加上过期时间, 基本就可以满足分布式锁的需求了.
除了锁, 还有哪些地方需要原子操作?
假如我们在操作 Redis 数据的时候, 需要判断 Redis 中某个值是否满足条件, 只有满足条件才做这个操作
我随便举个例子, 例如: 如果 key-xxx 的值不为 0, 则加 1, 如果为 0, 则删除.
这种情况 Redis 可以处理吗?
可以, Lua 脚本. Redis 支持 Lua 脚本. 针对上面的问题, 我们只要写这样的 Lua 脚本就可以了.
- local a = Redis.call('get', 'xxx') // 调用 Redis 的 get 方法, key 为'xxx'
- if(tonumber(a)> 0) then //Redis 都是以 String 进行存储的, 需要转型
- Redis.call('incr', 'xxx') // 调用 Redis 的 incr 方法, key 为'xxx'
- return 'OK'
- else
- return 'FAIL'
为什么 Lua 脚本可以实现原子操作, 看不出来它有用锁啊?
这与 Redis 的请求处理有关. Redis 只用一个线程来处理客户端的请求. 所以在执行 lua 脚本的时候, 没有其他客户端的请求在处理. 所以在 lua 脚本中的对 Redis 数据的修改操作就是原子的.
只用一个线程处理的过来吗?
Redis 的请求处理线程, 利用 Select 和事件循环进行处理, 大概就是下面这样:
- while(1) {
- events = getEvents(); // 先利用 SELECT 拿到最近的请求
- for(e in events) // 然后逐个处理
- processEvent(e);
- }
因为 Redis 的操作的数据都在内存中. 处理起来也很快, 所以也不会出现响应时间太长的情况.
万一这个线程阻塞了怎么办?
一般情况下, 阻塞不了. 前面也说了, 客户端上来的请求都是操作内存的, 不会有其他调用 (例如文件 I/O 这样的调用). 但是, 前面也说了, 这是一般情况. 别忘了 lua,lua 脚本里面是可以写阻塞操作的. 实测发现, 如果往 Redis 中提交一个死循环的 lua 脚本, Redis 就挂了. 所以写 lua 脚本的时候要小心.
来源: https://www.cnblogs.com/longfurcat/p/11250827.html