springboot第68集:字节跳动后端一面经,一文让你走出微服务迷雾架构周刊

d4d6494bc264ec112f1d8abe7f5a7e68.png

image.png
67abe86168bbe90bed5b3867bc6e8239.png
image.png

https://lbs.amap.com/api

https://lbs.amap.com/api/javascript-api/guide/transform/convertfrom

地球上同一个地理位置的经纬度,在不同的坐标系中,会有少许偏移,国内目前常见的坐标系主要分为三种:

  1. 地球坐标系——WGS84:常见于 GPS 设备,Google 地图等国际标准的坐标体系。

  2. 火星坐标系——GCJ-02:中国国内使用的被强制加密后的坐标体系,高德坐标就属于该种坐标体系。

  3. 百度坐标系——BD-09:百度地图所使用的坐标体系,是在火星坐标系的基础上又进行了一次加密处理。

因此在使用不同坐标系前,我们需要使用 AMap.convertFrom() 方法将这些非高德坐标系进行转换。

var gps = [116.3, 39.9];
AMap.convertFrom(gps, 'gps', function (status, result) {
  if (result.info === 'ok') {
    var lnglats = result.locations; // Array.<LngLat>
  }
});
// map.setDefaultCursor('pointer')
// map.setFeatures(['bg', 'point'])

Kubernetes(k8s)上搭建一主两从的mysql8集群

1)、所在部门可见
配置DataAuth注解,因为默认字段就是create_dept,所以无需配置column

91fe91114c72a2cd19570a211afe978b.png
image.png
5a773c27efcdb08c7dd734f95087d3ac.png
image.png
e348b4696dfeb83e4d8e18c509480703.png
image.png
06960f768dfdef56c0ab6441fbe4629b.png
image.png
5949db9f1b8301396f54bbb6c7a7b473.png
image.png

权限模块从大的方面来说,可以分为三种大的类型:功能权限、接口权限、数据权限。

1、功能权限

也就是我们熟悉的菜单、按钮权限。可以配置各个角色能看到 菜单、按钮。

2、接口权限

有些敏感的接口,是只能有固定的一些角色才能调用,普通角色是不能调用的。

3、数据权限

需要控制不同的角色、机构人员有查看不同数据范围的权限。

多租户使用

需要数据隔离的业务表,新建一个字段,tenant_id。

一般来说,权限有许多种,我们经常用到的一般有操作权限和[数据权限]两种。

oauth2

auth/oauth/token

dc238a98b4a67095a830a11c2223166d.png
image.png

通过lombok的@AllArgsContructor,可以省去@Autowired注解的使用

CRUD示例

5f65b0776a9b4d8854aa83562b655421.png
image.png
177a6c93f5c8d1111000f2746e2d2736.png
image.png
798f8a14277a06b5c8cff8dab37636c0.png
image.png
3ebb2e024560950622f50a5c98a2f1be.png
image.png

加上mybatis-plus的注解,因为数据库中id是自增的,所以不需要配置。若id为自定义,比如使用snow-flake算法,则需要在private Integer id上配置注解@TableId(value="id",type=IdType.ID_WORKER)。

@Data
@TableName("da_blog")
public class Blog implements Serializable {

   private static final long serialVersionUID = 1L;
   /**
    * 主键
    */
   private Integer id;
   /**
    * 标题
    */
   private String blogTitle;
   /**
    * 内容
    */
   private String blogContent;
   /**
    * 时间
    */
   private LocalDateTime blogDate;
   /**
    * 是否已删除
    */
   @TableLogic
   private Integer isDeleted;

}

在 Java 中,使用 @NoArgsConstructor(access = AccessLevel.PRIVATE) 注解是 Lombok 库的一部分,用于自动生成一个无参构造函数,并将其访问级别设置为 private

下面是这个注解的详细解释:

  • @NoArgsConstructor: 这是 Lombok 库提供的注解,用于自动生成一个无参数的构造函数(NoArgsConstructor 即 No-Argument Constructor 的缩写)。

  • access = AccessLevel.PRIVATE: 这是对生成的构造函数的访问级别进行设置。在这个例子中,它设置构造函数的访问级别为 private。这意味着这个构造函数不能从类的外部被访问或调用,仅在类的内部可用。

使用这个注解的目的通常是为了限制类的实例化,确保不能从外部通过默认的无参构造器创建类的实例。这在设计模式,如单例模式中很常见,或者当类中所有方法都是静态的时也可能会用到。

