后端——分布式系统知识点总结

目录

1.分布式id

2.分布式锁

3.分布式系统的幂等


1.分布式id

在数据库中每一个数据表都有一个唯一的id作为每一行数据的唯一标识符。但随着数据日渐增长,一个数据库已经无法满足数据存储要求,对数据分库分表后同样tongy需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求,此时一个能够生成全局唯一ID的系统是非常必要的。

目前阶段,生成分布式id的方案主要有以下四种:

(1)UUID:

包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符

其优点在于生成速度快且简单,可以保证主键全局唯一,跨服务器合并数据方便。但其缺点在于空间占用较多,且数据无序,使得索引效率下降。

UUID示例:9794f1c8-ca7b-40a6-8707-ccccf9cf0585

生成代码;

String id = UUID.randomUUID().toString();

(2)数据库自增id:

我们可以通过在数据对id字段配置自增的方式配置唯一id。

其优点在于int和bigInt占用空间都较小,且写入连续,查询速度快。其缺点并发性性能不高,且分库分表难以改造(可能会出现id一致问题),同时还会存在数据量泄密问题(可能最大值就是数据库中某个数据表的数据量)。

mysql中我们可以在创建表时,在id字段后使用auto_increment命令设置自增。

(3)redis生成id:

这主要依赖于Redis是单线程的,且Redis中的increase操作是原子操作。

其优点在于使用内存,并发性能好;但会存在数据丢失的问题,会存在数据量泄密问题。

(4)使用雪花算法:

雪花算法生成的ID是一个64位的长整型数字,由以下部分组成:1个bit:符号位,始终为0;41个bit:时间戳,精确到毫秒级别,可以使用69年;10个bit:工作机器ID,可以部署在1024个节点上;12个bit:序列号,每个节点每毫秒内最多可以生成4096个ID。

雪花算法目前被认为是分布式id的最终解决方案,其优点在于:可以保证全局唯一,用于分布式系统中的数据分片和数据合并,避免了ID冲突的问题;时间有序,雪花算法生成的ID中包含了时间戳信息,可以根据ID的大小推算出生成的时间,方便进行数据排序和查询;易于扩展性,雪花算法的数据结构相对简单,易于扩展和修改。

但同时雪花算法由于依靠于时间戳进行生成,如果系统时钟发生回拨,可能会导致生成的ID出现重复。同时,由于其大小为64位固定,占用内存也不小。

目前,mybatis plus框架下默认的id策略是基于雪花算法实现的,也就是说,我们使用@TableId注解后,mybatis plus会自动帮我们生成雪花算法id插入数据库中,

2.分布式锁

分布式锁,即分布式系统中的锁。

在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。

下面,主要介绍两种分布式锁的实现方式:

(1)基于redis的分布式锁:

主要基于setnx(set if not exists)命令。

setnx命令设置的key值当存在时,同一key值无法再进行设置,从而达到锁的目的。

(如我们存了一个key为lock,value为1234567890的键值到redis中,则redis中无法再存key为lock的键值对,从而达到锁的目的)

同时,为避免服务器宕机等原因,导致锁一直未被释放,需给改key值配置过期时间;而为了保证锁的有效性,设置过期时间需保证原子性;删除锁时,需进行判断,该锁的value是否属于该进程。

最后,使用watch dog可以保证key的有效时间续签,保证业务的完成再释放锁。

redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS)

而使用redission可以一致集成以上功能:

依赖:

        <!--redission相关依赖-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.16.0</version>
        </dependency>

使用锁示例:

        String lockKey = UUID.randomUUID().toString();
        RLock lock = redisson.getLock(lockKey);
        try {
            lock.lock();
            log.info("锁已开启");

            synchronized(this){
                    //业务逻辑
                }
            }
        }catch (Exception e){
            log.warn("系统错误,稍后重试");
        }
        finally {
            lock.unlock();
            log.info("锁已关闭");
        }

具体可参考:Springboot集成Redis——实现分布式锁-CSDN博客

(2)基于zookeeper的分布式锁:

主要依赖于临时节点和顺序节点。

临时节点,有客户端进行创建,当客户端与ZK集群断开连接时,该节点被自动删除。顺序节点:ZK集群会按照发起创建的顺序来创建节点

过程:客户端创建临时顺序节点,并调用相关方法获取到已创建的节点。然后客户端判断自己的创建序列号是否排第一,如果是,则获得了锁;如果不是,则监视比自己创建节点的序列号小的最大的节点,进入等待,直到下一次监视的子节点发生变更,再进行判断。

(3)区别:

  • Redis只保证最终一致性:由于redis主从复制会存在延迟,所以会存在主从切换之后,会存在一段时间部分数据未复制导致的锁丢失情况。
  • Redis相应时间均为最低。
  • 由于zookeeper依靠于临时节点创建锁,则客户端由于网络状况断开集群,则会被视为释放锁。
  • 依靠于创建节点来创建锁,会浪费较多时间。

3.分布式系统的幂等

什么是幂等?

即同一个操作的多次执行的是同一个结果

操作的幂等包括查询,set固定值,逻辑删除;

流程的幂等一般指分布式系统中因网络波动导致调用失败但服务器已经成功处理,发起重试机制,导致服务器收到两次一样的请求,系统需保证两次操作结果一致。

 如上图,请求访问服务A。 第一次访问,服务A已经执行成功,但返回时发送网络波动导致服务A没有返回。请求没得到返回结果,执行重试机制,这时服务A等于收到两次一样的请求,为了保证请求的幂等,会拒绝执行第二次请求,把第一次请求结果返回。

查询:本身是幂等的,在数据量不变的情况下,查询结果一致;删除:本身是幂等的,删除一次还是多次都是删除数据,仅会影响返回1或是更多。

而为了保证分布式系统的幂等,一般有以下方案:

(1)使用数据库索引:使用主键或是对字段组合唯一索引,可以防止脏数据,保证插入操作幂等。比如账户表中用户id和主键账户id组合唯一索引,保证每一个用户只有一个账户,即使第二次进行插入也只会有一条数据成功插入。

(2)使用token:进行业务时需携带token,当服务器处理完之后则删除存于redis中的该token,那么进行第二次处理校验出token时,会发现token无效(redis中无值)而拒绝请求,从而保证了幂等性。

(3)traceId:根据请求的来源,序列号,用户id等多个字段组成该次请求的id。当业务执行完毕后并其进行记录,若发送重试时查询验证请求id,一致则不再执行业务。

所以API如果要保证幂等,有两个字段是必传的:来源source和来源方序列号,成为一个唯一索引。在进行处理时,先查询一下是否已经处理过该业务。

猜你喜欢

转载自blog.csdn.net/tang_seven/article/details/129720516