主頁 > 後端開發 > Tars | 第6篇 基于TarsGo Subset路由規則的Java JDK實作方式(下)

Tars | 第6篇 基于TarsGo Subset路由規則的Java JDK實作方式(下)

2021-09-13 06:39:08 後端開發

目錄
  • 前言
  • 1. 修改.tars協議檔案
    • 1.1 Java原始碼位置及邏輯分析
    • 1.2 Java語言實作方式
    • 1.3 通過協議檔案自動生成代碼
    • 1.4 變更代碼的路徑
  • 2. 【核心】增添Subset核心功能
    • 2.1 Java原始碼位置及邏輯分析
    • 2.2 Java語言實作方式
    • 2.3 變更代碼的路徑
  • 3. 添加常量與獲取染色key的方法
    • 3.1 Java原始碼位置及邏輯分析
    • 3.2 Java語言實作方式
    • 3.3 變更代碼的路徑
  • 4.【核心】修改獲取服務IP規則
    • 4.1 Java原始碼位置及邏輯分析
    • 4.2 Java語言實作方式
    • 4.3 變更代碼的路徑
  • 5. 實作透傳染色Key功能(客戶端)
    • 5.1 Java原始碼位置及邏輯分析
    • 5.2 Java語言實作方式
    • 5.3 變更代碼的路徑
  • 6. 實作透傳染色Key功能(服務端)
    • 6.1 Java原始碼位置及邏輯分析
    • 6.2 Java語言實作方式
    • 6.3 變更代碼的路徑
  • 7. 給節點資訊增添Subset欄位
    • 7.1 Java原始碼位置及邏輯分析
    • 7.2 Java語言實作方式
    • 7.3 變更代碼的路徑
  • * 8. 正則演算法的實作
    • 8.1 Java原始碼位置及邏輯分析
    • 8.2 Java語言實作方式
    • 8.3 變更代碼的路徑
  • * 9. 添加測驗代碼
    • 9.1 Java原始碼位置及邏輯分析
    • 9.2 Java語言實作方式
    • 9.3 變更代碼的路徑
  • 最后


前言

利開園導師用Go語言實作了Subset路由規則,并在中期匯報分享會里介紹出來;這篇文章將基于利導師的實作方式,對Subset路由規則的細節做些理解與補充,

此篇文章為下半部分,將對上半部分提到的TarsGo對Subset路由規則的實作做一一分析,重點放在“如果開發語言是Java,對應功能將如何實作”問題上,

上下部分文章在目錄上一一對應,上半注重TarsGo分析,下半部分注重TarsJava實作方式,如上篇文章第一點修改.tars協議檔案記錄利導師在TarsGo的代碼修改,下片文章第一點也是修改.tars協議檔案,側重點在如何用Java語言實作,背景關系章相輔相成,建議對照學習,

一些資源鏈接如下:

上半部分文章鏈接
https://www.cnblogs.com/dlhjw/p/15245113.html

TarsJava 實作Subset路由規則JDK鏈接地址
https://github.com/TarsCloud/TarsJava/commit/cc2fe884ecbe8455a8e1f141e21341f4f3dd98a3

TarsGo 實作Subset路由規則JDK鏈接地址

https://github.com/defool/TarsGo/commit/136878e9551d68c4b54c402df564729f51f3dd9c#


1. 修改.tars協議檔案

需要修改兩處.tars協議檔案;

1.1 Java原始碼位置及邏輯分析

該部分的含義是:增加Subset配置增加獲取Subset資訊

通過上半文章的分析,增加的配置是在EndpointF.tarsQueryF.tars協議檔案里面添加,而tars協議檔案在所有語言中是統一的,一樣的;在Java中,EndpointF協議檔案在src/main/resources/EndpointF.tars;QueryF協議檔案在src/main/resources/QueryF.tars;

因此,我們可以得到以下資訊:

  • 定位對應原始碼位置如下:
Go語言 Java
tars/protocol/res/EndpointF.tars TarsJava-1.7.x\core\src\main\resources\EndpointF.tars
tars/protocol/res/QueryF.tars TarsJava-1.7.x\core\src\main\resources\QueryF.tars
  • 直接添加subset配置即可;

1.2 Java語言實作方式

module tars
{
    /**
     * Port information
     */
    struct EndpointF
    {
        0  require  string host;
        1  require  int    port;
        2  require  int    timeout;
        3  require  int    istcp;
        4  require  int    grid;
        5  optional int    groupworkid;
        6  optional int    grouprealid;
        7  optional string setId;
        8  optional int    qos;
        9  optional int    bakFlag;
        11 optional int    weight;
        12 optional int    weightType;
        13 optional string subset;
    };
    key[EndpointF, host, port, timeout, istcp, grid, qos, weight, weightType];


};

QueryF.tars

1.3 通過協議檔案自動生成代碼

Tars有個強大的功能,它能根據.tars里的組態檔自動生成相應Bean代碼;

在Java語言里,具體操作如下:

1. 在專案的pom.xml里配置對應插件

<build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
        <plugins>
            <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
            <plugin>
                <groupId>com.tencent.tars</groupId>
                <artifactId>tars-maven-plugin</artifactId>
                <version>1.7.2</version>
                <configuration>
                    <tars2JavaConfig>
                        <!-- tars檔案位置 -->
                        <tarsFiles>
                            <tarsFile>${basedir}/src/main/resources/EndpointF.tars</tarsFile>
                        </tarsFiles>
                        <!-- 源檔案編碼 -->
                        <tarsFileCharset>UTF-8</tarsFileCharset>
                        <!-- 生成服務端代碼 -->
                        <servant>false</servant>
                        <!-- 生成源代碼編碼 -->
                        <charset>UTF-8</charset>
                        <!-- 生成的源代碼目錄 -->
                        <srcPath>${basedir}/src/main/java</srcPath>
                        <!-- 生成源代碼包前綴 -->
                        <packagePrefixName>com.qq.tars.common.support.</packagePrefixName>
                    </tars2JavaConfig>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

