/images/avatar.png

Java架构演变历史

Java网站架构演变过程,大致分为5个阶段,分别为单体架构、集群架构、分布式架构、SOA架构和微服务架构。 单体架构 应用、数据库、文件都部署在一台机器上。简单来讲其实就是我们熟知的SSM架构(Spring+SpringMVC+MyBatis),把所有的业务模块都放在一个应用中开发,这里面又衍生出三层架构,即表示层、业务逻辑层和数据库访问层,虽然在软件设计中划分了经典的三层模型,但是对业务场景没有划分,一个典型的单体应用就是将所有的业务场景的表示层、业务逻辑层和数据访问层放在一个工程项目中,最终经过编译、打包,部署在一台服务器上。 单体架构优点 部署简单: 由于是完整的结构体,可以直接部署在一个服务器上即可。 技术单一: 项目不需要复杂的技术栈,往往一套熟悉的技术栈就可以完成开发。 用人成本低: 单个程序员可以完成业务接口到数据库的整个流程。 单体架构缺点 系统启动慢: 一个进程包含了所有的业务逻辑,涉及到的启动模块过多,导致系统的启动、重启时间周期过长; 系统错误隔离性差、可用性差:任何一个模块的错误均可能造成整个系统的宕机; 可伸缩性差:系统的扩容只能只对这个应用进行扩容,不能做到对某个功能点进行扩容; 线上问题修复周期长:任何一个线上问题修复需要对整个应用系统进行全面升级。 集群架构(cluster) 不同服务器部署同一套应用程序对外提供服务,实现服务的负载均衡或者互备(热备,主从)。同一种组件的多个实例,形成逻辑上的整体。单个节点可以提供完整服务,集群是物理形态。 集群架构相关技术点 应用和数据分离(大量用户高并发的访问导致系统性能越来越差,数据存储空间开始出现不足) 缓存的使用(QPS持续提高,为了降低接口访问时间、提高服务性能和并发,根据二八定律可以将80%的数据缓存) 负载均衡器的代理服务器 数据库读写分离 反向代理和CDN加速 负载平衡 集群就是把一个的事情交给多个人去做,假如要做1000个产品给一个人做要10天,我叫10个人做就是一天,这就是集群,负载均衡的话就是用来控制集群,他把做的最多的人让他慢慢做休息会,把做的最少的人让他加量让他做多点。 分布式架构 服务的不同模块部署在不同的机器上,单个节点不能提供完整服务,需要多节点协调提供服务(相同组件部署在不同节点,节点间通过交互信息协作提供服务),分布式强调的是工作方式。 分布式相关技术点 业务分库分表 业务模块拆分成子项目 NoSQL和搜索引擎对可伸缩的分布式特性具有更好的支持,应用服务器通过一个统一的数据访问模块访问各种数据,减轻应用程序管理诸多数据源的麻烦。 SOA架构 面向服务的设计架构,其中包含多个服务,服务之间通过相互依赖最终提供一系列的功能。一个服务通常以独立的形式存在于操作系统进程中。各个服务之间通过网络调用。 中心化实现:ESB(企业服务总线),各服务通过ESB进行交互,解决异构系统之间的连通性,通过协议转换,消息解析,消息路由把服务提供者的数据传送到服务消费者。 去中心化实现:微服务 微服务架构(在SOA上做的升华) 微服务就是一个独立的职责单一的服务应用程序,微服务架构强调业务需要彻底组件化和服务化,原有的单个业务系统会拆分为多个可独立开发,设计,运行的小应用。这些小应用通过服务完成交互和集成。 优点 每个服务直接足够内聚,代码容易理解 开发效率高,一个服务只做一件事,适合小团队开发 松耦合,有功能意义的服务。 可以用不同语言开发,面向接口编程。 易于第三方集成 微服务只是业务逻辑的代码,不会和HTML,CSS或其他界 可以灵活搭配,连接公共库/连接独立库 缺点 分布式系统的责任性 多服务运维难度加大。 系统部署依赖,服务间通信成本,数据一致 ,系统集成测试,性能监控。 服务间通信成本 数据一致性 系统集成测试 性能监控 Service Mesh 架构(集中管理微服务中非业务相关内容,让微服务更加专注于业务处理) 最初,流量管理和控制能力(比如图例中的熔断、服务发现)是和业务逻辑耦合在一起,即便以引用包的方式被调用,依然解决不了异构系统无法重用的问题。 流控功能和业务耦合相当不美好,于是出现了提供这些功能的公共库和框架。但这些库通常比较复杂,无论是学习使用,与业务系统整合、维护都会带来很大的成本。 为避免花费太多时间开发和维护这些通用库,人们希望流量控制能力可以下沉到网络通讯栈的层面,但几乎无法实现。 于是另一种思路出现,就是将这些功能独立成一个代理,由它先接管业务服务的流量,处理完成后再转发给业务服务本身,这就是 Sidecar 模式。 为统一管理 Sidecar,该模式进一步进化,形成网络拓扑,增加了控制平面,演变成 Service Mesh(最后的网格图中,绿色代表业务服务,蓝色代表 sidecar 服务)。 业务系统的核心价值应该是业务本身,而不是服务,微服务只是一种实现手段,实现业务才是目标。现有的微服务架构下,为解决可能出现的网络通信问题,提升系统的弹性,开发人员不得不花费大量时间和精力去实现流量控制相关的非业务需求,不能聚焦在业务本身。

