技术自由圈

2023-05-13 todo Spring Cloud 如何构建动态线程池?

2023-05-12 亿级别用户日活统计存储结构

方式一:通过 redis的 Set集合进行存储(用户id存储到Set 中,去重)
方式二:利用 Hash类型(用户id作为hase的key,value 设置为1,)
方式三:利用 bitmap 实现()
bitmap:即位图,是一串连续的二进制数组(0和1)(用户id表示位置)
image.png

方式四:利用HyperLogLog 实现
HyperLogLog是概率性数据结构,作用是提供不精确的去重计算
HLL算法思想是:利用随机哈希函数将元素映射到二进制字符串中
HLL算法的优点在于它具有极低的内存消耗和高效的计算速度,并且可以处理极大的数据集。

  1. bitmap:更细化的一种操作,以bit为单位。
  2. hyperloglog:基于概率的数据结构。 # 2.8.9新增
  3. Geo:地理位置信息储存起来
  4. 流(Stream)# 5.0新增

_2023-05-11 _1000W级用户场景的签到优化
  • 优化1:利用 Bitmap实现用户签到存储优化
    - 优化2:利用Redis Bitmap 实现用户签到的性能优化
    - 优化3:利用Nginx +lus 实现更进一步的用户签到的性能优化

Bitmap实现用户签到存储优化
把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路就称为位图(BitMap)。
02819bf08cb9c43eb4fea99d66f446e6.png
利用Redis Bitmap实现用户签到的性能优化
读和写都是如 reids,对列入库,入库时对保存的签到数据进行用数组保存。
f9ebe4334e5c99cfc5d5f536d5564e4d.png


_2023-05-09 _BigKey问题很致命,如何排查和处理?

bigKey 危害(本质上就是 大value 问题。)
redis中的value值很大,在序列化和反序列化过程中耗时,当我们操作bigkey时,导致redis发生阻塞,影响性能
1阻塞请求,2内存增大,3影响网络,4影响主从同步,主从切换
什么是 bigKey:

  • Key本身的数据量过大:一个String类型的Key,它的值为5MB
  • Key中的成员数过多:一个 ZSET类型的Key,它的成员数量为10000 个
  • Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有1000个但这些成员的Value值总大小为 100MB

如何识别Big Key?
- 方法1、使用redis-cli 命令加上--bigkeys参数 识别
- 方法2、scan 扫描+ 长度命令(scan扫描Redis中的所有key,利用strlen、hlen等命令判断kev的长度)
- 方法3、使用debug object key命令
- 方法4、redis-rdb-tools开源工具

如何解决Big Key问题?
- 1、对大Key进行拆分
- 2、对大Key进行清理
- 3、监控Redis的内存、网络带宽、超时等指标
- 4、定期清理失效数据
- 5、压缩value

Redis字符串的内部大小
Redis 自定义了一个属于特殊结构 SDS(Simple Dynamic String)即简单动态字符串),

  1. SDS 是一个可以修改的内部结构,非常类似于 Java 的 ArrayList。
struct sdshdr{
     //记录buf数组中已使用字符的数量,等于 SDS 保存字符串的长度
     int len;
     //记录 buf 数组中未使用的字符数量
     int free;
     //字符数组,用于保存字符串
     char buf[];
  1. string 采用了 预先分配 冗余空间 的方式来 减少内存的频繁分配

SpringBoot BigKey的scan扫描实操
867cf46451a26816fbf511b81178c78e.png

_2023-04-27 _如何实现一个100W ops 生产者消费者程序?
  • 版本1:不安全的生产者-消费者模式版本
  • 版本2:使用 内置锁实现的 生产者-消费者模式版本
  • 版本3:使用信号量实现(Semaphore)
  • 版本4:使用Blockingqueue 实现
  • 版本5:无锁实现生产者-消费者模式版本

2023-03-28 todo 10Wqps评论中台,如何架构?B站是这么做的

2023-03-05 62条SQL优化策略,太牛X了
  1. 为 WHERE 及 ORDER BY 涉及的列上建立索引
  2. where中使用默认值代替null
  3. 慎用 != 或 <> 操作符。
  4. 慎用 OR 来连接条件

可以使用 UNION 合并查询:

select id from t where num=10 
union all 
select id from t where num=20
  1. 慎用 IN 和 NOT IN

IN 和 NOT IN 也要慎用,否则会导致全表扫描。对于连续的数值,能用 BETWEEN 就不要用 IN:select id from t where num between 1 and 3。

  1. 慎用 左模糊like ‘%...’

而select id from t where name like‘abc%’才用到索引

2023-_03-02 _如何保障 MySQL 和 Redis 数据一致性?

旁路缓存又模式分为读缓存和写缓存
旁路缓存模式在读的时候,先读缓存,缓存命中的话,直接返回数据;如果缓存没有命中的话,就去读数据库,从数据库取出数据,放入缓存后,同时返回响应。
Cache-Aside Pattern(旁路缓存)模式写操作流程,具体如下:

  • step 1:接收用户的数据写入的请求;
  • step 2:先写入数据库;
  • step 3:再写入缓存。

懒汉模式,就会在使用时临时加载缓存。具体来说,就是当需要使用数据时,就从数据库中把它查询出来,然后写入缓存。第一次查询之后,后续的请求都能从缓存中查询到数据。
饿汉模式,就是提前预加载缓存。具体来说,在项目启动的时候,预加载数据到缓存。当需要使用数据时,能直接从缓存获取数据,而不需要从数据获取。

如何保证DB和Cache双写的数据一致性?
什么是延迟双删呢?就是先删Cache,后写DB,最后延迟一定时间,再次删Cache
延迟双删也会存在数据不一致,不过是持续时间比较短而已(延迟一定时间,防止别的服务写入缓存。)

基于binlog+消息队列删除缓存。
以Mysql为例,可以使用阿里的Canal中间件,采集在数据写入Mysql时生成的binlog日志,然后将日志发送到 RocketMq 队列。
编写一个专门的消费者(Cache Delete Consumer)完成缓存binlog日志订阅,筛选出其中的更新类型log,解析之后进行对应Cache的删除操作,并且通过RocketMq队列ACK机制确认处理这条更新 log,保证Cache删除能够得到最终的删除。

消息确认机制(ACK)
RocketMq 提供了消息确认机制
消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为 RabbitMQ 会一直等待 持有消息 直到消费者 显式调用 Basic.Ack 命令为止。

RabbitMQ 消息确认机制分为两大类:发送方确认、接收方确认。

基于binlog+消息队列去删除Cache的方案的优势是
微服务 Provider 在执行DB和Cache双写时,只需要执行写入DB的操作就可以了

2023-02-21 100亿订单超时处理,(超时未付款)
  1. 内存的延迟队列/优先级队列处理(延迟队列数据结构 DelayQueue )
  2. 分布式队列延迟消息的定时方案( RocketMQ )
  3. reids的过期监控
  4. 基于分布式K-V组件(如Redis)过期时间的定时方案

方案一:内存的延迟队列,DelayQueue
JDK中提供了一种延迟队列数据结构 DelayQueue

  1. 把订单插入DelayQueue中,以超时时间作为排序条件,
  2. 超时时间到了,就出队进行超时处理,并更新订单状态到数据库中。
  3. 为了防止机器重启导致内存中的DelayQueue 数据丢失,每次机器启动的时候,需要从数据库中初始化未结束的订单,加入到DelayQueue中。
  • 基于内存的时间轮调度

方案二:RocketMQ 的定时消息
只需要在发送消息的时候设置延时时间即可

MessageBuilder messageBuilder = null;
Long deliverTimeStamp = System.currentTimeMillis() + 10L * 60 * 1000; //延迟10分钟
Message message = messageBuilder.setTopic("topic")
        //设置消息索引键,可根据关键字精确查找某条消息。
        .setKeys("messageKey")
        //设置消息Tag,用于消费端根据指定Tag过滤消息。
        .setTag("messageTag")
        //设置延时时间
        .setDeliveryTimestamp(deliverTimeStamp) 
        //消息体
        .setBody("messageBody".getBytes())
        .build();
SendReceipt sendReceipt = producer.send(message);
System.out.println(sendReceipt.getMessageId());

RocketMQ 4.x 版本只支持 延迟消息,有一些局限性。而
RocketMQ 5.x 版本引入了 定时消息,弥补了 延迟消息的不足。

延迟消息

Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes());
//第 3 个级别,10s
message.setDelayTimeLevel(3);
producer.send(message);

延迟消息:18个等级,不灵活,可以通过修改 messageDelayLevel 配置来自定义延时级别,虽然可以修改,但是不灵活

"1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";

定时消息 细粒度定时消息
为了弥补 延迟消息的不足,RocketMQ 5.0 引入了细粒度定时消息。

时间轮 类似map,key 为时间的刻度,value为此刻度所对应的任务列表。

Bucket 的结构定义可以看出,Bucket 内部是一个双向链表结构,双向链表的每个节点持有一个 task 对象,task 代表一个定时任务。每个 Bucket 都包含双向链表 head 和 tail 两个 task 节点,这样就可以实现不同方向进行链表遍历

方案三:监控redins 的过期监控

  1. 在服务器中 修改redis配置文件, 开启"notify-keyspace-events Ex"
  2. 创建一个Redis监控类,用于监控过期的key,该类需继承KeyExpirationEventMessageListener
  3. 创建Redis配置类 , 装配这个 监听器