我們僅需要修改的地方在 tars檔案位置生成源代碼包前綴

2. 在專案根路徑下執行mvn tars:tars2java命令

專案根路徑輸入cmd
接著輸入mvn tars:tars2java命令后出現下面日志則說明生成成功;

BUILD SUCCESS

3. 檢查生成代碼

我們回到專案代碼,經檢查,EndpointF類發生了修改,新增SubsetConf類,(因為筆者在第一步生成源代碼包前綴沒有配置好,所有將生成后的代碼直接復制黏貼到源代碼路徑里了,影響不大,)

檢查代碼
4. 用同樣的方法可以自動生成QueryF代碼

1.4 變更代碼的路徑

通過上述操作,以下路徑的代碼發生改變,需要在github上提交:

  • TarsJava-1.7.x\core\src\main\resources\EndpointF.tars
  • TarsJava-1.7.x\core\src\main\resources\QueryF.tars
  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\support\query\prx\EndpointF.java
  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\support\query\prx\QueryFPrx.java
  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\support\query\prx\QueryFPrxCallback.java

2. 【核心】增添Subset核心功能

這部分是核心功能,不需要在原始碼里更改,屬于新增的內容,

2.1 Java原始碼位置及邏輯分析

該部分的含義是:增添Subset核心功能

由于Subset路由業務與客戶端相關,在Tars中的地位是:Tars支持(support)的功能之一,因此,筆者打算在參照原來的專案結構,在TarsJava-1.7.x\core\src\main\java\com\qq\tars\client路徑下新建包subset,包內實作以下功能:

新增型別 新增內容
結構體 新增Subset配置項的結構體 subsetConf
結構體 新增路由規則配置項的結構體ratioConfig
結構體 新增染色路徑的結構體keyRoute
結構體 新增染色配置項的結構體keyConfig
結構體 新增subset管理者的結構體subsetManager
方法 新增獲取subset配置項的方法getSubsetConfig
方法 新增獲取比例 / 染色路由配置項的方法getSubset
方法 新增根據subset規則過濾節點的方法subsetEndpointFilter
方法 新增根據一致hash的subset規則過濾節點的方法subsetHashEpFilter
方法 新增按比例路由路由路徑的方法findSubet
方法 新增按默認路由路徑findSubet

因此,我們可以得到以下資訊:

  • 定位對應原始碼位置如下:
Go語言 Java
tars/subset.go TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\subset\

2.2 Java語言實作方式

筆者的理解是五個結構體各自新建成一個類,此外新建Subset類;根據TarsGo實作邏輯:

  • SubsetConf類里定義一些屬性,并生成對應getter與setter方法;
  • RatioConfig類里實作findSubet()方法;
    • *在KeyRoute類里實作getRouteKey()setRouteKey()setRouteKeyToRequest()方法;
    • 這里提到的方法請見《3. 添加常量與獲取染色key的方法》與《5. 實作透傳染色Key功能》分析;
  • KeyConfig類里實作findSubet()方法;
  • SubsetManager類里實作getSubsetConfig()getSubset()方法;
  • Subset類里實作subsetEndpointFilter()subsetHashEpFilter()方法

具體的實作代碼如下:

SubsetConf

public class SubsetConf {

    private boolean enanle;
    private String ruleType;
    private RatioConfig ratioConf;
    private KeyConfig keyConf;

    private Instant lastUpdate;

    public SubsetConf() {
        lastUpdate =  Instant.now();
    }


    public SubsetConf(boolean enanle, String ruleType, RatioConfig ratioConf, KeyConfig keyConf) {
        this.enanle = enanle;
        this.ruleType = ruleType;
        this.ratioConf = ratioConf;
        this.keyConf = keyConf;
        lastUpdate =  Instant.now();
    }

    public boolean isEnanle() {
        return enanle;
    }

    public void setEnanle(boolean enanle) {
        this.enanle = enanle;
    }

    public String getRuleType() {
        return ruleType;
    }

    public void setRuleType(String ruleType) {
        this.ruleType = ruleType;
    }

    public RatioConfig getRatioConf() {
        return ratioConf;
    }

    public void setRatioConf(RatioConfig ratioConf) {
        this.ratioConf = ratioConf;
    }

    public KeyConfig getKeyConf() {
        return keyConf;
    }

    public void setKeyConf(KeyConfig keyConf) {
        this.keyConf = keyConf;
    }

    public Instant getLastUpdate() {
        return lastUpdate;
    }

    public void setLastUpdate(Instant lastUpdate) {
        this.lastUpdate = lastUpdate;
    }
}

RatioConfig

public class RatioConfig {

    private Map<String, Integer> rules;


    //進行路由規則的具體實作,回傳subset欄位
    public String findSubet(String routeKey){
        //routeKey為空時隨機
        if( "".equals(routeKey) ){
            //賦值routeKey為獲取的隨機值
            Random random = new Random();
            int r = random.nextInt( rules.size() );
            routeKey = String.valueOf(r);
            int i = 0;
            for (String key : rules.keySet()) {
                if(i == r){
                    return key;
                }
                i++;
            }
        }

        //routeKey不為空時實作按比例演算法
        int totalWeight = 0;
        int supWeight = 0;
        String subset = null;
        //獲得總權重
        for (Integer value : rules.values()) {
            totalWeight+=value;
        }
        //獲取亂數
        Random random = new Random();
        int r = random.nextInt(totalWeight);
        //根據亂數找到subset
        for (Map.Entry<String, Integer> entry : rules.entrySet()){
            supWeight+=entry.getValue();
            if( r < supWeight){
                subset = entry.getKey();
                return subset;
            }
        }
        return null;
    }

