ShardingSphere自定义分布式主键生成策略、自定义分片规则

news2024/12/22 19:32:17

文章目录

    • 主键生成策略源码
      • KeyGenerateAlgorithm
      • 源码入口
      • 实现
      • 扩展 自定义分布式主键生成策略
    • 分片算法
      • ShardingAlgorithm
      • 实现
      • 扩展 自定义分片算法
      • 踩的坑

主键生成策略源码

开发者手册

KeyGenerateAlgorithm

全限定类名org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm

分布式主键生成算法,已知实现

配置标识详细说明全限定类名
SNOWFLAKE基于雪花算法的分布式主键生成算法org.apache.shardingsphere.sharding.algorithm.keygen.SnowflakeKeyGenerateAlgorithm
UUID基于 UUID 的分布式主键生成算法org.apache.shardingsphere.sharding.algorithm.keygen.UUIDKeyGenerateAlgorithm
NANOID基于 NanoId 的分布式主键生成算法org.apache.shardingsphere.sharding.nanoid.algorithm.keygen.NanoIdKeyGenerateAlgorithm
COSID基于 CosId 的分布式主键生成算法org.apache.shardingsphere.sharding.cosid.algorithm.keygen.CosIdKeyGenerateAlgorithm
COSID_SNOWFLAKE基于 CosId 的雪花算法分布式主键生成算法org.apache.shardingsphere.sharding.cosid.algorithm.keygen.CosIdSnowflakeKeyGenerateAlgorithm



源码入口

package org.apache.shardingsphere.sharding.factory;

/**
 * Key generate algorithm factory.
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class KeyGenerateAlgorithmFactory {
    //加载所有的主键生成策略
    static {
        ShardingSphereServiceLoader.register(KeyGenerateAlgorithm.class);
    }
    
    /**
     * 根据配置的主键生成策略,获取一个主键生成算法
     * 例如:spring.shardingsphere.rules.sharding.key-generators.usercourse_keygen.type=SNOWFLAKE
     */
    public static KeyGenerateAlgorithm newInstance(final AlgorithmConfiguration keyGenerateAlgorithmConfig) {
        return ShardingSphereAlgorithmFactory.createAlgorithm(keyGenerateAlgorithmConfig, KeyGenerateAlgorithm.class);
    }
    
    /**
     * 判断是否包含配置的算法
     */
    public static boolean contains(final String keyGenerateAlgorithmType) {
        return TypedSPIRegistry.findRegisteredService(KeyGenerateAlgorithm.class, keyGenerateAlgorithmType).isPresent();
    }
}



先来看主键生成策略是如何加载的:ShardingSphereServiceLoader.register(KeyGenerateAlgorithm.class);

public final class ShardingSphereServiceLoader {
    //线程安全Map,缓存所有主键生成器
    private static final Map<Class<?>, Collection<Object>> SERVICES = new ConcurrentHashMap<>();
    
    // 进入到register()方法中
    public static void register(final Class<?> serviceInterface) {
        if (!SERVICES.containsKey(serviceInterface)) {
            // 调用下方的load()方法
            SERVICES.put(serviceInterface, load(serviceInterface));
        }
    }
    
    //使用java的SPI机制加载接口的所有实现类
    private static <T> Collection<Object> load(final Class<T> serviceInterface) {
        Collection<Object> result = new LinkedList<>();
        for (T each : ServiceLoader.load(serviceInterface)) {
            result.add(each);
        }
        return result;
    }
}



实现

ShardingJDBC是通过SPI机制,加载org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm接口的实现类,也就是上方表格中的内容

我们就可以直接在yml配置文件中进行配置分布式主键生成算法



接下来就以SNOWFLAKE雪花算法举例,下方就列举出了几个关键方法

// 实现了KeyGenerateAlgorithm接口
public final class SnowflakeKeyGenerateAlgorithm implements KeyGenerateAlgorithm, InstanceContextAware {
    
