每日八股文之Java

1、如何利用Redis实现一个分布式锁?

为什么要实现分布式锁、实现分布式锁的方式

理论+实操:158、缓存-分布式锁-分布式锁原理与使用哔哩哔哩bilibili

实现分布式锁的方式:

方案一:SETNX + EXPIRE

方案二:SETNX + value值是(系统时间 + 过期时间)

方案三:使用Lua脚本(包含SETNX + EXPIRE两条指令)

方案四:SET的扩展命令(SET EX PX NX)

方案五:SET EX PX NX + 校验唯一随机值,再释放锁

方案六:开源框架:Redisson

方案七:多机实现的分布式锁Redlock

2、请你说说聚簇索引和非聚簇索引

索引即数据、二次查询

两者主要区别是数据和索引是否分离。

聚簇索引是将数据与索引存储到一起,找到索引也就找到了数据;

而非聚簇索引是将数据和索引存储分离开,索引树的叶子节点存储了数据行的地址。

在InnoDB中,一个表有且仅有一个聚簇索引(因为原始数据只留一份,而数据和聚簇索引在一起),并且该索引是建立在主键上的,即使没有指定主键,也会特殊处理生成一个聚簇索引;其他索引都是辅助索引,使用辅助索引访问索引外的其他字段时都需要进行二次查找。 而在MyISAM中,所有索引都是非聚簇索引,叶子节点存储着数据的地址,对于主键索引和普通索引在存储上没有区别。

加分回答:在InnoDB存储引擎中,可以将B+树索引分为聚簇索引和辅助索引(非聚簇索引)。无论是何种索引,每个页的大小都为16KB,且不能更改。 聚簇索引是根据主键创建的一棵B+树,聚簇索引的叶子节点存放了表中的所有记录。辅助索引是根据索引键创建的一棵B+树,与聚簇索引不同的是,其叶子节点仅存放索引键值,以及该索引键值指向的主键。也就是说,如果通过辅助索引来查找数据,那么当找到辅助索引的叶子节点后,很有可能还需要根据主键值查找聚簇索引来得到数据,这种查找方式又被称为书签查找。因为辅助索引不包含行记录的所有数据,这就意味着每页可以存放更多的键值,因此其高度一般都要小于聚簇索引。

3、数据库为什么不用红黑树而用B+树?

磁盘IO

红黑树是一种近似平衡二叉树(不完全平衡),结点非黑即红的树,它的树高最高不会超过 2*log(n),因此查找的时间复杂度为 O(log(n)),无论是增删改查,它的性能都十分稳定; 但是,红黑树本质还是二叉树,在数据量非常大时,需要访问+判断的节点数还是会比较多,同时数据是存在磁盘上的,访问需要进行磁盘IO,导致效率较低;

而B+树是多叉的,可以有效减少磁盘IO次数;同时B+树增加了叶子结点间的连接,能保证范围查询时找到起点和终点后快速取出需要的数据。

加分回答: 红黑树做索引底层数据结构的缺陷 试想一下,以红黑树作为底层数据结构在面对在些表数据动辄数百万数千万的场景时,创建的索引它的树高得有多高? 索引从根节点开始查找,而如果我们需要查找的数据在底层的叶子节点上,那么树的高度是多少,就要进行多少次查找,数据存在磁盘上,访问需要进行磁盘IO,这会导致效率过低; 那么红黑树作为索引数据结构的弊端即是:树的高度过高导致查询效率变慢。

4、请你讲讲工厂模式,手写实现工厂模式

简单工厂、工厂方法、抽象工厂

工厂模式(Factory Method Pattern)也叫虚拟构造函数模式或多态性工厂模式,其用意是定义一个创建产品对象的工厂接口,将实际创建性工作推迟到子类中。 工厂模式可以分为简单工厂、工厂方法和抽象工厂模式

简单工厂适用于需要创建的对象较少或客户端不关心对象的创建过程的情况。

简单工厂的实现思路是: 定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。

示例: 创建一个可以绘制不同形状的绘图工具,可以绘制圆形,正方形,三角形,每个图形都会有一个draw()方法用于绘图:

首先可以定义一个接口或者抽象类,作为这三个图像的公共父类,并在其中声明一个公共的draw方法:

public interface Shape {
   void draw();
}

下面就是编写具体的图形,每种图形都实现Shape接口:

// 圆形
public class CircleShape implements Shape {
   public CircleShape() {
       System.out.println("CircleShape: created");
  }

   @Override
   public void draw() {
       System.out.println("draw: CircleShape");
  }
}

// 正方形
public class RectShape implements Shape {
   public RectShape() {
       System.out.println("RectShape: created");
  }

   @Override
   public void draw() {
       System.out.println("draw: RectShape");
  }
}

// 三角形
public class TriangleShape implements Shape {
   public TriangleShape() {
       System.out.println("TriangleShape: created");
  }

   @Override
   public void draw() {
       System.out.println("draw: TriangleShape");
  }
}

下面是工厂类的具体实现:

public class ShapeFactory {
   public static Shape getShape(String type) {
       Shape shape = null;
       if (type.equalsIgnoreCase("circle")) {
           shape = new CircleShape();
      } else if (type.equalsIgnoreCase("rect")) {
           shape = new RectShape();
      } else if (type.equalsIgnoreCase("triangle")) {
           shape = new TriangleShape();
      }
       return shape;
  }
}
//为工厂类传入不同的type可以new不同的形状,返回结果为Shape 类型,这个就是简单工厂核心的地方了。

 

工厂方法模式具有良好的封装性,代码结构清晰,一个对象创建是有条件约束的,如果一个调用者需要一个具体的产品对象,只要知道这个产品的类名或约束字符串即可,不用知道创建对象的过程如何,降低了模块间的耦合。工厂模式还拥有优秀的可扩展性,在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以适应变化。工厂方法模式是典型的解耦框架,高层模块只需要知道产品的抽象类或接口,其他的实现类都不用关心。

工厂方法的实现思路是:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。

举个例子,通过汽车工厂来演示工厂模式:

首先创建一个Car的接口:

public interface Car {
   //品牌
   public void brand();
   //速度
   public void speed();
   //价格
   public void price();
}

再创建一个Car的抽象工厂:

public interface CarFactory {
   public Car factory();
}

奥迪Audi类实现Car接口,是一个具体的产品:

public class Audi implements Car {
   @Override
   public void brand() {
       System.out.println("一台奥迪");
  }

   @Override
   public void speed() {
       System.out.println("快");
  }

   @Override
   public void price() {
       System.out.println("贵");
  }
}

奥拓Auto类实现Car接口,是一个具体的产品:

public class Auto implements Car {
   @Override
   public void brand() {
       System.out.println("一台奥拓");
  }

   @Override
   public void speed() {
       System.out.println("慢");
  }

   @Override
   public void price() {
       System.out.println("便宜");
  }
}

奥迪工厂AudiFactory实现CarFactory接口,专门用于生产奥迪:

public class AudiFactory implements CarFactory {
   @Override
   public Car factory() {
       return new Audi();
  }
}

奥拓工厂AutoFactory实现CarFactory接口,专门用于生产奥拓:

public class AutoFactory implements CarFactory {
   @Override
   public Car factory() {
       return new Auto();
  }
}

应用场景代码:

public class ClientDemo {
   public static void mn(String[] args) {
       CarFactory carFactory = new AudiFactory();
       Car audi = carFactory.factory();
       audi.brand();
       audi.speed();
       audi.price();
       carFactory = new AutoFactory();
       Car auto = carFactory.factory();
       auto.brand();
       auto.speed();
       auto.price();
  }
}

 

抽象工厂模式(Abstract Factory Pattern)是一种比较常用的模式。为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。抽象工厂模式是工厂方法模式的升级版本。在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式

抽象方法适用于下和工厂方法一样客户端不需要知道它所创建的对象的类,需要一组对象共同完成某种功能,可能存在多组对象完成不同功能以及系统结构稳定,不会频繁的增加对象的情况。

抽象工厂的实现思路是: 提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。

