普通视图

发现新文章,点击刷新页面。
昨天以前忘忧 忘忧的小站

MySql入门:MySQL核心概念与架构解析

2025年4月26日 21:14

MySQL核心概念与架构解析

在现代应用开发中,数据库是系统的核心支柱。而MySQL作为世界上最流行的开源关系型数据库,其重要性不言而喻。今天,我们将深入探讨MySQL的核心概念和架构设计,为你揭开这个强大数据库系统的神秘面纱。

1. MySQL概述与版本演进

什么是MySQL?关系型数据库的核心价值

MySQL是一个开源的关系型数据库管理系统(RDBMS),由瑞典MySQL AB公司开发,目前属于Oracle公司。它采用客户端-服务器模型,使用结构化查询语言(SQL)进行数据管理。

关系型数据库的核心价值:

-- ACID特性的具体体现START TRANSACTION;-- 原子性(Atomicity):要么全部成功,要么全部失败UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;-- 一致性(Consistency):始终满足业务规则约束-- 隔离性(Isolation):事务间互不干扰-- 持久性(Durability):提交后数据永久保存COMMIT;

MySQL的关键特性:

  • 开源免费(社区版)
  • 跨平台支持
  • 支持多种存储引擎
  • 强大的复制功能
  • 丰富的生态系统

MySQL发展历程与重要版本特性

版本演进时间线:

版本发布时间重要特性
MySQL 3.232001年引入InnoDB存储引擎
MySQL 4.02003年联合查询、重写解析器
MySQL 5.02005年视图、存储过程、触发器
MySQL 5.12008年分区、事件调度器
MySQL 5.52010年InnoDB成为默认引擎
MySQL 5.62013年全文索引、NoSQL API
MySQL 5.72015年原生JSON支持、多源复制
MySQL 8.02018年窗口函数、CTE、角色管理

MySQL 5.7 vs 8.0 核心差异对比

-- MySQL 5.7 特性示例SELECT * FROM users WHERE JSON_EXTRACT(profile, '$.age') > 25;-- MySQL 8.0 新特性示例-- 窗口函数SELECT     name,     salary,    AVG(salary) OVER (PARTITION BY department_id) as avg_dept_salaryFROM employees;-- 公用表表达式(CTE)WITH department_stats AS (    SELECT         department_id,        AVG(salary) as avg_salary    FROM employees     GROUP BY department_id)SELECT * FROM department_stats WHERE avg_salary > 5000;-- 角色管理CREATE ROLE read_only;GRANT SELECT ON company.* TO read_only;GRANT read_only TO 'report_user'@'%';

性能对比:

  • MySQL 8.0 在读写并发性能上提升约30%
  • 更好的JSON处理性能
  • 改进的优化器,更准确的成本估算

MySQL在现代应用架构中的定位

在现代微服务架构中,MySQL扮演着重要角色:

TypeError: Cannot read properties of undefined (reading 'v')

2. MySQL体系架构深度解析

整体架构概览

MySQL采用经典的客户端-服务器架构,其核心组件包括:

MySQL Architecture:├── 连接层 (Connection Layer)├── SQL层 (SQL Layer)│   ├── 连接池│   ├── 查询解析器│   ├── 查询优化器│   ├── 查询执行器│   └── 缓存└── 存储引擎层 (Storage Engine Layer)    ├── InnoDB (默认)    ├── MyISAM    ├── Memory    └── 其他引擎

连接层:连接池、身份验证、线程管理

连接处理机制:

public class MySQLConnectionPool{    // MySQL使用线程池处理连接    private const int MAX_CONNECTIONS = 151; // 默认最大连接数        public void HandleConnection(ClientConnection client)    {        // 1. 连接验证        if (!Authenticate(client.Username, client.Password))            throw new AuthenticationException();                    // 2. 权限检查        if (!CheckPrivileges(client.Username, client.Database))            throw new AccessDeniedException();                    // 3. 创建会话        var session = CreateSession(client);                // 4. 线程分配(一对一或线程池)        AssignThreadToSession(session);    }}

连接状态监控:

-- 查看当前连接信息SHOW PROCESSLIST;-- 查看连接统计SHOW STATUS LIKE 'Threads_%';-- 输出示例:-- Threads_cached: 10      -- 缓存中的线程数-- Threads_connected: 25   -- 当前连接数-- Threads_created: 1000   -- 已创建线程总数-- Threads_running: 5      -- 活跃线程数

SQL层:查询解析、优化器、执行器工作原理

SQL查询处理流程:

-- 示例查询SELECT u.name, COUNT(o.id) as order_countFROM users uJOIN orders o ON u.id = o.user_idWHERE u.created_at > '2023-01-01'GROUP BY u.idHAVING order_count > 5ORDER BY order_count DESCLIMIT 10;

处理步骤详解:

  1. 查询解析(Parser)

    • 语法分析:检查SQL语法正确性
    • 词法分析:将SQL分解为标记(tokens)
    • 生成解析树
  2. 查询优化(Optimizer)

    -- 使用EXPLAIN查看优化器决策EXPLAIN FORMAT=JSON SELECT u.name, COUNT(o.id) as order_countFROM users uJOIN orders o ON u.id = o.user_idWHERE u.created_at > '2023-01-01'GROUP BY u.idHAVING order_count > 5;
  3. 查询执行(Executor)

