RocketMQ实现原理
一、MQ协议AMQP
应用场景:主要是面向服务端,机器数量不会超过1万台,需要高性能、高吞吐量,RabbitMQ就是基础此协议。
AMQP全称Advanced Message Queuing Protocol(高级消息队列协议), 是一个二进制协议,具有以下特性:多通道、可协商、异步、安全、高效等。协议主要分为两层:
功能层:定义了一系列指令在应用层工作。
传输层:通过多路复用、分帧、内容编码、心跳检测、数据定义、错误处理等方式,实现数据在 客户端<-->服务端 双端的传输。
分层的好处:可以用任意协议替换传输层,而不需要修改功能层。也可以对不同的高级协议使用相同的传输层。
整体架构
AMQ模型
数据流:生产者把消息发送到服务端,由Exchange将消息路由到不同的消息队列中,消息队列进行存储然后再消息转发给消费者。
更多细节可以查看:https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdfMQTT协议
应用场景:面向移动端,海量连接,使用卫星网络,带宽小、不稳定性因素多。实现有Mosquitto。
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议) ,是用于消息队列服务的轻量级、发布订阅、端到端网络协议,运行在TCP/IP协议之上,为资源或网络带宽有限的远程设备提供实时有效的消息服务。
优点:格式简洁、占用带宽小、移动端通信、PUSH、嵌入式系统。
MQTT架构
用温度传感器的例子去理解整体架构:发布者首先定义一个温度Topic,随后发布温度值到队列中,消息发布后,另一侧的手机或PC设备订阅温度Topic,然后接收到发布的温度值消息。Kafka、RocketMQ
Kafka、RocketMQ都是使用的自定义协议,用于处理海量流式数据,具有分布式、高性能、高可靠、低延迟、高扩展性等特征。
Kafka架构图
RocketMQ架构图
二、使用场景异步解耦
用户支付完订单后,比较耗时并且不需要同步反馈的动作放入mq中,后续物流、短信等系统消费到后再进行对应的业务处理,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦。削峰
比如618、双11这种大促活动,C端流量很大,比如用户购买商品时,需要扣减库存,但是库存系统无法承载海量调用量,则需要把跟库存相关的操作都放于队列中。
同时可以利用时间窗口降低数据库压力:比如热门商品的库存是1000,共有900人在一秒内秒杀了,则可以通过设置消费时间窗口为1s,将相同商品的库存扣减聚合起来,只对数据库进行一次更新。延迟消息
定点发布文章、视频等等类似的场景,都可以利用延迟消息的特性。事务消息
需要保证两个系统之前的数据最终一致,比如订单生成成功则一定要发出通知,失败则不发通知。三、架构设计
详情:https://github.com/apache/rocketmq/tree/develop/docs/cn技术架构
RocketMQ架构上主要分为四部分,如上图所示:Producer:消息发布的角色,支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。Consumer:消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制,可以满足大多数用户的需求。NameServer:NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。主要包括两个功能:Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以动态感知Broker的路由的信息。BrokerServer:Broker主要负责消息的存储、投递和查询以及服务高可用保证,为了实现这些功能,Broker包含了以下几个重要子模块。Remoting Module:整个Broker的实体,负责处理来自clients端的请求。Client Manager:负责管理客户端(Producer/Consumer)和维护Consumer的Topic订阅信息Store Service:提供方便简单的API接口处理消息存储到物理硬盘和查询功能。HA Service:高可用服务,提供Master Broker 和 Slave Broker之间的数据同步功能。Index Service:根据特定的Message key对投递到Broker的消息进行索引服务,以提供消息的快速查询。
为什么RocketMQ不使用Zookeeper作为注册中心呢?
我认为有以下几个点是不使用zookeeper的原因:根据CAP理论,同时最多只能满足两个点,而zookeeper满足的是CP,也就是说zookeeper并不能保证服务的可用性,zookeeper在进行选举的时候,整个选举的时间太长,期间整个集群都处于不可用的状态,而这对于一个注册中心来说肯定是不能接受的,作为服务发现来说就应该是为可用性而设计。基于性能的考虑,NameServer本身的实现非常轻量,而且可以通过增加机器的方式水平扩展,增加集群的抗压能力,而zookeeper的写是不可扩展的,而zookeeper要解决这个问题只能通过划分领域,划分多个zookeeper集群来解决,首先操作起来太复杂,其次这样还是又违反了CAP中的A的设计,导致服务之间是不连通的。持久化的机制来带的问题,ZooKeeper 的 ZAB 协议对每一个写请求,会在每个 ZooKeeper 节点上保持写一个事务日志,同时再加上定期的将内存数据镜像(Snapshot)到磁盘来保证数据的一致性和持久性,而对于一个简单的服务发现的场景来说,这其实没有太大的必要,这个实现方案太重了。而且本身存储的数据应该是高度定制化的。消息发送应该弱依赖注册中心,而RocketMQ的设计理念也正是基于此,生产者在第一次发送消息的时候从NameServer获取到Broker地址后缓存到本地,如果NameServer整个集群不可用,短时间内对于生产者和消费者并不会产生太大影响。部署架构
RocketMQ 网络部署特点NameServer是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。Broker部署相对复杂,Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave 的对应关系通过指定相同的BrokerName,不同的BrokerId 来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。每个Broker与NameServer集群中的所有节点建立长连接,定时注册Topic信息到所有NameServer。 注意:当前RocketMQ版本在部署架构上支持一Master多Slave,但只有BrokerId=1的从服务器才会参与消息的读负载。Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic 服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,消费者在向Master拉取消息时,Master服务器会根据拉取偏移量与最大偏移量的距离(判断是否读老消息,产生读I/O),以及从服务器是否可读等因素建议下一次是从Master还是Slave拉取。
结合部署架构图,描述集群工作流程:启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。四、工作流程4.1 Topic创建、更新4.11 集群模式
假设ClusterA下存在broker-a、broker-b,在ClusterA中创建TopicA,读写queue均设置为3,则会分别在broker-a和broker-b中创建相同数量的读写queue。4.12 broker模式
以broker模式创建的Topic在不同的broker之间的queue允许不同,比如TopicA在broker-a中读写queue为6,而broker-b中的读写queue设置为4。4.13 读写queue
为什么需要分别设置读和写queue?
用途:topic路由信息
1、当w=r时,同一个组的consumer会均分队列进行消费
如下,w=r=5
2、当w > r时,会导致部分queue无法消费
如下场景,w=5, r=4,会出现queue4无人消费情况。
3、特别说明,当consumer实例数量>读queue数量时,会导致部分consumer没有消费
设置读写队列主要针对topic扩缩容场景,考虑如下情况:
topic原来w=5,r=5; 现在需要进行缩容,则先把w=3,r=5,待queue3、queue4消费完成后,再把r置为3
初始如下:
缩容,把w设置为3:
queue-3、queue-4消费完成,设置r为3,完成缩容:
4.2 消息发送
以broker双主模式部署举例,TopicA在broker-a中读写队列数量为4,在broker-b中读写队列数量为4,则消息发送过程如下:
4.3 消息存储
消息发送到broker后,数据存储流程如下:
CommitLog:消息主体以及元数据的存储主体,单个文件大小默认1G,思考大小为什么是1G?
ConsumeQueue:ConsumeQueue(逻辑消费队列)作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset,consumequeue文件可以看成是基于topic的commitlog索引文件,故consumequeue文件夹的组织方式如下:topic/queue/file三层组织结构。
IndexFile:IndexFile(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。
Broker单个实例下所有的队列共用一个日志数据文件(即为CommitLog)来存储。RocketMQ的混合型存储结构(多个Topic的消息实体内容都存储于一个CommitLog中)针对Producer和Consumer分别采用了数据和索引部分相分离的存储结构,Producer发送消息至Broker端,然后Broker端使用同步或者异步的方式对消息刷盘持久化,保存至CommitLog中。只要消息被刷盘持久化至磁盘文件CommitLog中,那么Producer发送的消息就不会丢失。
消息刷盘
(1) 同步刷盘:如上图所示,只有在消息真正持久化至磁盘后RocketMQ的Broker端才会真正返回给Producer端一个成功的ACK响应。同步刷盘对MQ消息可靠性来说是一种不错的保障,但是性能上会有较大影响,一般适用于金融业务应用该模式较多。
(2) 异步刷盘:能够充分利用OS的PageCache的优势,只要消息写入PageCache即可将成功的ACK返回给Producer端。消息刷盘采用后台异步线程提交的方式进行,降低了读写延迟,提高了MQ的性能和吞吐量。4.4 顺序消息4.41 业务使用场景交易场景中的订单创建、支付、退款等流程,先创建订单才能支付,支付完成的订单才能退款,这需要保证先进先出。同步mysql 的binlong 日志,数据库的操作是有顺序的。其他消息之间有先后的依赖关系,后一条消息需要依赖于前一条消息的处理结果的情况。4.42 分区顺序
一个Partition(queue)内所有的消息按照先进先出的顺序进行发布和消费。
在MQ的模型中,顺序需要由3个阶段去保障:消息被发送时保持顺序。消息被存储时保持和发送的顺序一致。消息被消费时保持和存储的顺序一致。
Producer端
Producer端确保消息顺序唯一要做的事情就是将消息路由到特定的分区,在RocketMQ中,通过MessageQueueSelector来实现分区的选择,比如通过把唯一键如id经过hash算法固定写到同一分区。
Consumer端
如何保证一个队列只被一个消费者消费?
创建消息拉取任务时,消息客户端向broker端申请锁定MessageQueue,使得一个MessageQueue同一个时刻只能被一个消费客户端消费。
消息消费时,多线程针对同一个消息队列的消费先尝试使用synchronized申请独占锁,加锁成功才能进行消费,使得一个MessageQueue同一个时刻只能被一个消费客户端中一个线程消费。4.43 全局顺序
需要设置topic下读写队列数量为1,一个Topic内所有的消息按照先进先出的顺序进行发布和消费.但是全局顺序极大的降低了系统的吞吐量,不符合mq的设计初衷。4.5 延迟消息
场景:用户下单后如果30分钟未支付,则该订单需要被关闭。RocketMQ支持发送延迟消息,但不支持任意时间的延迟消息的设置,仅支持内置预设值的延迟时间间隔的延迟消息;预设值的延迟时间间隔为:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h;在消息创建的时候,调用 setDelayTimeLevel(int level) 方法设置延迟时间;broker在接收到延迟消息的时候会把对应延迟级别的消息先存储到对应的延迟队列中,等延迟消息时间到达时,会把消息重新存储到对应的topic的queue里面。
思考:如何实现任意粒度的延迟消息?4.6 事务消息
事务消息流程:
1.事务消息发送及提交:
(1) 发送消息(half消息)。
(2) 服务端响应消息写入结果。
(3) 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)。
(4) 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)
2.补偿流程:
(1) 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次"回查"
(2) Producer收到回查消息,检查回查消息对应的本地事务的状态
(3) 根据本地事务状态,重新Commit或者Rollback
其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。
还未commit的半消息对用户不可见,那么,如何做到写入消息但是对用户不可见呢?
RocketMQ事务消息的做法是:如果消息是half消息,将备份原消息的主题与消息消费队列,然后改变主题为RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题,故消费端无法消费half类型的消息,然后RocketMQ会开启一个定时任务,从Topic为RMQ_SYS_TRANS_HALF_TOPIC中拉取消息进行消费,根据生产者组获取一个服务提供者发送回查事务状态请求,根据事务状态来决定是提交或回滚消息。
用Op消息标识事务消息已经确定的状态(Commit或者Rollback)。如果一条事务消息没有对应的Op消息,说明这个事务的状态还无法确定(可能是二阶段失败了)。引入Op消息后,事务消息无论是Commit或者Rollback都会记录一个Op操作。Commit相对于Rollback只是在写入Op消息前创建Half消息的索引。4.7 死信队列
死信队列用于处理无法被正常消费的消息。当一条消息初次消费失败,消息队列会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。
RocketMQ将这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),将存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。在RocketMQ中,可以通过使用console控制台对死信队列中的消息进行重发来使得消费者实例再次进行消费。4.8 Rebalance
触发Rebalance的根本因素无非是两个:1 ) 订阅Topic的队列数量变化 2)消费者组信息变化。导致二者发生变化的典型场景如下所示:broker宕机broker升级等运维操作队列扩容/缩容
topic queue变更&& broker实例变更
broker日常运维时的停止/启动或者broker异常宕机,也有可能导致队列数量发生变化,不论是停止/启动/扩容导致的所有变化最终都会上报给NameServer。客户端可以给NameServer发送GET_ROUTEINTO_BY_TOPIC请求,来获得某个Topic的完整路由信息。如果发现队列信息发生变化,则触发Reabalance。
consumer 变更在启动时,消费者会立即向所有Broker发送一次发送心跳(HEART_BEAT)请求,Broker则会将消费者添加由ConsumerManager维护的某个消费者组中。然后这个Consumer自己会立即触发一次Rebalance。在运行时,消费者接收到Broker通知会立即触发Rebalance,同时为了避免通知丢失,会周期性触发Rebalance;当停止时,消费者向所有Broker发送取消注册客户端(UNREGISTER_CLIENT)命令,Broker将消费者从ConsumerManager中移除,并通知其他Consumer进行Rebalance。
影响消费暂停:考虑在只有Consumer 1的情况下,其负责消费所有5个队列;在新增Consumer 2,触发Rebalance时,需要分配2个队列给其消费。那么Consumer 1就需要停止这2个队列的消费,等到这两个队列分配给Consumer 2后,这两个队列才能继续被消费。重复消费:Consumer 2 在消费分配给自己的2个队列时,必须接着从Consumer 1之前已经消费到的offset继续开始消费。然而默认情况下,offset是异步提交的,如consumer 1当前消费到offset为10,但是异步提交给broker的offset为8;那么如果consumer 2从8的offset开始消费,那么就会有2条消息重复。也就是说,Consumer 2 并不会等待Consumer1提交完offset后,再进行Rebalance,因此提交间隔越长,可能造成的重复消费就越多。消费突刺:由于rebalance可能导致重复消费,如果需要重复消费的消息过多;或者因为rebalance暂停时间过长,导致积压了部分消息。那么都有可能导致在rebalance结束之后瞬间可能需要消费很多消息五、缺点
架构设计决定了数据需保存在本地,本地数据的管理限制了扩展性,提高了运维成本,同时吞吐量受本地磁盘的限制。新一代计算存储分离的云原生消息队列应运而生,具体可查看pulsar的相关设计:https://pulsar.apache.org/docs/2.10.x/concepts-architecture-overview。
爱情和学习,其实是一回事越来越发现,教人学习进步,和教人学会处理亲密关系,是同一件事。01hr亲密关系好,原生家庭幸福,家庭教育适当的人,能更好的去爱,沐浴在融洽和谐幸福美满的关系里,并把这种爱遗传给下一
不要让爱你的人等太久,时间久了爱情就凉了不要让爱你的人等太久,时间久了爱情就凉了秋日生活打卡季你等我一会,我马上就到。然而,我等了10分钟,30分钟,一个小时,两个小时,你的身影依然没有出现在我的眼前。你等我把这局玩完,
凯尔特人允许污点主帅执教篮网,他们会不会有一天为此后悔新赛季以2胜5负开局后,执教了布鲁克林篮网161场常规赛16场季后赛的史蒂夫纳什毫不意外地下课了。篮网球迷闻讯赶来,额手称庆。篮网总经理肖恩马克斯在篮网对阵公牛的比赛前宣布了纳什卸
1962年中印战争,印度傻眼,巴铁狂喜,美苏直呼印军烂泥扶不上墙发生在1962年的那一场中印战争,不仅世界没有看明白,估计印度人自己都没有想清楚印度是哪来的勇气挑衅中国的呢?虽然当时我们并不强大,虽然印度从英国人那里捡了不少的洋落,但是我们同样
资治通鉴职场智慧用人察人不善,就是自掘坟墓秦朝后期,陈胜吴广刘邦项梁纷纷相继造反,此时李斯等人,多次陈述天下贫苦,各地纷纷起义,劝告秦二世派军平叛,并减少赋税减少宫殿建筑,休养生息,但宠臣赵高曾多次对秦二世胡亥说关东的盗贼
中美终极PK陆地文明与海洋文化之间的博弈曙光初现陆地文明与海洋文化的最早沟通,是明代郑和下西洋浩荡的船队,沿着海岸线展示东方繁华盛世。话说赠人玫瑰,手有余香,然而君子无罪,怀璧其罪。满船琳琅满目的货物,身怀技艺的工匠,除了带给西
皇太极之死清朝一共有十二位帝王努尔哈赤,皇太极,顺治,康熙,雍正,乾隆,嘉庆,道光,同治,光绪,宣统。其中皇太极,为大清国的建立奠定了坚实的基础,下面小编为大家介绍一下吧!天聪初年,皇太极基
中国历史上三大未解之谜,每一个都让人匪夷所思,你都知道几个?神秘消失的古国,呼风唤雨的大魔导师,这一切的背后究竟隐藏着什么?中国史上三大未解之谜,每一个都令人匪夷所思。大魔导师汉光武帝刘秀的一生可谓是开挂的一生。他本来也是汉室子孙,可由于推
胡天殿事件,拉开了羯族灭亡的序幕羯族是我国历史上非常神秘的一个民族,史载羯族人高鼻深目,金发碧眼,胡须茂密具有明显的白种人特征。羯人很有可能是来自黑海一带的白种人。羯族在五胡乱华时入主中原并掀起了一阵阵腥风血雨,
天子守国门,君王死社稷突然看到一段文字,说明朝是中国历史上最有骨气的王朝,在明朝十二世十六帝276年的历史上,虽历经无数战阵,不乏君王被俘,但不和亲不赔款不割地不纳贡的方针贯穿了整个有明一代。明朝建立之
故事康熙王朝名震一方的奇人!沂州私塾先生留下什么奇闻异事?苏烟客,大清康熙年间,沂州府蒙阴县以教书为业的一个怪人。烟客先生没有人知道他的籍贯所在,没有人认识他的亲戚家人,甚至都没有人知道他的真正名字。人们只知道他有一个特殊的爱好喜欢一个人
一个人得遇贵人,有这几个征兆01hr赏识你,甘愿做铺路石。大宋文豪苏轼的成名,得益于欧阳修的慧眼识珠。二十岁那年,苏轼参加进士考试。考场上,他苦心经营三易其稿,以六百余字汇成精华的以仁治国之论刑赏忠厚之至论,
剧透来袭!好事连连从宜阳开始乐游宜阳旅游推介会举行2023,记忆中的年味久别重逢,富美宜阳烹制文旅大餐,让新年更有盼头。今日上午,宜阳县2023年双节好事连连从宜阳开始乐游宜阳旅游推介会在宜阳县会议中心举行,诸多领导嘉宾景区负责人
自驾返乡过年,车辆检查必不可少春节假期将至,许多市民开始为回家和出行旅游做计划。随着经济发展人民逐渐富裕,越来越多的人选择自驾出行。这种返乡方式不仅快捷自由,也是减少因抢不到票而耽误回家行程的几率。不过,市民在
(新华全媒新春走基层)极寒之下的忙碌劲新疆阿勒泰地区禾木村地处阿尔泰山深处,距离乌鲁木齐600多公里,该村依托壮美的自然风光发展旅游推动乡村振兴,冬季到禾木看雪已是特色冰雪旅游名片。近日,受寒潮影响,禾木村经历暴雪超低
观世音菩萨的传说在中国民间东南亚国家以及其它西方国家的华人中,信奉观世音菩萨的人非常多,说有多少亿人信观音菩萨都不为过的。因为观世音菩萨是慈悲的化身,她搭救一切生活在苦难中的善男信女们。当人们说起
自成之败亡老蒋读明末史之二十一四月山海关战后,自成逃至北京,仅余三万人。遂勿忙举办登基大典,杀三桂家大小三十四口,火烧紫禁城,次日逃往西安。大顺军由晋豫两路撤兵,节节败退,降者甚多,自成疑心日重,终妄杀李岩等人
馆陶在春秋时属晋冠氏邑,在春秋3400年的跨度中,冠氏设于哪年河北省馆陶县和山东省冠县两地隔卫运河相望,今天虽然分属两省,但是它们的渊源极深,不说近代因行政区划变更造成原馆陶县的一部分被划归到冠县,就从历史上来说,两者也是有着同一起源。在两地
航拍夏村镇冷家村,原名孙家冷家村位于夏村镇西北部,东与肖家毗连。西北部有低矮山丘,南部为平原。有耕地887亩,其中泊地400亩,丘陵地487亩,全村居民218户,570人。明嘉靖年间(15221566年),
此情无计可消除,欲语泪先流李清照与赵明城郎才女貌的天意姻缘成婚之前的李清照与赵明诚都已经是当时青年人中的佼佼者,尤其是李清照,出身书香门第,自幼博览群书,习音律学弈棋攻书画,而且通晓历史掌故,以诗名闻名当时。李清照与赵明
罗马共和制罗马共和后期,监察官为何形同虚设?引言自公元前509年罗马共和制建立以来,执政官的选举加剧了罗马平民与贵族之间的阶级斗争。执政官皆在贵族组成的百人团大会中选出,这也造成了国家的权力皆被贵族阶级掌控。随着罗马经济的发
秦二世的鸵鸟行为秦二世是史上第一败家子。陈胜吴广起义,有使者来报告秦二世,秦二世嫌烦,不愿意听坏消息,就把使者下狱。后面的使者也不傻,就说都是些小毛贼,已经搞定了。一个CEO,一个leader,当