Nacos源码系列—关于服务注册的那些事

点赞再看,养成习惯,微信搜索【牧小农】关注我获取更多资讯,风里雨里,小农等你,很高兴能够成为你的朋友。
项目源码地址:公众号回复 nacos,即可免费获取源码

简介

首先我们在看Nacos源码之前,要先想想为什么我们要读源码?是为了装杯?还是为了在心仪的女神面前给她娓娓道来展示自己的代码功底?当然不全是!

这都不是我们读源码的最终目的。作为一名技术人,上面的都是浮云,真正激励我们的应该是能够提升我们技术功底和整体技术大局观。此乃大道也!闲言少叙,接下来我们就来看一看,看源码究竟有什么好处

  • 提升技术功底: 当我们去看源码的时候,能够学习源码里面优秀的设计思想,还含有设计模式和并发编程技术,解决问题的思路,能够知其所以然
  • 新技术学习能力: 当我们看多了源码,对于一个新技术或者框架的掌握速度会有大幅度提升,能根据经验或官网资料快速掌握底层的实现,技术更新迭代也可以更快的入手
  • 快速解决问题能力: 遇到问题,尤其是框架源码的问题,能够更快速的定位
  • 面试获取更高成功率: 现在出去面试,一般中高级一点的,都会问到框架源码级别的实现,如果能够说出来,可以提升面试的成功率和薪资待遇,源码面试是区别程序员水平另一面镜子
  • 认识更多圈子: 多活跃开源社区,熟读源码后多思考,发现问题或需求主动参与开源技术研发,与圈内大牛成为为朋友

阅读源码的方法

  1. 搭建入门demo: 我们可以先看一下官网提供的文档,搭建Demo,快速掌握框架的基本使用
  2. 看核心代码: 对于初次看源码的同学,不要太过于关注源码的细枝末节,先把主要核心流程梳理出来,找到其入口,分析静态代码,如果遇到问题,可以进行断点调试。
  3. 绘图和笔记: 梳理好核心功能后,可以用流程图记录下来,好记性不如烂笔头,同时对关键的源码部分可以进行备注,分析参数的变化。同时要善于用Debug,来观看源码的执行过程
  4. 复习总结: 当我们把框架的所有功能点的源码都分析完成后,回到主流程在梳理一遍,最后在自己脑袋中形成一个闭环,这样源码的核心内容和主流程就基本上理解了。

Nacos核心功能点

服务注册: Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如IP地址,端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。

服务心跳: 在服务注册后,Nacos Client会维护一个定时心跳来支持通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。

服务健康检查: Nacos Server会开启一个定时任务用来检查注册服务实例的健康状况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性设置为false(客户端服务发现时不会发现)。如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)

服务发现: 服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存

服务同步: Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性

Nacos源码下载

首先我们需要将Nacos的源码下载下来,下载地址:https://github.com/alibaba/nacos

image.png

我们将源码下下来以后,导入到idea中

proto编译

当我们导入成功以后,会出现程序包com.alibaba.nacos.consistency.entity不存在的错误提示,这是因为Nacos底层的数据通信会基于protobuf对数据做序列化和反序列化,需要先将proto文件编译为对应的Java代码。

image.png

最简单的 不安装任何的东西 idea2021.2已经捆绑安装了这个。


image.png

可以通过mvn copmpile来在target自动生成他们。

image.png

Nacos缺少Istio依赖问题解决

我们只需要在文件根目录下执行以下命令即可:

mvn clean package -Dmaven.test.skip=true -Dcheckstyle.skip=true

做完以上两步,我们就可以启动Nacos的了

启动Nacos

首先我们找到 nacos-console这个模块,这个就是我们的管理后台,找到它的启动类,因为Nacos默认为集群启动,所以我们要设置它为单机启动,方便演示

设置命令:

-Dnacos.standalone=true -Dnacos.home=E:\test\nacos

image.png

启动成功后,账号密码:nacos/nacos

image.png

到这里我们Nacos的源码启动就完成了。

开启源码

我们先从客户端服务的注册开始说起,我们可以先想一想如果Nacos客户端要注册,会把什么信息传递给服务器?
这里我们可以看到在 nacos-client下的NamingTest有这么一些信息


@Ignore
public class NamingTest {
    
