面试题和答案分布式
常见问题列表
- 分布式锁的实现(Done)
- 分布式事务的实现(包括2PC、幂等性和重传)
- 秒杀架构的实现(Done)
- 分布式定时任务(Done)
实现一个集群模式下系统生成唯一流水号的算法:流水号不能重复、不能生成一次访问一次数据库
- 利用redis的incr,生成一个唯一编号作为结尾 LSH-机器编号-时间-唯一编号:LSH-Server1-2010808161617-1
- 利用zk的临时节点,zkClient.createNode,获取到名字之后,拼上一些流水信息,实现全局唯一
- 利用server编号+时间戳+加锁生成的一个自增值(保证这个server上唯一),比如 LSH-Server1-2010808161617-1
- 如果可以利用数据库自增的话,那就更好实现了,插入一个数据,利用ID自增的原理,获取到全局唯一的序号,然后同上
分布式锁的实现方式和原理
- zookeeper
- 临时顺序节点,获取Children名字排序,首节点获取锁,次节点设置监听等待的方式,最为常用。
- zk的永久节点,命名唯一的特性,创建成功的client成功获取锁,释放锁的时候,删除该永久节点。
redis乐观锁(watch锁,开启事务,执行业务操作,锁值自增,提交)、悲观锁(SETNX原子操作,再引入setex破解异常,一般秒杀用悲观锁,因为竞争激烈)
数据库乐观锁(版本号)、Unique外键的形式、独占锁 select for update等形式
Redis 请求竞争
多个客户端同时对某个key的value进行操作,比如秒杀商品。
- 乐观锁策略(在竞争不激烈的时候,性能好)
- watch方法,带事务的修改数据,类似于volaile,如果price发生改动,则事务失效watch priceget price $price$price = $price + 10multiset price $priceexec
- 悲观锁+expire的实现策略
Redis 持久化方案和区别
- RDB的优点:
- 只一个文件备份文件,容易恢复数据和备份
- 性能最大化,fork子线程做持久化的操作
- 相比于AOF机制,如果数据集很大,RDB的启动效率会更高,因为是二进制文件
- 相同大小的存储内容,RDB是二进制文件,存储的空间更小,节省磁盘空间
- RDB的缺点:
- 数据不是百分百安全,未持久化的数据可能会丢失
- 当内存中的数据特别大的时候,因为每次都是生成一个完整的快照文件,速度会很慢
- AOF的优点:
- 可读性强,AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建,而RDB是二进制文件
- 数据集文件过大会自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性
- AOF 文件的格式可读性较强,提供了更灵活的处理方式。例如,如果我们不小心错用了 FLUSHALL 命令,在重写还没进行时,我们可以手工将最后的 FLUSHALL 命令去掉,然后再使用 AOF 来恢复数据
- AOF的缺点
- 大存储数据的时候重启的时候,加载速度比RDB要慢,但是因为是追加添加日志,不存在生成大快照的卡顿的情况出现
- 相对于RDB的方式,相同的数据量AOF占用的空间更大
Redis和Memcached的区别
http://blog.jobbole.com/101496/
- redis功能更强大:支持5种存储的数据类型、数据排序、持久化
- Redis支持内存数据持久化AOF和RDB,而且重启的时候自动加载持久化数据,但是Memcached数据仅存在内存中
- Redis在内存不够或者使用达到阈值时会把不常用的value交换到磁盘中,而Memcached只在内存中
- redis是单线程IO复用(读写排序等操作),事件驱动的,而memcached是多线程、非阻塞IO,主线程监听、worker线程工作的模式
- 内存管理的原理不同:memcached用Slab Allocation机制,高效无碎片,但是浪费内存空间,而redis用的是内存分配器有多种,相对而言碎片更多
- 集群管理上:Memcached不支持分布式,需要通过Hash分布式算法来实现Memcached的分布式存储;而Redis-Cluster支持分布式集群和主备的模型,可扩展性更好
- 一致性方面,redis通过事务和watch实现,memcached支持CAS操作
Memcached的内存管理机制Slab Allocation
- Page内存页默认1M,由若干个Slab class组成,每个slab由若干个大小一样的chunk组成,不同chunks按照ratio默认1.25增长。
- 写一个值的时候,会根据 key + value的大小,分配到大小最适合的slab中的空闲chunk里面,这样避免了内存碎片,但是会导致内容利用率不高的情况,比如100kb的item存入到128kb的chunk里面
- 当一条数据库过期或者丢弃时,该记录所占用的Chunk就可以回收,重新添加到空闲列表中
- 最大的优点就是效率高,无内存碎片,最大的缺点就是浪费了空间
秒杀架构的实现
参考资料:
https://www.jianshu.com/p/16300bf2660d
思路
- 页面做控制(页面jsp加载的时候,获取到后台的活动开始时间)
- 按钮做控制:活动开始前提交按钮置灰防止提前抢单
- 按钮做控制:开始时提交成功后置灰按钮,防止重复提交
- 防止恶意的抢单
- 控制层对请求的时间做校验,防止通过url或者程序恶意抢单,没开始就抢单了,超出了js控制
- redis做IP限流
- redis做用户名限流
缓存库存(Redis)
不管活动开始了,还是活动没开始,查询库存的时候,都是从redis获取库存分布式悲观锁(参考redis悲观锁的代码)
- 悲观锁(因为肯定争抢严重)
- Expire时间(抢到锁后,立刻设置过期时间,防止某个线程的异常停摆,导致整个业务的停摆)
- 定时循环和快速反馈(for缓存有超时设置,每次超时后,重新读取一次库存,还有货再进行第二轮的for循环争夺,实现快速反馈,避免没有货了还在持续抢锁)
- 异步处理订单
- redis抢锁成功后,记录抢到锁的用户信息后,就可以直接释放锁,并反馈用户,通过异步的方式来处理订单,提升秒杀的效率
- 为了避免异步的数据不同步,需要抢到锁的时候,在redis里面缓存用户信息列表,缓存结束后,触发抢单成功用户信息持久化,并且定时的比对一致性
消息队列削峰限流(RocketMQ自带的Consumer自带线程池和限流措施),集群。一般都是微服务,订单中心、库存中心、积分中心、用户的商品中心
数据库的并发修改(库存总数的扣减)
- 库存数据先加锁读取再修改的方式(select * for update 再 update 库存)避免集群模式下的修改无效的情况
- 扣减的时候再check一下,防止异常的消费,例如库存不能为负
架构图
Nginx在前端–> 对应多台集群的机子前端控制层(嵌套上Redis) –>MQ –>服务端处理订单 –> RPC更新各个微服务的库存中心、订单中心、积分中心等等完成订单
分布式事务的解决方案
单应用多数据源的业务场景
- 可以通过java的方式解决,设置不自动提交,然后开启事务,挨个提交,做好tryCatch,如果出现异常就回滚,可能要写补偿接口。如果A提交成功,B提交失败,那么需要调用A的补偿接口。
- JTA协议,atomikos 实现分布式事务,主要是DataSource要使用AtomikosDataSourceBean,管理器就可以实现。资源管理器绑定在一个公用的事务管理器上面,然后砽事务管理器 开启事务,执行事务,提交事务。
分布式应用+独立数据源
https://blog.csdn.net/z69183787/article/details/80235844
- 2PC:prepare阶段,check资源可用且业务能通畅,然后对要操作的数据加锁;如果全部锁定成功,就一起执行commit操作,如果有失败的则先重试(重试的时候要保证幂等性,可用通过添加ticketID的方法实现),重试一定次数失败,就调用补偿接口,回滚已经提交的资源。
- TCC:事务补偿。try阶段,通过业务的方式,仅仅锁定需要的资源,而不是整个资源,比如A账户的 存款,冻结要扣减的50元,而不是A账户的整个账户。然后类似于2PC实现
- 消息事务,rocketmq支持
- 消费者和提供者多数据源公用一张Event表;定时重传+eventid幂等性+消费者集群 redis分布式锁;实现最终一致性,但是要求可以使用公共的一张表。
分布式环境下的定时任务
创建定时任务锁表(lock_corn : id、lock_name、status),不同的业务争夺不同的行锁,互相不影响。
业务逻辑:tryLock() doBusiness() uplock() 在一个事务里面,避免了业务异常导致的加锁成功但解锁失败的情况。
设置的doJob()的隔离性为允许提交读,因为默认mysql的mvcc事务处理机制,事务id自增的特性,避免了重复读取重复消费的问题。
分布式软件的集群管理
redis集群管理
Redis Cluster的主从复制:异步,不保证一致性,主节点写入成功后直接反馈给用户,再通过日志同步给slave,如果反馈给客户端成功而slave未同步时,Master挂掉了,那么从节点晋升为主节点会丢失信息
zookeeper集群管理
特性:
- ZooKeeper通过复制来实现高可用性,只要集合体中半数以上的机器处于可用状态,它就能够提供服务
- 每个节点都有一份完整的数据
- 通过机器id和事务id来选主
一致性原理:
- 各个Server单独相应各自Client的读操作
- 写操作,必须交给Leader,发起投票,超过一半节点写成功的时候,才回传客户端成功,可以确保一致性。
solr_cloud集群管理
https://blog.csdn.net/u011026968/article/details/50336709
- 读操作: 所有的节点都对外提供服务,不分master、slave,而是随机取所有shard里面的任意一个repilca,收集信息,汇总后返回客户
- 写操作:需要转发到leader,如果不是这个shard处理,由该leader转到相应的leader处理,leader处理成功后会转发给replica更新