腾讯面试:SaaS多租户,如何设计?

尼恩说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业网易、美团、字节、如阿里、滴滴、极兔、有赞、希音、百度、美团的面试资格,遇到很多很重要的面试题:

多租户设计,如何 技术选型?

什么是多租户,如何做架构设计?

你们的多租户,是怎么隔离的?

最近有小伙伴在面试腾讯的企业BG,又遇到了相关的面试题。小伙伴懵了, 当然,面试也就挂了。

小伙伴赶紧来求助尼恩,尼恩借助这个小伙伴的面试真题,给大家做一下系统化、体系化的微服务底层架构 梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”

也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典PDF》V149版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

特别提示:尼恩的3高架构宇宙,持续升级。

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】取

文章目录

    • 尼恩说在前面
    • 一、SaaS多租户简介
      • 什么是SaaS多租户技术
      • 1.1、什么是 SaaS多租户
      • 1.2、SaaS多租户的优势
      • 1.3、多租户模型
    • 二、SaaS多租户的数据隔离设计方案
      • 2.1、三种数据隔离架构设计的对比如下:
      • 2.2、MyBatis-Plus多租户插件优雅实现数据隔离
    • 三、MyBatisPlus实现多租户功能
      • 3.1、表及实体类添加租户ID
      • 3.2、application文件中添加多租户配置和新增配置属性类
      • 3.3、编写多租户处理器实现TenantLineHandler接口
      • 3.4、MybatisPlus配置类启用多租户拦截插件
        • 运行sql实例:
      • 3.5、特定SQL语句忽略拦截
    • 参考文献:
    • 说在最后:有问题可以找老架构取经
    • 尼恩技术圣经系列PDF

一、SaaS多租户简介

多租户技术是一种软件架构技术,它是在探讨与实现如何于多用户的环境下共用相同的系统或程序组件,并且仍可确保各用户间数据的隔离性。它是为共用的数据中心内如何以单一系统架构与服务提供多数客户端相同甚至可定制化的服务,并且仍可保障客户的数据隔离。简单来说是一个单独的实例可以为多个组织服务。

多租户是SaaS(Software-as-a-Service)下的一个概念,意思为软件即服务,即通过网络提供软件服务。SaaS平台供应商将应用软件统一部署在自己的服务器上,客户可以根据工作的实际需求,通过互联网向厂商租用所需的应用软件服务,按定购的服务多少和时间长短向厂商支付费用,并通过互联网获得SaaS平台供应商提供的服务。

SaaS服务尤其利于一些中小企业,以低成本实现自己的软件需求。

注意:请点击图像以查看清晰的视图!

什么是SaaS多租户技术

  • 多租户技术或称多重租赁技术,是一种软件架构技术,是实现如何在多用户环境下(此处的多用户一般是面向企业)共用相同的系统或程序组件,并且确保各用户间数据隔离性。
  • 在一台服务器上运行单个应用实例,它为多个租户(客户)提供服务。从定义中我们可以理解:多租户是一种架构,目的是为了让多用户环境下使用同一套程序,且保证用户间数据隔离。多租户的重点就是同程序下实现多用户数据的隔离。

1.1、什么是 SaaS多租户

  • SaaS,是Software-as-a-Service的缩写名称,意思为软件即服务,即通过网络提供软件服务。
  • SaaS平台供应商将应用软件统一部署在自己的服务器上,客户可以根据工作实际需求,通过互联网向厂商定购所需的应用软件服务,按定购的服务多少和时间长短向厂商支付费用,并通过互联网获得Saas平台供应商提供的服务。
  • SaaS服务通常基于一套标准软件系统为成百上千的不同客户(又称为租户)提供服务。这要求SaaS服务能够支持不同租户之间数据和配置的隔离,从而保证每个租户数据的安全与隐私,以及用户对诸如界面、业务逻辑、数据结构等的个性化需求。由于SaaS同时支持多个租户,每个租户又有很多用户,这对支撑软件的基础设施平台的性能、稳定性和扩展性提出很大挑战。

多租户是SaaS领域的特有产物,探究何为多租户需回归到对SaaS的理解上。