    @Test
    public void testServiceList() throws Exception {
        
        //Nacos Server连接信息
        Properties properties = new Properties();
        //Nacos服务器地址
        properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
        //连接Nacos服务的用户名
        properties.put(PropertyKeyConst.USERNAME, "nacos");
        //连接Nacos服务的密码
        properties.put(PropertyKeyConst.PASSWORD, "nacos");
        
        //实例信息
        Instance instance = new Instance();
        //实例IP,提供给消费者进行通信的地址
        instance.setIp("1.1.1.1");
        //端口,提供给消费者访问的端口
        instance.setPort(800);
        //权重,当前实例的权限,浮点类型(默认1.0D)
        instance.setWeight(2);
        Map<String, String> map = new HashMap<String, String>();
        map.put("netType", "external");
        map.put("version", "2.0");
        instance.setMetadata(map);

        //关键代码 创建自己的实例
        NamingService namingService = NacosFactory.createNamingService(properties);
        namingService.registerInstance("nacos.test.1", instance);
        
        ThreadUtils.sleep(5000L);
        
        List<Instance> list = namingService.getAllInstances("nacos.test.1");
        
        System.out.println(list);
        
        ThreadUtils.sleep(30000L);
        //        ExpressionSelector expressionSelector = new ExpressionSelector();
        //        expressionSelector.setExpression("INSTANCE.metadata.registerSource = 'dubbo'");
        //        ListView<String> serviceList = namingService.getServicesOfServer(1, 10, expressionSelector);
        
    }
}

上面就是客户端注册的一个测试类,模仿了真实的服务注册到Nacos的过程,包括NacosServer连接、实例的创建、实例属性的赋值、注册实例,所以在这个其中包含了服务注册的核心代码,从这里我们可以大致看出,它包含了两个类的信息:Nacos Server连接信息和实例信息

Nacos Server连接信息:

从上述中我们可以看到有关于Nacos Server连接信息是存储在Properties中,

  • Server地址:Nacos服务器地址,属性的key为serverAddr;
  • 用户名:连接Nacos服务的用户名,属性key为username,默认值为nacos;
  • 密码:连接Nacos服务的密码,属性key为password,默认值为nacos;

实例信息:

从上述测试中我们可以看到注册实例信息用instance进行承载,而实例信息又分为两部分,一个是基础实例信息,一个是元数据信息

实例基础信息:

  • instanceId:实例的唯一ID;
  • ip:实例IP,提供给消费者进行通信的地址;
  • port: 端口,提供给消费者访问的端口;
  • weight:权重,当前实例的权限,浮点类型(默认1.0D);
  • healthy:健康状况,默认true;
  • enabled:实例是否准备好接收请求,默认true;
  • ephemeral:实例是否为瞬时的,默认为true;
  • clusterName:实例所属的集群名称;
  • serviceName:实例的服务信息;

元数据:

元数据类型为HashMap,从当前Demo我们能够看到的数据只有两个

  • netType:网络类型,这里设置的值为external(外网)
  • version Nacos版本,这里为2.0

除此之外,我们在Instance类中还可以看到一些默认信息,这些方法都是通过get方法进行提供的

  //心跳间隙的key,默认为5s,也就是默认5秒进行一次心跳
    public long getInstanceHeartBeatInterval() {
        return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL,
                Constants.DEFAULT_HEART_BEAT_INTERVAL);
    }

    //心跳超时的key,默认为15s,也就是默认15秒收不到心跳,实例将会标记为不健康;
    public long getInstanceHeartBeatTimeOut() {
        return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,
                Constants.DEFAULT_HEART_BEAT_TIMEOUT);
    }

    //实例IP被删除的key,默认为30s,也就是30秒收不到心跳,实例将会被移除;
    public long getIpDeleteTimeout() {
        return getMetaDataByKeyWithDefault(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
                Constants.DEFAULT_IP_DELETE_TIMEOUT);
    }

    //实例ID生成器key,默认为simple;
    public String getInstanceIdGenerator() {
        return getMetaDataByKeyWithDefault(PreservedMetadataKeys.INSTANCE_ID_GENERATOR,
                Constants.DEFAULT_INSTANCE_ID_GENERATOR);
    }

为什么要说这个呢?从这些参数中我们就可以了解到,我们服务的心跳间隙是多少以及超时时间,传递什么参数配置什么参数,以此来了解我们的实例是否健康。同时我们也可以看到一个比较关键且核心的类,是真正创建实例的类 ——NamingService

NamingService

NamingService是Nacos对外提供的一个统一的接口,当我们点进去查看,可以看到大概一下几个方法,这些方法提供了不同的重载方法,方便我们用于不同的场景。


image.png
//服务实例注册
void registerInstance(...) throws NacosException;

//服务实例注销
void deregisterInstance(...) throws NacosException;

//获取服务实例列表
List<Instance> getAllInstances(...) throws NacosException;

//查询健康服务实例
List<Instance> selectInstances(...) throws NacosException;

//查询集群中健康的服务实例
List<Instance> selectInstances(....List<String> clusters....)throws NacosException;

//使用负载均衡策略选择一个健康的服务实例
Instance selectOneHealthyInstance(...) throws NacosException;

//订阅服务事件
void subscribe(...) throws NacosException;

//取消订阅服务事件
void unsubscribe(...) throws NacosException;

