目录
限流从类型上分有两种:限制速率,一种是限制并发,采用漏桶算法,依次以一定速率的流出
1,redis的五种常用类型及数据结构(string,list,hash,set,zset)
写在前面:只是为了做笔记!!!
会持续不断地学习、更新,有喜欢的朋友一键三连啦~
一,网络协议
Linux
select poll epoll
select:有三种writefd,readfd,exceptfd(可读,可写,可接收),有最大长度限制,linux为1024,几乎所有平台都支持
poll:用一个poll指针来指向就读的fd,但是是需要去轮询就绪的fd
epoll:e是最大的改动(even事件回调),将用户关系的fd存放到一个事件表,先创建一个fd,注册进事件表,等待callback.在两种不同的模式下LT(回调通知不一定立执行),ET(高速模式)立即执行
Nginx
限流从类型上分有两种:限制速率,一种是限制并发,采用漏桶算法,依次以一定速率的流出
白名单:ngx_http_geo_module 和 ngx_http_map_module
下载速度:ngx_http_limit_req_module
1、limit_conn_zone
2、limit_req_zone
3、ngx_http_upstream_module:参数用max_conns限制最大并发数
//limit_conn_zone
http{
limit_conn_zone $binary_remote_addr zone=one:10m;
server
{
......
limit_conn one 10;
......
}
}
tcp工作在传输层,传输包数据
TCP三手握手:
1,客户端发送一个初始序列号和syn=1请求标志
2,服务端收到后返回一个syn请求标志,同时发送一个确认标志ack,自己的seq,客户端的ack+1
3,客户端收到ack后,发送一个ack,自己的seq对方的ack
三次是为了确保双方都知道自己接收发送正常
四手挥手:
1,客户端发出一个fin结束标志,自己的序列号seq=u,进入FIN-WAIT-1的状态
2,服务端发送ack=1,对方的ack,自己的seq 进入close-wait状态
3,客户端进入FIN-WAIT-2状态,服务端请求断开,发送seq ack,对方面的ack=对方的序列号+1,进入time-wait状态
4,客户端收到后,断开请求,发送ack seq 对方的ack=邓方的序列号+1
当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,是否结束还需要等待上层应用决定,所以ack和fin分开过需要四次
TCP拆包/粘包
定义: 粘包就是一个较大的包与一个较小的包顺序传输时,较大的包有部分数据与较小的包粘在了一起,即发生了拆包与粘包
原因:由于没有明确的包的界限,应用写入的数据大于套字节缓冲区,这会发生拆包,反之,粘包
解决办法:
1,明确边界,如特殊字符
2,固定长度,不足用0等补足
3,添加包首,包括包长度等信息
IP工作在网络层
二,mysql
1,mysql架构
一条sql如果执行的
先从连接器进行权限,用户校验,如果有缓存则直接返回结果,如果没再到第二层的解析器进行词法、语法检查,然后交给优化器进行索引优化,索引选择,再到执行器调用
engine接口,再返回
2,InnoDB介绍
2.1 整体架构图
内存池
缓冲池( innodb_buffer_pool)
参数:innodb_buffer_pool_size 调整缓存大小,通过lru(最近最小使用策略),利用checkpoint(擦除)机制将数据刷新到磁盘(PS:innodb是dml语句是先在缓冲区里操作)
重做日志
redo log(已修改记录日志) 保证事务的持久性,采用Write ahead Log方式,在事务提交时,先写重做日志,再修改页
redo log 在结构上分为两个部分:一个redo_log_buffer 一个redo_log_file
redo log通常是物理日志,记录的是数据页的物理修改,用来恢复数据页最后一次提交的位置
//参数:
innodb_log_buffer_size ##buffer大小
innodb_flush_log_at_trx_commit ##多少次提交进行一次log file写入,默认为1
innodb_flush_log_at_timeout ##从缓冲写入的时间频率
undo log (回滚日志)是一个逻辑日志,用来回滚到行记录到某个版本
事务
特性:ACID
原子性(Atomicity):undo log
隔离性(Isolation):锁
持久性(Durability):redo log
一致性(Consistency):redo log
事务的实现就是通过redo log与锁机制
事务的隔离级别
事务隔离级别 | 描述 | 实现原理 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|---|
READ UNCOMMITTED | 一个事务会读到另一个未提交事务修改过的数据。 | 无 | 是 | 是 | 是 |
READ COMMITTED | 一个事务只能读到另一个已经提交的事务修改过的数据。 | 乐观锁+MVCC | 否 | 是 | 是 |
REPEATABLE READ | 一个事务只能读到另一个已经提交的事务修改过的数据,而且该事务第一次读过某条记录后,即使其他事务修改了该记录的值并且提交,该事务之后再读该条记录时,读到的仍是第一次读到的值。 | 乐观锁+MVCC | 否 | 否 | 是。解决方式:快照读:MVCC;2、当前读:next-key锁解决 |
SERIALIZABLE | 事务串行化执行 | 悲观锁 | 否 | 否 | 否 |
Purge 最终实现delete update的操作,由于innodb支持mvcc,所以记录不能在事务提交时立即处理,因为还有其它事务在引用这行数据undo log (未修改记录日志) 保证事务的原子性,可回滚操作,和MVCC的实现
MVCC原理:用锁(并发控制)+undolog (多版本)
1,表中每行的隐藏列:记录了一个事务的ID,一个指向上一个版本undo log的指针
2,undo log的版本链
3,read view(快照):在RR级别下,开启事务后,select时,会创建一个快照,把其它活跃的事务记录下来,RC就是每个select都会生成快照
4,可见性判断:当前事务ID:trx_id_current,最早的事务ID up_limit_id,最晚的事务ID:low_limit_id
口诀:当前事务ID小于最早的,可见;大于最晚的,不可见,在两者之间,如果这个事务ID存在于活跃中的不可见,不在可见,需要查找undo log上一个版本
辅记点:事务ID越早越可见
锁
定义 | 实现/原因 | 解决办法 |
悲观锁 | 每次操作都会被修改 | mysql使用共享锁与排它锁,行锁 |
乐观锁 | 只有在修改时才会加锁 | 版本号或Cas(比较)提交 |
共享锁S | 读一行数据 | share on mode |
排他锁X | 更新/删除一行 | for update duplicate on update |
共享意向锁IS | 获取S锁前先获取此锁 | |
排他意向锁IX | 获取X锁前先获取此锁 | |
record lock | 单行锁 | |
gap lock | 间隙锁,不包括本身 使用普通索引锁定 记录不存在或查询一个范围 | |
record lock + gap lock | 区间锁 | |
死锁 | 两个事务都在请求同一个资源而持续等待无法释放过程 1,两个事务加锁的顺序不一致 | 1,编码时注意查询与写入的顺序 |
当前读与快照读
快照读:读取的是记录数据的可见版本
是通过MVCC与undo log版本链,读的是一个快照数据,RR是事务开启的第一个select,RC是每个select
当前读:读取的是最新版本的数据
以下语句会触发:
select...lock in share mode (共享读锁)
select...for update
update , delete , insert
是通过record lock 和 next-key lock实现,读的是最新的数据,并对读取的数据加锁
死锁
多个事务在请求同一个资源而互相等待对方释放的过程
查看方法:
//查看当前所有锁的情况
show status like '%lock%';
//查看当前进程
show processlist;
1:查看当前的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
2:查看当前锁定的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
3:查看当前等锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
造成原因及解决方法:两个(或以上)的Session加锁的顺序不一致
从操作数据库的动作来分析
1.Delete删除不存在的数据导致死锁
容易造成区间锁
解决方法:
①先检查记录是否存在,否则不删除。
③如果锁是gap锁,这种死锁需要将事务的隔离级别设置为read commit。
④对于等待锁的的会话使用innodb_lock_wait_timeout,防止过载。
2,insert update时造成死锁
解决方法:
1,使用mysql自有的方法
1,on duplicate key update
2,使用replace来解决
2,使用redis自增Key
3, 雪花算法
3,select for update(行锁) 或者 select share on mode
Innodb关键特性
1,插入缓冲 insert buffer 由于非聚集索引即辅助索引是非连续的,所以在刷盘之前先进行判断是否在索引缓存页中,如果存在即直接插入或修改,如果不再就放入insert buffer中以一定频率和辅助索引页的节点进行合并再刷盘,内部实现也为一颗b+tree
2,双写:写redo log和redo log副本,帮助灾难恢复
3,自适应hash,监控表的查询情况,建立一个hash表,不需要建立hash索引
4,异步I/O 预读,预写都是通过异步进行
5,刷新邻近页:即为同时邻近脏页数据
6,数据字典信息
Innodb索引:
原理:使用B+树的结构,采用聚集索引的结构,叶子节点才会存放行记录或主键记录,而且索引与数据文件放在一起的,一张表就是一个聚集索引,myisam是非聚集
优点:按索引排序,方便查询与order等操作,检索更快
主索引:叶子节点存放的是行记录
辅助索引:叶子节点存放的主键ID,有一个回表的操作,I/O次数为通常为(树高-1,ps:这个-1是因为根节点常驻内存)
前缀索引:由于mysql的数据页为16K,一个页在索引树上为一个节点,如果要存放更多的索引值,就需要减少索引的长度,而本身索引的长度是有限制的小于767个字节,组合索引不能超过307字节,引出一个问题,为什么是307呢,还是与page 16K相关,因为要预留两对(fil page )Header,如果一个页不能存放一个索引值就退化成了链表,不符合设计思想
覆盖索引:用一个sql说明,简单易懂:select a,b,c from user where a and b and c (a,b,c)union_index
PS:索引下推:(5.6后mysql服务器将在判断条件里有索引的列交给存储引掣进行判断,减少底层访问表的次数,也减少了mysql调用存储引掣的次数)
常见问题 Innodb与Myisam的区别:
Innodb是聚集索引,myisam是非聚集也就是索引与数据是分离的,每个叶子节点存放的是数据记录的指针,所以myisam不需要主键,就能直接查询到数据;Innodb支持事务与行锁,这是最为关键的两点
Mysql主从同步
1,架构图
2,同步方式
2.1 异步同步:在master上开启binlog功能一个I/O同步线程,在从库上配置,两个线程(一个IO线程,一个请求线程)
//主
server-id=1
log-bin=mysql-bin
//从
log_bin = mysql-bin
server_id = 2
relay_log = mysql-relay-bin
log_slave_updates = 1
read_only = 1
2.2 半同步 master会等待slave至少发送一个写入relay log成功后提交,否则一直等待至超时(10s)
2.3 多线程同步:slave多个线程从master同步binlog日志,再顺序写入
3,两种格式:statment (语句);raw(行记录),一个mixed(混合)
Mysql一些问题
1,mysql如何解决主从一致性问题?
关闭双1设置(binlog和redo log)写入到磁盘,如果是0只写入缓存等待按时间频率刷盘,如果是1会写入到磁盘
减少从库查询压力,添加机器
使用中间件(缓存)
对实时性要求比较高的强制走主
高性能Mysql主从架构的复制原理及配置详解_weixin_30311605的博客-CSDN博客
三,redis
1,redis的五种常用类型及数据结构(string,list,hash,set,zset)
SDS simple synamic string:支持自动动态扩容的字节数组,遍历O(N),获取长度O(1),二进制安全,有一个特定的结束符\0
list :链表,双向,无环,长度计数,多态
dict :使用双哈希表实现的, 支持平滑扩容的字典
zskiplist :附加了后向指针的跳跃表
intset : 用于存储整数数值集合的自有结构
ziplist :一种实现上类似于TLV, 但比TLV复杂的, 用于存储任意数据的有序序列的数据结构
quicklist:一种以ziplist作为结点的双链表结构, 实现的非常不错
zipmap : 一种用于在小规模场合使用的轻量级字典结构
String:
list:
hash:
set:
zset有三种还有一种是用hash(字典)
sds做为string的底层结构,buf为了一个byte数组,再用柔性数组(伸缩性)来解决容量问题
list 做为list的底层结构
hashTable做hash的底层结构之一
skiplist 跳表 用两张表
ziplist 压缩表作为 list,hash,zset的底层结构
intset 整数集合作为set的底层结构之一
2,缓存击穿,穿透,雪崩和一致性问题
2.1,穿透:缓存里没有,数据库里也没有。解决:数据的合法性、安全性进行校验;布隆过滤器
击穿:缓存里没有,数据库里有。解决:热点Key永不失效;互斥锁
雪崩:缓存里的key大面积失效。解决:随机过期时间;主动更新数据
一致性:延时双删(在更新数据库前和后都删除缓存数据,再加上一个延时时间);异步中间件订阅binlog进行一个补偿机制
3,缓存过期策略
3.1 定时删除:redis每隔100ms随机检查是否有过期Key
3.2 惰性删除:在访问到这个Key时检查
3.3 淘汰机制:lru(最近最少使用)
noeviction:不足时报错,无法写入
allkeys-lru: 不足时,在所有Key中最近最少使用的删除
allkeys-ramdom:不足时,在所有Key中随机删除
volatile-lru: 不足时,在过期Key中最近最少使用删除
volatile-ramdom:不足时,在过期Key中随机删除
volatile-ttl:不足时,在过期Key中删除更早的
4,缓存主从+哨兵
4.1,主从
概念:一般来讲redis的服务器搭建分为主从,分布式,而哨兵单独存在是没有意义的
原理及实现:
1,先在从节点redis.conf中
配置:slaveof 主数据库ip 主数据库port
先启动主节点,再启动从节点即可-------准备阶段
2,从节点发送sync(psync)命令,主节点收到后无条件触发RDB持久化并保存在此期间新的写命令
3,当快照完成后,主节点将RDB文件和命令一起发送给从节点
4,从节点会加载快照文件和命令进行同步
核心机制:
1,offset 偏移量
2,backlog 1M用来做增量复制留存记录页
3,run id :主从都会写
4,psync:同步命令
5,heatbeat机制:主节点10s一次,从节点1s一次
4.2,哨兵
作用:
1.监控主数据库和从数据库是否能够正常运行
2.主数据库出现故障时自动将从数据库转换为主数据库。
实现:由三个定时脚本
- 每隔10s向主数据库和从数据库发送info命令,检查节点信息
- 每隔2s向主数据里和从数据库的_sentinel_:hello频道发送自己的信息。
- 每隔1s向所有数据库节点和所有哨兵节点发送ping命令
选举:两个选举,一个是对节点故障的选举;另一个是哨兵自己本身的故障选举,哨兵为奇数方便投票
主节点下线:
- 选出领头哨兵
- 领头哨兵从在线的从数据库中,选择优先级最高的从数据库。优先级可以通过slave-priority选项设置。
- 如果优先级相同,则从复制的命令偏移量越大(即复同步数据越多,数据越新),越优先。
- 如果以上条件都一样,则选择run ID较小的从数据库
哨兵自我选举
- 发送主数据库客观下线的哨兵向每个哨兵命令,要求对方选择自己为领头。
- 如果没有选择过其他哨兵,则会同意请求
- 如果发现有超过半数,且超过quorum的哨兵同意自己的请求,则自己就是哨兵领头。
4.3,分布式系统数据一致性
首先一致性的特性(ACID)即一致性,隔离性,持久性,原子性。另外CAP即一致性、可用性、分区容忍性
一致性协议
两阶段提交:两个阶段:准备阶段(投票反馈阶段)和 提交阶段(执行阶段)
带来的问题:1,单点故障
2,网络引起的消息丢失和阻塞
三阶段提交:canCommit prepareCommit doCommit。解决了阻塞问题
RAFT协议:leader选举,日志复制,节占监控
互联网公司常用:TCC本质上在两阶段上增加了一个取消的流程即Try Comfirm,Cancel,保证高可用性
4.4,Redis 问题
aof文件过大,如何处理:重写 bigre
大key删除:set->sscan hash->hscan hdel 分批删除
四,PHP&Go
PHP hashtable packed array
Go gouroutine(协程),采用CSP的方式(在通信中共享内存),内部实现原理为MPG,大意是:M(内核进程),P(逻辑处理器),G(协程),Go会将G首先放入一个globle队列,然后分配到一个P的local队列,内核M进程会不断地去取G,P切换栈空间进行执行
channel 是 goroutine 之间通信的一种方式,可以类比成 Unix 中的进程的通信方式管道。
五,MQ
Kafka 与Mq:
rabbitmq 如何保证成功发送消息,tranction comfirm ack(默认)
如何保证成功消费消息 ack确认
保证顺序性?:一个queue对应一个consumer
六,数据结构&算法
一,归并算法
思想:利用递归,先拆分、后合并、再排序,其实就是一颗二叉树
步骤:时间复杂度O(N*logn)
- 先均分成两个数列
- 递归上一个步骤,直到只有一个元素
- 父数列合并两个子数列并排序,递归返回数列
//归并
<?php
function mergeSort($arr)
{
$count = count($arr);
if ($count == 1) {
return $arr;//只有一个
}
$mid = (int) ($count/2);
$left = array_slice($arr,0, $mid);
$right = array_slice($arr,$mid);
$left = mergeSort($left);
$right = mergeSort($right);
$arr = merge($left,$right);
print_r($arr);
}
function merge($left, $right)
{
$newArr = [];
while(count($left) && count($right)) {
$newArr[] = $left[0] < $right[0] ? array_unshift($left) : array_shift($right);
}
return array_merge($newArr, $left, $right);
}
$arr = [10,1,3,2,7,8,6,5,4];
mergeSort($$arr);
?>
二,快速排序
思想:确定基线,将大于基线的往后,将小于基线的往前,左右分别递归
时间复杂度O(N*logn)
function quickSort($arr)
{
$base = $arr[0];
$left = $right = [];
for ($i =1;$i<count($arr);$i++) {
if ($arr[$i] < $base) {
$left[] = $arr[$i];
} else {
$right[] = $arr[$i];
}
}
$left = quickSort($left);
$right = quickSort($right);
return array_merge($left,[$mid], $right);
}
三,单链表反序
<?php
class listRevert{
pubic function setNode($value){
$this->next = $value;
}
function getNext(){
return $this->next;
}
//反序核心代码
##单链表反序
function revert($head = null)
{
if (empty($head)) {
return $head;
}
//取出头变成尾
$pre = $head;
$cur = $this->getNext();//取出下一个节点
while($cur != null) {
$next = $this->getNext();
$this->setNode($cur); //设置当前节点
//交换
$pre = $cur;
$cur = $next;
}
$this->setNode(null);
$head = $pre;//将尾变为头
return $head;
}
}
四,冒泡
<?php
// 这里以一维数组做演示
$demo_array = array(23,15,43,25,54,2,6,82,11,5,21,32,65);
// 第一层for循环可以理解为从数组中键为0开始循环到最后一个
for ($i=0;$i<count($demo_array);$i++) {
// 第二层将从键为$i的地方循环到数组最后
for ($j=$i+1;$j<count($demo_array);$j++) {
// 比较数组中相邻两个值的大小
if ($demo_array[$i] > $demo_array[$j]) {
$tmp = $demo_array[$i]; // 这里的tmp是临时变量
$demo_array[$i] = $demo_array[$j]; // 第一次更换位置
$demo_array[$j] = $tmp; // 完成位置互换
}
}
}
七,系统架构
redis 高可用方案
nginx 高可用方案
mysql高可用方案
mq高可用方案
八,GO
巨人的肩膀: