本周推荐 | mysql中业务系统可借鉴的设计

news2024/11/15 5:02:14

fbb9f6f2e46a880e17e925ec9bd02403.gif

推荐语:学习优秀的开源系统来优化我们业务的架构设计,这是我们作为业务开发的必修课,这篇文章从经典的mysql系统原理引申到业务系统设计思考,让人耳目一新,值得我们学习。

——大淘宝技术工程师 默达

如果一个系统能存活5年,看到里面的代码我可能觉得要重构了,看到一个系统存活了10年,那么我就万万不敢动了。mysql能够从1979的一个报表工具,2000年开源,到现在支持高并发,高可用,成为互联网的活化石“世一库”,靠的是无数开源人对技术的热爱,创始人Monty Widenius的人格魅力,以及不断进化的能力……

3f4da062b9db91dd443a641b484c1a24.png

2fb9428defa0580d86fc2e924ea06e24.png

前言

之前在处理一些慢sql和索引失效问题的时候复习了一波mysql,加上给团队分享设计模式的时候,乱翻了好多源码和课程,越发觉得mysql写的很不错。mysql不仅仅是一个数据库,更是一个优秀的系统……我们不仅可以使用它,我们也可以借鉴它沉淀了数年的设计,技术升级我们的业务系统。

因为很多mysql的知识点大家都清楚,所以着重讨论,略过一些基础。时间匆忙,错误望指正,补充的请留言。

bdbedc998c81a464487a88193eb1e6f6.png

WAL和二阶段提交

  日志

开头肯定是绕不开mysql中经常提到的WAL技术,为了避免发生数据丢失的问题,当前事务数据库系统普遍都采用了 WAL(Write Ahead Log)策略:即当事务提交时,先写redo log,再修改页(先修改缓冲池,再刷新到磁盘);当由于发生宕机而导致数据丢失时,通过 redo log来完成数据的恢复。关键点是日志先行,再写磁盘。

那么记录什么样的日志呢?

引擎层会记录redolog,服务层会记录binlog。redo log是物理日志,记录的是“在XXX数据页上做了XXX修改”;binlog是逻辑日志,记录的是原始逻辑,其记录是对应的SQL语句;binlog 是追加写入的,就是说 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志;而 redo log 是循环写入的。

用户如果对数据库中的数据进行了修改,必须保证日志先于数据落盘。当日志落盘后,就可以给用户返回操作成功,并不需要保证当时对数据的修改也落盘。如果数据库在日志落盘前crash,那么相应的数据修改会回滚。在日志落盘后crash,会保证相应的修改不丢失。

在日志先行技术之前,数据库只需要把修改的数据刷回磁盘即可,用了这项技术,除了修改的数据,还需要多写一份日志,也就是磁盘写入量反而增大,但是由于日志是顺序的且往往先存在内存里然后批量往磁盘刷新,相比数据的离散写入,日志的写入开销比较小。

2c830caca2a6ee914c617c33f5747878.png

那么mysql是如何去做日志刷新和数据刷新的呢?

当用户线程产生日志的时候,首先缓存在一个线程私有的变量(mtr)里面,只有完成某些原子操作的时候,才把日志提交到全局的日志缓存区中。当线程的事务执行完后,把日志从缓冲区刷到磁盘。

当把日志成功拷贝到全局日志缓冲区后,会继续把当前已经被修改过的脏页加入到一个全局的脏页链表中。这个链表是order by modified time asc的且用一个字段来记录。这种机制保证从老到新刷入磁盘。这里最重要的是,脏页链表的有序性。

每个 InnoDB 存储引擎至少有 1 个redo log文件组,多个redo log文件。为了得到更高的可靠性,用户可以设置多个镜像日志组(mirrored log groups),将不同的文件组放在不同的磁盘上,以此提高 redo log 的高可用性。在日志组中每个 redo log file 的大小一致,并以循环写入的方式运行。

write pos 和 CheckPoint 之间的就是 redo log file 上还空着的部分,可以用来记录新的操作。

如果 write pos 追上 CheckPoint,就表示 redo log file 满了,这时候不能再执行新的更新,得停下来先覆盖一些 redo log,把CheckPoint 推进一下。

3c7a89c9aee1300d752ed6b894e13247.png

  • 业务可以借鉴-类WAL机制实现合并处理,异步处理,异常恢复回滚等

