作者回复: 你要的图来了。我在文中补充了一个流程图,便于同学们理解。
作者回复: 我认真的看了同学的对于秒杀的理解,技术上都没什么问题。
从业务角度,老师有一些不同的看法。
对于秒杀这种场景,宏观上的设计应该是倾向于利用有限的资源处理短时间内海量的请求,保证服务不宕机。有少量请求处理出错(注意是后端错误,用户不可见)或消息丢失,是可以接受的。
毕竟秒杀拼的就是运气,某个用户秒杀请求在处理的时候丢失,和处理成功但没秒到,对于用户来说都是运气不好而已。
基于这样的设计理念,很多保证数据可靠性的做法都可以牺牲掉,用于换取系统更大的吞吐量比较划算。
作者回复: 谢谢你,蔡徐坤同学。
作者回复: 是的,这时候就会有一个消费者没有干活的机会。
至于,如何分配队列与消费者的关系,不同的消息队列处理也不一样,有的消息队列是绑定队列与消费者,这样有一个消费者一直闲着,其它二个一直干活。
也有的消息队列是分时绑定,也就是你干一会儿,我干一会儿,但任何时刻,都会有一个消费者处于闲着的状态。
作者回复: 多个消费组的时候,确实有你说的问题,partition数量需要兼顾所有消费者。一般的做法都是照顾消费最慢的那个消费组,按照它的速度和消费者数量来确定partition数量。
大部分MQ都支持动态扩容,增加分区数量,分区数量变更后,会重新分配消费者和分区对应关系,对生产基本没什么影响。
作者回复: 同学那不是像,消息队列就是个分布式存储系统。
作者回复: 扩容后只需要等一会儿,确保扩容之前的消息都消费完成了(不确定的话可以等久一点儿也没关系)再消费新分区的数据就可以了,生产不需要停。
因为一致性哈希可以保证单调性:如果已经有一些内容通过哈希分派到了相应的分区中,又有新的分区加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的分区中去,而不会被映射到旧的分区集合中的其他分区
作者回复: JSR是一个标准,Spring是JSR的一个实现,并做了很多的扩展。
作者回复: 这个案例中,你说的这种情况是有可能存在的。
是否需要补偿,也无所谓对错,总体效果是一样的。秒杀的目的就是从众多秒杀用户中公平的选择n个用户,补偿或不补偿,影响的只是这n个用户是谁的问题。
所以这是一个架构选择的问题。
我建议是不用补偿,按失败处理,锁定的库存超时未支付后会自动释放,好处是比较简单。
作者回复: 你可以再看一下这课的文本,我补充了一个流程图。
对于你说的这个情况,是不会出现的。因为,后端服务返回的秒杀结果,只会存放在Map中,并不会直接返回APP。
给APP返回结果的,只能是处理APP请求的那个线程。
作者回复: 严格的说,ACI都没实现,只有D实现了。
放宽点儿限制的话,或者考虑实际效果的话,A(原子性)绝大多数情况下还是可以保证的,即“要么都成功,要么都失败”。C(一致性)通过补偿,大部分情况下也可以保证最终一致。
作者回复: 我觉得是Kafka吧,功能足够复杂,而且老外写代码的脑回路和我们不大一样。
作者回复: 其实这些最基础的算法和数据结构的应用是非常广泛的,只是为了方便,被很多中间件或者底层系统给封装后,对一般的业务开发人员就不可见了。
作者回复: 首先强调一下,并不是“每个队列只要一个单线程消费者”,而是“每个队列只能被一个消费者实例占用。”
rocketMQ的MessageListenerConcurrently,和我们上面讲的内容也不矛盾,它这个并行消费是完全在客户端实现的。大致的原理就是:
1. 客户端从服务端的某个队列读取一批消息;
2. 分发给客户端的多个线程消费;
3. 都消费成功后,给服务端返回消费成功确认。
作者回复: 队列
作者回复: 消息什么时候删除取决于消息队列的配置,比如Kafka默认就是超过多长时间后就自动删除了。