GenweiWu / Blog

个人技术能力提升
MIT License
4 stars 0 forks source link

2021_分布式事务 #76

Open GenweiWu opened 3 years ago

GenweiWu commented 3 years ago

分布式事务

事务的参与者不在同一个节点上,但是要求这些事务要么都成功,要么都失败;

比如在网上下单买商品。1是商品要减少1个,同时我的订单要更新为已下单状态,如果一部分成功部分失败,就会出现问题,破坏了事务一致性

解决方案

1、2pc(二阶段)

第一阶段去判断是否就绪

第二阶段去提交事务

## 如果第一阶段都返回就绪,则第二阶段提交事务;只要有一个资源返回false,就回滚事务

2、TCC (Try Confirm Cancle)

Try
进行业务一致性检查,预留资源

Cconfirm
执行业务;不作任务检查,使用预留资源

Cancel
取消执行,释放try阶段的资源
GenweiWu commented 3 years ago

nginx的作用

1. 缓存静态资源

server {
    listen      5000;
    root D:/2222/nginxDemo; 
}

2. 反向代理

server {

    listen80;

    location / {
    proxy_pass http://192.168.20.1:8080; # 应用服务器HTTP地址

    }
}

3. 负载均衡

upstream myapp {
    server192.168.20.1:8080; # 应用服务器1
    server192.168.20.2:8080; # 应用服务器2
}

server {
    listen80;
    location / {
    proxy_pass http://myapp;

    }
}

4. 虚拟主机

例如将aaa.com和bbb.com都部署到一台机器上,两个域名解析到同一个IP地址,但是访问的两个域名打开是不同的网站

server {
    listen 80;
    server_name www.aaa.com;
    location / {
        proxy_pass http://localhost:8080;
    }
}

server {
    listen 80;
    server_name www.bbb.com;
    location / {
        proxy_pass http://localhost:8081;
    }
}

负载均衡策略5

1. 轮询

upstream backserver{
    server 1.2.3.4;
    server 1.2.3.5;
}

2.加权轮询

upstream backserver{
    server 1.2.3.4 weight=3;
    server 1.2.3.5 weight=7;
}

3. fair

(翻译是 公平公正) 按照后台影响时间来分配,响应时间短的优先分配

upstream backserver{
    server server111;
    server server222;
    fair;
}

4. ip_hash

可以让用户总是访问特定的服务器

upstream backserver{ 
    ip_hash;
    server 1.2.3.4;
    server 1.2.3.5;
}

5. url_hash

upstream backserver {
    server squid1:3128;
    server squid2:3128;
    hash $request_uri;
    hash_method crc32;
}

大小限制

location / {
    root /home/test;
    client_max_body_size 10m;  # 改为你需要的大小
}
GenweiWu commented 3 years ago

mysql数据库初始化

## 创建database
CREATE DATABASE IF NOT EXISTS TestDB  CHARACTER SET=utf8 COLLATE=utf8_general_ci ;
## 创建user+密码
CREATE USER IF NOT EXISTS 'test_service' IDENTIFIED BY 'password@123' WITH MAX_USER_CONNECTIONS 500;
## 将database和用户绑定
grant all on TestDB.* to test_service;

## @'localhost'限定用户只能本地访问
CREATE USER 'myuser'@'localhost' IDENTIFIED BY '4myuser';
## @'%' 不限定,可以分别设定密码
CREATE USER 'myuser'@'%' IDENTIFIED BY '4myuser';
GenweiWu commented 3 years ago

秒杀设计

服务单一职责

订单服务、秒杀服务分开

url暴露

redis集群,对库存进行缓存

nginx负载均衡


静态资源单独CDN处理

前端按钮灰化

秒杀前灰化

限流

限制点击次数


削峰填谷:

把它放消息队列,然后一点点消费去改库存就好了嘛,不过单个商品其实一次修改就够了,

GenweiWu commented 3 years ago

分布式锁

1、redis

## 加锁,没有则加锁
setNx resourceName value

## 防止死锁
expire 

2、zookeeper

利用zookeeper的同级节点的唯一性特性

zookeeper的节点类型

ZooKeeper 节点是有生命周期的,这取决于节点的类型。在 ZooKeeper 中,节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL ),具体在节点创建过程中,一般是组合使用,可以生成以下 4 种节点类型。

持久节点(PERSISTENT)
所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。

持久顺序节点(PERSISTENT_SEQUENTIAL)
这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。

临时节点(EPHEMERAL)
和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。

临时顺序节点(EPHEMERAL_SEQUENTIAL)
可以用来实现分布式锁

客户端调用create()方法创建名为“_locknode_/guid-lock-”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。
客户端调用getChildren(“_locknode_”)方法来获取所有已经创建的子节点,注意,这里不注册任何Watcher。
客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点序号最小,那么就认为这个客户端获得了锁。
如果在步骤3中发现自己并非所有子节点中最小的,说明自己还没有获取到锁。此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时注册事件监听。
之后当这个被关注的节点被移除了,客户端会收到相应的通知。这个时候客户端需要再次调用getChildren(“_locknode_”)方法来获取所有已经创建的子节点,确保自己确实是最小的节点了,然后进入步骤3。