Redis主要使用了定期删除和惰性删除策略来进行过期key的删除

  • 定期删除:每隔一段时间(默认100ms)就随机抽取一些设置了过期时间的key,检查其是否过期,如果有过期就删除。
  • 惰性删除:不主动删除过期的key,每次从数据库访问key时,都检测key是否过期,如果过期则删除该key。

Redis过期通知也是不可靠的,Redis在过期通知的时候,

方案四:超大规模分布式定时批处理 架构
step1:通过分布式定时不停轮询数据库的订单,将已经超时的订单捞出来
step2:分而治之,分发给不同的机器分布式处理

2023-02-11 select分页要调优100倍,说说你的思路?

Limit接受一个或两个数字参数,参数必须是一个整数常量。
如果给定两个参数,

  • 第一个参数指定第一个返回记录行的偏移量,
  • 第二个参数指定返回记录行的最大数目

MySQL在数据量大的情况下分页起点越大,查询速度越慢

2023-02-02 千万级、亿级数据,如何性能优化? 教科书级 答案来了

优化1:业务架构解耦
对服务进行拆分,业务线拆分
优化2:数据量大的优化
对数据量大的问题,进行了以下优化:

  • 数据归档
  • 分表

数据归档:
根据二八定律,系统绝大部分的性能开销花在20%的业务
业务层面的优化:
如果你想看看3个月前的订单,需要访问历史订单页面
技术层面的优化:
将新老数据分开存储,将历史订单移入另一张表中,

分表:
分表又包含垂直分表和水平分表:主要考虑的是水平分表
单表的数据在 500-1000W,B+树的高度在2-3层,一般2-3次IO操作,就可以读取到数据记录。

优化3:吞吐量大的优化
吞吐量大的优化的解决方案有:

  • 使用缓存
  • 读写分离
  • 分库

redis分布式还是能够为DB分担一下压力
主库负责执行数据更新请求,然后将数据变更实时同步到所有从库,用多个从库来分担查询请求。
参考之前项目经验,并与公司中间件团队沟通后,采用了开源的 Sharding-JDBC 方案。
0861e81674238c408b35c1e0af1b89a3.jpg

分库分表策略
结合业务特性,选取用户标识作为分片键,
通过计算用户标识的哈希值再取模,来得到用户订单数据的库表编号
则库表编号的计算方式为:

  • 库序号:Hash(userId) / m % n
  • 表序号:Hash(userId) % m

推特 snowflake雪花id

分库分表的局限性和应对方案
分库分表解决了数据量和并发问题,但它会极大限制数据库的查询能力
①全局唯一ID设计
②历史订单号没有隐含库表信息

优化4:高速搜索引擎的数据一致性优化
如何在MySQL的订单数据变更后,同步到ES中呢?
1)MQ方案
ES更新服务作为消费者,接收订单变更MQ消息后对ES进行更新
2)Binlog方案
把自己伪装成MySQL的从节点,接收Binlog并解析得到实时的数据变更信息,然后根据这个变更信息去更新ES。

优化5:合理的选择数据库迁移措施
1)不停机迁移方案

  • 把旧库的数据复制到新库中,
  • 上线双写订单新旧库服务,只读写旧库;
  • 开启双写,同时停止同步程序,开启对比补偿程序,确保新库数据和旧库一致;
  • 逐步将读请求切到新库上
  • 读写都切换到新库上,对比补偿程序确保旧库数据和新库一致;
  • 下线旧库,下线订单双写功能,下线同步程序和对比补偿程序。

2)停机迁移方案:
新订单系统有双写旧库的开关

夜间停机方案的业务损失并不大,最终选用的是停机迁移方案。

优化6:合理的进行分布式事务方案的选型
在本地事务中将要执行的异步操作记录在消息表中,如果执行失败,可以通过定时任务来补偿。
46b930650c09f96cc946f8e6733efa58.jpg
优化7:其他的一些细节优化
只有极少数第三方接口可通过外网访问,且都会验证签名,
任何订单更新操作之前,会通过数据库行级锁加以限制,防止出现并发更新

2023-01-27 聊聊MySQL的七种日志

