高并发多级缓存架构解决方案 OpenResty、canal搭建及使用流程

news2024/11/20 3:31:58

高并发多级缓存架构解决方案

    • 1、缓存的常规使用方式
    • 2、请求流程拆分
      • 1、搭建tomcat集群
      • 2、搭建OpenResty
        • OpenResty的目录结构
        • nginx的配置文件
        • lua脚本的执行流程
        • http请求反向代理到tomcat服务器
      • 3、OpenResty、Redis的单点故障问题
      • 4、防止缓存穿透
        • java中通过redisson实现布隆过滤器
          • Redisson客户端搭建
        • OpenResty通过lua脚本实现Redisd的布隆过滤器
        • 多级缓存架构中加入布隆过滤器
      • 5、数据同步的问题
        • 设置有效期
        • 同步双写
        • 异步通知
        • canal实现异步通知
          • 1、canal业务流程
          • 2、docker搭建canal中间件
          • 3、查看canal运行状态
          • 4、springboot配置canal服务
            • 1、导入canal依赖
            • 2、配置文件
            • 3、修改实体类
            • 4、编写监听器
    • 3、多级缓存架构总结

1、缓存的常规使用方式

我们使用redis作为项目中的缓存中间件,一般的流程是这样的,接收请求并到Redis中查询,如果Redis缓存未命中,则到数据库中查询,再把查询到的值存入Redis中,为下次缓存命中做准备

在这里插入图片描述

2、请求流程拆分

此时从客户端发出请求到服务器端响应的流程是这样的

客户端发起异步请求到web服务器nginx(nginx可以提前保存好静态数据(图片、视频等),减轻后端服务器的压力),nginx反向代理到tomcat服务器,再去查询redis,若缓存命中,则直接返回,缓存未命中,则查询数据库进行缓存重建后返回结果

问题

依据这个流程,我们不难发现Tomcat将会是整个项目的性能瓶颈,Tomcat默认的http实现是采用阻塞式的Socket通信,每个请求都要创建一个线程处理,Tomcat默认配置的最大请求是150,也就是说单个tomcat服务器同时支持150个并发,但是具体承载多少并发,需要看硬件的配置,CPU核数越高,分配给JVM内存越多,性能也就越高,但同时也加重GC的负担,此时单个tomcat服务器节点已不能满足我们的需求,就需要搭建Tomcat集群以此通过横向拓展的方式提高我们整个项目的并发量

1、搭建tomcat集群

我们通过投钱搭建服务器,增加3台tomcat服务器数量以此提高并发量为150*3,如果当前每秒QPS是1w,并且假设平均的服务器处理时间 (RT)是0.5秒,那么在这里1s内,意味着有2000个线程同时在执行, 并且你的技术老大跟你说该业务不得出现熔断、限流的措施,必须保证响客户端请求,这个时候就应该意识到,tomcat是重量级服务器,它的算力上限瓶颈是无论如何都避免不了的

请求到达tomcat,即使做了tomcat集群整个后端服务的并发量也不达到我们的预期;

既然无法从tomcat这里入手,那么我就从nginx入手,可以把查询缓存的操作交由nginx去执行,正常Nginx Linux 服务器可以达到 500,000 – 600,000 次/秒 的请求处理性能,如果Nginx服务器经过优化的话,则可以稳定地达到 904,000 次/秒 的处理性能,大大提高Nginx的并发访问量,可以使用OpenResty 来替代原本的nginx
在这里插入图片描述

2、搭建OpenResty

那么什么是OpenResty呢,为什么说使用OpenResty

OpenResty是一个基于Nginx的高性能Web平台,用于方便搭建能够处理超高并发、拓展性极高的动态Web应用、Web服务和动态网关。

  • 具备Nginx的完整功能
  • 基于Lua语言进行拓展,继承大量精良的Lua库、第三方模块
  • 允许使用Lua自定义业务逻辑、自定义库

在这里插入图片描述

OpenResty的目录结构

