En savoir plus sur les documents d'entretien de l'entreprise avant l'entretien, qui seront très utiles pour les entretiens ultérieurs. Aujourd'hui, je vais partager avec vous 15 vraies questions d'entretien sur le serveur Shopee (avec analyse des réponses). Venez voir quel est votre niveau, j'espère que cela pourra vous aider !
Étant donné le nœud principal de la liste chaînée, veuillez le trier par ordre croissant et renvoyer la liste chaînée triée.
Exemple 1 :
输入:head = [4,2,1,3] 输出:[1,2,3,4]
Exemple 2 :
输入:head = [-1,5,3,4,0] 输出:[-1,0,3,4,5]
Ce problème peut être résolu en utilisant des pointeurs doubles + un algorithme de tri par fusion, principalement les quatre étapes suivantes
1. Méthode de pointeur rapide et lent, parcourez le liste chaînée pour trouver le nœud du milieu
2. Coupez la liste chaînée au nœud du milieu
3. Organisez les listes sous-liées gauche et droite respectivement en utilisant le tri par fusion
4. Fusionnez les listes sous-liées
Le le code complet est le suivant :
class Solution { public ListNode sortList(ListNode head) { //如果链表为空,或者只有一个节点,直接返回即可,不用排序 if (head == null || head.next == null) return head; //快慢指针移动,以寻找到中间节点 ListNode slow = head; ListNode fast = head; while(fast.next!=null && fast.next.next !=null){ fast = fast.next.next; slow = slow.next; } //找到中间节点,slow节点的next指针,指向mid ListNode mid = slow.next; //切断链表 slow.next = null; //排序左子链表 ListNode left = sortList(head); //排序左子链表 ListNode right = sortList(mid); //合并链表 return merge(left,right); } public ListNode merge(ListNode left, ListNode right) { ListNode head = new ListNode(0); ListNode temp = head; while (left != null && right != null) { if (left.val <= right.val) { temp.next = left; left = left.next; } else { temp.next = right; right = right.next; } temp = temp.next; } if (left != null) { temp.next = left; } else if (right != null) { temp.next = right; } return head.next; } }
Premier examen des concepts associés :
Texte clair : fait référence aux informations/données qui n'ont pas été cryptées.
Texte chiffré : une fois le texte brut chiffré par l'algorithme de chiffrement, il deviendra un texte chiffré pour garantir la sécurité des données.
Clé : est un paramètre entré dans l'algorithme qui convertit le texte brut en texte chiffré ou le texte chiffré en texte brut. Les clés sont divisées en clés symétriques et clés asymétriques.
Cryptage : processus de transformation du texte brut en texte chiffré.
Déchiffrement : processus de réduction du texte chiffré en texte brut.
Algorithme de cryptage symétrique : un algorithme de cryptage qui utilise la même clé pour le cryptage et le déchiffrement. Les algorithmes de chiffrement symétriques courants incluent AES, 3DES, DES, RC5, RC6, etc.
Algorithme de chiffrement asymétrique : L'algorithme de chiffrement asymétrique nécessite deux clés (clé publique et clé privée). Les clés publiques et les clés privées existent par paires. Si la clé publique est utilisée pour chiffrer des données, seule la clé privée correspondante peut les déchiffrer. Les principaux algorithmes de chiffrement asymétrique sont : RSA, Elgamal, DSA, D-H, ECC.
Tout d'abord, la connexion TCP est basée sur une poignée de main à trois voies, tandis que la déconnexion est basée sur quatre vagues. Assurer une connexion et une déconnexion fiables.
Deuxièmement, la fiabilité de TCP se reflète également dans son état ; TCP enregistrera quelles données sont envoyées, quelles données sont acceptées et lesquelles ne sont pas acceptées, et garantira que les paquets de données arrivent dans l'ordre et qu'il n'y a pas de paquets de données. erreurs dans la transmission des données.
Encore une fois, la fiabilité de TCP se reflète également dans sa contrôlabilité. Il dispose de mécanismes tels que la vérification des messages, la réponse ACK, la retransmission hors délai (expéditeur), la retransmission de données hors séquence (récepteur), la suppression des données en double, le contrôle de flux (fenêtre glissante) et le contrôle de congestion.
4.1 Modèle IO bloquant
Supposons que le processus d'application initie un appel IO, mais si les données du noyau ne sont pas encore prêtes, l'application le traite. a bloqué et attendu que les données du noyau soient prêtes et copiées du noyau vers l'espace utilisateur avant de renvoyer une invite de réussite. Cette opération IO est appelée blocage IO.
4.2 Modèle IO non bloquant
Si les données du noyau ne sont pas encore prêtes, vous pouvez d'abord renvoyer un message d'erreur au processus utilisateur, afin qu'il n'ait pas besoin d'attendre, mais demande à nouveau par le biais de sondages. Il s'agit d'IO non bloquantes. L'organigramme est le suivant :
4.3 Modèle de multiplexage IO
sélection du multiplexage IO
Le processus d'application peut surveiller plusieurs appareils en même temps en appelant. la fonction de sélection fd, dans le fd surveillé par la fonction de sélection, tant qu'un état de données est prêt, la fonction de sélection reviendra à l'état lisible, puis le processus d'application lancera une demande de réception pour lire les données.
select présente plusieurs inconvénients :
Le nombre maximum de connexions est limité, généralement 1024 sur les systèmes Linux.
Après le retour de la fonction select, elle parcourt le fdset pour trouver le descripteur prêt fd.
IO multiplexage epoll
Afin de résoudre les problèmes de sélection, le modèle de multiplexage epoll est né. Il est implémenté à l'aide d'un lecteur d'événements. L'organigramme est le suivant :
.epoll enregistre d'abord un fd (descripteur de fichier) via epoll_ctl(). Une fois qu'un certain fd est prêt, le noyau utilisera un mécanisme de rappel pour activer rapidement le fd. Lorsque le processus appelle epoll_wait(), il en sera informé. L'opération délicate consistant à parcourir les descripteurs de fichiers est supprimée ici et un mécanisme d'écoute des rappels d'événements est utilisé. C'est le point culminant d'epoll.
4.4 Modèle d'IO basé sur le signal
L'IO piloté par signal n'utilise plus de requête active pour confirmer si les données sont prêtes, mais envoie un signal au noyau (crée un signal SIGIO lors de l'appel de sigaction), Le processus utilisateur de l'application peut alors faire autre chose sans blocage. Lorsque les données du noyau sont prêtes, le processus d'application est informé via le signal SIGIO que les données sont prêtes à être lisibles. Une fois que le processus utilisateur de l'application a reçu le signal, il appelle immédiatement recvfrom pour lire les données.
4.5 Modèle IO IO asynchrone (AIO)
AIO réalise le non-blocage de l'ensemble du processus IO, c'est-à-dire qu'une fois que le processus d'application a émis un appel système, il revient immédiatement, mais ce qui est renvoyé immédiatement n'est pas le résultat du traitement, mais signifie que la soumission a réussi. Lorsque les données du noyau sont prêtes, copiez les données dans le tampon du processus utilisateur et envoyez un signal pour informer le processus utilisateur que l'opération d'E/S est terminée.
Le processus est le suivant :
Le diagramme de flux de travail Hystrix est le suivant :
1. , il transmettra au nom de l'une de vos tâches de demande de dépendance les paramètres requis pour demander des dépendances au constructeur.
2. Exécuter des commandes
Il existe quatre façons d'exécuter les commandes Hystrix. Ce sont :
Si le cache Hystrix est activé, il sera jugé s'il existe un cache pour la même exécution de commande avant. la tâche est exécutée. Si tel est le cas, l'Observable contenant la réponse mise en cache sera renvoyé directement ; s'il n'y a pas de résultat mis en cache mais que la mise en cache est activée, le résultat de l'exécution sera mis en cache pour une utilisation ultérieure.
4. Vérifiez si le disjoncteur est ouvert. Un disjoncteur (disjoncteur) est similaire à un fusible. Un fusible grillera pour protéger le circuit lorsqu'un danger survient, tandis qu'un disjoncteur peut déclencher un court-circuit lorsqu'il se produit. il atteint le seuil que nous avons fixé (par exemple, le taux d'échec des requêtes atteint 50 %) et refuse d'exécuter toute requête.
Si le looper est ouvert, Hystrix n'exécutera pas la commande et entrera directement dans la logique de traitement de repli.
5. Vérifiez l'état du pool de threads/sémaphore/file d'attente. Les méthodes d'isolation Hystrix incluent l'isolation du pool de threads et l'isolation du sémaphore. Lors de l'utilisation du pool de threads Hystrix, Hystrix alloue par défaut 10 threads à chaque service dépendant. Lorsque les 10 threads sont occupés, l'exécution de la commande sera refusée et passera immédiatement à l'exécution de la logique de secours.
6. Effectuez des tâches spécifiques. Utilisez HystrixObservableCommand.construct() ou HystrixCommand.run() pour exécuter les tâches réelles de l'utilisateur.
7. Calculez l'état de santé de la boucle. Chaque fois que vous commencez à exécuter une commande, terminez l'exécution d'une commande ou qu'une exception se produit, l'état d'exécution sera enregistré, tel que : succès, échec, rejet, délai d'attente et autres indicateurs. Ces données seront traitées régulièrement, puis en fonction des conditions définies pour déterminer s'il convient d'ouvrir le boucleur.
8. Exécutez la logique de repli lorsque la commande échoue. Exécutez la logique de repli spécifiée par l'utilisateur lorsque la commande échoue. Dans la figure ci-dessus, la coupure de circuit, le rejet du pool de threads, le rejet du sémaphore, l'exécution de l'exécution et le délai d'expiration de l'exécution entreront tous dans le traitement de secours.
9. Renvoie le résultat de l'exécution. Le résultat de l'objet original sera renvoyé sous forme d'Observable. Avant d'être renvoyé à l'utilisateur, certains traitements seront effectués en fonction de la méthode appelante.
6. Traitement des scénarios de retardHTTPS = HTTP + SSL/TLS, c'est-à-dire utiliser SSL/TLS pour crypter et déchiffrer les données, et Http pour la transmission.
SSL, ou Secure Sockets Layer, est un protocole de sécurité qui assure la sécurité et l'intégrité des données pour les communications réseau.
TLS, Transport Layer Security (Secure Transport Layer Protocol), c'est la version ultérieure de SSL 3.0.
Processus de requête http
1. L'utilisateur saisit une URL https dans le navigateur, puis se connecte au port 443 du serveur.
2. Le serveur doit disposer d'un ensemble de certificats numériques. Vous pouvez les créer vous-même ou en faire la demande à l'organisation. La différence est que le certificat émis par vous-même doit être vérifié par le client. Cet ensemble de certificats est en réalité une paire de clés publiques et privées.
3. Le serveur envoie son propre certificat numérique (contenant la clé publique) au client.
4. Une fois que le client a reçu le certificat numérique du serveur, il le vérifiera. En cas d'échec, une boîte d'avertissement apparaîtra. Si le certificat est OK, une clé est générée (chiffrement symétrique) et chiffrée avec la clé publique du certificat.
5. Le client lancera la deuxième requête HTTP en HTTPS et enverra la clé client cryptée au serveur.
6. Une fois que le serveur aura reçu le texte chiffré du client, il utilisera sa propre clé privée pour le déchiffrer de manière asymétrique, il obtiendra la clé client, puis utilisera la clé client pour chiffrer symétriquement les données renvoyées. de cette façon, les données deviennent du texte chiffré.
7. Le serveur renvoie le texte chiffré au client.
8. Le client reçoit le texte chiffré renvoyé par le serveur, utilise sa propre clé (clé client) pour le déchiffrer symétriquement et obtient les données renvoyées par le serveur.
8.1 Les quatre niveaux d'isolement de la base de données
Afin de résoudre les problèmes de lecture sale, non répétable lecture, lecture fantôme, etc. qui existent dans les transactions simultanées Question, l'oncle de la base de données a conçu quatre niveaux d'isolement. Ils sont lus non validés, lus validés, répétables et sérialisables.
Niveau d'isolement de lecture non validé : cela limite seulement le fait que deux données ne peuvent pas être modifiées en même temps. Cependant, lorsque les données sont modifiées, même si la transaction n'est pas soumise, elles peuvent être lues par d'autres transactions. l'isolation des transactions est sale. Problèmes de lecture, de lecture répétée et de lecture fantôme ;
Niveau d'isolement de lecture validé : la transaction actuelle ne peut lire que les données soumises par d'autres transactions, donc ce niveau d'isolation de transaction résout le problème de lecture sale, mais il Il existe toujours des problèmes de lecture répétée et de lecture fantôme ;
Lecture répétable : elle restreint la modification lors de la lecture des données, elle résout donc le problème de la lecture répétée, mais lors de la lecture des données de plage, des données peuvent être insérées, il y aura donc également une lecture fantôme. problèmes ;
Sérialisation : le niveau d'isolement le plus élevé pour les transactions. À ce niveau, toutes les transactions sont exécutées en série et séquentiellement. Il peut éviter tous les problèmes de concurrence liés aux lectures sales, aux lectures non répétables et aux lectures fantômes. Cependant, sous ce niveau d’isolement des transactions, l’exécution des transactions est très gourmande en performances.
Quels sont les problèmes de concurrence entre les quatre niveaux d'isolement ?
Niveau d'isolement | lecture sale | lecture non répétable | lecture fantôme |
---|---|---|---|
lecture non validée | √ | √ | √ |
lire Soumettre | × | √ | √ |
Lecture répétable | × | × | √ |
Sérialisation | × | × | × |
max_limit_id
indique la valeur d'identifiant qui doit être attribuée à la prochaine transaction dans le système lorsqu'une vue en lecture est générée.min_limit_id | représente le plus petit identifiant de transaction parmi les transactions de lecture et d'écriture actives dans le système actuel lors de la génération de la vue de lecture, c'est-à-dire la plus petite valeur en m_ids. |
---|---|
Créez l'ID de transaction de la vue de lecture actuelle |
Variable | value |
---|---|
m_ids | 100, 101 |
max_limit_id | 102 |
min_ limit_id | 100 |
creator_trx_id | 100 |
然后回到版本链:开始从版本链中挑选可见的记录:
由图可以看出,最新版本的列name的内容是孙权,该版本的trx_id值为100。开始执行read view可见性规则校验:
min_limit_id(100)=<trx_id(100)<102; creator_trx_id = trx_id =100;
由此可得,trx_id=100的这个记录,当前事务是可见的。所以查到是name为孙权的记录。
4、事务B进行修改操作,把名字改为曹操。把原数据拷贝到undo log,然后对数据进行修改,标记事务ID和上一个数据版本在undo log的地址。
5、事务B提交事务
6、事务A再次执行查询操作,因为是RR(可重复读)隔离级别,因此会复用老的Read View副本,Read View对应的值如下
Variable | value |
---|---|
m_ids | 100, 101 |
max_limit_id | 102 |
min_ limit_id | 100 |
creator_trx_id | 100 |
然后再次回到版本链:从版本链中挑选可见的记录:
从图可得,最新版本的列name的内容是曹操,该版本的trx_id值为101。开始执行read view可见性规则校验:
min_limit_id(100)=<trx_id(101)<max_limit_id(102); 因为m_ids{100,101}包含trx_id(101), 并且creator_trx_id (100) 不等于trx_id(101)
所以,trx_id=101这个记录,对于当前事务是不可见的。这时候呢,版本链roll_pointer跳到下一个版本,trx_id=100这个记录,再次校验是否可见:
min_limit_id(100)=<trx_id(100)< max_limit_id(102); 因为m_ids{100,101}包含trx_id(100), 并且creator_trx_id (100) 等于trx_id(100)
所以,trx_id=100这个记录,对于当前事务是可见的,所以两次查询结果,都是name=孙权的那个记录。即在可重复读(RR)隔离级别下,复用老的Read View副本,解决了不可重复读的问题。
1. 查询条件包含or,可能导致索引失效
2. 如何字段类型是字符串,where时一定用引号括起来,否则索引失效
3. like通配符可能导致索引失效。
4. 联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。
5. 在索引列上使用mysql的内置函数,索引失效。
6. 对索引列运算(如,+、-、*、/),索引失效。
7. 索引字段上使用(!= 或者 ,not in)时,可能会导致索引失效。
8. 索引字段上使用is null, is not null,可能导致索引失效。
9. 左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效。
10. mysql估计使用全表扫描要比使用索引快,则不使用索引。
虚拟内存,是虚拟出来的内存,它的核心思想就是确保每个程序拥有自己的地址空间,地址空间被分成多个块,每一块都有连续的地址空间。同时物理空间也分成多个块,块大小和虚拟地址空间的块大小一致,操作系统会自动将虚拟地址空间映射到物理地址空间,程序只需关注虚拟内存,请求的也是虚拟内存,真正使用却是物理内存。
现代操作系统使用虚拟内存,即虚拟地址取代物理地址,使用虚拟内存可以有2个好处:
虚拟内存空间可以远远大于物理内存空间
多个虚拟内存可以指向同一个物理地址
零拷贝实现思想,就利用了虚拟内存这个点:多个虚拟内存可以指向同一个物理地址,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址,这样的话,就可以减少IO的数据拷贝次数啦,示意图如下:
排行版的实现,一般使用redis的zset数据类型。
使用格式如下:
zadd key score member [score member ...],zrank key member
层内部编码:ziplist(压缩列表)、skiplist(跳跃表)
使用场景如排行榜,社交需求(如用户点赞)
实现demo如下:
分布式锁,是控制分布式系统不同进程共同访问共享资源的一种锁的实现。秒杀下单、抢红包等等业务场景,都需要用到分布式锁,我们项目中经常使用Redis作为分布式锁。
选了Redis分布式锁的几种实现方法,大家来讨论下,看有没有啥问题哈。
命令setnx + expire分开写
setnx + value值是过期时间
set的扩展命令(set ex px nx)
set ex px nx + 校验唯一随机值,再删除
Redisson
12.1 命令setnx + expire分开写
if(jedis.setnx(key,lock_value) == 1){ //加锁 expire(key,100); //设置过期时间 try { do something //业务请求 }catch(){ } finally { jedis.del(key); //释放锁 } }
如果执行完setnx加锁,正要执行expire设置过期时间时,进程crash掉或者要重启维护了,那这个锁就“长生不老”了,别的线程永远获取不到锁啦,所以分布式锁不能这么实现。
12.2 setnx + value值是过期时间
long expires = System.currentTimeMillis() + expireTime; //系统时间+设置的过期时间 String expiresStr = String.valueOf(expires); // 如果当前锁不存在,返回加锁成功 if (jedis.setnx(key, expiresStr) == 1) { return true; } // 如果锁已经存在,获取锁的过期时间 String currentValueStr = jedis.get(key); // 如果获取到的过期时间,小于系统当前时间,表示已经过期 if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { // 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间(不了解redis的getSet命令的小伙伴,可以去官网看下哈) String oldValueStr = jedis.getSet(key_resource_id, expiresStr); if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { // 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才可以加锁 return true; } } //其他情况,均返回加锁失败 return false; }
笔者看过有开发小伙伴就是这么实现分布式锁的,但是这种方案也有这些缺点:
过期时间是客户端自己生成的,分布式环境下,每个客户端的时间必须同步。
没有保存持有者的唯一标识,可能被别的客户端释放/解锁。
锁过期的时候,并发多个客户端同时请求过来,都执行了jedis.getSet(),最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。
12.3 set的扩展命令(set ex px nx)(注意可能存在的问题)
if(jedis.set(key, lock_value, "NX", "EX", 100s) == 1){ //加锁 try { do something //业务处理 }catch(){ } finally { jedis.del(key); //释放锁 } }
这个方案可能存在这样的问题:
锁过期释放了,业务还没执行完。
锁被别的线程误删。
12.4 set ex px nx + 校验唯一随机值,再删除
if(jedis.set(key, uni_request_id, "NX", "EX", 100s) == 1){ //加锁 try { do something //业务处理 }catch(){ } finally { //判断是不是当前线程加的锁,是才释放 if (uni_request_id.equals(jedis.get(key))) { jedis.del(key); //释放锁 } } }
在这里,判断当前线程加的锁和释放锁是不是一个原子操作。如果调用jedis.del()释放锁的时候,可能这把锁已经不属于当前客户端,会解除他人加的锁。
一般也是用lua脚本代替。lua脚本如下:
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end;
这种方式比较不错了,一般情况下,已经可以使用这种实现方式。但是存在锁过期释放了,业务还没执行完的问题(实际上,估算个业务处理的时间,一般没啥问题了)。
12.5 Redisson
分布式锁可能存在锁过期释放,业务没执行完的问题。有些小伙伴认为,稍微把锁过期时间设置长一些就可以啦。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。
当前开源框架Redisson就解决了这个分布式锁问题。我们一起来看下Redisson底层原理是怎样的吧:
只要线程一加锁成功,就会启动一个watch dog
看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。因此,Redisson就是使用Redisson解决了锁过期释放,业务没执行完问题。
零拷贝就是不需要将数据从一个存储区域复制到另一个存储区域。它是指在传统IO模型中,指CPU拷贝的次数为0。它是IO的优化方案
传统IO流程
零拷贝实现之mmap+write
零拷贝实现之sendfile
零拷贝实现之带有DMA收集拷贝功能的sendfile
13.1 传统IO流程
流程图如下:
用户应用进程调用read函数,向操作系统发起IO调用,上下文从用户态转为内核态(切换1)
DMA控制器把数据从磁盘中,读取到内核缓冲区。
CPU把内核缓冲区数据,拷贝到用户应用缓冲区,上下文从内核态转为用户态(切换2),read函数返回
用户应用进程通过write函数,发起IO调用,上下文从用户态转为内核态(切换3)
CPU将应用缓冲区中的数据,拷贝到socket缓冲区
DMA控制器把数据从socket缓冲区,拷贝到网卡设备,上下文从内核态切换回用户态(切换4),write函数返回
从流程图可以看出,传统IO的读写流程,包括了4次上下文切换(4次用户态和内核态的切换),4次数据拷贝(两次CPU拷贝以及两次的DMA拷贝)。
13.2 mmap+write实现的零拷贝
mmap 的函数原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr:指定映射的虚拟内存地址
length:映射的长度
prot:映射内存的保护模式
flags:指定映射的类型
fd:进行映射的文件句柄
offset:文件偏移量
mmap使用了虚拟内存,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址,从而减少数据拷贝次数!
mmap+write
实现的零拷贝流程如下:
用户进程通过mmap方法向操作系统内核发起IO调用,上下文从用户态切换为内核态。
CPU利用DMA控制器,把数据从硬盘中拷贝到内核缓冲区。
上下文从内核态切换回用户态,mmap方法返回。
用户进程通过write方法向操作系统内核发起IO调用,上下文从用户态切换为内核态。
CPU将内核缓冲区的数据拷贝到的socket缓冲区。
CPU利用DMA控制器,把数据从socket缓冲区拷贝到网卡,上下文从内核态切换回用户态,write调用返回。
可以发现,mmap+write实现的零拷贝,I/O发生了4次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中,包括了2次DMA拷贝和1次CPU拷贝。
mmap是将读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,所以节省了一次CPU拷贝‘’并且用户进程内存是虚拟的,只是映射到内核的读缓冲区,可以节省一半的内存空间。
sendfile实现的零拷贝
sendfile是Linux2.1内核版本后引入的一个系统调用函数,API如下:
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
out_fd:为待写入内容的文件描述符,一个socket描述符。,
in_fd:为待读出内容的文件描述符,必须是真实的文件,不能是socket和管道。
offset:指定从读入文件的哪个位置开始读,如果为NULL,表示文件的默认起始位置。
count:指定在fdout和fdin之间传输的字节数。
sendfile表示在两个文件描述符之间传输数据,它是在操作系统内核中操作的,避免了数据从内核缓冲区和用户缓冲区之间的拷贝操作,因此可以使用它来实现零拷贝。
sendfile实现的零拷贝流程如下:
sendfile实现的零拷贝
用户进程发起sendfile系统调用,上下文(切换1)从用户态转向内核态
DMA控制器,把数据从硬盘中拷贝到内核缓冲区。
CPU将读缓冲区中数据拷贝到socket缓冲区
DMA控制器,异步把数据从socket缓冲区拷贝到网卡,
上下文(切换2)从内核态切换回用户态,sendfile调用返回。
可以发现,sendfile实现的零拷贝,I/O发生了2次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中,包括了2次DMA拷贝和1次CPU拷贝。那能不能把CPU拷贝的次数减少到0次呢?有的,即带有DMA收集拷贝功能的sendfile
!
sendfile+DMA scatter/gather实现的零拷贝
linux 2.4版本之后,对sendfile做了优化升级,引入SG-DMA技术,其实就是对DMA拷贝加入了scatter/gather操作,它可以直接从内核空间缓冲区中将数据读取到网卡。使用这个特点搞零拷贝,即还可以多省去一次CPU拷贝。
sendfile+DMA scatter/gather实现的零拷贝流程如下:
用户进程发起sendfile系统调用,上下文(切换1)从用户态转向内核态
DMA控制器,把数据从硬盘中拷贝到内核缓冲区。
CPU把内核缓冲区中的文件描述符信息(包括内核缓冲区的内存地址和偏移量)发送到socket缓冲区
DMA控制器根据文件描述符信息,直接把数据从内核缓冲区拷贝到网卡
上下文(切换2)从内核态切换回用户态,sendfile调用返回。
可以发现,sendfile+DMA scatter/gather
实现的零拷贝,I/O发生了2次用户空间与内核空间的上下文切换,以及2次数据拷贝。其中2次数据拷贝都是包DMA拷贝。这就是真正的 零拷贝(Zero-copy) 技术,全程都没有通过CPU来搬运数据,所有的数据都是通过DMA来进行传输的。
synchronized是Java中的关键字,是一种同步锁。synchronized关键字可以作用于方法或者代码块。
一般面试时。可以这么回答:
反编译后,monitorenter、monitorexit、ACC_SYNCHRONIZED
monitor监视器
Java Monitor 的工作机理
对象与monitor关联
14.1 monitorenter、monitorexit、ACC_SYNCHRONIZED
如果synchronized作用于代码块,反编译可以看到两个指令:monitorenter、monitorexit,JVM使用monitorenter和monitorexit两个指令实现同步;如果作用synchronized作用于方法,反编译可以看到ACCSYNCHRONIZED标记,JVM通过在方法访问标识符(flags)中加入ACCSYNCHRONIZED来实现同步功能。
同步代码块是通过monitorenter和monitorexit来实现,当线程执行到monitorenter的时候要先获得monitor锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。
同步方法是通过中设置ACCSYNCHRONIZED标志来实现,当线程执行有ACCSYNCHRONIZED标志的方法,需要获得monitor锁。每个对象都与一个monitor相关联,线程可以占有或者释放monitor。
14.2 monitor监视器
monitor是什么呢?操作系统的管程(monitors)是概念原理,ObjectMonitor是它的原理实现。
在Java虚拟机(HotSpot)中,Monitor(管程)是由ObjectMonitor实现的,其主要数据结构如下:
ObjectMonitor() { _header = NULL; _count = 0; // 记录个数 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; // 处于wait状态的线程,会被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; // 处于等待锁block状态的线程,会被加入到该列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; }
ObjectMonitor中几个关键字段的含义如图所示:
14.3 Java Monitor 的工作机理
想要获取monitor的线程,首先会进入_EntryList队列。
当某个线程获取到对象的monitor后,进入Owner区域,设置为当前线程,同时计数器count加1。
如果线程调用了wait()方法,则会进入WaitSet队列。它会释放monitor锁,即将owner赋值为null,count自减1,进入WaitSet队列阻塞等待。
如果其他线程调用 notify() / notifyAll() ,会唤醒WaitSet中的某个线程,该线程再次尝试获取monitor锁,成功即进入Owner区域。
同步方法执行完毕了,线程退出临界区,会将monitor的owner设为null,并释放监视锁。
14.4 对象与monitor关联
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header),实例数据(Instance Data)和对象填充(Padding)。
对象头主要包括两部分数据:Mark Word(标记字段)、Class Pointer(类型指针)。
Mark Word 是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。
重量级锁,指向互斥量的指针。其实synchronized是重量级锁,也就是说Synchronized的对象锁,Mark Word锁标识位为10,其中指针指向的是Monitor对象的起始地址。
分布式id生成方案主要有:
UUID
数据库自增ID
基于雪花算法(Snowflake)实现
百度 (Uidgenerator)
美团(Leaf)
什么是雪花算法?
雪花算法是一种生成分布式全局唯一ID的算法,生成的ID称为Snowflake IDs。这种算法由Twitter创建,并用于推文的ID。
一个Snowflake ID有64位。
第1位:Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。
接下来前41位是时间戳,表示了自选定的时期以来的毫秒数。
接下来的10位代表计算机ID,防止冲突。
其余12位代表每台机器上生成ID的序列号,这允许在同一毫秒内创建多个Snowflake ID。
雪花算法
最后PHP中文网祝大家找到一份满意的工作!!!
【面试题专题】
Front-end : [Questions d'entretien Front-end][Questions d'entretien JS][ Questions d'entretien Vue][Questions d'entretien Ajax][Questions d'entretien React]
Base de données : [Entretien mysql questions][ questions d'entretien redis][questions d'entretien oracle]
Backend : [questions d'entretien PHP][questions d'entretien thinkphp][questions d'entretien python][questions d'entretien java][ Question d'entretien Android】