其实这一块mysql有很多贴近数据层面的设计,但是把数据想象为业务,数据的记录和回滚--->业务操作的记录和回滚,数据的原子性--->业务操作的原子性,那么会有一些灵感。

其实现在很多关注数据强一致性的系统,都会记录操作(记录入数据库)来达到异常恢复和回滚的效果。比如结算账单的发起收佣和分账,商品的发品上下架,交易订单的打标去标,等等,都会将业务操作记录下来,作为落库保障稳定性,同时支持错误情况下的回滚凭证。不仅如此,也可以实现异步和外部系统交互的操作。达到重试和异步的机制。

下面是mysql更新数据操作和结算系统分账操作的对比图。mysql的“用户调用-日志记录-磁盘”就类比于系统的“操作发起者-持久化操作-下游”。都是运用了WAL机制,首先从用户调用(业务层)查询或初始化等操作,然后在内存(or业务领域层)记录即将执行的原子性的操作,之后采用不同机制(mysql使用内存刷取机制or结算系统运用异步调用及其他机制)来执行最终操作(mysql磁盘or业务系统底层服务)。

fce5dbbf3d1a3f461e96d2915ef17839.jpeg

这里第二张图把回滚机制和多次重试的机制统一放到处理机制里面,并且和各内存中操作用双箭头表示调用和恢复回滚。

9d8e04b574d23a47e07252d87c11ac6d.jpeg

持久化的方式:最常见的,就用各种数据库把操作记录或者账单或者领域事件的状态记录下来,单条多次更新;或者比较少见使用日志文件记录下来每一次变更,就如同mysql写log一样;

处理机制中正向处理一般是单次的同步即时调用,也可以考虑的是:

  1. 合并处理减少调用量,在并发量较高的情况下,合并请求,或者也可以将一些更新操作合并到内存中进行调用;

  2. 定时捞取请求异步处理削峰,这种是比较常见的不关注实时性的请求处理,在系统水位不紧张的情况下,内存中定时异步捞取持久化的请求去调用。或者是使用消息机制比如metaq,去慢慢消费处理调用;

异常恢复:

服务调用的原子操作包括:a,b,c。当其中c服务调用超时或者失败,那么就会依次执行回滚操作c’,b’,a’。使用wal机制将服务执行的commit和rollback之前保留重要执行信息。举个简单例子,卖场佣金代扣到旗舰店-->调用平台收佣-->销账,当销账失败无数次由于比如风控原因不能重试成功的时候,需要采用异常回滚。那么将依次采取三个服务的回归方法,进行事务回滚。将钱最终返回原来卖场,否则钱岂不就是卡在旗舰店。

b1733b0b3fbc738c045b9b37a732a5a0.jpeg

springboot提供的拦截完全可以达到事务识别,同时各服务添加服务id,类似mysql的xid。这里可以参考一些github上的事务回滚框架。

举例,只有一个初级想法,可以讨论一下:

/**
* 回滚的具体方法
*/


public @interface Transactionable {
        String rollbackMethod();
}


/**
* 事务的状态
*/
public enum TransactionState {
    INIT(1),                                                                 
    COMMIT(2),
    ROLLBACK(3);
}


/**
* 各个服务的commit 和 rollback调用实体
*/
public interface Invocation {


    Class<?> getTargetClassType();


    String    getMethodName();


    Object[] getArgumentValues();


    Class<?>[] getArgumentTypes();


    Map<String,Object> getExtraAttachMap();


    Object getExtraAttachInfoByKey(String key,Object defaultValue);


    void putExtraAttachItem(String key,Object value);


}


/**
* 分布式事务的服务的核心结构
*/
public class aService implements Serializable{


    private static final long serialVersionUID = -4512371127490746819L;


    private  String xid;


    private  String serviceName;


    private String methodName;


    ......
}




/**
* transaction核心载体
*/
public class Transaction implements Serializable {


    private static final long serialVersionUID = 6648691752838557325L;


    private final TransactionGlobalId transactionGlobalId;


    private TransactionState transactionState;


    .....
}

  二阶段提交

redolog原是innodb引擎的东西,binlog是mysql server的东西,逻辑是独立的,可以理解为事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。

假如我们不使用二阶段提交。