显然可以发现,openResty包含了完整的Nginx,并且使用的方式都和原来一样,并且支持通过lua脚本连接到redis服务器查询缓存,同时OpenResty还支持本地缓存模块

在这里插入图片描述

nginx的配置文件

我们可以先看一下nginx的配置文件,主要做了以下几件事情

1、导入加载lua脚本的路径

2、导入加载c模块的路径

3、拦截客户端的异步请求,执行lua脚本,返回值类型为json

/usr/local/openresty/nginx/conf/nginx.conf

#user  nobody;
worker_processes  1;
error_log  logs/error.log;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    #lua 模块
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    #c模块     
    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
    # 添加共享字典 本地缓存
    # 启动缓存关键字  缓存名  缓存大小
    lua_shared_dict item_cache 150m;

    server {
        listen       8081;
        server_name  localhost;

        location ~ /api/item/(\d+) {
           default_type application/json;
           content_by_lua_file lua/item.lua;
        }

        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

lua脚本的执行流程

OpenResty通过lua脚本查询Redis缓存,并且将查询到的缓存保存在OpenResty本地

先通过查询OpenResty本地缓存,若查询不到则查询Redis,若缓存依旧未命中,则通过发送http请求到tomcat集群,但是http请求只携带请求路径和参数,并不包括ip和端口号,我们可以通过OpenResty中的nginx模块去捕获请求的路径,然后通过反向代理到tomcat服务器

read_http 封装了http请求函数

read_redis封装了调用redis请求函数

ngx.shared.item_cache 是OpenResty提供的本地缓存模块

-- 导入common函数库
local common = require('common')
local read_http = common.read_http
local read_redis = common.read_redis

-- 导入cjson库
local cjson = require('cjson')

-- 导入共享词典,本地缓存
local item_cache = ngx.shared.item_cache

-- 封装查询函数
function read_data(key,expire,path,params)
   -- 查询本地缓存
   local val = item_cache:get(key)
   if not val then
       ngx.log(ngx.ERR,"本地缓存查询失败,尝试查询redis, key:",key)
         -- 查询redis
        val = read_redis('127.0.0.1',6379,key)
         -- 判断查询结果
        if not val then
           -- redis 查询失败
           ngx.log(ngx.ERR,"redis查询失败,尝试查询http key:",key)
           -- 查询http
           val = read_http(path,params)
         end
     end
    -- 查询成功,把数据写入本地缓存
    item_cache:set(key,val,expire)
    -- 返回成功
  return val
end
-- 获取路径参数
local id = ngx.var[1]

-- 查询商品信息
local itemJSON = read_data("item:id:"..id,1800,"/item/" .. id, nil)

-- 查询商品库存
local stockJSON = read_data("item:stock:id:"..id,60,"/item/stock/" .. id, nil)

-- 转化为lua的table
local item = cjson.decode(itemJSON)
local stock = cjson.decode(stockJSON)
-- 组合数据
item.stock = stock.stock
item.sold = stock.sold

-- 返回结果,商品反序列化
ngx.say(cjson.encode(item))

http请求反向代理到tomcat服务器

#user  nobody;
worker_processes  1;
error_log  logs/error.log;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    #lua 模块
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    ##c模块     
    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
    # 添加共享字典 本地缓存
    # 启动缓存关键字  缓存名  缓存大小
    lua_shared_dict item_cache 150m;

    upstream tomcat-cluster {
        # 通过hash策略,计算出每个请求路径固定访问的tomcat节点,为jvm进程缓存做准备
        hash $request_uri;
        server 192.168.89.44:8081;
        server 192.168.89.45:8082;
        server 192.168.89.46:8083;
    }

    server {
        listen       8081;
        server_name  localhost;

        # 捕获lua脚本中的http请求路径为item,通过反向代理到tomcat集群
        location /item {
           proxy_pass http://tomcat-cluster;
        }
        location ~ /api/item/(\d+) {
           default_type application/json;
           content_by_lua_file lua/item.lua;
        }

        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

3、OpenResty、Redis的单点故障问题

任何分布式系统都难免出现单点故障,为保证服务的高可用,我们通过搭建集群,以数据冗余、服务冗余的方式防止由于网络波动、服务器挂机阻塞导致整个系统发生故障,所以OpenResty需要搭建集群,而Redis根据业务的需要,如果是读多写少的场景,并且是高并发读,可以搭建主从集群实现读写分离,并且通过哨兵集群实现故障自动转移,如果业务有海量的数据并且存在高并发写的场景,需要搭建分片集群,实现多主多从

目前整个业务流程如下

在这里插入图片描述

在这里插入图片描述

4、防止缓存穿透

使用分布式缓存后,伴随着提高服务的响应速度之外,也带了许多隐患,如缓存击穿、缓存穿透、缓存雪崩等问题,其中缓存穿透我们可以通过策略来避免

缓存穿透:redis缓存中不存在,数据库也查询不到,在高并发下大量请求都会落到数据库中,使数据库存在服务响应缓慢甚至宕机的风险

解决缓存穿透的方法也有很多,比如前端设置些id基础校验、或者增加id复杂度避免被恶意猜测,通过后端编码实现的方式就有缓存空对象和布隆过滤器了

缓存空对象这里不再阐述,我们重点介绍布隆过滤在业务流程中如何引入

布隆过滤器:依靠redis中bitmap特殊的数据结构,可以把bitmap理解为一个巨大的二进制数组,通过0跟1代表数据是否存在,可以提前把某个热点数据的id通过一定的hash算法存储到对应的bitmap中,客户端请求的id也通过同样的hash算法后去bitmap中查找,若为1则数据一定存在,再去redis或数据库中查找

布隆过滤器的原理:

第一步:插入集合:当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。

第二步:检索集合:我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。

(注意:Bloom Filter跟单哈希函数Bit-Map不同之处在于:Bloom Filter使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率

布隆过滤器作为一个判断集合中是否存在元素的算法。
第一个,设计一个足够好的hash算法,足够散列,足够平均,使哈希冲突的概率尽可能小。
第二个,一个哈希算法终究是不够的,使用多个哈希算法,使哈希冲突的概率尽可能小。
两个点都是一个目的,降低哈希冲突。

布隆过滤器的核心:不存储数据的本身。
布隆过滤器算法优点(因为不存储数据本身,所以数据空间小,检索快,带来了优点):
数据空间小,不用存储数据本身。
布隆过滤器算法本身缺点(因为不存储数据本身,所以误差和难以删除,带来了缺点):
(1)元素可以添加到集合中,但不能被删除。
(2)匹配结果只能是“绝对不在集合中”,并不能保证匹配成功的值已经在集合中。
(3)当集合快满时,即接近预估最大容量时,误报的概率会变大。
(4)数据占用空间放大。一般来说,对于1%的误报概率,每个元素少于10比特,与集合中的元素的大小或数量无关。 查询过程变慢,hash函数增多,导致每次匹配过程,需要查找多个位(hash个数)来确认是否存在。
小结:对于BloomFilter的优点来说,缺点都可以忽略。毕竟只需要kN的存储空间就能存储N个元素。空间效率十分优秀。

更详细的布隆过滤器原理,请转到 https://blog.csdn.net/qq_36963950/article/details/108532523

布隆过滤器优缺点

优点:优点很明显,二进制组成的数组,占用内存极少,并且插入和查询速度都足够快。

缺点:随着数据的增加,误判率会增加;还有无法判断数据一定存在;另外还有一个重要缺点,无法删除数据。

java中通过redisson实现布隆过滤器

Redisson客户端搭建
package com.heima.item.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: 小爽帅到拖网速
 */
@Configuration
public class RedisConfig {

  @Bean
  public RedissonClient redissonClient(){
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.79.129:6379");
    RedissonClient redissonClient = Redisson.create(config);
    return redissonClient;
  }
}

布隆过滤器配置类实现

import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: 小爽帅到拖网速
 */
@Configuration
public class RedissBloomFilerConfig {

  @Autowired
  private RedissonClient redissonClient;

  @Bean
  public RBloomFilter<String> bloomFilter(){
    RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("bloomtest");
    // 初始化布隆过滤器
    bloomFilter.tryInit((long)1E8,0.003);
    return bloomFilter;
  }
}

使用过程

import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: 小爽帅到拖网速
 */
@RestController
public class TestBloomFilterController {

  @Autowired
  RedissonClient redissonClient;

  @Autowired
  RBloomFilter bloomFilter;
  @GetMapping("/add/{name}")
  public  String addData(@PathVariable String name){
    return  bloomFilter.add(name)?"ok":"false";
  }
  @GetMapping("/list/{name}")
  public  boolean listData(@PathVariable String name){
    return bloomFilter.contains(name);
  }
}

启动项目,访问 http://localhost:8081/add/xiaoshuang 请求响应ok后,查看redis保存结果

参数说明:

expectedInsertions 预插入数

falseProbaility 误判率

hashIterations 哈希迭代次数

在这里插入图片描述

OpenResty通过lua脚本实现Redisd的布隆过滤器

这里记录一下OpenResty中通过lua脚本调用Redis模块代码仓库

https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter

Configuration你可以找到三个配置变量**“ SIZE”“ PRECISION”“ NAME”**。

SIZE表示位阵列的大小。精密度意味着布隆过滤器的缺失公差。NAME表示redis键的前缀。

修改它们以使其最适合你的程序。

用法

  1. bf_check(redis_client,data)返回0/1(0:找不到,1:找到)
  2. bf_add(redis_client,data)返回“确定”

redis_client:请新建一个redis对象,并使用lua-resty-redis将其连接。数据:存储在bloom过滤器中的数据。

多级缓存架构中加入布隆过滤器

1、将布隆过滤器引入我们的业务流程,可以在java中利用Redisson实现布隆过滤器,在请求通过负载均衡到tomcat后,查询redis缓存命中前,先去查询布隆过滤器中是否命中,若命中,则继续去redis查询缓存,或者数据库查询缓存后进行缓存重建,布隆过滤器判断该数据存在,则redis、数据库一定会查询到数据,这样避免了高并发请求同样的key获取不到数据,从而导致大量请求都落到数据库,避免缓存穿透

2、如果想绕开tomcat,可以在OpenResty 通过lua脚本实现布隆过滤器,判断缓存是否存在,若存在则接着执行lua脚本去redis中获取数据,之后再根据需要判断是否发起http请求到tomcat集群,是否重建OpenResty的本地缓存实现

在这里插入图片描述

5、数据同步的问题

我们在OpenResty中通过lua脚本查询Redis中的缓存,利用nginx、redis高并发的读写能力来提高客户端请求的响应速度,但是同时为了保证数据的一致性问题,必须采取一定的策略去更新缓存或者是重建缓存,常见缓存数据同步的方式有以下三种:

设置有效期

为缓存设置有效期,如果缓存的内容是商品信息、地理位置信息等不会轻易改动的数据,可以通过设置缓存有效期时间,当到达缓存时间后,再去进行缓存重建,并且根据缓存数据指定缓存有效期的长短

优势:简单

缺点:存在数据短期不一致的问题

场景:缓存数据修改频率低

同步双写

修改数据库的同时修改缓存

优势:时效性强,缓存与数据库保持强一致性

缺点:代码入侵性强,耦合性高

场景:对数据一致性、时效性要求比较高的缓存数据

异步通知

修改数据库时同时发送事件通知,相关服务监听到后修改缓存数据,主要实现的方式有mq、canal

优点:代码入侵性、耦合度低,并且可以实现同时通知多个缓存服务

缺点:时效性差,通过异步实现可能存在短期数据不一致的状态

场景:时效性要求不高,并且可以满足实现同时同步多个服务的需求(如果修改数据库后,可以同时更新redis缓存、es数据)

canal实现异步通知

1、canal业务流程

在这里插入图片描述

1、修改完数据后,本次线程执行的业务直接就结束了,不会有任何代码入侵

2、canal中间件监听到mysql数据库的binlog文件的变化,立刻通知cache-service缓存服务去更新缓存(redis、elasticsearch)

2、docker搭建canal中间件

搭建前需要开启mysql的主从复制

修改mysql的my.cnf文件

vi /tmp/mysql/conf/my.cnf
# 添加一下内容
log-bin=/var/lib/mysql/mysql-bin
binlog-do-db=数据库名

在mysql数据库中创建canal账号

-- 创建账号
CREATE USER canal IDENTIFIED BY 'canal';
-- 授予权限
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
-- 刷新权限 
FLUSH PRIVILEGES;
-- 修改密码
ALTER USER 'canal'@'%' IDENTIFIED WITH mysql_native_password BY 'canal';

创建账号成功后,数据库重启

docker启动canal

docker run -p 11111:11111 --name canal \
-e canal.destinations=test \
-e canal.instance.master.address=ip:3306  \
-e canal.instance.dbUsername=canal  \
-e canal.instance.dbPassword=canal  \
-e canal.instance.connectionCharset=UTF-8 \
-e canal.instance.tsdb.enable=true \
-e canal.instance.gtidon=false  \
-e canal.instance.filter.regex=db_name\\..* \
--network heima \
-d canal/canal-server:v1.1.5

环境参数说明

# canal连接mysql服务器ip、端口号
canal.instance.master.address= mysql服务器的ip地址:3306
# mysql中创建的账号
canal.instance.dbUsername=canal  
# 登录密码 
canal.instance.dbPassword=canal   
# 监听某数据库下的所有表,db_name为数据库名
canal.instance.filter.regex=db_name\\..* \   
3、查看canal运行状态

查看canal运行日志 docker logs -f canal

docker exec -it canal bash

tail -f canal-server/logs/canal/canal.log

tail -f canal-server/logs/db_name/table_name.log

4、springboot配置canal服务
1、导入canal依赖
<dependency>
  <groupId>top.javatool</groupId>
  <artifactId>canal-spring-boot-starter</artifactId>
  <version>1.2.1-RELEASE</version>
</dependency>
2、配置文件
canal:
  destination: test  # 启动时设定的环境变量参数canal.destinations
  server: ip:11111
3、修改实体类

@TableName(“table_name”) 映射表名

@Id 标记主键

@Column 数据库表名与类中属性名映射(相同则不用标注,自动进行驼峰转换)
@Transient 代表该属性在数据库表中不存在

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;

import javax.persistence.Column;
import java.util.Date;

@TableName("tb_item")
public class Item {
    @TableId(type = IdType.AUTO)
    @Id
    private Long id;//商品id
    @Column(name = "name")
    private String name;//商品名称
    @Transient
    private Integer stock;//商品库存
}
4、编写监听器

通过实现EntryHandler<T>接口编写监听器,监听Canal消息。注意两点:

  • 实现类通过@CanalTable("tb_item")指定监听的表信息
  • EntryHandler的泛型是与表对应的实体类
  • 重写了EntryHandler的三个方法,分别是增删改
@CanalTable("tb_item")
@Component
public class ItemHandler implements EntryHandler<Pojo> {

    @Override
    public void insert(Pojo pojo) {
        // 写数据到redis
        // 写数据到elasticsearch
        // .....
    }

    @Override
    public void update(Pojo before, Pojo after) {

        // 更新redis数据
        // 更新elasticsearch
        // .....
    }

    @Override
    public void delete(Pojo item) {
        // 删除数据到redis
		// 删除elasticsearch
        // .....
    }
}

3、多级缓存架构总结

整个请求的处理流程如下
在这里插入图片描述

这是最终高并发多级缓存架构解决方案的架构图

在这里插入图片描述

梳理后的架构流程图

在这里插入图片描述

我们并没有在架构中引入mq,在数据同步模块中,除了使用canal实现数据异步更新,还可以通过mq去实现,或者在一些需要流量削峰场景中使用mq,在引入mq之后,还需要对mq的消息可靠性进行保证,比如生产者消息确认、消息持久化,消费者消息确认以及消息消费失败重试机制,并且为了保证mq的高可用性,同样需要搭建mq集群。

以上便是高并发多级缓存架构解决方案,如有误解,请在评论区指出,谢谢

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

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

相关文章

Mac 下配置 go语言环境

Mac 下配置 go语言环境两种方法安装Go通过Homebrew安装&#xff08;不太推荐&#xff09;通过官网安装 &#xff08;推荐&#xff09;方法一安装Homebrew通过Homebrew安装Go方法二 通过官网进行安装配置go环境配置go环境国内镜像Vscode环境配置Helloworld.go两种方法安装Go 通…

LabVIEW中的VI脚本

LabVIEW中的VI脚本用户可使用VI脚本选板上的VI、函数和相关的属性、方法&#xff0c;通过程序创建、编辑和运行VI。通过VI脚本&#xff0c;可减少重复的VI编辑所需的时间&#xff0c;例如&#xff1a;创建若干类似VI对齐和分布控件显示或隐藏控件标签连接程序框图对象注: 必须先…

【13】Docker_DockerFile | 关键字

目录 1、DockerFile的定义 2、DockerFile内容基本知识 3、Docker执行DockerFile的大致流程 4、DockerFile的关键字 5、举例&#xff1a; 1、DockerFile的定义 Dockerfile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。 2、Do…

[前端笔记——HTML介绍] 2.开始学习HTML

[前端笔记——HTML介绍] 2.开始学习HTML1什么是HTML&#xff1f;2剖析一个HTML元素3块级元素和内联元素4空元素5属性6为一个元素添加属性7布尔属性8省略包围属性值的引号9单引号或双引号?10剖析HTML文档11实体引用&#xff1a;在 HTML 中包含特殊字符1什么是HTML&#xff1f; …

LeetCode 17. 电话号码的字母组合

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 17. 电话号码的字母组合&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 17.…

文件操作中的IO流——字节流与字符流

一&#xff0c;IO流1.什么是IO流IO流是存取和读取数据的解决方案2.IO流的作用IO流用于读写数据&#xff0c;这些数据包括本地文件和网络上的一些数据&#xff1b;比如读写本地文件的时候需要用到文件读写的IO流&#xff0c;读写网络上的数据时需要通过Socket套接字来调用数据流…

机器学习:公式推导与代码实现-监督学习单模型

线性回归 线性回归(linear regression)是线性模型的一种典型方法。 回归分析不再局限于线性回归这一具体模型和算法,更包含了广泛的由自变量到因变量的机器学习建模思想。 原理推导 线性回归学习的关键问题在于确定参数w和b,使得拟合输出y与真实输出yi尽可能接近 为了求…

PowerDesigner16.5配置安装与使用

PowerDesigner16.5百度云下载链接 链接&#xff1a;https://pan.baidu.com/s/1b9XUqxVZ8gTqk_9grptcAQ?pwd3pl7 提取码&#xff1a;3pl7 一&#xff1a;软件安装 1.下载安装包&#xff08;包含安装文件、汉化包、注册文件&#xff09; 2.下载后文件内容如下 3.进入安装文件中…

5. 统计学基础1:平均值...四分位数、方差、标准差(均方差)、标准误(标准误差、均方根误差)、 标准分

文章目录1. 平均值、中位数、众数、极差、四分位数&#xff08;即下、中、上四份位数&#xff09;2. 方差&#xff08;Var、D(X) 、σ^2^&#xff09;、标准差【也叫均方差】&#xff08;SD 、σ&#xff09;3. 标准误【也叫标准误差、均方根误差】&#xff08;SE&#xff09;4…

高空探测数据处理--对流层顶选取

对流层的概念(维基百科) 对流层(英语:Troposphere)是地球大气层中最靠近地面的一层,也是地球大气层里密度最高的一层。它蕴含了整个大气层约75%的质量,以及几乎所有的水蒸气及气溶胶。 对流层从地球表面开始向高空伸展,直至对流层顶,即平流层的起点为止。对流层的上…

【Labview】每日一题

&#x1f6a9;write in front&#x1f6a9; &#x1f50e;大家好&#xff0c;我是謓泽&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f3c5;2021年度博客之星物联网与嵌入式开发TOP5&#xff5…

The ATIS Spoken Language Systems Pilot Corpus

摘要 语音研究有巨大的进步在过去使用以下的模式: 定义研究问题收集语料针对性的衡量进展解决这个研究问题 自然语言研究&#xff0c;另一方面&#xff0c;取得了典型的进步在没有任何数据语料的情况下&#xff0c;这能够测试研究的假设。 我们描述了ATISATISATIS试点语料库…

从C到C++及类与对象

目录 从C到C 嵌入式领域常用的GUI 语法的升级 引用 默认参数 函数重载 堆内存 概念和思维的升级 类和对象 类的申明 类的成员函数 常成员、常对象&#xff08;C推荐const而不用#define&#xff0c; mutable &#xff09; 静态成员(属于类不属于对象) 友元(破坏…

Opencv实战案例——模板匹配实现银行卡号识别(附详细介绍及完整代码下载地址)

Opencv目录1.项目意义2.模板匹配3.图像二值化3.1全局阈值3.2全局阈值代码即效果展示3.3 自适应阈值3.4自适应阈值代码即效果展示4.轮廓筛选4.1轮廓检测4.2绘制轮廓4.3轮廓筛选代码及效果展示5.形态学变化5.1腐蚀5.2膨胀5.3开运算和闭运算、礼帽和黑帽6.项目实战6.1读取图片转化…

next_permutation函数讲解

目录 前言&#xff1a; 简要概述&#xff1a; 例题&#xff08;1&#xff09;&#xff1a; P1088 [NOIP2004 普及组] 火星人 题目描述 输入格式 输出格式 输入输出样例 说明/提示 代码&#xff08;1&#xff09;&#xff1a; 例题&#xff08;2&#xff09;&#xf…

如何开发 Vite 3 插件构建 Electron 开发环境?(文末附视频讲解)

开发新版本 Vue 项目推荐你使用 Vite 脚手架构建开发环境&#xff0c;然而 Vite 脚手架更倾向于构建纯 Web 页面&#xff0c;而不是桌面应用&#xff0c;因此开发者要做很多额外的配置和开发工作才能把 Electron 引入到 Vue 项目中&#xff0c;这也是很多开发者都基于开源工具来…

用户多兴趣建模MIND

1. 概述 在工业界&#xff0c;一个完整的推荐系统中通常包括两个阶段&#xff0c;分别为召回阶段和排序阶段。在召回阶段&#xff0c;根据用户的兴趣从海量的商品中去检索出用户&#xff08;User&#xff09;可能感兴趣的候选商品&#xff08; Item&#xff09;&#xff0c;满…

JavaEE进阶第二课:Spring创建与使用

上一篇我们介绍了Spring的概念&#xff0c;知道了Spring是众多工具方法的IoC容器。 但是纸上谈兵终觉浅&#xff0c;这一篇文章就来介绍Spring创建与使用&#xff0c; 注之后我们对对象的称呼就叫Bean 1.1Spring项目的创建与配置 1.创建maven项目&#xff0c;语言选java&…

【1819. 序列中不同最大公约数的数目】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个由正整数组成的数组 nums 。 数字序列的 最大公约数 定义为序列中所有整数的共有约数中的最大整数。 例如&#xff0c;序列 [4,6,16] 的最大公约数是 2 。 数组的一个 子序列 本质是一个序…

Python(16):Numpy之array数组的数值计算

目录 0. 相关文章链接 1. 创建Array数组 2. 基本数值计算 2.1. numpy中的函数 2.2. 数组中的函数 3. 指定维度进行计算 3.1. numpy中的函数 3.2. 数组中的函数 4. 复杂计算 4.1. 统计乘机 4.2. 获取对应值的索引位置 4.3. 求平均值 4.4. 求标准差 4.5. 求方差 4…