    • 根据执行计划访问存储引擎
    • 应用WHERE条件过滤
    • 执行JOIN操作
    • 进行GROUP BY和聚合
    • 应用HAVING条件
    • 排序和限制结果

存储引擎层:插件式架构设计

MySQL的存储引擎采用插件式架构,允许为不同表选择不同存储引擎:

-- 创建表时指定存储引擎CREATE TABLE users (    id INT PRIMARY KEY AUTO_INCREMENT,    name VARCHAR(100),    email VARCHAR(255),    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB;-- 查看表的存储引擎SHOW TABLE STATUS LIKE 'users';

存储引擎对比:

特性InnoDBMyISAMMemory
事务支持
行级锁
外键支持
崩溃恢复⚠️
全文索引✅ (5.6+)
适用场景事务型应用读密集型临时数据

InnoDB存储引擎架构详解

InnoDB是MySQL的默认存储引擎,其架构设计非常精妙:

InnoDB Architecture:├── 内存结构 (In-Memory Structures)│   ├── Buffer Pool (缓冲池)│   ├── Change Buffer (变更缓冲)│   ├── Adaptive Hash Index (自适应哈希索引)│   ├── Log Buffer (日志缓冲)│   └── Additional Memory Pool└── 磁盘结构 (On-Disk Structures)    ├── 表空间 (Tablespaces)    │   ├── 系统表空间    │   ├── 独立表空间    │   ├── 通用表空间    │   └── 临时表空间    ├── 重做日志 (Redo Logs)    ├── 撤销日志 (Undo Logs)    └── 二进制日志 (Binary Logs)

Buffer Pool工作机制:

-- 查看Buffer Pool状态SHOW ENGINE INNODB STATUS\G-- Buffer Pool相关配置SELECT @@innodb_buffer_pool_size;      -- 缓冲池大小SELECT @@innodb_buffer_pool_instances; -- 缓冲池实例数-- 监控Buffer Pool命中率SELECT     (1 - (Variable_value / (SELECT Variable_value                            FROM information_schema.global_status                            WHERE Variable_name = 'Innodb_pages_read'))) * 100 as hit_rateFROM information_schema.global_status WHERE Variable_name = 'Innodb_buffer_pool_reads';

内存结构与磁盘存储机制

内存管理:

public class InnoDBMemoryManager{    // Buffer Pool - 数据页缓存    private Dictionary<PageId, DataPage> bufferPool;        // Change Buffer - 非唯一索引变更缓存    private Dictionary<IndexId, IndexChange> changeBuffer;        // Log Buffer - 重做日志缓冲    private CircularBuffer<RedoLogRecord> logBuffer;        public DataPage ReadPage(PageId pageId)    {        // 1. 检查Buffer Pool        if (bufferPool.ContainsKey(pageId))            return bufferPool[pageId];                    // 2. 从磁盘读取        var page = diskStorage.ReadPage(pageId);                // 3. 使用LRU算法管理缓存        if (bufferPool.Count >= maxSize)            EvictLeastRecentlyUsedPage();                    bufferPool[pageId] = page;        return page;    }}

磁盘存储结构:

-- 表空间文件结构-- 系统表空间: ibdata1-- 独立表空间: db/table.ibd-- 查看表空间信息SELECT     table_name,    engine,    table_rows,    avg_row_length,    data_length,    index_length,    data_freeFROM information_schema.tables WHERE table_schema = 'your_database';

3. 安装部署与配置优化

多平台安装指南

Linux安装(Ubuntu为例):

# 更新包管理器sudo apt update# 安装MySQL服务器sudo apt install mysql-server-8.0# 安全配置sudo mysql_secure_installation# 启动服务sudo systemctl start mysqlsudo systemctl enable mysql# 验证安装mysql --version

Docker部署:

# docker-compose.ymlversion: '3.8'services:  mysql:    image: mysql:8.0    container_name: mysql-server    environment:      MYSQL_ROOT_PASSWORD: your_secure_password      MYSQL_DATABASE: app_db      MYSQL_USER: app_user      MYSQL_PASSWORD: app_password    ports:      - "3306:3306"    volumes:      - mysql_data:/var/lib/mysql      - ./conf.d:/etc/mysql/conf.d    command:       - --default-authentication-plugin=mysql_native_password      - --character-set-server=utf8mb4      - --collation-server=utf8mb4_unicode_civolumes:  mysql_data:

配置文件详解(my.cnf/my.ini)

生产环境配置示例:

[mysqld]# 基础配置datadir=/var/lib/mysqlsocket=/var/lib/mysql/mysql.sockport=3306# 内存配置innodb_buffer_pool_size=16G           # 建议为系统内存的70-80%innodb_log_file_size=2G               # 重做日志文件大小innodb_log_buffer_size=256M           # 日志缓冲区大小# 连接配置max_connections=1000                  # 最大连接数thread_cache_size=100                 # 线程缓存大小table_open_cache=4000                 # 表缓存大小# InnoDB配置innodb_file_per_table=ON              # 每个表独立表空间innodb_flush_log_at_trx_commit=1      # 事务提交时刷盘innodb_flush_method=O_DIRECT          # I/O方式innodb_buffer_pool_instances=8        # 缓冲池实例数# 复制配置(如果使用主从)server_id=1log_bin=mysql-binbinlog_format=ROW# 性能配置query_cache_type=0                    # 8.0已移除查询缓存sort_buffer_size=2Mread_buffer_size=2Mread_rnd_buffer_size=2M[mysql]default-character-set=utf8mb4[client]default-character-set=utf8mb4

系统参数调优实战

性能诊断查询:

-- 查看关键性能指标SHOW STATUS WHERE `variable_name` IN (    'Questions', 'Com_select', 'Com_insert', 'Com_update', 'Com_delete',    'Innodb_buffer_pool_reads', 'Innodb_buffer_pool_read_requests',    'Threads_connected', 'Threads_running',    'Key_reads', 'Key_read_requests');-- 计算缓冲池命中率SELECT     ROUND(1 - (variable_value / (        SELECT variable_value         FROM information_schema.global_status         WHERE variable_name = 'innodb_buffer_pool_read_requests'    )), 4) * 100 as buffer_pool_hit_rateFROM information_schema.global_status WHERE variable_name = 'innodb_buffer_pool_reads';-- 检查慢查询SHOW VARIABLES LIKE 'slow_query_log%';SHOW VARIABLES LIKE 'long_query_time';

安全配置与权限管理

基础安全配置:

-- 创建应用用户(遵循最小权限原则)CREATE USER 'app_user'@'192.168.1.%' IDENTIFIED BY 'secure_password_123';-- 授予精确权限GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'192.168.1.%';-- 创建只读用户用于报表CREATE USER 'report_user'@'%' IDENTIFIED BY 'readonly_password';GRANT SELECT ON app_db.* TO 'report_user'@'%';-- 查看用户权限SHOW GRANTS FOR 'app_user'@'192.168.1.%';-- 密码策略配置SET GLOBAL validate_password.policy = MEDIUM;SET GLOBAL validate_password.length = 12;

网络安全配置:

-- 限制连接来源RENAME USER 'root'@'%' TO 'root'@'localhost';-- 删除测试数据库和匿名用户DROP DATABASE IF EXISTS test;DELETE FROM mysql.user WHERE User = '';-- 刷新权限FLUSH PRIVILEGES;

监控工具与性能基线建立

系统监控查询:

-- 性能模式监控(MySQL 5.6+)SELECT * FROM performance_schema.events_statements_summary_by_digest ORDER BY sum_timer_wait DESC LIMIT 10;-- 查看锁信息SELECT * FROM information_schema.INNODB_LOCKS;SELECT * FROM information_schema.INNODB_LOCK_WAITS;-- 表统计信息SELECT     table_name,    table_rows,    data_length,    index_length,    ROUND((data_length + index_length) / 1024 / 1024, 2) as total_size_mbFROM information_schema.tables WHERE table_schema = 'your_database'ORDER BY total_size_mb DESC;

建立性能基线:

-- 创建性能基线表CREATE TABLE performance_baseline (    id INT AUTO_INCREMENT PRIMARY KEY,    metric_name VARCHAR(100),    metric_value DECIMAL(20,4),    collected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,    notes TEXT);-- 收集基线数据INSERT INTO performance_baseline (metric_name, metric_value)SELECT     'qps' as metric_name,    VARIABLE_VALUE as metric_valueFROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME = 'Queries';-- 定期收集其他关键指标...

总结

通过本篇的学习,我们深入了解了MySQL的核心概念和架构设计:

  1. MySQL的演进历程:从简单的数据库系统发展到功能丰富的企业级解决方案
  2. 分层架构设计:连接层、SQL层、存储引擎层的明确分工
  3. InnoDB的核心地位:作为默认存储引擎的先进特性
  4. 配置优化原则:根据硬件和工作负载进行针对性调优
  5. 安全最佳实践:权限最小化和网络安全配置

关键收获:

  • 理解MySQL的架构有助于更好地进行性能调优和故障排查
  • 合理的配置可以显著提升数据库性能和稳定性
  • 安全配置不是可选项,而是生产部署的必备条件

在接下来的篇章中,我们将深入探讨MySQL的数据类型、表设计、索引优化等高级主题,帮助你构建高性能的数据库应用。

思考与实践:

  1. 在你的环境中安装MySQL 8.0,并尝试不同的配置参数
  2. 使用性能模式监控数据库的运行状态
  3. 设计一个符合最小权限原则的用户权限体系
  4. 建立关键性能指标的监控基线

欢迎在评论区分享你的MySQL配置经验和遇到的问题!

Lucene.Net 分布式索引实现方案

2025年3月5日 10:43

Lucene.Net 本身是一个单机版全文搜索引擎库,不直接支持分布式索引,但通过合理的架构设计,可以实现分布式索引与搜索。以下是常见的分布式索引实现方案及其优缺点:

主从复制

原理

  • 主节点(Master):负责写入索引,定期将索引快照同步到从节点。
  • 从节点(Slave):只读副本,处理查询请求,提升查询吞吐量和可用性。

实现步骤

  1. 主节点写入:所有增删改操作由主节点处理。
  2. 索引同步
    • 方案1:主节点定期将索引文件复制到从节点(如通过 Rsync)。
    • 方案2:通过消息队列(如 RabbitMQ)广播增量变更,从节点实时更新。
  3. 查询负载均衡:查询请求通过负载均衡器分发到多个从节点。

优点

  • 提升读取性能和可用性(从节点可故障转移)。
  • 实现相对简单,无需修改 Lucene.Net 核心逻辑。

缺点

  • 写入能力受限于单主节点,可能成为瓶颈。
  • 同步延迟导致短暂数据不一致。

适用场景

  • 读多写少的场景(如新闻网站、知识库)。

方案2 实现

1. RabbitMQ Fanout 路由模式介绍
1. 核心原理
  • 交换器类型fanout 类型的交换器。
  • 行为规则
    生产者发送到 fanout 交换器的消息,会被复制并分发到所有绑定到该交换器的队列,无论队列绑定时是否指定了路由键。
  • 关键特点
    • 广播机制:消息无差别发送到所有队列,类似“发布-订阅”模式。
    • 路由键无效:消息的路由键(如 user.created)会被忽略,仅交换器类型决定分发行为。

2. 适用场景
  • 广播通知

    • 用户注册成功后,同时发送邮件、短信、站内信(多个消费者独立处理)。
    • 系统配置更新时,通知所有微服务刷新本地缓存。
  • 日志收集
    将日志消息广播到多个处理队列,分别用于实时监控、持久化存储、错误告警等。

  • 事件驱动架构
    解耦事件发布者与订阅者,新增订阅者只需绑定队列,无需修改生产者代码。


3. 对比其他路由模式
模式行为典型场景
Fanout广播到所有绑定队列日志分发、多通知渠道
Direct按精确匹配路由键发送到指定队列订单状态更新、任务分类处理
Topic按通配符匹配路由键(如 user.*复杂事件路由、分类订阅
Headers根据消息头键值对匹配高级路由逻辑(较少使用)

4. 总结

Fanout 模式是 RabbitMQ 中最简单的广播机制,适合需要将同一消息分发给多个消费者的场景。其优势在于快速实现解耦和扩展,但需注意无差别广播可能带来的资源浪费。对于需要精细化路由控制的场景,应选择 directtopic 模式。

2. 技术栈组成
组件作用
Lucene.Net单机版全文搜索核心
RabbitMQ消息广播中间件
MassTransit.NET消息总线框架
Docker容器化部署基础
3. 示意图

image

4. 安装MassTransit.RabbitMQ
5. docker启动RabbitMq服务
# 启动带管理界面的RabbitMQdocker run -d --name rabbitmq \  -p 5672:5672 -p 15672:15672 \  rabbitmq:3-management
6. 示例
services.AddMassTransit(x =>        {            x.AddConsumer<SyncIndexEventConsumer>();            x.UsingRabbitMq((context, cfg) =>            {                cfg.Host("127.0.0.1", $"/",h =>                {                    h.Username("guest");                    h.Password("guest");                });                cfg.Publish<SyncIndexEventConsumer>(x =>                {                    x.ExchangeType = "fanout";                });                // 配置接收端点并绑定到 Fanout 交换机                cfg.ReceiveEndpoint(Guid.NewGuid().ToString(), e =>                {                    e.Bind<SyncIndexEvent>(p =>                    {                        p.ExchangeType = "fanout";                        p.RoutingKey = "";                                         });                    e.ConfigureConsumer<SyncIndexEventConsumer>(context);                    e.PrefetchCount = 50; // 控制消费速率                });            });        });

SyncIndexEventConsumer.cs

public class SyncIndexEventConsumer: IConsumer<SyncIndexEvent>{    public async Task Consume(ConsumeContext<SyncIndexEvent> context)    {        var contextInput = context.Message;        Console.WriteLine("触发索引同步");        await Task.CompletedTask;    }}

SyncIndexEvent.cs

public class SyncIndexEvent{        /// <summary>    /// 表名    /// </summary>    public string TableName { get; set; }        /// <summary>    /// 实际实体对象    /// </summary>    public object Data { get; set; }}

RabbitMQController

[Route("api/[controller]/[action]")][ApiController]public class RabbitMqController : BaseApiController{    private readonly IPublishEndpoint _publishEndpoint;    public RabbitMqController(IPublishEndpoint publishEndpoint)    {        _publishEndpoint = publishEndpoint;    }    /// <summary>    /// 触发全局同步索引    /// </summary>    [HttpGet]    public async Task SyncIndex()    {        await _publishEndpoint.Publish<SyncIndexEvent>(new SyncIndexEvent(), x =>        {            x.Durable = true; // 持久化存储            x.Mandatory = true; // 强制路由            x.SetPriority(1); // 消息优先级        });    }}

方案对比分析

1. 同步机制对比
特性文件复制方案RabbitMQ Fanout方案
实时性分钟级延迟毫秒级延迟
扩展性需手动调整同步脚本动态增删从节点
可靠性依赖文件系统完整性消息持久化+确认机制
资源消耗高(全量复制)低(增量传播)
2. 性能压测数据
# 测试环境:4核8G服务器集群[写入吞吐量]单主节点:1,200 docs/sec从节点扩展:10节点时 8,000 docs/sec[同步延迟]99%请求 < 50ms最大延迟 120ms

Redis 面试题

2022年11月9日 08:33

1.1. 谈下你对 Redis 的了解?

Redis(全称:Remote Dictionary Server 远程字典服务)是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API

1.2. Redis 与其他 key – value 缓存产品有以下特点

  1. Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  2. Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  3. Redis支持数据的备份,即master-slave模式的数据备份。
  4. 使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
  5. value 值大小不同:Redis 最大可以达到 512M;Memcache 只有 1mb

1.3. Redis 一般都有哪些使用场景?

  1. 缓存:减轻 MySQL 的查询压力,提升系统性能
  2. 排行榜:利用 Redis 的 SortSet(有序集合)实现
  3. 计数器/限速器:利用 Redis 中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等。这类操作如果用 MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个 API 的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力
  4. 好友关系:利用集合的一些命令,比如求交集、并集、差集等。可以方便解决一些共同好友、共同爱好之类的功能
  5. 消息队列:除了 Redis 自身的发布/订阅模式,我们也可以利用 List 来实现一个队列机制,比如:到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的 DB 压力,完全可以用 List 来完成异步解耦

1.4. Redis 有哪些常见的功能?

  1. 数据缓存功能

  2. 分布式锁的功能

  3. 支持数据持久化

  4. 支持事务

  5. 支持消息队列

1.5. Redis为什么这么快

  1. 基于内存
  2. 数据结构简单,数据操作简单
  3. 采用单线程
  4. 使用多路I/O复用模型,非堵塞IO

1.6. Redis 数据类型

基础

  • string
  • hash
  • list
  • set
  • zset 即 sorted set

高级

  • HyperLogLog 基数统计
  • Geo 地理位置
  • Bitmap 位存储

1.7. Redis持久化

RDB
RDB( Redis Database ),按照一定的时间周期策略把Redis内存中的数据保存以快照的形式保存到硬盘的二进制文件
。生成dump.rdb 文件

AOF
AOF(Append-only file),将收到的Redis命令以日志的形式追加到文件,类似于MySQL的binlog。
生成.aof 文件

常用持久化模式

RDB+AOF
RDB做镜像全量持久化,AOF做增量持久化

1.8. RDB和AOF的区别

  1. AOF 文件比 RDB 更新频率高,优先使用 AOF 还原数据

  2. AOF比 RDB 更安全也更大

  3. RDB 性能比 AOF 好

  4. 如果两个都配了优先加载 AOF

1.9. Redis常见问题

1.9.1. 缓存雪崩

解释

大量的key过期时间设置过于集中,到过期时间节点,缓存大面积过期,Redis处理短暂卡顿现象,导致大量的查询请求打到数据库,数据库可能宕机

如何处理

  1. 热点数据永不过期
  2. 随机设置缓存过期时间

1.9.2. 缓存穿透

解释

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库

如何处理

  1. 查询参数校验
  2. 查询数据为null 设置缓存 过期时间较短
  3. 布隆过滤器(Bloom Filter)
  4. Nginx网关限制单IP请求数

1.9.3. 缓存击穿

解释

热点key大并发期间突然失效,导致大量请求请求数据库

如何处理

  1. 热点数据永不过期
  2. 互斥锁

1.10. Redis 内存淘汰策略有哪些?

no-enviction 默认Redis淘汰策略

  • volatile-lru:从已设置过期时间的数据集(server. db[i]. expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server. db[i]. expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server. db[i]. expires)中任意选择数据淘汰
  • allkeys-lru:从数据集(server. db[i]. dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server. db[i]. dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据
  • noeviction:达到内存限制时不保存新值。当数据库使用复制时,这适用于主数据库
  • allkeys-lru:保留最近使用的密钥;删除最近最少使用 (LRU) 键
  • allkeys-lfu : 保存常用键;删除最不常用 (LFU) 键
  • volatile-lfuexpire :删除字段设置为的最不常用键true。

1.11. Redis 常见性能问题和解决方案?

  1. Master不做RDB和AOF备份,由Slave开启AOF备份
  2. 主从服务器在同一局域网内
  3. 主从复制不采用树结构,使用单项链表如:Master <- Slave1 <- Slave2 <- Slave3…
  4. 尽量避免在压力很大的主库上增加从库

1.12. Redis如何做内存优化?

  1. 控制RedisKey数量
  2. key值名称长度越短越好
  3. value值内容越短越好

1.13. 缓存预热,缓存降级,缓存更新

缓存预热

缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

缓存预热解决方案:

数据量不大的时候,工程启动的时候进行加载缓存动作;
数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
数据量太大的时候,优先保证热点数据进行提前加载到缓存。

缓存降级

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。

缓存降级解决方案:
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

缓存刷新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰

缓存刷新解决方案:

  • 定时去清理过期的缓存;
  • 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

1.14. 为什么Redis的操作是原子性的,怎么保证原子性的?

对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。

Redis的操作之所以是原子性的,是因为Redis是单线程的。(Redis新版本已经引入多线程,这里基于旧版本的Redis)

Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。

多个命令在并发中也是原子性的吗?

不一定, 将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.

1.15. Redis实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。

将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作

解锁:使用 del key 命令就能释放锁

del lock-key

解决死锁:

  • 通过Redis中expire()给锁设定最大持有时间,如果超过,则Redis来帮我们释放锁。
  • 使用 setnx key “当前系统时间+锁持有的时间”和getset key “当前系统时间+锁持有的时间”组合的命令就可以实现。

1.16. Redis高可用

高可用是通过设计,减少系统不能提供服务的时间,是分布式系统的基础也是保障系统可靠性的重要手段

  • 数据持久化(RDB+AOF)
  • 主从数据同步(主从复制)
  • Redis 哨兵模式(Sentinel)
  • Redis 集群(Cluster)
    其中数据持久化保证了系统在发生宕机或者重启之后数据不会丢失,增加了系统的可靠性和减少了系统不可用的时间(省去了手动恢复数据的过程);而主从数据同步可以将数据存储至多台服务器,这样当遇到一台服务器宕机之后,可以很快地切换至另一台服务器以继续提供服务;哨兵模式用于发生故障之后自动切换服务器;而 Redis 集群提供了多主多从的 Redis 分布式集群环境,用于提供性能更好的 Redis 服务,并且它自身拥有故障自动切换的能力

1.16.1. 数据持久化

RDB+AOF 混合配置

127.0.0.1:6379> config get aof-use-rdb-preamble1) "aof-use-rdb-preamble"2) "yes"

1.16.2. Redis 主从同步

主从同步是 Redis 多机运行中最基础的功能,它是把多个 Redis 节点组成一个 Redis 集群,在这个集群当中有一个主节点用来进行数据的操作,其他从节点用于同步主节点的内容,并且提供给客户端进行数据查询。
Redis 主从同步分为:主从模式和从从模式。
主从模式就是一个主节点和多个一级从节点,而从从模式是指一级从节点下面还可以拥有更多的从节点。
主从模式可以提高 Redis 的整体运行速度,因为使用主从模式就可以实现数据的读写分离,把写操作的请求分发到主节点上,把其他的读操作请求分发到从节点上,这样就减轻了 Redis 主节点的运行压力,并且提高了 Redis 的整体运行速度。
不但如此使用主从模式还实现了 Redis 的高可用,当主服务器宕机之后,可以很迅速的把从节点提升为主节点,为 Redis 服务器的宕机恢复节省了宝贵的时间。
并且主从复制还降低了数据丢失的风险,因为数据是完整拷贝在多台服务器上的,当一个服务器磁盘坏掉之后,可以从其他服务器拿到完整的备份数据。

优点

  • 高可靠性:一方面,采用双机主备架构,能够在主库出现故障时自动进行主备切换,从库提升为主库提供服务,保证服务平稳运行;另一方面,开启数据持久化功能和配置合理的备份策略,能有效的解决数据误操作和数据异常丢失的问题
  • 读写分离策略:从节点可以扩展主库节点的读能力,有效应对大并发量的读操作

缺点

  • 故障恢复复杂,如果没有RedisHA系统(需要开发),当主库节点出现故障时,需要手动将一个从节点晋升为主节点,同时需要通知业务方变更配置,并且需要让其它从库节点去复制新主库节点,整个过程需要人为干预,比较繁琐;

  • 主库的写能力受到单机的限制,可以考虑分片;

  • 主库的存储能力受到单机的限制,可以考虑Pika;

  • 原生复制的弊端在早期的版本中也会比较突出,如:Redis复制中断后,Slave会发起psync,此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时可能会造成毫秒或秒级的卡顿;又由于COW机制,导致极端情况下的主库内存溢出,程序异常退出或宕机;主库节点生成备份文件导致服务器磁盘IO和CPU(压缩)资源消耗;发送数GB大小的备份文件导致服务器出口带宽暴增,阻塞请求,建议升级到最新版本

1.16.3. Redis 哨兵模式

Redis 主从复制模式有那么多的优点,但是有一个致命的缺点,就是当 Redis 的主节点宕机之后,必须人工介入手动恢复,那么到特殊时间段,比如公司组织全体团建或者半夜突然发生主节点宕机的问题,此时如果等待人工去处理就会很慢,这个时间是我们不允许的,并且我们还需要招聘专职的人来负责数据恢复的事,同时招聘的人还需要懂得相关的技术才能胜任这份工作。既然如此的麻烦,那有没有简单一点的解决方案,这个时候我们就需要用到 Redis 的哨兵模式了。
Redis 哨兵模式就是用来监视 Redis 主从服务器的,当 Redis 的主从服务器发生故障之后,Redis 哨兵提供了自动容灾修复的功能.Redis 哨兵模块存储在 Redis 的 src/redis-sentinel 目录.
我们可以使用命令./src/redis-sentinel sentinel.conf来启动哨兵功能。
有了哨兵功能之后,就再也不怕 Redis 主从服务器宕机了。哨兵的工作原理是每个哨兵会以每秒钟 1 次的频率,向已知的主服务器和从服务器,发送一个 PING 命令。如果最后一次有效回复 PING 命令的时间,超过了配置的最大下线时间(Down-After-Milliseconds)时,默认是 30s,那么这个实例会被哨兵标记为主观下线。
如果一个主服务器被标记为主观下线,那么正在监视这个主服务器的所有哨兵节点,要以每秒 1 次的频率确认主服务器是否进入了主观下线的状态。如果有足够数量(quorum 配置值)的哨兵证实该主服务器为主观下线,那么这个主服务器被标记为客观下线。此时所有的哨兵会按照规则(协商)自动选出新的主节点服务器,并自动完成主服务器的自动切换功能,而整个过程都是无须人工干预的。

1.16.4. Redis 集群

Redis 集群也就是 Redis Cluster,它是 Redis 3.0 版本推出的 Redis 集群方案,将数据分布在不同的主服务器上,以此来降低系统对单主节点的依赖,并且可以大大提高 Redis 服务的读写性能。Redis 集群除了拥有主从模式 + 哨兵模式的所有功能之外,还提供了多个主从节点的集群功能,实现了真正意义上的分布式集群服务.
Redis 集群可以实现数据分片服务,也就是说在 Redis 集群中有 16384 个槽位用来存储所有的数据,当我们有 N 个主节点时,可以把 16384 个槽位平均分配到 N 台主服务器上。当有键值存储时,Redis 会使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位,再把此键值存储在对应的服务器上,读取操作也是同样的道理,这样我们就实现了数据分片的功能。

Redis脑裂

某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着,此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master。这个时候,集群里就会有两个master,也就是所谓的脑裂。此时虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧master的数据可能也丢失了。因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据。 Redis

未完待续ing…

博客文章TODO

2022年11月4日 08:13
  • Asp.Net Core Swagger Api 分组和控制器注释
  • Asp.Net Core 全局请求返回封装
  • 编程工具集(updating)
  • 个人能力掌握(updating)
  • .Net Core Jenkins 正式版本回滚+发布
  • Flutter快速入门
  • 快速搭建kubernetes+kubernetes dashboard学习环境
  • 踩坑:.Net Core 项目使用 XMLRPC
  • Asp.Net Core 使用Serilog记录日志
  • Asp.Net Core 基于WorkFlow的工作流引擎
  • 踩坑 XMLRPC 踩坑实战 进行中
  • Asp.Net Core 全局请求Log
  • Asp.Net Core 全局错误拦截
  • Asp.Net Core 使用AutoFac注册服务
  • Asp.Net Core封装简单EntityFrameworkCore
  • Asp.Net Core封装简单的Redis操作库
  • Asp.Net Core AutoWrapper库 快速入门
  • Asp.Net Core AutoMapper使用
  • Asp.net Core分布式系统的日志收集,跟踪
  • Asp.net Core GRPC网关
  • Asp.net Core Identity Server 4实战
  • Asp.net Core消息队列
  • Asp.net Core订阅发布
  • Asp.net Core Singlr简单使用
  • Asp.net Core性能检测(分布式)
  • Redis集群踩坑
  • Redis集群部署
  • Redis快速入门
  • 2021程序人生年终总结
  • DDD项目实践
  • .Net Core 面试题更新中
  • Redis 面试题
  • RabbitMQ 面试题
  • MySQL 面试题
  • Docker 快速入门

.Net Core常见面试题

2022年11月4日 07:41

1. .Net Framework和.Net Core的区别

2. 什么是ASP.NET Core?

首先ASP.NET Core可以说是 ASP.NET的升级版本。它遵循了.NET的标准架构,是一个基于.NET Core的

Web开发框架, 可以运行于多个操作系统上。它更快,更容易配置,更加模块化,可扩展性更强。

3. Asp.Net Core框架优点

  • 依赖注入
  • 日志系统框架
  • 使用start up来注册服务 .Net6 已弃用
  • 更好的支持异步编程
  • signal IR,更好的支持WebSocket
  • 内置kestrel内核脱离IIS运行项目

4. 依赖注入

依赖注入是ASP.Net Core的核心,依赖注入我们可以分开来理解
依赖:当一个类需要另一个类协作来完成工作的时候就产生了依赖
注入:把依赖的创建丢给其它人,自己只负责使用,其它人丢给你依赖的这个过程理解为注入。

5. 依赖注入生命周期

  • Transient 每一次GetService都会创建一个新的实例 services.AddTransient<>()
  • Scoped 在同一个Scope内只初始化一个实例 ,可以理解为( 每一个request级别只创建一个实例,同一个http request会在一个 scope内) services.AddScoped<>()
  • Singleton 整个应用程序生命周期内只创建一个实例 services.Singleton<>()

6. IOC

IOC容器就是一个工厂,负责创建对象的
IOC控制反转:只是把上端对下端的依赖,换成第三方容器决定

7. 值类型和引用类型

基于值类型的变量直接包含值。将一个值类型变量赋给另一个值类型变量时,将复制包含的值。这与引用类型变量的赋值不同,引用类型变量的赋值只复制对对象的引用,而不复制对象本身。所有的值类型均隐式派生自 System.ValueType。与引用类型不同,从值类型不可能派生出新的类型。但与引用类型相同的是,结构也可以实现接口。与引用类型不同,值类型不可能包含 null值。然而,可空类型功能允许将 null 赋给值类型。每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。

值类型主要由两类组成:结构、枚举,结构分为以下几类:Numeric(数值)类型、int型(整型)、float型(浮点型)、decimal​bool​(布尔型)、用户定义的结构。引用类型的变量又称为对象,可存储对实际数据的引用。声明引用类型的关键字:classinterfacedelegate、内置引用类型:objectstring

8. 序列化与反序列化

序列化:将对象状态转换为可保持或传输的格式的过程。将对象实例的字段及类的名称转换成字节流,然后把字节流写入数据流

反序列化:将流转换为对象

9. 实现多态的过程中overload 重载与override 重写的区别

overload 重载是方法的名称相同,参数或参数类型不同,进行多次重载以适应不同的需要。

Override 是进行基类中函数的重写,实现多态。

10. 一列数的规则如下: 1、1、2、3、5、8、13、21、34… 求第30位数是多少,用递归算法实现

public class MainClass {    public static void Main(){        Console.WriteLine(Foo(30));          }    public static int Foo(int i){        if (i <= 0) return 0;        else if(i > 0 && i <= 2) return 1;        else return Foo(i - 1) + Foo(i - 2);    }}

11. 请编程实现一个冒泡排序算法?

int [] array = new int [*];int temp = 0;for(int i = 0; i<array.Length-1; i++){    for(int j = i+1 ; j < array.Length ; j++){        if (array[j] < array[i]){            temp = array[i] ;            array[i] = array[j] ;            array[j] = temp ;        }      }}

12. AsNotracking的作用

EF Core 默认情况下对数据的访问都是启用模型跟踪

在第一次对象加载到内存中时进行一次快照,添加快照发生在返回一次查询或添加一个对象到DbSet中时。当Entity Framework需要知道对象的变动时,将先把当前实体与快照中的对象进行扫描对比。实现扫描对比的方法是调用DbContext.ChangeTracker的DetectChanges方法

变动跟踪代理:变动跟踪代理是一种会主动通知Entity Framework实体对象发生变动的机制。如:延迟加载的实现方式。要使用变动跟踪代理,需要在定义的类结构中,Entity Framework可以在运行时从POCO类中创建动态类型并重写POCO属性。动态代理就是一种动态类型,包含重写属性和通知Entity Framework实体对象变动的逻辑
  大部分的实例对象的变动调整需要在Entity Framework进行SaveChanges时才会知道,但也可以根据需要调用变动跟踪获取当前对象的状态。

Entity Framework Code FirstDbContext.DetectChanges在检测实例对象的变动时,大部分情况不会有性能的问题。但当有大量的实例对象在内存中,或DbContext有大量的操作时,自动的DetectChanges行为可能会一定程度的影响性能。

使用AsNoTracking方法查询返回无变动跟踪的Province的DbSet,由于是无变动跟踪,所以对返回的Province集中数据的任何修改,在SaveChanges()时,都不会提交到数据库中。

使用AsNoTracking()方法注意:

只能用于查询,不能把查询后的实体用作其他的用途,不然会导致其他的错误。例如:我想查询数据库有没有一条数据,用AsNotracking()查询后,如果有就把他赋值给一个实体,如果没有就new一个新实体 。这样的话,因为AsNotracking()没有把这个实体加到EF跟踪里面,EF就会认为这个实体在数据库不存在,就会把查询后的实体添加到数据库,但是实际情况是这个实体是存在于数据库里面的,这样就会报数据重复的错误。所以使用AsNotracking()只能用于查询,不能用于其他赋值的操作。

13. 在c#中using和new这两个关键字有什么意义,请写出你所知道的意义?using 指令和语句 new 创建实例 new 隐藏基类中方法。

using:引入名称空间或者使用非托管资源,使用完对象后自动执行实现了IDisposable接口的类的Dispose方法

new:新建实例或者隐藏父类方法

14. 什么是虚函数?什么是抽象函数?

虚函数:没有实现的,可由子类继承并重写的函数。​virtual CallSomeOne();​

抽象函数:规定其非虚子类必须实现的函数,必须被重写。​public abstract void CallSomeOne(); ​

15. 委托关键词

delegate 委托是一种定义方法签名的类型。当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联。您可以通过委托实例调用方法。

16. 事件关键词

event
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。

发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。

订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。

17. 不允许集成类

类声明时标记sealed

18. 常见名词

  • CLR Common Language Runtime 公共语言运行库

  • CTS Common Type System 通用类型系统

  • CLS Common Language Specification 通用语言规范

  • CIL Common Intermediate Language 公共中间语言

  • CLI Common Language Infrastructure 公共语言基础架构

  • BCL Base Class Library基础类库

  • FCL Framework Class Library 框架类库

  • JIT Just In Time .Net边运行边编译的一种机制

19. 什么是继承?什么是重载?

继承指子类可以拥有父类允许访问的属性、方法等。重载是一个函数通过参数类型或者数量区分不同的实现。

20. 什么是SQL注入,如何防止SQL注入攻击?

攻击者通过接口参数注入sql查询语句,代码没有过滤特殊字符时,在执行sql语句处填入’;等之类的字符来截断sql语句后再输入要执行的sql语句(删表等)来对数据库进行攻击。可以使用SQLParameter(把所有参数当字符串而不是关键字来处理)或者ORM来避免。

21. 简单存储过程示例

create proc delTable@tname char(20) --入参asdeclare @cmd char(50)='truncate table '+@tnameexec(@cmd)exec delTable '表名'

22. 常用的设计模式

  • 创建型
    1. 单例
    2. 原型
    3. 抽象工厂
    4. 工厂
    5. 建造者
  • 结构性
    1. 适配器
    2. 代理
    3. 策略
  • 行为型
    1. 责任链
    2. 观察者
    3. 模板方法

23. 装箱和拆箱

值类型到object是装箱,object到值类型是拆箱

24. Override 和重载的区别?

override是重写方法的关键字,重载指一个方法的不同实现

25. 面向对象的三大基本原则?

封装,继承,多态。

26. string str = null,string str = "",string.Empty 的区别?

null表示不存在于内存中没有地址,""表示存在于内存中的""这个对象,string.Empty 固定内存中存放的空字符串

27. 进程和线程的区别

进程是系统进行资源分配和调度的单位;线程是CPU分配和调度的单位。一个进程可以有多个线程,这些线程共享这个进程的资源。
(进程是系统层面,线程是CPU层面。线程∈进程)

28. 进程和线程分别该怎么理解?

进程是比线程大的程序运行单元,都是由 操作系统所体会的系统运行单元,一个程序中至少要有一个进程,有一个进程中,至少要有一个线程,线程的划分尺度要比进程要小,进程拥有独立的 内存单元,线程是共享内存,从而极大的提高了程序的运行效率同一个进程中的多个线程可以并发执行。

29. 当一个线程进入一个对象的方法后,其它线程是否可以进入该对象的方法?

不可以,一个对象的方法只能由一个线程访问。

30. 线程同步

  • 阻塞(调用Sleep()或Join())

  • 加互斥锁lock

  • 信号和句柄(AutoResetEvent/ManualResetEvent,调用Set()和WaitOne())

31. 描述Class和Struct异同?

Class属于引用类型,是分配在内存的堆上的;

Struct属于值类型,是分配在内存的栈上的;不能从另外一个结构或者类继承,本身也不能被继承;没有默认的构造函数,但是可以添加构造函数;可以不使用new 初始化

32. GC是什么?为什么需要GC?

GC(Garbage Collector)是垃圾收集器。.net中垃圾收集器会自动对内存垃圾进行回收管理,因为有了CLR的GC机制。正常情况下,.net会自动的帮忙释放内存,如果非托管代码,则需要手动释放,比如datareadWebRequest

33. try里有一个return语句,那么finally中的代码会不会执行,在return前还是return后?

会,在return前执行。

34. 堆和栈的区别?

栈上放的是由编译器自动分配释放的资源。堆上放的是代码中自己分配释放的资源,比如new对象时分配的内存空间

35. 什么是反射?

程序运行时动态获取程序集信息:接口、类、方法、属性等

36. 概述反射和序列化?

反射:要给发射下一个定义还是比较难的,这里先说说我的理解。反射提供了封装程序集,模块和类型对象,可以用反射动态地创建类型的实例,将类型绑定到现有对象,或者从现有对象类型里获取类型,然后调用类型的方法或访问字段和属性。
序列化:将对象转换为另一种媒介传输的格式过程。如,序列化一个对象,用Http通过internet在客户端和 服务器之间传递该对象,在另一端用反序列化从该流中重新得到对象

37. .NET中lock关键字根据什么来产生边界(线程同步)?

静态变量 (static object o=new Object();

38. 如何全局处理异常?

使用中间件(Middlware)记录处理异常,或者使用ExceptionFilter来捕获全局异常

39. 泛型的内部机制?

List<T>为例,第一次编译时只是为T生成一个占位符。在实际用到时比如List<User>时,JIT(Just-In-Time即时编译器)会用User去代替T的占位符实例化User,以此来实现泛型

40. a=“123”; b=“123”;内存怎么分布,b=a时呢?

"123"由于CLR的字符串驻留机制只存有一份。所以当a="123";b="123";时a和b都指向了"123"这个字符串的引用。
b=a时是把a的引用copy了一份给b

41. 什么是 Code First ,Data First, Model First

  • Code First 这种方式需要先写一些代码,如实体对象数据关系等,然后根据已有的代码描述,自动创建数据对象。
  • Data First 基于已存在的数据库,利用某些工具(如VS提供的EF设计器)创建实体类数据库对象实体类匹配关系等,你也可以手动修改这些自动生成的代码及匹配文件。也就是从一个数据库开始,然后生成实体框架和相应代码
  • Model First 利用某些工具(如VS的EF设计器)设计出可视化实体数据模型及他们之间的关系,然后再根据这些实体关系去生成数据库对象相关代码文件

42. 说说对EFCore中EntityState的理解

因为EFCore对于数据库的所有操作都是通过上下文DbContext来完成的,且是通过SaveChanges方法统

一落实到数据库中去的;EntityStateEFCore 在对数据库操作增删改的时候,记录当前被操作的数据对

象和Context的关系,针对与不同的操作,对应的一个状态信息,一共五种状态

  • Detached = 0, 当前对象和context没有任何关系,没有被上下文跟踪

  • Unchanged = 1, 当前对象被context跟踪,数据没有做任何修改

  • Deleted = 2, 当前对象被context跟踪,且标记是数据删除,调用SaveChanges后将会从数据中删

除;

  • Modified = 3, 当前对象被context跟踪,且有属性数据被修改过,调用SaveChanges后将会从数据中

修改;

  • Added = 4 , 当前对象被context跟踪,且数据并没有存在数据库中,调用SaveChanges后将会新增

到数据库中去;

43. 说说什么是EF Core中导航属性和引用属性

EF Core导航属性分为三种:

  • 集合导航属性:主表中对子表相关数据的引用
  • 引用导航属性:子表中对主表数据的引用
  • 反转导航属性:一个导航属性对应的另一端的导航属性

44. 请问对GRPC有了解吗,说说GRPC

有了解,说GRPC可以先说RPC,PRC:所谓RPC(Remote Procedure Call 远程过程调用)框架实际是提供了

一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用

server端提供的接口就像是调用本地的函数一样。

所谓GRPC 是由谷歌开发的一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供

45. GRPC有几种模式?

四种模式:

  1. 简单模式:简单模式只是使用参数和返回值作为服务器与客户端传递数据的方式,最简单。

  2. 客户端流模式:即从客户端往服务器端发送数据使用的是,即服务器端的参数为流类型,然而在服

务器相应后返还数据给客户端,使用的也是流的send方法。一般在服务器端的代码,需要先recv再

send,而客户端与此相反。但是在后面的双向模式中可以使用go的协程协作。

  1. 服务器端流模式:即服务器端返回结果的时候使用的是流模式,即传入的数据是通过参数形式传入

的。但是在往客户端发送数据时使用send方法,与客户端返回数据的方式大同小异。

  1. 双向模式:客户端如果不适用协程,那么发送必须在接收之前。如果使用协程,发送与接收并没有先

后顺序。为了保证协程的同步,可以使用互斥量进行约束。

46. GPRC作为一种被调用的服务,有什么保护安全的措施吗?

可以使用授权验证(JWT.....),无论是对称可逆加密还是非对称可逆加密,都是可以支持的;

47. 什么是AOP

AOP(Aspect Oriented Programming)面向切面编程,它把系统需求按照功能分门归类,把它们封装在一个个切面中,然后再指定这些系统功能往业务功能中注入的规则。最后由第三方机构根据你指定的注入规则,将系统功能整合到业务功能中

48. .NET垃圾回收

.NET 的垃圾回收器管理应用程序的内存分配和释放。 每当有对象新建时,公共语言运行时都会从托管堆为对象分配内存。 只要托管堆中有地址空间,运行时就会继续为新对象分配空间。 不过,内存并不是无限的。 垃圾回收器最终必须执行垃圾回收来释放一些内存。 垃圾回收器的优化引擎会根据所执行的分配来确定执行回收的最佳时机。 执行回收时,垃圾回收器会在托管堆中检查应用程序不再使用的对象,然后执行必要的操作来回收其内存。

名词 解释
垃圾回收的基本知识 描述垃圾回收的工作原理、如何在托管堆上分配对象,以及其他核心概念
工作站和服务器垃圾回收 描述了客户端应用的工作站垃圾回收与服务器应用的服务器垃圾回收之间的区别
后台垃圾回收 描述了后台垃圾回收,它是在进行第二代回收时对第 0 代和第 1 代对象的回收
大型对象堆 描述了大型对象堆 (LOH) 及其垃圾回收方式
垃圾回收和性能 介绍了可用来诊断垃圾回收和性能问题的性能检查
已引发回收 描述如何完成垃圾回收
延迟模式 描述确定垃圾回收侵入性的模式
针对共享 Web 承载优化 介绍了如何在多个小网站共用的服务器上优化垃圾回收
垃圾回收通知 介绍了如何确定全面垃圾回收的开始时间和结束时间
应用程序域资源监视 介绍了如何监视应用程序域的 CPU 和内存使用情况
弱引用 描述允许应用程序访问对象的同时也允许垃圾回收器收集相应对象的功能

参考链接:描述垃圾回收的工作原理、如何在托管堆上分配对象,以及其他核心概念。

49. .NET中如何实现深拷贝(deep copy)?

实现ICloneable接口

50. .NET 中ICloneable的作用是?

详细阅读:https://docs.microsoft.com/zh-cn/dotnet/api/system.icloneable?view=net-6.0

支持克隆,即用与现有实例相同的值创建类的新实例。

ICloneable接口使您能够提供创建现有对象的副本的自定义实现。 ICloneable接口包含一个成员,即 Clone 方法,该方法旨在提供超出提供的克隆支持 Object.MemberwiseClone 。 有关克隆、深层副本和示例的详细信息,请参阅 Object.MemberwiseClone 方法

51. IOC&DI相关

IServiceCollection负责服务注册

IServiceProvider(一个内置的服务容器)负责提供实例。

52. 什么是ASP.NET Core中间件?

中间件指的是注入到应用中处理请求和响应的的组件,而这个过程我们称之为请求管道。

每个组件:

选择是否将请求传递到管道中的下一个组件。

可在管道中的下一个组件前后执行工作。

请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

使用 Run、Map 和 Use 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。

53. 中间件注册的三种方法?

Run(),使用Run调用中间件的时候,会直接返回一个响应,所以后续的中间件将不会被执行了。

Use(),它会对请求做一些工作或处理,例如添加一些请求的上下文数据,有时候甚至什么也不做,直接把请求交给下一个中间件。

Map(),它会把请求重新路由到其它的中间件路径上去。

54. 中间件执行顺序

参考资料
下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。 你可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。
middleware-pipeline

55. .NET Core中的五大过滤器及其用途

55.1. 授权过滤器(AuthorizeFilter

(1) 说明:它是过滤器管道中第一个过滤器,控制对方法的访问权限及授权策略

(2) 实现:继承Attribute类,实现IAuthorizationFilter接口,重写OnAuthorization方法。

注:继承Attribute类的目的是可以该过滤器以特性的形式作用于Controller或Action,下面过滤器都类似,不再说明。

(3).用途:通常用来做权限校验。

55.2. 资源过滤器(IResourceFilter

(1) 说明:只有授权过滤器在资源过滤器之前运行,里面的OnResourceExecuting重写是在创建控制器调用的。

(2) 实现:继承Attribute类,实现IResourceFilter接口,重写OnResourceExecuting 和 OnResourceExecuted方法。

(异步的话实现IAsyncResourceFilter接口,重写OnResourceExecutionAsync方法)

(3) 用途:做一些对变化要求不高的页面的缓存。

55.3. 操作、行为过滤器(ActionFilter

(1) 说明:分别在操作方法之前和之后执行

(2) 实现:继承Attribute类,实现IActionFilter接口,重写OnActionExecuting 和 OnActionExecuted方法。 或者直接继承ActionFilterAttribute类,观察源码可知,该类继承了Attribute类,而且还实现IActionFilter,IResultFilter接口。(异步的话实现IAsyncActionFilter接口,重写OnActionExecutionAsync方法)

55.4. 结果过滤器(ResultFilter

(1) 说明:在方法执行前后,且操作过滤器之后;结果(如:页面渲染)的前后运行。

(2) 实现:继承Attribute类,实现IResultFilter接口,重写OnResultExecuting 和 OnResultExecuted方法。 或者直接继承ResultFilterAttribute类,(或ActionFilterAttribute类), 观察源码可知,该类继承了Attribute类,而且还实现IResultFilter接口。(异步的话实现IAsyncActionFilter接口, 重写OnActionExecutionAsync方法) 还可以实现:IAlwaysRunResultFilter 或 IAsyncAlwaysRunResultFilter 接口。

(3).用途:可以获取action的返回结果,进行一些处理,比如:根据要求返回json数据或jsonp数据(详见cors章节)。

55.5. 异常过滤器(ExceptionFilter

(1) 说明:用于实现常见的错误处理策略,没有之前和之后事件,处理 Razor 页面或控制器创建、模型绑定、操作过滤器或操作方法中发生的未经处理的异常。 但无法捕获资源过滤器、结果过滤器或 MVC 结果执行中发生的异常 。

(2) 实现:继承Attribute类,实现IExceptionFilter接口,重写OnException方法。 或者直接继承ExceptionFilterAttribute类,观察源码可知,该类继承了Attribute类,而且还实现IExceptionFilter接口。(异步的话实现 IAsyncExceptionFilter接口,重写OnExceptionAsync方法)

(3) 用途:全局捕获异常,进行相关处理。

56. ASP.NET Core Filter的注册方式有哪些?

  • 方法注册:只对方法生效

  • 控制器注册:对控制器中的所有方法生效

  • 全局注册:对整个项目生效;

57. Startup类中两个方法的作用? .Net6 中已弃用类

ConfigureServices()方法配置应用程序所需的服务

Configure()方法配置应用程序的请求处理管道

58. Async,await 详解

C#异步编程入门看这篇就够了

PS 未完待续…

欢迎补充

代理下载工具

2022年10月24日 09:56

代理下载工具

项目地址

proxtWebDownlaod

在线使用

代理下载工具

为什么要做这个项目

我们在下载很多国外资源的时候,会遇到只有几kb的网速,导致我们下载东西过慢或者无法下载下来,由此我做了一个代理下载的web网站

这里我们下载 GithubDesktop 软件 ,在没有挂代理的情况下,133kb/s 预计下载完15分钟 ,软件本体 为121兆,可想而知我们在下载大文件的时候,速度该有多慢

如何使用

正在下载的任务右键复制链接 粘贴到地址栏中
image
点击开始下载即可
image-1666526120923

下载完点击链接 即可下载
image-1666526129078

原理

通过代理的方式先把文件下载到国内服务器,再去下载国内的文件比直接下载国外的文件快

JavaScript字符串每N位截取一次

2022年8月31日 06:03

目的

针对二进制流进行参数分割

简洁实现

代码

/.{1,3}/g 正则中的3 为匹配几位,可修改

var foo = "foofaafoofaafoofaafoofaafoofaa";console.log( foo.match(/.{1,3}/g) );

效果
059a96b9b20c3d59b3e8ddbb6ffabf1

错误示范

第一种

function splitStr(str) {  const res = []  let count = 0  let tmpStr = ''  for (const c of str) {    tmpStr += c    count++    if (count == 3) {      res.push(tmpStr)      count = 0      tmpStr = ''    }  }  tmpStr && res.push(tmpStr)  return res}

第二种

function fenge(str){    let result=str.replace(/(\d)(?=(\d{3})+$)/g,"$1,");    return result.split(',');}var str="001010101";console.log(fenge(str));

第三种

function splitMsg(str){    let allmsg = []    try{        while(str.length > 0){            allmsg.push(str.substring(0,3))            str = str.substring(3)        }    }catch(e){        console.log(e)    }    return allmsg;}console.log(splitMsg("000101110"))

Asp.Net Core 使用Serilog记录日志

2022年8月13日 08:57

Serilog简介

Serilog是Asp.Net Core 新兴的日志框架,本文这里简单的介绍一下它的用法以及对他进行简单的封装
官网

Serilog配置

安装基础库

  • Install-Package Serilog
  • Install-Package Serilog.Sinks.Console
    其中包Serilog是Log核心库,Serilog.Sinks.Console是Log的控制台输出库,这个也是日志框架的一贯策略,一个核心库加多个输出库组合使用,这样可以保持良好的扩展性。

简单的示例:

using (var log = new LoggerConfiguration()           .WriteTo.Console()           .CreateLogger()){    log.Information("Hello, world!");    log.Warning("Goodbye, world.");    log.Error("Goodbye, world.");}

image-1660302334737

Serilog介绍

LoggerConfiguration

这里用了一个LoggerConfiguration对象,它主要用于创建和设置Log对象,类似于Nlog里面的LogManager类。这里主要用它两个方法:

  • WriteTo:WriteTo属性用来设置日志的输出,Serilog将其称为Sink(水槽),还是比较形象的。
  • CreateLogger:用于创建一个ILogger类型的Logger对象.

Serilog封装

 public static class SerilogHostingExtensions    {        /// <summary>        /// 加载Serilog配置文件        /// </summary>        /// <returns></returns>        public static IConfiguration AddDefaultConfiguration()        {            var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");            return new ConfigurationBuilder()            .SetBasePath(Directory.GetCurrentDirectory())            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)            .AddJsonFile($"appsettings.{environmentName}.json", optional: false, reloadOnChange: true)            .AddEnvironmentVariables()            .Build();        }        /// <summary>        /// 使用Serilog日志记录 以文件的方式记录        /// </summary>        /// <param name="hostBuilder"></param>        /// <returns></returns>        public static IWebHostBuilder UseSerilogDefault(this IWebHostBuilder hostBuilder)        {                        var config = AddDefaultConfiguration();            Log.Logger = new LoggerConfiguration()                .ReadFrom.Configuration(config)                .WriteTo.Console()                .CreateLogger();            hostBuilder.UseSerilog();            return hostBuilder;        }        /// <summary>        /// 获取log文件        /// </summary>        /// <param name="config"></param>        /// <param name="defaultValue"></param>        /// <returns></returns>        public static string GetLogFile(this IConfiguration config, string defaultValue)        {            return config.GetValue<string>("LogFile", defaultValue);        }    }

Program.cs

//注入日志builder.WebHost.UseSerilogDefault();

如何使用

ILogger

ILogger对象用于记录日志,和其他日志框架差不多。Serilog日志级别分为如下5级

  • Verbose
  • Debug
  • Information
  • Warning
  • Error
  • Fatal
    大多数的日志也是这样5级,只是有的名称叫的不同(NLog第1级叫Trace,其它的一致),每一级别对应一个写Log的函数:

全局Logger对象

在实际的使用过程中,往往并不是每次使用都去创建一个ILogger,一种方式是通过依赖注入的方式创建一个全局的Logger。不过这种方式需要引入DI框架。在小程序中使用不算方便。

另一种方式是直接使用静态的Log类,它也携带了写入日志的方法,用起来非常方便。
image-1660303085369

常见存储格式

Serilog的输出对象称之为Sink(水槽),github上提供了大量的第三方的可用sinks,这里简单的列举几个常用的:

  • Console 输出到控制台
  • Debug 输出到VS的Debug窗口
  • File 输出到文件
  • MongoDB 输出到MongoDB
  • LiteDB 输出到文件数据库LiteDB
  • SQLite 输出到文件数据库SQLite
  • SignalR 输出为SignalR服务
  • HTTP 输出到REST服务
  • Loki 输出Loki分布式分布式日志框架
  • Elasticsearch 输出到ELK分布式日志框架

输出格式配置

Serilog的日志输出通过LoggerConfiguration类配置,详细的配置参数可以参看官方文档:Configuration Basics。在日常使用中,感觉更多的是直接通过LoggerConfiguration在代码中配置。

输出到文件中

安装扩展

  • Install-Package Serilog.Sinks.File
        /// <summary>        /// 使用Serilog日志记录 以Loki分布式的方式记录        /// </summary>        /// <param name="hostBuilder"></param>        /// <returns></returns>        public static IWebHostBuilder UseSerilogDefault(this IWebHostBuilder hostBuilder)        {                        var config = AddDefaultConfiguration();            Log.Logger = new LoggerConfiguration()                .ReadFrom.Configuration(config)                .WriteTo.Console()                .WriteTo.File(config.GetLogFile("logs/.log"), rollingInterval: RollingInterval.Day)                .CreateLogger();            hostBuilder.UseSerilog();            return hostBuilder;        }

配合Loki使用

安装扩展

  • Install-Package Serilog.Sinks.Grafana.Loki
  • Install-Package Serilog.Settings.Configuration
        /// <summary>        /// 使用Serilog日志记录 以Loki分布式的方式记录        /// </summary>        /// <param name="hostBuilder"></param>        /// <returns></returns>        public static IWebHostBuilder UseSerilogDefault(this IWebHostBuilder hostBuilder)        {                        var config = AddDefaultConfiguration();            Log.Logger = new LoggerConfiguration()                .ReadFrom.Configuration(config)                .WriteTo.Console().Enrich.FromLogContext()    .WriteTo.GrafanaLoki(        "http://localhost:3100")                .CreateLogger();            hostBuilder.UseSerilog();            return hostBuilder;        }

json配置文件形式

{  "Serilog": {    "Using": [      "Serilog.Sinks.Grafana.Loki"    ],    "MinimumLevel": {      "Default": "Debug"    },    "WriteTo": [      {        "Name": "GrafanaLoki",        "Args": {          "uri": "http://localhost:3100",          "labels": [            {              "key": "app",              "value": "web_app"            }          ],          "propertiesAsLabels": [            "app"          ]        }      }    ]  }}

配合ELK使用

安装扩展

  • Install-Package serilog.formatting.elasticsearch
  • Install-Package serilog.sinks.elasticsearch
  • Install-Package Serilog.Settings.Configuration
        /// <summary>        /// 使用Serilog日志记录 以ELK分布式的方式记录        /// </summary>        /// <param name="hostBuilder"></param>        /// <returns></returns>        public static IWebHostBuilder UseSerilogDefault(this IWebHostBuilder hostBuilder)        {                        var config = AddDefaultConfiguration();            Log.Logger = new LoggerConfiguration()                .ReadFrom.Configuration(config)                .WriteTo.Console().WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200") ){             AutoRegisterTemplate = true,             AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6     })                .CreateLogger();            hostBuilder.UseSerilog();            return hostBuilder;        }

json配置项

        /// <summary>        /// 使用Serilog日志记录 以ELK分布式的方式记录        /// </summary>        /// <param name="hostBuilder"></param>        /// <returns></returns>        public static IWebHostBuilder UseSerilogDefault(this IWebHostBuilder hostBuilder)        {            var config = new ConfigurationBuilder()    .SetBasePath(env.ContentRootPath)    .AddJsonFile("appsettings.json")    .Build();            Log.Logger = new LoggerConfiguration()                .ReadFrom.Configuration(config)                .WriteTo.Console().Enrich.FromLogContext()            hostBuilder.UseSerilog();            return hostBuilder;        }var logger = new LoggerConfiguration()    .ReadFrom.Configuration(configuration)    .CreateLogger();
{  "Serilog": {    "WriteTo": [{        "Name": "Elasticsearch",        "Args": {          "nodeUris": "http://localhost:9200;http://remotehost:9200/",          "indexFormat": "custom-index-{0:yyyy.MM}",          "templateName": "myCustomTemplate",          "typeName": "myCustomLogEventType",          "pipelineName": "myCustomPipelineName",          "batchPostingLimit": 50,          "batchAction": "Create",          "period": 2,          "inlineFields": true,          "restrictedToMinimumLevel": "Warning",          "bufferBaseFilename":  "C:/Temp/docker-elk-serilog-web-buffer",          "bufferFileSizeLimitBytes": 5242880,          "bufferLogShippingInterval": 5000,          "bufferRetainedInvalidPayloadsLimitBytes": 5000,          "bufferFileCountLimit": 31,          "connectionGlobalHeaders" :"Authorization=Bearer SOME-TOKEN;OtherHeader=OTHER-HEADER-VALUE",          "connectionTimeout": 5,          "emitEventFailure": "WriteToSelfLog",          "queueSizeLimit": "100000",          "autoRegisterTemplate": true,          "autoRegisterTemplateVersion": "ESv2",          "overwriteTemplate": false,          "registerTemplateFailure": "IndexAnyway",          "deadLetterIndexName": "deadletter-{0:yyyy.MM}",          "numberOfShards": 20,          "numberOfReplicas": 10,          "templateCustomSettings": [{ "index.mapping.total_fields.limit": "10000000" } ],          "formatProvider": "My.Namespace.MyFormatProvider, My.Assembly.Name",          "connection": "My.Namespace.MyConnection, My.Assembly.Name",          "serializer": "My.Namespace.MySerializer, My.Assembly.Name",          "connectionPool": "My.Namespace.MyConnectionPool, My.Assembly.Name",          "customFormatter": "My.Namespace.MyCustomFormatter, My.Assembly.Name",          "customDurableFormatter": "My.Namespace.MyCustomDurableFormatter, My.Assembly.Name",          "failureSink": "My.Namespace.MyFailureSink, My.Assembly.Name"        }    }]  }}

参考文章

Serilog简介

uniapp 原生echarts组件封装

2022年7月22日 07:08

在混合开发中遇到了ucharts无法实现的图表

ucharts

散点图

image-1660300716540

盒须图

image

热力图

image-1660300740192

为了兼容IOS,安卓,H5 并展示图片 ,只能通过uniapp 特定的renderjs语法来实现

代码实现

引入包

image-1660300893171

echarts.js
ecStat.min.js

echarts.vue

<template><view class="content"><!-- #ifdef APP-PLUS || H5 --><view @click="echarts.onClick" :prop="optionData" :moduleParamProp="moduleParam":change:moduleParamProp="echarts.moduleParamUp" :change:prop="echarts.updateEcharts" :id="moduleParam.id"class="echarts"></view><!--<button @click="changeOption">更新数据</button>--><!-- #endif --><!-- #ifndef APP-PLUS || H5 --><view>非 APP、H5 环境不支持</view><!-- #endif --></view></template><script>export default {data() {return {}},emits: ["getClickData"],props: {moduleParam: {type: Object,default: () => {id: "myCharts";width: "100%";height: "300rpx";type: ""}},optionData: {type: Object,default: () => {}}},onLoad() {},methods: {changeOption() {// 父组件刷新数据// this.$emit("changeOption")},onViewClick(options) {this.$emit("getClickData", options)}}}</script><script module="echarts" lang="renderjs">let myChartexport default {data() {return {clickData: null}},mounted() {if (typeof window.echarts === 'function') {this.initEcharts()} else {// 动态引入较大类库避免影响页面展示const script = document.createElement('script')// view 层的页面运行在 www 根目录,其相对路径相对于 www 计算script.src = 'static/js/echarts.js'script.onload = this.initEcharts.bind(this)document.head.appendChild(script)if(this.moduleParam.type === "scatter"){const ecStat = document.createElement("script")ecStat.src = 'static/js/ecStat.min.js'ecStat.onload = this.initEcharts.bind(this)document.head.appendChild(ecStat)}}},methods: {initEcharts() {myChart = echarts.init(document.getElementById(this.moduleParam.id))// 观测更新的数据在 view 层可以直接访问到if (this.optionData == null) {return;}if (this.moduleParam.type == "boxplot") {this.optionData.tooltip.formatter = (params) => {let txt = ''txt += params.seriesName + '<br/>' +params.marker + '上限值:' + params.data[5] + '' + '<br/>' +params.marker + '上四分位数:' + params.data[4] + '' + '<br/>' +params.marker + '中位数:' + params.data[3] + '' + '<br/>' +params.marker + '下四分位数:' + params.data[2] + '' + '<br/>' +  params.marker + '下限值:' + params.data[1] + '' + '<br/>'return txt}}else if(this.moduleParam.type === "scatter"){echarts.registerTransform(ecStat.transform.clustering);}myChart.setOption(this.optionData)// 点击传参myChart.on('click', params => {this.clickData = params})},updateEcharts(newValue, oldValue, ownerInstance, instance) {// // 监听 service 层数据变更// // console.log(newValue) myChart = echarts.init(document.getElementById(this.moduleParam.id)) myChart.setOption(newValue)},moduleParamUp(newvalue, oldvalue) {},onClick(event, ownerInstance) {// console.log(this.clickData)ownerInstance.callMethod('onViewClick', {value: this.clickData.value,name: this.clickData.name,dataIndex: this.clickData.dataIndex,seriesName: this.clickData.seriesName})}}}</script><style>.echarts {margin-top: 10px;width: 100%;    height: 226px;}</style>

如何使用

<template><view class="content"><dom-echart :moduleParam="moduleParam1" :optionData="option" @getClickData="getClickData1"></dom-echart><dom-echart :moduleParam="moduleParam2" :optionData="option1" @getClickData="getClickData2"></dom-echart><dom-echart :moduleParam="moduleParam3" :optionData="option2" @getClickData="getClickData3"></dom-echart></view></template><script>const dataAll = [{value: [10.0, 80],symbolSize: 10,itemStyle: {color: '#B797C4',}},];// prettier-ignoreconst markLineOpt = {animation: false,lineStyle: {type: 'solid'},data: [[{coord: [0, 50],symbol: 'none'},{coord: [20, 50],symbol: 'none'}],[{coord: [10, 0],symbol: 'none'},{coord: [10, 100],symbol: 'none'}]]};import domEchart from '@/components/echarts/echarts.vue'export default {components: {domEchart},data() {return {moduleParam1: {id: "moId1",type: "scatter"},moduleParam2: {id: "moId2",},moduleParam3: {id: "moId3",},option: {title: {text: '平均正确率:%',textStyle: {color: '#9E9E9E',fontSize: 10,}},grid: {left: 30,top: 30,right: 20,bottom: 20},tooltip: {formatter: 'Group {a}: ({c})'},xAxis: [{gridIndex: 0,min: 0,max: 20}],yAxis: [{gridIndex: 0,min: 0,max: 100}],series: [{name: 'I',type: 'scatter',data: dataAll,markLine: markLineOpt}]},option1: {title: {text: 'ECharts2'},series: [{name: '销量',type: 'pie',data: [53, 20, 28, 12, 10, 20]}]},option2: {title: {text: 'ECharts3'},xAxis: {data: ["衬衫1", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]},yAxis: {},series: [{name: '销量',type: 'line',data: [53, 20, 28, 12, 10, 20]}]}}},onLoad() {},methods: {getClickData1(option) {console.log("点击的值", option)},getClickData2(option) {console.log("点击的值", option)},getClickData3(option) {console.log("点击的值", option)},}}</script><style></style>
❌
❌