下面通过抽象工厂方式完成这款游戏的架构设计:

由题可知,游戏里边的各个平台的UIController和OperationController应该是我们最终生产的具体产品。所以新建两个抽象产品接口。

//抽象操作控制器:
interface OperationController {
   void control();
}

//抽象界面控制器:
interface UIController {
   void display();
}

然后完成各个系统平台的具体操作控制器和界面控制器。

// Android:
class AndroidOperationController implements OperationController {
   @Override
   public void control() {
       System.out.println("AndroidOperationController");
  }
}
class AndroidUIController implements UIController {
   @Override
   public void display() {
       System.out.println("AndroidInterfaceController");
  }
}

//IOS:
class IosOperationController implements OperationController {
   @Override
   public void control() {
       System.out.println("IosOperationController");
  }
}
class IosUIController implements UIController {
   @Override
   public void display() {
       System.out.println("IosInterfaceController");
  }
}

//WP:
class WpOperationController implements OperationController {
   @Override
   public void control() {
       System.out.println("WpOperationController");
  }
}
class WpUIController implements UIController {
   @Override
   public void display() {
       System.out.println("WpInterfaceController");
  }
}

下面定义一个抽象工厂,该工厂需要可以创建OperationController和UIController。

public interface SystemFactory {
   public OperationController createOperationController();

   public UIController createInterfaceController();
}

在各平台具体的工厂类中完成操作控制器和界面控制器的创建过程。

// Android:
public class AndroidFactory implements SystemFactory {
   @Override
   public OperationController createOperationController() {
       return new AndroidOperationController();
  }

   @Override
   public UIController createInterfaceController() {
       return new AndroidUIController();
  }
}

//IOS:
public class IosFactory implements SystemFactory {
   @Override
   public OperationController createOperationController() {
       return new IosOperationController();
  }

   @Override
   public UIController createInterfaceController() {
       return new IosUIController();
  }
}

//WP:
public class WpFactory implements SystemFactory {
   @Override
   public OperationController createOperationController() {
       return new WpOperationController();
  }

   @Override
   public UIController createInterfaceController() {
       return new WpUIController();
  }
}

总结:简单工厂模式其实并不算是一种设计模式,更多的时候是一种编程习惯。简单工厂的实现思路是,定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。 工厂方法模式是简单工厂的仅一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说每个对象都有一个与之对应的工厂。工厂方法的实现思路是,定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。 抽象工厂模式是工厂方法的仅一步深化,在这个模式中的工厂类不单单可以创建一个对象,而是可以创建一组对象。这是和工厂方法最大的不同点。抽象工厂的实现思路是,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。

5、说说缓存穿透、击穿、雪崩的区别

三种问题的发生原因以及解决方式

高并发下缓存失效问题:

缓存穿透:客户端访问不存在的数据,使得请求直达存储层,导致负载过大,直至宕机。原因可能是业务层误删了缓存和库中的数据,或是有人恶意访问不存在的数据。解决方案如下:

  1. 存储层未命中后,返回空值存入缓存层,客户端再次访问时,缓存层直接返回空值。

  1. 将数据存入布隆过滤器,访问缓存之前经过滤器拦截,若请求的数据不存在则直接返回空值。

 

缓存雪崩:大量数据同时过期、或是redis节点故障导致服务不可用,缓存层无法提供服务,所有的请求直达存储层,造成数据库宕机。解决方案如下:

  1. 避免数据同时过期,设置随机过期时间。

  2. 启用降级和熔断措施。

  3. 设置热点数据永不过期。

  4. 采用redis集群,一个宕机,另外的还能用

 

缓存击穿:一份热点数据,它的访问量非常大,在它缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。解决方案如下:

  1. 永不过期:对热点数据不设置过期时间。

  2. 加互斥锁,当一个线程访问该数据时,另一个线程只能等待,这个线程访问之后,缓存中的数据将被重建,届时其他线程就可以从缓存中取值。

 

posted @ 2023-04-20 17:18  小謝同學  阅读(37)  评论(0)    收藏  举报