为什么Redis日志持久化的COW机制不用子线程而是用子进程呢
今日的话题主要集中在异步持久化保存日志,如使用 bgsave 保存RDB,或者是 bgrewriteaof 保存AOF。
使用这两个命令的时候,父进程会阻塞,调用fork()创建出一个子进程来完成异步的持久化,照理来说创建一个子线程的开销不是比创建一个子进程的开销更加的小吗,毕竟子线程共享父进程的一些资源(内存、文件描述符等),就不涉及复制一份页表之类的活了。
确实如此,但是如果父进程需要修改这份共享内存,异步子线程和修改子线程之间就会发生冲突,对于线程间的并发问题,我们通常使用锁机制来保证线程安全问题,这势必就会影响到持久化的效率;
那如果我们考虑使用子进程呢,虽然说在创建子进程的时候的开销会比创建子线程的开销略大,但是子进程和父进程对应的那个共享内存是只读的,当父进程需要修改这个共享内存的时候,就会使用COW(本质上是CPU的写保护中断,操作系统就会在中断处理函数里面进行物理内存的复制以及更改页表),这个时候主线程会被阻塞,先来拷贝一份冲突的数据到新的物理地址,修改子进程的页表,指向这个新的物理地址,这样父进程修改原来的物理地址的数据就和子进程复制新的物理地址的旧数据不会冲突了,如 ...
简单了解什么是I/O多路复用
进程在不同的主机间通信无论是使用TCP还是UDP,都需要用到socket,以TCP为例,服务端会调用socket()函数创建一个网络协议为IPv4,传输协议为TCP的socket,接下来给socket绑定IP地址和端口,绑定端口是为了可以找到相应的进程,绑定IP是为了知道是哪个网卡接收到的数据包,每个网卡都有一个IP地址,接下来会调用listen()监听端口,调用accept()等待客户端连接到来,accept获取到一个已连接的socket,就可以进行客户端和服务端之间的进程通信了。
多进程方式对于每个accept()获取的已连接的socket,父进程都会fork()一个子进程,子进程会共享父进程的文件描述符、内存等数据,但是进程的创建和销毁的代价较大,并且一个进程会占用较多的系统资源,系统承受不了太大的并发量。
多线程方式对于每个accept()获取的已连接的socket,父进程调用函数创建子线程,把文件描述符等资源传递给子线程,虽然线程的创建和销毁的代价比进程略小,因为线程只独享栈、程序计数器等私有资源,会共享进程的一些内存、文件等资源,但是系统也承受不了太大的并发量。
I ...
简单了解什么是零拷贝
在讨论零拷贝之前,我们需要了解传统传输文件的问题才能更好的理解零拷贝。
传统的文件传输需要经过以下几步:
read()系统调用陷入内核态
CPU拷贝磁盘缓冲区的数据到Page Cache
CPU拷贝Page Cache的数据到用户态
write()系统调用陷入内核态
CPU拷贝用户态的数据到Socket缓冲区
CPU拷贝Socket缓冲区的数据到网卡
总共是2次系统调用以及4次CPU拷贝。
这里CPU会被频繁的中断,所以引入了DMA拷贝技术,代替CPU在Page Cache和磁盘缓冲区之间的数据拷贝,减少了2次CPU的数据拷贝,但是还是存在2次系统调用以及2次CPU拷贝。
这时零拷贝技术就会派上用场,零拷贝技术主要是为了减少CPU拷贝次数而存在的,那零拷贝技术有两种实现方式:
mmap() + write() ,也就是用mmap()代替了read():这里会把Page Cache和用户缓冲区共享内存,CPU直接把数据拷贝到Socket缓冲区就好了,减少了1次CPU拷贝。
sendfile():这时是真正意义上的CPU 0次拷贝,数据拷贝和用户态没有关系,数据从Page Cache ...
简单了解对象实例化过程
开始我们知道对象的创建过程包括以下几步:
类加载
实例化
初始化
引用赋值
这几步一般来讲是顺序进行的,不过由于硬件层面的一些优化,可能会发生指令重排的情况,导致3、4步调换位置,我们在程序中就会看到NPE空指针异常,通常可以用volatile修饰这个变量来解决,不过我们今天主要谈谈这个实例化过程,也就是给对象分配内存的过程。
在Java程序,我们绝大多数的对象都在堆内分配内存,只有少部分的对象经过JIT的逃逸分析之后会在栈上分配内存,那对于大多数对象来说,具体是在堆内的什么位置分配内存呢,我们需要首先来看看堆内更加详细的图
堆内的对象会被分代存储,一个是新生代(包括Eden、From、To)和一个老年代,他们的内存大小比例默认是新生代:老年代 = 2:1,在新生代内部,Eden:From:To的大小比例默认是8:1:1。
新对象一般会在Eden进行分配,除了一些大对象(数组、字符串)直接进入到老年代,这样可以减少GC后对这些大对象的移动(因为新生代使用的是标记-整理算法),在分配对象内存的时候我们需要考虑Eden还有哪些空闲的区域,这里JVM针对不同的垃圾回收器用了两 ...
简单了解事务和日志和锁的关系
先来说说什么是事务,事务是数据库并发控制的基本单位,事务内有一系列的逻辑操作,需要一起成功或者一起失败,让数据库从一个一致性状态转换到另一个一致性状态,事务和日志和锁的关系在于事务的特性需要依靠这些机制来保证。
我们可以先看看事务的四大特性:
原子性(使用undo log):一个事务中的所有操作都是不可分割的,要么全部完成,要么全部失败
隔离性(锁 or MVCC):防止多个事务间并行操作时,其它事务对本事务操作数据的影响
持久性(使用redo log):事务提交后,数据被保存在磁盘中,数据不会丢失
一致性(所追求):结果符合约束,比如有钱才能转账、转账双方加起来银行账户的钱总和总是不变
先来说说原子性,我们知道活跃的事务一般有两种结果,失败回滚或者成功提交,对于失败回滚,我们会用到undo log,对于事务内的每次逻辑操作(也就是一条SQL语句),会把未修改前的快照行数据保存到undo log页,这也就形成了undo log版本链(本质上是每个行记录的隐藏字段roll_pointer指针指向了undo log页内的先前的快照行记录)。
当发生错误需要事务回滚的时候,我们根据und ...
简单了解Redis常用数据类型的应用场景
String
存储序列化后的对象
计数,整数的加减 incr decr
分布式锁 setnx
session共享
List消息队列 lpush + brpop
Hash存储对象,这里和 String 的区别是便于修改value
Set
存储文章对应的点赞等
交集、差集操作,共同关注 sinter、sdiff
随机抽奖 srandmember、spop
zSet排序:
取前几位(排行榜)zrange
取某个分数区间 zrangebyscore
取某些字典序的(名字、电话本)zrangebylex
BitMap
两个状态(登录登出、签到未签到)
某段时间打卡总次数(key 设置为某个月,offset表示哪一天)
连续打卡(每天的BitMap的offset表示用户ID,几天的BitMap做&运算)
简单了解一个数据包在网络的一生
在主题之前,我想先谈谈目前计算机的网络模型,主要谈谈 TCP/IP 模型:
应用层:产生最原始的数据,常见协议如 http、ftp、websocket、DNS、QUIC
传输层:传递应用层的数据给网络层,必要时进行切割,常见协议如 TCP、UDP
网络层:目标寻址,常见协议如 IP、ARP、ICMP
网络接入层:通过物理链路传输比特流到目标主机
接下来就正式进入主题了
1.查看浏览器缓存没有缓存的话才会继续往下走,否则直接使用缓存的资源
2.解析URL和DNS解析解析URL,如果应用层协议使用http,默认端口号是80
DNS解析:
第一步,先查询本地DNS缓存,如果没有这个域名对应的IP,再进行下一步
第二步,先询问本地的DNS服务器,交给本地的DNS服务器去解析这个域名,他会逐级找比较厉害的DNS服务器,最后找到域名对应的IP,比如对于www.baidu.com,他会先找.com,再找baidu.com,最后找到www.baidu.com
3.考虑TCP和TLS握手对于一些重要的数据(不允许丢失),我们需要先建立TCP连接,他能保证我们的数据安全到达对方 ...
简单了解Innodb引擎如何加行锁
我们说对于MYSQL加锁一般是说Innodb加锁,因为一定版本号后的MySQL默认的存储引擎就是Innodb,我们可以先来看看Innodb和MyISAM的区别
Innodb
MyISAM
事务
√
×
行锁
√
×
索引
索引和数据耦合在一起
索引和数据分开存储,对于所有索引都是二级索引
文件
.frm表结构文件、.ibd数据文件
.frm表结构文件、.myd数据文件、.myi索引文件
MySQL有三种锁:全局锁、表级锁、行级锁,对于行锁又有以下三种:
记录锁 Record Lock:单点区域,读写互斥
间隙锁 Gap Lock:左开右开区域,读写不互斥
临键锁 Record Lock:左开右闭区域,读写互斥
对于不同的隔离级别,锁也会有所区别,比如在读已提交的隔离级别,只有记录锁,到了可重复读的隔离级别,才会有间隙锁以及临键锁
对于行锁来说,事务提交才会释放锁
如何加行锁?
Innodb引擎加行锁都是加在索引上的,如果没有索引条件,默认会锁住全表(相当于锁在了其它索引或者那个隐藏的id索引)
如何避免锁住全表呢?
写SQL语句的时候使用where加 ...
如何交替打印输出0~100
两个线程交替打印0~100synchronized1234567891011121314151617181920212223242526272829303132333435public class JUCTest { private static final Object monitor = new Object(); private static volatile int count = 0; private static final int MAX = 100; public static void main(String[] args) { Thread t1 = new Thread(new Print(), "thread-1"); Thread t2 = new Thread(new Print(), "thread-2"); t1.start(); t2.start(); } static class Print ...
线上问题的排查
CPU使用率飙升使用 jdk 自带的命令 jstack
12345671.top // 查看所有进程的CPU使用率2.top -Hp PID // 查看某个进程内的线程的CPU使用率3.printf '%x\n' TID // 查看线程ID对应的16进制数4.jstack PID | grep -A 200 TID(16进制)
使用 Alibaba 的 arthas
代码死锁使用 jdk 自带的命令 jstack
模拟的死锁代码
12345678910111213141516171819202122232425262728293031323334353637383940public class DeadLock { private static final Object obj1 = new Object(); private static final Object obj2 = new Object(); public static void main(String[] args) { Thread t1 = ...