    public Map<String, Integer> getRules() {
        return rules;
    }

    public void setRules(Map<String, Integer> rules) {
        this.rules = rules;
    }
}

KeyRoute

  • 這里提到的方法請見《3. 添加常量與獲取染色key的方法》分析;
public class KeyRoute {

    private String action = null;
    private String value = https://www.cnblogs.com/dlhjw/archive/2021/09/12/null;
    private String route = null;

    public static final String TARS_ROUTE_KEY ="TARS_ROUTE_KEY";

    private static final Logger logger = LoggerFactory.getClientLogger();


    //根據分布式背景關系資訊獲取KeyRoute
    public static String getRouteKey(DistributedContext distributedContext){
        if( distributedContext == null ){
            logger.info("無分布式背景關系資訊distributedContext");
        }
        String routeValuehttps://www.cnblogs.com/dlhjw/archive/2021/09/12/= "";
        if(distributedContext != null){
            TarsServantRequest tarsServantRequest = distributedContext.get(DyeingSwitch.REQ);
            if( tarsServantRequest != null){
                routeValue = https://www.cnblogs.com/dlhjw/archive/2021/09/12/tarsServantRequest.getStatus().get(TARS_ROUTE_KEY);
            }
        }
        return routeValue;
    }

    //根據分布式背景關系資訊設定KeyRoute
    public static void setRouteKey(DistributedContext distributedContext, String routeKey){

        if(distributedContext != null && routeKey != null ){
            TarsServantRequest tarsServantRequest = distributedContext.get(DyeingSwitch.REQ);
            tarsServantRequest.getStatus().put(TARS_ROUTE_KEY, routeKey);
        }
    }

    public static void setRouteKeyToRequest(DistributedContext distributedContext, TarsServantRequest request){
        if( distributedContext == null ){
            logger.info("無分布式背景關系資訊distributedContext");
        }
        String routeValue = https://www.cnblogs.com/dlhjw/archive/2021/09/12/KeyRoute.getRouteKey(distributedContext);
        if( routeValue != null && !"".equals(routeValue)){
            if(request.getStatus() != null){
                request.getStatus().put(KeyRoute.TARS_ROUTE_KEY ,routeValue);
            } else {
                HashMap<String, String> status = new HashMap<>();
                status.put(KeyRoute.TARS_ROUTE_KEY ,routeValue);
                request.setStatus(status);
            }
        }
    }

    //將分布式背景關系資訊的routeValue 設定到KeyRoute.value
    public void setValue(DistributedContext distributedContext){
        String routeKey = getRouteKey(distributedContext);
        if( !"".equals(routeKey) && routeKey != null){
            this.value = https://www.cnblogs.com/dlhjw/archive/2021/09/12/routeKey;
        }
    }

    public KeyRoute() {
    }

    public KeyRoute(String action, String value, String route) {
        this.action = action;
        this.value = value;
        this.route = route;
    }

    public String getValue() {
        return value;
    }

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    public String getRoute() {
        return route;
    }

    public void setRoute(String route) {
        this.route = route;
    }
}

KeyConfig

  • 因為這里涉及正則匹配,所有在StringUtils工具類里有正則演算法的實作,詳情見《8. 正則演算法的實作》;
public class KeyConfig {

    private String defaultRoute;

    private List<KeyRoute> rules;

    private DistributedContext distributedContext = DistributedContextManager.getDistributedContext();

    private static final Logger logger = LoggerFactory.getClientLogger();

    public String findSubet(String routeKey){
        //非空校驗
        if( routeKey == null || "".equals(routeKey) || rules == null){
            return null;
        }
        for ( KeyRoute rule: rules) {
            //根據根據分布式背景關系資訊獲取 “請求的染色的key”
            String routeKeyReq;
            if( distributedContext != null){
                routeKeyReq = KeyRoute.getRouteKey(distributedContext);
            } else {
                logger.info("無分布式背景關系資訊distributedContext");
                return null;
            }
            //精確匹配
            if( "match".equals(rule.getAction())  ){
                if( routeKeyReq.equals(rule.getValue()) ){
                    return rule.getRoute();
                } else {
                    logger.info("染色key匹配不上,請求的染色key為:" + routeKeyReq + "; 規則的染色key為:" + rule.getValue());
                }
            }
            //正則匹配
            if( "equal".equals(rule.getAction()) ){
                if( StringUtils.matches(routeKeyReq, rule.getValue()) ){
                    return rule.getRoute();
                } else {
                    logger.info("正則匹配失敗,請求的染色key為:" + routeKeyReq + "; 規則的染色key為:" + rule.getValue());
                }

            }
            //默認匹配
            if( "default".equals(rule.getAction()) ){
                //默認路由無需考慮染色key
                return rule.getRoute();
            }
        }
        return null;
    }

    public KeyConfig() {
    }

    public KeyConfig(String defaultRoute, List<KeyRoute> rules) {
        this.defaultRoute = defaultRoute;
        this.rules = rules;
    }

    public String getDefaultRoute() {
        return defaultRoute;
    }

    public void setDefaultRoute(String defaultRoute) {
        this.defaultRoute = defaultRoute;
    }

    public List<KeyRoute> getRules() {
        return rules;
    }

    public void setRules(List<KeyRoute> rules) {
        this.rules = rules;
    }
}

SubsetManager

public class SubsetManager {

    private Map<String, SubsetConf> cache = new HashMap<>();

    private QueryFPrx queryProxy;