先写 redo log 后写 binlog。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来。但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行值与原库的值不同。

先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行无更新。但是 binlog 里面已经记录了这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 与原库的值不同。

2c32887299f1f5b6b1236a783c4c7fd1.png

同时log会有完整格式及xid来确认完整性和关联。

  • 业务可以借鉴-保证数据一致性

其实这个已经有很多分布式事务的理论都写了,通过多段式来保证数据一致性。

这里写一下自己业务的应用,两个例子。比如在配置结算规则,业务方会设置一个规则,并进行自己的校验,平台方也会保存规则,进行校验和打款;再比如预约单核销创建尾款单的场景,需要保障电子凭证状态和订单状态的数据一致性。

其中有一些是需要强一致性,有些是需要弱一致性但是需要最终一致性的。可以参考base version的上图,进行一些定制。

结算规则设置就选择强一致性的二阶段请求,如下:

/**
     * 单协议插入:二阶段强一致-先落库初始化,再调用服务H,成功后再次落库生效
     */
    private void addAgreement(SettleAgreement settleAgreement, SettleAgreementSaveReqDTO settleAgreementSaveReqDTO){
        settleAgreement.setEffectStatus(0);
        int num = agreementWriteRespository.insertAgreement(settleAgreement);
        if(num != 1){
            throw new SettleBizException(CommonErrorDef.DB_HANDLE_FAIL);
        }
        // 实际生效规则需要同步H系统
        if(settleAgreementSaveReqDTO.getStatus() == 1){
            settleAgreement = agreementReadRespository.getAgreementsByOutId(SettleAgreementReq.of(settleAgreementSaveReqDTO.getRuleRelatedId(),settleAgreementSaveReqDTO.getBizCode(), null));
            if(settleAgreement.needCallOut()) {
                // 同步H系统
                syncRule(settleAgreement);
            }
            settleAgreement.setEffectStatus(1);
            // 调用成功后生效本地规则
            num = agreementWriteRespository.updateAgreement(settleAgreement);
            if(num != 1){
                throw new SettleBizException(CommonErrorDef.DB_HANDLE_FAIL);
            }
        }
    }

其实也是init本地-prepare-提交另一侧数据-commit。如果那一方的系统异常(类比于mysql的宕机)则失败,且回滚。

而选择弱一致性(最终一致)的二阶段请求则需要设计回补方案。比如预约单核销创建尾款单的场景。尾款单的订单状态需要和电子凭证的状态保持一致(电子凭证未冻结-现订单可创建,电子凭证已冻结-已有尾款单创建,电子凭证已核销-已有尾款单支付成功)。但是创单是p0场景,不能完全依赖电子凭证服务,电子凭证服务不可用或者延迟,不能影响创单,那么我们可以使用下面的方式,弱依赖+异步回补机制。

ec83320e1c6dac5cb900f7923db4fded.jpeg

e039a619e258be40829bda490df29878.png

一些结构

  引擎

Mysql的引擎不是固定的,比较常用的是innodb和myisam,很多模块都是通过插件的形式的方式加载到Mysql主程序上的,这其中不仅有一些日志,状态等插件,还有数据引擎等核心的插件。

在Mysql中访问接口的方式主要有两类,一类是通过注册使用观察者模式来调用;另外一类就是数据库引擎通过handlerton的方式来实现。在数据存储引擎中,对表及事务的相关操作都是通过这种方式来访问相关的引擎插件的。handlerton的源码太长,复制过来很丑就略了。基本分成两大块,是一系列的相关的变量定义,比如state、type、slot等等;另外是一系列的函数指针,诸如binlog_func等。

在Mysql中是通过全局变量来管理这个插件的,它其实是一个插件相关的哈希数组,它可以通过plugin_find_internal来发现插件。像innobase_hton,myisam_hton之类的。像实现的时候,引擎去初始化其实就是调用相关的函数plugin_initialize来实现,调用的话就是从plugin_foreach开始的。

  • 业务可借鉴-可拔插的思路

这种可拔插的,使用观察者和handlerton的形式来支持扩展的设计模式,其实中台大部分代码都是这样,就不多做引申了。

  内存的运用和一些算法

内存管理结构

mysql划分架构Server 层与引擎层(innodb),使用不同的方式进行管理。其中Server 层是由 mem_root 来进行内存管理,包括Sharing与Thead memory;而引擎层则主要由 Free List,LRU List,FLU List 等多个链表来统一管理 Innodb_buffer_pool。