    // 在init方法中,会把我们yml配置文件中定义的props的配置项,保存在下面方法的形参中,并赋值给props成员属性
    // 其他地方再用props对象获取我们的配置项
    @Override
    public void init(final Properties props) {
        this.props = props;
        maxVibrationOffset = getMaxVibrationOffset(props);
        maxTolerateTimeDifferenceMilliseconds = getMaxTolerateTimeDifferenceMilliseconds(props);
    }
    
    // 实现KeyGenerateAlgorithm接口中的抽象方法generateKey()
    // 也就是在这个方法中具体生成分布式主键值的
    @Override
    public synchronized Long generateKey() {
        long currentMilliseconds = timeService.getCurrentMillis();
        if (waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) {
            currentMilliseconds = timeService.getCurrentMillis();
        }
        if (lastMilliseconds == currentMilliseconds) {
            if (0L == (sequence = (sequence + 1) & SEQUENCE_MASK)) {
                currentMilliseconds = waitUntilNextTime(currentMilliseconds);
            }
        } else {
            vibrateSequenceOffset();
            sequence = sequenceOffset;
        }
        lastMilliseconds = currentMilliseconds;
        return ((currentMilliseconds - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (getWorkerId() << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
    }
    
    // getType() 方法中返回的字符串就是我们上方yml配置文件中type配置项填写的值
    @Override
    public String getType() {
        return "SNOWFLAKE";
    }
}



其他几个实现类也是一样的格式

在这里插入图片描述



扩展 自定义分布式主键生成策略

package com.hs.sharding.algorithm;

import com.google.common.base.Preconditions;
import org.apache.shardingsphere.infra.instance.InstanceContext;
import org.apache.shardingsphere.infra.instance.InstanceContextAware;
import org.apache.shardingsphere.sharding.algorithm.keygen.TimeService;
import org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm;

import java.util.Calendar;
import java.util.Properties;

/**
 * 改进雪花算法,让他能够 %4 均匀分布。
 * @auth hs
 */
public final class MySnowFlakeAlgorithm implements KeyGenerateAlgorithm, InstanceContextAware {

    public static final long EPOCH;

    private static final String MAX_VIBRATION_OFFSET_KEY = "max-vibration-offset";

    private static final String MAX_TOLERATE_TIME_DIFFERENCE_MILLISECONDS_KEY = "max-tolerate-time-difference-milliseconds";

    private static final long SEQUENCE_BITS = 12L;

    private static final long WORKER_ID_BITS = 10L;

    private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

    private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;

    private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;

    private static final int DEFAULT_VIBRATION_VALUE = 1;

    private static final int MAX_TOLERATE_TIME_DIFFERENCE_MILLISECONDS = 10;

    private static final long DEFAULT_WORKER_ID = 0;

    private static TimeService timeService = new TimeService();

    public static void setTimeService(TimeService timeService) {
        MySnowFlakeAlgorithm.timeService = timeService;
    }

    private Properties props;

    @Override
    public Properties getProps() {
        return props;
    }

    private int maxVibrationOffset;

    private int maxTolerateTimeDifferenceMilliseconds;

    private volatile int sequenceOffset = -1;

    private volatile long sequence;

    private volatile long lastMilliseconds;

    private volatile InstanceContext instanceContext;

    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2016, Calendar.NOVEMBER, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        EPOCH = calendar.getTimeInMillis();
    }

    @Override
    public void init(final Properties props) {
        this.props = props;
        maxVibrationOffset = getMaxVibrationOffset(props);
        maxTolerateTimeDifferenceMilliseconds = getMaxTolerateTimeDifferenceMilliseconds(props);
    }

    @Override
    public void setInstanceContext(final InstanceContext instanceContext) {
        this.instanceContext = instanceContext;
        if (null != instanceContext) {
            instanceContext.generateWorkerId(props);
        }
    }

    private int getMaxVibrationOffset(final Properties props) {
        int result = Integer.parseInt(props.getOrDefault(MAX_VIBRATION_OFFSET_KEY, DEFAULT_VIBRATION_VALUE).toString());
        Preconditions.checkArgument(result >= 0 && result <= SEQUENCE_MASK, "Illegal max vibration offset.");
        return result;
    }

    private int getMaxTolerateTimeDifferenceMilliseconds(final Properties props) {
        return Integer.parseInt(props.getOrDefault(MAX_TOLERATE_TIME_DIFFERENCE_MILLISECONDS_KEY, MAX_TOLERATE_TIME_DIFFERENCE_MILLISECONDS).toString());
    }

    @Override
    public synchronized Long generateKey() {
        long currentMilliseconds = timeService.getCurrentMillis();
        if (waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) {
            currentMilliseconds = timeService.getCurrentMillis();
        }
        if (lastMilliseconds == currentMilliseconds) {
//            if (0L == (sequence = (sequence + 1) & SEQUENCE_MASK)) {
                currentMilliseconds = waitUntilNextTime(currentMilliseconds);
//            }
        } else {
            vibrateSequenceOffset();
//            sequence = sequenceOffset;
            sequence = sequence >= SEQUENCE_MASK ? 0:sequence+1;
        }
        lastMilliseconds = currentMilliseconds;
        return ((currentMilliseconds - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (getWorkerId() << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
    }

    private boolean waitTolerateTimeDifferenceIfNeed(final long currentMilliseconds) {
        if (lastMilliseconds <= currentMilliseconds) {
            return false;
        }
        long timeDifferenceMilliseconds = lastMilliseconds - currentMilliseconds;
        Preconditions.checkState(timeDifferenceMilliseconds < maxTolerateTimeDifferenceMilliseconds,
                "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastMilliseconds, currentMilliseconds);
        try {
            Thread.sleep(timeDifferenceMilliseconds);
        } catch (InterruptedException e) {
        }
        return true;
    }

    private long waitUntilNextTime(final long lastTime) {
        long result = timeService.getCurrentMillis();
        while (result <= lastTime) {
            result = timeService.getCurrentMillis();
        }
        return result;
    }

    @SuppressWarnings("NonAtomicOperationOnVolatileField")
    private void vibrateSequenceOffset() {
        sequenceOffset = sequenceOffset >= maxVibrationOffset ? 0 : sequenceOffset + 1;
    }

    private long getWorkerId() {
        return null == instanceContext ? DEFAULT_WORKER_ID : instanceContext.getWorkerId();
    }

    @Override
    public String getType() {
        return "MYSNOWFLAKE";
    }

    @Override
    public boolean isDefault() {
        return true;
    }
}



使用spi机制加载我们上方定义的类

在这里插入图片描述



yml配置文件中使用我们自己定义的类

在这里插入图片描述



分片算法

开发者手册

ShardingAlgorithm

全限定类名org.apache.shardingsphere.sharding.spi.ShardingAlgorithm

分片算法,已知实现

配置标识自动分片算法详细说明类名
MODY基于取模的分片算法ModShardingAlgorithm
HASH_MODY基于哈希取模的分片算法HashModShardingAlgorithm
BOUNDARY_RANGEY基于分片边界的范围分片算法BoundaryBasedRangeShardingAlgorithm
VOLUME_RANGEY基于分片容量的范围分片算法VolumeBasedRangeShardingAlgorithm
AUTO_INTERVALY基于可变时间范围的分片算法AutoIntervalShardingAlgorithm
INTERVALN基于固定时间范围的分片算法IntervalShardingAlgorithm
CLASS_BASEDN基于自定义类的分片算法ClassBasedShardingAlgorithm
INLINEN基于行表达式的分片算法InlineShardingAlgorithm
COMPLEX_INLINEN基于行表达式的复合分片算法ComplexInlineShardingAlgorithm
HINT_INLINEN基于行表达式的 Hint 分片算法HintInlineShardingAlgorithm
COSID_MODN基于 CosId 的取模分片算法CosIdModShardingAlgorithm
COSID_INTERVALN基于 CosId 的固定时间范围的分片算法CosIdIntervalShardingAlgorithm
COSID_INTERVAL_SNOWFLAKEN基于 CosId 的雪花ID固定时间范围的分片算法CosIdSnowflakeIntervalShardingAlgorithm



实现

这里就拿CLASS_BASED自定义分片策略来举例。我们之前的配置项如下所示。

这里就有一个问题,props的值我怎么知道写什么,我又怎么知道我自定义的类需要实现什么接口?

在这里插入图片描述



我们现在进入到CLASS_BASED分片算法的实现类中ClassBasedShardingAlgorithm去看看它的源码

public final class ClassBasedShardingAlgorithm implements StandardShardingAlgorithm<Comparable<?>>, ComplexKeysShardingAlgorithm<Comparable<?>>, HintShardingAlgorithm<Comparable<?>> {
    
    // 定义两个常量,我们会发现这里就是props中我们进行配置的值
    private static final String STRATEGY_KEY = "strategy";
    
    private static final String ALGORITHM_CLASS_NAME_KEY = "algorithmClassName";
    
    @Getter
    private Properties props;
    
    private ClassBasedShardingAlgorithmStrategyType strategy;
    
    private String algorithmClassName;
    
    private StandardShardingAlgorithm standardShardingAlgorithm;
    
    private ComplexKeysShardingAlgorithm complexKeysShardingAlgorithm;
    
    private HintShardingAlgorithm hintShardingAlgorithm;
    
    // init()方法中会获取到props对象,props对象中保存了我们yml配置文件中的配置内容
    // 这里就会取出来,赋值给 strategy  和  algorithmClassName 成员属性
    @Override
    public void init(final Properties props) {
        this.props = props;
        strategy = getStrategy(props);
        algorithmClassName = getAlgorithmClassName(props);
        initAlgorithmInstance(props);
    }
    
    private ClassBasedShardingAlgorithmStrategyType getStrategy(final Properties props) {
        String strategy = props.getProperty(STRATEGY_KEY);
        Preconditions.checkNotNull(strategy, "Properties `%s` can not be null when uses class based sharding strategy.", STRATEGY_KEY);
        return ClassBasedShardingAlgorithmStrategyType.valueOf(strategy.toUpperCase().trim());
    }
    
    private String getAlgorithmClassName(final Properties props) {
        String result = props.getProperty(ALGORITHM_CLASS_NAME_KEY);
        Preconditions.checkNotNull(result, "Properties `%s` can not be null when uses class based sharding strategy.", ALGORITHM_CLASS_NAME_KEY);
        return result;
    }
    
    // 这里就会判断 strategy 属性是哪一个  STANDARD、COMPLEX、HINT
    // 然后在进行具体的实例 StandardShardingAlgorithm、ComplexKeysShardingAlgorithm、HintShardingAlgorithm
    private void initAlgorithmInstance(final Properties props) {
        switch (strategy) {
            case STANDARD:
                standardShardingAlgorithm = ClassBasedShardingAlgorithmFactory.newInstance(algorithmClassName, StandardShardingAlgorithm.class, props);
                break;
            case COMPLEX:
                complexKeysShardingAlgorithm = ClassBasedShardingAlgorithmFactory.newInstance(algorithmClassName, ComplexKeysShardingAlgorithm.class, props);
                break;
            case HINT:
                hintShardingAlgorithm = ClassBasedShardingAlgorithmFactory.newInstance(algorithmClassName, HintShardingAlgorithm.class, props);
                break;
            default:
                break;
        }
    }
    
    
    // doSharding()方法,具体的分片算法逻辑
    @SuppressWarnings("unchecked")
    @Override
    public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<Comparable<?>> shardingValue) {
        return standardShardingAlgorithm.doSharding(availableTargetNames, shardingValue);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public Collection<String> doSharding(final Collection<String> availableTargetNames, final RangeShardingValue<Comparable<?>> shardingValue) {
        return standardShardingAlgorithm.doSharding(availableTargetNames, shardingValue);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public Collection<String> doSharding(final Collection<String> availableTargetNames, final ComplexKeysShardingValue<Comparable<?>> shardingValue) {
        return complexKeysShardingAlgorithm.doSharding(availableTargetNames, shardingValue);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public Collection<String> doSharding(final Collection<String> availableTargetNames, final HintShardingValue<Comparable<?>> shardingValue) {
        return hintShardingAlgorithm.doSharding(availableTargetNames, shardingValue);
    }
    
    // 返回trye为 CLASS_BASED  这里也就是和yml配置文件中的type对应上了
    @Override
    public String getType() {
        return "CLASS_BASED";
    }
}



其他的分片算法也是类似的实现

在这里插入图片描述



扩展 自定义分片算法

自定义一个java类,实现ShardingAlgorithm接口,或者是它的子接口 StandardShardingAlgorithm、ComplexKeysShardingAlgorithm、HintShardingAlgorithm都行,重写其中的doSharding()方法,我们自己指定分片逻辑



重写getType()方法,返回一个字符串,能够让我们在yml配置文件中进行配置

@Override
public String getType() {
    return "MY_COMPLEX_ALGORITHM";
}



例如我现在自定义的分片类如下

package com.hs.sharding.algorithm;

import com.google.common.base.Preconditions;
import com.google.common.collect.Range;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;


import java.util.*;

/**
 * 自定义分片策略 , 我们这里实现标准的分片算法接口StandardShardingAlgorithm
 * 我这里是分片逻辑就是按照个数取模,在分发到sys_user1   sys_user2数据表中
 */
public class HsComplexAlgorithm implements StandardShardingAlgorithm<Long> {

    /**
     * 数据库个数
     */
    private final String DB_COUNT = "db-count";
    /**
     * 数据表个数
     */
    private final String TAB_COUNT = "tab-count";
    /**
     * 真实数据表前缀
     */
    private final String PERTAB = "pertab";


    private Integer dbCount;
    private Integer tabCount;
    private String pertab;

    private Properties props;

    @Override
    public void init(Properties props) {
        this.props = props;
        this.dbCount = getDbCount(props);
        this.tabCount = getTabCount(props);
        this.pertab = getPertab(props);
        // 校验条件
        Preconditions.checkState(null != pertab && !pertab.isEmpty(),
                "Inline hsComplex algorithm expression cannot be null or empty.");
    }

    /**
     * 精确查询分片执行接口(对应的sql是where ??=值)
     * @param collection 可用的分片名集合(分库就是库名,分表就是表名)
     * @param preciseShardingValue 分片键
     */
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {

        Long uid = preciseShardingValue.getValue();
        String resultTableName = pertab + ((uid + 1) % (dbCount * tabCount) / tabCount + 1);
        if (collection.contains(resultTableName)){
            return resultTableName;
        }
        throw new UnsupportedOperationException("route: " + resultTableName + " is not supported, please check your config");
    }

    /**
     * 范围分片规则(对应的是where ??>='XXX' and ??<='XXX')
     * 范围查询分片算法(分片键涉及区间查询时会进入该方法进行分片计算)
     */
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
        List<String> result = new ArrayList<>();
        Range<Long> valueRange = rangeShardingValue.getValueRange();
        Long upperEndpoint = valueRange.upperEndpoint();
        Long aLong = valueRange.lowerEndpoint();

        // TODO 进行相应的分片判断
//        return result;

        return collection;
    }

    private String getPertab(Properties props) {
        return props.getProperty(PERTAB);
    }

    private Integer getDbCount(Properties props) {
        String count = props.getProperty(DB_COUNT);
        return count == null || count.isEmpty() ? 0 : Integer.valueOf(count);
    }

    private Integer getTabCount(Properties props) {
        String count = props.getProperty(TAB_COUNT);
        return count == null || count.isEmpty() ? 0 : Integer.valueOf(count);
    }


    @Override
    public Properties getProps() {
        return props;
    }

    @Override
    public String getType() {
        return "HS";
    }
}



需要添加一个SPI的配置文件org.apache.shardingsphere.sharding.spi.ShardingAlgorithm,在该文件中指定我们上方创建的java类

在这里插入图片描述



yml配置文件中进行相应的更改

在这里插入图片描述



踩的坑

我先是自定义的类实现的是ComplexKeysShardingAlgorithm接口,但是我们yml配置类中还是一直按照standard的配置,导致我自定义的类中的doSharding()方法所以就一直没有调用到

在这里插入图片描述



之后我修改了complex就能调用了

在这里插入图片描述



在配置还是standard时,我通过debug,发现init()getType()方法都能够调用,证明SPI机制相关的文件没问题。

我就想会不会是单分片键、精确查询、范围查询相关问题导致的?

我修改了实现接口,改为了StandardShardingAlgorithm,然后就进入了其中单分片键的doSharding()方法。最后就一点一点的排查,再到了这上面的配置

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

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

相关文章

【HBZ分享】bean的生命周期 以及 各个阶段在spring的哪个类被调用

Ioc容器通过配置文件读取bean的定义信息&#xff0c;并保存在BeanDefinition中执行BeanFactoryPostProcessor的postProcessBeanFactory方法&#xff0c;对bean定义的信息进行处理&#xff0c;这里还是可以设置bean的基本信息&#xff0c;比如&#xff1a;单例多例&#xff0c;初…

如何将 GTA Online 的加载时间缩短 70%

注&#xff1a;机翻。未校。 How I cut GTA Online loading times by 70% t0st 2021-02-28 GTA Online. Infamous for its slow loading times. Having picked up the game again to finish some of the newer heists I was shocked (/s) to discover that it still loads j…

如何使用浏览器发post请求

如何使用浏览器发送post请求 第一种&#xff1a;无请求体第二种&#xff1a;要设置请求体的post请求 通过浏览器发送post请求有两种简单的方式&#xff0c;只需要根据实际情况在console执行以下代码即可。 第一种&#xff1a;无请求体 没有请求体&#xff0c;可以直接使用以下…

Vue3与Vue2的主要区别

本篇文章适用于熟练掌握Vue2的小伙伴们&#xff0c;不想重新学习Vue3&#xff0c;那看本篇文章就够啦&#xff01;希望大家收获多多&#xff01;&#xff01; Vue3是向下兼容的&#xff0c;可以运行Vue2代码 一、页面区别 Vue2定义属性方法 <template><div ><…

【网络爬虫篇】“逆向实战—某东:滑块验证码(逆向登录)”自动化实现滑块登录验证(2024.8.7)最新发布,包干货,包详细

【网络爬虫篇】更多优秀文章借鉴&#xff1a; 1. 使用Selenium实现黑马头条滑块自动登录 2. 使用多线程采集爬取豆瓣top250电影榜 3. 使用Scrapy爬取去哪儿网游记数据 4. 数据采集技术综合项目实战1&#xff1a;国家水稻网数据采集与分析 5. 数据采集技术综合项目实战2&#x…

【leetcode】根据二叉树创建字符串、二叉树的前中后遍历(非递归链表实现二叉树)

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构、LeetCode专栏 &#x1f4da;本系…

扫地机/洗地机语音芯片ic,工业级声音播放芯片ic,NV170H

扫地机/洗地机作为智能家居清洁领域的创新驱动力&#xff0c;不仅赋予了清洁设备&#xff0c;还需要一些智能化的功能&#xff0c;比如语音提示&#xff0c;将用户体验提升至全新高度。NV170H语音芯片成为了首要选择。 NV170H语音芯片是一款OTP&#xff08;‌一次性可编程&…

html+css网页设计 酷狗首页1个页面 (无js)

htmlcss网页设计 酷狗首页1个页面无js功能 页面还原度80% 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 …

干货满满!Stable Diffusion 从入门到精通之提示词手册,免费分享,自学转行,零基础首选!

前言 Stable Diffusion 技术把 AI 图像生成提高到了一个全新高度&#xff0c;文生图 Text to image 生成质量很大程度上取决于你的提示词 Prompt 好不好。本文从“如何写好提示词”出发&#xff0c;从提示词构成、调整规则和 AIGC辅助工具等角度&#xff0c;对文生图的提示词输…

腾讯云AI代码助手评测:如何智能高效完成Go语言Web项目开发

腾讯云AI代码助手评测&#xff1a;如何智能高效完成Go语言Web项目开发 &#x1f680; 文章目录 腾讯云AI代码助手评测&#xff1a;如何智能高效完成Go语言Web项目开发 &#x1f680;背景引言开发环境介绍腾讯云AI代码助手使用实例1. 代码补全2. 技术对话3. 代码优化4. 规范代码…

LVS实验(实现服务器集群的负载均衡)

基本概念 LVS&#xff08;Linux Virtual Server&#xff09;是Linux虚拟服务器的简称。 LVS通过将一个真实服务器集群虚拟成一台服务器来对外提供服务&#xff0c;同时在集群内部实现负载均衡。这种技术能够显著提高服务的处理能力和可靠性&#xff0c;降低单台服务器的负载压…

C++——类和对象(part1)

前言 本篇博客来为大家介绍C中的一个重要内容——类与对象&#xff0c;这部分的内容较多&#xff0c;将会分三篇文章来介绍&#xff0c;本篇为第一篇&#xff0c;如果你学习C或对C感兴趣&#xff0c;那么请继续往下阅读&#xff0c;下面进入正文部分。 1. 类的定义 1.1 类定…

【Material-UI】Button Group:实验性 API 详解

文章目录 一、按钮组概述1. 组件介绍2. 基本用法 二、实验性 API 详解1. LoadingButton 组件1.1 基本用法1.2 位置属性 三、实验性 API 的应用场景1. 数据加载按钮2. 提交表单按钮3. 保存操作按钮 四、按钮组的样式定制1. 变体&#xff08;Variants&#xff09;2. 颜色&#xf…

解决Ubuntu/Kali手动创建的启动器在dock上没有图标,且不能“添加到dock中“的问题

文章目录 问题描述问题解决解决方案 1 | 添加StartupWMClass字段解决方案 2 | 重命名文件名 如何获取 WM 值&#xff1f;方式 1 | xprop 命令方式 2 | 直接查看 问题描述 这个启动器无论是在菜单还是桌面都是正常的&#xff0c;只有在dock中没有图标&#xff0c;且不像其他APP…

《向量数据库指南》——非结构化数据的行业需求及向量数据库的关键角色

非结构化数据的行业需求及向量数据库的关键角色 引言 在当今数字化时代,数据已成为驱动社会进步与产业升级的核心要素。随着技术的飞速发展,特别是人工智能(AI)技术的广泛应用,数据的类型与规模正以前所未有的速度增长。其中,非结构化数据作为数据海洋中的主体部分,其…

同态加密和SEAL库的介绍(六)BGV 方案

前面介绍 BFV 和 CKKS 加密方案&#xff0c;这两者更为常用。并且也解释了 Batch Encoder 和 级别的概念&#xff0c;这对接下来演示 BGV 会很有帮助。 一、BGV简介 BGV (Brakerski-Gentry-Vaikuntanathan) 方案 是一种基于环学习同态加密&#xff08;RLWE&#xff09;问题的加…

霍尼韦尔落地灯怎么样?书客、霍尼、柏曼护眼大路灯多维度实测

霍尼韦尔落地灯怎么样&#xff1f;护眼大路灯作为最适合新时代学生照明工具&#xff0c;以良好的作用表现得到了许多家长及社会人士的认同&#xff0c;但同时也因为火爆&#xff0c;市面上的品牌繁杂&#xff0c;出现了许多劣质或者不专业的产品&#xff0c;促使一些人不知道如…

学习java的日子 Day64 学生管理系统 web2.0 web版本

MVC设计模式 概念 - 代码的分层 MVC&#xff1a;项目分层的思想 字母表示层理解MModle模型层业务的具体实现VView视图层展示数据CController控制器层控制业务流程&#xff08;跳转&#xff09; 1.细化理解层数 Controller&#xff1a;控制器层&#xff0c;用于存放Servlet&…

中职云计算实训室

一、实训室建设背景 随着信息技术的飞速发展&#xff0c;云计算已成为推动数字化转型、促进经济社会发展的重要力量。《中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要》明确提出&#xff0c;要加快数字化发展&#xff0c;建设数字中国。云计算作为数…

我的创新大赛经验分享:打造一份出色的商业计划书

我的创新大赛经验分享&#xff1a;打造一份出色的商业计划书 前言封面和目录&#xff1a;第一印象至关重要执行摘要&#xff1a;一语中的项目背景&#xff1a;市场与行业的深度剖析产品/服务&#xff1a;展现独特性和竞争力市场分析&#xff1a;深入洞察目标市场商业模式&#…