垂直拆分是指数据表列的拆分:按照业务交易数据库、用户数据库、商品数据库、店铺数据库等进行拆分
拆分原则
1.把不常用的字段单独放在一张表;,
2.把text,blob等大字段拆分出来放在附表中
3.经常组合查询的列放在一张表中
优点
1.拆分后业务清晰,拆分规则明确。
2.系统之间整合或扩展容易。
3.数据维护简单。
缺点
1.部分表关联无法在数据库级别完成,需要在程序中完成
2. 事务处理相对更为复杂
3. 切分达到一定程度之后,扩展性会遇到限制
垂直拆分后遇到单机瓶颈,可以使用水平拆分。
水平拆分:水平拆分的典型场景就是大家熟知的分库分表(hash到不同的机器)。
按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中。
垂直拆分是把不同的表拆到不同的数据库中,而水平拆分是把同一个表拆到不同的数据库中。
优点
1.表关联基本能够在数据库端全部完成;
2.不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;
3.事务处理相对简单
1.广播:主题消费Topic
2.单播:生产者 消费者
3.消息丢失:生产者在发送消息时,会有一个ack机制,当ack=0 或者 ack=1时,都可能会丢消息
acks=0
表示producer不需要等待任何broker确认收到消息的回复,就可以继续发送下一条消息。性能最高,但是最容易丢消息。大数据统计报表场景,对性能要求很高,对数据丢失不敏感的情况可以用这种。
acks=1
至少要等待leader已经成功将数据写入本地log,但是不需要等待所有follower是否成功写入。就可以继续发送下一条消息。这种情况下,如果follower没有成功备份数据,而此时leader又挂掉,则消息会丢失。
————————————————————————————————
4.消息重复消费:幂等性的问题,发送消息如果配置了重试机制,比如由于网络波动,生产者未得到broker收到了消息的响应,就会触发重试机制,3秒后再次发送此消息。
新版kafka的broker幂等性
kafka每次发送消息会生成PID和Sequence Number,并将这两个属性一起发送给broker,broker会将PID和Sequence Number跟消息绑定一起存起来,下次如果生产者重发相同消息,broker会检查PID和Sequence Number,如果相同不会再接收
———————————————— ————————————————
5.顺序消息
kafka想要保证消息顺序,是需要牺牲一定性能的,方法就是一个消费者,消费一个分区,可以保证消费的顺序性。但也仅限于消费端消费消息的有序性,无法保证生产者发送消息有序。
kafka的顺序消息仅仅是通过partitionKey,将某类消息写入同一个partition,一个partition只能对应一个消费线程,以保证数据有序。
比如:如果发送端配置了重试机制,kafka不会等之前那条消息完全发送成功才去发送下一条消息,这样可能会出现,发送了1,2,3条消息,第一条超时了,后面两条发送成功,再重试发送第1条消息,这时消息在broker端的顺序就是2,3,1了。发送端消息发送已经乱序,到了消费端消费时,自然无法保证顺序!
如果一定要保证生产-消费全链路消息有序,发送端需要同步发送,ack回调不能设置为0。且只能有一个分区,一个消费者进行消费,但这样明显有悖于kafka的高性能理论!
————————————————
如何在多个分区中保证消息顺序和消息处理效率呢?
首先使用多个分区,消息可以被发送端发送至多个分区,保证消息发送的效率。
然后在消费端在拉消息时使用ConutdownLunch来记录一组有序消息的个数。如果达到个数,说明已拉取到完整的一组有序消息。然后在消费端根据消息序号进行排序,消费端将排好序的消息发到内存队列(可以搞多个),一个内存队列开启一个线程顺序处理消息。即可最大程度上既保证顺序又保证效率!
————————————————
6.消息积压
线上有时因为发送方发送消息速度过快,或者消费方处理消息过慢,可能会导致broker积压大量未消费消息。
解决方案:此种情况如果积压了上百万未消费消息需要紧急处理,可以修改消费端程序,让其将收到的消息快速转发到其他topic(可以设置很多分区),然后再启动多个消费者同时消费新主题的不同分区。
7.延时消息
延时队列存储的对象是延时消息。所谓的“延时消息”是指消息被发送以后,并不想让消费者立刻获取,而是等待特定的时间后,消费者才能获取这个消息进行消费,延时队列的使用场景有很多
在订单系统中, 一个用户下单之后通常有 30 分钟的时间进行支付,如果 30分钟之内没有支付成功,那么这个订单将进行异常处理,这时就可以使用延时队列来处理这些订单了
8. 消息回溯
如果某段时间对已消费消息计算的结果觉得有问题,可能是由于程序bug导致的计算错误,当程序bug修复后,这时可能需要对之前已消费的消息重新消费,可以指定从多久之前的消息回溯消费,这种可以用consumer的offsetsForTimes、seek等方法指定从某个offset偏移的消息开始消费,完成消息的回溯消费!
————————————————
1.抽象类和抽象方法都必须使用abstract关键修饰。
2.子类继承抽象类【抽象类一定是父类】时,要重写抽象类中的所有抽象方法,如果没有重写所有的抽象方法,则该子类也必须定义为抽象类
3.抽象类不能创建对象,抽象类中有没有构造方法
===============================================================
多态【正是程序运行时判断执行其中哪个方法的能力】
一、要有继承(或者接口的实现);
二、要有重写;
三、父类引用指向子类对象。
我们在程序中定义的引用变量所指向的具体类型和通过该引用变量的方法调用在编程的时候并不确定,当处于运行期间才确定。就是这个引用变量究竟指向哪一个实例对象,在编译期间是不确定的,只有运行期才能确定,这样不用修改源码就可以把变量绑定到不同的类实例上,让程序拥有了多个运行状态,这就是多态
必须要有继承的情况存在;
在继承中必须要有方法覆盖;
必须由基类的引用指向派生类的实例,并且通过基类的引用调用被覆盖的方法;
public class Animal {
static {
System.out.println("Animal的静态方法");
}
public Animal() {
}
public void eat() {
System.out.println("吃东西...");
}
}
public class Cat extends Animal {
static {
System.out.println("Cat的静态方法");
}
// 腹泻的方法
@Override
public void eat() {
System.out.println("小猫吃东西...");
}
//特有的方法
public void catchMouse() {
System.out.println("抓老鼠...");
}
public static void main(String[] args) {
// 1.Animal的静态方法
// 2.Cat的静态方法
Animal animal = new Cat();
animal.eat(); //可以调用【因为复写:小猫吃东西...】
//animal.catchMouse(); //不可以调用
Cat cat = new Cat();
cat.catchMouse();//可以调用【抓老鼠...】
cat.eat();//小猫吃东西...
}
}
@SpringBootApplication:
包含@SpringbootConfiguration、@EnableAutoConfiguration、@ComponentScan
@Controller//标明这是一个SpringMVC的Controller控制器;
@Configuration//这是一个配置Spring的配置类;
@Configuration // 加载外部配置
@PropertySource(value = { “classpath:jdbc.properties”, “classpath:env.properties”, “classpath:httpclient.properties” }) // 读取配置文件数据
@ComponentScan(basePackages = “com.demo”) // 扫描包
@ImportResource(value = “classpath:dubbo/dubbo-consumer.xml”)//导入dubbo资源
@SpringBootApplication Spring Boot项目的核心注解,主要目的是开启自动配置,Spring Boot来自动选择并且完成web的相关加载工作。
@Repository:用于标注数据访问组件,即DAO组件。
@Service:用于标注业务层组件。
@RestController:用于标注控制层组件(如struts中的action),包含 @Controller@ResponseBody。
@ResponseBody:表示该方法的返回结果直接写入HTTP response body中
一般在异步获取数据时使用,在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。比如异步获取json数据,加上@responsebody后,会直接返回json数据。
@Component:泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
@ComponentScan:组件扫描。个人理解相当于context:component-scan,如果扫描到有@Component @Controller @Service等这些注解的类,则把这些类注册为bean。
@Configuration:指出该类是 Bean 配置的信息源,相当于XML中的,一般加在主类上。
@Bean:相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理。
@EnableAutoConfiguration:让 Spring Boot 根据应用所声明的依赖来对 Spring 框架进行自动配置,一般加在主类上。
@AutoWired:byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
当加上(required=false)时,就算找不到bean也不报错。
@Qualifier:当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用
@Resource(name=“name”,type=“type”):没有括号内内容的话,默认byName。与@Autowired干类似的事。
@RequestMapping:RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
@RequestParam:用在方法的参数前面。
@RequestParam String a =request.getParameter(“a”)。
@PathVariable:路径变量。参数与大括号里的名字一样要相同。
@ConfigurationProperties Spring Boot将尝试校验外部的配置,默认使用JSR-303(如果在classpath路径中)。你可以轻松的为你的@ConfigurationProperties类添加JSR-303 javax.validation约束注解:
@ControllerAdvice:包含@Component。可以被扫描到。
统一处理异常。
@ExceptionHandler(Exception.class):用在方法上面表示遇到这个异常就执行以下方法。
===================================================================
@EnableAutoConfiguration 就是 为SpringBoot实现自动配置的核心注解。它的意思就是开启自动配置功能。也就是说我们之前需要配置的东西,现在都不需要配置了 而在 @EnableAutoConfiguration 的内部又有两个非常重要的注解,分别为 @AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class) 。
进入 @AutoConfigurationPackag 的内部,我们发现这个该注解是由一个 @Import注解来完成的
@AutoConfigurationPackag 的作用就是将SpringBoot 主配置类所在的包 及其 下面的所有子包里面的所有组件 扫描到 Spring 容器中
而import,AutoConfigurationImportSelector中的方法getCandidateConfigurations,得到待配置的class的类名集合,这个集合就是所有需要进行自动配置的类,而是是否配置的关键在于META-INF/spring.factories文件中是否存在该配置信息
SpringBoot 在启动的时候就从类路径下的 META-INF/spring.factories 中获取 EnableAutoConfiguration指定的值,并将这些值加载到自动配置类导入到容器中,自动配置类 就生效,帮助我们进行自动配置功能
// 一、节点类
class Node {
public int key, val;
public Node next, prev; //下一个和前面一个
public Node (int key, int val) {
this.key = key;
this.val = val;
}
}
// 二、双端链表
class DoubleLinked {
private Node head, tail;//头结点和尾结点
private int size;//链表的元素数目
public DoubleLinked() {
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
tail.prev = head;
size = 0;
}
// 在头部插入node结点
public void addFirst(Node x) {
x.prev = head;
x.next = head.next;
head.next.prev = x;
head.next = x;
size++;
}
// 移除指定结点
public void remove(Node x) {
x.prev.next = x.next;
x.next.prev = x.prev;
size--;
}
// 删除链表的第一个结点,并返回该结点
public Node removeLast() {
if(head.next == tail) return null;//返回空
Node last = tail.prev;
remove(last);//删除尾结点;
return last;
}
public int size() {
return size;
}
}
// 三、构造hashMap映射
class LRUCache {
HashMap<Integer, Node> map;
DoubleLinked linked;
int cap; //容量
public LRUCache(int capacity) {
map = new HashMap<>();
linked = new DoubleLinked();
this.cap = capacity;
}
public int get(int key) {
if(!map.containsKey(key)) return -1;
int val = map.get(key).val;
put(key, val);//放入头结点
return val;
}
public void put(int key, int value) {
Node x = new Node(key, value);
if(map.containsKey(key)) {
linked.remove(map.get(key));//移除结点
linked.addFirst(x);
map.put(key, x);
}else {
if(cap == cache.size()) {
Node last = linked.removeLast();
map.remove(last.key);
}
linked.addFirst(x);
map.put(key, x);
}
}
}
HASH函数必须具备两个基本特征:单向性 和 碰撞约束
单向性是指其的操作方向的不可逆性,在HASH函数中是指 只能从输入推导出输出,而不能从输出计算出输入;
碰撞约束是指 不能找到一个输入使其输出结果等于一个已知的输出结果 或者 不能同时找到两个不同的输入使其输出结果完全一致
1.在Java中,String可以直接使用+和+=实现两个字符串的链接。这是Java编译器提供的支持。但其实在编译器的背后呢,Java编译器还是会生成StringBuilder,+和+=还是会转化成append。
2.使用+链接和使用StringBuilder效果是一样的。但是在循环的情况下,可能使用StringBuilder效率会更高。
3.因为在循环的情况下,String的每一次+操作都会生成一个StringBuilder对象
UDP报文没有可靠性保证、顺序保证和流量控制。超时重传、拥塞控制 字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如短消息、广播信息等。
1.UDP服务器同样以报文形式做出响应,如果服务器未收到 此请求,客户端不会进行重发,因此报文的传输是不可靠的
2.UDP只会把想发的数据报文一股脑的丢给对方,并不在意数据有无安全完整到达。
3.在UDP方式下,客户端并不与服务器建立连接,它只负责调用发送函数向服务器发出数据报。类似地,服务器也不从客户端接收连接,只负责调用接收函数,等待来自某客户端的数据到达。
1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
5、可以考虑将登陆信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中。
无连接
无连接并不是指不连接,客户与服务器之间的HTTP连接是一种一次性连接,它限制每次连接只处理一个请求,当服务器返回本次请求的应答后便立即关闭连接,下次请求再重新建立连接。这种一次性连接主要考虑到WWW服务器面向的是Internet中成干上万个用户,且只能提供有限个连接,故服务器不会让一个连接处于等待状态,及时地释放连接可以大大提高服务器的执行效率。
无状态
服务器不保留与客户交易时的任何状态。这就大大减轻了服务器记忆负担,从而保持较快的响应速度。HTTP是一种面向对象的协议。允许传送任意类型的数据对象。它通过数据类型和长度来标识所传送的数据内容和大小,并允许对数据进行压缩传送。当用户在一个HTML文档中定义了一个超文本链后,浏览器将通过TCP/IP协议与指定的服务器建立连接
sessionid是一个会话的key,浏览器第一次访问服务器会在服务器端生成一个session,有一个sessionid和它对应。tomcat生成的sessionid叫做 jsessionid。
session在访问tomcat服务器HttpServletRequest的getSession(true)的时候创建,tomcat的ManagerBase类提供创建sessionid的方法:随机数+时间+jvmid;
session存储在服务器的内存中,tomcat的StandardManager类将session存储在内存中,也可以持久化到file,数据库,memcache,redis等。
客户端只保存sessionid到cookie中,而不会保存session,session销毁只能通过invalidate或超时,关掉浏览器并不会关闭session。
1.利用top命令确定是哪一个线程占用内存过高导致的
2.若是自己的Java程序线程则利用thread命令:查看某一个线程的详情,确定是业务线程还是GC垃圾回收的线程
3.jmap -histo 进程号 :打印jvm中所有的对象,查看哪一个对象占用比较大;并且查看日志是否有outofMemary产生
4.利用jinfo工具就可以修改参数产生快照即dump文件并下载到windows环境中
5.visualvm工具打开进行分析并结合jmap 命令打印的对象定位到出问题的代码块
6.结合分析结果和代码,定位问题出现的原因
引用计数法记录着每一个对象被其它对象所持有的引用数。如果一个对象的引用计数为零,那么该对象就变成了所谓的不可达对象,亦即可以被回收的
1.只靠强引用计数方式,会存在循环引用的问题,导致对象永远无法被释放,弱引用就是专门用来解决循环引用问题的
2.若 A 强引用了 B,那 B 引用 A 时就需使用弱引用,当判断是否为无用对象时仅考虑强引用计数是否为 0,不关心弱引用计数的数量
3.这样就解决了循环引用导致对象无法释放的问题,但这会引发野指针问题:当 B 要通过弱指针访问 A 时,A 可能已经被销毁了,那指向 A 的这个弱指针就变成野指针了。在这种情况下,就表示 A 确实已经不存在了,需要进行重新创建等其他操作
给堆内存分代是为了提高对象内存分配和垃圾回收的效率。如果堆内存没有区域划分,所有的新创建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁进行垃圾收集,而每次回收都要遍历所有的对象,遍历这些对象所花费的时间代价是巨大的,会严重影响我们的GC效率
Java虚拟机将堆内存划分为新生代、老年代和永久代
新生代:新生成的对象优先存放在新生代,回收效率很高
老年代:新生代年龄达到15以后,进入老年代,开始进行fullgc
永久代【元空间】:存储类信息,常量,静态变量,一般不会进行垃圾回收
Minor GC 又称为新生代GC 指的是发生在新生代的垃圾回收操作(包括Eden区和Survivor区)。当JVM无法为一个新的对象分配空间时候,会触发Minor GC。因为新生代中大多数对象的生命周期都很短,因此Minor GC(采用复制算法)非常频繁,虽然它会触发stop-the-world,但是回收速度也比较快。
Major GC清理老年代,出现Major GC通常会出现至少一次Minor GC即大多数Major GC是由Minor GC触发的
Full GC是针对整个堆空间包括新生代、老年代、元空间GC,Full GC不等于Major GC,也不等于Minor GC+Major GC。
复制算法(新生代算法)
【解决了标记清除算法的内存碎片问题】当这块内存需要垃圾回收时,会将此区域还活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要按序分配即可。此方法实现简单,运行高效。
标记-整理算法(老年代回收算法)
复制算法 在对象存活率较高时 会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用复制算法。针对老年代的特点,提出了一种称之为"标记-整理"算法。标记过程仍与"标记-清除"中标记的过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界(除存活对象)以外的内存。
HTTP 幂等方法,是指无论调用多少次都不会有不同结果的 HTTP 方法。不管你调用一次,还是调用一百次,一千次,结果都是相同的。
HTTP GET 方法,用于获取资源,不管调用多少次接口,结果都不会改变,所以是幂等的。
HTTP POST 方法是一个非幂等方法,因为调用多次,都将产生新的资源。
HTTP PUT 方法因为它直接把实体部分的数据替换到服务器的资源,我们多次调用它,只会产生一次影响,但是有相同结果的 HTTP 方法,所以满足幂等性。
HTTP DELETE 方法用于删除资源,会将资源删除。调用一次和多次对资源产生影响是相同的,所以也满足幂等性。
(1)加载
首先通过一个类的全限定名来获取此类的二进制字节流;其次将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;最后在java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口。总的来说就是查找并加载类的二进制数据。
(2)链接
验证:确保被加载类的正确性;
准备:为类的静态变量分配内存,并将其初始化为默认值;
解析:把类中的符号引用转换为直接引用;
(3)为类的静态变量赋予正确的初始值
3、类的初始化
(1)类什么时候才被初始化
1)创建类的实例,也就是new一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName(“com.lyj.load”))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM启动时标明的启动类,即文件名和类名相同的那个类
(2)类的初始化顺序
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
4)总的来说,初始化顺序依次是:(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器;
果有父类,则顺序是:父类static方法 –> 子类static方法 –> 父类构造方法- -> 子类构造方法
4、类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区类的对象
在缓存的内存淘汰策略中有FIFO、LRU、LFU三种,其中LRU和LFU是Redis在使用的。
LRU(Least Recently Used)表示最近最少使用,该算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”
LFU(Least Frequently Used)表示最不经常使用,它是根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。
惰性删除:当key被访问时检查该key的过期时间,若已过期则删除;已过期未被访问的数据仍保持在内存中,消耗内存资源;
定期删除:每隔一段时间,随机检查设置了过期的key并删除已过期的key;维护定时器消耗CPU资源;
在RDB持久化模式中我们可以使用save和bgsave命令进行数据持久化dump操作
在AOF持久化模式中使用rewriteaof和bgrewriteaof命令进行持久化操作
简单
1.开闭原则(the Open Closed Principle OCP)
2.替换原则 (the Liskov Substitution Principle LSP)
3.依赖原则 (the Dependency Inversion Principle DIP)
4.接口分离原则(the Interface Segregation Principle ISP)
简单
二面(8.9)