比较一下php和go的区别?
Go是一种静态类型语言。PHP是一种动态类型语言。
PHP 每个请求进来时都会创建 fpm-worker 进程,从而导致系统并发高时 CPU 会产生频繁创建进程的开销,而 Go 不会。
golang 是先编译,后执行。由于编译方面的原因,即使是糟糕的Golang代码也会优于良好的PHP代码,从而提高性能。
由于多线程技术提高了Golang的效率,减少了部署规模,减少了内存占用量,
Golang降低了企业的间接成本。
什么是守护进程?
守护进程(daemon)是一种特殊的进程,它的生命周期很长,它在后台运行并且没有控制终端(这样可以保证守护进程不会接收到各种来自终端的信号)。
比如:crontab、sshd、nginx等,都会使用守护进程的形式运行,确保可以一直正常的提供服务。
如何实现守护进程?
fork子进程,父进程退出(当前子进程会成为init进程的子进程)
子进程调用setsid(),开启一个新会话,成为新的会话组长,并且释放于终端的关联关系
再次fork子进程,父进程退出(可以防止会话组长重新申请打开终端)
关闭打开的文件描述符
改变当前工作目录chdir
清除进程的umask
PHP实现
$pid = pcntl_fork();
if ($pid == -1) {
die('创建子进程失败');
} else if ($pid) {
exit(0);
}
posix_setsid();
echo "成功输出,脱离终端" . PHP_EOL;
sleep(5);
$pid = pcntl_fork();
if ($pid == -1) {
die('创建子进程失败');
} else if ($pid) {
exit(0);
}
echo "依然可以输出" . PHP_EOL;
sleep(5);
@fclose(STDOUT);
@fclose(STDERR);
$STDOUT = fopen('/dev/null', "a");
$STDERR = fopen('/dev/null', "a");
chdir('/');
umask(0);
echo "这里不会输出, ps aux | grep daemon.php 查看进程,20s后退出" . PHP_EOL;
sleep(20);
注意事项:
php daemon.php &这样使用,当关闭终端后,当前php进程也会同时关掉
nohup php daemon.php > daemon.log &终端关闭后,依然会继续运行
使用supervisor
如何理解框架?
框架是构成一类特定软件可复用设计的一组相互协作的类。框架规定了应用的体系结构。定义了整体结构,类和对象的分隔,各部分的主要责任,类和对象怎么协作,以及控制流程。框架预定义了这些设计参数,以便于应用设计者或实现者能集中精力于应用本身的特定细节。框架记录了其应用领域的共同的设计决策。因而框架更强调设计复用,尽管框架常包括具体的立即可用的子类
框架常用的主要设计模式有哪些?
创建型
单例(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点
抽象工厂(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口
工厂方法(Factory Method):定义一个用于创建对象的接口,让子类决定哪一个类实例化
原型(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象
结构型
适配器(Adapter):将一个类的接口转换成期望的另一个接口
代理(Proxy):为其他对象提供一个代理以控制对这个对象的访问
行为型
备忘录(Memento):备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态
观察者(Observer):在对象间定义一个一对多的联系性,由此当一个对象改变了状态,所有其他相关的对象会被通知并且自动刷新
策略(Strategy):定义一个算法的系列,将其各个分装,并且使他们有交互性。策略模式使得算法在用户使用的时候能独立的改变
类的静态调用和实例化调用
占用内存
静态方法在内存中只有一份,无论调用多少次,都是共用的
实例化不一样,每一个实例化是一个对象,在内存中是多个的
不同点
静态调用不需要实例化即可调用
静态方法不能调用非静态属性,因为非静态属性需要实例化后,存放在对象里
静态方法可以调用非静态方法,使用 self 关键字。php 里,一个方法被self::后,自动转变为静态方法
调用类的静态函数时不会自动调用类的构造函数
接口和抽象的区别
抽象用于描述不同的事物,接口用于描述事物的行为。
PHP 不实例化调用方法
静态调用、使用 PHP 反射方式。
有哪些常见的 php.ini 配置选项
配置选项
名字 默认 备注
short_open_tag "1" 是否开启缩写形式(<? ?>)
precision "14" 浮点数中显示有效数字的位数
disable_functions "" 禁止某些函数
disable_classes "" 禁用某些类
expose_php "" 是否暴露 PHP 被安装在服务器上
max_execution_time 30 最大执行时间
memory_limit 128M 每个脚本执行的内存限制
error_reporting NULL 设置错误报告的级别E_ALL& ~E_NOTICE& ~E_STRICT& ~E_DEPRECATED
display_errors "1" 显示错误
log_errors "0" 设置是否将错误日志记录到 error_log 中
error_log NULL 设置脚本错误将被记录到的文件
upload_max_filesize "2M" 最大上传文件大小
post_max_size "8M" 设置POST最大数据限制
php -ini | grep short_open_tag //查看 php.ini 配置
动态设置
ini_set(string $varname , string $newvalue);
ini_set(‘date.timezone’, ‘Asia/Shanghai’); //设置时区
ini_set(‘display_errors’, ‘1’); //设置显示错误
ini_set(‘memory_limit’, ‘256M’); //设置最大内存限制
MySQL、MySQLi、PDO 区别
MySQL
允许 PHP 应用与 MySQL 数据库交互的早期扩展
提供了一个面向过程的接口,不支持后期的一些特性
MySQLi
面向对象接口
prepared 语句支持
多语句执行支持
事务支持
增强的调试能力
PDO
PHP 应用中的一个数据库抽象层规范
PDO 提供一个统一的 API 接口,无须关心数据库类型
使用标准的 PDO API,可以快速无缝切换数据库
php代码执行过程是怎样的?
PHP 代码 => 启动 php 及 zend 引擎,加载注册拓展模块 => 对代码进行词法/语法分析 => 编译成opcode(opcache) => 执行 opcode
PHP7 新增了抽象语法树(AST),在语法分析阶段生成 AST,然后再生成 opcode 数组。
怎么评价对象关系映射/ORM?
优点
缩短编码时间、减少甚至免除对 model 的编码,降低数据库学习成本
动态的数据表映射,在表结构发生改变时,减少代码修改
可以很方便的引入附加功能(cache 层)
缺点
映射消耗性能、ORM 对象消耗内存
SQL 语句较为复杂时,ORM 语法可读性不高(使用原生 SQL)
有哪些 PHP 支持回调的函数,如何实现?
数组函数:
array_map、array_filter、array_walk、usort
自己实现的思路:
is_callable + callbacks + 匿名函数实现
PHP 数组底层怎么实现的?
关键点: (HashTable + Linked list)
PHP 数组底层依赖的散列表数据结构,定义如下(位于 Zend/zend_types.h)。
数据存储在一个散列表中,通过中间层来保存索引与实际存储在散列表中位置的映射。
由于哈希函数会存在哈希冲突的可能,因此对冲突的值采用链表来保存。
哈希表的查询效率是o(1),链表查询效率是o(n);因此PHP数据索引速度很快;但是相对比较占用空间。
PHP内存管理机制与垃圾回收机制
参考答案:http://www.cnblogs.com/zk0533/p/5667122.html
php的内存管理机制是:预先给出一块空间,用来存储变量,当空间不够时,再申请一块新的空间。
存储变量名,存在符号表。
变量值存储在内存空间。
在删除变量的时候,会将变量值存储的空间释放,而变量名所在的符号表不会减小。
php垃圾回收机制是:
在5.2版本或之前版本,PHP会根据 引用计数 (refcount)值来判断是不是垃圾,如果refcount值为0,PHP会当做垃圾释放掉,这种回收机制有缺陷,对于环状引用的变量无法回收。
在5.3之后版本改进了垃圾回收机制。具体如下:
如果发现一个zval容器中的refcount在增加,说明不是垃圾; 如果发现一个zval容器中的refcount在减少,如果减到了0,直接当做垃圾回收; 如果发现一个zval容器中的refcount在减少,并没有减到0,PHP会把该值放到缓冲区,当做有可能是垃圾的怀疑对象; 当缓冲区达到了临界值,PHP会自动调用一个方法去遍历每一个值,如果发现是垃圾就清理。
为什么使用B+树,而不是用B*树
因为B*树非叶子节点使用了指向兄弟节点的指针。如果一个节点满了之后,自己的兄弟节点还没有满,需要将一部分数据转移到自己的兄弟节点去。如果兄弟节点也满了,就在自己和兄弟节点之间添加新的节点。因为兄弟之间分配新节点的概率还是比较低的,所以空间利用率还是比较高的。 是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针;
除了主键索引,还用过什么
唯一索引
普通索引
覆盖索引
前缀索引
联合索引
主键索引和唯一索引的区别
主键一定会创建一个唯一索引,但是有唯一索引的列不一定是主键;
主键不允许为空值,唯一索引列允许空值;
一个表只能有一个主键,但是可以有多个唯一索引;
主键可以被其他表引用为外键,唯一索引列不可以;
主键是一种约束,而唯一索引是一种索引,是表的冗余数据结构,两者有本质的差别
最左匹配原则案例
where a = 1 and b=1 and c = 1. 能命中abc
where a = 1 and b > 1 and c = 1 不能命中c 因为b是范围索引。范围索引的话,意味着b可能是无序的。
where a > 1 and b = 1 and = 1 bc不能命中索引,因为范围查询是不能命中索引的。
order by 能用上索引么?
CREATE TABLE t
(
id
int(11) NOT NULL,
city
varchar(16) NOT NULL,
name
varchar(16) NOT NULL,
age
int(11) NOT NULL,
addr
varchar(128) DEFAULT NULL,
PRIMARY KEY (id
),
KEY city
(city
)) ENGINE=InnoDB;
这种情况下对name进行排序的话,是不会用上索引的。因为是对全文进行排序。
select city,name,age from t where city=‘杭州’ order by name limit 1000 ;
如果认为字段值过大的话,会进行rowid排序,也就是每行,根据city 取到行数据之后。只取 id 和 name,然后去按name进行排序。这种情况下,其实是内存不够的情况。这种情况下,name是无序的,需要多一次排序的操作。
如果在city 和name上面建立联合索引的话,根据city取的值,name就是有序的,减少排序的操作。
alter table t add index city_user(city, name);
这种情况下, 不需要临时表,也不需要排序。
using index 说明使用了覆盖索引,覆盖索引的效率还是比较高的。
mysql索引的底层B+树,说说为什么使用B+树,跟红黑树有什么区别,B树和B+树的区别?
主要考虑的是IO影响吧。因为B+ 树只有叶子节点存储数据,B树内部也存储数据。在查询相同数据量的情况下,B树高度更高,IO次数更多,然后只能一点点加载数据页。 B树的话,所有的节点都是数据地址。需要在内部节点和叶子之间去查询数据。b树的分支节点也有数据。 b树范围查询只能中序遍历。
B+树只有叶子节点数据,而且叶子节点之间由链表构成的,在叶子节点直接顺序查询会比较快。b+树的数据都集中在叶子节点。分支节点只负责索引。b+树的层高 会小于 B树 平均的Io次数会远大于 B+树(因为B+树是顺序查找)b+树更擅长范围查询。叶子节点 数据是按顺序放置的双向链表。 b+树可以把索引完全加载至内存中。支持多路,多路的好处:可以每次只加载一个节点的数据进去,因为内存的容量是有限的。【这个就是多路的好处了
MYSQL分页limit速度太慢,如何优化?
为什么要对数据库进行主从分离?
参考答案:https://my.oschina.net/candiesyangyang/blog/203425
索引查找在Linux的磁盘上是怎么操作的
innodb为什么必须要有主键索引?
使用InnoDB引擎,数据在硬盘上是如何存放的?
可参考:https://blog.csdn.net/hollis_chuang/article/details/113153569
聚族索引与非聚族索引的区别
按物理存储分类:聚簇索引(clustered index)、非聚簇索引(non-clustered index)
聚簇索引的叶子节点就是数据节点,而非聚簇索引的叶子节点仍然是索引节点,只不过有指向对应数据块的指针。
数据库主从复制 M-S 是怎么同步的?是推还是拉?会不会不同步?怎么办?
如何保障数据的可用性,即使被删库了也能恢复到分钟级别。你会怎么做?
数据库连接过多,超过最大值,如何优化架构。从哪些方便处理?
int 占多少字节?
bigint 呢?int (3) 和 int (11) 有区别吗?
可以往 int (3) 里存 1 亿吗?
varchar 最长多少?
drop delete truncate的区别?
drop 删除表和数据
delete 删除数据 带where
truncate 不带where 的删除,不太安全。
where in (几个) where in (几万个) 有什么区别
select * from single_table where key1 in ('aa), ‘aa1’, ‘aa2’, …, ‘zz100’);
mysql在5.7.3之前的版本是的eq_range_index_dive_limit的默认值是10,在5.7.3之后是200.
当in语句的单点区间数量大于等于eq_range_index_dive_limit的值时,就不会使用index dive来计算各个单点区间对应的索引记录条数,而是使用索引统计数据。
例如rows是9693,key1列的不重复值为968,所以key1列的平均重复次数为:9693/968 = 10条。
当in的数量为20000个时,意味着有20000个单点区间的时候,就直接使用统计数据来估算对应的记录条数。每个区间对应10条,对应的回表记录数就是20000 * 10 = 200000条。
当in的数量为几个的时候,由于key1列只是一个普通索引的话,每个单点的值对应多少条记录并不确定。计算方式就是直接获取索引对应的B+树的区间最左记录和区间最右记录,然后再计算这两条记录之间有多少条记录。
这种直接访问索引对应B+树来计算某个扫描区间内对应的索引记录条数的方式就是index dive
内容来自《MySQL是怎样运行的》
mysql --help | grep max-allowed-packet mysql: [Warning] World-writable config file ‘/usr/local/etc/my.cnf’ is ignored. --max-allowed-packet=# max-allowed-packet 16777216
in 没有大小限制。但是受max-allowed-packet的限制,最多也就2000个吧
innodb 的索引组织方式?
聚簇索引必须要很清楚,注意 innodb 聚簇索引叶子结点保存的是完整数据,innodb 普通索引叶子保存的是记录的主键,myisam 索引叶子保存的是记录的位置 / 偏移量。
数量级在多少适合分表?
MySQL 单表容量在500万左右,性能处于最佳状态,此时,MySQL 的 BTREE 索引树高在3~5之间
BTree 与 BTree-/BTree+ 索引原理是什么?
BTree
二叉树导致树高度非常高,逻辑上很近的节点,物理上非常远,无法利用局部性,IO 次数多,查找效率低
BTree-
每个节点都是二元数组[key,data],所有节点都可以存储数据,key 为索引,data 为索引外的数据。插入删除数据会破坏 BTree 性质,插入数据时候,需要对数据进行分裂、合并、转移等操作保持 BTree 性质,造成 IO 操作频繁
BTree+
非叶子节点不存储 data,只存储索引 key,只有叶子节点才存储 data
MySQL中的 BTree+
在经典 BTree+ 的基础上进行了优化,增加了顺序访问指针。在 BTree+ 的每个叶子节点增加了一个指向相邻叶子节点的指针,形成了带顺序访问指针的 BTree+,提高了区间访问性能。
JOIN和UNION区别
join 是两张表做交连后里面条件相同的部分记录产生一个记录集,
union是产生的两个记录集(字段要一样的)并在一起,成为一个新的记录集 。
mysql常见问题和解决思路?
引《mysql45讲》里面的一个留言
数据库——解决数据存储的问题
WAL——解决数据一致性问题
多线程——解决性能差异的问题
锁——解决多线程并发导致数据不一致的问题
索引——解决数据查询或者操作慢的问题
日志——解决数据备份、同步、恢复等问题
数据库主备——解决数据高可用的问题
数据库读写分离——解决数据库压力的问题
数据库分库分表——解决数据量大的问题
死锁产生原因是什么?
多个事务在同一资源上相互占用,并请求锁定对方占用资源,从而导致恶性循环的现象 InnoDB 目前处理方法:将持有最少行级排他锁的事务进行回滚。
什么是mysql的锁机制,以及什么是死锁,以及发生死锁的场景?
MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则。最显著的特点是不同的存储引擎支持不同的锁机制,InnoDB支持行锁和表锁,MyISAM支持表锁。
表锁就是把整张表锁起来,特点是加锁快,开销小,不会出现死锁,锁粒度大,发生锁冲突的概率高,并发相对较低。
行锁就是以行为单位把数据锁起来,特点是加锁慢,开销大,会出现死锁,锁粒度小,发生锁冲突的概率低,并发度也相对表锁较高。
锁等待是指一个事务过程中产生的锁,其他事务需要等待上一个事务释放它的锁,才能占用该资源,如果该事务一直不释放,就需要继续等待下去,直到超过了锁等待时间,会报一个超时错误。
出现死锁的问题并不可怕,解决死锁通常有什么办法?
不要把无关的操作放到事务里,小事务发生冲突的概率较低。
如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样事务就会形成定义良好的查询并且没有死锁。
尽量按照索引去查数据,范围查找增加了锁冲突的可能性。
对于非常容易产生死锁的业务部分,可以尝试升级锁粒度,通过表锁定来减少死锁产生的概率。
引用:https://www.cnblogs.com/xxcn/p/9941365.html
InnoDB的锁
InnoDB的索引与行记录存储在一起,这一点和MyISAM不一样;
InnoDB的聚集索引存储行记录,普通索引存储PK,所以普通索引要查询两次;
记录锁锁定索引记录;
间隙锁锁定间隔,防止间隔中被其他事务插入;
临键锁锁定索引记录+间隔,防止幻读;
锁的类型
全局锁,用来做全库逻辑备份。。Flush tables with read lock 这个是一个全局锁。
表锁,表级锁,lock tables t1 read, t2 write; unlock tables
表锁的语法是 lock tables … read/write
另一类表级的锁是 MDL(metadata lock)(默认会启动)
如果想要拿到表的结构,可以选择等待多长时间,如果等待能拿到的话,最好。拿不到的话,也不会阻塞。
ALTER TABLE tbl_name NOWAIT add column …
ALTER TABLE tbl_name WAIT N add column …
如何实现 MySQL 的读写分离?
其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。
MySQL 主从复制原理的是啥?
主库将变更写入 binlog 日志,然后从库连接到主库之后,从库有一个 IO 线程,将主库的 binlog 日志拷贝到自己本地,写入一个 relay 中继日志中。接着从库中有一个 SQL 线程会从中继日志读取 binlog,然后执行 binlog 日志中的内容,也就是在自己本地再次执行一遍 SQL,这样就可以保证自己跟主库的数据是一样的。
这里有一个非常重要的一点,就是从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行 SQL 的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。
而且这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。 所以 MySQL 实际上在这一块有两个机制,一个是半同步复制,用来解决主库数据丢失问题;一个是并行复制,用来解决主从同步延时问题。
这个所谓半同步复制,也叫 semi-sync 复制,指的就是主库写入 binlog 日志之后,就会将强制此时立即将数据同步到从库,从库将日志写入自己本地的 relay log 之后,接着会返回一个 ack 给主库,主库接收到至少一个从库的 ack 之后才会认为写操作完成了。 所谓并行复制,指的是从库开启多个线程,并行读取 relay log 中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。
MySQL 主从同步延时问题
以前线上确实处理过因为主从同步延时问题而导致的线上的 bug,属于小型的生产事故。 是这个么场景。有个同学是这样写代码逻辑的。先插入一条数据,再把它查出来,然后更新这条数据。在生产环境高峰期,写并发达到了 2000/s,这个时候,主从复制延时大概是在小几十毫秒。线上会发现,每天总有那么一些数据,我们期望更新一些重要的数据状态,但在高峰期时候却没更新。用户跟客服反馈,而客服就会反馈给我们。
我们通过 MySQL 命令:
show status
查看 Seconds_Behind_Master,可以看到从库复制主库的数据落后了几 ms。 一般来说,如果主从延迟较为严重,有以下解决方案:
分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。【此时是主库的执行性能可能不好】
打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。
重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。
如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询设置直连主库。不推荐这种方法,你要是这么搞,读写分离的意义就丧失了。
一个大表(数据有1000w)该怎么加索引?
存在锁住表的可能哦。
建一个一样的 tpm表,给 tmp表加索引,然后两个表rename,给主表加索引,再RENAME回来,把TMP表的 新增数据在主表中没有的给INSERT回主表。
如何修改worker 进程数?
在 conf/nginx.conf 文件中修改 worker_processes 数字即可。
Nginx 基本命令有哪些?
./nginx -s stop
./nginx -s quit
./nginx -s reload
./nginx
./nginx -t
Nginx进程模型
Nginx 抢占机制
怎么修改nginx打印的日志格式?
Nginx 服务器提供了一个 HttpLogModule 模块,可以通过它来设置日志的输出格式。
Nginx 日志格式中,有很多参数,总结如下:
参数 说明 示例
$remote_addr 客户端地址 14.116.133.170
$remote_user 客户端用户名称 –
$time_local 访问时间和时区 03/Mar/2019:16:43:53 +0800
$request 请求的URI和HTTP协议 “GET /city/static/js/illegals/vehicle-search.js HTTP/1.1”
$http_host 请求地址,即浏览器中你输入的地址(IP或域名) www.super.com 192.168.118.15
$status HTTP请求状态 200
$upstream_status upstream状态 200
$body_bytes_sent 发送给客户端文件内容大小 1547
$http_referer url跳转来源 https://www.baidu.com/
$http_user_agent 用户终端浏览器等信息 Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0
$ssl_protocol SSL协议版本 TLSv1
$ssl_cipher 交换数据中的算法 RC4-SHA
$upstream_addr 后台upstream的地址,即真正提供服务的主机地址 192.168.118.16:8080
$request_time 整个请求的总时间 0.205
$upstream_response_time 请求过程中,upstream响应时间 0.002
通过更改这里,可以达到想要的结果,如果想打印post请求传参,可以通过安装第三方组件来完成。
简单的说下nginx优化的点
1.使用epoll模型
2.提高进程数、打开连接数
3.启用gzip压缩传输
4.为静态文件启用缓存
5.Timeouts设置
6.防盗链设置
7.非法脚本
8.防止ddoc攻击
9.禁止恶意域名解析
10.隐藏版本信息
keepalived是什么?
keepalived最初是专为LVS负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了实现高可用的VRRP功能。keepalived除了能够管理LVS软件外,还能支持其他服务的高可用解决方案。
keepalived通过VRRP协议实现高可用功能的。VRRP(Virtual Router Redundancy Protocol)虚拟路由冗余协议。VRRP出现的目的就是为了解决静态路由单点故障问题,它能保证当个别节点宕机时,整个网络可以不间断地运行。
keepalived高可用故障转移原理是什么?
keepalived高可用服务之间的故障转移,是通过VRRP来实现的。在keepalived服务工作时,主Master节点会不断地向备节点发送(多播的方式)心跳消息,用来告诉备Backup节点自己还活着。
当主节点发生故障时,无法给备节点发送心跳消息,如果备节点无法继续检测到来自主节点的心跳。就会调用自身的接管程序,接管主节点的IP资源和服务。当主节点恢复时,备节点又会释放主节点故障时自身接管的IP资源和服务,恢复到原来的备用角色。
缓存不一致怎么办?
问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。
ROB的原理是什么?
copy on write。父进程会fork一个子进程,父进程和子进程共享内存空间。父进程继续提供读写服务,写脏的页面数据会继续和子进程区分开来。
你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
除了五种常见数据类型, 还有其他的数据类型么?
string 可以用来计数,缓存,分布式锁
hash 可以用来保存用户的一些属性信息,用户的详情页
list 可以用来做队列,可以用来做栈,可以用来做数据, 可以维护一个评论列表。lrange区间操作。
set 可以用来做 交集 并集 差集,微博抽奖,随机事件问题。无序、去重
sorted set 可以用来做排行榜,带权重的队列。
bitmap 用来保存用户的登录信息,可以查询最近几个月的登录情况:bitop 可以用来做and or意味着有更多的选择。
pub/sub 发布订阅
stream
hyperloglog 布隆过滤器器(滑动窗口)
stream的备注:支持多播的可持久化的消息队列。 Stream 的消费模型借鉴了 Kafka 的消费分组的概念,它弥补了 Redis Pub/Sub 不能持久化消息的缺陷。但是它又不同于 kafka,Kafka 的消息可以分 partition,而 Stream 不行。如果非要分 parition 的话,得在客户端做,提供不同的 Stream 名称,对消息进行 hash 取模来选择往哪个 Stream 里塞。
分布式锁如何实现?
单机采用set nx 就可以了。最好设计过期时间,防止锁不释放的情况。
// 获取锁 // NX是指如果key不存在就成功,key存在返回false,PX可以指定过期时间
SET anyLock unique_value NX PX 30000
// 释放锁:通过执行一段lua脚本
// 释放锁涉及到两条指令,这两条指令不是原子性的
// 需要用到redis的lua脚本支持特性,redis执行lua脚本是原子性的
if redis.call(“get”,KEYS[1]) == ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end
因为redis大部分是单机部署,如果master加锁成功之后,突然宕机了怎么办呢? 会出现锁丢失的情况。 这个时候就得需要redlock锁了。只要大多数(半数以上的机器)加锁成功了,就算是加锁成功了。只要加锁时间小于当前时间,就是加锁成功了。其他的节点就得需要不断的来轮询了。
简述Redlock算法
在Redis的分布式环境中,我们假设有N个Redis master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。之前我们已经描述了在Redis单实例下怎么安全地获取和释放锁。我们确保将在每(N)个实例上使用此方法获取和释放锁。在这个样例中,我们假设有5个Redis master节点,这是一个比较合理的设置,所以我们需要在5台机器上面或者5台虚拟机上面运行这些实例,这样保证他们不会同时都宕掉。
为了取到锁,客户端应该执行以下操作:
获取当前Unix时间,以毫秒为单位。
依次尝试从N个实例,使用相同的key和随机值获取锁。在步骤2,当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis实例。
客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。
主从同步是怎么实现的?
因为单机QPS是有上限的。
当启动一个slave的时候,他发送psync给master。如果是首次连接master,那么master会启动一个线程,进行全量的rdb快照,然后发送给slave。然后把新的请求写到缓存里面,然后slave会执行rdb文件,然后写到自己的本地。然后再读取master里面新增的请求。
说一说你对redis集群架构的理解
redis cluster 着眼于可扩展。当单个redis不足时,使用cluster进行分片存储。。
来看 Redis 的高可用。Redis 支持主从同步,提供 Cluster 集群部署模式,通过 Sentine l哨兵来监控 Redis 主服务器的状态。当主挂掉时,在从节点中根据一定策略选出新主,并调整其他从 slaveof 到新主。 选主的策略简单来说有三个:
slave 的 priority 设置的越低,优先级越高;
同等情况下,slave 复制的数据越多优先级越高;
相同的条件下 runid 越小越容易被选中。 在 Redis 集群中,sentinel 也会进行多实例部署,sentinel 之间通过 Raft 协议来保证自身的高可用。
Redis Cluster 使用分片机制,在内部分为 16384 个 slot 插槽,分布在所有 master 节点上,每个 master 节点负责一部分 slot。数据操作时按 key 做 CRC16 来计算在哪个 slot,由哪个 master 进行处理。数据的冗余是通过 slave 节点来保障。
redis的Sentinal哨兵模式是什么?
就是高可用,master宕机之后,会将slave提升为master继续提供服务。
哨兵组件的作用:
集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
说一说数据类型的底层实现
string -> int string
hash -> ziplist hashable
zset -> ziplist skiplist
set-> intset hash
list -> ziplist linkedlist
https://mp.weixin.qq.com/s/GLqZf-0sLQ7nnJ8Xb9oVZQ
redis的事件模型是什么?
select epoll
select 是每次去拿文件描述符去查,看哪些符合条件,然后去执行
epoll 是采用事件监听的形式,只会执行符合条件的事件。
keys读取命令为什么禁用?
key会堵塞。所以,最好还是使用scan来进行查询。scan可以分批查。
redis禁用危险命令有哪些?
keys *
虽然其模糊匹配功能使用非常方便也很强大,在小数据量情况下使用没什么问题,数据量大会导致 Redis 锁住及 CPU 飙升,在生产环境建议禁用或者重命名!
flushdb
删除 Redis 中当前所在数据库中的所有记录,并且此命令从不会执行失败
flushall
删除 Redis 中所有数据库中的所有记录,不只是当前所在数据库,并且此命令从不会执行失败。
config
客户端可修改 Redis 配置。
Redis连接时的connect与pconnect的区别是什么?
connect:脚本结束之后连接就释放了。
pconnect:脚本结束之后连接不释放,连接保持在php-fpm进程中。每个php-fpm进程占用一个连接,当php-fpm进程结束时会释放掉 ;
所以使用pconnect代替connect,可以减少频繁建立redis连接的消耗。
lua 脚本的作用是什么?
Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为EVAL。
相比Redis事务来说,Lua脚本有以下优点
减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求,而脚本只需一次即可,减少网络传输;
原子操作:Redis 将整个脚本作为一个原子执行,无需担心并发,也就无需事务;
复用:脚本会永久保存 Redis 中,其他客户端可继续使用
分布式限流最关键的是要将限流服务做成原子化,而解决方案可以使用redis+lua或者nginx+lua技术进行实现,通过这两种技术可以实现的高并发和高性能。
首先我们来使用redis+lua实现时间窗内某个接口的请求数限流,实现了该功能后可以改造为限流总并发/请求数和限制总资源数。Lua本身就是一种编程语言,也可以使用它实现复杂的令牌桶或漏桶算法。
因操作是在一个lua脚本中(相当于原子操作),又因Redis是单线程模型,因此是线程安全的。
扩展:https://www.runoob.com/redis/redis-scripting.html
redis队列解决抢购高并发?
在程序跟数据库之前呢我们可以利用redis队列做一个缓冲机制,让所有用户的请求进行排队,禀行先进先出的原则(redis中的lpush和rpop),
lpush程序是把用户的请求压入redis队列,然后用rpop做一个守护进程来取队列中的数据,按规定的抢购名额写好,
把所有抢购成功的用户写入redis并且生成订单,在lpush程序中查看中奖的用户并且给用户及时提醒抢购结果!
集合命令的实现方法
命令 intset 编码的实现方法 hashtable 编码的实现方法
SADD 调用 intsetAdd 函数,将所有新元素添加到整数集合里面 调用 dictAdd,以新元素为键,NULL 为值,将键值对添加到字典里面
SCARD 调用 intsetLen 函数,返回整数集合所包含的元素数量,这个数量就是集合对象所包含的元素数量 调用 dictSize 函数,返回字典所包含的键值对数量,这个数量就是集合对象所包含的元素数量
SISMEMBER 调用 intsetFind 函数,在整数集合中查找给定的元素,如果找到了元素存在于集合,没找到则说明元素不存在集合 调用 dictFind 函数,在字典的键中查找给定的元素,如果找到了说明元素存在于集合,没找到则说明元素不存在于集合
SMEMBERS 遍历整个整数集合,调用 inisetGet 函数返回集合元素 遍历整个字典,使用 dictGetKey 函数返回字典的键作为集合元素
SRANDMEMBER 调用 intsetRandom 函数,从整数集合中随机返回一个元素 调用 dictGetRandomKey 函数,从字典中随机返回一个字典键
SPOP 调用 intsetRandom 函数,从整数集合中随机取出一个元素,再将这个随机元素返回给客户端之后,调用 intsetRemove 函数,将随机元素从整数集合中删除掉 调用 dictGetRandomKey 函数,从字典中随机取出一个字典键,在将这个随机字典键的值返回给客户端之后,调用 dictDelete 函数,从字典中删除随机字典键所对应的键值对
SREM 调用 intsetRemove 函数,从整数集合中删除所有给定的元素 调用 dictDelete 函数,从字典中删除所有键为给定元素的键值对
有序集合命令的实现方法
命令 ziplist 编码的实现方法 zset 编码的实现方法
ZADD 调用 ziplistInsert 函数,将成员和分值作为两个节点分别插入到压缩列表 先调用 zslInsert 函数,将新元素添加到跳跃表,然后调用 dictAdd 函数,将新元素关联到字典
ZCARD 调用 ziplistLen 函数,获得压缩列表包含节点的数量,将这个数量除以2得出集合元素的数量 访问跳跃表数据结构的 length 属性,直接访问集合元素的数量
ZCOUND 遍历压缩列表,统计分值在给定范围内的节点的数量 遍历跳跃表,统计分值在给定范围内的节点的数量
ZRANGE 从表头向表尾遍历压缩列表,返回给定索引范围内的所有元素 从表头向表尾遍历跳跃表,返回给定索引范围内的所有元素
ZREVRANGE 表尾向表头遍历压缩列表,返回给定索引范围内的所有元素 从表尾向表头遍历跳跃表,返回给定索引范围的所有元素
ZRANK 从表头向表尾遍历压缩列表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后,沿途节点的数量就是该成员所对应元素的排名 从表头向表尾遍历跳跃表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后,沿途节点的数量就是该成员所对应元素的排名
ZREVRANK 从表尾向表头遍历压缩列表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后,沿途节点的数量就是该成员所对应元素的排名 从表尾向表头遍历跳跃表,查找给定的成员,沿途纪录经过节点的数量,当找到给定成员之后,沿途节点的数量就是该成员所对应元素的排名
ZREM 遍历压缩列表,删除所有包含给定成员的节点,以及被删除成员节点旁边的分值节点 遍历跳跃表,删除所有包含了给定成员的跳跃表节点。并在字典中解除被删除元素的成员和分值关联
ZSCORE 遍历压缩列表,查找包含了给定成员的节点,然后取出成员节点旁边的分值节点保存的元素分值
脑裂问题是啥?
当客户端无法取到锁时,应该在一个随机延迟后重试,防止多个客户端在同时抢夺同一资源的锁(这样会导致脑裂,没有人会取到锁)。同样,客户端取得大部分Redis实例锁所花费的时间越短,脑裂出现的概率就会越低(必要的重试),所以,理想情况一下,客户端应该同时(并发地)向所有Redis发送SET命令。
需要强调,当客户端从大多数Redis实例获取锁失败时,应该尽快地释放(部分)已经成功取到的锁,这样其他的客户端就不必非得等到锁过完“有效时间”才能取到(然而,如果已经存在网络分裂,客户端已经无法和Redis实例通信,此时就只能等待key的自动释放了,等于被惩罚了)。
原子执行的命令(multi + exec)
MULTI
OK
INCR foo
QUEUED
INCR bar
QUEUED
EXEC
- (integer) 1
- (integer) 1
redis set 和get为什么这么快?
就是hash计算
redis如何实现ACID?
redis 可以实现原子性,一致性,隔离性。但是不能保证持久性。
原子性,multi会先把命令放到队列里面,然后exec执行命令。
#开启事务
127.0.0.1:6379> MULTI
OK
#将a:stock减1,
127.0.0.1:6379> DECR a:stock
QUEUED
#将b:stock减1
127.0.0.1:6379> DECR b:stock
QUEUED
#实际执行事务
127.0.0.1:6379> EXEC
- (integer) 4
- (integer) 9
命令入队时就报错,会放弃事务执行,保证原子性;
命令入队时没报错,实际执行时报错,不保证原子性;
EXEC 命令执行时实例故障,如果开启了 AOF 日志,可以保证原子性。redis-check-aof检查aof文件,可以把已执行的事务操作从aof文件中删除。
一致性
隔离性 watch机制。
跳跃表的时间复杂度是什么?
一般是O(logn) 最差是 O(n)
一致性哈希是什么?
一致性哈希用于解决分布式缓存系统中的数据选择节点存储问题和数据选择节点读取问题以及在增删节点后减少数据缓存的消失范畴,防止雪崩的发生。
类似于mysql的分库分表策略,优点是可以动态的添加删除redis节点,不会造成缓存雪崩,可以同时既当缓存,又当数据库使用。
哈希槽是什么?
Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念。
哈希槽是在redis cluster集群方案中采用的,redis cluster集群没有采用一致性哈希方案,而是采用数据分片中的哈希槽来进行数据存储与读取的。
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽。这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。
使用哈希槽的好处就在于可以方便的添加或移除节点。
当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;
在这一点上,我们以后新增或移除节点的时候不用先停掉所有的 redis 服务。
扩展:https://segmentfault.com/a/1190000022718948
集群情况下,节点较少时数据分布不均匀怎么办?
对于分布式系统来说,整个集群的存储容量和处理能力,往往取决于集群中容量最大或响应最慢的节点。因此在前期进行系统设计和容量规划时,应尽可能保证数据均衡。但是,在生产环境的业务系统中,由于各方面的原因,数据倾斜的现象还是比较常见的。Redis Cluster也不例外,究其原因主要包括两个:一个是不同分片间key数量不均匀,另一个是某分片存在bigkey;接下来我们看看,在腾讯云数据库redis中,如何及时发现和解决分片数据不均匀的问题。
对于分片间key数量不均匀,导致数据倾斜问题,可考虑以下方案(可能性小):
解决方案:
(1)垂直扩容:扩容单分片内存容量(不推荐)
(2)水平扩容:扩容分片数,以把key打散到不同分片(推荐)
对于某分片存在bigkey,导致数据倾斜问题,可考虑以下方案(可能性大):
(1)垂直扩容:扩容单分片内存容量(不推荐)
(2)对bigkey进行改造,拆分成多个key打散(推荐)
Linux 基础
查看系统信息、内存信息、磁盘信息、负载信息、路由信息、端口信息、进程、登录用户、关机、重启、系统时间、用户管理、文件权限、压缩解压
Linux 目录结构
/
├── bin #存放二进制可执行文件,常用命令一般都在这里
├── boot #存放用于系统引导时使用的各种文件
├── dev #用于存放设备文件
├── etc #存放系统管理和配置文件
├── home #存放所有用户文件的根目录
├── lib #存放着和系统运行相关的库文件
├── media #linux 系统会自动识别一些设备,当识别后,linux 会把识别的设备挂载到这个目录下
├── mnt #用户临时挂载其他的文件系统
├── opt #额外安装的可选应用程序包所放置的位置
├── proc #虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息
├── root #超级用户的主目录
├── run #是一个临时文件系统,存储系统启动以来的信息
├── sbin #存放二进制可执行文件,只有 root 才能访问
├── srv #该目录存放一些服务启动之后需要提取的数据
├── sys #存放内核相关文件
├── tmp #用于存放各种临时文件,是公用的临时文件存储点
├── usr #用于存放系统应用程序
└── var #用于存放运行时需要改变数据的文件,比如服务的日志文件
文件描述符
文件描述符是一个非负的索引,一般从3开始(0,1,2均被使用),指向内核中的“文件记录表”,内核为进程要使用的文件维护一个“文件记录表”。
• 进程需要打开或新建文件时,内核向进程返回一个文件描述符;
• 进程需要读写文件时,也需要将文件描述符作为参数传递给函数;
• Linux下所有对设备和文件的操作都由文件描述符完成。
命令与文件查找有哪些命令?
which-寻找可执行文件
[root@localhost ~]# which php
/usr/bin/php
whereis-特定目录寻找
[root@localhost ~]# whereis php
php: /usr/bin/php /usr/lib64/php /etc/php.d /etc/php.ini /usr/include/php /usr/share/php /usr/share/man/man1/php.1.gz
find-直接搜索硬盘
[root@localhost ~]# find / -name php-fpm
/run/php-fpm
/etc/sysconfig/php-fpm
/etc/logrotate.d/php-fpm
/var/log/php-fpm
/usr/sbin/php-fpm
数据流怎么分类?
数据流分为三类:标准输入(stdin)、标准输出(stdout)、标准错误输出(stderr)
/dev/null:是一个特殊的设备文件,这个文件接收到的任何数据都会被丢弃。因此,null 这个设备通常也被成为位桶(bit bucket)或黑洞
计划任务
代表意义 分钟 小时 日期 月份 星期 指令
数字范围 0-59 0-23 1-31 1-12 0-7 command
特殊符号 意义 示例
- 表示任何时刻 *
, 表示分隔时段 0 3,6 * * * command(3:00与6:00)
- 表示一段时间范围 20 8-12 * * * command(8:20~12:20)
/n 每隔 n 段时间 */5 * * * * command(每五分钟进行一次)
I/O模型
同步阻塞IO:在内核等待数据和将数据复制到进程地址空间的两个过程,除了等待啥也不做。及时返回数据,无延迟。
同步非阻塞IO:在内核等待数据的阶段,进程可以轮询(瞅瞅它准备好数据了没),后一阶段等待。此外,在轮询之外的时间可以干其他活儿了,但是这样会拉长此进程的时延(也许人家在你轮询之前准备好了)。
异步IO:异步模式时被动接收消息,如通过回调、通知、状态等方式被动获取;不是顺序执行。异步非阻塞IO中,用户进程进行系统调用后,无论内核是否准备好都会返回响应,进程可以去做别的事情,内核复制好数据之后会通知进程。
信号驱动IO:建立SIGIO信号处理函数,数据准备好后进程会收到SIGIO信号。可以在信号处理函数中调用IO操作函数处理数据。
IO多路复用:
多路:多个连接,复用:一个或少量线程。即使用一个或少量的线程去处理多个连接。
不停地查看多个任务的完成状态,只要有任何一个任务完成了,就去处理它。
三种模式:
select:轮询,任何一个进程的数据准备好了就来通知一声,限制只能同时监视1024个接口;
poll:和select一样,不过去掉了1024的限制;
epoll:回调,不用去轮询了。
同步与异步:主要关注消息通信机制。
同步就是在发出一个调用之后,调用没有得到结果之前该调用不返回。即同步是主动等待消息。
异步调用不会等待结果而是立即返回,然后等待被调用者使用消息、通知或者回调函数来通知调用者。即异步是被动接收消息。
阻塞与非阻塞:主要关注程序在等待时的状态。
阻塞是指程序在等待结果的时候被挂起,不能去完成别的任务【浪费时间】;
非阻塞是指程序在等待的过程中可以做别的事情【需要切换开销】。
.Linux常用指令有哪些?
• ls:列出文件或者目录的信息;
• cd:切换路径(绝对/相对路径);
• mkdir:新建目录;
• rmdir:删除目录,目录必须为空;
• touch:更新文件时间或新建文件;
• cp:复制文件,若源文件有两个及以上,目标路径一定要是目录;
• mv:移动文件;• rm:删除文件;
• top:在进程运行过程中对其显示方式进行控制。
虚拟地址和物理地址是什么?
虚拟内存是为了将物理内存扩充为更大的逻辑内存。为了更方便地管理内存,操作系统将内存抽象成地址空间,每个程序有自己对应地地址空间。地址空间被分为很多块,一块被称为一页。虚拟地址空间的页映射到物理地址空间,不用连续映射也不用全部映射。当引用的页没有映射到物理内存时,引发缺页中断,将其调入再重新执行命令即可。这使得一个程序可以在不完全装入内存地情况下执行。
内存管理单元(MMU)维护页表,保存程序虚拟地址空间与物理地址空间的映射关系。
虚拟地址 = 页面号 + 偏移量。
vim使用方式?
操作模式:normal、insert、command、visual、replace
翻页与移动:
:向下移动一页 (相当于:ctrl + f)
:向下移动半页
:向上移动一页
:向上移动半页
h、j、k、l:←、↓、↑、→
nh:向左移动 n 个字符(四个方向均可)
^:移动到行首
: 移 动 到 行 尾 n G : 移 动 到 指 定 行 数 g g : 移 动 到 文 档 第 一 行 , 相 当 于 1 G G : 移 动 到 文 档 最 后 一 行 查 找 与 替 换 : / w o r d : 输 入 / 会 进 入 c o m m a n d 模 式 , 在 输 入 关 键 字 回 车 进 行 搜 索 ? w o r d : / 是 向 光 标 以 后 搜 索 , ? 是 向 前 搜 索 n : 根 据 搜 索 方 向 定 位 到 下 一 个 匹 配 目 标 N : 与 n 相 反 方 向 定 位 匹 配 目 标 : n 1 , n 2 s / w o r d 1 / w o r d 2 / g : n 1 , n 2 表 示 数 字 , 替 换 n 1 行 到 n 2 行 的 单 词 : 1 , :移动到行尾 nG:移动到指定行数 gg:移动到文档第一行,相当于 1G G:移动到文档最后一行 查找与替换: /word:输入/会进入 command 模式,在输入关键字回车进行搜索 ?word:/是向光标以后搜索,?是向前搜索 n:根据搜索方向定位到下一个匹配目标 N:与n相反方向定位匹配目标 :n1,n2s/word1/word2/g:n1,n2 表示数字,替换n1行到n2行的单词 :1, :移动到行尾nG:移动到指定行数gg:移动到文档第一行,相当于1GG:移动到文档最后一行查找与替换:/word:输入/会进入command模式,在输入关键字回车进行搜索?word:/是向光标以后搜索,?是向前搜索n:根据搜索方向定位到下一个匹配目标N:与n相反方向定位匹配目标:n1,n2s/word1/word2/g:n1,n2表示数字,替换n1行到n2行的单词:1,s/word1/word2/g:全文替换,也可以写成:%s/word1/word2/g
:1,$s/word1/word2/gc:全文替换,并出现确认提示
linux进程间通信(IPC)有什么方式?
通信方式:信号量、消息队列、共享内存、信号、管道、套接字
信号机制是什么?
信号是操作系统中进程间通讯的一种有限制的方式,是一种异步的通知机制,用来提醒进程一个事件已经发送
SIGHUP:控制台操作
SIGINT:终止进程,Ctrl + C
SIGKILL:终止进程,kill -9
SIGSTOP:停止进程的执行
SIGCONT:恢复进程的执行
多线程和多进程的区别是什么?
参考答案:
http://www.cnblogs.com/kaituorensheng/p/3603057.html
进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序是死的(静态的),进程是活的(动态的)。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身;所有由你启动的进程都是用户进程。进程是操作系统进行资源分配的单位。在Windows下,进程又被细化为线程,也就是一个进程下有多个能独立运行的更小的单位。
硬链接与软链接的区别是什么?
每个文件都是创建了一个指针指向inode(代表物理硬盘的一个区块),可以通过ls -i查看。
硬链接是创建另一个文件,也通过创建指针指向inode。当你删除一个文件/硬链接时,它会删除一个到底层inode的指针。当inode的所有指针都被删除时,才会真正删除文件。
软连接是另外一种类型的文件,保存的是它指向文件的全路径,访问时会替换成绝对路径
如何查看某个进程中的线程?
ps -T -p
查看某个文件夹中每个文件夹的大小
du --max-depth=1 -h
CPU负载的含义
一段时间内CPU正在处理和等待处理的进程总数与CPU最大处理进程数的比例。对于多核CPU来说最大LOAD是最大的核心数量。
使用top和top -Hp查找到CPU占用比较大的线程
怎么查看Linux服务器的负载,及判断哪些操作引起的负载过高?
如何统计日志文件中访问次数最多的十个ip地址?
在当前目录下,如何查找包含keyword文件?
docker了解么,说下你在工作中有没有使用?
cgroup在linux的具体实现?
DNS是什么,怎么工作的?
是一个将域名和IP地址相互映射的分布式数据库。
解析过程(分级解析):
根域名 — 顶级域名 — 二级域名
步骤(递归查询本地服务器,迭代查询其他远程服务器):
看看DNS缓存里有没有,有的话直接返回;
使用UDP向DNS服务器发送查询消息;
接收返回的响应消息;
传输协议:
除超过512字节和主从DNS服务器的区域传送外,都是用UDP协议。
为什么使用UDP:因为快啊!只需要一个请求一个应答就够了,而TCP需要三次握手,请求与应答、四次挥手。如果多几次查询,每次都要握手挥手的时间开销太大了。并且DNS查询的数据都很小。
为什么区域传送使用TCP:因为可靠啊!从主DNS服务器上复制内容需要可靠,并且同步的数据可能超过512字节。
TCP如何实现可靠交付?
序列号:只确认最后一个有序到达的数据包,保证有序;
校验和:每个数据包保持一个端到端的校验和,接收方收到之后检查数据在传输过程中有没有改变,若发生了改变则丢弃;
流量控制:保证接收方缓冲区足够接收数据,防止丢失;
拥塞控制:降低网络拥塞程度,防止数据包丢失;
停止等待:发送一个数据包之后暂停发送,等到接收到对方的确认后在发送下一个数据包;若接收方接收到重复数据包则丢弃,但仍需返回确认;
超时重传:若超时未接受到对方的确认,立即重传数据包。
TCP连接的三次握手讲一下
双方都确认自己和对方的收发能力是正常的,需要的最少握手次数。用不着4次,3次就够了。
此外,若使用2次握手,当客户端的失效连接请求到达服务器,服务器会误打开连接,浪费资源。
若第三次握手失败,服务器会关闭连接,防止SYN洪泛攻击。
为什么建立连接是三次握手,关闭连接确是四次挥手?
建立连接时,服务器处于listen状态,当收到客户端的SYN请求时,会将响应的SYN、ACK放在同一个响应报文里发送给客户端。
关闭连接时,服务器收到客户端的FIN请求,表示客户端不再发送数据,但此时还可以接收数据。因此,服务器可以先响应ACK给客户端,表示收到了请求,然后将服务器端还没发送完的数据全部发送给客户端之后再发送FIN表示不再发送数据。服务器将ACK和FIN分开发送,导致多了一次数据传输。
为什么客户端最后在TIME_WAIT还要等待2MSL(最大报文存活时间)?
保证服务器收到客户端的最后一个ACK,若这个ACK丢失,服务器会重发一次FIN+ACK,这时客户端还没有关闭连接,就能收到重发的请求并给出响应。同时重启2MSL计时器。
客户端发送完最后一个ACK后,本连接持续时间内的所有报文段都会从网络中消失,新的连接中就不会收到已经关闭的旧连接中的报文。
TCP流量控制是什么?
流量控制是为了调整发送方的发送速率,使得接收方来得及接收。
接收方的确认报文中有一个窗口字段,用来控制发送方的窗口大小,从而控制发送速率。
TCP拥塞控制是什么?
拥塞控制是为了降低整个网络的拥塞程度。当网络出现拥塞时,分组丢失引发重传,继而加重拥塞程度,因此需要控制。发送方维护一个叫做拥塞窗口的状态变量cwnd,实际决定发送数据量的还是发送窗口。
慢启动 & 拥塞避免:
发送最初执行慢启动,cwnd = 1,发送方只能发送一个报文段。发送方每次收到ACK后将cwnd加倍。
为避免成倍增加的cwnd使得网络拥塞的可能增加,设置慢启动阈值ssthreash。当cwnd >= ssthresh的时候,进入拥塞避免,每次只能将cwnd的值加一。
若出现超时,则将 ssthresh减半,cwnd=1,重新开始慢启动。
快速重传 & 快速恢复:
在接受方,每次只确认收到的最后一个有序报文段;
在发送方,若收到m2的3次重复ACK,则可以确认m3丢失,此时执行快速重传,即立即重传m3;同时,由于只是丢包而不是网络拥塞,执行快速恢复:ssthresh = cwnd / 2 ,cwnd = ssthresh
HTTP请求headers参数有哪些?
HTTP 协议的 Header 是一块数据区域,分为请求头和响应头两种类型,客户端向服务区发送请求时带的是请求头,而服务器响应客户端数据时带的是响应头。
请求头里主要是客户端的一些基础信息,UA(user-agent)就是其中的一部分,而响应头里是响应数据的一些信息,以及服务器要求客户端如何处理这些响应数据的指令。请求头里面的关键信息如下:
accept
表示当前浏览器可以接受的文件类型,假设这里有 image/webp,表示当前浏览器可以支持 webp 格式的图片,那么当服务器给当前浏览器下发 webp 的图片时,可以更省流量。
accept-encoding
表示当前浏览器可以接受的数据编码,如果服务器吐出的数据不是浏览器可接受的编码,就会产生乱码。
accept-language
表示当前使用的浏览语言。
Cookie
很多和用户相关的信息都存在 Cookie 里,用户在向服务器发送请求数据时会带上。例如,用户在一个网站上登录了一次之后,下次访问时就不用再登录了,就是因为登录成功的 token 放在了 Cookie 中,而且随着每次请求发送给服务器,服务器就知道当前用户已登录。
user-agent
表示浏览器的版本信息。当服务器收到浏览器的这个请求后,会经过一系列处理,返回一个数据包给浏览器,而响应头里就会描述这个数据包的基本信息。
响应头里的关键信息有:
content-encoding
表示返回内容的压缩编码类型,如“Content-Encoding :gzip”表示这次回包是以 gzip 格式压缩编码的,这种压缩格式可以减少流量的消耗。
content-length
表示这次回包的数据大小,如果数据大小不匹配,要当作异常处理。
content-type
表示数据的格式,它是一个 HTML 页面,同时页面的编码格式是 UTF-8,按照这些信息,可以正常地解析出内容。content-type 为不同的值时,浏览器会做不同的操作,如果 content-type 是 application/octet-stream,表示数据是一个二进制流,此时浏览器会走下载文件的逻辑,而不是打开一个页面。
set-cookie
服务器通知浏览器设置一个 Cookie;通过 HTTP 的 Header,可以识别出用户的一些详细信息,方便做更定制化的需求,如果大家想探索自己发出的请求中头里面有些什么,可以这样做:打开 Chrome 浏览器并按“F12”键,唤起 Chrome 开发者工具,选择 network 这个 Tab,浏览器发出的每个请求的详情都会在这里显示。
简述下tcp滑动窗口
冒泡排序是什么,用php实现下?
$arr[$j]) { $temp = $arr[$j - 1]; $arr[$j - 1] = $arr[$j]; $arr[$j] = $temp; } } } return $arr; } printSortArr('mp_sort', 10); 输出结果 原数据:504,480,612,677,613,395,506,129,479,605 算法[mp_sort]数据打印 129 395 479 480 504 506 605 612 613 677 ## 快速排序是什么,用php实现下? 1){ //定义一基准数,再定义一个小于基准数的数组,和一个大于基准数的数组,然后再递归进行快速排序 $k = $arr[0];//定基准数 $x = [];//小于基准数的数组 $y = [];//大于基准数的数组 $size = count($arr); for($i=1;$i<$size;$i++){ if($arr[$i]>$k){ $y[] = $arr[$i]; }elseif($arr[$i]<=$k){ $x[] = $arr[$i]; } } $x = quickSort($x); $y = quickSort($y); return array_merge($x,array($k),$y); }else{ return $arr; } } $arr = [2,78,3,23,532,13,67]; print_r(quickSort($arr)); 选择排序? $arr[$j]) { $min = $j; } } if ($min != $i) { $temp = $arr[$min]; $arr[$min] = $arr[$i]; $arr[$i] = $temp; } } return $arr; } printSortArr('xz_sort', 10); 运行结果 原数据:845,435,918,889,232,62,162,617,729,540 算法[xz_sort]数据打印 62 162 232 435 540 617 729 845 889 918 快速排序有什么优化点? 单向链表反转 无序链表如何查找中位数 判断一个数是不是质数 怎么计算时间复杂度? 递归 1,'name'=>'湖北省','pid'=>0,'son'=>''], ['id'=>2,'name'=>'广东省','pid'=>0,'son'=>''], ['id'=>3,'name'=>'湖南省','pid'=>0,'son'=>''], ['id'=>4,'name'=>'武汉市','pid'=>1,'son'=>''], ['id'=>5,'name'=>'荆州市','pid'=>1,'son'=>''], ['id'=>6,'name'=>'宜昌市','pid'=>1,'son'=>''], ['id'=>7,'name'=>'咸宁市','pid'=>1,'son'=>''], ['id'=>8,'name'=>'仙桃市','pid'=>1,'son'=>''], ['id'=>9,'name'=>'潜江市','pid'=>1,'son'=>''], ['id'=>10,'name'=>'深圳市','pid'=>2,'son'=>''], ['id'=>11,'name'=>'广州市','pid'=>2,'son'=>''], ['id'=>12,'name'=>'珠海','pid'=>2,'son'=>''], ['id'=>13,'name'=>'佛山市','pid'=>2,'son'=>''], ['id'=>14,'name'=>'长沙市','pid'=>3,'son'=>''], ['id'=>15,'name'=>'岳阳市','pid'=>3,'son'=>''], ['id'=>16,'name'=>'株洲市','pid'=>3,'son'=>''], ['id'=>17,'name'=>'衡阳市','pid'=>3,'son'=>''], ]; function recursive($arr,$pid=0){ $tree = []; foreach($arr as $k=>$v){ if($v['pid'] == $pid){ $v['son'] = recursive($arr,$v['id']); $tree[] = $v; } } return $tree; } print_r(recursive($areaList)); 乘法口诀 '一','2'=>'二','3'=>'三','4'=>'四','5'=>'五','6'=>'六','7'=>'七','8'=>'八','9'=>'九', '10'=>'十','12'=>'十二','14'=>'十四','15'=>'十五','16'=>'十六','18'=>'十八','20'=>'二十','21'=>'二十一','24'=>'二十四','25'=>'二十五','27'=>'二十七','28'=>'二十八','30'=>'三十', '31'=>'三十一', '32'=>'三十二','35'=>'三十五','36'=>'三十六','40'=>'四十','42'=>'四十二','45'=>'四十五','48'=>'四十八','49'=>'四十九','52'=>'五十二','54'=>'五十四','56'=>'五十六', '63'=>'六十三','64'=>'六十四','72'=>'七十二','81'=>'八十一' ]; for($i=1;$i<=9;$i++){ for($j=1;$j<=$i;$j++){ $num = $j*$i; if($j==$i){ echo $word[$j].$word[$i].'得'.$word[$num]."\n"; }else{ echo $word[$j].$word[$i].'得'.$word[$num].' '; } unset($num); } } 二分查找 $arr2[$j]){ $j++; }else{ $sameArr[] = $arr1[$i]; $i++; $j++; } } if(!empty($sameArr)){ $sameArr = array_unique($sameArr); } return $sameArr; } $result = findCommon($arr1,$arr2); print_r($result); 寻最小的n个数 $arr[$pos]) { //echo '$i:'.$i.'--$post:'.$pos.PHP_EOL; $pos = $i; } } return $pos; } $array = [1, 100, 20, 22, 33, 44, 55, 66, 23, 79, 18, 20, 11, 9, 129, 399, 145,88,56,84,12,17]; //$min_array = get_min_array($array, 10); //print_r($min_array); $arr = [130,2,4,100,89,8,99]; $num = get_max_pos($arr); echo $num; print_r($arr[$num]); 抽奖 ```php 1,'name'=>'特等奖','v'=>1], ['id'=>2,'name'=>'二等奖','v'=>3], ['id'=>3,'name'=>'三等奖','v'=>5], ['id'=>4,'name'=>'四等奖','v'=>20], ['id'=>5,'name'=>'谢谢参与','v'=>71], ]; function draw($arr) { $result = []; //计算总抽奖池的积分数 $sum = []; foreach($arr as $key=>$value){ $sum[$key] = $value['v']; } $randSum = array_sum($sum); $randNum = mt_rand(1,$randSum); error_log('随机数数:'.$randNum); $count = count($arr); $s = 0; $e = 0; for($i=0;$i<$count;$i++){ if($i==0){ $s = 0; }else{ $s += $sum[$i-1]; } $e += $sum[$i]; if($randNum>=$s && $randNum<=$e){ $result = $arr[$i]; } } unset($sum); return $result; } print_r(draw($arr)); ``` 数组反转 $arr[$i]){ $min = $i; } } echo "数组中最小值的索引为:".$min.',最小值为:'.$arr[$min]; 背包算法 电梯算法 股票算法 找一个无序数组的中位数 参考 PHP 冒泡排序 php实现快速排序 PHP实现各种经典算法 PHP常见算法-面试篇 php实现二分查找法 用 PHP 的方式实现的各类算法合集 扩展学习:https://github.com/m9rco/algorithm-php ## 什么是进程和线程? 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位 ## 进程和线程的关系是什么? 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量 处理机分给线程,即真正在处理机上运行的是线程 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步 ## 进程与线程的区别是什么? 资源:进程是资源分配的基本单位,但线程不拥有资源,线程能访问其所属进程的资源; 调度:线程是独立调度的基本单位,同一进程中线程的切换不会引起进程的切换,而不同进程间线程的切换会引起进程的切换; 系统开销:进程的新建和撤销时,系统需要为其分配和回收资源,如内存空间和I/O设备等,开销远大于线程的新建和撤销。进程的切换需要当前进程CPU环境的保护和新进程环境的设置,而线程的切换只需要保存和设置少量的寄存器内容,开销很小。因此,线程的系统开销远低于进程。 通信:线程可以直接读写进程数据进行通信,但进程需要IPC(进程间通信技术)进行通信(管道、消息队列、共享内存)。 ## 进程的状态,各个状态之间如何切换? 就绪:进程已处于准备好运行的状态,即进程已分配到除CPU外的所有必要资源后,只要再获得CPU,便可立即执行 执行:进程已经获得CPU,程序正在执行状态 阻塞:正在执行的进程由于发生某事件(如I/O请求、申请缓冲区失败等)暂时无法继续执行的状态 怎么选择多进程还是多线程? • CPU密集型:偏重于计算,频繁使用CPU,适合多进程。如机器学习。 • I/O密集型:经常输入输出,适合多线程。如爬虫。 ## 为什么进程上下文切换比线程上下文切换代价高? 进程切换分两步: 切换页目录以使用新的地址空间 切换内核栈和硬件上下文 对于linux来说,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的 切换的性能消耗: 线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出 另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor's Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。 ## 什么是进程(线程)同步? 首先需要明白广义上的“同步”,所谓同步,即在一定条件下应当发生什么事件 如果只有一个进程,那么进程同步指的是这个进程每次运行时的过程是一样的。而现在的操作系统在多道程序设计的背景下,进程基本上是异步的,即每次运行的过程都是不一样的。但是结果可能是一样的。 如果有两个进程A和B(一般是协作关系),那么进程同步的意思是说,两个进程的运行过程是相互制约的。相反,异步就是说两个进程各走各的,不会考虑另一个进程的状态。可想而知,两个异步运行的进程如果是协作关系,那么很有可能出现不协调的情况(竞争条件的出现)。 两个以上进程的同步与两个进程的情况类似。 “进程”也可以换成线程,“互斥”只是为了实现进程同步而使用的一种手段。 ## 进程同步的任务和原则是什么? 进程同步的主要任务:是对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地共享资源和相互合作,从而使程序的执行具有可再现性 同步机制遵循的原则: 空闲让进; 忙则等待(保证对临界区的互斥访问); 有限等待(有限代表有限的时间,避免死等); 让权等待,(当进程不能进入自己的临界区时,应该释放处理机,以免陷入忙等状态) ## 协程是什么? 协程是微线程,在一个线程中执行,执行过程中可以随时中断,由用户控制(进程和线程本质上是系统运行)。执行效率高,减少了线程切换和锁开销。 协程失去了标准线程使用多核的能力。多进程+协程,既可以利用多核,又能利用协程的高效率。 实现:asyncio(异步IO,即不用等待其结束就能进行其他操作),在yield处暂停等待指令。 ## 协程相比线程的优势是什么? 协程执行效率高。 协程由用户控制,不需要线程切换开销。与多线程相比,线程数量越多,协程的优势越明显; 协程不需要锁机制。线程中需要用锁保护数据,而协程不需要写变量保护,只需要判断状态就好了。 ## 进程、线程、协程的堆栈区别是什么? 进程:有独立的堆栈,不共享堆也不共享栈;由操作系统调度; 线程:有独立的栈,共享堆而不共享栈;由操作系统调度; 协程:有独立的栈,共享堆而不共享栈;由程序员自己调度。 ## 进程间通信的意义是什么? 数据传输:一个进程需要将它的数据发送给另一个进程; 资源共享:多个进程间共享同样的资源; 通知事件:一个进程需要向另一个或一组进程发消息,通知它们发生了某种事件(如进程终止时要通知父进程)。 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。 ## 进程间高级通信机制如何分类? 共享存储器系统(存储器中划分的共享存储区);实际操作中对应的是“剪贴板”(剪贴板实际上是系统维护管理的一块内存区域)的通信方式,比如举例如下:word进程按下ctrl+c,在ppt进程按下ctrl+v,即完成了word进程和ppt进程之间的通信,复制时将数据放入到剪贴板,粘贴时从剪贴板中取出数据,然后显示在ppt窗口上。 消息传递系统(进程间的数据交换以消息(message)为单位,当今最流行的微内核操作系统中,微内核与服务器之间的通信,无一例外地都采用了消息传递机制。应用举例:邮槽(MailSlot)是基于广播通信体系设计出来的,它采用无连接的不可靠的数据传输。邮槽是一种单向通信机制,创建邮槽的服务器进程读取数据,打开邮槽的客户机进程写入数据。 管道通信系统(管道即:连接读写进程以实现他们之间通信的共享文件(pipe文件,类似先进先出的队列,由一个进程写,另一进程读))。实际操作中,管道分为:匿名管道、命名管道。匿名管道是一个未命名的、单向管道,通过父进程和一个子进程之间传输数据。匿名管道只能实现本地机器上两个进程之间的通信,而不能实现跨网络的通信。命名管道不仅可以在本机上实现两个进程间的通信,还可以跨网络实现两个进程间的通信。 管道:管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的道端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样地,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。 信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段 消息队列:是一个在系统内核中用来保存消 息的队列,它在系统内核中是以消息链表的形式出现的。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点 共享内存:共享内存允许两个或多个进程访问同一个逻辑内存。这一段内存可以被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取读出,从而实现了进程间的通信**。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。共享内存是最快的IPC方式,它是针对其它进程间通信方式运行效率低而专门设计的。它往往与其它通信机制(如信号量**)配合使用,来实现进程间的同步和通信。 套接字:套接字也是一种进程间通信机制,与其它通信机制不同的是,它可用于不同机器间的进程通信。 相关参考:https://www.cnblogs.com/WindSun/p/11441090.html 进程的调度 调度种类 高级调度:(High-Level Scheduling)又称为作业调度,它决定把后备作业调入内存运行 低级调度:(Low-Level Scheduling)又称为进程调度,它决定把就绪队列的某进程获得CPU 中级调度:(Intermediate-Level Scheduling)又称为在虚拟存储器中引入,在内、外存对换区进行进程对换 非抢占式调度与抢占式调度 非抢占式:分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程 抢占式:操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式 调度算法 FIFO或First Come, First Served (FCFS)先来先服务 调度的顺序就是任务到达就绪队列的顺序 公平、简单(FIFO队列)、非抢占、不适合交互式 未考虑任务特性,平均等待时间可以缩短 Shortest Job First (SJF) 最短的作业(CPU区间长度最小)最先调度 SJF可以保证最小的平均等待时间 Shortest Remaining Job First (SRJF) SJF的可抢占版本,比SJF更有优势 SJF(SRJF): 如何知道下一CPU区间大小?根据历史进行预测: 指数平均法 优先权调度 每个任务关联一个优先权,调度优先权最高的任务 注意:优先权太低的任务一直就绪,得不到运行,出现“饥饿”现象 Round-Robin(RR)轮转调度算法 设置一个时间片,按时间片来轮转调度(“轮叫”算法) 优点: 定时有响应,等待时间较短;缺点: 上下文切换次数较多 时间片太大,响应时间太长;吞吐量变小,周转时间变长;当时间片过长时,退化为FCFS 多级队列调度 按照一定的规则建立多个进程队列 不同的队列有固定的优先级(高优先级有抢占权) 不同的队列可以给不同的时间片和采用不同的调度方法 存在问题1:没法区分I/O bound和CPU bound 存在问题2:也存在一定程度的“饥饿”现象 多级反馈队列 在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务 可以根据“享用”CPU时间多少来移动队列,阻止“饥饿” 最通用的调度算法,多数OS都使用该方法或其变形,如UNIX、Windows等 ## 什么是共享内存,好处是什么? 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。 ## 共享内存是怎么实现的? 简单的说,共享内存是通过把同一块内存分别映射到不同的进程空间中实现进程间通信。 而共享内存本身不带任何互斥与同步机制。但当多个进程同时对同一内存进行读写操作时会破坏该内存的内容,所以,在实际中,同步与互斥机制需要用户来完成。 共享内存的特点是什么? (1)共享内存就是允许两个不想关的进程访问同一个内存 (2)共享内存是两个正在运行的进程之间共享和传递数据的最有效的方式 (3)不同进程之间共享的内存通常安排为同一段物理内存 (4)共享内存不提供任何互斥和同步机制,一般用信号量对临界资源进行保护。 (5)接口简单 ## 一个程序从开始运行到结束的完整过程(四个过程)是什么? 预处理:条件编译,头文件包含,宏替换的处理,生成.i文件。 编译:将预处理后的文件转换成汇编语言,生成.s文件 汇编:汇编变为目标代码(机器代码)生成.o的文件 链接:连接目标代码,生成可执行程序 ## 布隆过滤器是什么,有什么特性? 本质是一个数据结构+哈希函数。 布隆过滤器(英语:Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。主要用于判断一个元素是否在一个集合中。 通常我们会遇到很多要判断一个元素是否在某个集合中的业务场景,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间也会呈现线性增长,最终达到瓶颈。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为 , , 。 这个时候,布隆过滤器(Bloom Filter)就应运而生。 特性 一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在。 布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。 添加与查询元素步骤 添加元素 将要添加的元素给 k 个哈希函数 得到对应于位数组上的 k 个位置 将这k个位置设为 1 查询元素 将要查询的元素给k个哈希函数 得到对应于位数组上的k个位置 如果k个位置有一个为 0,则肯定不在集合中 如果k个位置全部为 1,则可能在集合中 ## 布隆过滤器的优缺点? **优点** 相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数 ,另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。 布隆过滤器可以表示全集,其它任何数据结构都不能; **缺点** 但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。 另外,一般情况下不能从布隆过滤器中删除元素。我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加 1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面。这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。 在降低误算率方面,有不少工作,使得出现了很多布隆过滤器的变种。 布隆过滤器的原理是什么? 布隆过滤器是一个 bit 向量或者说 bit 数组,长这样: 如果我们要映射一个值到布隆过滤器中,我们需要使用多个不同的哈希函数生成**多个哈希值,**并对每个生成的哈希值指向的 bit 位置 1,例如针对值 “baidu” 和三个不同的哈希函数分别生成了哈希值 1、4、7,则上图转变为: Ok,我们现在再存一个值 “tencent”,如果哈希函数返回 3、4、8 的话,图继续变为: 值得注意的是,4 这个 bit 位由于两个值的哈希函数都返回了这个 bit 位,因此它被覆盖了。现在我们如果想查询 “dianping” 这个值是否存在,哈希函数返回了 1、5、8三个值,结果我们发现 5 这个 bit 位上的值为 0,说明没有任何一个值映射到这个 bit 位上,因此我们可以很确定地说 “dianping” 这个值不存在。而当我们需要查询 “baidu” 这个值是否存在的话,那么哈希函数必然会返回 1、4、7,然后我们检查发现这三个 bit 位上的值均为 1,那么我们可以说 “baidu”存在了么?答案是不可以,只能是 “baidu” 这个值可能存在。 这是为什么呢?答案跟简单,因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个值 “taobao” 即使没有被存储过,但是万一哈希函数返回的三个 bit 位都被其他值置位了 1 ,那么程序还是会判断 “taobao” 这个值存在。 布隆过滤器的使用场景有哪些? 利用布隆过滤器减少磁盘 IO 或者网络请求,因为一旦一个值必定不存在的话,我们可以不用进行后续昂贵的查询请求。 网页 URL 去重、垃圾邮件识别、大集合中重复元素的判断和缓存穿透等问题。 布隆过滤器的典型应用有: 数据库防止穿库。 Google Bigtable,HBase 和 Cassandra 以及 Postgresql 使用BloomFilter来减少不存在的行或列的磁盘查找。避免代价高昂的磁盘查找会大大提高数据库查询操作的性能。 业务场景中判断用户是否阅读过某视频或文章,比如抖音或头条,当然会导致一定的误判,但不会让用户看到重复的内容。 缓存宕机、缓存击穿场景,一般判断用户是否在缓存中,如果在则直接返回结果,不在则查询db,如果来一波冷数据,会导致缓存大量击穿,造成雪崩效应,这时候可以用布隆过滤器当缓存的索引,只有在布隆过滤器中,才去查询缓存,如果没查询到,则穿透到db。如果不在布隆器中,则直接返回。 WEB拦截器,如果相同请求则拦截,防止重复被攻击。用户第一次请求,将请求参数放入布隆过滤器中,当第二次请求时,先判断请求参数是否被布隆过滤器命中。可以提高缓存命中率。Squid 网页代理缓存服务器在 cache digests 中就使用了布隆过滤器。Google Chrome浏览器使用了布隆过滤器加速安全浏览服务 Venti 文档存储系统也采用布隆过滤器来检测先前存储的数据。 SPIN 模型检测器也使用布隆过滤器在大规模验证问题时跟踪可达状态空间。 ## cdn如何防劫持? 通过https加密可以防止CDN劫持 CDN又叫内容分发网络,主要是提高网站的速度,可以优化网站的访问速度,提高网站的安全性和稳定性。 CDN能加速大家都知道,但其实,CDN本身是一种DNS劫持,只不过是良性的。不同于黑客强制DNS把域名解析到自己的钓鱼IP上,CDN则是让DNS主动配合,把域名解析到临近的服务器上。 同时服务器开启了HTTP代理,让用户感觉不到CDN的存在。不过CDN劫持不像黑客那样贪心,劫持用户所有流量,它只“劫持”用户的静态资源访问,对于之前用户访问过的资源,CDN将直接从本地缓存里反馈给用户,因此速度有了很大的提升。 把所有的内容加密起来,在传输过程中,任何劫持者都不能探测到实际传输交互的内容。自然也就能防止劫持了。 ## XSS攻击如何防御? XSS(Cross Site Scripting,跨站脚本攻击) XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。 运行预期之外的脚本带来的后果有很多中,可能只是简单的恶作剧——一个关不掉的窗口: while (true) { alert("你关不掉我~"); } 也可以是盗号或者其他未授权的操作。 XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF。 ## 如何防御 XSS 攻击? 理论上,所有可输入的地方没有对输入数据进行处理的话,都会存在XSS漏洞,漏洞的危害取决于攻击代码的威力,攻击代码也不局限于script。防御 XSS 攻击最简单直接的方法,就是过滤用户的输入。 如果不需要用户输入 HTML,可以直接对用户的输入进行 HTML escape 。下面一小段脚本: 经过 escape 之后就成了: 它现在会像普通文本一样显示出来,变得无毒无害,不能执行了。 当我们需要用户输入 HTML 的时候,需要对用户输入的内容做更加小心细致的处理。仅仅粗暴地去掉 script 标签是没有用的,任何一个合法 HTML 标签都可以添加 onclick 一类的事件属性来执行 JavaScript。更好的方法可能是,将用户的输入使用 HTML 解析库进行解析,获取其中的数据。然后根据用户原有的标签属性,重新构建 HTML 元素树。构建的过程中,所有的标签、属性都只从白名单中拿取。 ## 如何防御CSRF攻击? CSRF(Cross-site request forgery,跨站请求伪造) CSRF(XSRF) 顾名思义,是伪造请求,冒充用户在站内的正常操作。 例如,一论坛网站的发贴是通过 GET 请求访问,点击发贴之后 JS 把发贴内容拼接成目标 URL 并访问: http://example.com/bbs/create_post.php?title=标题&content=内容 那么,我们只需要在论坛中发一帖,包含一链接: http://example.com/bbs/create_post.php?title=我是脑残&content=哈哈 只要有用户点击了这个链接,那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧,但是既然发贴的请求可以伪造,那么删帖、转帐、改密码、发邮件全都可以伪造。 如何防范 CSRF 攻击?可以注意以下几点: 关键操作只接受POST请求 验证码 CSRF攻击的过程,往往是在用户不知情的情况下构造网络请求。所以如果使用验证码,那么每次操作都需要用户进行互动,从而简单有效的防御了CSRF攻击。 但是如果你在一个网站作出任何举动都要输入验证码会严重影响用户体验,所以验证码一般只出现在特殊操作里面,或者在注册时候使用。 检测 Referer 常见的互联网页面与页面之间是存在联系的,比如你在www.baidu.com应该是找不到通往www.google.com的链接的,再比如你在论坛留言,那么不管你留言后重定向到哪里去了,之前的那个网址一定会包含留言的输入框,这个之前的网址就会保留在新页面头文件的Referer中 通过检查Referer的值,我们就可以判断这个请求是合法的还是非法的,但是问题出在服务器不是任何时候都能接受到Referer的值,所以 Referer Check 一般用于监控 CSRF 攻击的发生,而不用来抵御攻击。 Token 目前主流的做法是使用 Token 抵御 CSRF 攻击。下面通过分析 CSRF 攻击来理解为什么 Token 能够有效 CSRF攻击要成功的条件在于攻击者能够预测所有的参数从而构造出合法的请求。所以根据不可预测性原则,我们可以对参数进行加密从而防止CSRF攻击。 另一个更通用的做法是保持原有参数不变,另外添加一个参数Token,其值是随机的。这样攻击者因为不知道Token而无法构造出合法的请求进行攻击。 Token 使用原则 Token 要足够随机————只有这样才算不可预测 Token 是一次性的,即每次请求成功后要更新Token————这样可以增加攻击难度,增加预测难度 Token 要注意保密性————敏感操作使用 post,防止 Token 出现在 URL 中 注意:过滤用户输入的内容不能阻挡 csrf,我们需要做的是过滤请求的来源。 如何防御中间人攻击? 中间人攻击,即所谓的Main-in-the-middle attack(MITM),顾名思义,就是攻击者插入到原本直接通讯的双方,让双方以为还在直接跟对方通讯,但实际上双方的通讯对方已变成了中间人,信息已经是被中间人获取或篡改。 HTTPS在建立了TCP连接之后,会进行SSL握手(SSL Handshake)来校验证书,协商加密协议和对称加密的密钥,之后就会使用协商好的密钥来进行传输。所以HTTPS攻击一般分为SSL连接建立前的攻击,以及HTTPS传输过程中的攻击; 常见的HTTPS中间人攻击,首先需要结合ARP、DNS欺骗等技术,来对会话进行拦截, 1、SSL证书欺骗攻击 此类攻击较为简单常见。首先通过ARP欺骗、DNS劫持甚至网关劫持等等,将客户端的访问重定向到攻击者的机器,让客户端机器与攻击者机器建立HTTPS连接(使用伪造证书),而攻击者机器再跟服务端连接。这样用户在客户端看到的是相同域名的网站,但浏览器会提示证书不可信,用户不点击继续浏览就能避免被劫持的。所以这是最简单的攻击方式,也是最容易识别的攻击方式。 ssl证书欺骗 防范措施 : 钓鱼类攻击,App直接调用系统API创建的HTTPS连接(NSURLConnection)一般不会受到影响,只使用默认的系统校验,只要系统之前没有信任相关的伪造证书,校验就直接失败,不会SSL握手成功;但如果是使用WebView浏览网页,需要在UIWebView中加入较强的授权校验,禁止用户在校验失败的情况下继续访问。 2 SSL剥离攻击(SSLStrip) SSL剥离,即将HTTPS连接降级到HTTP连接。假如客户端直接访问HTTPS的URL,攻击者是没办法直接进行降级的,因为HTTPS与HTTP虽然都是TCP连接,但HTTPS在传输HTTP数据之前,需要在进行了SSL握手,并协商传输密钥用来后续的加密传输;假如客户端与攻击者进行SSL握手,而攻击者无法提供可信任的证书来让客户端验证通过进行连接,所以客户端的系统会判断为SSL握手失败,断开连接。 该攻击方式主要是利用用户并不会每次都直接在浏览器上输入https://xxx.xxx.com 来访问网站,或者有些网站并非全网HTTPS,而是只在需要进行敏感数据传输时才使用HTTPS的漏洞。中间人攻击者在劫持了客户端与服务端的HTTP会话后,将HTTP页面里面所有的 https:// 超链接都换成 http:// ,用户在点击相应的链接时,是使用HTTP协议来进行访问;这样,就算服务器对相应的URL只支持HTTPS链接,但中间人一样可以和服务建立HTTPS连接之后,将数据使用HTTP协议转发给客户端,实现会话劫持。 这种攻击手段更让人难以提防,因为它使用HTTP,不会让浏览器出现HTTPS证书不可信的警告,而且用户很少会去看浏览器上的URL是 https:// 还是 http:// 。特别是App的WebView中,应用一般会把URL隐藏掉,用户根本无法直接查看到URL出现异常。 ssl剥离攻击 防范措施: 该种攻击方式同样无法劫持App内的HTTPS连接会话,因为App中传入请求的URL参数是固定带有“https://” 的;但在WebView中打开网页同样需要注意,在非全网HTTPS的网站,建议对WebView中打开的URL做检查,检查应该使用 “https://” 的URL是否被篡改为 “http://” ;也建议服务端在配置HTTPS服务时,加上“HTTP Strict Transport Security”配置项。 3 针对SSL算法进行攻击 上述两种方式,技术含量较低,而且一般只能影响 WebApp,而很难攻击到 Native App , 所以高阶的 Hacker,会直接针对SSL算法相关漏洞进行攻击,期间会使用很多的密码学相关手段。由于本人非专业安全相关人员,没有多少相关实践经验,所以本节不会深入讲解相关的攻击原理和手段,有兴趣的同学可以查看以下拓展阅读: OpenSSL漏洞 常见的HTTPS攻击方法 防范措施: 这类攻击手段是利用SSL算法的相关漏洞,所以最好的防范措施就是对服务端 SSL/TLS 的配置进行升级: 只支持尽量高版本的TLS(最低TLS1); 禁用一些已爆出安全隐患的加密方法; 使用2048位的数字证书; 防范措施: 不要随意连入公共场合内的WiFi,或者使用未知代理服务器 不要安装不可信或突然出现的描述文件,信任伪造的证书; App内部需对服务器证书进行单独的对比校验,确认证书不是伪造的; 使用WireShark模拟中间人证书伪造攻击http://blog.csdn.net/phunxm/article/details/38590561 **使用Charles模拟中间人证书伪造攻击 **http://www.jianshu.com/p/a81b496348bc 如何有效防御DDOS攻击? 什么是DDOS攻击? 恶意流量攻击通常指的是DDOS攻击。 DDOS攻击:Distributed Denial of Service简称DDOS,中文意思是分布式拒绝服务攻击。简单的来说就是一种针对目标系统的恶意攻击行为,会导致被攻击者的业务无法正常访问,甚至服务器瘫痪。简单的举个例子,假如你开了一家只能容纳100个客人的饭店,生意还不错,隔壁家的店生意不好,对你心生妒忌,于是请了1000个无所事事的人来你店里捣乱,不买东西,还赖着不走,把你的店挤满,让正常的顾客无法进来消费。这就是DDOS攻击。 如何有效防御DDOS攻击? DDoS攻击最大的难点在于攻击者发起的攻击的成本远低于防御的成本。 DDoS 攻击究其本质其实是无法彻底防御的,我们能做得就是不断优化自身的网络和服务架构,来提高对 DDoS 的防御能力。 比如黑客可以轻易的控制大量傀儡主机发起10G,100G的攻击,而要防御这样的攻击10G,100G带宽的成本却是攻击成本的很多倍。所以说靠增加自己服务器带宽来防御DDOS是非常不现实的。除了靠增加带宽来防御DDOS之外呢,还可以通过购买网络安全公司的高防产品来抵御DDOS攻击。比如说高防CDN,在防御DDOS上会更加具有灵活性。ddos攻击是直接解析域名IP进行攻击的,从而对域名ip进行各种发包,导致宽带流量处于峰值,用户便无法正常访问。使用cdn加速后,相当于在服务器和用户之间增加一个中转站,这样就能达到隐藏服务器真实ip的效果,当攻击者攻击服务器时,攻击到的是服务商的IP,而不会对我们真实的服务器造成威胁。并且会实时监测DDOS攻击,当监测到有DDOS攻击时会自动识别清洗,预警。 客户端http请求从服务器server到nginx到php响应返回整个流程? HTTP 事务执行过程 客户端(浏览器)做出请求操作(输入网址、点击链接、提交表单)。 客户端对域名进行解析,向设定的 DNS 服务器请求 IP 地址。 客户端根据 DNS 服务器返回 IP 地址采用三次握手与服务端建立 TCP/IP 连接。 TCP/IP 连接成功后,客户端向服务端发送 HTTP 请求。 服务端的 Web Server 会判断 HTTP 请求的资源类型,进行内容分发处理;如果请求的资源为 PHP 文件,服务端软件会启动对应的 CGI 程序进行处理,并返回处理结果。 服务端将 Web Server 的处理结果响应给客户端 客户端接收服务端的响应,并渲染处理结果,如果响应内容需要请求其他静态资源,通过 CDN 加速访问所需资源。 客户端将渲染好的视图呈现出来并断开 TCP/IP 连接 ## 什么是分布式,解决了什么问题? 分布式的核心就一个字:拆。只要是将一个项目拆分成了多个模块,并将这些模块分开部署,那就算是分布式。 如何拆呢?有两种方式:水平拆分,或垂直拆分(也称为“横向拆分”和“垂直拆分”) 分布式属于微服务,微服务也就是将模块拆分成一个独立的服务单元通过接口来实现数据的交互。 怎么理解分布式的拆分? **水平拆分:**根据“分层”的思想进行拆分。例如,可以将一个项目根据“三层架构”拆分成 表示层(jsp+servlet)、业务逻辑层(service)和数据访问层(dao),然后再分开部署:把表示层部署在服务器A上,把service和dao层部署在服务器B上,然后服务器A和服务器B之间通过dubbo等RPC进行进行整合(在左下角的“阅读原文”里有dubbo的视频课程,可以点击学习),如图所示。 **垂直拆分:**根据业务进行拆分。例如,可以根据业务逻辑,将“电商项目”拆分成“订单项目”、“用户项目”和“秒杀项目”。显然这三个拆分后的项目,仍然可以作为独立的项目使用。像这种拆分的方法,就成为垂直拆分。 ## 什么是微服务,解决了什么问题? 微服务的设计是为了不因为某个模块的升级和BUG影响现有的系统业务。微服务与分布式的细微差别是,微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器。 总结: 分布式:拆了就行。 微服务:细粒度的垂直拆分。 扩展介绍微服务的文章:https://www.wangtianyi.top/blog/2017/04/16/microservies-1-introduction-to-microservies/?utm_source=github&utm_medium=github ## 微服务和soa的区别? 表面上,微服务很像SOA。通过这两种方法,架构包含一组服务。然而,SOA是已经被商业化提供服务的并且有ESB(Enterprise Service Bus)。基于微服务的应用支持更简单,轻量的协议比如REST而不是Web Service。并且非常避免使用ESB,而是在微服务中实现ESB类似的功能。微服务架构也拒绝SOA其他部分,比如规范模式的概念。 zk的分布式协调是什么? 这个其实是 zookeeper 很经典的一个用法,简单来说,就好比,你 A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上对某个节点的值注册个监听器,一旦 B 系统处理完了就修改 zookeeper 那个节点的值,A 立马就可以收到通知,完美解决。 ## zk如何实现分布式锁? 举个栗子。对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也尝试去创建那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等着,等第一个机器执行完了自己再执行。 zk的元数据/配置信息管理 zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用 zookeeper 来做一些元数据、配置信息的管理,包括 dubbo 注册中心不也支持 zookeeper 么? zk如何保证HA高可用性? 这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zookeeper 来开发 HA 高可用机制,就是一个重要进程一般会做主备两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程。 ## 分布式系统中的CAP理论,了解么? CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。 一致性(C):对某个指定的客户端来说,读操作能返回最新的写操作结果 可用性(A):非故障节点在合理的时间返回合理的响应 分区容错性(P):分区容错性是指当网络出现分区(两个节点之间无法连通)之后,系统能否继续履行职责 CAP理论就是说在分布式系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以考虑最差情况,分区容忍性是一般是需要实现的 个人理解 满足之后: 如果选择C,则需要将数据同步更新到所有节点上,每次写操作就都要等待全部节点写成功,会导致A的降低。 如果选择A,就需要将数据复制到很多个节点,需要复制的数据很多,复制的过程缓慢,会导致C的降低 正常情况下,不存在CP还是AP的选择,可以做到CA,但如果网络出现分区(节点之间的网络连接不正常),就必须要为了满足P,而放弃C或A,放弃哪个?一般我们要保证的是系统不能挂掉(AP),然后通过同步数据的方式还原C,还可能让我们的系统挂一部分(CP),然后通过重启节点的方式还原A 对于不同业务也会需要考虑到不同的CAP选择,以电商网站为例,会员登录、个人设置、个人订单、购物车、搜索用AP,因为这些数据短时间内不一致不影响使用;后台的商品管理就需要CP,避免商品数量的不一致;支付功能需要CA,保证支付功能的安全稳定 BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 ## 服务容错的保护措施有哪些? 服务降级 服务降级类似女生旅行:在用户访问量高峰期,整体资源面临不足的时候,将一些重要优先程度相对较低的服务先关掉,等到过了高峰期再恢复。比如京东商城在双十一期间,可能会对评论服务进行服务降级。 回到微服务系统,服务A调用服务B,当我们对服务B进行降级后,服务A将直接调用预定义的降级逻辑(即方法调用代替跨服务请求),从而快速获取返回结果,而降级方法逻辑的返回结果与真实服务B的返回结果的区别 就好比 残次品与良品的区别,此时我们认为服务B所提供的服务质量降低了,即我所说的降级。 服务熔断 在分布式架构中,断路器模式的作用也是类似的,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不再调用目标服务,直接返回结果,快速释放资源,避免最终因为服务不可用蔓延导致系统雪崩灾难。 断路器什么时候会打开 这里涉及到断路器的三个重要参数: 快照时间窗:断路器确定是否需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认10秒 请求总数下限:在快照时间窗内,必须满足请求总数下限才会启用熔断。默认20,意味着在10秒内,如果调用不足20次,即便所有的请求都失败,断路器都不会打开 错误百分比下限:当请求总数在快照时间内超过了下限,比如发生了30次调用,如果在这 30次调用中,有16次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%下限的情况下,断路器就会打开 断路器打开之后发生什么 熔断打开之后,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级逻辑,这个时候就会快速返回,而不是等待5秒后才返回fallback。通过断路器实现了自动发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果 主逻辑如何恢复 Hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器就进入半开状态,释放一次请求到原来的主逻辑上。如果此次请求正常返回,那么断路器将会关闭,主逻辑恢复正常。否则,断路器继续保持打开状态,而休眠时间窗会重新计时 微服务的API网关是什么? 在一个项目中微服务节点很多,如果让每一个节点都去处理上面这些 “鉴权认证功能、Session处理、安全检查、日志处理等” 会多出很多冗余的代码,也会给增加业务代码的复杂度,因此我们就需要有一个「 API网关 」把这些公共的功能独立出来成为一个服务来统一的处理这些事情。 ## API网关功能是什么? 其主要功能有: 路由转发 之前说了「API网关」是内部微服务的对外唯一入口,所以外面全部的请求都会先发到这个「API网关」上,然后由「API网关」来根据不同的请求去路由到不同的微服务节点上。例如可以 根据路径 来转发、也可以 根据参数 来转发。 并且由于内部微服务实例也会随着业务调整不停的变更,增加或者删除节点,「API网关」可以与「服务注册」模块进行协同工作,保证将外部请求转发到最合适的微服务实例上面去。 负载均衡 既然「API网关」是内部微服务的单一入口,所以「API网关」在收到外部请求之后,还可以根据内部微服务每个实例的负荷情况进行动态的负载均衡调节。一旦内部的某个微服务实例负载很高,甚至是不能及时响应,则「API网关」就通过负载均衡策略减少或停止向这个实例转发请求。当所有的内部微服务实例都处理不过来的时候,「API网关」还可以采用限流或熔断的形式阻止外部请求,以保障整个系统的可用性。 安全认证 「API网关」就像是微服务的大门守卫,每一个请求进来之后,都必须先在「API网关」上进行身份验证,身份验证通过后才转发给后面的服务,转发的时候一般也会带上身份信息。 同时「API网关」也需要对每一个请求进行安全性检查,例如参数的安全性、传输的安全性等等。 日志记录 既然所有的请求都需要走「API网关」,那么我们就可以在「API网关」上统一集中的记录下这些行为日志。这些日志既可以作为我们后续事件查询使用,也可以作为系统的性能监控使用。 数据转换 因为「API网关」对外是面向多种不同的客户端,不同的客户端所传输的数据类型可能是不一样的。因此「API网关」还需要具备数据转换的功能,将不同客户端传输进来的数据转换成同一种类型再转发给内部微服务上,这样,兼容了这些请求的多样性,保证了微服务的灵活性。 服务发现是什么? 定义 在微服务应用中,运行的服务实例集会动态更改。实例能动态分配网络位置。所以为了使客户端向服务端发送请求它必须使用服务发现机制。 客户端应用进程向注册中心发起查询,来获取服务的位置。服务发现的一个重要作用就是提供一个可用的服务列表。 原理 当User Service启动的时候,会向Consul发送一个POST请求,告诉Consul自己的IP和Port Consul 接收到User Service的注册后,每隔10s(默认)会向User Service发送一个健康检查的请求,检验User Service是否健康(Consul其实支持其他健康检查机制) 当Order Service发送 GET 方式请求/api/addresses到User Service时,会先从Consul中拿到一个存储服务 IP 和 Port 的临时表,从表中拿到User Service的IP和Port后再发送GET方式请求/api/addresses 该临时表每隔10s会更新,只包含有通过了健康检查的Service 上面注册、查询的逻辑不是Consul提供的,一般是你使用的微服务框架中已经封装好的服务注册发现功能,如果没有特殊的需求是不会修改这部分逻辑的 说一下你对中台的理解 中台 这个理念在国内最早是由阿里巴巴带起来的。 中台 就是一个架构理念,它是介于前台与后台之间的(这句好像是废话),它是希望将一些可复用的“能力”统一起来,采用共享的方式去建设,用来解决各个业务团队重复开发、数据分散、试错成本高等问题,中台的核心就是**“对能力的共享”、“对能力的复用”**,它应该是公司内部的统一协同平台。 另外再给个参考,在《说透中台》专栏中王健老师将中台定义为: 企业级的能力复用平台 讲完了中台的定义,我们再来看看 前台、中台、后台 的区别吧。 「前台」是直接服务客户、触达用户的平台,能够洞察用户需求,进行产品创新、提升用户价值,保持精简和足够敏捷度的平台。比如阿里的 淘宝、天猫、聚划算等。 「中台」前面已经定义过了。它通过组件化的形式输出通用能力,为所有「前台」的业务运营和创新,提供专业能力的共享平台。中台部门提炼各业务线的共性需求,将各种资源转化为方便「前台」使用的能力,最大程度避免重复“造轮子”。 「后台」的职能是提供基础设施建设、服务支持,为「前台」和「中台」提供基础保障。后台会比中台更底层、更通用。「中台」有的时候会更关注在某一行业/领域内的,而「后台」应该是行业/领域通用的。 扩展:https://www.cnblogs.com/jsjwk/p/11725879.html 比较有价值的扩展学习 分布式: https://www.cnblogs.com/xybaby/p/8544715.html 微服务: https://zhuanlan.zhihu.com/p/100649803 https://baijiahao.baidu.com/s?id=1645458206460413610&wfr=spider&for=pc https://www.zhihu.com/question/65502802 https://www.cnblogs.com/kenshinobiy/p/11113124.html ## 网页很卡的原因有哪些? 带宽不足、硬件配置低、CPU或者是内存被占满。 http请求次数太多。 接收数据时间过长,如下载资源过大。 JS脚本过大,阻塞了页面的加载。 网页资源过多、接受数据时间长、加载某个资源慢。 DNS解析速度。 ## APP稳定性查、闪退的原因有哪些? 弱网络情况下,服务端响应不及时,可能倒是闪退。(网络异常引起的) APP应用版本太低,会导致不兼容,造成闪退。(有些API在老版本中有,在新版本中没有,造成对象为空引起闪退) APP的SDK和手机的系统不兼容。 缓存垃圾过多:由于安卓系统的特性,如果长时间不清理垃圾文件。会导致越来越卡,也会出现闪退情况。 设计不合理,1个接口,拉取的数据量太大,请求结果会很慢,且占用大量内存,APP会闪退(比如,我们现在做的记录仪,进入相册列表时候,要拉取所有图片,拉取太慢了,就闪退了) 不同APP间切换,交互测试,可能会出现闪退。 APP的手机权限问题。 ## 如何测算接口的最大并发量? 安装ab压测工具 yum install httpd-tools **使用ab** ab -n 1000 -c 100 http://www.baidu.com/ -n 总的请求数 -c 并发数 -k 是否开启长连接 请求静态页面 ab -n 1000 -c 100 http://www.youku.com/test.html -n 1000 总请求数1000 -c 100 单个时刻并发数100 返回结果中的这个值: Requests per second: 115.79 [#/sec] (mean) 代表接口的最大并发量 Time per request,这个值代表用户平均等待的时间。 ## 如何设计RBAC表结构 RBAC RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。 简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。 角色 角色可以理解为一定数量的权限的集合,权限的载体。 权限 一个论坛系统,“超级管理员”、“版主”都是角色。版主可管理版内的帖子、可管理版内的用户等,这些是权限。 通过角色赋予权限 要给某个用户授予这些权限,不需要直接将权限授予用户,可将“版主”这个角色赋予该用户。 用户分组 当用户的数量非常大时,要给系统每个用户逐一授权(授角色),是件非常烦琐的事情。这时,就需要给用户分组,每个用户组内有多个用户。除了可给用户授权外,还可以给用户组授权。 这样一来,用户拥有的所有权限,就是用户个人拥有的权限与该用户所在用户组拥有的权限之和。 在应用系统中,权限表现成什么? 权限的范畴 对功能模块的操作 对上传文件的删改 菜单的访问 甚至页面上某个按钮 某个图片的可见性控制 ... 有些权限设计,会把功能操作作为一类,而把文件、菜单、页面元素等作为另一类,这样构成“用户-角色-权限-资源”的授权模型。而在做数据表建模时,可把功能操作和资源统一管理,也就是都直接与权限表进行关联,这样可能更具便捷性和易扩展性。 请留意权限表中有一列“权限类型”,我们根据它的取值来区分是哪一类权限,如“MENU”表示菜单的访问权限、“OPERATION”表示功能模块的操作权限、“FILE”表示文件的修改权限、“ELEMENT”表示页面元素的可见性控制等。 RBAC的设计优点 其一,不需要区分哪些是权限操作,哪些是资源,(实际上,有时候也不好区分,如菜单,把它理解为资源呢还是功能模块权限呢?)。 其二,方便扩展,当系统要对新的东西进行权限控制时,我只需要建立一个新的关联表“权限XX关联表”,并确定这类权限的权限类型字符串。 这里要注意的是,权限表与权限菜单关联表、权限菜单关联表与菜单表都是一对一的关系。(文件、页面权限点、功能操作等同理)。也就是每添加一个菜单,就得同时往这三个表中各插入一条记录。这样,可以不需要权限菜单关联表,让权限表与菜单表直接关联,此时,须在权限表中新增一列用来保存菜单的ID,权限表通过“权限类型”和这个ID来区分是种类型下的哪条记录。 角色组 随着系统的日益庞大,为了方便管理,可引入角色组对角色进行分类管理,跟用户组不同,角色组不参与授权。 例如:某电网系统的权限管理模块中,角色就是挂在区局下,而区局在这里可当作角色组,它不参于权限分配。另外,为方便上面各主表自身的管理与查找,可采用树型结构,如菜单树、功能树等,当然这些可不需要参于权限分配。 ## 工作中有用到ES么 概述 Elasticsearch基于Lucene(搜索引擎库)的开源搜索引擎,对外提供一系列基于Java和HTTP的API, 目的是通过简单的RESTful API来隐藏Lucene的复杂性。 具有以下特点: 支持全文检索 分布式的实时文件存储,每个字段都被索引并可被搜索 分布式的实时分析搜索引擎 可以对照数关系型据库来理解Elasticsearch的有关概念。 关系型数据库(Relational DB) Elasticsearch(搜索引擎) 数据库(Databases) Indices 表(Tables) Types 行(Rows) Documents 字段(Columns) Fields Elasticsearch集群可以包含多个索引(indices)(数据库),每一个索引可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。 索引 索引只是一个把一个或多个分片分组在一起的逻辑空间。可以把索引看成关系型数据库的表。 然而,索引的结构是为快速有效的全文索引准备的,特别是它不存储原始值。 Elasticsearch 可以把索引存放在一台机器或者分散在多台服务器上,每个索引有一或多个分片(shard),每个分片可以有多个副本(replica)。发送一个新的文档给集群时,你指定一个目标索引并发送给它的任意一个节点。这个节点知道目标索引有多少分片,并且能够确定哪个分片应该用来存储你的文档。可以更改Elasticsearch的这个行为。现在你需要记住的重要信息是,Elasticsearch使用文档的唯一标识符来计算文档应该被放到哪个分片中。索引请求发送到一个节点后,该节点会转发文档到持有相关分片的目标节点中。 一次索引操作 检索 尝试用文档标识符来获取文档时,发送查询到一个节点,该节点使用同样的路由算法来决定持有文档的节点和分片,然后转发查询,获取结果,并把结果发送给你。另一方面,查询过程更为复杂。除非使用了路由,查询将直接转发到单个分片,否则,收到查询请求的节点会把查询转发给保存了属于给定索引的分片的所有节点,并要求获取查询匹配的文档的最少信息(默认情况下是标识符和得分)。这个过程称为发散阶段(scatter phase)。收到这些信息后,该聚合节点(收到客户端请求的节点)对结果排序,并发送第2个请求来获取结果列表所需的文档(除了标识符和得分以外的所有信息)。这个阶段称为收集阶段(gather phase)。这个阶段执行完毕后,结果返回到客户端。 一次查询请求: curl -X GET http://localhost:9200/megacorp/employee/1? 文档 存储在Elasticsearch中的主要实体叫文档(document)。用关系型数据库来类比的话,一个文档相当于数据库表中的一行记录。相同字段必须有相同类型,文档由多个字段组成,每个字段可能多次出现在一个文档里,这样的字段叫多值字段(multivalued)。每个字段有类型,如文本、数值、日期等。字段类型也可以是复杂类型,一个字段包含其他子文档或者数组。字段类型在Elasticsearch中很重要,因为它给出了各种操作(如分析或排序)如何被执行的信息。幸好,这可以自动确定,然而,我们仍然建议使用映射。与关系型数据库不同,文档不需要有固定的结构,每个文档可以有不同的字段,此外,在程序开发期间,不必确定有哪些字段。当然,可以用模式强行规定文档结构。 从客户端的角度看,文档是一个JSON对象(关于JSON格式的更多内容,参见http://en.wikipedia.org/wiki/JSON)。每个文档存储在一个索引中并有一个Elasticsearch自动生成的唯一标识符和文档类型。文档需要有对应文档类型的唯一标识符,这意味着在一个索引中,两个不同类型的文档可以有相同的唯一标识符。 文档类型 在Elasticsearch中,一个索引对象可以存储很多不同用途的对象。例如,一个博客应用程序可以保存文章和评论。 文档类型让我们轻易地区分单个索引中的不同对象。每个文档可以有不同的结构,但在实际部署中,将文件按类型区分对数据操作有很大帮助。当然,需要记住一个限制,不同的文档类型不能为相同的属性设置不同的类型。例如,在同一索引中的所有文档类型中,一个叫title的字段必须具有相同的类型 映射 模式映射(schema mapping,或简称映射)用于定义索引结构。Elasticsearch在映射中存储有关字段的信息。每一个文档类型都有自己的映射,即使我们没有明确定义。映射在文件中以JSON对象传送。所以,创建一个映射文件来匹配上述需求,称之为mapping.json。其内容如下: { "mappings": { "post": { "properties": { "id": {"type":"long", "store":"yes", "precision_step":"0" }, "name": {"type":"string", "store":"yes", "index":"analyzed" }, "published": {"type":"date", "store":"yes", precision_step":"0" }, "contents": {"type":"string", "store":"no", "index":"analyzed" } } } } } 使用上述文件创建posts索引,运行命令: curl -XPOST 'http://localhost:9200/posts' -d @mapping.json 分片 当有大量的文档时,由于内存的限制、硬盘能力、处理能力不足、无法足够快地响应客户端请求等,一个节点可能不够。在这种情况下,数据可以分为较小的称为分片(shard)的部分(其中每个分片都是一个独立的Apache Lucene索引)。每个分片可以放在不同的服务器上,因此,数据可以在集群的节点中传播。 当你查询的索引分布在多个分片上时,Elasticsearch会把查询发送给每个相关的分片,并将结果合并在一起,而应用程序并不知道分片的存在。此外,多个分片可以加快索引。 副本 为了提高查询吞吐量或实现高可用性,可以使用分片副本。副本(replica)只是一个分片的精确复制,每个分片可以有零个或多个副本。换句话说,Elasticsearch可以有许多相同的分片,其中之一被自动选择去更改索引操作。这种特殊的分片称为主分片(primary shard),其余称为副本分片(replica shard)。在主分片丢失时,例如该分片数据所在服务器不可用,集群将副本提升为新的主分片。 es-head简介 elasticsearch有很多的插件来丰富它的功能。此处,我们推荐使用elasticsearch-head。 elasticsearch-head是一个界面化的集群操作和管理工具,可以对集群进行傻瓜式操作。你可以通过插件把它集成到es(首选方式),也可以安装成一个独立webapp。 操作 显示集群的拓扑,并且能够执行索引和节点级别操作 搜索接口能够查询集群中原始json或表格格式的检索数据 能够快速访问并显示集群的状态 有一个输入窗口,允许任意调用RESTful API。 这个接口包含几个选项,可以组合在一起以产生有趣的结果; 1. 请求方法(get、put、post、delete),查询json数据,节点和路径 2. 支持JSON验证器 3. 支持重复请求计时器 4. 支持使用javascript表达式变换结果 5. 收集结果的能力随着时间的推移(使用定时器),或比较的结果 6. 能力图表转换后的结果在一个简单的条形图(包括时间序列) 常用插件es-head https://github.com/mobz/elasticsearch-head es-head查看es集群 在浏览器打开127.0.0.1:9200/_plugin/head/,查看到界面表示安装成功。 我的经验 以上内容,可供入门了解下。es在我上家公司的应用,是搭建了一个单节点的es服务,用于实现全站搜索功能,他可以按照自定义的查询条件和匹配程度给我数据集合,安装简单,有es-head,使用方便,使用restful方式。 es在各大互联网公司还是很常见的一门技术,所以推荐大家学习下,我个人的es实战积累不多。所以列下清单,一起成长。 web开发者知识体系 干货 | BAT等一线大厂 Elasticsearch面试题解读 elasticsearch面试必考(亲身经历的问题) **MQ(消息队列)的使用场景** 消息队列(Message Queue) “消息”是在两台计算机间传送的数据单位。消息可以非常简单,例如只包含文本字符串;也可以更复杂 ,包括对象等。 队列是一种数据结构,先进先出,保证了顺序性。 生产者:发送消息的一端。用于把消息写入到队列中 消费者:从消息队列中,依次读取每条消息的一端。 消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。 目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。 应用场景 1. 异步处理 场景说明:用户注册后,需要发注册邮件和注册短信。传统的做法有两种 1.串行的方式;2.并行方式 (1)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端 (2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间 假设三个业务节点每个使用50毫秒钟,不考虑网络等其他开销,则串行方式的时间是150毫秒,并行的时间可能是100毫秒。 因为CPU在单位时间内处理的请求数是一定的,假设CPU1秒内吞吐量是100次。则串行方式1秒内CPU可处理的请求量是7次(1000/150)。并行方式处理的请求量是10次(1000/100) 引入消息队列,将不是必须的业务逻辑,异步处理。改造后的架构如下: 按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍 2. 应用解耦 场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。 传统模式的缺点: 1) 假如库存系统无法访问,则订单减库存将失败,从而导致订单失败; 2) 订单系统与库存系统耦合; 如何解决以上问题呢?引入应用消息队列后的方案 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。 库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。 假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。 3. 流量削峰 流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。 应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。 可以控制活动的人数; 可以缓解短时间内高流量压垮应用; 用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面; 秒杀业务根据消息队列中的请求信息,再做后续处理。 4. 日志处理 日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。架构简化如下: 日志采集客户端,负责日志数据采集,定时写受写入Kafka队列; Kafka消息队列,负责日志数据的接收,存储和转发; 日志处理应用:订阅并消费kafka队列中的日志数据; 5. 消息通讯 消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。 点对点通讯: 客户端A和客户端B使用同一队列,进行消息通讯。 聊天室通讯: 客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。 以上实际是消息队列的两种消息模式,点对点或发布订阅模式。 消息中间件实例 电商系统 消息队列采用高可用,可持久化的消息中间件。比如Active MQ,Rabbit MQ,Rocket Mq。 (1)应用将主干逻辑处理完成后,写入消息队列。消息发送是否成功可以开启消息的确认模式。(消息队列返回消息接收成功状态后,应用再返回,这样保障消息的完整性) (2)扩展流程(发短信,配送处理)订阅队列消息。采用推或拉的方式获取消息并处理。 (3)消息将应用解耦的同时,带来了数据一致性问题,可以采用最终一致性方式解决。比如主数据写入数据库,扩展应用根据消息队列,并结合数据库方式实现基于消息队列的后续处理。 日志收集系统 分为Zookeeper注册中心,日志收集客户端,Kafka集群和Storm集群(OtherApp)四部分组成。 Zookeeper注册中心,提出负载均衡和地址查找服务 日志收集客户端,用于采集应用系统的日志,并将数据推送到kafka队列 Kafka集群:接收,路由,存储,转发等消息处理 Storm集群:与OtherApp处于同一级别,采用拉的方式消费队列中的数据 消息模型 JMS标准中,有两种消息模型P2P(Point to Point),Publish/Subscribe(Pub/Sub)。 P2P(点对点)模式 P2P模式包含三个角色:消息队列(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。 P2P的特点 每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中) 发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列 接收者在成功接收消息之后需向队列应答成功 如果希望发送的每个消息都会被成功处理的话,那么需要P2P模式 Pub/sub模式 消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。 包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。 Pub/Sub的特点 每个消息可以有多个消费者 发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息 为了消费消息,订阅者必须保持运行的状态 为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。 如果希望发送的消息可以不被做任何处理、或者只被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用Pub/Sub模型 流行模型对比 传统企业型消息队列ActiveMQ遵循了JMS规范,实现了点对点和发布订阅模型,但其他流行的消息队列RabbitMQ、Kafka并没有遵循JMS规范。 RabbitMQ RabbitMQ实现了AQMP协议,AQMP协议定义了消息路由规则和方式。生产端通过路由规则发送消息到不同queue,消费端根据queue名称消费消息。 RabbitMQ既支持内存队列也支持持久化队列,消费端为推模型,消费状态和订阅关系由服务端负责维护,消息消费完后立即删除,不保留历史消息。 (1)点对点 生产端发送一条消息通过路由投递到Queue,只有一个消费者能消费到。 (2)多订阅 当RabbitMQ需要支持多订阅时,发布者发送的消息通过路由同时写到多个Queue,不同订阅组消费不同的Queue。所以支持多订阅时,消息会多个拷贝。 ## Kafka Kafka只支持消息持久化,消费端为拉模型,消费状态和订阅关系由客户端端负责维护,消息消费完后不会立即删除,会保留历史消息。因此支持多订阅时,消息只会存储一份就可以了。但是可能产生重复消费的情况。 (1)点对点&多订阅 发布者生产一条消息到topic中,不同订阅组消费此消息。! 对比 1.redis消息队列:一个轻量级的消息队列(数据量越大,效率越低,一般用于数据量较小的即时秒杀系统) 2.rabbitmq:一个重量级的、可靠的消息队列(数据量越大,效率越低,一般用于缓存可延迟的操作,比如银行转账) 3.kafka/Jafka:一个追求高吞吐量的、较不可靠的消息队列(一般用于缓存大数据中采集的数据) ## 如何设计防超卖的架构 场景 第一种方法 悲观锁 悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作读某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。 悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。 简而言之,悲观锁主要用于保护数据的完整性。当多个事务并发执行时,某个事务对数据应用了锁,则其他事务只能等该事务执行完了,才能进行对该数据进行修改操作。 update goods set num = num - 1 WHERE id = 1001 and num > 0 假设现在商品只剩下一件了,此时数据库中 num = 1; 但有100个线程同时读取到了这个 num = 1,所以100个线程都开始减库存了。 但你会最终会发觉,其实只有一个线程减库存成功,其他99个线程全部失败。 需要注意的是,FOR UPDATE生效需要同时满足两个条件时才生效: 数据库的引擎为 innoDB 操作位于事务块中(BEGIN/COMMIT) 悲观锁采用的是「先获取锁再访问」的策略,来保障数据的安全。但是加锁策略,依赖数据库实现,会增加数据库的负担,且会增加死锁的发生几率。此外,对于不会发生变化的只读数据,加锁只会增加额外不必要的负担。在实际的实践中,对于并发很高的场景并不会使用悲观锁,因为当一个事务锁住了数据,那么其他事务都会发生阻塞,会导致大量的事务发生积压拖垮整个系统。 第二种办法 乐观锁 select version from goods WHERE id= 1001 update goods set num = num - 1, version = version + 1 WHERE id= 1001 AND num > 0 AND version = @version(上面查到的version); 这种方式采用了版本号的方式,其实也就是CAS的原理。 假设此时version = 100, num = 1; 100个线程进入到了这里,同时他们select出来版本号都是version = 100。 然后直接update的时候,只有其中一个先update了,同时更新了版本号。 那么其他99个在更新的时候,会发觉version并不等于上次select的version,就说明version被其他线程修改过了。那么我就放弃这次update 第三种方法 redis消息队列 在秒杀的情况下,高频率的去读写数据库,会严重造成性能问题。所以必须借助其他服务, 利用redis的单线程预减库存。比如商品有100件。那么我在redis存储一个k,v。例如 每一个用户线程进来,key值就减1,等减到0的时候,全部拒绝剩下的请求。 那么也就是只有100个线程会进入到后续操作。所以一定不会出现超卖的现象。 第四种办法 redis分布式锁 $expire = 10;//有效期10秒 $key = 'lock';//key $value = time() + $expire;//锁的值 = Unix时间戳 + 锁的有效期 $lock = $redis->setnx($key, $value); //判断是否上锁成功,成功则执行下步操作 if(!empty($lock)) { //下单逻辑... } ## 如何设计高并发的架构 纵向和横向 通用的设计方法主要是从「纵向」和「横向」两个维度出发,俗称高并发处理的两板斧:纵向扩展和横向扩展。 纵向扩展(scale-up) 它的目标是提升单机的处理能力,方案又包括: 1、提升单机的硬件性能:通过增加内存、CPU核数、存储容量、或者将磁盘升级成SSD等堆硬件的方式来提升。 2、提升单机的软件性能:使用缓存减少IO次数,使用并发或者异步的方式增加吞吐量。 横向扩展(scale-out) 因为单机性能总会存在极限,所以最终还需要引入横向扩展,通过集群部署以进一步提高并发处理能力,又包括以下2个方向: 1、做好分层架构:这是横向扩展的提前,因为高并发系统往往业务复杂,通过分层处理可以简化复杂问题,更容易做到横向扩展。 真实的高并发系统架构会在此基础上进一步完善。比如会做动静分离并引入CDN,反向代理层可以是LVS+Nginx,Web层可以是统一的API网关,业务服务层可进一步按垂直业务做微服务化,存储层可以是各种异构数据库。 2、各层进行水平扩展:无状态水平扩容,有状态做分片路由。业务集群通常能设计成无状态的,而数据库和缓存往往是有状态的,因此需要设计分区键做好存储分片,当然也可以通过主从同步、读写分离的方案提升读性能。 ## 实践方案 针对高性能、高可用、高扩展3个方面,总结下可落地的实践方案。 高性能的实践方案 1、集群部署,通过负载均衡减轻单机压力。 2、多级缓存,包括静态数据使用CDN、本地缓存、分布式缓存等,以及对缓存场景中的热点key、缓存穿透、缓存并发、数据一致性等问题的处理。 3、分库分表和索引优化,以及借助搜索引擎解决复杂查询问题。 4、考虑NoSQL数据库的使用,比如HBase、TiDB等,但是团队必须熟悉这些组件,且有较强的运维能力。 5、异步化,将次要流程通过多线程、MQ、甚至延时任务进行异步处理。 6、限流,需要先考虑业务是否允许限流(比如秒杀场景是允许的),包括前端限流、Nginx接入层的限流、服务端的限流。 7、对流量进行削峰填谷,通过MQ承接流量。 8、并发处理,通过多线程将串行逻辑并行化。 9、预计算,比如抢红包场景,可以提前计算好红包金额缓存起来,发红包时直接使用即可。 10、缓存预热,通过异步任务提前预热数据到本地缓存或者分布式缓存中。 11、减少IO次数,比如数据库和缓存的批量读写、RPC的批量接口支持、或者通过冗余数据的方式干掉RPC调用。 12、减少IO时的数据包大小,包括采用轻量级的通信协议、合适的数据结构、去掉接口中的多余字段、减少缓存key的大小、压缩缓存value等。 13、程序逻辑优化,比如将大概率阻断执行流程的判断逻辑前置、For循环的计算逻辑优化,或者采用更高效的算法。 14、各种池化技术的使用和池大小的设置,包括HTTP请求池、线程池(考虑CPU密集型还是IO密集型设置核心参数)、数据库和Redis连接池等。 15、JVM优化,包括新生代和老年代的大小、GC算法的选择等,尽可能减少GC频率和耗时。 16、锁选择,读多写少的场景用乐观锁,或者考虑通过分段锁的方式减少锁冲突。 上述方案无外乎从计算和 IO 两个维度考虑所有可能的优化点,需要有配套的监控系统实时了解当前的性能表现,并支撑你进行性能瓶颈分析,然后再遵循二八原则,抓主要矛盾进行优化。 ## 高可用的实践方案 1、对等节点的故障转移,Nginx和服务治理框架均支持一个节点失败后访问另一个节点。 2、非对等节点的故障转移,通过心跳检测并实施主备切换(比如redis的哨兵模式或者集群模式、MySQL的主从切换等)。 3、接口层面的超时设置、重试策略和幂等设计。 4、降级处理:保证核心服务,牺牲非核心服务,必要时进行熔断;或者核心链路出问题时,有备选链路。 5、限流处理:对超过系统处理能力的请求直接拒绝或者返回错误码。 6、MQ场景的消息可靠性保证,包括producer端的重试机制、broker侧的持久化、consumer端的ack机制等。 7、灰度发布,能支持按机器维度进行小流量部署,观察系统日志和业务指标,等运行平稳后再推全量。 8、监控报警:全方位的监控体系,包括最基础的CPU、内存、磁盘、网络的监控,以及Web服务器、JVM、数据库、各类中间件的监控和业务指标的监控。 9、灾备演练:类似当前的“混沌工程”,对系统进行一些破坏性手段,观察局部故障是否会引起可用性问题。 高可用的方案主要从冗余、取舍、系统运维3个方向考虑,同时需要有配套的值班机制和故障处理流程,当出现线上问题时,可及时跟进处理。 ## 高扩展的实践方案 1、合理的分层架构:比如上面谈到的互联网最常见的分层架构,另外还能进一步按照数据访问层、业务逻辑层对微服务做更细粒度的分层(但是需要评估性能,会存在网络多一跳的情况)。 2、存储层的拆分:按照业务维度做垂直拆分、按照数据特征维度进一步做水平拆分(分库分表)。 3、业务层的拆分:最常见的是按照业务维度拆(比如电商场景的商品服务、订单服务等),也可以按照核心接口和非核心接口拆,还可以按照请求源拆(比如To C和To B,APP和H5)。 业务决定架构 高并发确实是一个复杂且系统性的问题,由于篇幅有限,诸如分布式Trace、全链路压测、柔性事务都是要考虑的技术点。另外,如果业务场景不同,高并发的落地方案也会存在差异,但是总体的设计思路和可借鉴的方案基本类似。 高并发设计同样要秉承架构设计的3个原则:简单、合适和演进。“过早的优化是万恶之源”,不能脱离业务的实际情况,更不要过度设计,合适的方案就是最完美的。 如何设计高可用的订单业务架构 概述 本文主要讲述了在传统电商企业中,订单系统应承载的角色,就订单系统所包含的主要功能模块梳理了设计思路,并对订单系统未来的发展做了一些思考。 1. 订单系统在企业中的角色 在搭建企业订单系统之前,需要先梳理企业整体业务系统之间的关系和订单系统上下游关系,只有划分清业务系统边界,才能确定订单系统的职责与功能,进而保证各系统之间高效简洁的工作。 2. 订单系统与各业务系统的关系 (1)对外系统: 所有给企业外部用户使用的系统都在这一层,包括官网、普通用户使用的C端,还包括给商户使用的商家后台和在各个销售渠道进行分销的系统,比如与银行信用卡中心合作、微信合作在合作商的平台露出本企业的产品。这类系统站在与客户接触的最前线,是公司实现商业模式的桥头堡。 (2)管理中后台: 每个C端的业务形态都会有一个对应的系统模块,如负责管理平台交易的订单系统,管理优惠信息的促销系统,管理平台所有产品的产品系统,以及管理所有对外系统显示内容的内容系统等。 (3)公共服务系统: 随着企业的发展,信息化建设到达一定程度后,企业需要将通用功能服务化、平台化,以保证应用架构的合理性,提升服务效率。这类系统主要给其他应用系统提供基础服务能力支持。 3. 订单系统上下游关系 由此可见,订单系统对上接收用户信息,将用户信息转化为产品订单,同时管理并跟踪订单信息和数据,承载了公司整个交易线的重要对客环节。对下则衔接产品系统、促销系统、仓储系统、会员系统、支付系统等,对整个电商平台起着承上启下的作用。 5. 订单系统的业务架构 (1)订单服务 该模块的主要功能是用户日常使用的服务和页面,主要有订单列表、订单详情、在线下单等,还包括为公共业务模块提供的多维度订单数据服务。 (2)订单逻辑 订单系统的核心,起着至关重要的作用,在订单系统负责管理订单创建、订单支付、订单生产、订单确认、订单完成、取消订单等订单流程。还涉及到复杂的订单状态规则、订单金额计算规则以及增减库存规则等。在4节核心功能设计中会重点来说。 (3)底层服务 信息化建设达到一定程度的企业,一般会将公司公共服务模块化,比如:产品,会构建对应的产品系统,代码、数据库,接口等相对独立。但是,这也带来了一个问题,比如:订单创建的场景下需要获取的信息分散在各个系统。 如果需要从各个公共服务系统调用:一是会花费大量时间,二是代码的维护成本非常高。因此,订单系统接入所需的公共服务模块接口,在订单系统即可完成对接公共系统的服务。 订单系统核心功能 1. 订单中所包含的内容信息 为了使订单系统能够对订单进行高效、精准的管理和跟踪,订单会储存关于产品、优惠、用户、支付信息等一系列的订单实时数据,来和下游系统,如:促销、仓储、物流进行交互。 以一个通用B2C商城的订单为例,梳理其包含的信息如下: 这里要注意的是订单类型,随着平台业务的不断发展,品类丰富、交易方式丰富后,需要对订单进行多维度的分类管理,同时订单类型利于订单系统的扩展性。每种订单类型将会对应一套流程及一套状态,便于对订单进行分类管理和复用。 2. 流程引擎 流程是指从平台角度出发,将订单从创建到完成的整个流转过程进行抽象,从而行程了一套标准流程规则。而不同的产品类型或交易类型在系统中的流程会千差万别,因此为了方便对订单流程进行管理,会组建流程引擎模块。 每套订单流程中会包含正向流程及逆向流程,正向流程可以比作一次顺利的网购体验过程中,后台系统之间的信息流转。逆向流程则是修改订单、取消订单、退款、退货等各种动作引起的后台系统流程,同时每个流程触发的条件又可分为系统触发和人工触发两种场景。 (1)正向流程 以一个通用B2C商城的订单系统为例,根据其实际业务场景,其订单流程可抽象为5大步骤:订单创建>订单支付>订单生产>订单确认>订单完成。 而每个步骤的背后,订单是如何在多系统之间交互流转的,可概括如下图: 订单创建: 用户下单后,系统需要生成订单,此时需要先获取下单中涉及的商品信息,然后获取该商品所涉及到的优惠信息,如果商品不参与优惠信息,则无此环节。 接着获取该账户的会员权益,这里要注意的是:优惠信息与会员权益的区别,比如:商品满减是优惠信息,SUPER会员全场9.8折指的是会员权益,一个是针对商品,另一个是针对账户。其次就是优惠活动的叠加规则和优先级规则等。 增减库存规则是指订单中的商品,何时从仓储系统中对相应商品库存进行扣除,目前主流有两种方式: 下单减库存——即用户下单成功时减少库存数量 **优势:**用户体验友好,系统逻辑简洁; **缺点:**会导致恶意下单或下单后却不买,使得真正有需求的用户无法购买,影响真实销量; 解决办法: 设置订单有效时间,若订单创建成功N分钟不付款,则订单取消,库存回滚; 限购,用各种条件来限制买家的购买件数,比如一个账号、一个ip,只能买一件; 风控,从技术角度进行判断,屏蔽恶意账号,禁止恶意账号购买。 付款减库存——即用户支付完成并反馈给平台后再减少库存数量 **优势:**减少无效订单带来的资源损耗; **缺点:**因第三方支付返回结果存在时差,同一时间多个用户同时付款成功,会导致下单数目超过库存,商家库存不足容易引发断货和投诉,成本增加。 解决办法: 付款前再次校验库存,如确认订单要付款时再验证一次,并友好提示用户库存不足; 增加提示信息:在商品详情页,订单步骤页面提示不及时付款,不能保证有库存等。 综上所述,两种方式各有优缺点,因此,需结合实际场景进行考虑,如:秒杀、抢购、促销活动等,可使用下单减库存的方式。而对于产品库存量大,并发流量没有那么强的产品使用付款减库存的方式。 将两种方式带入到销售场景中,关联商品类型、促销类型、供需关系等,灵活使用,以充分发挥计算机系统的优势。 订单支付: 用户支付完订单后,需要获取订单的支付信息,包括支付流水号、支付时间等。支付完订单接着就是等商家发货,但在发货过程中,根据平台业务模式的不同,可能会涉及到订单的拆分。 订单拆分一般分两种: 一种是用户挑选的商品来自于不同渠道(自营与商家,商家与商家); 另一种是在SKU层面上拆分订单:不同仓库,不同运输要求的SKU,包裹重量体积限制等因素需要将订单拆分。 订单拆分也是一个相对独立的模块,这里就不详细描述了。 **订单生产:**订单生产,是指产品从企业到用户这一流程的概述。如电商平台中,商家发货过程已有一个标准化的流程,订单内容会发送到仓库,仓库对商品进行打单、拣货、包装、交接快递进行配送。 **订单确认:**收到货后,订单系统需要在快递被签收后提醒用户对商品做评价。这里要注意,确认收到货不代表交易成功,相反是售后服务的开始。 **订单完成:**订单完成是指在收到货X天的状态,此时订单不在售后的支持时间范围内。到此,一个订单的正向流程就算走完了。 (2)逆向流程 上面说到逆向流程是各种修改订单、取消订单、退款、退货等操作,需要梳理清楚这些流程与正向流程的关系,才能理清订单系统完整的订单流程。 **订单修改:**可梳理订单内信息,根据信息关联程度及业务诉求,设定订单的可修改范围是什么,比如:客户下单后,想修改收货人地址及电话。此时只需对相应数据进行更新即可。 **订单取消:**用户提交订单后没有进行支付操作,此时用户原则上属于取消订单,因为还未付款,则比较简单,只需要将原本提交订单时扣减的库存补回,促销优惠中使用的优惠券,权益等视平台规则,进行相应补回。 **退款:**用户支付成功后,客户发出退款的诉求后,需商户进行退款审核,双方达成一致后,系统应以退款单的形式完成退款,关联原订单数据。因商品无变化,所以不许考虑与库存系统的交互,仅需考虑促销系统及支付系统交互即可。 **退货:**用户支付成功后,客户发出退货的诉求后,需商户进行退款审核,双方达成一致后,需对库存系统进行补回,支付系统、促销系统以退款单形式完成退款。最后,在退款/退货流程中,需结合平台业务场景,考虑优惠分摊的逻辑,在发生退款/退货时,优惠该如何退回的处理规则和流程。 (3)状态机 状态机是管理订单状态逻辑的工具。状态机可归纳为3个要素,即现态、动作、次态。 **现态:**是指当前所处的状态。 **动作:**动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。 **次态:**动作满足后要迁往的新状态,“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。 状态机的设计需要结合平台实际业务场景,将状态间的切换细化成了执行了某个动作。 以一个B2C商城的订单系统举例如下: 订单系统为了高效的对订单进行跟踪和管理,会对订单流程当中的关键节点,抽象出订单状态。而订单状态从不同用户的角度可分为,系统订单状态、商家订单状态、买家订单状态等。 对于订单系统来说,订单状态细分的颗粒度越细、越明确,订单系统管理的精度和可靠性就越高,比如:在待付款和待发货两个状态中,订单系统后台会细分为订单超时取消、订单支付失败、订单付款完成等。 因此,订单状态模块中,通常会维护状态映射表,以不同的用户角色对系统订单状态进行重新划分,以满足不同用户的需求。 除此以外,随着电商平台的不断发展,不同的业务类型,所对应的订单状态都会有所区别。所以,订单系统中一般会储存多套状态机,以满足不同的订单类型来使用。 订单系统的发展 订单系统的主体框架,和主要业务模块已基本讲完,那么随着企业的发展,业务量和业务形式不断变化,企业有可能形成多个订单系统并存以满足不同的业务需要的情况。 业务系统架构如下: 这种状况的出现,将会给平台带来非常大的发展瓶颈,如: 三个订单系统,每个订单系统处理不同类型的订单,没有统一的订单销量、订单状态信息,网站前台对订单的状态展示与控制不统一,只能是在网站前台会员中心硬代码维护一套面向会员的统一订单明细与状态数据。而无线侧上线后,由于不了解前台网站会员中心的订单状态管理逻辑,所以需要把前台网站的订单明细及状态管理再在无线应用侧再实现一遍。 三套后台订单系统与公共业务系统如会员中心、支付与财务、促销工具、客户分单等系统都需要对接一遍,公共业务处理逻辑不统一,一旦逻辑变更多个系统统一个接口都要修改一遍,接口的重复维护开发工作量大。 订单开发目前分到事业部,各个事业部只会考虑自己的逻辑,不会考虑公共架构,只会越走越远。碰到像无线这样的项目,需要对接各个事业部,无线侧应用上线进展慢。 因此未来的订单系统可拆分为订单中心与业务订单系统两个模块,以管理公司所有订单数据,并为各个模块提供统一服务。 业务系统架构如下: 最后 对于企业订单系统的搭建,并不是要做的大而全、也不是要小而精。而需要结合市场、公司、业务的实际情况来最终制定系统设计方案和产品迭代计划。 最终,和公司整体发展相互协调,相辅相成。 如何设计单点登录的架构 前言 1、SSO说明 SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 例如访问在网易账号中心(http://reg.163.com)登录之后 访问以下站点都是登录状态 网易直播http://v.163.com 网易博客http://blog.163.com 网易花田http://love.163.com 网易考拉https://www.kaola.com 网易Lofterhttp://www.lofter.com 2、设计目标 本篇文章也主要是为了探讨如何设计&实现一个SSO系统 以下为需要实现的核心功能: 单点登录 单点登出 支持跨域单点登录 支持跨域单点登出 SSO设计与实现 1、核心应用与依赖 应用/模块/对象 说明 前台站点 需要登录的站点 SSO站点-登录 提供登录的页面 SSO站点-登出 提供注销登录的入口 SSO服务-登录 提供登录服务 SSO服务-登录状态 提供登录状态校验/登录信息查询的服务 SSO服务-登出 提供用户注销登录的服务 数据库 存储用户账户信息 缓存 存储用户的登录信息,通常使用Redis 2、用户登录状态的存储与校验 常见的Web框架对于[Session]的实现都是生成一个SessionId存储在浏览器Cookie中。然后将Session内容存储在服务器端内存中。 用户登录成功之后,生成AuthToken交给客户端保存。如果是浏览器,就保存在Cookie中。如果是手机App就保存在App本地缓存中。本篇主要探讨基于Web站点的SSO。 用户在浏览需要登录的页面时,客户端将AuthToken提交给SSO服务校验登录状态/获取用户登录信息 对于登录信息的存储,建议采用Redis,使用Redis集群来存储登录信息,既可以保证高可用,又可以线性扩充。同时也可以让SSO服务满足负载均衡/可伸缩的需求。 对象 说明 AuthToken 直接使用UUID/GUID即可,如果有验证AuthToken合法性需求,可以将UserName+时间戳加密生成,服务端解密之后验证合法性 登录信息 通常是将UserId,UserName缓存起来 3、用户登录/登录校验 用户登录后Authtoken保存在Cookie中。 domian= test. com 浏览器会将domain设置成 .test.com, 这样访问所有*.test.com的web站点,都会将Authtoken携带到服务器端。 然后通过SSO服务,完成对用户状态的校验/用户登录信息的获取 登录信息获取/登录状态校验 4、用户登出 用户登出时要做的事情很简单: 服务端清除缓存(Redis)中的登录状态 客户端清除存储的AuthToken 5、跨域登录、登出 前面提到过,核心思路是客户端存储AuthToken,服务器端通过Redis存储登录信息。由于客户端是将AuthToken存储在Cookie中的。所以跨域要解决的问题,就是如何解决Cookie的跨域读写问题。 解决跨域的核心思路就是: 登录完成之后通过回调的方式,将AuthToken传递给主域名之外的站点,该站点自行将AuthToken保存在当前域下的Cookie中。 登出完成之后通过回调的方式,调用非主域名站点的登出页面,完成设置Cookie中的AuthToken过期的操作。 跨域登录(主域名已登录) 跨域登录(主域名未登录) 跨域登出 此上文字转载自https://ken.io,作者文笔和思路很nice 【关键点】业务站点B怎么验证信任 首先业务站点A去访问受限资源,跳转到sso认证中心https://login.sso.com/login?redirectURL=https://www.a.com/center,用户登录成功之后,sso认证中心生成一个令牌(如token)返回给系统A。 其实这时候,浏览器中存有两个Cookie,一个是业务站点A的,一个sso认证中心的,并且对应的session都是登录状态。 然后业务站点B第一次去访问受限资源,依然要先跳转sso认证中心,https://login.sso.com/login?redirectURL=https://www.b.com/center,此时,sso认证中心发现携带的Cookie(sso认证中心域名的Cookie)对应的session是登录状态的,那就直接省去登录的过程,直接把用户的令牌返回给系统B就行了。 此时浏览器中有三个Cookie,对应的session都是登录状态的。 如何应对大流量高并发? 硬件方面,提升磁盘,cpu,等性能 禁止外部盗链 控制大文件的下载 负载均衡 分布式 集群 主从数据库 分布式数据库 分布式缓存 项目设计 经理面试的初步考察问题 会问一些你做的项目的用户量、PV、吞吐量、相关难点和解决方法等 假如你现在是12306火车订票的设计师,你该如何设计满足全国人民订票? 参考答案:https://www.zhihu.com/question/20017917 假如有1亿用户的访问量,你的服务器架构是怎样的? 用户信息的存储方案如何设计? 点评: A、此问答也比较泛,考察的是对解决一个问题的分析思路; B、从哪些方面,哪些层面对问题进行考察; C、对于想到的方面和层面,再细致挖掘考虑是否严谨; D、用户信息存储考察面试者对用户信息业务本身的了解、存储方式及其特点的了解。 从你的经验方面谈一下如何构建高性能web站点? 需要哪些环节? 步骤? 每个步骤需要注意什么如何优化等? 参考提示点:带宽、DNS、CDN、gzip、负载均衡以及数据库等。 ## 如何设计秒杀架构? 整体思路 业务上适当规避 根据某些规则对部分用户直接返回没抢到。比如有些用户曾经被系统识别为恶意用户、垃圾用户、僵尸用户,直接告诉用户已经抢完 分散不同客户端打开活动入口的时间。比如将1秒内的流量分散到10秒 技术上硬核抗压 限流策略。比如在压力测试中我们测到系统QPS达到了极限,那么超过的部分直接返回已经抢完,通过Nginx的lua脚本可以查redis看到QPS数据从而可以动态调节 异步削峰。对Redis中的红包预减数量,立即返回抢红包成功请用户等待,然后把发送消息发给消息队列,进行流量的第二次削峰,让后台服务慢慢处理 服务逻辑。比如业务逻辑是使用事务控制对数据库的创建订单记录,减库存的操作,那么创建操作要放到减库存操作之前,从而避免减数量update的行锁持有时间 机器配置。当然是服务器机器配置约高越好,数据库配置越猛越好,高并发抢红包主要是CPU与网络IO的负载较高,要选择偏向CPU与网络IO性能的机器 架构和实现细节 前端模块(页面静态化、CDN、客户端缓存) 排队模块(Redis、队列实现异步下单) 服务模块(事务处理业务逻辑、避免并发问题) 防刷模块(验证码、限制用户频繁访问) 模块解析 前端模块 页面静态化,将后台渲染模板的方式改成使用HTML文件与AJAX异步请求的方式,减少服务端渲染开销,同时将秒杀页面提前放到CDN 客户端缓存,配置Cache-Control来让客户端缓存一定时间页面,提升用户体验 静态资源优化,CSS/JS/图片压缩,提升用户体验 排队模块 对Redis中的抢购对象预减库存,然后立即返回抢购成功请用户等待,这里利用了Redis将大部分请求拦截住,少部分流量进入下一阶段 如果参与秒杀的商品太多,进入下一阶段的流量依然比较大,则需要使用消息队列,Redis过滤之后的请求直接放入到消息队列,让消息队列进行流量的第二次削峰 服务模块 消息队列的消费者,业务逻辑是使用事务控制对数据库的下订单,减库存操作,且下订单操作要放到减库存操作之前,可以避免减库存update的行锁持有时间 防刷模块 针对恶意用户写脚本去刷,在Redis中保存用户IP与商品ID进行限制 针对普通用户疯狂的点击,使用JS控制抢购按钮,每几秒才能点击一次 在后台生成数学计算型的验证码,使用Graphics、BufferedImage实现图片,ScriptEngineManager计算表达式 异常流程的处理 如果在秒杀的过程中由于服务崩溃导致秒杀活动中断,那么没有好的办法,只能立即尝试恢复崩溃服务或者申请另寻时间重新进行秒杀活动 如果在下订单的过程中由于用户的某些限制导致下单失败,那么应该回滚事务,立即告诉用户失败原因 总结 原则 业务优化思路:业务上适当规避 技术优化思路:尽量将请求拦截在数据库的上游,因为一旦大量请求进入数据库,性能会急剧下降 架构原则:合适、简单、演化(以上内容是最终版本,初版可以说没有用到队列,直接使用缓存-数据库这样的架构) 难点 如何将高并发大流量一步步从业务和技术方面有条不紊地应对过来 如何在代码中处理好异常情况以及应急预案的准备 坑 以上的解决方案能通过利用Redis与消息队列集群来承载非常高的并发量,但是运维成本高。比如Redis与消息队列都必须用到集群才能保证稳定性,会导致运维成本太高。所以需要有专业的运维团队维护。 避免同一用户同时下多个订单,需要写好业务逻辑或在订单表中加上用户ID与商品ID的唯一索引;避免卖超问题,在更新数量的sql上需要加上>0条件 优化 将7层负载均衡Nginx与4层负载均衡LVS一起使用进一步提高并发量 以上是应用架构上的优化,在部署的Redis、消息队列、数据库、虚拟机偏向选择带宽与硬盘读写速度高的 提前预热,将最新的静态资源同步更新到CDN的所有节点上,在Redis中提前加载好需要售卖的产品信息 使用分布式限流减少Redis访问压力,在Nginx中配置并发连接数与速度限制