一张网图,侵删。

4687327e05a7ce3a1c11c688ccfa95e4.png

业务开发的话关于mem_root了解一下即可,其实就是一个函数初始化一块较大的内存空间,向内存分配器申请内存空间,然后另一个函数在这块内存空间中分配出内存进行使用,其目的就是将多次零散的操作合并请求,以提升性能。并且不同的线程会产生不同的mem_root来管理各自的内存。

在innodb内存管理中,有一些分配方式。

内存分配方式

由于 CPU速度与磁盘速度之间的不匹配,通常会使用缓冲池技术来提高数据库的整体性能。通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。前文聊到了查询和更新页操作,就是依赖这个buffer pool:从磁盘读到的页存放在缓冲池中,下一次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。修改操作的具体步骤就是这样的:修改在缓冲池中的页;然后再以一定的频率刷新到磁盘上。控制poos和包含配置的主结构是buf_pool_t,控制数据页的是buf_page_t。

这个地方用了一个内存分配算法,在释放一个内存块的时候没有直接放回,而是先查看其伙伴是否也空闲,如果是则进行合并,再尝试对合并后的内存块进行合并。如果其伙伴是在使用的状态,这里做了一次重新分配操作,将其内容拷贝到其它空闲的内存块上,再进行对它合并。

另外一个比较好聊的是LRU list的算法,即最少使用的老数据先从buffer pool驱逐,新的页数据加入到list的中间位置,这就是所谓的中点插入策略。一般情况下list 头部存放的是热数据,就是所谓的young page,list尾部存放的就是old page。这个算法就保证了最近经常使用的page信息会被保存在最近访问的sublist,相反的不被经常访问的就会保存在old sublist。一般比例是对半分或young page少点。这样既能支持热点数据的读取写入,又防止了大量数据对全表数据的影响。

  • 业务可以借鉴-缓存的思路

关于内存的思路没什么太多可以借鉴。主要是一些缓存的想法,包括热点商品的插入可以使用lru算法,在一些占用性能较大的服务上使用伙伴算法,等等。

29f4dcdca4975fa14f392400320d784f.png

动态地看待锁

mysql大量使用锁包括全局锁,表锁,行锁,mdl锁,间隙锁等等,来处理并发问题。作为共享的资源,当出现并发访问的时候,数据库需要合理地控制资源的访问规则。而锁就是用来实现这些访问规则的重要数据结构。

在mysql锁的设计中,在不同场景下使用不同粒度的锁,且锁也是放在最合适的地方,来提升并发度。

比如全库逻辑备份的时候,使用全局锁;当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加 MDL 写锁;对于行的更新操作,最小粒度加行锁。

  事务顺序

而如果事务中需要锁多个行,也会把最可能造成锁冲突,最可能影响并发度的锁尽量往后放。举个简单例子,交易发货。那么需要做:1. 更新消费者订单状态;2. 该货品量扣减;3. 插一条发货记录。为了保证交易的原子性,我们要把这三个操作放在一个事务中,很显然如果随意加锁的话,会产生大量锁冲突。比如两笔订单发货的是同一个货品,那这个货品这一行数据就会冲突。所以,如果把语句 2 安排在最后,比如按照 3-1-2 这样的顺序,那么该货品这一行的锁时间就最少,大量减少事务之间的锁等待,提升了并发度。

  锁的退化

虽然集团貌似为了避免死锁用的是Read Committed,而mysql默认的是Repeatable Reads。但是Repeatable Reads下的next key lock我觉得还是需要了解一下的也挺有意思。查找过程中访问到的对象会加next key lock;索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁;索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。这种资源的降级退化思路是可以借鉴的。

ba44f3e04fdf4e08532dba90716c31ea.png

状态机的使用

  状态流转

mysql中有很多的状态,包括Checking table,Closing tables,Killed,Locked,Sending data,Sleeping,Waiting for tables……等等状态,状态之间会互相流转。

比如checking table状态在查询之后会进入locked,以及更新状态会进入locked,查到锁冲突时会进入waiting等等。

8a3e85877012013003a949d841e567ef.png