SaaS服务是指部署在云上的,客户可以按需购买,并通过网络请求就能获取到的服务;也就是说,在这样的场景下,会有N个客户同时使用同一套SaaS服务。

那么对SaaS服务供应商来说,构建SaaS体系需要完成两部分工作:上层服务+底层多租户系统。

上层服务是供应商对外售卖的软件服务,其可以为客户创造价值、为公司带来营收;而底层多租户系统则是SaaS模式实现的具体方式,公司在对外售卖SaaS服务时,需要考虑如何实现客户之间的数据隔离、服务的权限控制、计费管理等;因此需要引入多租户概念来解决上述问题。

通过多租户系统,公司可以更好的管理客户和上层服务,客户也可以更好的使用软件服务。这也就是多租户系统存在的意义了。

1.2、SaaS多租户的优势

  • 开发和运维成本低
  • 按需付费,节约成本
  • 即租即用,软件版本更新快
  • 故障排查更及时
  • 大数据和AI的能力支持更强大

1.3、多租户模型

注意:请点击图像以查看清晰的视图!

如图所示,涉及主要模型有以下几类:

(1)租户:指一个企业客户或是个人客户,租户之间数据与行为隔离,上下级租户间通过授权实现数据共享。每个租户只能操作归属或授权给该租户的数据;

(2)组织:如果租户是一个企业客户,通常就会拥有自己的组织架构;

(3)用户:租户下的具体使用者,拥有用户名、密码、邮箱等账号信息的自然人;

(4)角色:用户操作权限的集合;

(5)员工:组织内的某位员工;

(6)解决方案:为了解决客户的某类型业务问题,SaaS供应商一般都将产品和服务组合在一起,为客户提供整体的打包方案;

(7)产品能力:能够帮助客户实现场景解决方案闭环的能力;

(8)资源域:用来运行1个或多个产品应用的一套云资源环境;

(9)云资源:SaaS产品一般都部署在各种云平台上,例如阿里云、腾讯云、华为云等。对这些云平台提供的计算、存储、网络、容器等资源,抽象为云资源。

二、SaaS多租户的数据隔离设计方案

多租户对于用户来说,最主要的一点就在于数据隔离。

绝对不能出现:一个用户登了A用户单位的号,但是看到了B用户单位的数据。因此,多租户的数据库设计方案和代码实现就相当有必要考虑了。

目前开发者们普遍接受的SaaS多租户设计方案,常见的大概就3种:即为每个租户提供独立的数据库、独立的表空间、按字段区分租户,每种方案都有其各自的适用情况。

  • 一个租户独立一个数据库

一个租户独立使用一个数据库,那就意味着我们的SaaS系统需要连接多个数据库,这种实现方案其实就和分库分表架构设计是一样的,好处就是数据隔离级别高、安全性好,毕竟一个租户单用一个数据库,但是物理硬件成本,维护成本也变高了。

  • 独立的表空间

这种方案的实现方式,就是所有租户共用一个数据库系统,但是每个租户在数据库系统中拥有一个独立的表空间。

  • 按租户id字段隔离租户

这种方案是多租户方案中最简单的数据隔离方法,即在每张表中都添加一个用于区分租户的字段(如tenant_id或org_id啥的)来标识每条数据属于哪个租户,当进行查询的时候每条语句都要添加该字段作为过滤条件,其特点是所有租户的数据全都存放在同一个表中,数据的隔离性是最低的,完全是通过字段来区分的,很容易把数据搞串或者误操作。

2.1、三种数据隔离架构设计的对比如下:

隔离方案成本支持租户数量优点缺点
独立数据库系统数据隔离级别高,安全性,可以针对单个租户开发个性化需求数据库独立安装,物理成本和维护成本都比较高
独立的表空间较多提供了一定程度的逻辑数据隔离,一个数据库系统可支持多个租户数据库管理比较困难,表繁多,同时数据修复稍复杂
按租户id字段区分维护和购置成本最低,每个数据库能够支持的租户数量最多隔离级别最低,安全性也最低

大部分公司都是采用第三种多租户设计方案:按租户id字段隔离租户 架构设计实现多租户数据隔离的。

因为这种方案服务器成本最低,但是提高了开发成本。