统一API响应结果

/**
 * 统一API响应结果封装
 *
 * @author Chill
 */
@Getter
@Setter
@ToString
@ApiModel(description = "返回信息")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class R<T> implements Serializable {

   private static final long serialVersionUID = 1L;

   @ApiModelProperty(value = "状态码", required = true)
   private int code;
   @ApiModelProperty(value = "是否成功", required = true)
   private boolean success;
   @ApiModelProperty(value = "承载数据")
   private T data;
   @ApiModelProperty(value = "返回消息", required = true)
   private String msg;

   private R(IResultCode resultCode) {
      this(resultCode, null, resultCode.getMessage());
   }

   private R(IResultCode resultCode, String msg) {
      this(resultCode, null, msg);
   }

   private R(IResultCode resultCode, T data) {
      this(resultCode, data, resultCode.getMessage());
   }

   private R(IResultCode resultCode, T data, String msg) {
      this(resultCode.getCode(), data, msg);
   }

   private R(int code, T data, String msg) {
      this.code = code;
      this.data = data;
      this.msg = msg;
      this.success = ResultCode.SUCCESS.code == code;
   }

   /**
    * 判断返回是否为成功
    *
    * @param result Result
    * @return 是否成功
    */
   public static boolean isSuccess(@Nullable R<?> result) {
      return Optional.ofNullable(result)
         .map(x -> ObjectUtil.nullSafeEquals(ResultCode.SUCCESS.code, x.code))
         .orElse(Boolean.FALSE);
   }

   /**
    * 判断返回是否为成功
    *
    * @param result Result
    * @return 是否成功
    */
   public static boolean isNotSuccess(@Nullable R<?> result) {
      return !R.isSuccess(result);
   }

   /**
    * 返回R
    *
    * @param data 数据
    * @param <T>  T 泛型标记
    * @return R
    */
   public static <T> R<T> data(T data) {
      return data(data, BladeConstant.DEFAULT_SUCCESS_MESSAGE);
   }

   /**
    * 返回R
    *
    * @param data 数据
    * @param msg  消息
    * @param <T>  T 泛型标记
    * @return R
    */
   public static <T> R<T> data(T data, String msg) {
      return data(HttpServletResponse.SC_OK, data, msg);
   }

   /**
    * 返回R
    *
    * @param code 状态码
    * @param data 数据
    * @param msg  消息
    * @param <T>  T 泛型标记
    * @return R
    */
   public static <T> R<T> data(int code, T data, String msg) {
      return new R<>(code, data, data == null ? BladeConstant.DEFAULT_NULL_MESSAGE : msg);
   }

   /**
    * 返回R
    *
    * @param msg 消息
    * @param <T> T 泛型标记
    * @return R
    */
   public static <T> R<T> success(String msg) {
      return new R<>(ResultCode.SUCCESS, msg);
   }

   /**
    * 返回R
    *
    * @param resultCode 业务代码
    * @param <T>        T 泛型标记
    * @return R
    */
   public static <T> R<T> success(IResultCode resultCode) {
      return new R<>(resultCode);
   }

   /**
    * 返回R
    *
    * @param resultCode 业务代码
    * @param msg        消息
    * @param <T>        T 泛型标记
    * @return R
    */
   public static <T> R<T> success(IResultCode resultCode, String msg) {
      return new R<>(resultCode, msg);
   }

   /**
    * 返回R
    *
    * @param msg 消息
    * @param <T> T 泛型标记
    * @return R
    */
   public static <T> R<T> failure(String msg) {
      return new R<>(ResultCode.FAILURE, msg);
   }


   /**
    * 返回R
    *
    * @param code 状态码
    * @param msg  消息
    * @param <T>  T 泛型标记
    * @return R
    */
   public static <T> R<T> failure(int code, String msg) {
      return new R<>(code, null, msg);
   }

   /**
    * 返回R
    *
    * @param resultCode 业务代码
    * @param <T>        T 泛型标记
    * @return R
    */
   public static <T> R<T> failure(IResultCode resultCode) {
      return new R<>(resultCode);
   }

   /**
    * 返回R
    *
    * @param resultCode 业务代码
    * @param msg        消息
    * @param <T>        T 泛型标记
    * @return R
    */
   public static <T> R<T> failure(IResultCode resultCode, String msg) {
      return new R<>(resultCode, msg);
   }

   /**
    * 返回R
    *
    * @param flag 成功状态
    * @return R
    */
   public static R status(boolean flag) {
      return flag ? success(BladeConstant.DEFAULT_SUCCESS_MESSAGE) : failure(BladeConstant.DEFAULT_FAILURE_MESSAGE);
   }

}

