Contents
  1. 1. 常见问题列表
  2. 2. 实现一个集群模式下系统生成唯一流水号的算法:流水号不能重复、不能生成一次访问一次数据库
  3. 3. 分布式锁的实现方式和原理
  4. 4. Redis 请求竞争
  5. 5. Redis 持久化方案和区别
  6. 6. Redis和Memcached的区别
    1. 6.1. Memcached的内存管理机制Slab Allocation
  7. 7. 秒杀架构的实现
    1. 7.1. 思路
    2. 7.2. 架构图
  8. 8. 分布式事务的解决方案
    1. 8.1. 单应用多数据源的业务场景
    2. 8.2. 分布式应用+独立数据源
  9. 9. 分布式环境下的定时任务
  10. 10. 分布式软件的集群管理
    1. 10.1. redis集群管理
    2. 10.2. zookeeper集群管理
    3. 10.3. solr_cloud集群管理

常见问题列表

  • 分布式锁的实现(Done)
  • 分布式事务的实现(包括2PC、幂等性和重传)
  • 秒杀架构的实现(Done)
  • 分布式定时任务(Done)

实现一个集群模式下系统生成唯一流水号的算法:流水号不能重复、不能生成一次访问一次数据库

  1. 利用redis的incr,生成一个唯一编号作为结尾 LSH-机器编号-时间-唯一编号:LSH-Server1-2010808161617-1
  2. 利用zk的临时节点,zkClient.createNode,获取到名字之后,拼上一些流水信息,实现全局唯一
  3. 利用server编号+时间戳+加锁生成的一个自增值(保证这个server上唯一),比如 LSH-Server1-2010808161617-1
  4. 如果可以利用数据库自增的话,那就更好实现了,插入一个数据,利用ID自增的原理,获取到全局唯一的序号,然后同上

分布式锁的实现方式和原理

  1. zookeeper
  • 临时顺序节点,获取Children名字排序,首节点获取锁,次节点设置监听等待的方式,最为常用。
  • zk的永久节点,命名唯一的特性,创建成功的client成功获取锁,释放锁的时候,删除该永久节点。
  1. redis乐观锁(watch锁,开启事务,执行业务操作,锁值自增,提交)、悲观锁(SETNX原子操作,再引入setex破解异常,一般秒杀用悲观锁,因为竞争激烈)

  2. 数据库乐观锁(版本号)、Unique外键的形式、独占锁 select for update等形式

Redis 请求竞争

多个客户端同时对某个key的value进行操作,比如秒杀商品。

  1. 乐观锁策略(在竞争不激烈的时候,性能好)
  • watch方法,带事务的修改数据,类似于volaile,如果price发生改动,则事务失效
    watch price
    get price $price
    $price = $price + 10
    multi
    set price $price
    exec
  1. 悲观锁+expire的实现策略

Redis 持久化方案和区别

  1. RDB的优点:
  • 只一个文件备份文件,容易恢复数据和备份
  • 性能最大化,fork子线程做持久化的操作
  • 相比于AOF机制,如果数据集很大,RDB的启动效率会更高,因为是二进制文件
  • 相同大小的存储内容,RDB是二进制文件,存储的空间更小,节省磁盘空间
  1. RDB的缺点:
  • 数据不是百分百安全,未持久化的数据可能会丢失
  • 当内存中的数据特别大的时候,因为每次都是生成一个完整的快照文件,速度会很慢
  1. AOF的优点:
  • 可读性强,AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建,而RDB是二进制文件
  • 数据集文件过大会自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性
  • AOF 文件的格式可读性较强,提供了更灵活的处理方式。例如,如果我们不小心错用了 FLUSHALL 命令,在重写还没进行时,我们可以手工将最后的 FLUSHALL 命令去掉,然后再使用 AOF 来恢复数据
  1. 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

思路

  1. 页面做控制(页面jsp加载的时候,获取到后台的活动开始时间)
  • 按钮做控制:活动开始前提交按钮置灰防止提前抢单
  • 按钮做控制:开始时提交成功后置灰按钮,防止重复提交
  1. 防止恶意的抢单
  • 控制层对请求的时间做校验,防止通过url或者程序恶意抢单,没开始就抢单了,超出了js控制
  • redis做IP限流
  • redis做用户名限流
  1. 缓存库存(Redis)
    不管活动开始了,还是活动没开始,查询库存的时候,都是从redis获取库存

  2. 分布式悲观锁(参考redis悲观锁的代码)

  • 悲观锁(因为肯定争抢严重)
  • Expire时间(抢到锁后,立刻设置过期时间,防止某个线程的异常停摆,导致整个业务的停摆)
  • 定时循环和快速反馈(for缓存有超时设置,每次超时后,重新读取一次库存,还有货再进行第二轮的for循环争夺,实现快速反馈,避免没有货了还在持续抢锁)
  1. 异步处理订单
  • redis抢锁成功后,记录抢到锁的用户信息后,就可以直接释放锁,并反馈用户,通过异步的方式来处理订单,提升秒杀的效率
  • 为了避免异步的数据不同步,需要抢到锁的时候,在redis里面缓存用户信息列表,缓存结束后,触发抢单成功用户信息持久化,并且定时的比对一致性
  1. 消息队列削峰限流(RocketMQ自带的Consumer自带线程池和限流措施),集群。一般都是微服务,订单中心、库存中心、积分中心、用户的商品中心

  2. 数据库的并发修改(库存总数的扣减)

  • 库存数据先加锁读取再修改的方式(select * for update 再 update 库存)避免集群模式下的修改无效的情况
  • 扣减的时候再check一下,防止异常的消费,例如库存不能为负

