- 2025-06-12·Java后端查看全文Day 6 每天学完累了就像休息,总是忘了打卡。。。 ✅ 今天做了: 面试鸭: 1.HashTable,HashMap,TreeMap的区别?...400分享
- 2025-05-31·Java后端
Day 8 昨天把面试鸭上背八股又复习了一下加深印象,有很多基本都快忘完了。 今天把手写rpc的注解驱动写完了,算是完结了,但是之前的哪个tcp协议有报错,暂时搁置了,还得把那个写一下。 注解驱动,感觉也不是很难,定义好注解,配置好注解范围,然后定义spring容器初始化时的行为(初始化rpc,提供者启动服务器,消费者不需要启动)。 对于服务提供者,我们期望的是,给想要提供的服务添加上注解@RpcService ,就可以实现服务注册的功能,即,消费者可以通过注解来调用我们的服务。通过Spring的Bean监听机制,在Bean加载完成后,扫描含有@RpcService 注解的bean并获取注解的内容进行服务注册。 对于消费者,我们期望的是,添加注解注入对象后,可以直接调用对象的方法。这实际上是利用注解生成代理对象后注入到创建的对象上。在Bean加载完后,根据注解里的信息生成代理对象。 在这里我做了一些改进,也是从评论区看到的思路。在RpcReference注解中我们定义了很多属性比如重试机制,负载均衡的类别,但是在实际代码中是直接通过rpcConfig读取的默认配置,如果我们允许用户在注解中选中某些属性值,比如不用默认的负载均衡类型,而改用其他的,就需要将注解中的内容读出来,存储到一个map中,然后在动态代理时根据map里的信息从工厂中获取对应的实现类。 思路是, 1.创建一个新的生成代理对象方法,另外接收一个参数map,里面存放内容为注解内容 2.自定义一个RpcReference对象,对象属性为@RpcReference 内的内容,这里不能直接从该注解获取内容,因为如果导入starter依赖,就会造成循环依赖,所以我的解决方法是创建一个class对象。 3.在ServiceProxy中创建一个RpcReference属性,创建有参构造方法,接收map,将map中的值赋值给RpcReference,不必担心用户填了哪些值,哪些值没填,因为在@RocReference注解中已经对这些值写了默认值 4.在invoke方法中,将原来的从rpcConfig中获取字符串,从工厂实例化改为直接从RpcReference中获取字符串然后工厂创建。
Day 7 ✅ 今天做了: 面试鸭: 1.springboot的自动配置是如何实现的? 简单总结一下,@EnableAutoConfiguration注解下的@Import注解会引入一个AutoConfigurationImportSelector加载器,它会扫描META-INF/spring.factories下定义的所有要自动配置的类路径。还提供了一些条件加载注释比如@ConditionalOnClass,@ConditionOnMissingBean,以及优先级自动配置注解@AutoConfigurationBeFore,@AutoConfigurationAfter等注解 2.Mysql的主从同步机制 主从同步就是将主库中的数据同步到一个或多个从数据库中。 分为三种:异步复制,同步复制,半同步复制。 手写RPC框架-重试机制 没什么难点,失败了就重试,学了两种重试算法:1.不重试,2.固定重试间隔 主要就是创建一个重试类,编写doRetry方法,接收参数为一个Callable<返回参数>的任务对象,并在内部写入重试机制(如使用RetryerBuilder类定制重写机制),最后.call()提交。
Day 6 每天学完累了就像休息,总是忘了打卡。。。 ✅ 今天做了: 面试鸭: 1.HashTable,HashMap,TreeMap的区别? HashTable线程安全,不可存入null的key或value,比较古老,性能不好。 HashMap非线程安全,可存入一个null的key和多个null的value,多线程可用concurrentHashMap TreeMap非线程安全,底层由红黑树实现,支持key的自然排序或者自定义排序,不可存入null的key,但可存入null的value 2.Redis如何实现分布式锁 Redis通过setnx和Lua脚本实现分布式锁,上锁通过setnx获取锁,并设置过期时间,解锁通过lua脚本实现,解锁时还需要判断锁是否是自己的,避免释放别人的锁。 Redis设置锁的过期时间要合理,即不能太长,占用资源,也不能太短,业务还未执行结束就提前释放。 并且,在主从模式下,如果主节点获取锁,在还未进行主从同步时,就宕机了,哨兵选举出新的主节点,由于没有主从同步,新的主节点又创建了锁,而此时主节点恢复了,此时就出现了两把锁,可能造成数据不一致。 Redis为了解决这一问题,推出了红锁。 红锁的实现基于多台Redis,客户端获取锁,需要向所有Redis发送上锁请求,只有当过半的Redis同意上锁,才可以上锁,否则获取锁失败。但是红锁开发成本较高。 手写RPC框架-负载均衡 在之前的代码中,获取服务,直接从serverList中get(0),获取第一个服务,然后直接调用,而经过增加负载均衡服务,可以从serverList中选择一个调用。 随机负载:创建一个随机数,每次都从serverList中随机获取一个返回。 轮询负载:创建一个轮询数,每次都从serverLIst中获取轮询id对应的数据,轮询数+1。 Hash一致负载:将Hash值空间划分成一个圆环,所有服务节点都映射在环的某个位置,每个请求根据哈希值,也映射在环的某个位置,然后顺时针寻找第一个哈希值大于该哈希值的节点,将请求路由发送到该节点。 Hash一致的优点是,就算某个节点宕机了,请求仍然可以发送到其他节点。 但是Hash一致存在一个问题,那就是如果服务节点过少,可能造成资源分配不均匀的问题,所以同一个服务可以映射在环的多个哈希值节点位置。 然后在Rpcconfig创建属性,添加默认负载均衡机制。在invoke调用服务时添加负载均衡机制。 太伤心了,前两天在写自定义协议,很难,写了一天,到最后程序一直出错,就是找不到问题在哪里,不得已直接copy鱼皮的代码,发现还是存在同样的问题,compeleteFuture的get方法阻塞获取返回结果,一直获取不到,不知道怎么弄,只好把代码恢复了,白写了,只能等其他写完后,再去琢磨琢磨了,哭了
Day 5 ✅ 今天做了: 面试鸭: 1.Mysql如何调优? 。设计合理的联合索引,尽量实现索引覆盖 。避免使用select * 。避免使用左Like模糊查询 。避免对查询字段进行函数操作,导致索引无法命中 。尽量符合最左前缀匹配原则 2.count(1),count(*),count(字段)有什么区别? count(1),count(字段)都可以行数量,不包括null值 count(*)查询行数量,包括Null 3.Mysql的乐观锁和悲观锁 乐观锁: 通过比较操作数据前后版本号,或某个字段值是否一致来保证操作期间没有别的线程修改数据,如果不同则回滚。适用于并发冲突少,读多写少的情景。 悲观锁: 在操作数据时对数据加锁,可以通过行级锁或表级锁实现,如select...for update。适用于并发冲突多,且写多读少的情景
Day 4 ✅ 今天做了: 面试鸭: cookie,session,token的区别? 为什么要用消息队列? Mysql的默认隔离级别是什么? 常见的几种设计模式? Mysql的事务二阶段提交是什么? Mysql是如何实现业务的? 手写RPC框架-第五节-实现注册中心简易版 包括功能:服务注册,服务发现。 创建服务中心配置类,包含服务中心地址,类别等信息。 创建服务信息类,包含服务名,版本号,地址和端口号等信息,提供获取服务key的方法(服务名+版本号)。 分布式储存器选择etcd,键值对存储,键定义为服务名+版本号,值存储服务信息类对象。 添加默认版本号字段,设值为1.0 为了提供多种注册中心实现方法,跟序列器一样,创建一个注册中心接口,定义方法:初始化,服务注册,服务发现,关闭服务,并进行如下操作: 1.在META-INF目录下创建以注册中心接口全引用为名的文件,内含注册中心类以及对应的实现接口地址。方便之后的读取以及 反射实例化。 2.定义注册中心工厂类,静态代码块,调用spiLoader,读取并记录META-INF目录下定义的注册中心信息。 3.提供getInstance方法,接受注册中心类型名key,通过spiLoader获取key对应的注册中心实现类。 实现各注册中心实现类,继承注册中心接口。 初始化:接受注册中心配置类,根据配置类信息定义注册中心地址,以及创建KVclient 服务注册:接受一个注册信息类,获取服务名等信息,根据默认标识+服务名+版本号为key,注册信息类为value。 定义一个租期LeaseClient,通过grand设置有效时长,获取其id。 最后通过PutOption存入key,value,id添加到etcd。 服务发现:在invoke调用方法中,先获取注册中心配置类,获取注册中心类型,然后通过注册中心工厂的getInstance,传入注册中心类型,获取实现类,创建注册信息类,填入服务名,版本信息,再通过注册信息类的getKey方法获取key,然后调用注册中心的getService,服务发现。在内部,通过前缀匹配来查询,通过输入的key,通过GetOption并设置isPrefix(true),设置一个查询条件,然后通过kvClient的get方法,输入前缀key,以及Getoption查询条件,获取查询到的数据列表,然后以流的形式,讲每一个值转化为注册信息类对象(因为服务注册时,value直接存入的就是信息注册类),然后返回list。在调用时,先用用list的第一个对象。获取到的信息注册类,包含地址,端口号,还要在进行一下地址和端口号的拼接,如果地址包含http,说明地址host本就是一个http接口,则直接拼接host和port,如果不包含,则说明是localhost,需要拼接一个http://然后再放入http请求中
Day 3 ✅ 今天做了: 面试鸭: 1.RabbitMQ无法路由的消息会去到哪? 如果配置备用交换机,则发送到备用交换机 默认直接丢掉,配置mandatory=true,则将消息返回给生产者 如果配置死信交换机,则发送给死信交换机 2.MySql为什么选用B+树作为索引结构?(B+树的优点) 查询效率高:B+树是自平衡树,插入或删除节点会进行分裂和合并操作,保证树的平衡 减少I/O:B+树非叶子节点存放主键或索引值和指针,可以在内存中存放很多索引,提高缓存命中率,减少I/O 范围查询能力强:B+树叶子节点组成链表,由根节点找到叶子节点后可以循环遍历 拓展:B树与B+树的区别? B节点存放数据,而B+树只有叶子节点存放数据,非叶子节点存放主键或索引值和指针,在内存中可以存放更多索引 B+树叶子节点构成链表,可以遍历查询,而B树则需要层级遍历 3.一条Sql语句的执行过程? 发送sql语句 连接器校验用户是否有操作权限 分析器进行词法分析和语法分析,词法分析,分析sql的功能如select、update,查询的表如User等。语法分析,分析语法是否 合法 优化器,选择合适的索引,表连接顺序 执行器,判断用户是否有操作表的权限,获取数据并返回 手写RPC框架第3,4章节 了解了接口mock,就是为接口设置一个默认返回值,方便开发和测试,不需要每次测试都真实的写出接口实现。 通过给rpc配置类添加一个mock字段,默认为false,不开启。在代理工厂中,自定义mock代理方法,判断mock字段值,如果为true,则执行mock代理方法,生成Mock代理对象。添加mock代理对象的invoke方法,获取method的返回类型,然后为几种常见的类型范围一个默认数据 了解了自定义序列化器和spi机制,自定义4中序列化器的实现方式。 定义序列化器工厂,需要实现的功能是,传入一个key(序列化器名字,需要规范化,可以写一个接口统一规范序列化器名字),工厂通过名字,从一个Map中获取对应的实现类对象并返回。在invoke时,只需要调用工厂的方法,传入选中的序列化器名字,即可获取对应的序列化器对象。但上述的实现是基于提前将序列化器写死在map中,然后根据key获取对应的序列化器对象。优化,通过配置文件读取,可以再写一个loader加载器。 在META-INF文件中写一个配置文件,配置文件命名需要以Serializer接口的全引用名一致。内部写入 序列化器名=序列化器实现类的全引用名 其目的是为了方便之后读取文件,分割序列化器名字与其对应的全引用名,然后通过反射创建对象。 在Loader中,需要有多个功能: 1.首次加载时,读取配置文件,将序列化器名与其实现类的全引用名以<key,value>的方式存入一个Load Map中 2.根据序列化器key获取对象。为了避免多次创建序列化器实例,应采用单例模式,用一个instanceMap<key,Class<?>>存放序列化器对应的实现类,在获取时,先去判断序列化器key是否存在,不存在就从LoadMap中获取对应的全引用名,然后反射实例化,然后存入instanceMap,返回。 之后,在invoke中,就可以直接调用seralizerFactory的方法getInstance,通过该方法调用序列化器加载器的方法,通过key获得实例。
Day 2 ✅ 今天做了: 面试鸭: 1.String,StringBuilder,StringBuffer的区别? String内容不可改,每次修改实际上都是创建了新的字符串,线程不安全,适用于单线程下的少量字符修改操作 StringBuilder,内容可改,底层由动态扩容字符数组实现,线程不安全,适用于单线程下的字符串修改 StringBuffer,内容可改,与StringBuilder类似,但是线程安全,适用于多线程下的字符串修改 2.MySQL的索引覆盖是什么? 索引包含要查询的所有字段,可以直接从二级索引中获取到所有查询信息就是索引覆盖 3.InnoDB引擎中,聚集索引与非聚集索引的区别? InnoDB中,索引底层都由B+树实现。 聚集索引一个表只能由一个,叶子节点存放索引值,非叶子节点存放整行数据,默认为主键索引,如果没有就选择第一个unique索引,如果没有就默认rowId(总之必须有且仅有一个) 非聚簇索引一个表可以由很多个,叶子借钱存放索引值,非叶子节点存放主键id以及索引列, 4.RabbitMQ什么样的消息会进入死信队列? 1.消息被拒绝 2.消息过期 3.消息队列满了 手写RPC框架: 1.复习了第一节,对基本的RPC框架流程理解更加透彻 2.学习了第二节,并且手动写了一遍,了解了通过Props读取properties配置文件,了解了通过YamlUtil读取yml配置文件。实现了优先读取properties文件,如果没有则读取yml配置文件的功能
Day 1 ✅ 今天做了: 面试鸭: 1.索引失效的场景?聚合索引不符合最左匹配原则;对所有使用了函数或表达式;使用like进行左或者左右模式查询;使用OR而只有一个索引(一个有索引一个没有);不同字段比较(比如id>age,两个字段不同);索引字段为字符串而查询时用数字(会调用CAST函数进行隐式转换,也算是使用了函数); 2.什么是索引下推?索引下推是一种减少回表查询的技术。发生在使用了二级索引,查询列包含非索引时。 索引下推就是将原来查询二级索引,获取主键id后,直接去聚集索引查询数据的流程,改为了先去二级索引查询数据,然后筛选出复合where的数据,然后再去聚集索引查询数据。在存储层过滤掉一部分数据,大大减少回表查询次数。 手写rpc框架第一节完成。 简易的rpc包括什么? 服务注册:创建一个本地map,存放接口名字以及接口实现类对象 web服务器:使用的Vert.x创建,先创建Vert.x对象,再设置监听端口,创建Http客户端,自定义端口处理请求方法 处理请求方法:类似一个请求拦截器,解析请求中的数据(调用函数时输入的参数),然后使用反射创建调用的接口对象,接口方法,然后invoke动态调用,并将返回结果序列化后返回 动态代理工厂:生成动态代理对象 invoke调用:当动态代理对象调用方法时,实际调用invoke方法,方法内部进行了序列化,并且根据调用的方法动态选择请求路由(需要配合服务注册和服务发现,暂时是写死的)。发送请求时,会触发请求拦截器,在请求拦截器内部解析数据,反射调用具体方法。并将结构序列化返回。调用成功,获取到序列化的结果数据,再反序列化返回给调用方。 消费者想要远程调用提供者,那提供者首先得暴露自己的方法吧?这里用Vert.x做为web服务器,监听接口,并创建了请求处理器,端口监听到请求后,做一些处理。光暴露还不行,还得让消费者知道你可以为它提供服务,所以要进行服务注册,将接口名字和实现类注册到一个全局的map。 消费者要调用提供者,需要知道提供者的请求接口,如果一个个去写,很麻烦,所以使用动态代理,生成一个代理接口对象,重写invoke方法,当代理对象调用方法时,会默认调用invoke方法。在invoke方法中,会解析调用的是哪一个服务的哪一个方法,然后通过服务发现(后期实现,目前直接写死),发现服务地址后,发送请求。发送请求,被请求拦截器拦截,根据请求信息解析出请求接口名字,方法名以及请求数据,通过本地的服务注册,实例化对应的接口对象,反射调用方法。将返回结果序列化后返回。 这样,消费者就可以获取提供者的服务了