    //獲取Subset路由規則,并存到subsetConf配置項
    public SubsetConf getSubsetConfig(String servantName){
        SubsetConf subsetConf = new SubsetConf();
        if( cache.containsKey(servantName) ){
            subsetConf = cache.get(servantName);

            //小于10秒從快取中取
            if( Duration.between(subsetConf.getLastUpdate() , Instant.now()).toMillis() < 1000 ){
                return subsetConf;
            }
        }
        // get config from registry
        Holder<SubsetConf> subsetConfHolder = new Holder<SubsetConf>(subsetConf);
        int ret = queryProxy.findSubsetConfigById(servantName, subsetConfHolder);
        SubsetConf newSubsetConf = subsetConfHolder.getValue();
        if( ret == TarsHelper.SERVERSUCCESS ){
            return newSubsetConf;
        }
        //從registry中獲取失敗時,更新subsetConf添加進快取
        subsetConf.setRuleType( newSubsetConf.getRuleType() );
        subsetConf.setLastUpdate( Instant.now() );
        cache.put(servantName, subsetConf);
        //決議subsetConf
        if( !newSubsetConf.isEnanle() ){
            subsetConf.setEnanle(false);
            return subsetConf;
        }
        if( "ratio".equals(newSubsetConf.getRuleType())){
            subsetConf.setRatioConf( newSubsetConf.getRatioConf() );
        } else {
            //按引數匹配
            KeyConfig newKeyConf = newSubsetConf.getKeyConf();
            List<KeyRoute> keyRoutes = newKeyConf.getRules();
            for ( KeyRoute kr: keyRoutes) {
                KeyConfig keyConf = new KeyConfig();
                //默認
                if("default".equals(kr.getAction())){
                    keyConf.setDefaultRoute(newKeyConf.getDefaultRoute());
                    subsetConf.setKeyConf(keyConf);
                }
                //精確匹配
                if("match".equals(kr.getAction())){
                    List<KeyRoute> rule = new ArrayList<>();
                    rule.add(new KeyRoute("match", kr.getValue() , kr.getRoute()));
                    keyConf.setRules( rule );
                }
                //正則匹配
                if("equal".equals(kr.getAction())){
                    List<KeyRoute> rule = new ArrayList<>();
                    rule.add(new KeyRoute("equal", kr.getValue() , kr.getRoute()));
                    keyConf.setRules( rule );
                }
            }
            subsetConf.setKeyConf(newKeyConf);
        }
        return subsetConf;
    }

    // 根據路由規則先獲取到比例 / 染色路由的配置,再通過配置獲取String的subset欄位
    public String getSubset(String servantName, String routeKey){
        //check subset config exists
        SubsetConf subsetConf = getSubsetConfig(servantName);
        if( subsetConf == null ){
            return null;
        }
        // route key to subset
        if("ratio".equals(subsetConf.getRuleType())){
            RatioConfig ratioConf = subsetConf.getRatioConf();
            if(ratioConf != null){
                return ratioConf.findSubet(routeKey);
            }
        }
        KeyConfig keyConf = subsetConf.getKeyConf();
        if ( keyConf != null ){
            return keyConf.findSubet(routeKey);
        }
        return null;
    }

    public SubsetManager() {
    }

    public SubsetManager(Map<String, SubsetConf> cache) {
        if(cache == null){
            this.cache = new HashMap<>();
        }
    }

    public Map<String, SubsetConf> getCache() {
        return cache;
    }

    public void setCache(Map<String, SubsetConf> cache) {
        this.cache = cache;
    }

}

Subset

public class Subset {

    private String hashString;

    private SubsetConf subsetConf;

    private KeyConfig keyConfig;
    private KeyRoute keyRoute;
    private RatioConfig ratioConfig;

    private SubsetManager subsetManager;


    //獲取到規則后的subset,與節點的subset比較,過濾不匹配節點
    public Holder<List<EndpointF>> subsetEndpointFilter(String servantName, String routeKey, Holder<List<EndpointF>> eps){

        if( subsetConf==null || !subsetConf.isEnanle() ){
            return eps;
        }

        if(eps.value =https://www.cnblogs.com/dlhjw/archive/2021/09/12/= null || eps.value.isEmpty()){
            return eps;
        }

        //呼叫subsetManager,根據比例/匹配等規則獲取到路由規則的subset
        String subset = subsetManager.getSubset(servantName, routeKey);
        if("".equals(subset) || subset == null){
            return eps;
        }
        //和每一個eps的subset比較,淘汰不符合要求的

        Holder<List<EndpointF>> epsFilter = new Holder<>(new ArrayList<EndpointF>());
        for (EndpointF ep : eps.value) {
            if( subset.equals(ep.getSubset())){
                epsFilter.getValue().add(ep);
            }
        }
        return epsFilter;
    }

    public Subset() {
    }

    public Subset(String hashString, SubsetConf subsetConf, KeyConfig keyConfig, KeyRoute keyRoute, RatioConfig ratioConfig) {
        this.hashString = hashString;
        this.subsetConf = subsetConf;
        this.keyConfig = keyConfig;
        this.keyRoute = keyRoute;
        this.ratioConfig = ratioConfig;
    }

    public String getHashString() {
        return hashString;
    }

    public void setHashString(String hashString) {
        this.hashString = hashString;
    }

    public SubsetConf getSubsetConf() {
        return subsetConf;
    }

    public void setSubsetConf(SubsetConf subsetConf) {
        this.subsetConf = subsetConf;
    }

    public KeyConfig getKeyConfig() {
        return keyConfig;
    }

    public void setKeyConfig(KeyConfig keyConfig) {
        this.keyConfig = keyConfig;
    }

    public KeyRoute getKeyRoute() {
        return keyRoute;
    }

    public void setKeyRoute(KeyRoute keyRoute) {
        this.keyRoute = keyRoute;
    }

    public RatioConfig getRatioConfig() {
        return ratioConfig;
    }

    public void setRatioConfig(RatioConfig ratioConfig) {
        this.ratioConfig = ratioConfig;
    }

    public SubsetManager getSubsetManager() {
        return subsetManager;
    }

    public void setSubsetManager(SubsetManager subsetManager) {
        this.subsetManager = subsetManager;
    }
}