谈谈缓存穿透、击穿、雪崩的区别,又如何去解决?

缓存穿透

缓存穿透代表的意思是在我们的缓存中没有找到缓存信息,那么我们在高并发场景下就会面临所有的请求都会直接打到DB,缓存则失去了它原本的意义,并且极有可能导致数据库压力过大而造成服务不可用。

  • 缓存空结果信息

  • 布隆过滤器(不存在的一定不存在,存在的可能不存在,通过bitmap实现,想深入布隆过滤器可以专门去看看这部分专题内容)

  • 过滤常见非法参数,拦截大部分无效请求()

缓存击穿

缓存击穿代表的意思是我们数据库中存在数据,但是缓存中不存在数据.这种场景一般是在缓存失效时发生的. 在高并发的场景下极有可能瞬间打垮数据库.

  • 我们可以考虑面对请求量大的热点接口直接将缓存设置永不过期.

  • 当然我们也可能碰到一些特殊场景不能设置永久缓存,那么我们可以在db为空时设置互斥锁,当查询完db更新至缓存时再释放锁

缓存雪崩

缓存雪崩代表是意思是我们在某一个时间段,碰到大量热点缓存数据过期导致大量请求直接打垮数据库

  • 我们可以考虑面对请求量大的热点接口直接将缓存设置永不过期.

  • 缓存过期时间可以设置一个随机的波动值,防止大量数据在同一时间过期

MySQL里有2000w数据Redis中只存20w的数据,如何保证 redis 中的数据都是热点数据?

首先我们可以看到Redis的空间实际上比我们MySQL少的多,那么Redis如何能够筛选出热点数据,这道题主要考察的是Redis的数据淘汰策略(这里有个误区,很多人容易混淆把数据淘汰策略当做数据过期策略),在Redis 4.0之后是为我们提供了8种淘汰策略,4.0之前则是提供的6种,主要是新增了LFU算法。其实说说是有8种,但是真正意义上是5种,针对random、lru、lfu是提供了两种不同数据范围的策略,一种是针对设置了过期时间的,一种是没有设置过期时间的。具体的五种策略分别为:

  1. noeviction 选择这种策略则代表不进行数据淘汰,同时它也是redis中默认的淘汰策略,当缓存写满时redis就不再提供写服务了,写请求则直接返回失败。

  2. random 随机策略这块则是分为两种,一种是volatile,这种是设置了过期时间得数据集,而另外一种是allkeys,这种是包含了所有的数据,当我们缓存满了的时候,选用这种策略就会在我们的数据集中进行随机删除。

  3. volatile-ttl 这种策略是针对设置了过期时间的数据,并且按照过期时间的先后顺序进行删除,越早过期的越先被删除

  4. lru 这里的lru策略和我们上面random策略一样也是提供了两种数据集进行处理,LRU算法全程为(最近最少使用)简单一句话来概括就是“如果数据最近被访问过,那么将来被访问的几率也就越高”。这种算法其实已经比较符合我们的实际业务需求了,但是还是存在一些缺陷。

  5. lfu 最后一种策略就是我们的LFU算法,它是在我么LRU算法基础上增加了请求数统计,这样能够更加精准的代表我们的热点数据。

我们再回看我们的这个问题,我们能很清楚的知道,我们需要的策略是LFU算法。选择volatile还是allkeys就要根据具体的业务需求了。

零拷贝是什么

零拷贝指的是,应用程序在需要把内核中的一块区域数据转移到另外一块内核区域去时,不需要经过先复制到用户空间,再转移到目标内核区域去了,而直接实现转移。

16975ba8fcece1dab2a8f1998a570f59.png

跨域请求是什么?有什么问题?怎么解决?

跨域是指浏览器在发起网络请求时,会检查该请求所对应的协议、域名、端口和当前网页是否一致,如果不一致则浏览器会进行限制,比如在www.baidu.com的某个网页中,如果使用ajax去访问www.jd.com是不行的,但是如果是img、iframe、script等标签的src属性去访问则是可以的,之所以浏览器要做这层限制,是为了用户信息安全。但是如果开发者想要绕过这层限制也是可以的:

  1. response添加header,比如resp.setHeader("Access-Control-Allow-Origin", "*");表示可以访问所有网站,不受是否同源的限制

  2. jsonp的方式,该技术底层就是基于script标签来实现的,因为script标签是可以跨域的

  3. 后台自己控制,先访问同域名下的接口,然后在接口中再去使用HTTPClient等工具去调用目标接口

  4. 网关,和第三种方式类似,都是交给后台服务来进行跨域访问