//获取所有(或指定)服务名称
ListView<String> getServicesOfServer(...) throws NacosException;

//获取所有订阅的服务
List<ServiceInfo> getSubscribeServices() throws NacosException;
 
//获取Nacos服务的状态
String getServerStatus();
 
//主动关闭服务
void shutDown() throws NacosException;

NamingService的实例化是通过NacosFactory.createNamingService(properties);实现的,内部源码是通过反射来实现实例化过程

 NamingService namingService = NacosFactory.createNamingService(properties);
 

    public static NamingService createNamingService(Properties properties) throws NacosException {
        try {
            Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
            Constructor constructor = driverImplClass.getConstructor(Properties.class);
            return (NamingService) constructor.newInstance(properties);
        } catch (Throwable e) {
            throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
        }
    }

接下来我们就来看一看NamingService的具体实现

//调用registerInstance方法
namingService.registerInstance("nacos.test.1", instance);
 @Override
    public void registerInstance(String serviceName, Instance instance) throws NacosException {
        //默认的分组为“DEFAULT_GROUP” 
        registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
    }
 @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        //检查心跳时间是否正常
        NamingUtils.checkInstanceIsLegal(instance);
        //通过代理注册服务
        clientProxy.registerService(serviceName, groupName, instance);
    }

心跳检测代码

   //心跳间隙超过限制 返回错误
        if (instance.getInstanceHeartBeatTimeOut() < instance.getInstanceHeartBeatInterval()
                || instance.getIpDeleteTimeout() < instance.getInstanceHeartBeatInterval()) {
            throw new NacosException(NacosException.INVALID_PARAM,
                    "Instance 'heart beat interval' must less than 'heart beat timeout' and 'ip delete timeout'.");
        }

通过代理注册服务,我们了解到clientProxy代理接口是通过NamingClientProxyDelegate来完成,我们可以在init构造方法中得出,具体的实例对象

  private void init(Properties properties) throws NacosException {
        //使用NamingClientProxyDelegate来完成
         this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);
    }
image.png

NamingClientProxyDelegate实现
NamingClientProxyDelegate中,真正调用注册服务的并不是代理实现类,而且先判断当前实例是否为瞬时对象后,来选择对应的客户端代理来进行请求。

  @Override
    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
    }

如果当前实力是瞬时对象,则采用gRPC协议(NamingGrpcClientProxy)进行请求,否则采用Http协议(NamingHttpClientProxy),默认为瞬时对象,在2.0版本中默认采用gRPC协议进行与Nacos服务进行交互

    //判断当前实例是否为瞬时对象
      private NamingClientProxy getExecuteClientProxy(Instance instance) {
        return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
    }

NamingGrpcClientProxy中的实现

    @Override
    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
                instance);
        //数据的缓存
        redoService.cacheInstanceForRedo(serviceName, groupName, instance);
        //gRPC进行服务调用
        doRegisterService(serviceName, groupName, instance);
    }

大体关系图如下所示:

image.png

Nacos客户端在项目的应用

  1. 我们想要让某一个服务注册到Nacos中,首先要引入一个依赖:
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
  1. 在依赖中,去查看SpringBoot自动装配文件自动装配文件META-INF/spring.factories
image.png
  1. 通过SpringBoot的自动装配来加载EnableAutoConfiguration对应的类,这里我们可以看到很多有关于Nacos相关的类,怎么知道哪个是我们真正需要关心的类,服务在注册的时候走的是哪个,一般自动装配,我们都会找到带有“Auto”关键字的文件进行查看,然后在结合我们需要找的,我们是客户端注册服务,所以我们大体可以定位到NacosServiceRegistryAutoConfiguration这个文件
image.png
  1. 查看NacosServiceRegistryAutoConfiguration源码,在这里我们只需要关注最核心的nacosAutoServiceRegistration方法
image.png
image.png

而我们真正关心的只有三个类NacosAutoServiceRegistration类是注册的核心,我们来看一下它的继承关系

image.png
    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosAutoServiceRegistration nacosAutoServiceRegistration(
            NacosServiceRegistry registry,
            AutoServiceRegistrationProperties autoServiceRegistrationProperties,
            NacosRegistration registration) {
          return new NacosAutoServiceRegistration(registry,
          autoServiceRegistrationProperties, registration);
    }
  1. 从上述内容中我们可以知道,Nacos服务自动注册是从NacosServiceRegistryAutoConfiguration类开始的,并自动注册到NacosAutoServiceRegistration类中。

在下图中我们可以看到,主要是调用了super 方法,所以我们继续查看该类的构造方法:AbstractAutoServiceRegistration

