异步IO\数据库\队列\缓存

# 异步IO\数据库\队列\缓存
# 1.协程:线程上下文切换会保存到cpu的寄存器里 协程是由用户自己实现的
# 2.函数中有yield时候会变成生成器 最简单的协程
# 3.send有两个作用: 唤醒生成器并传递数据 (交互)
# 4.遇到IO操作就切换 协程处理并发的原理就是避开IO操作
#   IO操作完成切回去 IO操作系统负责操作系统的控制
# 5.greenlet 手动切换
# 6.gevent 自动切换
# 7.RabbitMQ 消息队列
# 8.redis
# 9.Mysql

##########################################################################
# 1.greenlet手动切换
# from greenlet import greenlet
#
# def test1():
# 	print(1)
# 	gr2.switch()
# 	print(2)
# 	gr2.switch()
#
# def test2():
# 	print(3)
# 	gr1.switch()
# 	print(4)
#
# gr1 = greenlet(test1)
# gr2 = greenlet(test2)
# gr1.switch()
##########################################################################
# 2.gevent自动切换
# import gevent
# def foo():
# 	print("foo1")
# 	gevent.sleep(5)
# 	print("foo2")
#
# def bar():
# 	print("bar1")
# 	gevent.sleep(2)
# 	print("bar2")
#
# def func():
# 	print("func1")
# 	gevent.sleep(0.5)
# 	print("func2")
#
# gevent.joinall([
# 	gevent.spawn(foo),
# 	gevent.spawn(bar),
# 	gevent.spawn(func)
# ])
##########################################################################
# 3.并发下的网页爬取
# from urllib import request
# import gevent, time
# from gevent import monkey
# monkey.patch_all()
#
# def f(url):
# 	print("GET: %s" % url)
# 	resp = request.urlopen(url)
# 	print(resp)
# 	data = resp.read()
# 	print("%s bytes received from %s" % (len(data), url))
#
# urls = [
# 	'https://www.python.org/',
# 	'https://www.baidu.com/',
# 	'https://www.hao123.com/?tn=93730506_hao_pg',
# ]
# time_start = time.time()
# for url in urls:
# 	f(url)
# print("串行时间: ", time.time()-time_start)
# async_time = time.time()
# gevent.joinall([
# 	gevent.spawn(f, 'https://www.python.org/'),
# 	gevent.spawn(f, 'https://www.baidu.com/'),
# 	gevent.spawn(f, 'https://www.hao123.com/?tn=93730506_hao_pg'),
# ])
# print("并行时间: ", time.time()-async_time)
##########################################################################
# 4.异步IO and 事件驱动
# 在进行解释之前,首先要说明几个概念:
# - 用户空间和内核空间:
# 	现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。
# - 进程切换:
# 	为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。
# 	从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
# 	1. 保存处理机上下文,包括程序计数器和其他寄存器。
# 	2. 更新PCB信息。
# 	3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
# 	4. 选择另一个进程执行,并更新其PCB。
# 	5. 更新内存管理的数据结构。
# 	6. 恢复处理机上下文。
# - 进程的阻塞:
# 	正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。
# - 文件描述符:
# 	文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
# 	文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
# - 缓存I/O:
# 	缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
# 	缓存 I/O 的缺点:
# 数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。
# 刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:
# 1. 等待数据准备 (Waiting for the data to be ready)
# 2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
#
# 正式因为这两个阶段,linux系统产生了下面五种网络模式的方案。
# - 阻塞 I/O(blocking IO)
# - 非阻塞 I/O(nonblocking IO)
# - I/O 多路复用( IO multiplexing)
# - 信号驱动 I/O( signal driven IO)
# - 异步 I/O(asynchronous IO)

##########################################################################
# 5.同步IO和异步IO,阻塞IO和非阻塞IO
# select poll epoll
# IO多路复用
# 理论上 1个链接4k 1G内存10w并发```
# IO多路复用 如何使用?

# blocking和non-blocking的区别
# 调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回
# synchronous IO和asynchronous IO的区别
# 在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。POSIX的定义是这样子的:
# - A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
# - An asynchronous I/O operation does not cause the requesting process to be blocked;
# 两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。
#non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。
# 而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。