浏览器发出一个请求到收到响应经历了哪些步骤?

  1. 浏览器解析用户输入的URL,生成一个HTTP格式的请求

  2. 先根据URL域名从本地hosts文件查找是否有映射IP,如果没有就将域名发送给电脑所配置的DNS进行域名解析,得到IP地址

  3. 浏览器通过操作系统将请求通过四层网络协议发送出去

  4. 途中可能会经过各种路由器、交换机,最终到达服务器

  5. 服务器收到请求后,根据请求所指定的端口,将请求传递给绑定了该端口的应用程序,比如8080被tomcat占用了

  6. tomcat接收到请求数据后,按照http协议的格式进行解析,解析得到所要访问的servlet

  7. 然后servlet来处理这个请求,如果是SpringMVC中的DispatcherServlet,那么则会找到对应的Controller中的方法,并执行该方法得到结果

  8. Tomcat得到响应结果后封装成HTTP响应的格式,并再次通过网络发送给浏览器所在的服务器

  9. 浏览器所在的服务器拿到结果后再传递给浏览器,浏览器则负责解析并渲染

你的项目中是怎么保证微服务敏捷开发的?

  • 开发运维一体化。

  • 敏捷开发: 目的就是为了提高团队的交付效率,快速迭代,快速试错

  • 每个月固定发布新版本,以分支的形式保存到代码仓库中。快速入职。任务面板、站立会议。团队人员灵活流动,同时形成各个专家代表

  • 测试环境- 生产环境 -开发测试环境SIT-集成测试环境-压测环境STR-预投产环境-生产环境PRD

  • 晨会、周会、需求拆分会

如何进行消息队列选型?

  • Kafka:

    • 优点: 吞吐量非常大,性能非常好,集群高可用。

    • 缺点:会丢数据,功能比较单一。

    • 使用场景:日志分析、大数据采集

  • RabbitMQ:

    • 优点: 消息可靠性高,功能全面。

    • 缺点:吞吐量比较低,消息积累会严重影响性能。erlang语言不好定制。

    • 使用场景:小规模场景。

  • RocketMQ:

    • 优点:高吞吐、高性能、高可用,功能非常全面。

    • 缺点:开源版功能不如云上商业版。官方文档和周边生态还不够成熟。客户端只支持java。

    • 使用场景:几乎是全场景。

什么是中台?

所谓中台,就是将各个业务线中可以复用的一些功能抽取出来,剥离个性,提取共性,形成一些可复用的组件。

大体上,中台可以分为三类 业务中台、数据中台和技术中台。大数据杀熟-数据中台

中台跟DDD结合: DDD会通过限界上下文将系统拆分成一个一个的领域, 而这种限界上下文,天生就成了中台之间的逻辑屏障。

DDD在技术与资源调度方面都能够给中台建设提供不错的指导。

DDD分为战略设计和战术设计。 上层的战略设计能够很好的指导中台划分,下层的战术设计能够很好的指导微服务搭建。

怎样设计出高内聚、低耦合的微服务?

高内聚低耦合,是一种从上而下指导微服务设计的方法。实现高内聚低耦合的工具主要有 同步的接口调用 和 异步的事件驱动 两种方式。

有没有了解过DDD领域驱动设计?

什么是DDD: 在2004年,由Eric Evans提出了, DDD是面对软件复杂之道。Domain-Driven- Design –Tackling Complexity in the Heart of Software

大泥团: 不利于微服务的拆分。大泥团结构拆分出来的微服务依然是泥团机构,当服务业务逐渐复杂,这个泥团又会膨胀成为大泥团。

DDD只是一种方法论,没有一个稳定的技术框架。DDD要求领域是跟技术无关、跟存储无关、跟通信无关。

SOA、分布式、微服务之间有什么关系和区别?

  1. 分布式架构是指将单体架构中的各个部分拆分,然后部署不同的机器或进程中去,SOA和微服务基本上都是分布式架构的

  2. SOA是一种面向服务的架构,系统的所有服务都注册在总线上,当调用服务时,从总线上查找服务信息,然后调用

  3. 微服务是一种更彻底的面向服务的架构,将系统中各个功能个体抽成一个个小的应用程序,基本保持一个应用对应的一个服务的架构