Java知识大纲

基础 数据结构与算法 数组、链表、二叉树、队列、栈的各种操作(性能,场景) 二分查找和各种变种的二分查找 各类排序算法以及复杂度分析(快排、归并、堆) 各类算法题(手写) 理解并可以分析时间和空间复杂度。 动态规划(笔试回回有。。)、贪心。 红黑树、AVL树、Hash树、Tire树、B树、B+树。 图算法(比较少,也就两个最短路径算法理解吧) 操作系统 进程通信IPC(几种方式),与线程区别 OS的几种策略(页面置换,进程调度等,每个里面有几种算法) 互斥与死锁相关的 linux常用命令(问的时候都会给具体某一个场景) Linux内核相关(select、poll、epoll) 网络基础 OSI7层模型(TCP4层) 每层的协议 url到页面的过程 HTTP http/https 1.0、1.1、2.0 get/post 以及幂等性 http 协议头相关 网络攻击(CSRF、XSS) TCP/IP 三次握手、四次挥手 拥塞控制(过程、阈值) 流量控制与滑动窗口 TCP与UDP比较 子网划分(一般只有笔试有) DDos攻击 (B)IO/NIO/AIO 三者原理,各个语言是怎么实现的 Netty Linux内核select poll epoll 数据库 索引(包括分类及优化方式,失效条件,底层结构) sql语法(join,union,子查询,having,group by) 引擎对比(InnoDB,MyISAM) 数据库的锁(行锁,表锁,页级锁,意向锁,读锁,写锁,悲观锁,乐观锁,以及加锁的select sql方式) 隔离级别,依次解决的问题(脏读、不可重复读、幻读) 事务的ACID B树、B+树 优化(explain,慢查询,show profile) 数据库的范式。 分库分表,主从复制,读写分离。 Nosql相关(redis和mem***d区别之类的,如果你熟悉redis,redis还有一堆要问的) 编译原理 Java Java基础 把我之后的面经过一遍,Java感觉覆盖的就差不多了,不过下面还是分个类。 Java基础(面向对象、四个特性、重载重写、static和final等等很多东西) 集合(HashMap、ConcurrentHashMap、各种List,最好结合源码看) 并发和多线程(线程池、SYNC和Lock锁机制、线程通信、volatile、ThreadLocal、CyclicBarrier、Atom包、CountDownLatch、AQS、CAS原理等等) JVM(内存模型、GC垃圾回收,包括分代,GC算法,收集器、类加载和双亲委派、JVM调优,内存泄漏和内存溢出) IO/NIO相关 反射和***、异常、Java8相关、序列化 设计模式(常用的,jdk中有的) Web相关(servlet、cookie/session、Spring<AOP、IOC、MVC、事务、动态***>、Mybatis、Tomcat、Hibernate等) 并发编程 JVM 性能优化 性能指标体系 JVM调优 Tomcat调优 MySQL调优 故障排除 最佳实践 重构 设计模式 开发框架 Spring体系 MyBatis 常见业务 支付幂等性 减库存 秒杀 分布式锁 redis实现的分布式锁。 应该保证互斥性(在任何时候只有一个客户端持有锁。使用setnx)。 不能死锁(设置过期时间)。 保证上锁和解锁是同一个客户端(设置不同的value值)。 业务时间太长。导致锁过期(设置看门狗。自动续锁)。 锁的重入性(使用redis的hset)。 分布式事务 分布式缓存 中间价 消息队列 缓存 本地缓存 分布式缓存 ELK 数据库 分库分表 数据同步 数据库连接池 分布式 CAP原理和BASE理论。 Nosql与KV存储(redis,hbase,mongodb,mem***d等) 服务化理论(包括服务发现、治理等,zookeeper、etcd、springcloud微服务、) 负载均衡(原理、cdn、一致性hash) RPC框架(包括整体的一些框架理论,通信的netty,序列化协议thrift,protobuff等) 消息队列(原理、kafka,activeMQ,rocketMQ) 分布式存储系统(GFS、HDFS、fastDFS)、存储模型(skipList、LSM等) 分布式事务、分布式锁等 四大理论 拜占庭将军问题 CAP 理论 ACID 理论 BASE 理论 八大协议/算法 Paxos 算法 Raft 算法 一致性 Hash 算法 Gossip 协议算法 Quorum NWR 算法 FBFT 算法 POW 算法 ZAB 协议 大数据与数据分析: hadoop生态圈(hive、hbase、hdfs、zookeeper、storm、kafka) spark体系 语言:python、R、scala 搜索引擎与技术 工具 版本管理 Git 项目管理 Maven/Gradle 代码质量管理 Sonar 持续集成部署 Jenkins&GitLab CI/CD 监控系统 测试 Postman Jmeter VisualVM