能够在如此复杂的状态中进行精准流转,且代码并不臃肿。在mysql其中一个版本的分支代码中,尝试使用二维的形式来流转状态,是可以借鉴的。

  • 业务可以借鉴-二维简化复杂状态流转

一般业务系统中,状态机的使用是流程调用中set不同的状态,并允许在特定状态下进行特定操作。

最简单的做法是分支逻辑,即if-else,将每一个状态转移,原模原样地直译成代码。这种会使得极易漏写或者错写某个状态转移,可读性和可维护性都很差。具体请参考各种老系统的状态流转。

平时常见的做法是充血模式状态机,所有的状态转移和动作执行的代码逻辑,都集中在业务的实体类中,代码分散开来,同时存在一个状态机类作为流转。其实这种方案是比较好的,但是当状态很多的时候,会引入更多状态类和操作,代码会越来越臃肿。

实际上,除了用状态转移图来表示之外,状态机还可以用二维映射来表示,也叫做查表法,比如说,一维表示状态,另一维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改二维映射即可。

比如交易订单是有很多状态的。这里写的不一定对,举个例子而已。

状态\操作

关闭订单

付款

发货

确认收获

退款

。。。

交易关闭

/

/

/

/

/


待付款

交易关闭

待发货

/

/

/


待发货

交易关闭

/

待确认收货

/

交易关闭


待确认收货

交易关闭

/

待确认收货交易成功交易关闭

交易成功

/

/

/

/

。。。
。。。





public enum Event {
  closeOrder(0),
  pay(1),
  sendGoods(2),
  receiveGoods(3),
  refund(4);


  private int value;


  private Event(int value) {
    this.value = value;
  }


  public int getValue() {
    return this.value;
  }
}
public class OrderStateMachine {
  private State currentState;


  private static final State[][] transitionTable = {
          {trade_close, trade_close, trade_close, trade_close, trade_close},
          {trade_close, wait_sendgoods, wait_pay, wait_pay, wait_pay},
          {trade_close, wait_sendgoods, wait_receivegoods, wait_sendgoods, trade_close},
          {trade_close, wait_receivegoods, wait_receivegoods, trade_success, trade_close},
          {trade_success, trade_success, trade_success, trade_success, trade_success}
  };


  public OrderStateMachine() {
    this.currentState = State.trade_init;
  }


  public void closeOrder() {
    executeEvent(Event.closeOrder);
  }


  public void pay() {
    executeEvent(Event.pay);
  }


  public void sendGoods() {
    executeEvent(Event.sendGoods);
  }


  public void receiveGoods() {
    executeEvent(Event.receiveGoods);
  }


  public void refund() {
    executeEvent(Event.refund);
  }


  ...
  ...


  private void executeEvent(Event event) {
    int stateValue = currentState.getValue();
    int eventValue = event.getValue();
    this.currentState = transitionTable[stateValue][eventValue];
  }


  public State getCurrentState() {
    return this.currentState;
  }


}

这个只是简化一下。复杂做法,可以具体excute方法可以在各模块或产品包,映射模型也可以统一维护在配置文件中。只是为了将状态流转放到一处去维护。

同时除了订单状态,有些操作会引发物流单,支付单等状态,可以将二维升级为三维等等。而不是把各种状态的流转放到event代码中,会很难维护。针对不同的业务身份,不同业务类型,也可以设置不同的状态流转配置。

当然这种方式适合event比较简单,但是状态较多的场景,比如mysql中,其实很多事件只是加个锁,发个数据,等等。像交易如果越做越重的话,还是使用状态充血模式,需要依业务来选型。

d493ac7a2dadc403aa1fe2b0c92d81fb.png

如何“删库跑路”

首先,大家应该是没有单独数据权限的,且有审批,所以删库跑路还是不要多想了!从恢复难易程度来看几个删除数据的方法。

  1. 使用 rm 命令删除整个 MySQL 实例:登上机器,查看mysql安装路径然后查找是否存在服务,之后直接kill并rm带mysql的东西即可。这种方式的恢复方法,就是即使删除一个节点的实例,集群也会推举出新的主库,然后根据集群其他节点数据恢复这个节点的数据即可。对于高可用+跨机房的集群来说,除非批量全下掉实例,不然应该是最好恢复的。

  2. 删库/删表:使用drop database直接删除数据库,drop table 或者 truncate table来删除表。此时恢复需要全量备份,并且新的操作会有实时增量binlog,使用这些binlog恢复一个临时库,然后设置主备关系即可。如果binlog也删除了直接从binlog备份系统中找到需要的 binlog,再放回备库中,这样恢复事件一般很长。dba应该有些其他科技来加速。比如使用一些并行的方式。

  3. 使用delete语句删除一些数据行:除了简单delete外,搞复杂点比如delete完再insert一条不想干的,然后再update一下。其实对恢复来说复杂度差不多,使用binlog解析工具把语句反译一下,反过来执行一下放回备库重放,但是需要确保binlog_format=row 和 binlog_row_image=FULL,这个应该是默认的所以不用担心。

