消息队列
消息队列的作用?使用场景?
解耦、异步处理、消峰。
解耦:比如订单系统下单后需要扣库存,可以把扣库存部分扔入mq,解耦。
异步处理:比如用户注册,需要更新数据库、发送邮件、发送短信,三部分串行完成后返回给客户端,则可以通过mq将发送邮件和短信扔给mq后直接返回。
消峰:比如秒杀系统,可以在服务器端做一个消息队列,用户请求过来后直接放入消息队列,队列满了之后直接返回用户错误页面。
如何保证消息队列高可用
kafka数据存储模型
如何保证消息队列重复消费问题?(保证消息的幂等性)
服务端会为每条消息加一个全局唯一id,消费者存储该id,消费者自己实现幂等性验证
如何保证消息的可靠性传输?(消息丢失问题)
kafka:
ack机制+高可用+偏移量
消费者:当消费者拉取消息后自动提交偏移量,消费者处理消息失败,此时就会丢失消息。 所以,在确保消费者已经处理消息后在由消费者手动提交偏移量。
生产者:生产者发送消息,要等收到服务端成功反馈,才认为成功
服务端:首先leader收到生产者的写请求,要等同步到分区副本成功后在给生产者反馈。
队列大量积压消息
当生产者入队消息远大于消费者消费速度的时候会出现,或者消费者出现问题
修复消费者
临时扩容
设置过期时间,防止队列积压大量数据,事后在从硬盘拿数据做恢复。
分布式事务
比如有个系统要做一个操作,1、更新本地A库的表tA,2、更新远程服务B。
大致怎么做?
1、在A库中创建一个表tB,记录对B的更新操作。
2、系统操作的时候分别在插入一条数据到tA和tB,因为是同库,可以控制事务。
3、后台启动一个job,扫tB表,将数据扔到消息队列中。
4、远程服务B自己去队列中抓数据
这样虽然数据有延迟性,不能保证实时的一致,但是最终结果肯定是一致的。
CAP定律
在分布式服务大行其道的今天,我们经常会听到CAP原理这个词?
那么什么是CAP原理,
CAP原理是现代分布式系统的理论基石,好比是分布式领域的牛顿定律。CAP原理发布后,各种分布式存储中间件如雨后春笋般一个个冒出来了。我们这里只理解CAP,不对其原理做深入研究。
CAP三个字母分别代表:
C - Consistent ,一致性A - Availability ,可用性P - Partition tolerance ,分区容错性分布式系统之所以叫分布式,是因为提供服务的各个节点分布在不同机器上,相互之间通过网络交互。那么必然存在网络故障断开的风险,这个网络断开的专业场景成为网络分区。
在网络分区发生时,两个分布式节点之间无法进行通信,那么我们对一个节点进行的修改操作将无法同步到另外一个节点,所以数据的「一致性」将无法满足,因为两个分布式节点的数据不再保持一致。除非我们牺牲「可用性」,也就是暂停分布式节点服务,在网络分区发生时,不再提供修改数据的功能,直到网络状况完全恢复正常再继续对外提供服务。或者为了保证可用性,而牺牲数据一致性。
所以,CAP一句话就是,在网络分区时,不能同时保证可用性和一致性。
为了保证分布式中间件的可用性,大部分中间件会支持最终一致性。
优惠券
1、首先创建批次,批次包含优惠券的属性:名称、数量、类型、有效期、使用下限、渠道、平台等等。
2、给批次关联商家、商品、品类、黑名单
3、业务审批,财务审批
4、定时任务制造券:
5分钟执行一次,扫描状态为未制券+制券中的一个批次
查询出该批次已经制造出多少张了(上次制一半的时候服务器down的情况)
计算出本次需要制造多少张(批次优惠券数量 - 已经制造出来的数量)
更新批次状态未制造中
生成券码放入list中(如何防重的:首先每个批次有个批次头能防止多个批次间重复,然后批次内每次生成券码都判断一下list中是否已经存在)
多线程执行批量插入(一个线程1000张券)
更新批次状态为制造完成
5、发券:包括订单、大屏、三方
6、领券:活动
创建活动:包含活动有效期、用户全程/每天领取限制、预警阈值、运营人员邮箱
活动关联批次
活动上线:每个活动对应一个redis队列,然后把活动关联的批次的优惠券灌入到队列中,知道高于预警阈值停止,当活动发券后队列中优惠券数量少于阈值的时候继续灌券,活动下如果没有批次的话发送预警邮件。
7、领券:
首先在接口中会验证一下用户是否超过限制(redis incry)
然后插入mysql领券表中
定时任务执行绑定:
5秒钟执行一次
多线程执行,每个线程50条数据(线程池大小为2000固定)
判断活动是否下线
队列是否有券
更新数据库
8、查看:guava缓存,3秒失效
9、使用:
免邮券单独一个接口
优惠券和礼品卡一个接口
优惠券一订单最多一个张,礼品卡10张
验证商家、平台、渠道、黑白名单(信息都存在jvm中,10分钟更新一次,支持发布订阅手动更新)
验证有效期、使用下线
分摊金额到商品:单件商品总价 * 优惠券面额 然后在除以所有商品总价 = 单件商品分摊的金额
更新优惠券状态为已经使用(乐观锁)
压测:
- 64台高配虚机,4c16G
- 单机并发1000、平均响应时间0.1s,qps1万
- 集群并发10000、平均响应时间0.05秒,qps16-22万
- dubbo线程池数量300、队列5000-10000
优化:
1.去掉券池,用户真正使用时在发放
2.大礼包
3.分享券
4.现金池
oauth
什么是oauth2?
涉及三个角色:用户、三方应用、资源存储者。用户的信息存储在资源存储者处,用户在三方应用处通过临时令牌的方式访问资源存储者处用户的资源。
想要访问网站A(三方应用),登陆页面可以选择注册账号、微信登陆、qq登陆, 点击微信登陆,跳转到微信授权页面,微信(资源存储者)给该用户生成一个token,点击授权,返回A网站并且登陆成功,同时用户在A网站保留了该token(临时临牌),后续用户所有验权的操作都是通过该token。
三方应用基础github oauth流程如下:
什么是sso
单点登陆流程,采用密码式获取token:
1、a、b网站是三方应用,他们之间要实现单点登录,c是认证服务器
2、首先用户访问a网站,发现没有登陆,重定向到登陆页面,输入用户名和密码,向c发起认证(密码采用rsa加密),传递a的clientid。
3、c对用户密码认证,生成token,存储在c的redis中。token中包含用户名称、租户id、租户类型、用户真实名称、有效期
4、返回token给a,a存储token
5、用户从a跳转到b,传递token。b通过token去c获取资源授权。
jwt token
jwt包含三个部分:
1、头
{
"alg": "HS256",
"typ": "JWT"
}
上边字符串中指明了加密的算法,token类型 JWT固定。然后通过base64生成一个字符串做为头。
2、body
{
"sub": "1234567890",
"name": "chongchong",
"admin": true
}
包含了一些默认信息,还可以增加自定义信息,同样通过base64编译。
3、签名
防止token被串改,服务器保存一个密钥,然后通过头中的加密类型对头和body进行签名
数据库连接池
常见数据库连接池:
- C3P0:无监控
- DBCP:速度慢、无监控
- Proxool:稳定性差
- Druid:最佳选择
最小连接数20左右、最大100以内、空闲回收时间30分钟以内
TLAB
- 在堆中创建对象,首先要分配一块内存,然后在创建对象,那么分配内存这个动作如何保证线程安全
- 对分配动作进行cas操作,效率低下
- 通过tlab方式,jvm采用的方式
- tlab是什么?
- 在堆中为每个线程分配一块私有的空间,分配内存的时候向这个私有空间分配
- 分配私有空间这块是如何保证线程安全
- 不得不用同步手段了,但是这会比在分配内存的时候就使用cas效率高很多
- tlab的线程私有空间,只是指分配创建对象分配空间, 后续的使用、读取、回收等就不是私有的了
- -XX:+/-UseTLAB来指定开关
拷贝
拷贝分深拷贝和浅拷贝。
深拷贝:将对象中的对象属性也复制一份
浅拷贝:对象中的对象属性不复制,只是复制一个指针
通过clone方法可以实现对象的拷贝。
浅拷贝:
public class Subject {
private String name;
public Subject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "[Subject: " + this.hashCode() + ",name:" + name + "]";
}
}
public class Student implements Cloneable {
//引用类型
private Subject subject;
//基础数据类型
private String name;
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 重写clone()方法
* @return
*/
@Override
public Object clone() {
//浅拷贝
try {
// 直接调用父类的clone()方法
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
}
}
public class ShallowCopy {
public static void main(String[] args) {
Subject subject = new Subject("yuwen");
Student studentA = new Student();
studentA.setSubject(subject);
studentA.setName("Lynn");
studentA.setAge(20);
Student studentB = (Student) studentA.clone();
}
}
深拷贝:引用类型变量也要实现clone,同时被拷贝对象的clone方法也要改变。
public class Subject implements Cloneable {
private String name;
public Subject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//Subject 如果也有引用类型的成员属性,也应该和 Student 类一样实现
return super.clone();
}
@Override
public String toString() {
return "[Subject: " + this.hashCode() + ",name:" + name + "]";
}
}
public class Student implements Cloneable {
//引用类型
private Subject subject;
//基础数据类型
private String name;
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 重写clone()方法
* @return
*/
@Override
public Object clone() {
//深拷贝
try {
// 直接调用父类的clone()方法
Student student = (Student) super.clone();
student.subject = (Subject) subject.clone();
return student;
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
}
}
自动拆装箱
java基本数据类型可以在javac阶段进行自动拆装箱。
1、比如 Integer a = 100;
2、在编译阶段会进行自动转换:Integer a = Integer.valueOf(100);
3、继续看下Integer.valueOf的实现
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
其中IntegerCache.low = -128,IntegerCache.high = 127
如果值在这个区间,则直接从IntegerCache中获取,否则新new一个。
==比较分两种情况:
1、int和Integer比较:Integer自动拆箱成int
2、Integer和Integer比较:自动装箱+Cache
int a1 = 10;
Integer b1 = new Integer(10);
System.out.println(a1== b1); //自动拆箱成int类型 true
Integer a = 127; //从IntegerCache中获取
Integer b = 127;//从IntegerCache中获取
System.out.println(a == b); // 输出 true
Integer a1 = 128;//超过缓存区间,创建一个对象
Integer b1 = 128;//超过缓存区间,创建一个对象
System.out.println(a1 == b1); // 输出 false
Integer a2 = 127;
int b2 = 127;
System.out.println(a2 == b2); // 自动拆箱成int类型 输出 true
Integer a3 = 128;
int b3 = 128;
System.out.println(a3 == b3); //自动拆箱成int类型 输出 true
Double不适合这一套,因为Double没有Cache,Double自动装箱是直接创建一个新对象。
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2); //false
System.out.println(i3==i4); //false
内存泄漏场景
静态集合
静态变量的生命周期和类的生命周期一样,所以在方法内回收静态集合会失败。
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
//虽然这里把o设置为null,但引用v还持有o的引用,所以o并不会被gc回收
o = null;
}
//解决方法就是完事的时候将v也设置为null
单例
单例对象整个应用只有一个,生命周期和应用一样,当单例对象中持有其他对象引用时容易发生内存泄漏。
当创建单例对象的时候需要传入一个上下文,该上下文被单例对象应用,如果该上下文不是应用级别的,当回收的时候无法回收。
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
解决办法:改成弱引用
public class AppManager {
private static AppManager instance;
private WeakReference<Context> context;//这里改成弱引用
private AppManager(WeakReference<Context> context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
数据库、网络、io等资源不正常关闭
使用非静态内部类
非静态内部类会持有一个外部类的引用,当内部类生命周期长的时候,会影响外部类的回收。
改成使用静态内部类,使用静态内部类的时候,生成两个class文件,内外类是独立的。
String的itern()方法
该方法会将字符串放入到常量池中,不会被回收,如果对大字符串使用该方法容易发生内存泄漏
泛型
泛型解决的两个问题
public class NeedGeneric1 {
private static int add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static float add(float a, float b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static double add(double a, double b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
//在这里指定泛型,上面的重载方法就可以省略掉了
private static <T extends Number> double add(T a, T b) {
System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
return a.doubleValue() + b.doubleValue();
}
public static void main(String[] args) {
NeedGeneric1.add(1, 2);
NeedGeneric1.add(1f, 2f);
NeedGeneric1.add(1d, 2d);
NeedGeneric1.add(Integer.valueOf(1), Integer.valueOf(2));
NeedGeneric1.add(Float.valueOf(1), Float.valueOf(2));
NeedGeneric1.add(Double.valueOf(1), Double.valueOf(2));
}
}
public class NeedGeneric2 {
static class C{
}
public static void main(String[] args) {
List list=new ArrayList();//这里list没有指定泛型,什么类型都可以添加到list,后续就会报错
list.add("A");
list.add("B");
list.add(new C());
list.add(100);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
String value= (String) list.get(i);
System.out.println(value);
}
}
}
- 方法的参数,可以不比指定具体类型,用泛型替代
- 创建集合的时候,可以指定具体类型
泛型的使用
泛型类
public class GenericClass<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static void main(String[] args) {
GenericClass<String> genericClass=new GenericClass<>();
genericClass.setData("Generic Class");
System.out.println(genericClass.getData());
}
}
泛型接口
public interface GenericIntercace<T> {
T getData();
}
public class ImplGenericInterface1<T> implements GenericIntercace<T> {
private T data;
private void setData(T data) {
this.data = data;
}
@Override
public T getData() {
return data;
}
public static void main(String[] args) {
ImplGenericInterface1<String> implGenericInterface1 = new ImplGenericInterface1<>();
implGenericInterface1.setData("Generic Interface1");
System.out.println(implGenericInterface1.getData());
}
}
泛型方法
public class GenericMethod1 {
private static int add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static <T> T genericAdd(T a, T b) {
System.out.println(a + "+" + b + "="+a+b);
return a;
}
public static void main(String[] args) {
GenericMethod1.add(1, 2);
GenericMethod1.<String>genericAdd("a", "b");
}
}
限定泛型类型变量
public class TypeLimitForMethod {
public static <T extends Comparable<T>> T getMin(T a, T b) {
return (a.compareTo(b) < 0) ? a : b;
}
}
public class TypeLimitForClass<T extends List & Serializable> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
try catch finally
public static String aa(){
String t= "";
try{
t = "try";
return t;
}catch (Exception e){
t = "catch";
return t;
}finally {
t = "finally";
}
}
//输出: try
public static String aa(){
String t= "";
try{
t = "try";
return t;
}catch (Exception e){
t = "catch";
return t;
}finally {
t = "finally";
return t;
}
}
//输出:finally
public static String aa(){
String t= "";
try{
t = "try";
Integer.parseInt(null);
return t;
}catch (Exception e){
t = "catch";
return t;
}finally {
t = "finally";
}
}
//输出:catch
public static String aa(){
String t= "";
try{
t = "try";
Integer.parseInt(null);
return t;
}catch (Exception e){
t = "catch";
return t;
}finally {
t = "finally";
return t;
}
}
//输出:finally
public static String aa(){
String t= "";
try{
t = "try";
Integer.parseInt(null);
return t;
}catch (Exception e){
t = "catch";
Integer.parseInt(null);
return t;
}finally {
t = "finally";
}
}
//报错
public static String aa(){
String t= "";
try{
t = "try";
Integer.parseInt(null);
return t;
}catch (Exception e){
t = "catch";
Integer.parseInt(null);
return t;
}finally {
t = "finally";
return t;
}
}
//输出:finally
总结起来:
- 不管有没有异常,finally中的代码都会执行
- 当try、catch中有return时,finally中的代码依然会继续执行
- finally是在return后面的表达式运算之后执行的,此时并没有返回运算之后的值,而是把值保存起来,不管finally对该值做任何的改变,返回的值都不会改变,依然返回保存起来的值。也就是说方法的返回值是在finally运算之前就确定了的。
- finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是try或catch中的值
双亲委派
- 类加载器有哪些
- Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
- ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
- AppClassLoader:主要负责加载应用程序的主函数类
这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,但是在这种机制下这些系统的类已经被Bootstrap classLoader加载过了,所以并不会再去加载,从一定程度上防止了危险代码的植入