Redis复习

文章目录

    • 分布式锁
      • 基本概念及问题
      • 超时问题
      • 可重入锁
    • Pub/Sub
      • 消息多播
      • Pub/Sub
      • 模式订阅
      • 消息结构
      • `PubSub`缺陷
    • 线程 `I/O`模型
      • 非阻塞I/O
      • 事件轮询(多路复用)
        • `select`函数
        • 指令队列

分布式锁

基本概念及问题

如果操作要修改用户状态,需要先读取修改用户状态,在内存修改,修改过之后再还回去,这个操作不是原子的

ps: 原子操作是不会被线程调度打断的操作,这种操作一旦开始,就会运行到结束,不会有context switch切换

image-20230621101554723

分布式锁指令:

setnx lock:codehole true
... do something
del lock:codehole

出现的问题1:

如果在setnxdel中间出现异常,就无法正常del

解决方案:

我们拿到锁之后给锁加上一个expire,超过时间自动释放

伪逻辑:

setnx lock:codehole true
expire lock:codehole 5
... do something
del lock:codehole

出现的问题2:

如果在setnxexpire之间执行的过程中服务器出现异常,就会导致lock无法正常释放

解决方案:

究其原因还是setnxexpire是两条指令而不是原子指令,之后的redissetsetnxexpire可以一起执行

伪逻辑:

set lock:codehole true ex 5 nx OK

超时问题

出现的问题:

thread1出现超时问题,这个时候thread2就暂时拿到了这个锁,但是紧接着thread1完成了逻辑,thread3就会在thread2执行完成前获取锁

解决方案:

  1. Redis尽量不要进行长时间任务
tag = random.nextint() # 随机数 if redis.set(key, tag, nx=True, ex=5):
do_something()
redis.delifequals(key, tag) # 假象的 delifequals 指令

2.使用Redisset设置一个value随机数,释放锁的时候需要匹配随机数是否一致,然后再del key,因为valuedel key不是原子操作,所以需要借助lua来实现这个原子操作

# delifequals
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end

可重入锁

可重入锁是指一个线程在持有锁的情况下可以再次加锁,如果一个锁可以支持一个线程多次加锁,那么这个锁就是可重入的

#- * -coding: utf - 8
import redis
import threading
locks = threading.local() locks.redis = {}
def key_for(user_id):return "account_{}".format(user_id)
def _lock(client, key):return bool(client.set(key, True, nx = True, ex = 5))
def _unlock(client, key): client.delete(key)
def lock(client, user_id): key = key_for(user_id) if key in locks.redis:locks.redis[key] += 1
return True
ok = _lock(client, key) if not ok:return False locks.redis[key] = 1
return True
def unlock(client, user_id): key = key_for(user_id) if key in locks.redis:locks.redis[key] -= 1
if locks.redis[key] <= 0: del locks.redis[key]
return True
return False
client = redis.StrictRedis()
print "lock", lock(client, "codehole") 
print "lock", lock(client, "codehole") 
print "unlock", unlock(client, "codehole") 
print "unlock", unlock(client, "codehole")

Java版本

public class RedisWithReentrantLock
{private ThreadLocal < Map > lockers = new ThreadLocal < > ();private Jedis jedis;public RedisWithReentrantLock(Jedis jedis){this.jedis = jedis;}private boolean _lock(String key){return jedis.set(key, "", "nx", "ex", 5 L) != null;}private void _unlock(String key){jedis.del(key);}private Map < String, Integer > currentLockers(){Map < String, Integer > refs = lockers.get();if(refs != null){return refs;}lockers.set(new HashMap < > ());return lockers.get();}public boolean lock(String key){Map refs = currentLockers();Integer refCnt = refs.get(key);if(refCnt != null){refs.put(key, refCnt + 1);return true;}boolean ok = this._lock(key);if(!ok){return false;}refs.put(key, 1);return true;}public boolean unlock(String key){Map refs = currentLockers();Integer refCnt = refs.get(key);if(refCnt == null){return false;}refCnt -= 1;if(refCnt > 0){refs.put(key, refCnt);}else{refs.remove(key);this._unlock(key);}return true;}public static void main(String[] args){Jedis jedis = new Jedis();RedisWithReentrantLock redis = new RedisWithReentrantLock(jedis);System.out.println(redis.lock("codehole"));System.out.println(redis.lock("codehole"));System.out.println(redis.unlock("codehole"));System.out.println(redis.unlock("codehole"));}
}

Pub/Sub

消息多播

消息多播允许producer生产一次消息,中间件将消息复制到多个消息队列,但是Redis消息队列不支持消息多播

Pub/Sub

为了支持消息多播,Redis单独使用了一个模块Pub/Sub