2.2、MyBatis-Plus多租户插件优雅实现数据隔离

该系统只有一个数据库,所有租户共用数据表。

在每一个数据表中增加一列租户ID,用以区分租户的数据。

增删查改时,一定要带上租户ID,否则就会操作到其他租户的数据。因此,这里的设计一定要重点考虑。

我们要保证的就是一定不要忘记带上租户ID。一个很好的方案就是通过AOP的方案,隐式的为我们的每一个SQL带上这个租户ID。

推荐使用MyBatisPlus来操作数据库的。它提供了插件的机制,我们可以通过拦截它提供的四大组件的某些对象,某些方法,来操作SQL,动态的为我们的SQL拼接上租户ID字段。

当然,MyBatis-Plus高版本提供了更加方便的拦截器,并且已经将多租户插件放入JAR包,我们只需稍加实现,并将该插件加入到MyBatis的拦截器链中,就可以不用再显式的拼接租户ID字段了,降低了出错的概率。

三、MyBatisPlus实现多租户功能

如果希望以最少的服务器为最多的租户提供服务,并且租户接受以牺牲隔离级别换取降低成本。可以采用方案三,即共享数据库,共享数据架构,因为这种方案服务器成本最低,但是提高了开发成本。

所以MybatisPlus就提供了一种多租户的解决方案,实现方式是基于多租户插件TenantLineInnerInterceptor进行实现的。

在 MyBatis Plus 中,采用“共享数据库,共享数据架构”方式实现多租户。

MybatisPlus提供了租户处理器( TenantId 行级 ),租户之间共享数据库,共享数据架构,通过表字段(租户ID)进行数据逻辑隔离。

该种实现方式,需要我们在要实现多租户的表中添加 tenant_id(租户ID)字段,每次在对数据库操作时都需要在 where 后面添加租户判断条件“tenant_id=用户的租户ID”。

然而,使用了 MyBatis Plus 后,我们就不需要每次都手动在 wehre 后面添加 tenant_id 条件。

注意事项:

  • 多租户 != 权限过滤,不要乱用,租户之间是完全隔离的!!!
  • 启用多租户后所有执行的method的sql都会进行处理.
  • 自写的sql请按规范书写(sql涉及到多个表的每个表都要给别名,特别是 inner join 的要写标准的 inner join)
<!-- Mybatis-Plus 增强CRUD -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version>
</dependency><!-- Mybatis-Plus 扩展插件 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-extension</artifactId><version>3.5.1</version>
</dependency>

TenantLineInnerInterceptor是MybatisPlus中提供的多租户插件,其使用方法大致分为下面4步:

3.1、表及实体类添加租户ID

应用添加维护一张tenant(租户表),记录租户的信息,每一个租户,有一个租户ID。

然后,在需要进行隔离的数据表上新增租户id,例如,现在有数据库表(user)如下:

租户ID一般用tenant_id

字段名字段类型描述
idLong主键
tenantIdLong租户编码
othervarchar(256)其他属性

将tenantId用来隔离租户与租户之间的数据,如果要查询当前服务商的用户,SQL大致如下:

SELECT * FROM table t WHERE  t.tenantId = 1;

3.2、application文件中添加多租户配置和新增配置属性类

(1)设置环境变量,配置拦截规则:

  • tenant.enable: 可以设置是否开启多租户,
  • tenant.ignoreTables:需要进行租户id过滤的表名集合。
  • tenant.filterTables:对多租户的表设置白名单忽略多租户拦截等。例如sys_user表结构中,没有tenant_id多租户字段,那么多租户拦截器不拦截该表。
#多租户配置
tenant:enable: truecolumn: tenant_idfilterTables:ignoreTables:- sys_app- sys_config- sys_dict_data- sys_dict_type- sys_logininfor- sys_menu- sys_notice- sys_oper_log- sys_role- sys_role_menu- sys_user- sys_user_roleignoreLoginNames:

(2)多租户配置属性类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;/*** 多租户配置属性类** @author hege* @Date 2023-08-25**/
@Data
@ConfigurationProperties(prefix = "tenant")
public class TenantProperties {/*** 是否开启多租户*/private Boolean enable = true;/*** 租户id字段名*/private String column = "tenant_id";/*** 需要进行租户id过滤的表名集合*/private List<String> filterTables;/*** 需要忽略的多租户的表,此配置优先filterTables,若此配置为空,则启用filterTables*/private List<String> ignoreTables;/*** 需要排除租户过滤的登录用户名*/private List<String> ignoreLoginNames;
}

3.3、编写多租户处理器实现TenantLineHandler接口

在 MyBatis Plus 中,提供了 TenantLineInnerInterceptor 插件和 TenantLineHandler 接口。

其中:

  • TenantLineInnerInterceptor 插件用来自动向每个 SQL 的 where 后面添加判断条件“tenant_id=用户的租户ID”。
  • 而 TenantLineHandler 接口用来给 TenantLineInnerInterceptor 插件提供租户ID、租户字段名。

TenantLineHandler 接口定义如下:

public interface TenantHandler {/*** 获取租户 ID 值表达式,支持多个 ID 条件查询* 支持自定义表达式,比如:tenant_id in (1,2) @since 2019-8-2* @param where 参数 true 表示为 where 条件 false 表示为 insert 或者 select 条件* @return 租户 ID 值表达式*/Expression getTenantId(boolean where);/*** 获取租户字段名* @return 租户字段名*/String getTenantIdColumn();/*** 根据表名判断是否进行过滤* @param tableName 表名* @return 是否进行过滤, true:表示忽略,false:需要解析多租户字段*/boolean doTableFilter(String tableName);
}

实现TenantHandler接口并实现它的方法,下面是一个例子:

import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.List;/*** 多租户处理器实现TenantLineHandler接口** @author hege* @Date 2023-08-25*/
public class MultiTenantHandler implements TenantLineHandler {private final TenantProperties properties;public MultiTenantHandler(TenantProperties properties) {this.properties = properties;}/*** 获取租户ID值表达式,只支持单个ID值 (实际应该从用户信息中获取)** @return 租户ID值表达式*/@Overridepublic Expression getTenantId() {//实际应该从用户信息中获取if(SecurityUtils.getTenantLoginUser()!=null){//SecurityUtils 从ThreadLocal里面的安全上下文 中获取 用户所归属的单位id(租户id)Long tenantId = SecurityUtils.getLoginUser().getUser().getRootPartyId();if(tenantId!=null){return new LongValue(tenantId);}}return new LongValue(0);}/*** 获取租户字段名,默认字段名叫: tenant_id** @return 租户字段名*/@Overridepublic String getTenantIdColumn() {//通过配置获取return properties.getColumn();}/*** 根据表名判断是否忽略拼接多租户条件** 默认都要进行解析并拼接多租户条件** @param tableName 表名* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件*/@Overridepublic boolean ignoreTable(String tableName) {//忽略指定用户对租户的数据过滤List<String> ignoreLoginNames=properties.getIgnoreLoginNames();//SecurityUtils 从ThreadLocal里面的安全上下文 中获取 用户名称String loginName=SecurityUtils.getTenantUsername();if(null!=ignoreLoginNames && ignoreLoginNames.contains(loginName)){return true;}//忽略指定表对租户数据的过滤List<String> ignoreTables = properties.getIgnoreTables();if (null != ignoreTables && ignoreTables.contains(tableName)) {return true;}return false;}
}

SecurityUtils 从ThreadLocal里面的安全上下文 中获取 用户名称, 用户所归属的单位id(租户id)

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.List;
import java.util.stream.Collectors;/*** 安全服务工具类** @author hege*/
public class SecurityUtils {/*** 获取多租户用户**/public static LoginUser getTenantLoginUser() {try {LoginUser loginUser = null;// 获取安全上下文对象,就是那个保存在ThreadLocal里面的安全上下文对象,总是不为null(如果不存在,则创建一个authentication属性为null的empty安全上下文对象)SecurityContext securityContext = SecurityContextHolder.getContext();// 获取当前认证了的 principal(当事人) 或者 request token (令牌); 如果没有认证,会是 null,该例子是认证之后的情况Authentication authentication = securityContext.getAuthentication();if(authentication!=null){if(authentication.getPrincipal()!=null){if (authentication.getPrincipal() instanceof LoginUser) {loginUser = (LoginUser) authentication.getPrincipal();}}}return loginUser;} catch (Exception e) {e.printStackTrace();throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);}}}

3.4、MybatisPlus配置类启用多租户拦截插件

前面讲到,在 MyBatis Plus 中,提供了 TenantLineInnerInterceptor 插件和 TenantLineHandler 接口。

其中,TenantLineInnerInterceptor 插件用来自动向每个 SQL 的 where 后面添加判断条件“tenant_id=用户的租户ID”。

TenantLineInnerInterceptor 插件 调用 TenantLineHandler 接口用来给 提供租户ID、租户字段名。

使用 @Configuration 和 @Bean 注解配置 MyBatis Plus 的多租户插件,

iimport com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;/*** Mybatis Plus 配置** @author hege*/
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
@EnableConfigurationProperties(TenantProperties.class)
public class MybatisPlusConfig {/*** 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor** @param tenantProperties* @return*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties tenantProperties) {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();if (Boolean.TRUE.equals(tenantProperties.getEnable())) {// 启用多租户插件拦截interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MultiTenantHandler(tenantProperties)));}// 分页插件interceptor.addInnerInterceptor(paginationInnerInterceptor());// 乐观锁插件interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());// 阻断插件interceptor.addInnerInterceptor(blockAttackInnerInterceptor());return interceptor;}}

配置好之后,不管是查询、新增、修改删除方法,MP都会自动加上租户ID的标识,测试如下:

@Test
public void select(){List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery().eq(User::getAge, 18));users.forEach(System.out::println);
}
运行sql实例:
DEBUG==> Preparing: SELECT id, login_name, name, password, email, salt, sex, age, phone, user_type, status,organization_id, create_time, update_time, version,tenant_id FROM sys_user WHERE sys_user.tenant_id = '001' AND is_delete = '0' AND age = ?

验证结果:

针对MybatisPlus提供的API、自定义Mapper中的statement均可正常拦截,会在SQL执行增删改查的时候自动加上tenant_id。

3.5、特定SQL语句忽略拦截

如果在程序中,有部分SQL不需要加上租户ID的表示,需要过滤特定的sql,或者对于一些超级管理员使用的接口,希望跨租户查询、免数据鉴权时,无需多租户拦截。

怎么办?

可以通过下面几种方式实现忽略拦截:

  • 方法1:使用MybatisPlus框架自带的@InterceptorIgnore注解,以用在Mapper类上,也可以用在方法上
  • 方法2:添加超级用户账号白名单,在自定义的Handler里进行逻辑判断,跳过拦截
  • 方法3:添加数据表白名单,在自定义的Handler里进行逻辑判断,跳过拦截

使用MybatisPlus框架自带的@InterceptorIgnore注解,以用在Mapper类上,也可以用在方法上, 下面是一个例子:

/*** 使用@InterceptorIgnore注解,忽略多租户拦截 <br/>* 注解@InterceptorIgnore可以用在Mapper类上,也可以用在方法上** @param id* @return*/
@InterceptorIgnore(tenantLine = "true")
UserOrgVO myFindByIdNoTenant(@Param(value = "id") Long id);

参考文献:

https://mp.weixin.qq.com/s/TR75wnxsXgFZ2ot1dOvX2w

https://mp.weixin.qq.com/s/CVTuEINWHCLue1oB7Yr3ng

https://mp.weixin.qq.com/s/Nl5Oll9GcF6JB8JvIb2YqA

https://zhuanlan.zhihu.com/p/420696556

https://blog.csdn.net/CSDN2497242041/article/details/132525117

说在最后:有问题可以找老架构取经

多租户相关的面试题,是非常常见的面试题。

以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。很多小伙伴刷完后, 吊打面试官, 大厂横着走。

在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

另外,如果没有面试机会,可以找尼恩来帮扶、领路。

尼恩指导了大量的小伙伴上岸,前段时间,刚指导一个40岁+就业困难小伙伴,拿到了一个年薪100W的offer。

尼恩技术圣经系列PDF