public class NacosAutoServiceRegistration
        extends AbstractAutoServiceRegistration<Registration> {

      public NacosAutoServiceRegistration(ServiceRegistry<Registration> serviceRegistry,
          AutoServiceRegistrationProperties autoServiceRegistrationProperties,
          NacosRegistration registration) {
        super(serviceRegistry, autoServiceRegistrationProperties);
        this.registration = registration;
      }

}

AbstractAutoServiceRegistration实现了ApplicationListener接口,用来监听Spring容器启动过程中WebServerInitializedEvent事件,一般如果我们实现这个类的时候,会实现一个方法onApplicationEvent(),这个方法会在我们项目启动的时候触发

image.png
    @Override
    @SuppressWarnings("deprecation")
    public void onApplicationEvent(WebServerInitializedEvent event) {
        bind(event);
    }

由此我们可以看到bind里面的这个方法

    @Deprecated
    public void bind(WebServerInitializedEvent event) {
  //获取 ApplicationContext对象
        ApplicationContext context = event.getApplicationContext();
    //判断服务的 Namespace
        if (context instanceof ConfigurableWebServerApplicationContext) {
            if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
                return;
            }
        }
    //记录当前服务的端口
        this.port.compareAndSet(0, event.getWebServer().getPort());
    //【核心】启动注册流程
        this.start();
    }

start()方法调用register();方法来注册服务

    public void start() {
        if (!isEnabled()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Discovery Lifecycle disabled. Not starting");
            }
            return;
        }

        // only initialize if nonSecurePort is greater than 0 and it isn't already running
        // because of containerPortInitializer below
    //如果服务是没有运行状态时,进行初始化
        if (!this.running.get()) {
    //发布服务开始注册事件
            this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
      //【核心】注册服务
            register();
            if (shouldRegisterManagement()) {
                registerManagement();
            }
      //发布注册完成事件
            this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
      //服务状态设置为运行状态
            this.running.compareAndSet(false, true);
        }

    }

NacosServiceRegistry.register()方法,如下所示:

@Override
    public void register(Registration registration) {
 //判断ServiceId是否为空
        if (StringUtils.isEmpty(registration.getServiceId())) {
            log.warn("No service to register for nacos client...");
            return;
        }
 //获取Nacos的服务信息
        NamingService namingService = namingService();
  //获取服务ID和分组
        String serviceId = registration.getServiceId();
        String group = nacosDiscoveryProperties.getGroup();
    //构建instance实例(IP/Port/Weight/clusterName.....)
        Instance instance = getNacosInstanceFromRegistration(registration);

        try {
     //向服务端注册此服务
            namingService.registerInstance(serviceId, group, instance);
            log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
                    instance.getIp(), instance.getPort());
        }
        catch (Exception e) {
            if (nacosDiscoveryProperties.isFailFast()) {
                log.error("nacos registry, {} register failed...{},", serviceId,
                        registration.toString(), e);
                rethrowRuntimeException(e);
            }
            else {
                log.warn("Failfast is false. {} register failed...{},", serviceId,
                        registration.toString(), e);
            }
        }
    }

NacosNamingService.registerInstance()方法,如下:

    @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        //检查超时参数是否异常,心跳超时时间(15s)必须大于心跳间隙(5s)
        NamingUtils.checkInstanceIsLegal(instance);
        //拼接服务名,格式:groupName@@serviceName
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
        //判断是否为临时实例,默认为true
        if (instance.isEphemeral()) {
            //临时实例,定时向Nacos服务发送心跳
            BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
            beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
        //【核心】发送注册服务实例请求
        serverProxy.registerService(groupedServiceName, groupName, instance);
    }

registerService中我们可以看到Nacos服务注册接口需要的完整参数

    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        
        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
                instance);
        
        final Map<String, String> params = new HashMap<String, String>(16);
        //环境
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        //服务名称
        params.put(CommonParams.SERVICE_NAME, serviceName);
        //分组名称
        params.put(CommonParams.GROUP_NAME, groupName);
        //集群名称
        params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
        //当前实例IP
        params.put("ip", instance.getIp());
        //当前实例端口
        params.put("port", String.valueOf(instance.getPort()));
        //权重
        params.put("weight", String.valueOf(instance.getWeight()));
        params.put("enable", String.valueOf(instance.isEnabled()));
        params.put("healthy", String.valueOf(instance.isHealthy()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
        
        reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
        
    }

补充

在这里我们会发现我们请求实例接口的地址为/nacos/v1/ns/instance,其实这个在官网中也有提供对应的地址给我们,并且是对应的

image.png

image.png
image.png

客户端注册流程图

image.png

总结

以上就是Nacos的客户端注册流程,阅读源码并没有我们想象中的那么难,道阻且长,行之将至,当你开始行动的时候,你就已经开始进步了,别管学多少,如果您对文中有疑问或者问题,欢迎在下方留言,小农看见了会第一时间回复,大家加油~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容