4e23615f515f22e46d67ebf8c75c5e43.png

总结



本短文大致介绍了一下mysql的wal机制,一些内部结构和算法,锁和状态机的视角,以及程序员经常碰到的“删除”。mysql发展这么多年了,涌现了很多专业分析和经典课程,本文主要是另辟蹊径从业务借鉴的角度来看看它的设计,给大伙儿提供一个引子,希望后续继续和评论区讨论。

其实在当前技术同学视角下,最常见的两方面,一是完成一个业务研发活动,比如商品的3d详情,交易的改价分摊,双十一的秒杀;二是实现技术上的突破,比如缓存tair支持sql,mq消息队列的升级,部署安全等等。因为这些都是容易让人获得成就感的,是容易量化的。然而还有一些比如合理设计系统架构,构建开放开源文化,不同技术互相融合,是容易让人忽略的,却也是非常重要的。

b100f241e63c6b1604aa0ce428fde73c.png

团队介绍

我们是大淘宝技术创新业务团队,支撑淘宝,天猫核心电商以及家装新零售,优品,汽车等创新业务,服务n亿用户,赋能各行业数千万商家,并作为核心技术团队,保障双十一购物狂欢节的成功。家装新零售业务围绕卖场线和品牌线,以门店数字化交易为基础,通过营销工具,私域导购,客户留资等手段构建线上线下相结合的家装新零售解决方案,为家装新零售商家持续带来增量价值。

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

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

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

相关文章

HTML+CSS鲜花静态网页设计

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

Win11的两个实用技巧系列之如何关闭文字热门搜索、任务栏上的应用

目录 in10和Win11 22H2如何关闭文字热门搜索? Win11 22H2关闭文字热门搜索 Win10 22H2关闭文字热门搜索 Win11中如何不用鼠标打开已固定在任务栏上的应用 鼠标的操作方式如下&#xff1a; 点击拿去 in10和Win11 22H2如何关闭文字热门搜索? 不管是Win10还是Win11&#…

R语言GARCH-DCC模型和DCC(MVT)建模估计

这个简短的演示说明了使用r软件包的DCC模型及其方法的使用&#xff0c;尤其是在存在MVT分布形状参数的情况下进行2级DCC估计的另一种方法。 最近我们被客户要求撰写关于GARCH-DCC的研究报告&#xff0c;包括一些图形和统计输出。 相关视频&#xff1a;时间序列分析&#xff…

[附源码]计算机毕业设计springboot小型银行管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

用HTML、CSS技术设计的个人网页与实现制作(web前端期末大作业)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

“极致成本向左,本质安全向右”-谈谈锂电池储能系统的发展趋势

极致成本 or 本质安全? 1 快速增长的电化学储能电站 根据CNESA全球储能项目库的不完全统计,截至 2021 年底,全球已投运电力储能项目累计装机规模 209.4GW, 同比增长 9%。其中,抽水蓄能的累计装机规模占比首次低于 90%,比去年同期下降4.1个百分点;新型储能的累计装机规模…

Windows性能监视器使用说明

如何使用性能计数器 进入性能计数器 方式一&#xff1a; 命令行 perfmon 方式二&#xff1a;图形化操作 控制面板→系统与安全→管理工具→性能监视器 创建计数器收集器 创建收集器 在采集数据前&#xff0c;需要做好相应的收集器创建。 监视工具性能监视器&#xf…

Global Mapper将0-255的色彩映射表导出为RGB波段

当我们拿到0-255的色彩映射表栅格文件的时候&#xff0c;可能不太好用&#xff0c;需要导出为RGB波段或者其他波段形式的&#xff0c;在Global Mapper中可以做到。 打开0-255色彩映射表的栅格文件&#xff0c;在Arcmap中可以看到是这样的&#xff1a; 在Global Mapper中操作…

