首页 文章 精选 留言 我的

精选列表

搜索[鸿蒙实践],共10000篇文章
优秀的个人博客,低调大师

LLM与办公IM实践

办公 IM 是 LLM 落地最被低估的一环——它同时提供了天然的组织上下文(谁、和谁聊天、在哪个群、在讨论什么)和唯一的执行出口(发消息、建日程、写文档、发起会议)。这两点决定了 AI 办公 Agent 的成败。先看整体架构怎么搭:落到具体场景上,办公IM 的 AI 价值可以按"读上下文"和"写动作"两个维度分成四类,落地优先级差异很大:再补一层平台侧的差异,这是选型时最容易踩坑的地方:

优秀的个人博客,低调大师

Spring Boot 启动优化实践

作者:vivo 互联网服务器团队- Liu Di 本文系统性分析并优化了一个Spring Boot项目启动耗时高达 280 秒的问题。通过识别瓶颈、优化分库分表加载逻辑、异步初始化耗时任务等手段,最终将启动耗时缩短至 159 秒,提升近 50%。文章涵盖启动流程分析、性能热点识别、异步初始化设计等关键技术细节,适用于大型Spring Boot项目的性能优化参考。 文章太长?1分钟看图抓住核心观点👇 一、前言 随着业务的发展,笔者项目对应的Spring Boot工程的依赖越来越多。随着依赖数量的增长,Spring 容器需要加载更多组件、解析复杂依赖并执行自动装配,导致项目启动时间显著增长。在日常开发或测试过程中,一旦因为配置变更或者其他热部署不生效的变更时,项目重启就需要等待很长的时间影响代码的交付。加快Spring项目的启动可以更好的投入项目中,提升开发效率。 整体环境介绍: Spring版本:4.3.22 Spring Boot版本:1.5.19 CPU:i5-9500 内存:24GB 优化前启动耗时:280秒 二、Spring Boot项目启动流程介绍 Spring Boot项目主要启动流程都在org.spring- framework.boot.SpringApplication#run(java.lang.String...)方法中: public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); // Spring上下文 ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); // 初始化SpringApplicationRunListener监听器 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // 环境准备 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 打印banner Banner printedBanner = printBanner(environment); // 创建上下文 context = createApplicationContext(); analyzers = new FailureAnalyzers(context); // 容器初始化 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 刷新容器内容 refreshContext(context); afterRefresh(context, applicationArguments); // 结束监听广播 listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } } 可以看到在启动流程中,监听器应用在了应用的多个生命周期中。并且Spring Boot中也预留了针对listener的扩展点。我们可以借此实现一个自己的扩展点去监听Spring Boot的每个阶段的启动耗时,实现如下: @Slf4j public class MySpringApplicationRunListener implements SpringApplicationRunListener{ private Long startTime; public MySpringApplicationRunListener(SpringApplication application, String[] args){ } @Override public void starting(){ startTime = System.currentTimeMillis(); log.info("MySpringListener启动开始 {}", LocalTime.now()); } @Override public void environmentPrepared(ConfigurableEnvironment environment){ log.info("MySpringListener环境准备 准备耗时:{}毫秒", (System.currentTimeMillis() - startTime)); startTime = System.currentTimeMillis(); } @Override public void contextPrepared(ConfigurableApplicationContext context){ log.info("MySpringListener上下文准备 耗时:{}毫秒", (System.currentTimeMillis() - startTime)); startTime = System.currentTimeMillis(); } @Override public void contextLoaded(ConfigurableApplicationContext context){ log.info("MySpringListener上下文载入 耗时:{}毫秒", (System.currentTimeMillis() - startTime)); startTime = System.currentTimeMillis(); } @Override public void finished(ConfigurableApplicationContext context, Throwable exception){ log.info("MySpringListener结束 耗时:{}毫秒", (System.currentTimeMillis() - startTime)); startTime = System.currentTimeMillis(); } } 接着还需要在classpath/META-INF目录下新建spring.factories文件,并添加如下文件内容: org.springframework.boot.SpringApplicationRunListener=com.vivo.internet.gameactivity.api.web.MySpringApplicationRunListener 至此,借助Listener机制,我们能够追踪Spring Boot启动各阶段的耗时分布,为后续性能优化提供数据支撑。 contextLoaded事件是在run方法中的prepareContext()结束时调用的,因此contextLoaded事件和finished事件之间仅存在两个语句:refreshContext(context)和afterRefresh (context,applicationArguements)消耗了285秒的时间,调试一下就能发现主要耗时在refreshContext()中。 三、AbstractApplicationContext#refresh refreshContext()最终调用到org.spring-framework.context.support.AbstractApplicationContext#refresh方法中,这个方法主要是beanFactory的预准备、对beanFactory完成创建并进行后置处理、向容器添加bean并且给bean添加属性、实例化所有bean。通过调试发现,finishBeanFactoryInitialization(beanFactory) 方法耗时最久。该方法负责实例化容器中所有的单例 Bean,是启动性能的关键影响点。 四、找出实例化耗时的Bean Spring Boot也是利用的Spring的加载流程。在Spring中可以实现InstantiationAwareBeanPost- Processor接口去在Bean的实例化和初始化的过程中加入扩展点。因此我们可以实现该接口并添加自己的扩展点找到处理耗时的Bean。 @Service public class TimeCostCalBeanPostProcessor implements InstantiationAwareBeanPostProcessor { private Map<String, Long> costMap = Maps.newConcurrentMap(); @Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { if (!costMap.containsKey(beanName)) { costMap.put(beanName, System.currentTimeMillis()); } return null; } @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { return true; } @Override public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { return pvs; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (costMap.containsKey(beanName)) { Long start = costMap.get(beanName); long cost = System.currentTimeMillis() - start; // 只打印耗时长的bean if (cost > 5000) { System.out.println("bean: " + beanName + "\ttime: " + cost + "ms"); } } return bean; } } 具体原理就是在Bean开始实例化之前记录时间,在Bean初始化完成后记录结束时间,打印实例化到初始化的时间差获得Bean的加载总体耗时。结果如图: 可以看到有许多耗时在10秒以上的类,接下来可以针对性的做优化。值得注意的是,统计方式为单点耗时计算,未考虑依赖链上下文对整体加载顺序的影响,实际优化还需结合依赖关系分析。 五、singletonDataSource @Bean(name = "singletonDataSource") public DataSource singletonDataSource(DefaultDataSourceWrapper dataSourceWrapper) throws SQLException { //先初始化连接 dataSourceWrapper.getMaster().init(); //构建分库分表数据源 String dataSource0 = "ds0"; Map<String, DataSource> dataSourceMap = new HashMap<>(); dataSourceMap.put(dataSource0, dataSourceWrapper.getMaster()); //分库分表数据源 DataSource shardingDataSource = ShardingDataSourceFactory.createDataSource (dataSourceMap,shardingRuleConfiguration, prop); return shardingDataSource; } singletonDataSource是一个分库分表的数据源,连接池采用的是Druid,分库分表组件采用的是公司内部优化后的中间件。通过简单调试代码发现,整个Bean耗时的过程发生在createDataSource方法,该方法中会调用createMetaData方法去获取数据表的元数据,最终运行到loadDefaultTables方法。该方法如下图,会遍历数据库中所有的表。因此数据库中表越多,整体就越耗时。 笔者的测试环境数据库中有很多的分表,这些分表为了和线上保持一致,分表的数量都和线上是一样的。 因此在测试环境启动时,为了加载这些分表会更加的耗时。可通过将分表数量配置化,使测试环境在不影响功能验证的前提下减少分表数量,从而加快启动速度。 六、初始化异步 activityServiceImpl启动中,主要会进行活动信息的查询初始化,这是一个耗时的操作。类似同样的操作在工程的其他类中也存在。 @Service public class ActivityServiceImpl implements ActivityService, InitializingBean{ // 省略无关代码 @Override public void afterPropertiesSet() throws Exception { initActivity(); } // 省略无关代码 } 可以通过将afterPropertiesSet()异步化的方式加速项目的启动。 观察Spring源码可以注意到afterPropertiesSet方法是在AbstractAutowireCapableBeanFactory# invokeInitMethods中调用的。在这个方法中,不光处理了afterPropertiesSet方法,也处理了init-method。 因此我们可以写一个自己的BeanFactory继承AbstractAutowireCapableBeanFactory,将invokeInitMethods方法进行异步化重写。考虑到AbstractAutowireCapableBeanFactory是个抽象类,有额外的抽象方法需要实现,因此继承该抽象类的子类DefaultListableBeanFactory。具体实现代码如下: public class AsyncInitListableBeanFactory extends DefaultListableBeanFactory{ public AsyncInitBeanFactory(ConfigurableListableBeanFactory beanFactory){ super(beanFactory); } @Override protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd)throws Throwable { if (beanName.equals("activityServiceImpl")) { AsyncTaskExecutor.submitTask(() -> { try { super.invokeInitMethods(beanName, bean, mbd); } catch (Throwable throwable) { throwable.printStackTrace(); } }); } else { super.invokeInitMethods(beanName, bean, mbd); } } } 又因为Spring在refreshContext()方法之前的prepareContext()发放中针对initialize方法提供了接口扩展(applyInitializers())。因此我们可以通过实现该接口并将我们的新的BeanFactory通过反射的方式更新到Spring的初始化流程之前。 public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> { /** * Initialize the given application context. * @param applicationContext the application to configure */ void initialize(C applicationContext); } 改造后的代码如下,新增AsyncAccelerate- Initializer类实现ApplicationContextInitializer接口: public class AsyncBeanFactoryInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @SneakyThrows @Override public void initialize(ConfigurableApplicationContext applicationContext){ if (applicationContext instanceof GenericApplicationContext) { AsyncInitListableBeanFactory beanFactory = new AsyncInitListableBeanFactory(applicationContext.getBeanFactory()); Field field = GenericApplicationContext.class.getDeclaredField("beanFactory"); field.setAccessible(true); field.set(applicationContext, beanFactory); } } } public class AsyncBeanInitExecutor{ private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final AtomicReference<ThreadPoolExecutor> THREAD_POOL_REF = new AtomicReference<>(); private static final List<Future<?>> FUTURES = new ArrayList<>(); /** * 创建线程池实例 */ private static ThreadPoolExecutor createThreadPoolExecutor(){ int poolSize = CPU_COUNT + 1; return new ThreadPoolExecutor(poolSize, poolSize, 50L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy() ); } /** * 确保线程池已初始化(线程安全) */ private static void ensureThreadPoolExists(){ if (THREAD_POOL_REF.get() != null) { return; } ThreadPoolExecutor executor = createThreadPoolExecutor(); if (!THREAD_POOL_REF.compareAndSet(null, executor)) { executor.shutdown(); // 另一线程已初始化成功 } } /** * 提交异步初始化任务 * * @param task 初始化任务 * @return 提交后的 Future 对象 */ public static Future<?> submitInitTask(Runnable task) { ensureThreadPoolExists(); Future<?> future = THREAD_POOL_REF.get().submit(task); FUTURES.add(future); return future; } /** * 等待所有初始化任务完成并释放资源 */ public static void waitForInitTasks(){ try { for (Future<?> future : FUTURES) { future.get(); } } catch (Exception ex) { throw new RuntimeException("Async init task failed", ex); } finally { FUTURES.clear(); shutdownThreadPool(); } } /** * 关闭线程池并重置引用 */ private static void shutdownThreadPool(){ ThreadPoolExecutor executor = THREAD_POOL_REF.getAndSet(null); if (executor != null) { executor.shutdown(); } } } 实现类后,还需要在META-INF/spring.factories下新增说明org.springframework.context. ApplicationContextInitializer=com.xxx.AsyncAccelerateInitializer,这样这个类才能真正生效。 这样异步化以后还有一个点需要注意,如果该初始化方法执行耗时很长,那么会存在Spring容器已经启动完成,但是异步初始化任务没执行完的情况,可能会导致空指针等异常。为了避免这种问题的发生,还要借助于Spring容器启动中finishRefresh()方法,监听对应事件,确保异步任务执行完成之后,再启动容器。 public class AsyncInitCompletionListener implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware, PriorityOrdered{ private ApplicationContext currentContext; @Override public void setApplicationContext(ApplicationContext applicationContext)throws BeansException { this.currentContext = applicationContext; } @Override public void onApplicationEvent(ContextRefreshedEvent event){ if (event.getApplicationContext() == currentContext) { AsyncBeanInitExecutor.waitForInitTasks(); } } @Override public int getOrder(){ return Ordered.HIGHEST_PRECEDENCE; } } 七、总结 启动优化后的项目实际测试结果如下: 通过异步化初始化和分库分表加载优化,项目启动时间从 280 秒缩短至 159 秒,提升约 50%。这对于提升日常开发效率、加快测试与联调流程具有重要意义。