SpringBoot总结

使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置,简化Spring应用的初始搭建以及开发过程。简单理解,就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像Maven整合了所有的Jar包,Spring Boot整合了所有的框架。 SpringBoot 的启动过程 开始源码分析,先从 SpringBoot 的启动类的 run() 方法开始看,以下是调用链:SpringApplication.run() -> run(new Class[]{primarySource}, args) -> new SpringApplication(primarySources)).run(args)。 一直在run,终于到重点了,我们直接看 new SpringApplication(primarySources)).run(args) 这个方法。 1 2 3 public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); } 上面的方法主要包括两大步骤: 创建 SpringApplication 对象。 运行 run() 方法。 创建 SpringApplication 对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public SpringApplication(ResourceLoader resourceLoader, Class.

SpringMVC总结

SpringMVC 执行流程 用户请求发送到前端控制器DispatcherServlet。 前端控制器DispatcherServlet接收到请求后,DispatcherServlet会使用HandlerMapping来处理,HandlerMapping会查找到具体进行处理请求的Handler对象。 HandlerMapping找到对应的Handler之后,并不是返回一个Handler原始对象,而是一个Handler执行链,在这个执行链中包括了拦截器和处理请求的Handler。HandlerMapping返回一个执行链给DispatcherServlet。 DispatcherServlet接收到执行链之后,会调用Handler适配器去执行Handler。 Handler适配器执行完成Handler(也就是我们写的Controller)之后会得到一个ModelAndView,并返回给DispatcherServlet。 DispatcherServlet接收到Handler适配器返回的ModelAndView之后,会根据其中的视图名调用视图解析器。 视图解析器根据逻辑视图名解析成一个真正的View视图,并返回给DispatcherServlet。 DispatcherServlet接收到视图之后,会根据上面的ModelAndView中的model来进行视图中数据的填充,也就是所谓的视图渲染。 渲染完成之后,DispatcherServlet就可以将结果返回给用户了。

Spring总结

Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。 我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。 Spring 官网列出的 Spring 的 6 个特征: 核心技术 :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。 测试 :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。 数据访问 :事务,DAO支持,JDBC,ORM,编组XML。 Web支持 : Spring MVC和Spring WebFlux Web框架。 集成 :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。 语言 :Kotlin,Groovy,动态语言。 Spring模块 IOC IOC容器初始化过程 BeanFactory和ApplicationContext是Spring中两种很重要的容器,前者提供了最基本的依赖注入的支持,后者在继承前者的基础上进行了功能的拓展,增加了事件传播,资源访问,国际化的支持等功能。同时两者的生命周期也稍微有些不同。 Spring IOC容器初始化过程分为Resource定位,载入解析,注册。IOC容器初始化过程中不包含Bean的依赖注入。Bean的依赖注入一般会发生在第一次通过getBean向容器索取Bean的时候。 IOC容器初始化过程 关键步骤 IOC容器初始化入口是在构造方法中调用refresh开始的。 通过ResourceLoader来完成资源文件位置的定位,DefaultResourceLoader是默认的实现,同时上下文本身就给除了ResourceLoader的实现。 创建的IOC容器是DefaultListableBeanFactory。 IOC对Bean的管理和依赖注入功能的实现是通过对其持有的BeanDefinition进行相关操作来完成的。 通过BeanDefinitionReader来完成定义信息的解析和Bean信息的注册。 XmlBeanDefinitionReader是BeanDefinitionReader的实现了,通过它来解析xml配置中的bean定义。 实际的处理过程是委托给BeanDefinitionParserDelegate来完成的。得到Bean的定义信息,这些信息在Spring中使用BeanDefinition对象来表示。 BeanDefinition的注册是由BeanDefinitionRegistry实现的registerBeanDefiition方法进行的。内部使用ConcurrentHashMap来保存BeanDefiition。 Spring解决循环依赖的过程总结 Spring在初始化Bean的时候,会先初始化当前Bean所依赖的Bean,如果两个Bean互相依赖,就产生了循环依赖,Spring针对循环依赖的办法是:提前曝光加上三个缓存singletonObjects、earlySingletonObjects、singletonFactories。 假设当前Bean是A,A依赖的Bean是B,B又依赖A。 提前曝光的意思就是,当前Bean A实例化完,还没有初始化完就先把当前Bean曝光出去,在B初始化需要依赖A的时候,就先拿到提前曝光的A,这样就可以继续将B初始化完成,然后返回A继续进行初始化。 循环依赖解决只针对单例Bean。 总结 Spring启动。 加载配置文件,xml、JavaConfig、注解、其他形式等等,将描述我们自己定义的和Spring内置的定义的Bean加载进来。 加载完配置文件后将配置文件转化成统一的Resource来处理。 使用Resource解析将我们定义的一些配置都转化成Spring内部的标识形式:BeanDefinition。 在低级的容器BeanFactory中,到这里就可以宣告Spring容器初始化完成了,Bean的初始化是在我们使用Bean的时候触发的;在高级的容器ApplicationContext中,会自动触发那些1. lazy-init=false的单例Bean,让Bean以及依赖的Bean进行初始化的流程,初始化完成Bean之后高级容器也初始化完成了。 在我们的应用中使用Bean。 Spring容器关闭,销毁各个Bean。 SpringBean生命周期 SpringBean生命周期 手动或者自动的触发获取一个Bean,使用BeanFactory的时候需要我们代码自己获取Bean,ApplicationContext则是在IOC启动的时候自动初始化一个Bean。 IOC会根据BeanDefinition来实例化这个Bean,如果这个Bean还有依赖其他的Bean则会先初始化依赖的Bean,这里又涉及到了循环依赖的解决。实例化Bean的时候根据工厂方法、构造方法或者简单初始化等选择具体的实例来进行实例化,最终都是使用反射进行实例化。 Bean实例化完成,也就是一个对象实例化完成后,会继续填充这个Bean的各个属性,也是使用反射机制将属性设置到Bean中去。 填充完属性后,会调用各种Aware方法,将需要的组件设置到当前Bean中。BeanFactory这种低级容器需要我们手动注册Aware接口,而ApplicationContext这种高级容器在IOC启动的时候就自动给我们注册了Aware等接口。 接下来如果Bean实现了PostProcessor一系列的接口,会先调用其中的postProcessBeforeInitialization方法。BeanFactory这种低级容器需要我们手动注册PostProcessor接口,而ApplicationContext这种高级容器在IOC启动的时候就自动给我们注册了PostProcessor等接口。 如果Bean实现了InitializingBean接口,则会调用对应的afterPropertiesSet方法。 如果Bean设置了init-method属性,则会调用init-method指定的方法。 接下来如果Bean实现了PostProcessor一系列的接口,会先调用其中的postProcessAfterInitialization方法。BeanFactory这种低级容器需要我们手动注册PostProcessor接口,而 ApplicationContext这种高级容器在IOC启动的时候就自动给我们注册了PostProcessor等接口。 到这里Bean就可以使用了。 容器关闭的时候需要销毁Bean。 如果Bean实现了DisposableBean,则调用destroy方法。 如果Bean配置了destroy-method属性,则调用指定的destroy-method方法。 AOP Spring AOP流程大致上可以分为三个阶段:标签解析和AutoProxyCreator的注册、AOP代理的创建、代理的使用。

MyBatis总结

MyBatis框架整体设计 接口层-和数据库交互的方式 MyBatis和数据库的交互有两种方式: 使用传统的MyBatis提供的API; 使用Mapper接口; 参考文章 常见问题

Redis概览

Redis总览 Redis是一款内存高速缓存数据库。Redis全称为:Remote Dictionary Server(远程数据服务. ,Redis是一种支持key-value等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。 单线程的redis为什么这么快 纯内存操作 单线程操作,避免了频繁的上下文切换 采用了非阻塞I/O多路复用机制 基础数据类型 Redis所有的key(键. 都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的5种数据类型,分别是:String、List、Set、Zset、Hash 结构类型 结构存储的值 结构的读写能力 String字符串 可以是字符串、整数或浮点数 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作; List列表 一个链表,链表上的每个节点都包含一个字符串 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素; Set集合 包含字符串的无序集合 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等 Hash散列 包含键值对的无序散列表 包含方法有添加、获取、删除单个元素 Zset有序集合 和散列一样,用于存储键值对 字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素 持久化 为了防止数据丢失以及服务重启时能够恢复数据,Redis支持数据的持久化,主要分为两种方式,分别是RDB和AOF; 当然实际场景下还会使用这两种的混合模式 RDB 持久化 RDB 就是 Redis DataBase 的缩写,中文名为快照/内存快照,RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值。 触发方式 手动触发 save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会造成长时间阻塞,线上环境不建议使用 bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短 bgsave命令具体流程如下: redis客户端执行bgsave命令或者自动触发bgsave命令; 主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回; 如果不存在正在执行的子进程,那么就fork一个新的子进程进行持久化数据,fork过程是阻塞的,fork操作完成后主进程即可执行其他操作; 子进程先将数据写入到临时的rdb文件中,待快照数据写入完成后再原子替换旧的rdb文件; 同时发送信号给主进程,通知主进程rdb持久化完成,主进程更新相关的统计信息(info Persitence下的rdb_*相关选项. 。 自动触发 在以下4种情况时会自动触发 redis.conf中配置save m n,即在m秒内有n次修改时,自动触发bgsave生成rdb文件; 主从复制时,从节点要从主节点进行全量复制时也会触发bgsave操作,生成当时的快照发送到从节点; 执行debug reload命令重新加载redis时也会触发bgsave操作; 默认情况下执行shutdown命令时,如果没有开启aof持久化,那么也会触发bgsave操作; RDB优缺点 优点 RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景; Redis加载RDB文件恢复数据要远远快于AOF方式; 缺点 RDB方式实时性不够,无法做到秒级的持久化; 每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高; RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全; 版本兼容RDB文件问题; AOF 持久化 Redis是“写后”日志,Redis先执行命令,把数据写入内存,然后才记录日志。日志里记录的是Redis收到的每一条命令,这些命令是以文本形式保存。PS: 大多数的数据库采用的是写前日志(WAL.

缓存概览

缓存问题 Redis最常用的一个场景就是作为缓存,在实践中可能会有哪些问题?比如一致性, 穿击, 穿透, 雪崩, 污染等。 缓存穿透 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。 在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。 解决方案: 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截; 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用. 。这样可以防止攻击用户反复用同一个id暴力攻击 布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小。 缓存击穿 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。 解决方案 设置热点数据永远不过期。 接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些服务不可用时候,进行熔断,失败快速返回机制。 加互斥锁. 缓存雪崩 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。 解决方案: 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。 设置热点数据永远不过期。 缓存污染(或满了) 缓存污染问题说的是缓存中一些只会被访问一次或者几次的的数据,被访问完后,再也不会被访问到,但这部分数据依然留存在缓存中,消耗缓存空间。 缓存污染会随着数据的持续增加而逐渐显露,随着服务的不断运行,缓存中会存在大量的永远不会再次被访问的数据。缓存空间是有限的,如果缓存空间满了,再往缓存里写数据时就会有额外开销,影响Redis性能。这部分额外开销主要是指写的时候判断淘汰策略,根据淘汰策略去选择要淘汰的数据,然后进行删除操作。 缓存淘汰策略 不淘汰 noevictionv4.0后默认的。 对设置了过期时间的数据中进行淘汰 随机:volatile-random ttl:volatile-ttl 越早过期的数据越优先被选择。 lru:volatile-lru LRU算法:LRU 算法的全称是 Least Recently Used,按照最近最少使用的原则来筛选数据。这种模式下会使用 LRU 算法筛选设置了过期时间的键值对。 lfu:volatile-lfu LFU 算法:LFU 缓存策略是在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。 全部数据进行淘汰 随机:allkeys-random lru:allkeys-lru lfu:allkeys-lfu 数据库和缓存一致性 方案:队列 + 重试机制 流程如下所示 更新数据库数据; 缓存因为种种问题删除失败 将需要删除的key发送至消息队列 自己消费消息,获得需要删除的key 继续重试删除操作,直到成功 然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。 方案:异步更新缓存(基于订阅binlog的同步机制) MySQL binlog增量订阅消费+消息队列+增量数据更新到redis

Kafka

Kafka 是一个分布式消息引擎与流处理平台,经常用做企业的消息总线、实时数据管道,有的还把它当做存储系统来使用。早期 Kafka 的定位是一个高吞吐的分布式消息系统,目前则演变成了一个成熟的分布式消息引擎,以及流处理平台。 使用消息队列不可能是单机的(必然是分布式or集群) Kafka天然是分布式的,往一个topic丢数据,实际上就是往多个broker的partition存储数据 数据写到消息队列,可能会存在数据丢失问题,数据在消息队列需要持久化 Kafka会将partition以消息日志的方式(落磁盘)存储起来,通过 顺序访问IO和缓存(等到一定的量或时间)才真正把数据写到磁盘上,来提高速度。 想要保证消息(数据)是有序的,怎么做? Kafka会将数据写到partition,单个partition的写入是有顺序的。如果要保证全局有序,那只能写入一个partition中。如果要消费也有序,消费者也只能有一个。 Kafka术语 Producer:生产者,消息产生和发送端。 Broker:Kafka 实例,多个 broker 组成一个 Kafka 集群,通常一台机器部署一个 Kafka 实例,一个实例挂了不影响其他实例。 Consumer:消费者,拉取消息进行消费。 一个 topic 可以让若干个消费者进行消费,若干个消费者组成一个 Consumer Group 即消费组,一条消息只能被消费组中一个 Consumer 消费。 Topic:主题,服务端消息的逻辑存储单元。一个 topic 通常包含若干个 Partition 分区。 Partition:topic 的分区,分布式存储在各个 broker 中, 实现发布与订阅的负载均衡。若干个分区可以被若干个 Consumer 同时消费,达到消费者高吞吐量。一个分区拥有多个副本(Replica),这是Kafka在可靠性和可用性方面的设计,后面会重点介绍。 message:消息,或称日志消息,是 Kafka 服务端实际存储的数据,每一条消息都由一个 key、一个 value 以及消息时间戳 timestamp 组成。 offset:偏移量,分区中的消息位置,由 Kafka 自身维护,Consumer 消费时也要保存一份 offset 以维护消费过的消息位置。 Kafka特点 高吞吐、低延时:这是 Kafka 显著的特点,Kafka 能够达到百万级的消息吞吐量,延迟可达毫秒级; 持久化存储:Kafka 的消息最终持久化保存在磁盘之上,提供了顺序读写以保证性能,并且通过 Kafka 的副本机制提高了数据可靠性。 分布式可扩展:Kafka 的数据是分布式存储在不同 broker 节点的,以 topic 组织数据并且按 partition 进行分布式存储,整体的扩展性都非常好。 高容错性:集群中任意一个 broker 节点宕机,Kafka 仍能对外提供服务。 Kafka消息发送机制 异步发送 Kafka 自从 0.

ZooKeeper

简介 ZooKeeper主要服务于分布式系统,可以用ZooKeeper来做:统一配置管理、统一命名服务、分布式锁、集群管理。 使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点进行统一管理等等),而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够通用解决这些问题的中间件就应运而生了。 ZooKeeper数据结构 ZooKeeper的数据结构,跟Unix文件系统非常类似,可以看做是一颗树,每个节点叫做ZNode。每一个节点可以通过路径来标识,结构图如下: ZooKeeper 那ZooKeeper这颗"树"有什么特点呢??ZooKeeper的节点我们称之为Znode,Znode分为两种类型: 短暂/临时(Ephemeral):当客户端和服务端断开连接后,所创建的Znode(节点)会自动删除 持久(Persistent):当客户端和服务端断开连接后,所创建的Znode(节点)不会删除 监听器 在上面我们已经简单知道了ZooKeeper的数据结构了,ZooKeeper还配合了监听器才能够做那么多事的。 常见的监听场景有以下两项: 监听Znode节点的数据变化 监听子节点的增减变化 zookeeperWatch 通过监听+Znode节点(持久/短暂[临时]),ZooKeeper就可以玩出这么多花样了。 统一配置管理 我们可以将common.yml这份配置放在ZooKeeper的Znode节点中,系统A、B、C监听着这个Znode节点有无变更,如果变更了,及时响应。 zookeeperConfig 统一命名服务 zookeeperNaming 集群管理。 还是以我们三个系统A、B、C为例,在ZooKeeper中创建临时节点即可: zookeeperCluster2 只要系统A挂了,那/groupMember/A这个节点就会删除,通过监听groupMember下的子节点,系统B和C就能够感知到系统A已经挂了。(新增也是同理) 除了能够感知节点的上下线变化,ZooKeeper还可以实现动态选举Master的功能。(如果集群是主从架构模式下) 原理也很简单,如果想要实现动态选举Master的功能,Znode节点的类型是带顺序号的临时节点(EPHEMERAL_SEQUENTIAL)就好了。 Zookeeper会每次选举最小编号的作为Master,如果Master挂了,自然对应的Znode节点就会删除。然后让新的最小编号作为Master,这样就可以实现动态选举的功能了。 分布式锁 参考分布式锁 ZooKeeper 的一些重要概念 ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。 ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟(但是内存限制了能够存储的容量不太大,此限制也是保持znode中存储的数据量较小的进一步原因)。 ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。) ZooKeeper有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个ZNode被创建了,除非主动进行ZN 移除操作,否则这个ZNode将一直保存在Zookeeper上。 ZooKeeper 底层其实只提供了两个功能: 管理(存储、读取)用户程序提交的数据; 为用户程序提交数据节点监听服务。 可构建集群 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么zookeeper本身仍然是可用的。 客户端在使用 ZooKeeper 时,需要知道集群机器列表,通过与集群中的某一台机器建立 TCP 连接来使用服务,客户端使用这个TCP链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了,客户端可以连接到另外的机器上。 ZooKeeper 官方提供的架构图: ZooKeeper 上图中每一个Server代表一个安装Zookeeper服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据的一致性。