  • 《NIO圣经:一次穿透NIO、Selector、Epoll底层原理》
  • 《Docker圣经:大白话说Docker底层原理,6W字实现Docker自由》
  • 《K8S学习圣经:大白话说K8S底层原理,14W字实现K8S自由》
  • 《SpringCloud Alibaba 学习圣经,10万字实现SpringCloud 自由》
  • 《大数据HBase学习圣经:一本书实现HBase学习自由》
  • 《大数据Flink学习圣经:一本书实现大数据Flink自由》
  • 《响应式圣经:10W字,实现Spring响应式编程自由》
  • 《Go学习圣经:Go语言实现高并发CRUD业务开发》

……完整版尼恩技术圣经PDF集群,请找尼恩领取

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/271439.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac中Finder是什么?怎么打开?

很多人都知道windows系统中的资源管理器&#xff0c;不过不知道什么是finder。finder和资源管理器一样&#xff0c;都是用来管理文件&#xff0c;finder存在于mac统中&#xff0c;那么finder有什么作用呢?下面下班就为大家分享关于mac系统中finder的相关内容。   1、怎么打开…

phpstudy页面不存在_网站的404页面对于SEO的重要作用

随着网站建设的需求和要求越来越多&#xff0c;网站的新形式也逐渐成为人们改版的方向&#xff0c;但是在网站的改版中难免会出现一些小差错&#xff0c;导致网站的运行不顺畅&#xff0c;这很有可能就是网站建设中忘记设计404页面&#xff0c;那么4040页面能给网站带来哪些好处…

计算机系统基础:虚拟存储管理知识笔记

1、虚拟存储管理概念 一个计算机任务只需要部分装入主存便可以启动运行&#xff0c;其余部分留在磁盘上&#xff0c;在需要的时候装入主存&#xff0c;这样可以提高主存空间的利用率。这样该系统所具有的主存容量会比实际主存容量大很多&#xff0c;这样的存储器称为虚拟存储器…

git推送指令配置_git 常用命令

git 简介在实际开发中&#xff0c;会使用git作为版本控制工具来完成团队协作。因此&#xff0c;对基本的git操作指令进行总结是十分有必要的&#xff0c;本文对一些术语或者理论基础&#xff0c;不重新码字&#xff0c;可以参考廖雪峰老师的博文&#xff0c;本文只对命令做归纳…

传统的线性降维方法效果不佳。_机器学习西瓜书简明笔记(11)降维与度量学习...

上篇主要介绍了几种常用的聚类算法&#xff0c;首先从距离度量与性能评估出发&#xff0c;列举了常见的距离计算公式与聚类评价指标&#xff0c;接着分别讨论了K-Means、LVQ、高斯混合聚类、密度聚类以及层次聚类算法。K-Means与LVQ都试图以类簇中心作为原型指导聚类&#xff0…

计算机系统基础:设备管理知识笔记

1、设备管理介绍 设备管理主要包括设备分配、缓冲区管理、实际物理I/O设备操作、通过管理提高设备利用率和方便用户使用的目的。 设备属于计算机系统和外界交互的工具&#xff0c;不要负责计算机和外部的输入/输出工作&#xff0c;通常称为外设。 2、设备分类 2.1 按数据组织分…

灌篮高手微信登录是什么服务器,灌篮高手微信登录版本

这款《灌篮高手》手游游戏是由同名的火爆动漫改写而成的&#xff0c;里边的游戏角色都是有极致的复原和保存&#xff0c;足球运动员特点都是有一定的展现。也有经典的动漫故事情节和每个高等院校足球队的复原。现阶段为大伙儿出示的是灌篮高手微信登录版本&#xff0c;能够和小…

华为发布岳云鹏手机_刚刚,华为发布鸿蒙2.0!手机可用!

不凡的2020年激动人心的时刻终于到来今天下午3时华为开发者大会2020在正式开幕鸿蒙2.0发布&#xff01;本次大会包含主题演讲、技术论坛、松湖对话、Codelabs、Teach.Hour、互动体验等系列环节&#xff0c;大会将持续3天&#xff0c;众多活动也将于线上同步直播。技术论坛环节在…

Mac下安装jdk8

直接点击dmg文件 安装成功 在终端输入 Java -vesion 提供百度云链接方便大家下载 链接: https://pan.baidu.com/s/1n2SY-61KFb6-c1UcshZt1Q 链接: https://pan.baidu.com/s/1n2SY-61KFb6-c1UcshZt1Q 密码: g0mj

硬件基础:嵌入式物联网系统软硬件基础知识大全

本文主要介绍嵌入式系统的基础知识&#xff0c;涉及嵌入式软件和硬件的方方面面&#xff0c;希望对各位有帮助。嵌入式系统基础1、嵌入式系统的定义&#xff08;1&#xff09;定义&#xff1a;以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软硬件可裁剪&#xff0c;…

ctrl z撤销后如何恢复_回收站清空后数据如何恢复?

回收站清空后数据如何恢复&#xff1f;怎么恢复回收站误删除文件&#xff1f;很多人为了电脑的更好运行以及其它的原因&#xff0c;都会定期清理一下桌面的回收站里的文件&#xff0c;有时候会一键清空&#xff0c;可能是想全部删除又或者是想特定删除一些而不小心全清理了&…

jh锂电保护电路_锂电池过充电、过放电、过流及短路保护电路原理及电路图

下图为一个典型的锂离子电池保护电路原理图。该保护回路由两个MOSFET(V1、V2)和一个控制IC(N1)外加一些阻容元件构成。控制IC负责监测 电池电压与回路电流&#xff0c;并控制两个MOSFET的栅极&#xff0c;MOSFET在电路中起开关作用&#xff0c;分别控制着充电回路与放电回路的导…

一个串口接2个设备_重庆市有2个大观镇,一个乡村旅游发达,一个特产柚子

同名的乡镇是很常见的现象&#xff0c;就连一个市里就有很多同名的乡镇&#xff0c;在之前的文章里说过&#xff0c;重庆市有2个临江镇、2个义和镇等。今天继续看看&#xff0c;重庆市内两个同名乡镇——大观镇&#xff0c;一个属于南川区&#xff0c;一个属于梁平区&#xff0…

计算机系统基础:设备管理采用的相关技术知识笔记

1、通道技术 设备管理的通道技术是为了数据传输可以独立于CPU&#xff0c;让CPU从繁琐的I/O工作中解脱出来。设置通道后&#xff0c;CPU、只需要向通道发I/O指令,通道接收到指令后&#xff0c;从主存中取出本次要执行的通道程序并执行&#xff0c;只有完成了I/O任何后才会向CPU…

treelist自动定位行_国内首创!金川集团千米深井双定位补偿摇台投用

新甘肃客户端金昌讯(新甘肃甘肃日报记者谢晓玲)近日&#xff0c;金川集团二矿区18行副井提升系统罐笼自动化改造项目顺利完成并投入使用。项目研发的双定位补偿摇台是国内同行业首创&#xff0c;可以实现罐笼在井筒内水平和竖直方向的同步定位&#xff0c;从根本上解决了传统摇…

计算机系统基础:磁盘调度知识笔记

1、磁盘调度介绍 磁盘可以被多个进程共享的设备&#xff0c;如果有多个进程请求访问磁盘时&#xff0c;为了保证信息的安全&#xff0c;系统的每一时刻只允许一个进程进入磁盘进行I/O操作&#xff0c;别的进程需要等待。 磁盘需要采用一种适当的算法&#xff0c;使每个进程对磁…

freemarker 去掉最后一个逗号_从零开始做一个SLG游戏(六)游戏系统以及配置表...

本文主要是来梳理下游戏内的基本系统(虽然只是照搬polytopia的&#xff0c;但是还是要总结一下)&#xff0c;并预先做一些配置表&#xff0c;并实现读取。一、经济系统1.总述经济系统可以是整个游戏的核心。在本游戏中只有一种资源&#xff1a;金币。每个回合都会获得一定数量的…

计算机系统基础:文件管理相关知识笔记

一、文件的相关概念 1、为什么要有文件管理? 文件系统主要是为了方面用户访问外部存储器信息&#xff0c;而不去关注内部存储的机制&#xff0c;只需要根据文件名就可以高效的存取信息。 2、文件的定义 文件是这一组具有符号名、在逻辑上具有完整意义的一组相关信息项的集合。…