# IO多路复用之select、poll、epoll详解
# select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间
# select(rlist, wlist, xlist, timeout=None)
#
# int poll (struct pollfd *fds, unsigned int nfds, int timeout);
#
# int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
# int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
# int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

#_*_coding:utf-8_*_

# import socket, logging
# import select, errno
#
# logger = logging.getLogger("network-server")
#
# def InitLog():
#     logger.setLevel(logging.DEBUG)
#
#     fh = logging.FileHandler("network-server.log")
#     fh.setLevel(logging.DEBUG)
#     ch = logging.StreamHandler()
#     ch.setLevel(logging.ERROR)
#
#     formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
#     ch.setFormatter(formatter)
#     fh.setFormatter(formatter)
#
#     logger.addHandler(fh)
#     logger.addHandler(ch)
#
#
# if __name__ == "__main__":
#     InitLog()
#
#     try:
#         # 创建 TCP socket 作为监听 socket
#         listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
#     except socket.error as  msg:
#         logger.error("create socket failed")
#
#     try:
#         # 设置 SO_REUSEADDR 选项
#         listen_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#     except socket.error as  msg:
#         logger.error("setsocketopt SO_REUSEADDR failed")
#
#     try:
#         # 进行 bind -- 此处未指定 ip 地址,即 bind 了全部网卡 ip 上
#         listen_fd.bind(('', 2003))
#     except socket.error as  msg:
#         logger.error("bind failed")
#
#     try:
#         # 设置 listen 的 backlog 数
#         listen_fd.listen(10)
#     except socket.error as  msg:
#         logger.error(msg)
#
#     try:
#         # 创建 epoll 句柄
#         epoll_fd = select.epoll()
#         # 向 epoll 句柄中注册 监听 socket 的 可读 事件
#         epoll_fd.register(listen_fd.fileno(), select.EPOLLIN)
#     except select.error as  msg:
#         logger.error(msg)
#
#     connections = {}
#     addresses = {}
#     datalist = {}
#     while True:
#         # epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待
#         epoll_list = epoll_fd.poll()
#
#         for fd, events in epoll_list:
#             # 若为监听 fd 被激活
#             if fd == listen_fd.fileno():
#                 # 进行 accept -- 获得连接上来 client 的 ip 和 port,以及 socket 句柄
#                 conn, addr = listen_fd.accept()
#                 logger.debug("accept connection from %s, %d, fd = %d" % (addr[0], addr[1], conn.fileno()))
#                 # 将连接 socket 设置为 非阻塞
#                 conn.setblocking(0)
#                 # 向 epoll 句柄中注册 连接 socket 的 可读 事件
#                 epoll_fd.register(conn.fileno(), select.EPOLLIN | select.EPOLLET)
#                 # 将 conn 和 addr 信息分别保存起来
#                 connections[conn.fileno()] = conn
#                 addresses[conn.fileno()] = addr
#             elif select.EPOLLIN & events:
#                 # 有 可读 事件激活
#                 datas = ''
#                 while True:
#                     try:
#                         # 从激活 fd 上 recv 10 字节数据
#                         data = connections[fd].recv(10)
#                         # 若当前没有接收到数据,并且之前的累计数据也没有
#                         if not data and not datas:
#                             # 从 epoll 句柄中移除该 连接 fd
#                             epoll_fd.unregister(fd)
#                             # server 侧主动关闭该 连接 fd
#                             connections[fd].close()
#                             logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1]))
#                             break
#                         else:
#                             # 将接收到的数据拼接保存在 datas 中
#                             datas += data
#                     except socket.error as  msg:
#                         # 在 非阻塞 socket 上进行 recv 需要处理 读穿 的情况
#                         # 这里实际上是利用 读穿 出 异常 的方式跳到这里进行后续处理
#                         if msg.errno == errno.EAGAIN:
#                             logger.debug("%s receive %s" % (fd, datas))
#                             # 将已接收数据保存起来
#                             datalist[fd] = datas
#                             # 更新 epoll 句柄中连接d 注册事件为 可写
#                             epoll_fd.modify(fd, select.EPOLLET | select.EPOLLOUT)
#                             break
#                         else:
#                             # 出错处理
#                             epoll_fd.unregister(fd)
#                             connections[fd].close()
#                             logger.error(msg)
#                             break
#             elif select.EPOLLHUP & events:
#                 # 有 HUP 事件激活
#                 epoll_fd.unregister(fd)
#                 connections[fd].close()
#                 logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1]))
#             elif select.EPOLLOUT & events:
#                 # 有 可写 事件激活
#                 sendLen = 0
#                 # 通过 while 循环确保将 buf 中的数据全部发送出去
#                 while True:
#                     # 将之前收到的数据发回 client -- 通过 sendLen 来控制发送位置
#                     sendLen += connections[fd].send(datalist[fd][sendLen:])
#                     # 在全部发送完毕后退出 while 循环
#                     if sendLen == len(datalist[fd]):
#                         break
#                 # 更新 epoll 句柄中连接 fd 注册事件为 可读
#                 epoll_fd.modify(fd, select.EPOLLIN | select.EPOLLET)
#             else:
#                 # 其他 epoll 事件不进行处理
#                 continue
#
# epoll socket echo server
##########################################################################
# 6.非阻塞模式下的多路复用
# select
# selector
##########################################################################
# 7.RabbitMQ
##########################################################################
# 8.Radis

