多处理器调度
多处理器架构多处理器与单处理器最大的区别在于对硬件缓存的使用,以及处理器之间共享数据的方式。
在单CPU系统中,存在多级硬件缓存,一般会让程序执行更快。
假设一个程序需要从内存中加载指令并读取一个值,在它第一次读取时,需要从内存中读取,之后处理器判定在短期内很可能会再次使用这个数据,会将其放入缓存中,下一次再请求时,直接从缓存中取即可。
在多CPU系统中,缓存则会变得复杂。
假如有两个CPU,一个内存。现在有一个运行在CPU1上的程序,要去地址A读值,但是CPU1里面没有该数据的缓存,所以需要去内存中读取,读取完成后,程序要修改地址A上的值,假设改为D,它会先写入缓存中,然后再去写入内存。但是写入内存时发生了中断,中断完后此程序交由CPU2执行,但是CPU2中没有数据的缓存,就需要从内存中读,这时候就会读到旧得数据,而不是修改后得D。
硬件提供了这个问题得解决方案:通过监控内存得访问,硬件可以保证获得正确数据,并确保共享内存的唯一性。
单队列调度这种调度方式就是采用一个队列来存储任务,然后有多个cpu从中获取任务,然后执行。但是这里需要用加锁的方式来保证调度的正确性,而加锁又会带来性能 ...
调度:多级反馈队列
基本规则MLFQ有许多独立的队列,每个队列都有优先级,任何时刻,一个任务只能存在一个队列当中,而MLFQ总是执行优先级最高的队列中的任务。在同一个队列中的任务,采用轮转调度。而MLFQ会观察任务的行为,然后调整他们的优先级。
尝试1:改变优先级工作进入系统时,放在优先级最高的队列当中,工作用完整个时间片后,降低其优先级,如果时间片没用完,主动放弃CPU,则优先级不进行变化。
按照这种方法,如果有一个很长的cpu密集型任务要执行,那么它会被慢慢降级到最低的优先级。而此时来了一个短任务,会被放在优先级最高的队列中执行,它大概率会在降到最低优先级之前处理完,这种情况下MLFQ近似于短任务优先。
存在的问题这样设计,如果一个任务一直不把CPU时间片用完,那么他就一直可以处于优先级最高的队列当中,那么会导致一些用完时间片而导致降级的任务永远无法处理。
而且会有程序恶意放弃CPU资源而一直占用处理器,比如在时间片用完之间,执行一段I/O操作,主动放弃CPU。
尝试2:提升优先级一个简单的设计,经过一段时间S后,就把队列中的所有任务全部放在优先级最高的队列当中。
但是这个S的值设置不合适也 ...
MySQL的幻读
这篇文章将介绍MySQL幻读相关的内容。
假设数据库中有以下表:
id
c
d
0
0
0
5
5
5
10
10
10
15
15
15
20
20
20
25
25
25
其中id是主键,c上建立有普通索引。
以下讨论场景除非必要说明,否则都是在可重复读的隔离级别下。
什么是幻读现在考虑以下场景:
其中,select * from table for update表示当前读,并且加上写锁。
幻读就是指在同一个事务中执行相同的sql,后一次查询看到了前一次没看到的数据。就比如Q3比Q2多了一条数据。
Q2看到Q1并不被称为幻读,幻读仅仅指新插入的数据。
在可重复读的隔离级别下,普通查询是看不到其他插入的数据,只有当前读(要能读到所有已经提交的记录的最新值)才有可能出现幻读。
幻读存在的问题1、语义的破坏session A已经声明了写锁,即要锁住d = 5的行,不允许其他事务进行读写,而后续却插入了一条d = 5的数据。
考虑另一个更明显的场景:
sessionB将id = 0的这一行的d改成了5,然后又把c改成 ...
DNS域名解析
DNS是一个由分层的DNS服务器实现的分布式数据库,它主要记录了域名与IP地址的对应关系。
我们平时访问网站,都是输入域名,而不是IP地址,而域名被解析为对应IP地址这一步,就是由DNS实现的。
它还提供了一些其他的服务,比如说将一个域名映射到多个IP地址上,用于减轻单个服务器的压力。
DNS架构它是一个分布式的数据库,且分多层次。
不采用单点是因为如果单点故障,那么整个因特网就不能用,而且单点无法承载那么大的请求量,存储那么多的数据。如果采用单点,还会因为地理位置原因导致很大的时延。
分层DNS分为根域名服务器,顶级域名服务器,权威域名服务器和本地域名服务器。
一个用户在浏览器中输入一个域名,解析的过程大致如下(迭代查询):
1、向本地域名服务器发送请求。
2、本地域名服务器向根域名服务器发送请求,根域名服务器返回顶级域名服务器的地址。
3、本地再向顶级域名服务器发起请求,顶级域名服务器返回权威域名服务器的地址,到这一步就可以获取到IP。
4、本地域名服务器将获取到的ip返回给用户浏览器。
递归查询:
DNS缓存某一个DNS服务器接受一个DNS回答时,它可以将映射存储在本地的 ...
进程调度:介绍
本文主要介绍一些基础的调度策略。
调度指标以下算法的性能指标只考虑周转时间。
周转时间 = 完成时间 - 到达时间
先进先出(FIFO)这个算法的思路很简单,先来的先执行,并且易于实现。就好比有三个任务A,B,C,他们到来的顺序是B,A,C,那么就按照他们到来的顺序进行执行。
存在的问题:如果先来的任务执行了很长时间,会导致后边的任务等待很久才可以执行,这样会导致系统的周转时间变的很长。
最短任务优先(SJF)该算法会先运行短任务,然后运行次短任务,依次下去。如果任务同时到达,该算法较FIFO算法可以很好的解决平均周转时间长的问题。
但是如果任务不同时到达,那么执行时间长的任务比短时任务先到达,还是会导致短任务需要等待。
最短完成时间优先(STCF)上面两种算法都是非抢占式的。
而最短完成时间优先可以理解为一种抢占式的最短任务优先算法。
该算法下,没当有任务进入系统,会判断当前正在执行任务的剩余时间和新任务的时间,哪个短就执行哪个。
新的性能指标从现在开始,性能不仅考虑周转时间,还要考虑响应时间。
响应时间 = 首次运行 - 到达时间。
轮转在加入新的性能指标后,上 ...
机制:受限直接执行
当我们采用时分共享或者空分共享虚化CPU时,也要考虑它带来的问题,第一个是性能问题,频繁的进行上下文切换必然会导致性能下降。第二个是控制权,如何有效的运行进程,同时保留对CPU的控制。
关键问题:如何高效、可控的进行虚化。
受限直接执行1、直接执行直接执行比较好理解,即直接在CPU资源上运行程序即可。
当程序需要运行时,直接将代码加载到内存中,然后找到入口点运行用户代码。
但是如果没有限制,那么会产生问题:如何保证程序不做我们不想让它做的事情,另一方面是如何停止一个进程让另一个进程运行。
2、受限制的操作关键问题:如何执行受限制的操作
一个进程要能够执行I/O和其他一些受限制的操作,但又不能让进程控制系统。
针对这个问题,引入了一个新的处理器模式:用户模式。在用户模式下运行的代码会收到限制,比如不能发出I/O请求。
与之对应的是内核模式。该模式代码可以做任何操作。
目前硬件提供了用户程序执行系统调用的功能,即用户代码可以通过系统调用执行如创建和销毁进程,与其他进程通信等。
要执行系统调用,程序必须执行特殊的陷阱(trap)指令。该指令跳入内核并将特权级别提升到内核模 ...
抽象:进程
进程就是运行中的程序。
一台电脑往往只有少数CPU,但是用户想要运行许多个应用,这里的关键问题:如何提供有许多CPU的假象。
操作系统通过虚拟化CPU来提供这假象,让一个进程只运行一个时间片,然后切换到其他进程。每个进程都运行一会儿,给用户的感觉就好像每个进程都在运行一样。
时分共享和空分共享时分共享是指让每个进程运行一段时间后让出处理器,让其他进程再运行一段时间,来达到虚拟化CPU的目的。
空分共享是指CPU资源在空间上被划分给希望使用它的进程。但这里需要注意,一旦分给一个进程,某些情况下如果它不释放空间,就不会再把这块空间分给其他进程。
进程创建程序如何转变为进程?
操作系统运行程序必须先把代码和静态数据加载到内存中,加载到进程的地址空间中。如下图所示:
早期操作系统在程序运行前加载完成,而现在的是懒加载,即在程序执行期间需要的代码或者数据,才会加载到内存。
通过将代码和静态数据加载到内存中,然后创建和初始化栈以及执行与I/O设置相关的工作,就只剩下最后一个任务,启动程序。
进程状态进程有以下三种状态:
1、运行。说明进程正在处理器上运行,占用着资源。意味着正在执行指 ...
MySQL索引失效的情况
条件字段函数操作假如我们现在有如下需求,我们要统计一张表2021年和2022年8月份的记录,那么SQL语句可以这么写:
1select column_1, column_2 from table_1 where month(record_time) = 7;
虽然数据库表在record_time上加了索引(结构图如下),但是这个sql还是会执行的特别慢。
因为在用month函数对record_time做计算后,得到的数字是记录的月份,而索引的值却并不是一个单独的数字,所以会导致它走全表扫描。
也就是说,对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。
12345// 无法使用id这个索引select * from table_1 where id + 1 = 1000 // 可以使用select * from table_1 where id = 1000 - 1
隐式类型转换现在有如下例子:
1select * from tradelog where id = 110717;
其中,id是一个varchar类型的值,虽然它上面有索引,但还是 ...
order by是怎么工作的
假设我们现在有如下表,其中id是主键,city上有普通索引。
id
city
name
age
1
杭州
张三
22
2
杭州
李四
23
3
郑州
王五
24
……
……
……
……
现在需要查出city为杭州的人的性命和年龄,并按照年龄排序的前100个人。那么sql语句如下:
1select city, name, age from t where city='杭州' order by name limit 100;
全字段排序先初始化sort_buffer,这是一块用于排序的内存,然后确定放入city,name,age三个字段。
之后,会在city索引树中找到值为杭州的记录,然后去主键索引取这一行的值存入sort_buffer,直到city不为杭州。
然后在sort_buffer中使用快排根据name字段进行排序,对排序结果取前100行返回给客户端。
其中,如果取出的数据量太大,无法存入sort_buffer,则需要利用磁盘临时文件辅助排序。
rowid排序上述情况可以把所有数据都放入sort_buffer中进行排序,但是如果 ...
Redis数据删除
有时候,在删除了Redis中部分数据后,会发现Redis仍然占用了很多内存,这是因为当数据删除后,Redis 释放的内存空间会由内存分配器管理,并不会立即返回给操作系统。所以,操作系统仍然会记录着给 Redis 分配了大量内存。
但是这样会导致一个问题,这些空间可能都是碎片化的,Redis无法拿来存取数据,还占用了大量的内存空间。
什么是内存碎片一个较为简单的解释,就是虽然存在空间,但是这些空间却由于零散的分布在内存的各个地方,导致无法使用。
形成原因1、内因:内存分配器的分配策略
内存分配器一般是按固定大小来分配内存,而不是完全按照应用程序申请的内存空间大小给程序分配。比如说现在Redis需要申请20字节的空间,但是分配器可能会分配32字节,此时如果还要写入10字节的数据,就不需要在此分配空间。但是这会导致2字节的空间很难被在此利用。
2、外因:键值对大小不一样和删改操作
Redis用来存储不同的键值对,这样就需要申请不同的空间,这一点与内因相同。而修改和删除操作,就会导致空间的扩容和释放,这就会导致新的内存分配,另一方面,删除的键值对就不再需要内存空间了,此时,就会把空间释放出来, ...