package mainimport ("fmt""github.com/go-redis/redis"
)const (RedisChannel = "my_channel"RedisTopic   = "my_topic"
)// 定义一个 Redis 客户端实例
var redisClient *redis.Clientfunc init() {redisClient = redis.NewClient(&redis.Options{Addr:     "localhost:6379",Password: "zyr5201314", // no password setDB:       0,            // use default DB})
}// 生产者:向指定的频道发布消息
func publish(channel string, message string) {err := redisClient.Publish(channel, message).Err()if err != nil {fmt.Println("Failed to publish message:", err)}
}// 消费者:从指定的频道订阅消息
func subscribe(channel string) {pubsub := redisClient.Subscribe(channel)defer pubsub.Close()// 读取订阅的消息for msg := range pubsub.Channel() {fmt.Printf("Received message on channel [%s]: %s\n", channel, msg.Payload)}
}func main() {// 启动两个消费者,分别订阅 "my_channel" 和 "my_topic" 频道go subscribe(RedisChannel)go subscribe(RedisTopic)// 生产者向 "my_channel" 频道发布消息publish(RedisChannel, "Hello, Redis!")// 生产者向 "my_topic" 频道发布消息publish(RedisTopic, "Hello, Redis with topic!")// 阻塞主线程,等待消息处理完成select {}
}

image-20230621142120580

需要注意的是ClientSub之后,Redis会给予反馈成功消息,因为网络延迟,所以在sub之后需要休眠一会才能拿到get\_message的消息,ClientPub之后也需要续面一会才能通过get\_message拿到发布的消息,如果没有get\_message会返回空

模式订阅

Consumer可以订阅一个或多个主题

subscribe codehole.image codehole.text codehole.blog

如下:

subscribe

## 进行多条订阅
127.0.0.1:6379> subscribe codehole.image codehole.text codehole.blog
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "codehole.image"
3) (integer) 1
1) "subscribe"
2) "codehole.text"
3) (integer) 2
1) "subscribe"
2) "codehole.blog"
3) (integer) 3

publish

127.0.0.1:6379> publish codehole.image https://www.baidu.com
(integer) 1
127.0.0.1:6379> publish codehole.text "hello,welcome to wuhan"
(integer) 1
127.0.0.1:6379> publish codehole.blog '{"content":"hello,everyone","title":"welcome"}'
(integer) 1

subscribe

# 直接订阅codehole主题下的所有消息
127.0.0.1:6379> psubscribe codehole.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "codehole.*"
3) (integer) 1
1) "pmessage"
2) "codehole.*"
3) "codehole.blog"
4) "{\"content\":\"hello,everyone\",\"title\":\"welcome\"}"

消息结构

{'pattern': None, 'type': 'subscribe', 'channel': 'codehole', 'data': 1L}
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'python comes'}
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'java comes'} 
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'golang comes'}

pattern: 那种方式订阅的,subscribe指令订阅的,这个字段就是空

type:消息的类型, message,subscribe,psubscribe,unsubscribe

channel:订阅的主题

data:消息内容,一个字符串

PubSub缺陷

Producer产出消息,Redis会找对应的Consumer,如果有3个Consumer这个时候一个Consumer挂掉,Producer会发送消息,另外两个Consumer正常接收消息,但是挂掉的Consumer脸上,这个Consumer就丢失了

线程 I/O模型

非阻塞I/O

套接字读写方法

read传递进去一个参数n,表示读入这么多字节的数据后返回,如果没读够thread会卡在哪里,读够之后read方法返回

write一般不会阻塞,除了缓冲区满了,write方法才会阻塞

image-20230621170922342

**非阻塞I/O**就是在读写的时候不阻塞,Read取决于读缓冲区的数据字节数,Write取决于写缓冲区的空闲字节数

事件轮询(多路复用)

问题:线程不知道什么时候继续读,什么时候继续写数据

解决方案:使用事件轮询api解决

image-20230621172038922

select函数

select是操作系统提供的API,提供了read_fdswrite_fds操作符,同时提供timeout,刚开始线程处于阻塞状态,如果有:

  1. 有任何事件到来,就会返回
  2. timeout时间到达也会返回
read_events, write_events = select(read_fds, write_fds, timeout) 
for event in read_events:handle_read(event.fd) 
for event in write_events:handle_write(event.fd)
handle_others() # 处理其它事情,如定时任务等

我们通过select同时处理多个操作符,这种叫做多路复用API,现在用的epoll(linux)kqueue(freebsd&macosx),因为select描述符多的时候性能会很差

服务器套接字websocket是调用accept接受Client新连接,也是通过select系统调用新事件通知的

指令队列

Redis每个Client都会关联一个指令队列,指令会队列排队处理,FIFO

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/2161.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx | 苹果电脑Mac安装和验证Nginx服务过程记录