优秀的个人博客,低调大师

PikiwiDB(Pika) 3.5 最佳实践

PikiwiDB(Pika) 是 360 技术中台中间件团队基于 RocksDB 开发的大容量类 Redis 存储系统,力求在完全兼容 Redis 协议、继承 Redis 便捷运维设计的前提下通过持久化存储方式解决 Redis 在大容量场景下主从同步代价高、恢复时间慢、单线程相对脆弱、内存成本高等问题。 我们根据 360 内部的 PikiwiDB(Pika) 使用经验及社区用户的问题反馈,整理了本文并在这里分享给大家。 之一 在微信群(群管理员微信号:PikiwiDB)中提问时,请主动带上版本号,可大幅度加快问题解决速度。 之二 PikiwiDB(Pika) 已在 2024 年 5 月更新至 3.5.4,但仍然有大量用户停留在 3.3.6 或3.3.2,我们建议使用 3.5.4 的最新版(预计本周内发布 v4.0.0),你会发现你遇到的很多问题都在我们的 bug 修复列表中。 之三 PikiwiDB(Pika) 的线程数量 thread-num 建议设置为 CPU core 数目的 80% 左右,如果是单机多实例的部署,每个 PikiwiDB(Pika) 实例的线程数量可以酌情降低,但不建议低于 CPU core 数的 1/2。 之四 PikiwiDB(Pika) 的工作线程池数量 thread-pool-size 建议和 CPU core 数目一致,如果是单机多实例的部署,每个 PikiwiDB(Pika) 实例的线程数量可以酌情降低,但不建议低于 1/2 CPU core 数。 之五 PikiwiDB(Pika) 的性能和 IO 性能息息相关,如果对耗时非常敏感,建议使用 NVMe SSD。另外,主从服务器的硬件规格应当尽量一致。 之六 在使用 PikiwiDB(Pika) 复合数据结构(hash,list,zset,zset)时,尽量确保每个 key 中的二级 key(或者成为 field)不要太多(不要超过 1 万个),在业务层或者代理层对大 key 符合数据结构进行拆分(类似于分库分表), 这样可以避免超大 key 带来很多潜在的性能风险。 之七 root-connection-num 参数非常有用,意为“允许通过 127.0.0.1 登录 PikiwiDB(Pika) 的连接数”,它不会被算进客户端最大连接数配置项 maxclients,因此在发生异常 maxclients 被用尽的场景中,管理员仍然可以登录 PikiwiDB(Pika) 所在服务器并通过 127.0.0.1 登入 PikiwiDB(Pika) 处理问题,可以认为是超级管理员通道。 之八 client kill 命令被加强了,如果你想一次性杀掉当前 PikiwiDB(Pika) 的所有客户端连接,只需要执行 client kill all 命令即可。注意,主从同步的网络连接不受影响。 之九 适当地调整 timeout 参数,PikiwiDB(Pika) 会主动断开不活跃时间超过 timeout 值的连接,避免连接数耗尽。由于网络连接会占用主机内存,因此合理的配置 timeout 参数也能够在一定程度上降低 PikiwiDB(Pika) 的内存使用量。 之十 PikiwiDB(Pika) 的内存占用主要集中在 SST 文件的 cache 和网络连接内存占用量,通常网络连接内存量会比 SST 的 cache 大,PikiwiDB(Pika) 目前已支持连接申请内存的动态调整与回收,因此连接占用的总内存大小是可以粗略估算的,如果你的 PikiwiDB(Pika) 内存占用远超预估(如大于 10GiB),那么可能为你当前使用的版本存在内存泄漏问题,尝试依次执行命令 client kill all 对连接内存进行强制回收,或者升级到最新版本。 之十一 非常不建议单副本运行 PikiwiDB(Pika),单副本的数据安全性无法保障,诸如 RocksDB Bug 或者资源不够(如:ERR IO error: While fdatasync: /data1/db/zsets/16566747.log: Cannot allocate memory)导致 RocksDB 存储数据被污染,此时无法全量恢复数据。 最简集群状态应为一主一从。 之十二 如果 PikiwiDB(Pika) 单副本运行(非主从集群),只在乎性能,且不在乎数据安全性(如缓存场景),可以考虑通过关闭 binlog(将 write-binlog 参数设置为 no)来提高写入性能。 之十三 PikiwiDB(Pika) v3.5.2 以及之后的版本提供了关闭 RocksDB WAL (DisableWAL true) 的命令,如果你的 PikiwiDB(Pika) 实例出现间断性的写性能阻塞的情况,你可以通过关闭 WAL 命令暂时关闭 WAL,这种方式有断电情况下数据丢失的风险,待性能恢复时,请及时再打开。对数据完整性要求不高时,建议关闭 WAL。 之十四 PikiwiDB(Pika) 的数据目录中有大量的 SST 文件,这些文件随着 PikiwiDB(Pika) 数据量的增加而增加,建议为 PikiwiDB(Pika) 配置一个较大的 open_file_limit ,以避免 fd 不够用,如果不希望 Pika 占用太多的文件描述符,可以通过适当增大单个 SST 的体积来降低 SST 的总数量,对应参数为 target-file-size-base。 之十五 不要修改 log 目录中的 write2file 文件和 manifest。write2file 记录了 binlog 文件列表等关键信息,而 manifest 则记录了 RocksDB 的 version 信息,二者关乎 PikiwiDB(Pika) 实例重启后的 binlog 续写及 slave 断点续传时的数据正确性。 之十六 自 PikiwiDB(Pika) v3.5.0 之后的版本摒弃了用 rsync 进程进行全量同步,PikiwiDB(Pika) 进程内部重新实现了一套新的全量同步机制(通过名称为 rsync 的线程传输)。PikiwiDB(Pika) 提供了 rsync 的总传输限速参数 throttle-bytes-per-second 和并发 rsync 线程数 max-rsync-parallel-num,throttle-bytes-per-second 参数的单位是 MiB,建议在千兆环境中该参数设置不应高于 45,而在万兆环境中不应高于 500,以避免 PikiwiDB(Pika) 在全量同步的时候将所在服务器网卡流量用尽而影响到 PikiwiDB(Pika) 服务客户端。 之十七 在 PikiwiDB(Pika) 中执行 “ key * ” 并不会造成 Pika 阻塞(PikiwiDB(Pika) 是多线程的),但在存在巨量 key 的场景下可能会造成临时占用巨量内存(这些内存用于该连接存放 key *的执行结果,会在 “ key * ”执行完毕后释放),因此使用 “ key * ” 一定要小心谨慎。 之十八 如果发现 PikiwiDB(Pika) 有数据但 info keyspace 的显示均为 0,这是因为 Pika 并没有像 Redis 那样对 key 的数量进行实时统计,PikiwiDB(Pika) 中 key 的统计需要人工触发,执行 info keyspace 1,注意执行 info keyspace 是不会触发统计的,没有带上最后的参数 1 将会仅仅展示上一次的统计结果,key 的统计是需要时间的,执行状态可以通过 info stats 中的 is_scaning_keyspace 进行查看,该项值为 yes 表明统计正在进行,为 no 时表明没有正在进行的统计/上一次统计已结束,在统计执行完毕前 info keyspace 不会更新,info keyspace 的数据是存放在内存里的,重启将清零。 之十九 不要在 PikiwiDB(Pika) 执行全量 compact 的时候触发 key 统计(info keyspace 1)或执行 keys *,否则会造成数据体积暂时膨胀直到 key 统计、keys *执行结束。 之二十 对存在大量过期数据的 PikiwiDB(Pika) 实例,compact-cron 配置项可以在固定时段(一般配置为低峰流量时间段)进行过期数据清理。自 PikiwiDB(Pika) v3.5.0 之后还提供了 auto_compact 配置型,启用后 PikiwiDB(Pika) 会自动周期性执行 compact。 异常的数据体积(大于估算值 10%以上),可以通过执行 compact 命令,在 compact 执行完毕后观察数据体积是否恢复正常。 请求耗时突然异常增大,可以通过执行 compact 命令,在 compact 执行完毕后观察请求耗时是否恢复正常。 之二十一 自 PikiwiDB(Pika) v3.5.0 之后可统计过期 key(可通过 info keyspace 1 来触发统计,通过 info keyspace 查看统计结果),统计结果中的 invaild_keys 的值为“已删除/过期但还未被物理删除的 key 的数量”,PikiwiDB(Pika) 会在后台逐步地对已删除/过期的 key 进行物理清理,由于这是一个后台行为,因此在存在大规模过期 key 的场景下这些 key 可能无法被及时清理,因此建议关注该值,若发现无效 key 数量过多可通过 compact 命令进行全面清理,这样能够将未物理清理的无效数据控制在一个较好的程度从而确保 Pika 的性能稳定,如果 PikiwiDB(Pika) 中存储的数据是规律性过期的,例如每个 key 的过期时间为 7 天,那么建议通过配置 compact-cron 参数来实现每天的定时自动进行全量 compact,compact 会占用一定的 IO 资源,因此如果磁盘 IO 压力过大,建议将其配置为业务低峰期执行,例如深夜。 之二十二 write2file 的角色相当于 binlog,建议 write2file 保留周期/数量不低于 48 小时,足够的 write2file 有利于 大数据集群的从库扩容、从库服务器关机维修、从库迁移 等工作,不会因为主库 write2file 过期而被迫全量重传。 之二十三 PikiwiDB(Pika) 的备份生成为快照式,通过硬链接存放在 dump 目录下,以日期为后缀,每天只生成一份,多次生成备份时新的备份会覆盖之前的旧文件。在生成备份快照的时,为了确保数据的一致性 PikiwiDB(Pika) 会暂时阻塞写入,阻塞时间与实际数据量相关,根据测试PikiwiDB(Pika) 生成 500GiB 备份快照仅需 50ms。在写入阻塞的过程中连接不会中断,但 client 会感觉到 “在那一瞬间请求耗时增加了一些”。由于PikiwiDB(Pika)Pika 的快照是 db 目录中 sst 文件的硬连接,因此最初这个目录是不会占用磁盘空间的。 但在 PikiwiDB(Pika) db 目录中的 SST 文件发生了合并、删除后,硬链接的旧文件并不删除,这会导致 PikiwiDB(Pika) 占用的磁盘空间超出预估,所以请根据实际的磁盘空间调整备份保留天数,避免备份太多而造成磁盘空间用尽。 之二十四 如果写入量巨大且磁盘性能不足以满足 RocksDB memtable 的及时刷盘需求,那么 RocksDB 很可能会进入写保护模式(write stall,写入将被全部阻塞),建议更换性能更好的存储系统来支撑,或者降低写入频率(例如将集中写数据的 2 小时拉长到 4 小时),也可适当加大 write-buffer-size 的值来提高 memtable 的总容量从而降低整个 memtable 被写满的可能。 之二十五 PikiwiDB(Pika) 对数据进行了压缩,默认压缩算法为 snappy,并允许改为 zlib,因此每一次数据的存入、读出都需要经过压缩、解压,这对 CPU 有一定的消耗,建议像使用 Redis 一样使用 PikiwiDB(Pika):在 PikiwiDB(Pika) 中关闭压缩,而在 client 中完成数据的压缩、解压,这样不仅能够降低数据体积,还能有效降低 Pikiw。注意关闭和开启压缩后,需要重启 PikiwiDB(Pika) 实例。 之二十六 读写分离很重要,PikiwiDB(Pika) 在常见的主从集群中由于写入是单点的(仅 master 支持写),因此写入性能是有极限的。可通过多个 slave 来共同支撑读流量,因此 PikiwiDB(Pika) 集群的读性能是随着 slave 数量的增加而增加的,所以对于读量很大的场景,建议在业务层代码加入读写分离策略,同时在 PikiwiDB(Pika) 层增加 slave 数量。 之二十七 全量 compact 的原理是逐步对 RocksDB 的每一层做数据合并、清理工作,在这个过程中会新增、删除大量的 SST 文件,因此在执行全量 compact 的时候可以发现数据体积先增大后减小并最终减小到一个稳定值(无效、重复数据合并、清理完毕仅剩有效数据),建议在执行 compact 前确保磁盘空余空间不低于 30%,以避免新增 SST 文件时将磁盘空间耗尽,另外 PikiwiDB(Pika) 支持对指定数据结构进行 compact,例如一个实例中已知 hashtable 结构的无效数据很少但 hashtable 结构数据量很大,set 结构数据量很大且无效数据很多,在这个例子中 hashtable 结构的 compaction(命令是 compact hash) 是没有必要的,你可以通过 compact set 实现只对 set 结构进行 compaction。 注意:在 PikiwiDB v4.0.0 版本之后,不再支持对特定类型的 compaction。因为 PikiwiDB v3.x 使用的存储引擎是 Blackwidow,每个数据类型使用一个 RocksDB,而 v4.0.0 的存储引擎升级为 Floyd,可以在单个 RocksDB 中存储所有类型的数据。 之二十八 PikiwiDB(Pika) 3.5.0 以后的版本支持通过 rate-limiter-bandwidth 配置项以限制磁盘 IO 速率,可以通过调整该配置参数来调整读写速度。在 v4.0.0 之前只支持写限速,在 v4.0.0 之后支持读写限速,可以通过调整配置参数中的 rate-limiter-mode 来设置限速模式。 之二十九 PikiwiDB(Pika) 和 Redis 一样支持慢日志功能,可通过 slowlog 命令查看。slowlog 的原始内容只存于内存中,内存空间有上限,且这个上限可配置,当然如果配置过大会造成 slowlog 占用太多内存。PikiwiDB(Pika) 也允许将 slowlog-write-errorlog 设置为 yes,以把慢日志记录到 pika.ERROR 日志中,用于追溯、分析。 之三十 PikiwiDB(Pika) v3.5.2 以后的版本支持冷热数据分离,并在 Pika 磁盘存储之上增加了内存缓存层(称之为 RedisCache),将用户访问的热数据放在缓存层,冷数据放在磁盘,可减少查询磁盘的次数,提升服务的读性能,不论 PikiwiDB(Pika) 使用的是主从复制模式还是集群模式,可以配置 cache-mode 为 1 ,并设置缓存的大小和个数,以提升读性能。如果实例内存较小,不足以支撑缓存层的资源耗费,你可以选择将 cache-mode 设置成为 0 将缓存层关闭掉。 之三十一 PikiwiDB(Pika) 3.5.3 以后的版本支持了 Redis ACL 功能,设置用户密码的方式发生了变化,ACL的认证方式和 Redis 保持一致,在 config 文件中按照 ACL 规则对 user 进行配置。PikiwiDB(Pika) 3.5.3 仍然兼容以前旧版本的认证方式。 之三十二 PikiwiDB(Pika) 3.5.3 以后的版本支持快、慢命令分离,有快、慢两个线程池,可以防止慢命令对快命令线程池阻塞的影响。可以通过 slow-cmd-list 配置项设置慢命令列表,通过设置 slow-cmd-thread-pool-size 设置慢命令线程池个数。 之三十三 欲知后事如何,且待微信群里分解。请添加 PikiwiDB 小助手【微信号: PikiwiDB】为好友,它会拉您加入官方微信群。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册