2.3 變更代碼的路徑

通過上述操作,新增了以下代碼,需要在github上提交:

  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\subset\SubsetConf.java
  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\subset\KeyConfig.java
  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\subset\KeyRoute.java
  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\subset\RatioConfig.java
  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\subset\Subset.java
  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\subset\SubsetManager.java

3. 添加常量與獲取染色key的方法

3.1 Java原始碼位置及邏輯分析

該部分的含義是:添加常量添加獲取染色key的方法

在TarsJava中,染色相關的邏輯在DyeingKeyCacheDyeingSwitch類里;但我們新增的TARS_ROUTE_KEY染色key與原染色邏輯相關性不大,這里的TARS_ROUTE_KEY是隨著Tars的請求體TarsServantRequest里的中獲取status引數(map型別)傳遞而來的;

  • Tars的請求體路徑:TarsJava-1.7.x\core\src\main\java\com\qq\tars\rpc\protocol\tars\TarsServantRequest.java

因此設定 / 獲取染色key的邏輯應該是:通過分布式背景關系資訊DistributedContext獲取到TarsServantRequest請求體,再從請求體里的statusmap資料設定 / 獲取染色key相關;

因此,我們可以得到以下資訊:

  • 定位對應原始碼位置如下:
Go語言 Java
tars/subset.go TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\subset\KeyRoute.java

3.2 Java語言實作方式

跟《2.2 Java語言實作方式》中的KeyRoute一樣

3.3 變更代碼的路徑

通過上述操作,改變了以下代碼,需要在github上提交:

  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\subset\KeyRoute.java

4.【核心】修改獲取服務IP規則

4.1 Java原始碼位置及邏輯分析

該部分的含義是:節點管理

在Go語言中,我們點進去tars/endpointmanager.go查看原始碼發現該代碼的作用是:創建一個結點管理器,通過管理器可以實作查看節點狀態checkEpStatus()、更新節點資訊updateEndpoints()等功能,

修改的方法為SelectAdapterProxy()選擇配接器代理,原邏輯為獲取服務端節點串列,新增邏輯為subsetEndpointFilter()為根據subset規則過濾節點;

而在Java語言中,類似功能在ObjectProxyFactory類里,該類的功能主要是:創建代理物件,通過代理物件實作更新節點updateServantEndpoints()、創建服務代理配置項createServantProxyConfig()等功能,

其中在updateServantEndpoints()方法里涉及到更新服務節點串列,但在Java中使用了一個QueryHelper查詢工具,里面有個getServerNodes()方法獲取服務端節點串列,我們要修改的地方就在這里,

因此,我們可以得到以下資訊:

  • 定位對應原始碼位置如下:
Go語言 Java語言
tars/endpointmanager.go TarsJava-1.7.x\core\src\main\java\com\qq\tars\support\query\QueryHelper.java
  • 增加的方法邏輯

由于在java里節點的儲存是使用Holder<List<EndpointF>>物件而并不是LIst,因此對應引數型別改成Holder;

專案 說明
方法名 subsetEndpointFilter
實作邏輯 根據subset規則過濾節點
傳入引數 服務名String、染色狀態String、存活的節點Holder
回傳引數 過濾后的節點Holder

這里的染色邏輯

新添加的獲取染色key的方法與原來染色邏輯類似,可以參照相應實作邏輯;

在TarsGo里,通過msg.Req.狀態[current.STATUS_ROUTE_KEY]獲取routeKey欄位;通過msg.Req.SServantName獲取服務名;

而在TarsJava里,通過ServantProxyConfig.getSimpleObjectName()獲取服務名,獲取routeKey欄位則比較復雜;我們需要的最終染色欄位在Tars請求體TarsServantRequest里的status引數(map型別);

獲取的邏輯是:通過分布式背景關系資訊DistributedContext獲取到TarsServantRequest請求體,再從請求體里的statusmap獲取染色key;

4.2 Java語言實作方式

public String getServerNodes(ServantProxyConfig config) {
    QueryFPrx queryProxy = getPrx();

    //【新增】通過KeyRoute類與分布式背景關系資訊獲取routeKey
    String routeKey = getRouteKeyByContext();
    String name = config.getSimpleObjectName();

    //存活的節點
    Holder<List<EndpointF>> activeEp = new Holder<List<EndpointF>>(new ArrayList<EndpointF>());
    //掛掉的節點
    Holder<List<EndpointF>> inactiveEp = new Holder<List<EndpointF>>(new ArrayList<EndpointF>());
    int ret = TarsHelper.SERVERSUCCESS;
    //判斷是否為啟用集
    if (config.isEnableSet()) {
        ret = queryProxy.findObjectByIdInSameSet(name, config.getSetDivision(), activeEp, inactiveEp);
    } else {
        ret = queryProxy.findObjectByIdInSameGroup(name, activeEp, inactiveEp);
    }

    if (ret != TarsHelper.SERVERSUCCESS) {
        return null;
    }
    Collections.sort(activeEp.getValue());
    
    //【新增】根據Subset規則過濾節點
    Holder<List<EndpointF>> activeEpFilter = subset.subsetEndpointFilter(name, routeKey, activeEp);
    
    //將獲取到的節點串列格式化為一個字串格式
    StringBuilder value = https://www.cnblogs.com/dlhjw/archive/2021/09/12/new StringBuilder();
    if (activeEpFilter.value != null && !activeEpFilter.value.isEmpty()) {
        for (EndpointF endpointF : activeEpFilter.value) {
            if (value.length() > 0) {
                value.append(":");
            }
            value.append(ParseTools.toFormatString(endpointF, true));
        }
    }
    //個格式化后的字串加上Tars的服務名
    if (value.length() < 1) {
        return null;
    }
    value.insert(0, Constants.TARS_AT);
    value.insert(0, name);
    return value.toString();
}