排他锁:在特定目录下创建临时子节点

方法1:要大范围注册watcher

比如在 /exclusive下创建 子节点/exclusive_lock/lock,

方法2:创建临时有序节点,监听前一个节点

调用create方法,创建名为 locknode/guid-lock-xxx的临时有序节点

客户端调用getChilder("locknode")方法获取节点下所有子节点,

共享锁:在目录下创建临时顺序节点

curator

实际开发过程中,可以 curator 工具包封装的API帮助我们实现分布式锁。

GenweiWu commented 3 years ago

Zookeeper

zk上节点类型

  1. PERSISTENT持久化节点:客户端与zookeeper断开连接后,节点依然存在
    create /aaa value
  2. EPHEMERAL临时节点:客户端与zookeeper断开连接后,该节点就会被删除
    create -e /aaa value
  3. PERSISTENT_SEQUENTIAL持久化有序节点:客户端与zookeeper断开连接后,节点依然存在 + 节点命名时按照顺序进行命名
    create -s /zktest
  4. EPHEMERAL_SEQUENTIAL临时有序节点: 客户端与zookeeper断开连接后,该节点就会被删除 + 节点命名时候按照顺序进行命名
    create -e -s /zktest

zookeeper watch机制

可以注册watcher的方法:getData、exists、getChildren。 可以触发watcher的方法:create、delete、setData。连接断开的情况下触发的watcher会丢失。

zk实现分布式锁

1. 方式1:临时节点+监听

指定1个路径,多个客户端同时创建节点,创建成功的则认为抢到了锁;其他的客户端通过监听这个节点;一旦获取锁的客户端释放了锁,则其他客户端再来尝试抢占锁(即创建该节点)

2. 方式2:临时有序节点+最小节点获取到锁+各自监听前一个比它小的节点

GenweiWu commented 3 years ago

redis分布式锁

https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/

//加锁
setnx key value
//释放锁
DEL key
//防止锁超时
EXPIRE key timeout

不足

1. SETNX和EXPIRE非原子性

使用lua脚本来实现

2. 超时解锁导致并发(即逻辑还没执行完,锁就到期了)

方法1:设置较长时间的超时时间 方法2:增加守护线程来刷新锁的时间

3. 锁被误解除(线程A抢到锁,还没执行完就超时释放了,结果线程B又抢到了正在用,A执行完把B的锁给释放了)

给锁增加线程id等标识

4. 不可重入

增加锁的标识,增加重入次数计数

GenweiWu commented 3 years ago

redis分布式锁2:redis集群故障和redLock算法

https://redis.io/topics/distlock https://www.cnblogs.com/rgcLOVEyaya/p/RGC_LOVE_YAYA_1003days.html

分布式锁3个要素

redis分布式锁要考虑三个要素:

  1. 安全性:任何时刻,只能有一个客户端能成功获取到锁

  2. 活跃性A:避免死锁: 获取锁的客户端,发生奔溃或故障后,要能释放锁,不能一直占据锁

  3. 活跃性B:容错性 redis的master节点发生故障,也不能影响分布式锁的正常释放

    ## 异常场景:
    1. 客户端1在redis集群的masterA上获取到锁
    2. masterA挂了,没来得及将锁的数据同步到从节点B
    3. 从节点经过选择,变成了masterB
    4. 另一个客户端2,尝试在masterB上获取锁,能获取成功(此时就和客户端1冲突了)

1. 如何保证唯一获取锁

可以利用SETNX key value 这样,如果一开始没设置这个key,就会返回设置成功;否则,重复设置会返回失败

2. 如何防止死锁

可以利用redis的ttl超时机制

SET key value
expire key seconds ## 超时时间

但是,上面两个操作不具备原子性

SET key value NX PX seconds
## NX选项:同上,不存在才能设置成功
## PX选项:用来设置超时

### 优化点1:
value改成random value,删除锁时要同时执行key和value,以防止出现客户端1加锁后,被客户端B误解锁了

3. master集群中节点故障

了解下redLock算法

## 分为4个步骤
假设现在有N个master节点的redis集群

1. 获取当前时间t1,毫秒级
2. 依次在N个节点上尝试获取锁,使用相同的key和随机数value,
   在尝试获取锁的过程中,设置一个比ttl时间小得多的超时时间,避免某个redis节点故障导致在该节点上浪费时间
   比如ttl为10s的话,尝试获取锁的超时时间为5~50ms;如果当前节点不可用,则立即去尝试下一个节点
3. client获取到锁的时间t1,要减去步骤1的时间t2,这个时间差要大于ttl,才能认为成功获取到锁
4. 如果可以成功获取到锁,则实际的ttl时间要进行校准:
    即实际ttl = 初始ttl - (t2-t1),(其实还要再减去时钟漂移)
    比如ttl为5s,但是获取锁已经用了2s,时钟漂移为1s,则实际ttl = TTL - (t1-t1) - 漂移时间 = 5 - 2 - 1 = 2;
5. 回滚:如果加锁失败(比如加锁节点未达到一半以上),则需要对所有节点进行解锁操作