一个人内耗,说明他活在过去;一个人焦虑,说明他活在未来。只有当一个人平静时,他才活在现在。
日常
1、起床6:00
2、健身1.5h
今天练了背部和胸,然后爬坡半小时
3、LeetCode刷了1题
- 二叉树的最小深度:BFS
- 使用BFS广度优先遍历,用存放TreeNode的队列来实现,队列用ArrayList< TreeNode >数组以及头尾指针head和rear来实现,首先将root加入队列中,入队要让rear++,出队要让head++,当head!=rear时,说明当前队列中有未遍历的元素
- 求最小深度就是从节点开始层序遍历(BFS),并记录当前遍历的层数,当遇到第一个叶子节点时就是最小深度,故每次要遍历一层的所有元素,所以每一层中先记录当前层的范围(pos = rear),然后再遍历当前层的所有节点(从head->pos),当某个节点为叶子节点时,直接返回当前的层数就是最小深度,如果某个节点非叶结点,则分别判断左右孩子是否存在,存在则加入队列(rear++),此时必须要记录当前层的位置,不可以用rear,因为每次加入rear就会改变,不再是当前层,当前层遍历结束后,深度+1,继续遍历下一层
- 岛屿问题:BFS/DFS
- 岛屿问题就是使用BFS或者DFS遍历所有的节点,而且不进行回溯,遍历后就标记当前节点已经遍历,不需要进行回溯
- 如求岛屿个数时,先遍历所有的节点,对未遍历的节点使用DFS且不回溯,此时就是遍历一个岛屿,进行几次DFS就是几个岛屿
- 求岛屿最大面积时,在DFS的同时使用全局变量进行计数,可以先遍历再计数,也可以先计数再遍历,然后每遍历一个岛屿就判断面积是不是最大
- 求孤立岛屿时,对边界上的岛屿进行DFS并标记已经遍历过(直接修改为海洋即可,不需要重新设置一个新的数组),然后遍历所有位置就可以找打孤立岛屿
4、复盘
不复盘等于白学!!!
学习
1. SpringBoot集成Redis
- 中间件总体概述
- Java程序连接Redis必须使用中间件,类似于连接MySQL的JDBC,将对Redis的操作封装为API供Java程序进行使用
- 第一代是Jedis,然后优化为Lettuce,现在是redis对Lettuce进行整合为RedisTemplate
- 常见问题
- 因为是Java远端服务器连接Redis服务器,故要在配置文件中实现相应的配置,关闭redis服务端的绑定和保护模式等
- Jedis
- 是Redsi提供的面向Java客户端,实现了对各类API的封装调用
- 创建SpringBoot微服务:约定>配置>编码
- 建项目
- 改POM,引入Jedis的POM依赖
- 写YML,实现对redis服务端连接需要的一些配置,Java程序作为redis客户端
- 主启动
- 业务类
- Jedis就是将对redis客户端的各种操作封装为API
- Lettuce:保证只有一个redis连接,多个线程共享
- 当使用Jedis时,此时每个线程都要创建一个Jedis实例去连接Redis,会造成大量开销反复关闭Jedis,而且也是线程不安全的,多个线程并发访问redis
- 故引入了Lettuce,底层使用的是Netty,当有多个线程连接Redis时,保证只有一个Lettuce连接,多个线程共享,而且也是线程安全的
- 改POM引入Lettuce依赖
- 业务类:使用commands执行各种操作
- 就是Jedis的连接池
- RedisTemplate
- SpringBoot连接Redis单机
- 建项目
- 改POM
- 在RedisTemplate依赖中包含Lettuce的依赖
- 写YML
- 要配置redis服务器的IP和端口号
- 主启动
- 业务类
- RedisTemplate默认配置会出现中文乱码,故要进行配置序列化,或者使用StringRedisTemplate,然后开启客户端时使用–raw
- 要创建RedisConfig配置类,如果不配置使用默认时,则会出现中文乱码,故要对RedisTemplate进行配置实现序列化
- 当使用StringRedisTemplate时key不会出现序列化问题,因为此时使用的是StringSerializer序列化,但value仍然会出现乱码,可以在开启redis客户端时加上 --raw 来解决中文乱码
- RedisTemplate配置类实现序列化
- ![[Pasted image 20241029075631.png]]
- SpringBoot连接Redis集群
- SpringBoot连接redis集群时,一定要在配置文件中开启Lettuce的动态刷新,否则无法动态感知拓扑结构
- 连接集群时要注意开启RedisTemplate集群节点拓扑的动态刷新,否则SpringBoot无法动态感知,连接集群时一定要开启动态刷新,
- 修改YML使其连接redis集群,修改YML配置文件来设置redis集群的配置
- 此时直接按照单机的业务类也可正常运行,但要开启RedisTemplate的动态刷新,复制无法动态感知
- 当某个主机master宕机时,集群Cluster会自动监控并完成主从切换,redis端正常运行,但此时Java客户端再访问时会出现故障,显示连接不到指定的已经宕机的master
- 因为SpringBoot客户端没有动态感知到RedisCluster的最新集群消息,故不知道master已经宕机,无法正常访问
- Redis默认连接池使用的是Lettuce,当redis集群节点发生改变时,Lettuce默认是不会动态刷新节点信息,故SpringBoot没有动态感知到节点的改变
- 解决:开启自动刷新节点集群拓扑动态感应,SpringBoot3已经解决这个问题,默认开启动态刷新
- SpringBoot连接Redis单机
2. Redis单线程 VS 多线程
- Redis4之前为什么选择单线程
- 单线程是指所有的操作都是原子操作,而且都是顺序执行,当前一个指令未完成时会阻塞后面的指令的执行
- redis4.0之前都是单线程,之后慢慢引入多线程,6/7之后才稳定为多线程
- redis的工作线程是单线程的,但整个redis是多线程的,只有一个主线程执行命令,有多个IO线程处理请求的socket解析的数据传输![[Pasted image 20241029085501.png]]
- 为什么redis4之前要使用单线程,因为单线程安全,避免上下文切换,且性能瓶颈不是CPU,
- 为什么又逐渐加入了多线程
- 单线程虽然提供了多路复用IO和非阻塞IO,但如果某个客户端的指令非常耗时(del bigkey),则会影响其他客户端指令的运行,造成卡顿
- 故引入多线程异步处理指令
- Redis多线程特性和IO多路复用
- 使用多IO线程处理网络请求,使用主线程串行执行命令
- Java程序连接Redis也是socket连接,此时也会分配一个FD,Redis服务端会使用多个IO线程来解析多个FD对应的客户端的请求,但只有主线程会执行命令读写数据
- 对于Redis的主要性能瓶颈是网络带宽(io)或者内存,而不是CPU
- redis6/7引入真正的多线程:使用多IO线程来并行处理网络请求,使用单线程执行读写操作命令
- redis多线程处理网络IO请求,一个主线程执行读写操作(单线程原子操作),多个IO线程建立连接并解析请求
- 多个IO线程对请求进行解析,由主线程进行执行操作,然后把数据放入缓冲区,再由IO线程将数据返回
- Unix网络编程中的五种IO模型
- 阻塞IO:一直等待结果返回
- 非阻塞IO:先返回回去,不用一直等待
- IO多路复用:只用一个服务端进程通过epoll可以同时处理多个socket连接
- 只有一个redis-server服务端,可以连接多个redis-cli客户端,Java程序访问redis也是客户端,一个服务端只有一个主线程来执行命令,但是有多个IO线程处理网络请求
- 不会为每一个socket连接分配一个线程或进程,而是分配一个FD,并注册进入epoll中,由epoll监控每个socket,有请求就用异步IO线程处理,从而实现一个服务端进程同时处理多个socket请求
- 即一个服务端redis-server进程可以同时处理多个套接字描述符(每个描述符对应一个访问该服务端的客户端redis-cli)
- 实现IO多路复用的模型由select、poll、epoll
- 文件描述符FD就是一个索引值,指向每个进程文件的打开记录表,就是进程每打开一个文件就会返回一个文件描述符FD指向这个文件,这样进程就可以通过FD找到打开的这个文件
- IO多路复用就是一个或者一组线程处理多个TCP的连接
- 每次TCP连接发送到线程时为其分配一个FD,并将FD放到等待队列中,当某个FD准备就绪时,就会解析并处理该连接的请求
- IO多路复用
- epoll实现IO多路复用
- 每个socket连接到服务端后都会分配一个FD描述符,并把FD放到等待队列中,每次由socket发出请求,为其分配一个IO线程处理,实现一个主线程同时处理多个socket套接字
- 如何实现处理多个socket:轮询、一对一、一对多(IO多路复用)
- epoll模型实现多路IO复用:为每个连接该服务端的请求分配一个FD,并注册进epoll中,由epoll对每个socket进行监听,当有消息到达时才会对相应的socket进行处理,事件驱动
- 此时单个线程来记录追踪每一个socket的状态来同时管理多个IO流,一个服务端进程可以同时管理多个套接字FD
- redis之所以快就是因为IO多路复用+epoll函数使用,不仅仅是内存、数据结构和单线程,redis之所以快就是用了IO多路复用和epoll
- 小总结
- 主线程只执行命令(单线程安全),由多个IO线程解析发送socket网络数据,解决网络问题
- redis6/7后引入了多线程,此时工作线程仍是单线程,但整体是多线程;主线程只进行执行socket的命令,与socket有关的解析和读写由专门的IO线程进行处理;即与redis数据库有关的读写操作由主线程单独处理,从而实现原子性,但与客户端socket连接的数据IO由专门的IO线程进行异步处理,从而提高并行性
- socket连接就是客户端向服务端发送命令,读写数据并向客户端返回结果,redis主线程读写数据是阻塞的,此时主线程只进行读写操作,但读到用户态后由IO线程将数据写回客户端,此时redis主线程会继续处理别的socket请求
- redis的工作线程是单线程的,但整个redis是多线程的,有多个IO线程来处理多个socket的网络请求
- redis之所以快是因为主要性能瓶颈不在CPU和内存,而是网络,而通过引入多线程,使得主线程只执行命令,多个IO线程解析发送网络数据,从而提高性能
- Redis7默认不开启多线程(io-thread)
- 通过在配置文件中设置配置项来开启多线程,修改配置文件后要重启redis-server才会生效
- redis-server服务端是通过配置文件来启动的,服务端只有一个,但可以有多个客户端redis-cli、Java来连接该服务端,当开启多线程时,为每个请求socket连接分配一个FD放入epoll中,当有请求时会分配一个IO线程对请求进行解析,然后把指令发送给唯一的主线程进行处理并将结果发送给IO线程,由IO线程返回给客户端,从而实现多线程
- 如果CPU开销不大但吞吐量上不去,则可能没有开启redis7的多线程机制,多线程是默认关闭的,通过在服务端redis-server的配置文件中开启多线程
- Redis引入多线程只是为了让Redis越来越快(只是让IO读写变为了多线程,命令执行仍然是主线程串行执行,是线程安全的)
3. BigKey
- 面试题
- MoreKey案例(简历加分)
- 向Redis中插入大量数据
- 先使用linux操作创建一个文件,里面存放了一百万条set指令,使用管道指令将所有的指令在Redis中运行
- 使用DBSIZE可以查看Redis数据库中的key的个数
- 禁用keys * 等指令
- 数据量很大时,一定要避免使用keys * ,可以直接在配置文件中禁止该指令
- 当数据量很大时,不要使用keys * 和flushdb,因为Redis只有主线程串行执行命令,且keys * 是遍历实现的,故会非常耗费时间,造成Redis主线程卡顿,则此时后面的所有请求的命令均会卡顿!
- 通过在redis.conf配置文件中设置SECURITY配置项禁止这些命令,或者重命名,更改配置文件后一定要先重启服务端
- 使用SCAN遍历 key
- 使用scan命令来查找指定的key(不使用keys * )
- Scan命令用于迭代数据库中的数据库键,是基于游标的迭代器,当前遍历要基于上一次遍历的游标
- SCAN命令基于游标来遍历数据库中的所有key,游标从0开始,每次返回下一次遍历的游标和当前遍历的key(不保证数量),不是顺序遍历,而是高位进位加法遍历
- SCAN命令的意思就是对所有满足条件的key进行非顺序遍历,而是基于游标遍历,每次返回模糊数量的key,以及下一次遍历游标,直到所有的遍历结束返回游标0
- 向Redis中插入大量数据