//【新增】根據分布式背景關系資訊獲取RouteKey
public String getRouteKeyByContext(){
    KeyRoute routeKey = new KeyRoute();
    return KeyRoute.getRouteKey(DistributedContextManager.getDistributedContext())
}

4.3 變更代碼的路徑

通過上述操作,改變了以下代碼,需要在github上提交:

  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\support\query\QueryHelper.java

5. 實作透傳染色Key功能(客戶端)

5.1 Java原始碼位置及邏輯分析

該部分的含義是:透傳染色Key

是指染色key和value放到tars請求結構體的status引數,需要透傳給下游,這里討論客戶端,

在TarsGo里,這部分代碼位置在tars/servant.go,通過閱讀原始碼背景關系,我們可以得知這個類主要圍繞ServantProxy服務代理器而作業的;透傳染色Key是在ServantProxyTars_invoke方法里實作的,invoke方法一般是最終要執行的方法;

在TarsJava里,對Tars_invoke類似的方法進行了層層封裝;通過之前分析的客戶端負載均衡原始碼分析可知,最終的執行方法在TarsInvoker類的doInvokeServant方法里,而該方法又對異步呼叫、同步呼叫、協程呼叫三種形式,這三個呼叫才是最終執行方法,

因此,我們可以得到以下資訊:

  • 定位對應原始碼位置如下:
Go語言 Java
tars/servant.go TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\rpc\tars\TarsInvoker.java

5.2 Java語言實作方式

在KeyRoute類里添加一個靜態方法setRouteKeyToRequest(),邏輯是通過分布式背景關系資訊,判斷Tars請求體的status(map型別)是否存在TARS_ROUTE_KEY鍵值對,存在則設定到Tars的回應體透傳給下游,不存在則不處理;

之所以添加到KeyRoute類,是因為該方法需要在多處地方重用,如《6.2 Java語言實作方式》;

public static void KeyRoute.setRouteKeyToRequest(DistributedContext distributedContext, TarsServantRequest request){
    String routeKey = KeyRoute.getRouteKey(distributedContext);
    if( routeKey != null && !"".equals(routeKey)){
        if(request.getStatus() != null){
            request.getStatus().put(KeyRoute.TARS_ROUTE_KEY ,routeKey);
        } else {
            HashMap<String, String> status = new HashMap<>();
            status.put(KeyRoute.TARS_ROUTE_KEY ,routeKey);
            request.setStatus(status);
        }
    }
}

然后在同步呼叫方法invokeWithSync()、異步呼叫方法invokeWithAsync()和協程呼叫方法invokeWithPromiseFuture()里,呼叫上述方法即可,

呼叫setRouteKey方法(客戶端)

5.3 變更代碼的路徑

通過上述操作,改變了以下代碼,需要在github上提交:

  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\rpc\tars\TarsInvoker.java
  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\client\subset\KeyRoute.java

6. 實作透傳染色Key功能(服務端)

6.1 Java原始碼位置及邏輯分析

該部分的含義是:透傳染色Key

是指染色key和value放到tars請求結構體的status引數,需要透傳給下游,這里討論服務端,

在TarsGo里,這部分代碼位置在tars/tarsprotocol.go,通過閱讀原始碼背景關系,我們可以得知這個類主要圍繞TarsProtocolTars服務端協議而作業的;透傳染色Key是在TarsProtocolInvoke方法里實作的,其主要功能是將request請求作為位元組陣列,呼叫dispather,然后以位元組陣列回傳response回應;

在TarsJava中,Tars服務處理器為TarsServantProcessor,其中的process()方法邏輯是處理request請求到response回應轉換;

因此,我們可以得到以下資訊:

  • 定位對應原始碼位置如下:
Go語言 Java
tars/tarsprotocol.go TarsJava-1.7.x\core\src\main\java\com\qq\tars\server\core\TarsServantProcessor.java

6.2 Java語言實作方式

直接呼叫setRouteKeyToRequest()方法即可;

呼叫setRouteKey方法(服務端)

6.3 變更代碼的路徑

通過上述操作,改變了以下代碼,需要在github上提交:

  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\server\core\TarsServantProcessor.java

7. 給節點資訊增添Subset欄位

7.1 Java原始碼位置及邏輯分析

該部分的含義是:增添Subset欄位

在TarsGo中,這部分代碼位置在endpoint.go,比較簡單,增加了一個String型別的Subset欄位屬性;

在TarsJava中,endpoint的原始碼位置很容易找到,直接修改即可;主要修改兩處,增加一個subset欄位以及修改決議方法;

因此,我們可以得到以下資訊:

  • 定位對應原始碼位置如下:
Go語言 Java
tars/util/endpoint/endpoint.go和tars/util/endpoint/convert.go TarsJava-1.7.x\core\src\main\java\com\qq\tars\common\support\Endpoint.java

7.2 Java語言實作方式

public class Endpoint {

    private final String type;
    private final String host; 
    private final int port; 

    private final int timeout;
    private final int grid; 
    private final int qos; 
    private final String setDivision;
    //新增
    private String subset;
    ……
}

7.3 變更代碼的路徑

通過上述操作,改變了以下代碼,需要在github上提交:

  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\common\support\Endpoint.java

* 8. 正則演算法的實作

8.1 Java原始碼位置及邏輯分析

該部分的含義是:正則演算法匹配

因為在引數匹配里要求正則匹配,因此在String工具類里新增一個演算法實作正則匹配;

8.2 Java語言實作方式