common wx&#xff1a;CodingTechWork&#xff0c;一起学习进步。 引言 本文主要总结如何在Mac电脑上进行Nginx服务的安装&#xff0c;重点讲解使用brew命令进行安装和验证的过程及问题记录。 安装步骤 安装过程记录 查看nginx信息 首先使用命令brew info nginx进行本机ng…

Linux下Master-Master Replication Manager for MySQL 双主故障切换

简述&#xff1a; Master-Master Replication Manager for MySQL&#xff08;MMRM&#xff09;是一种用于MySQL数据库的主-主复制管理工具。它允许在多个MySQL主机之间建立双向的主-主复制关系&#xff0c;实现数据的同步和高可用性。 工作原理是通过在每个MySQL主机上配置双…

传统后端漏洞----(Web Server) 解析漏洞

笔记 前言IIS解析漏洞文件夹解析漏洞原理限制条件 ";" 分号截断漏洞原理 IIS解析漏洞检测IIS 文件夹解析漏洞检测IIS 分号截断漏洞检测 防御手段 Nginx解析漏洞Nginx 文件类型错误解析漏洞导致任意PHP代码执行原理Nginx 空字节解析漏洞导致任意文件可解析&#xff08…

【详细分析】thinkphp反序列化漏洞

文章目录 配置xdebug反序列化漏洞利用链详细分析poc1&#xff08;任意文件删除&#xff09;测试pocpoc2&#xff08;任意命令执行&#xff09;poc3&#xff08;任意命令执行&#xff09; 补充代码基础函数trait关键字应用案例优先级多trait 配置xdebug php.ini [Xdebug] zend…

Spring Boot 中的 WebMvc 是什么,原理,如何使用

Spring Boot 中的 WebMvc 是什么&#xff0c;原理&#xff0c;如何使用 介绍 在 Spring Boot 中&#xff0c;WebMvc 是非常重要的一个模块。它提供了一系列用于处理 Web 请求的组件和工具。在本文中&#xff0c;我们将介绍 Spring Boot 中的 WebMvc 是什么&#xff0c;其原理…

“配置DHCP Snooping实验:保护网络中的DHCP服务和防止欺骗攻击“

"配置DHCP Snooping实验&#xff1a;保护网络中的DHCP服务和防止欺骗攻击" 【实验目的】 部署DHCP服务器。熟悉DHCP Snooping的配置方法。验证拓扑。 【实验拓扑】 实验拓扑如图所示。 设备参数如下表所示。 设备 接口 IP地址 子网掩码 默认网关 R1 F0/0 …

vue element UI在button按钮使用 @keyup.enter不生效

如图所示&#xff0c;没效果。在按钮上绑定keyup事件&#xff0c;加上.native覆盖原有封装的keyup事件 解决办法 created () {document.onkeyup e > {if (e.keyCode 13 && e.target.baseURI.match(/login/)) {// 调用登录 验证方法this.submitForm()}}}成功解决&…

MySQL子查询

&#x1f607;作者介绍&#xff1a;一个有梦想、有理想、有目标的&#xff0c;且渴望能够学有所成的追梦人。 &#x1f386;学习格言&#xff1a;不读书的人,思想就会停止。——狄德罗 ⛪️个人主页&#xff1a;进入博主主页 &#x1f5fc;专栏系列&#xff1a;进入MySQL专栏知…

如何使用upupw搭建服务器,并映射外网访问

作为计算机行业从业人员&#xff0c;相信很多人都接触并使用过phpstudy等类似环境集成包&#xff0c;着对于upupw就比较好理解了。UPUPW绿色服务器平台是Windows下很有特色的一款免费服务器PHP套件&#xff0c;UPUPW PHP套件简化了PHP环境搭建步骤&#xff0c;一个压缩包解压到…

【MOOC 作业】第4章 网络层

不是标答也不是参考答案 仅从个人理解出发去做题 1、(20分) 考虑如图示的网络。 a. 假定网络是一个数据报网络。显示路由器 A 中的转发表&#xff0c;其中所有指向主机 H3 的流量通过接口 3 转发。 目的网络链路接口H33 b. 假定网络是一个数据报网络。你能写出路由器 A 中的…

设计模式篇(Java):单例模式

上一篇&#xff1a;设计模式篇(Java)&#xff1a;前言(UML类图、七大原则) 四、单例模式 所谓类的单例设计模式&#xff0c;就是采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其对象实例的方法(静态方法)…

【Linux系列P5】gccg++与【动静态库】的美妙邂逅

前言 大家好吖&#xff0c;欢迎来到 YY 滴 Linux系列 &#xff0c;热烈欢迎&#xff01;本章主要内容面向接触过Linux的老铁&#xff0c;主要内容含 欢迎订阅 YY 滴Linux专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 订阅专栏阅读&#xff1a;YY的《…