netty连接websocket问题记录
背景利用netty创建服务端,然后与websocket建立连接,发现一直出现能够接收到前端的请求,但是无法建立正确的websocket连接。
正确代码如下1234567891011121314151617181920212223242526272829303132public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("收到客户端消息,此时一共有多少个用户建立连接:" + NettyServer.user.keySet()); if (msg instanceof FullHttpRequest) { FullHttpRequest request = (FullHttpRequest) msg; if (!request.decoderResult().isSuccess() || !"websocket".equals(request.he ...
Java中的NIO与I/O多路复用
Java的NIO
它的大致模型如上图所示,采用了I/O多路复用的Reactor模型。
单线程模型上图展示的是单线程模型,也就是说Reactor这里是单线程的,每当有一个事件过来,只有一个线程可以处理该事件,当有下一个事件需要处理时,必须等待上一个事件结束后,才能够被处理。这里与Redis 的模型一致。Redis是采用了一个队列,存储所有的待处理命令,然后有一个线程依次从队列中取出执行。
多线程模型为了提高处理效率,也有Reactor多线程模型,即Reactor这里变为多线程。
由上图可以看到,当有一个事件变的待处理时,不再是原来的一个线程,而是从线程池中选择一个线程去处理对应的事件,提高了处理效率。
MySQL的组提交
为什么要组提交因为MySQL为了保证redo log 和binlog的数据一致性,而采用了两阶段提交。即redo log 会先处理prepare状态,然后写binlog,当binlog写完之后,才去修改事务的最终状态为commit状态。
如果配置了双1,那么就会在prepare阶段,将redo log写入磁盘,而binlog也要刷盘,之后才可以提交事务,即一次事务要刷两次盘,影响性能。
并发事务的影响则更为明显,因为为了保证事务的顺序,需要加锁去保证,即一个事务完全写完之后,才能去写第二个。
因此,出现了组提交的优化。
组提交
以下的分析都是针对于双1配置。
在组提交中,prepare阶段不发生太多的变化,只是写完redo log 后,不再执行刷盘。
然后将commit阶段做了流水线处理,分为了三步,一共有3个队列,每个队列分别加锁:
第一个队列,也就是flush stage:将存的一批事务,按其顺序将其redo log 刷盘,然后写binlog。但是这里的binlog只是写cache(缓存),不刷盘。
第二个队列,sync stage:将队列中积攒的不同事务的binlog 刷盘 ...
Condition实现原理
这篇文章也是参考了 https://javadoop.com/post/AbstractQueuedSynchronizer-2 这篇文章,总结思路。
Condition的实现原理condition的一个简单的demo如下:
12345678910111213141516171819202122232425262728293031323334353637383940class BoundedBuffer { final Lock lock = new ReentrantLock(); // condition 依赖于 lock 来产生 final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; // 生产 public void put(Object x) ...
AQS的执行过程
本文是参考了https://www.javadoop.com/post/AbstractQueuedSynchronizer 该篇博客,然后根据他写的思路将加锁的过程转化为流程图。
流程图不太标准。
加锁
解锁释放的流程就不再画图,过程如下:
首先调用release(1)方法。
该方法内部,会先调用tryRelease(1)方法。
tryRelease方法中,会先判断当前线程是否是持有锁的线程,如果不是就返回异常,如果是,则判断释放一次锁后锁的标志位是否为0,如果为0则意味着没有重入,直接全部释放,否则不能完全释放。
如果调用tryRelease(1)成功,则会回到release方法中。后续会先获取到头节点,并判断状态,如果都没事,就唤醒后继节点。
123456if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true;}
unparkSuccess ...
Redis6.0新特性之多线程
多线程
需要注意的是,Redis 多 IO 线程模型只用来处理网络读写请求,对于 Redis 的读写命令,依然是单线程处理。
要采用多线程原因:随着硬件性能提升,Redis 的性能瓶颈可能出现网络 IO 的读写,也就是:单个线程处理网络读写的速度跟不上底层网络硬件的速度。
多线程下处理请求流程如下:
一、服务端和客户端建立 Socket 连接,并分配处理线程
首先,主线程负责接收建立连接请求。当有客户端请求和实例建立 Socket 连接时,主线程会创建和客户端的连接,并把 Socket 放入全局等待队列中。紧接着,主线程通过轮询方法把 Socket 连接分配给 IO 线程。
二、IO 线程读取并解析请求
主线程一旦把 Socket 分配给 IO 线程,就会进入阻塞状态,等待 IO 线程完成客户端请求读取和解析。因为有多个 IO 线程(这里就体现了多线程)在并行处理,所以,这个过程很快就可以完成。
三、主线程执行请求操作
等到 IO 线程解析完请求,主线程还是会以单线程的方式执行这些命令操作。
Synchronized锁升级的过程
Synchronized锁升级的过程首先是偏向锁,该锁是偏向某一个线程,在加锁时如果发现只有一个线程在竞争锁,该锁会直接偏向该线程。并在线程栈中创建一个LR(锁记录),并将markword拷贝到LR中。同时锁中(或者说被加锁对象)的markword中的指针也会指向加锁线程栈中的LR。
因为Synchronized是可重入锁,所以说LR可以用来表示加了几次锁,每当解锁时,就从线程栈中弹出一个LR。
如果有多个线程同时竞争锁时,就会将锁升级为轻量级锁(自旋锁)。
当一个线程加偏向锁成功时,另一个线程过来抢占锁发现已被占用,此时这把偏向锁会被撤销,然后两个线程通过自旋 + CAS的方式抢占锁,谁抢到就将markword的锁记录指向对应线程的LR,代表该线程获得锁,另一个线程会一直自旋,当自旋到一定次数后,就会将该锁升级为重量级锁。
对于重量级锁,早期加锁以及释放锁的过程需要用户态与内核态的切换。因为它需要向系统申请锁,申请成功后还需要将这把锁从内核态返回给用户态。
原因:JVM中synchronized重量级锁的底层原理monitorenter和monitorexit字节码依赖于底层的操 ...
乐观锁踩坑记录
业务场景背景:Excel的导入导出。由于数据量大,为了优化导出时的分页查询,插入数据时采用的是手动维护的自增id。
具体场景:如果有多个专家同时导入题库,如何保证手动维护自增id的不重复。
分析:由于同时多个专家同时导入的情况并不多见,为了不影响绝大多数的场景的性能,这里准备使用乐观锁来解决并发。
具体代码使用EasyExcel,具体策略是单线程读取,然后使用线程池处理数据插入。核心的LockCityDataListener代码如下:
注意,这一版代码是存在问题的。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231 ...
Redis变慢的原因
命令使用这里主要涉及到一些时间复杂度比较高的查询或者聚合计算,要根据实际情况,使用合适的命令。可以通过查看 Redis 的响应延迟,来判断Redis是否真的变慢。
文件系统AOFRedis虽然是内存数据库,但为了保证数据不丢失,需要把数据写入到磁盘当中。而AOF的刷盘策略有以下三种:
当使用evertsec时,允许丢失一秒的数据。这时候,使用后台子线程来进行fsync,并且是异步执行。这种情况下对主进程的影响并不会很大。
但是当写回策略配置为always时,情况就不一样了。为了确保每条数据都写入磁盘,Redis就不能使用后台子线程来进行fsync,因为这样无法知道是否写入完成。所以只能由主进程来执行,这大概率会影响性能。
另一方面,随着AOF文件的增大,还需要执行AOF重写,AOF重写采用的是后台子线程来进行,但是需要注意的是:
AOF 重写会对磁盘进行大量 IO 操作,同时,fsync 又需要等到数据写到磁盘后才能返回,所以,当 AOF 重写的压力比较大时,就会导致 fsync 被阻塞。虽然 fsync 是由后台子线程负责执行的,但是,主线程会监控 fsync 的执行进度。
当主 ...
Redis的切片集群
切片集群切片集群,也叫分片集群,就是指启动多个 Redis 实例组成一个集群,然后按照一定的规则,把收到的数据划分成多份,每一份用一个实例来保存。
这里需要注意的是,这多个实例对外其实是提供一个服务,因为每个实例只有所有数据的一部分,他们内部是5个,客户端在使用时其实只会感知到1个。
如何保存更多数据Redis应对数据量增多有两种办法:
纵向扩展(scale up):指的是升级单个Redis实例的配置资源,包括增加容量,使用更高配CPU等。
横向扩展(scale out):增加Redis实例的个数。
纵向扩展实现起来最简单,换一个机器就可以,但是会面临问题,即内存越大,单实例存储的数据就越多,在进行RDB持久化时,主线程fork子进程时就可能阻塞。如果不要求持久化,是一个不错的选择。
而针对于特别大的数据量,更好的方案是切片集群。
数据切片和实例的对应分布数据和实例之间如何对应,与Redis Cluster有关。
具体来说,Redis Cluster 方案采用哈希槽(Hash Slot,接下来我会直接称之为 Slot),来处理数据和实例之间的映射关系。在 Redis Cl ...