public static Boolean matches(String regex, String input){
    //非空校驗
    if(regex==null || "".equals(regex) || input == null){
        return false;
    }
    char[] chars = regex.toCharArray();
    boolean flage = true;
    if( chars[0] == '*'){
        //如果regex是*開頭,如:*d123等,從d往后匹配;
        if( regex.length() < 2){
            return true;
        }
        int i;
        flage = false;
        for (i = 0; i < input.length(); i++) {
            if( input.charAt(i) == regex.charAt(1)){
                flage = true;
                for (int j = 1; j < regex.length(); j++) {

                    if( i > input.length() -1 && regex.charAt(j) != '*' ){
                        return false;
                    }

                    if( regex.charAt(j) == '*' || input.charAt(i) == regex.charAt(j)  ){
                        i++;
                    } else {
                        flage = false;
                    }


                }
            }
        }
    }else {
        if( chars[chars.length-1] == '*'){
            //如果regex是*結尾,如uid12*,從第一個字符開始匹配
            for (int i = 0; i < Math.min(regex.length(), input.length()); i++) {
                if(regex.charAt(i) == input.charAt(i) || regex.charAt(i) == '*'){
                    if( i == Math.min(regex.length(), input.length()) -1 && regex.length() > input.length()+1 ){
                        flage = false;
                    }

                } else {
                    flage = false;
                }
            }
        } else {
            //如果沒有*,如uid123,
            flage = regex.equals(input);
        }
    }

    return flage;
}

8.3 變更代碼的路徑

通過上述操作,改變了以下代碼,需要在github上提交:

  • TarsJava-1.7.x\core\src\main\java\com\qq\tars\common\util\StringUtils.java

* 9. 添加測驗代碼

9.1 Java原始碼位置及邏輯分析

該部分的含義是:主要流量路由規則測驗

測驗中包含按比例路由單次測驗、按比例路由多次測驗、按引數精確路由測驗、按引數路由正則測驗,以及registry測驗;

由于其他同學部分的相關registry介面功能還未完成,故registry測驗會失敗,

9.2 Java語言實作方式

public class TestSubset {

    //創建Subset過濾器
    Subset subsetFilter = new Subset();

    //模擬objectName
    String objectName = "objectName";

    //模擬routeKey
    String routeKey = "routeKey";

    //存活節點list串列
    List<EndpointF> endpointFList = new ArrayList<EndpointF>();
    Holder<List<EndpointF>> activeEp = new Holder<List<EndpointF>>(new ArrayList<EndpointF>());

    //定義一個Session域,用來構建Tars請求體
    Session session;


    /**
     * 按比例路由規則 - 單次測驗
     * 沒有測驗registry獲取subsetConf功能
     */
    @Test
    public void testRatioOnce() {

        //1. 給過濾器設定過濾規則
        //1.1 創建SubsetManager管理器
        SubsetManager subsetManager = new SubsetManager();


        //1.1 設定比例路由規則
        RatioConfig ratioConf = new RatioConfig();
        Map<String , Integer> map = new HashMap<>();
        map.put("v1",20);
        map.put("v2",80);
        //map.put("v3",20);
        ratioConf.setRules(map);

        //1.2 設定subsetConf,并加入快取
        SubsetConf subsetConf = new SubsetConf();
        subsetConf.setEnanle(true);
        subsetConf.setRuleType("ratio");
        subsetConf.setRatioConf(ratioConf);
        subsetConf.setLastUpdate( Instant.now() );

        Map<String, SubsetConf> cache = new HashMap<>();
        cache.put(objectName,subsetConf);
        subsetManager.setCache(cache);

        //1.3 給過濾器設定過濾規則和管理者
        subsetFilter.setSubsetConf(subsetConf);
        subsetFilter.setSubsetManager(subsetManager);


        //2. 模擬存活節點
        endpointFList.add(new EndpointF("host1",1,2,3,4,5,6,"setId1",7,8,9,10,"v1"));
        endpointFList.add(new EndpointF("host2",1,2,3,4,5,6,"setId2",7,8,9,10,"v1"));
        endpointFList.add(new EndpointF("host3",1,2,3,4,5,6,"setId3",7,8,9,10,"v2"));
        endpointFList.add(new EndpointF("host4",1,2,3,4,5,6,"setId4",7,8,9,10,"v2"));
        endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v2"));
        endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v3"));
        activeEp.setValue(endpointFList);


        //3. 輸出過濾前資訊
        System.out.println("過濾前節點資訊如下:");
        for( EndpointF endpoint : endpointFList){
            System.out.println(endpoint.toString());
        }

        //4. 對存活節點按subset規則過濾
        Holder<List<EndpointF>> filterActiveEp = subsetFilter.subsetEndpointFilter(objectName, routeKey, activeEp);

        //5. 輸出過濾結果

        System.out.println("過濾后節點資訊如下:");
        for( EndpointF endpoint : filterActiveEp.getValue() ){
            System.out.println(endpoint.toString());
        }
    }