日志

  • 错误日志(error log)(MySQL在启动、关闭或者运行过程中的错误信息
  • 慢查询日志(slow query log)(MySQL中响应时间超过阀值的语句
  • 重写日志(redo log)(执行成功后的日志
  • 回滚日志(undo log)(执行前日志,用户回滚
  • 二进制日志(bin log)(主从同步日志
  • 中继日志 relay log 日志文件具有与bin log日志文件相同的格式
  • **一般查询日志(general query log) **用来记录用户的所有操作:

23ffc19a8b0c5e6f9b1eb10754ea7cab.png
日志存储流程。
17b3771779c768a5c56eea44e88fb621.png

错误日志(error log)(MySQL在启动、关闭或者运行过程中的错误信息

慢查询日志(slow query log)(MySQL中响应时间超过阀值的语句
MySQL 数据库没有开启慢查询日志,需要我们手动来设置这个参数
在 SQL 优化过程中会经常使用到

重写日志(redo log)(执行成功后的日志
redo log如何保证数据不丢失 ,实现高可靠,实现事务持久性

回滚日志(undo log)(执行前日志,用户回滚
undo log也是属于MySQL存储引擎InnoDB的事务日志
每当我们要对一条记录做改动时(这里的改动可以指INSERT、DELETE、UPDATE),都需要"留一手"把回滚时所需的东西记下来

二进制日志(bin log)(主从同步日志
以二进制形式存储在磁盘中的逻辑日志
bin log记录了数据库所有DDL和DML操作(不包含 SELECT 和 SHOW等命令,因为这类操作对数据本身并没有修改)

mysql主从复制:mysql replication 在master端开启binlog, master把它的二进制日志传递给slaves来达到master-slave数据一致的目的。
5b25ed7c09951634e8c35c7d26cf58f8.png
relay log(中继日志)
relay log日志文件具有与bin log日志文件相同的格式
relay log起到一个中转的作用,slave先从主库master读取二进制日志数据,写入从库本地,后续再异步由SQL线程读取解析relay log为对应的SQL命令执行

2022-12-21 用过分布式锁吗?你们是怎么做选型的?

基于数据库的悲观锁或者乐观锁
基于redis实现分布式锁
基于zookeeper实现分布式锁
基于其他的中间件实现分布式锁

什么是高可用的RedLock?
不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,n / 2 + 1,
必须在大多数redis节点上都成功创建锁,才能算这个整体的RedLock加锁成功,
避免说仅仅在一个redis实例上加锁而带来的问题。

即当客户端在大多数redis实例上申请加锁成功后,且加锁总耗时小于锁过期时间,则认为加锁成功

2021-03-31 Redis内存淘汰策略

Redis对于过期的key,有两种删除策略:
•定期删除
•惰性删除
redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定期遍历这个字典来删除到期的 key
redis默认是每隔 100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。
所谓惰性策略就是在客户端访问这个key的时候,redis对key的过期时间进行检查,如果过期了就立即删除,不会给你返回任何东西。
redis内存淘汰策略

  1. 该策略对于写请求不再提供服务,会直接返回错误
  2. 从redis中随机选取key进行淘汰

•LRU(Least Recently Used,最近最少使用)
•LFU(Least Frequently Used,最不经常使用)

LRU 主要是通过 Key 的最后访问时间来判定哪些 Key 更适合被淘汰
LFU(Least Frequently Used)表示最不经常使用,它是根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。
LFU算法的常见实现方式为链表:
新数据放在链表尾部 ,链表中的数据按照被访问次数降序排列,访问次数相同的按最近访问时间降序排列,链表满的时候从链表尾部移出数据

keys * 这个命令千万别在生产环境乱用。因为Keys会引发Redis锁,并且增加Redis的CPU占用。

架构演进1:单机架构

首先经过DNS服务器(域名系统)把域名转换为实际IP地址10.102.4.1,浏览器转而访问该IP对应的Tomcat。

架构演进2:引入缓存架构
通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。

架构演进3:接入层引入反向代理实现负载均衡
使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。
架构演进4:数据库读写分离
把数据库划分为读库和写库,读库可以有多个,
可以通过DB的同步机制,把写库的数据同步到读库,对于需要查询最新写入数据场景,可通过在缓存中多写一份,通过缓存获得最新数据。
其中涉及的技术包括:shardingjdbc ,它是数据库中间件,可通过它来组织数据库的分离读写和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。
架构演进5:数据库按业务分库

架构演进6:使用LVS或F5接入层负载均衡
随着吞吐量大于5W,接入层Nginx扛不住了
由于瓶颈在Nginx,因此无法LVS或F5来实现多个Nginx的负载均衡。

架构演进7:通过DNS轮询实现机房间的负载均衡
此时用户数达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同。

架构演进8:引入NoSQL数据库和搜索引擎等技术
检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求。
当数据库中的数据多到一定规模时,数据库就不适用于复杂的查询了,往往只能满足普通查询的场景。
使用 elasticsearch 分布式搜索引擎解决。
如对于海量文件存储,可通过分布式文件系统hbase解决

架构演进9:大应用拆分为微服务
SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。
这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心 Nacos来解决。
架构演进10:引入企业服务总线ESB对微服务进行编排
架构演进11:引入容器化技术实现动态扩容和缩容
目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),
应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。
架构演进12:以云平台承载系统
所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体

posted @ 2023-07-05 22:33  微辰  阅读(15)  评论(0编辑  收藏  举报