架构图

Nginx在前端–> 对应多台集群的机子前端控制层(嵌套上Redis) –>MQ –>服务端处理订单 –> RPC更新各个微服务的库存中心、订单中心、积分中心等等完成订单

分布式事务的解决方案

单应用多数据源的业务场景

  1. 可以通过java的方式解决,设置不自动提交,然后开启事务,挨个提交,做好tryCatch,如果出现异常就回滚,可能要写补偿接口。如果A提交成功,B提交失败,那么需要调用A的补偿接口。
  2. JTA协议,atomikos 实现分布式事务,主要是DataSource要使用AtomikosDataSourceBean,管理器就可以实现。资源管理器绑定在一个公用的事务管理器上面,然后砽事务管理器 开启事务,执行事务,提交事务。

分布式应用+独立数据源

https://blog.csdn.net/z69183787/article/details/80235844

  1. 2PC:prepare阶段,check资源可用且业务能通畅,然后对要操作的数据加锁;如果全部锁定成功,就一起执行commit操作,如果有失败的则先重试(重试的时候要保证幂等性,可用通过添加ticketID的方法实现),重试一定次数失败,就调用补偿接口,回滚已经提交的资源。
  2. TCC:事务补偿。try阶段,通过业务的方式,仅仅锁定需要的资源,而不是整个资源,比如A账户的 存款,冻结要扣减的50元,而不是A账户的整个账户。然后类似于2PC实现
  3. 消息事务,rocketmq支持
  4. 消费者和提供者多数据源公用一张Event表;定时重传+eventid幂等性+消费者集群 redis分布式锁;实现最终一致性,但是要求可以使用公共的一张表。

分布式环境下的定时任务

创建定时任务锁表(lock_corn : id、lock_name、status),不同的业务争夺不同的行锁,互相不影响。
业务逻辑:tryLock() doBusiness() uplock() 在一个事务里面,避免了业务异常导致的加锁成功但解锁失败的情况。
设置的doJob()的隔离性为允许提交读,因为默认mysql的mvcc事务处理机制,事务id自增的特性,避免了重复读取重复消费的问题。

@Transactional(isolation=Isolation.READ_COMMITTED)
doJob(String lockName){
tryLock(lockName);
sleep(500);//等待事务完全提交到mysql上
doBusiness();
uplock(lockName) ;
}
tryLock(String lockName) 阻塞等待锁,可以复用的,不需要返回值: update set Status=“LOCKED” where lockName="wx_push"
doBusiness(): checkTask(), excuteTask();
unlock(String lockName): update set Status=“OPEN” where lockName="wx_push"

分布式软件的集群管理

redis集群管理

Redis Cluster的主从复制:异步,不保证一致性,主节点写入成功后直接反馈给用户,再通过日志同步给slave,如果反馈给客户端成功而slave未同步时,Master挂掉了,那么从节点晋升为主节点会丢失信息

zookeeper集群管理

特性:

  1. ZooKeeper通过复制来实现高可用性,只要集合体中半数以上的机器处于可用状态,它就能够提供服务
  2. 每个节点都有一份完整的数据
  3. 通过机器id和事务id来选主

一致性原理:

  1. 各个Server单独相应各自Client的读操作
  2. 写操作,必须交给Leader,发起投票,超过一半节点写成功的时候,才回传客户端成功,可以确保一致性。

solr_cloud集群管理

https://blog.csdn.net/u011026968/article/details/50336709

  1. 读操作: 所有的节点都对外提供服务,不分master、slave,而是随机取所有shard里面的任意一个repilca,收集信息,汇总后返回客户
  2. 写操作:需要转发到leader,如果不是这个shard处理,由该leader转到相应的leader处理,leader处理成功后会转发给replica更新
Contents
  1. 1. 常见问题列表
  2. 2. 实现一个集群模式下系统生成唯一流水号的算法:流水号不能重复、不能生成一次访问一次数据库
  3. 3. 分布式锁的实现方式和原理
  4. 4. Redis 请求竞争
  5. 5. Redis 持久化方案和区别
  6. 6. Redis和Memcached的区别
    1. 6.1. Memcached的内存管理机制Slab Allocation
  7. 7. 秒杀架构的实现
    1. 7.1. 思路
    2. 7.2. 架构图
  8. 8. 分布式事务的解决方案
    1. 8.1. 单应用多数据源的业务场景
    2. 8.2. 分布式应用+独立数据源
  9. 9. 分布式环境下的定时任务
  10. 10. 分布式软件的集群管理
    1. 10.1. redis集群管理
    2. 10.2. zookeeper集群管理
    3. 10.3. solr_cloud集群管理