##########################################################################
# 9.Mysql

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis缓存是一种基于内存的高性能键值存储数据库。它常被用作缓存服务器,可以将常用的数据缓存在内存中,以提高应用程序的响应速度。 优点: 1. 快速读取:与传统的磁盘数据库相比,Redis缓存可以更快地读取数据。 2. 高并发:Redis缓存的单线程模型能够避免并发问题,同时支持高并发访问。 3. 数据结构丰富:Redis缓存支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,方便开发人员使用。 4. 分布式:Redis缓存可以实现分布式缓存,提高了系统的扩展性和容错性。 缺点: 1. 内存限制:Redis缓存存储的数据量受限于服务器的内存大小。 2. 数据一致性:Redis缓存中的数据可能会因为故障等原因丢失,需要进行备份和恢复操作。 3. 高并发写入:当Redis缓存中的数据需要频繁更新时,可能会导致性能下降。 Redis缓存支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等。其中,字符串适用于缓存简单的值或对象,哈希表适用于缓存复杂的对象,列表适用于缓存队列等数据结构,集合适用于缓存无序的元素集合,有序集合适用于缓存有序的元素集合。 Redis缓存的分布式实现可以通过一致性哈希算法等方式来实现。一致性哈希算法可以使得数据在多个节点之间均匀分布,提高系统的性能和可靠性。 为了保证Redis缓存的可靠性和数据一致性,可以使用持久化方式来将数据写入到磁盘中,以防止数据丢失。同时,可以设置主从复制,将数据复制到多个节点,提高系统的可靠性。 Redis缓存的过期策略有两种:定时过期和惰性过期。定时过期是指设置一个过期时间,在这个时间之后数据会被自动删除;惰性过期是指在访问数据时检查它是否过期,如果过期则进行删除。可以通过设置过期时间和过期策略来控制Redis缓存中数据的有效性。 Redis缓存的持久化方式有两种:RDB和AOF。RDB将内存中的数据周期性地写入到磁盘中,适用于需要快速备份和恢复数据的场景;AOF则将Redis缓存的写操作记录到文件中,适用于需要保证数据一致性和可靠性的场景。 为了优化Redis缓存的性能,可以采用以下方法: 1. 合理使用数据结构,选择适合的数据类型和算法。 2. 设置合理的过期时间和过期策略,避免数据的过期和无效。 3. 使用分布式缓存,将数据分散在多个节点中,提高系统的性能和可靠性。 4. 使用连接池和异步IO等技术,避免因连接和IO造成的性能瓶颈。 为了保证Redis缓存数据库的一致性,可以使用缓存更新策略。当数据库中的数据发生变化时,可以通过订阅数据库更新事件的方式,将更新的数据同步到Redis缓存中,以保证数据的一致性。 为了实现Redis缓存的高可用性,可以使用主从复制和哨兵模式。主从复制可以将数据复制到多个节点,提高系统的容错性;哨兵模式则可以监控Redis缓存的状态,当主节点出现故障时,自动选择新的主节点,保证系统的高可用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值