騰訊面試: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.pswp.cn/news/271439.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/271439.shtml
英文地址,請注明出處:http://en.pswp.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頁面能給網站帶來哪些好處…

ajax類型的區別,ajax請求的類型 有post get 請問兩種有何區別?

1、 get是把參數數據隊列加到提交表單的ACTION屬性所指的URL中&#xff0c;值和表單內各個字段一一對應&#xff0c;在URL中可以看到。post是通過HTTP post機制&#xff0c;將表單內各個字段與其內容放置在HTML HEADER內一起傳送到ACTION屬性所指的URL地址。用戶看不到這個過程…

計算機系統基礎:虛擬存儲管理知識筆記

1、虛擬存儲管理概念 一個計算機任務只需要部分裝入主存便可以啟動運行&#xff0c;其余部分留在磁盤上&#xff0c;在需要的時候裝入主存&#xff0c;這樣可以提高主存空間的利用率。這樣該系統所具有的主存容量會比實際主存容量大很多&#xff0c;這樣的存儲器稱為虛擬存儲器…

lamba把逗號拼接的字符串轉為list

List<Long> detailIds Arrays.stream(settlementDetailIds.split(",")).map(s -> Long.parseLong(s.trim())).collect(Collectors.toList());

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

git 簡介在實際開發中&#xff0c;會使用git作為版本控制工具來完成團隊協作。因此&#xff0c;對基本的git操作指令進行總結是十分有必要的&#xff0c;本文對一些術語或者理論基礎&#xff0c;不重新碼字&#xff0c;可以參考廖雪峰老師的博文&#xff0c;本文只對命令做歸納…

Ubuntu 配置swftools(Ubuntu14.04)

1.下載文件 wget http://swftools.org/swftools-0.9.0.tar.gz tar -zvxf swftools-0.9.0.tar.gz wget http://www.ijg.org/files/jpegsrc.v7.tar.gz tar -zvxf jpegsrc.v7.tar.gz wget http://download.savannah.gnu.org/releases-noredirect/freetype/freetype-2.3.12.tar.gz …

傳統的線性降維方法效果不佳。_機器學習西瓜書簡明筆記(11)降維與度量學習...

上篇主要介紹了幾種常用的聚類算法&#xff0c;首先從距離度量與性能評估出發&#xff0c;列舉了常見的距離計算公式與聚類評價指標&#xff0c;接著分別討論了K-Means、LVQ、高斯混合聚類、密度聚類以及層次聚類算法。K-Means與LVQ都試圖以類簇中心作為原型指導聚類&#xff0…

計算機系統基礎:設備管理知識筆記

1、設備管理介紹 設備管理主要包括設備分配、緩沖區管理、實際物理I/O設備操作、通過管理提高設備利用率和方便用戶使用的目的。 設備屬于計算機系統和外界交互的工具&#xff0c;不要負責計算機和外部的輸入/輸出工作&#xff0c;通常稱為外設。 2、設備分類 2.1 按數據組織分…

灌籃高手微信登錄是什么服務器,灌籃高手微信登錄版本

這款《灌籃高手》手游游戲是由同名的火爆動漫改寫而成的&#xff0c;里邊的游戲角色都是有極致的復原和保存&#xff0c;足球運動員特點都是有一定的展現。也有經典的動漫故事情節和每個高等院校足球隊的復原。現階段為大伙兒出示的是灌籃高手微信登錄版本&#xff0c;能夠和小…

寫代碼時注意的幾點

一.規范性 書寫清晰布局清晰命名合理命名盡量使用有意義的單詞; 變量常用i,j,k命名&#xff1b; 函數常用f,h,g命名。 二.完整性 完成基本功能考慮邊界條件 做好錯誤處理對應的考慮好三個測試 功能性測試 邊界測試 容錯測試 三.魯棒性 采取防御式編程 處理無效的輸入 轉載于:…

華為發布岳云鵬手機_剛剛,華為發布鴻蒙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;可能是想全部刪除又或者是想特定刪除一些而不小心全清理了&…

云上城之個服務器維護時間,云上城之歌寒冬邊界開服時間表_云上城之歌新區開服預告_第一手游網手游開服表...

今日開服15:00三十八區蒼炎之門已經開服2021-08-1010:00三十八區巨石林野已經開服2021-08-0915:00三十八區熒光要塞已經開服10:00三十八區雷神圣所已經開服2021-08-0710:00三十八區萬華天街已經開服2021-08-0615:00三十八區不朽要塞已經開服2021-08-0515:00三十八區圣靈古域已經…

java中成員變量和局部變量的區別

成員變量和局部變量的區別 (1)在類中的位置不同 成員變量&#xff1a;類中方法外 局部變量&#xff1a;在方法或者代碼塊中&#xff0c;或者方法的聲明上&#xff08;即在參數列表中&#xff09; (2)在內存中的位置不同 成員變量&#xff1a;在…

jh鋰電保護電路_鋰電池過充電、過放電、過流及短路保護電路原理及電路圖

下圖為一個典型的鋰離子電池保護電路原理圖。該保護回路由兩個MOSFET(V1、V2)和一個控制IC(N1)外加一些阻容元件構成。控制IC負責監測 電池電壓與回路電流&#xff0c;并控制兩個MOSFET的柵極&#xff0c;MOSFET在電路中起開關作用&#xff0c;分別控制著充電回路與放電回路的導…

一個串口接2個設備_重慶市有2個大觀鎮,一個鄉村旅游發達,一個特產柚子

同名的鄉鎮是很常見的現象&#xff0c;就連一個市里就有很多同名的鄉鎮&#xff0c;在之前的文章里說過&#xff0c;重慶市有2個臨江鎮、2個義和鎮等。今天繼續看看&#xff0c;重慶市內兩個同名鄉鎮——大觀鎮&#xff0c;一個屬于南川區&#xff0c;一個屬于梁平區&#xff0…

泛型入門

java集合有個缺點——將一個對象放進集合之后&#xff0c;該對象的編譯類型就會變成Object類型&#xff08;其運行時類型不變&#xff09;編譯時不檢查對象的類型。 泛型概念&#xff1a;java的參數化類型&#xff0c;即在創建集合時指定集合元素的類型。 如&#xff0c;List&l…