android灰色滤镜布局

android灰色滤镜布局 h5网页灰色滤镜 只要给 html 加下列css 样式就可以了 html {filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale1);-webkit-filter: grayscale(100%); }Android组件灰色滤镜 ColorMartrix类&#xff0c;这个类对外提供了很多 API&#x…

重点问题!CPU利用率过高排查思路|原创

本文讲解了重点面试问题CPU利用率高如何排查和解决。点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达CPU利用率高怎么办&#xff1f;如何排查和解决这是一个常见的面试问题&#xff0c;也是线上常遇到的问题之一。遇到线上服务器异常告警&…

【Win10】如何关闭Windows10自动更新

如何关闭Windows10自动更新 零、问题 Windows10老是自动更新&#xff0c;有时候第二天起来又得重新打开软件&#xff0c;真麻烦&#xff0c;Win10自动更新的时候还有点卡。 如何关闭&#xff1f; 经过上网查询&#xff0c;发现完全关闭难度比较大&#xff0c;但是我们可以选…

HTML学生个人网站作业设计 明星易烊千玺介绍(HTML+CSS) web前端开发技术 web课程设计 网页规划与设计

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

【信管2.5】项目可研(一)立项和可行性研究

项目可研&#xff08;一&#xff09;立项和可行性研究对于一个项目来说&#xff0c;最开始的步骤就是这个项目的立项过程。在这个过程中&#xff0c;我们要做的工作主要就是可行性研究。也就是说&#xff0c;这个项目值不值得我们来做&#xff0c;就需要对项目所涉及的领域、投…

fcpx插件:Stupid Raisins Grid Pop(网格弹出布局模板)

Stupid RAIsins Grid Pop是一款fcpx插件&#xff0c;可以在Final Cut Pro中快速布局任何网格弹出内容。为您轻松排队&#xff0c;均匀分配和安排任何物品&#xff01; 快速轻松地对齐fcpx中的所有内容&#xff01; Grid Pop是在Final Cut Pro X中进行布局&#xff0c;对齐和分布…

【RTS】安海波老师:SIP与RTC融合分享笔记

2020年已经应用。至今,呼叫中心与RTC结合,已经有了新探索。呼叫中心和RTC领域实践 : SIP与RTC融合 如何应用。五部分 贝壳联络中心 2018年分拆周三的财报:基于FS开源平台 <

(AVL)平衡二叉树

还是照旧&#xff0c;本篇主要讲一下代码实现&#xff0c;AVL相关的定义什么的这里不多赘述。 AVL树就是为了解决bst树出现了“线性”的问题&#xff0c;而发明的。什么是线性的就是一棵bst树全都只有左子树或者全都只有右子树&#xff0c;能想象来吧。 目录 LL型调整(左旋) …

HTML5期末考核大作业、HTML个人主页界面设计源码

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

uniapp 中 vuex 的使用

1. uniapp 中 vuex 的介绍 2. uniapp 中 vuex 的使用 3. require.context 介绍 4. vuex 模块分离 5. vuex 模块分离 - 代码优化 1. uniapp 中 vuex 的介绍 uniapp 内置了 vuex&#xff0c;不需像 vue 脚手架那样里通过 npm 安装了&#xff0c;我们只需要引用就行了 2. un…

会员接口治理的探索与实践

随着爱奇艺会员破亿&#xff0c;会员服务从小而快的单一业务系统升级为了按领域划分的微服务模式&#xff0c;满足了业务的高速发展和服务的高流量调用&#xff0c;但是微服务的拆分&#xff0c;系统间的交互越来越多&#xff0c;在需求开发时&#xff0c;协作成本随之增加&…

[激光原理与应用-30]:典型激光器 -2- 气体激光器 (连续激光器)

目录 第1章 概览 1.1 什么气体激光器 1.2 主要激励方式 1.3 发展历程 1.4 组成 1.5 特点 第2章 气体激光器分类 2.1 原子气体激光器 2.2 离子气体激光器 2.3 分子气体激光器 2.4 准分子激光器 第1章 概览 1.1 什么气体激光器 气体激光器利用气体作为工作物质产生激…