怎么拆分微服务?

拆分微服务的时候,为了尽量保证微服务的稳定,会有一些基本的准则:

  1. 微服务之间尽量不要有业务交叉。

  2. 微服务之前只能通过接口进行服务调用,而不能绕过接口直接访问对方的数据。

  3. 高内聚,低耦合。

什么是服务熔断?什么是服务降级?区别是什么?

  1. 服务熔断是指,当服务A调用的某个服务B不可用时,上游服务A为了保证自己不受影响,从而不再调用服务B,直接返回一个结果,减轻服务A和服务B的压力,直到服务B恢复。

  2. 服务降级是指,当发现系统压力过载时,可以通过关闭某个服务,或限流某个服务来减轻系统压力,这就是服务降级。

相同点:

  1. 都是为了防止系统崩溃

  2. 都让用户体验到某些功能暂时不可用

不同点:熔断是下游服务故障触发的,降级是为了降低系统负载

分布式缓存寻址算法

  • hash算法:根据key进行hash函数运算、结果对分片数取模,确定分片 适合固定分片数的场景,扩展分片或者减少分片时,所有数据都需要重新计算分片、存储

  • 一致性hash:将整个hash值得区间组织成一个闭合的圆环,计算每台服务器的hash值、映射到圆环中。使用相同的hash算法计算数据的hash值,映射到圆环,顺时针寻找,找到的第一个服务器就是数据存储的服务器。新增及减少节点时只会影响节点到他逆时针最近的一个服务器之间的值 存在hash环倾斜的问题,即服务器分布不均匀,可以通过虚拟节点解决

  • hash slot:将数据与服务器隔离开,数据与slot映射,slot与服务器映射,数据进行hash决定存放的slot,新增及删除节点时,将slot进行迁移即可

Spring Cloud和Dubbo有哪些区别?

Spring Cloud是一个微服务框架,提供了微服务领域中的很多功能组件,Dubbo一开始是一个RPC调用框架,核心是解决服务调用间的问题,Spring Cloud是一个大而全的框架,Dubbo则更侧重于服务调用,所以Dubbo所提供的功能没有Spring Cloud全面,但是Dubbo的服务调用性能比Spring Cloud高,不过Spring Cloud和Dubbo并不是对立的,是可以结合起来一起使用的。

分布式系统中常用的缓存方案有哪些

  • 客户端缓存:页面和浏览器缓存,APP缓存,H5缓存,localStorage 和 sessionStorage CDN缓存:内容存储:数据的缓存,内容分发:负载均衡

  • nginx缓存:静态资源

  • 服务端缓存:本地缓存,外部缓存

  • 数据库缓存:持久层缓存(mybatis,hibernate多级缓存),mysql查询缓存 操作系统缓存:PageCache、BufferCache

缓存过期都有哪些策略?

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立 即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量

  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,但是很消耗内存、许多的过期数据都还存在内存中。极端情况可能出现大量的过期key没有 再次被访问,从而不会被清除,占用大量内存。

  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key(是随机的), 并清除其中已过期的key。该策略是定时过期和惰性过期的折中方案。通过调整定时扫描的时间间隔和 每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

  • 分桶策略:定期过期的优化,将过期时间点相近的key放在一起,按时间扫描分桶。

如何避免缓存穿透、缓存击穿、缓存雪崩?

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

  • 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。

  • 缓存预热互斥锁

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承 受大量请求而崩掉。

解决方案:

  • 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

  • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有 效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户 反复用同一个id暴力攻击

  • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同 时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪 崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查 数据库。

解决方案:

  • 设置热点数据永远不过期。加互斥锁

Spring Cloud有哪些常用组件,作用是什么?

  1. Eureka:注册中心

  2. Nacos:注册中心、配置中心

  3. Consul:注册中心、配置中心

  4. Spring Cloud Config:配置中心

  5. Feign/OpenFeign:RPC调用

  6. Kong:服务网关

  7. Zuul:服务网关

  8. Spring Cloud Gateway:服务网关

  9. Ribbon:负载均衡

  10. Spring CLoud Sleuth:链路追踪

  11. Zipkin:链路追踪

  12. Seata:分布式事务

  13. Dubbo:RPC调用

  14. Sentinel:服务熔断

  15. Hystrix:服务熔断

加群联系作者vx:xiaoda0423

仓库地址:https://github.com/webVueBlog/JavaGuideInterview

  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值