    /**
     * 按比例路由規則 - 多次測驗
     * 沒有測驗registry獲取subsetConf功能
     */
    @Test
    public void testRatioTimes() {

        //1. 給過濾器設定過濾規則
        //1.1 創建SubsetManager管理器
        SubsetManager subsetManager = new SubsetManager();


        //1.1 設定比例路由規則
        RatioConfig ratioConf = new RatioConfig();
        Map<String , Integer> map = new HashMap<>();
        map.put("v1",20);
        map.put("v2",80);
        map.put("v3",20);
        ratioConf.setRules(map);

        //1.2 設定subsetConf,并加入快取
        SubsetConf subsetConf = new SubsetConf();
        subsetConf.setEnanle(true);
        subsetConf.setRuleType("ratio");
        subsetConf.setRatioConf(ratioConf);
        subsetConf.setLastUpdate( Instant.now() );

        Map<String, SubsetConf> cache = new HashMap<>();
        cache.put(objectName,subsetConf);
        subsetManager.setCache(cache);

        //1.3 給過濾器設定過濾規則和管理者
        subsetFilter.setSubsetConf(subsetConf);
        subsetFilter.setSubsetManager(subsetManager);


        //2. 模擬存活節點
        endpointFList.add(new EndpointF("host1",1,2,3,4,5,6,"setId1",7,8,9,10,"v1"));
        endpointFList.add(new EndpointF("host2",1,2,3,4,5,6,"setId2",7,8,9,10,"v1"));
        endpointFList.add(new EndpointF("host3",1,2,3,4,5,6,"setId3",7,8,9,10,"v2"));
        endpointFList.add(new EndpointF("host4",1,2,3,4,5,6,"setId4",7,8,9,10,"v2"));
        endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v2"));
        endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v3"));
        activeEp.setValue(endpointFList);


        //3. 回圈times次
        int times = 1000000;
        int v1Times = 0;
        int v2Times = 0;
        int v3Times = 0;
        int errTimes = 0;
        for (int i = 0; i < times; i++) {
            //對存活節點按subset規則過濾
            Holder<List<EndpointF>> filterActiveEp = subsetFilter.subsetEndpointFilter(objectName, routeKey, activeEp);
            String subsetValue = https://www.cnblogs.com/dlhjw/archive/2021/09/12/filterActiveEp.getValue().get(0).getSubset();
            if("v1".equals(subsetValue)){
                v1Times++;
            } else if("v2".equals(subsetValue)){
                v2Times++;
            } else if("v3".equals(subsetValue)){
                v3Times++;
            } else {
                errTimes++;
            }

        }
        //輸出結果
        System.out.println("一共回圈次數:" + times);
        System.out.println("路由到v1次數:" + v1Times);
        System.out.println("路由到v2次數:" + v2Times);
        System.out.println("路由到v3次數:" + v3Times);
        System.out.println("路由例外次數:" + errTimes);
    }


    /**
     * 測驗引數匹配 - 精確匹配
     * 沒有測驗registry獲取subsetConf功能
     * 注意要成功必須routeKey和match匹配上
     */
    @Test
    public void testMatch() {

        //1. 給過濾器設定過濾規則
        //1.1 創建SubsetManager管理器
        SubsetManager subsetManager = new SubsetManager();


        //1.1 設定引數路由規則,這里的KeyRoute的value為 “規則的染色key”
        KeyConfig keyConf = new KeyConfig();
        List<KeyRoute> krs = new LinkedList<>();
        krs.add(new KeyRoute("match","routeKey","v1"));
        keyConf.setRules(krs);

        //1.2 設定subsetConf,并加入快取
        SubsetConf subsetConf = new SubsetConf();
        subsetConf.setEnanle(true);
        subsetConf.setRuleType("key");
        subsetConf.setKeyConf(keyConf);
        subsetConf.setLastUpdate( Instant.now() );

        Map<String, SubsetConf> cache = new HashMap<>();
        cache.put(objectName,subsetConf);
        subsetManager.setCache(cache);

        //1.3 給過濾器設定過濾規則和管理者
        subsetFilter.setSubsetConf(subsetConf);
        subsetFilter.setSubsetManager(subsetManager);

        //1.4 模擬Tars “請求的染色key” TARS_ROUTE_KEY,但請求染色key和規則染色key匹配時,才能精確路由
        //1.4.1 創建Tars的請求體TarsServantRequest
        TarsServantRequest request = new TarsServantRequest( session );
        //1.4.2 往請求體的status添加{TARS_ROUTE_KEY, "routeKey"}鍵值對
        Map<String, String> status = new HashMap<>();
        status.put("TARS_ROUTE_KEY", "routeKey");
        request.setStatus(status);
        //1.4.3 構建分布式背景關系資訊,將請求放入分布式背景關系資訊中,因為getSubset()的邏輯是從分布式背景關系資訊中取
        DistributedContext distributedContext = new DistributedContextImpl();
        distributedContext.put(DyeingSwitch.REQ,request);

        //2. 模擬存活節點
        endpointFList.add(new EndpointF("host1",1,2,3,4,5,6,"setId1",7,8,9,10,"v1"));
        endpointFList.add(new EndpointF("host2",1,2,3,4,5,6,"setId2",7,8,9,10,"v1"));
        endpointFList.add(new EndpointF("host3",1,2,3,4,5,6,"setId3",7,8,9,10,"v2"));
        endpointFList.add(new EndpointF("host4",1,2,3,4,5,6,"setId4",7,8,9,10,"v2"));
        endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v2"));
        endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v3"));
        activeEp.setValue(endpointFList);


        //3. 輸出過濾前資訊
        System.out.println("過濾前節點資訊如下:");
        for( EndpointF endpoint : endpointFList){
            System.out.println(endpoint.toString());
        }

        //4. 對存活節點按subset規則過濾
        Holder<List<EndpointF>> filterActiveEp = subsetFilter.subsetEndpointFilter(objectName, routeKey, activeEp);

        //5. 輸出過濾結果

        System.out.println("過濾后節點資訊如下:");
        for( EndpointF endpoint : filterActiveEp.getValue() ){
            System.out.println(endpoint.toString());
        }
    }


    /**
     * 測驗引數匹配 - 正則匹配
     * 沒有測驗registry獲取subsetConf功能
     * 注意要成功必須routeKey和match匹配上
     */
    @Test
    public void testEqual() {

        //1. 給過濾器設定過濾規則
        //1.1 創建SubsetManager管理器

9.3 變更代碼的路徑

通過上述操作,改變了以下代碼,需要在github上提交:

  • TarsJava-1.7.x\core\src\test\java\com\qq\tars\client\subset\TestSubset.java

最后

新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公眾號,會分享一些更日常的東西!
如需轉載,請標注出處!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/299575.html

標籤:其他

上一篇:【Python從入門到精通】(二十五)Python多行程的使用

下一篇:磨刀不誤砍柴工,PyCharm開發工具的常規配置,充分提高開發效率!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more