mybatis源码分析-数据库连接池(二)

Mybatis作为持久化框架,datasource是必不可少的,对应的自然少不了连接池,常用的数据库连接池有c3p0、BasicDatasource、druid。我们一起去看看

数据源类图一波带走不谢
PooledDatasource和UnpooledDataSource是mybatis自定义数据源,从下图不难看出,下面是一个工厂方法。不同的工厂创建其对应的数据源


mybatis数据源类图

工厂模式场景文章参见: http://blog.csdn.net/zhangziwen94nb/article/details/44062793

UnpooledDatasource创建并获取连接的时序图


UnpooledDatasource时序图.png

注册驱动的过程

//UnpooledDataSource.java 
//1.注册驱动
//2.创建连接
//3.设置属性
private Connection doGetConnection(Properties properties) throws SQLException {
//初始化驱动
  initializeDriver();
  //创建连接
Connection connection = DriverManager.getConnection(url, properties);
  //配置数据库autocommit、隔离级别
configureConnection(connection);
  return connection;
}
//通过反射实例化驱动
private synchronized void initializeDriver() throws SQLException {
  if (!registeredDrivers.containsKey(driver)) {
    Class<?> driverType;
    try {
      if (driverClassLoader != null) {
        driverType = Class.forName(driver, true, driverClassLoader);
      } else {
        driverType = Resources.classForName(driver);
      }
      // DriverManager requires the driver to be loaded via the system ClassLoader.
      // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
      Driver driverInstance = (Driver)driverType.newInstance();
      DriverManager.registerDriver(new DriverProxy(driverInstance));
      registeredDrivers.put(driver, driverInstance);
    } catch (Exception e) {
      throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
    }
  }
}
Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();
注意上段代码中: UnpooledDataSource中的registeredDrivers与DriverManager中的registeredDrivers的区别 这2个集合中都存有注册驱动的实例信息,DriverProxy 是对Driver的做了一层代理
//DriverManager.java 驱动管理器注册驱动
public static synchronized void registerDriver(java.sql.Driver driver,
        DriverAction da)
    throws SQLException {

    /* Register the driver if it has not already been added to our list */
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }

    println("registerDriver: " + driver);
} 
CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>()

//针对copyOnwriter的理解可参考文章
http://ifeve.com/java-copy-on-write/
大致意思就是写入时将容器进行拷贝,将元素写入新的容器,完毕后将原有引用指向新的元素,这样目的可以提高并发读性能。

创建连接

//DriverManager.java getConnection() 
private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    //获取类加载器
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
    //创建连接
    SQLException reason = null;

    for(DriverInfo aDriver : registeredDrivers) {
        // 如果类加载器不允许加载该驱动,则跳过
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());

         //通过不同的驱动类创建驱动
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }

        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }

    }
//其中驱动类poolingDriver.java 就是apache.jdbc的driver

实现了Driver的类图如下:


driver类图.png
###配置属性
//UnpooledDataSourceFactory.java 这里主要用于设置配置文件配置的数据库属性
public void setProperties(Properties properties) {
  Properties driverProperties = new Properties();
  MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
  for (Object key : properties.keySet()) {
    String propertyName = (String) key;
    if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
      String value = properties.getProperty(propertyName);
      driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
    } else if (metaDataSource.hasSetter(propertyName)) {
      String value = (String) properties.get(propertyName);
      Object convertedValue = convertValue(metaDataSource, propertyName, value);
      metaDataSource.setValue(propertyName, convertedValue);
    } else {
      throw new DataSourceException("Unknown DataSource property: " + propertyName);
    }
  }
  if (driverProperties.size() > 0) {
    metaDataSource.setValue("driverProperties", driverProperties);
  }
}

Mybatis内部是如何实现数据连接池的呢,时序图如下:


数据库连接池时序图.png

连接池中类图如下:


类图.png

通过PooledDataSource创建数据源,实现依赖于UnpooledDataSource,获取连接交给了PooledConnection,其实现了jdk自带的InvocationHandler接口,(很熟悉的感觉,干啥的,必然做代理的。代理的目标对象是Connection啦);获取的连接的状态交给PoolState去管理,揭开神秘的面纱吧

//PooledDataSource.java
private PooledConnection popConnection(String username, String password) throws SQLException {
  boolean countedWait = false;
  PooledConnection conn = null;
  long t = System.currentTimeMillis();
  int localBadConnectionCount = 0;

  while (conn == null) {//如果连接为null
    synchronized (state) {//PoolState对象加锁
      if (!state.idleConnections.isEmpty()) {//如果空闲连接池中有可用的对象
        conn = state.idleConnections.remove(0);//则从中取出一个直接返回
        if (log.isDebugEnabled()) {
          log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
        }
      } else {
        // 否则 如果活跃连接数<最大的活跃连接数poolMaximumActiveConnections默认为10
        if (state.activeConnections.size() < poolMaximumActiveConnections) {
          // 则创建连接,注意构造方法有代理对象的处理 
          conn = new PooledConnection(dataSource.getConnection(), this);
          if (log.isDebugEnabled()) {
            log.debug("Created connection " + conn.getRealHashCode() + ".");
          }
        } else {
          // 活跃连接池已满,不能再创建连接,取出第一个连接 
          PooledConnection oldestActiveConnection = state.activeConnections.get(0);
          //当前时间-获取其从连接池中取出连接的时间戳【判断这个连接使用了多长时间了】
          long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
          if (longestCheckoutTime > poolMaximumCheckoutTime) {//是否大于设置的最大checkout时长默认20000
            // 记录该连接信息,准备从活跃连接池中移除
            state.claimedOverdueConnectionCount++;
            state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
            state.accumulatedCheckoutTime += longestCheckoutTime;
            state.activeConnections.remove(oldestActiveConnection);
            if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {//如果当前连接不是自动提交
              try {
                oldestActiveConnection.getRealConnection().rollback();//则回滚
              } catch (SQLException e) {
                log.debug("Bad connection. Could not roll back");
              }  
            }
//重新创建连接
            conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
            conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
            conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
            oldestActiveConnection.invalidate();//设置原来连接失效
            if (log.isDebugEnabled()) {
              log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
            }
          } else {
            //如果活跃连接池中没有可丢掉的对象,则阻塞等待
            try {
              if (!countedWait) {//等待状态为false
                state.hadToWaitCount++;//等待次数加+1
                countedWait = true;
              }
              if (log.isDebugEnabled()) {
                log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
              }
              long wt = System.currentTimeMillis();
              state.wait(poolTimeToWait);//设置等待时间 默认20000
              state.accumulatedWaitTime += System.currentTimeMillis() - wt;//记录等待时长
            } catch (InterruptedException e) {
              break;
            }
          }
        }
      }
      //如果连接不为空
      if (conn != null) {
        // 进行pingConnection校验
        if (conn.isValid()) {
     //如果不是自动提交的,则将之前的数据回滚
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
     //设置连接类型 url+name+password的hashcode
          conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
          conn.setCheckoutTimestamp(System.currentTimeMillis());
          conn.setLastUsedTimestamp(System.currentTimeMillis());
          //添加到活跃连接池中,设置请求次数
state.activeConnections.add(conn);
          state.requestCount++;
          state.accumulatedRequestTime += System.currentTimeMillis() - t;
        } else {
          if (log.isDebugEnabled()) {
            log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
          }
          state.badConnectionCount++;
          localBadConnectionCount++;
          conn = null;
          if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {//如果失效连接数>最大空闲连接数默认5+可容忍的最大失效连接数默认3
            if (log.isDebugEnabled()) {
              log.debug("PooledDataSource: Could not get a good connection to the database.");
            }
            throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
          }
        }
      }
    }
  }

下段代码中调用close方法的时候会触发代理对象,调用invoke方法 会调用PooledDataSource.pushConnection方法,将当前连接对象重新仍回到连接池中,知道持有引用的原因了吧

protected void pushConnection(PooledConnection conn) throws SQLException {

  synchronized (state) {
//从活跃对象中先删除当前的对象
    state.activeConnections.remove(conn);
//判断连接有效
    if (conn.isValid()) {//如果空闲连接数<最大空闲连接数 &&连接hashCode[用于多数据源的情况]一致
      if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {//设置checkout的时间
        state.accumulatedCheckoutTime += conn.getCheckoutTime();
        if (!conn.getRealConnection().getAutoCommit()) {//如果不是自动提交,则回滚
          conn.getRealConnection().rollback();
        }//创建新的连接
        PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
        state.idleConnections.add(newConn);//添加到空闲连接池中
        newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
        newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
        conn.invalidate();//设置连接失效
        if (log.isDebugEnabled()) {
          log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
        }
        state.notifyAll();//唤醒在等待
      } else { //超过最大空闲连接数 则关闭
        state.accumulatedCheckoutTime += conn.getCheckoutTime();
        if (!conn.getRealConnection().getAutoCommit()) {
          conn.getRealConnection().rollback();
        }
        conn.getRealConnection().close();
        if (log.isDebugEnabled()) {
          log.debug("Closed connection " + conn.getRealHashCode() + ".");
        }
        conn.invalidate();
      }
    } else {//如果无效 说明该连接是异常的
      if (log.isDebugEnabled()) {
        log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
      }
      state.badConnectionCount++;
    }
  }
}

在调用isValid时,通过pingConnection方法来检查连接是否有效 一般执行select 1

protected boolean pingConnection(PooledConnection conn) {
  boolean result = true;

  try {
    result = !conn.getRealConnection().isClosed();
  } catch (SQLException e) {
    if (log.isDebugEnabled()) {
      log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
    }
    result = false;
  }

  if (result) {//关闭异常
    if (poolPingEnabled) {//连接超过它设置的值>0&& 当前时间-连接最后被使用的时间>该值
      if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {//实际就是说该连接最后一次被使用的时间大于了我们设置的默认时间就执行一次
        try {
          if (log.isDebugEnabled()) {
            log.debug("Testing connection " + conn.getRealHashCode() + " ...");
          }
          Connection realConn = conn.getRealConnection();
          Statement statement = realConn.createStatement();
          ResultSet rs = statement.executeQuery(poolPingQuery);
          rs.close();
          statement.close();
          if (!realConn.getAutoCommit()) {
            realConn.rollback();
          }
          result = true;
          if (log.isDebugEnabled()) {
            log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
          }
        } catch (Exception e) {
          log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
          try {
            conn.getRealConnection().close();
          } catch (Exception e2) {
            //ignore
          }
          result = false;
          if (log.isDebugEnabled()) {
            log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
          }
        }
      }
    }
  }
  return result;
}

pooledConnection实现的invoke方法

PooledConnection.java 
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  String methodName = method.getName();
//判断连接是否关闭,如果关闭则扔回到池中
  if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
//连接对象扔回到PooledDataSource中
    dataSource.pushConnection(this);
    return null;
  } else {
    try {
      if (!Object.class.equals(method.getDeclaringClass())) {
        //检查连接是否存在 通过valid判断 valid方法会检查该连接是否存在
        checkConnection();
      }
//调用该连接的方法
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
}
private final int hashCode;//连接对象的hashCode
private final PooledDataSource dataSource;//标识该连接对象所持有的数据源
private final Connection realConnection;//真正的连接对象
private final Connection proxyConnection;// 连接对象的代理
private long checkoutTimestamp;//从连接池中取出该连接的时间戳
private long createdTimestamp;//创建该连接的时间戳
private long lastUsedTimestamp;//该连接对象最后一次被使用的时间戳
private int connectionTypeCode;//通过数据库url、密码、用户名计算出来的hash 标识连接所在的连接池对象
private boolean valid;//检查该连接是否有效

PoolState状态管理的属性

PoolState.java主要用于管理PooledConnection对象状态的集合如下
protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();//空闲连接集合
protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();//活动连接集合

protected long requestCount = 0;//数据库连接请求次数
protected long accumulatedRequestTime = 0;//所有连接访问累计时间
protected long accumulatedCheckoutTime = 0;//所有连接累计checkouttime时长【从连接池中取出连接到将连接放回连接池中的时长】
protected long claimedOverdueConnectionCount = 0;//连接放回连接池时超时的连接个数
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
protected long accumulatedWaitTime = 0;//所有连接等待时长
protected long hadToWaitCount = 0;//等待次数
protected long badConnectionCount = 0;//无效连接次数

Mybatis数据源就介绍到这里了,下面我们对比下c3p0、BasicDatasource、druid一些性能
对比

Git clone:https://github.com/alibaba/druid.git
找到测试类Case0.java

//Case0.java
private void p0(DataSource dataSource, String name) throws SQLException {
//执行时间
        long startMillis = System.currentTimeMillis();
//youngGC时间
        long startYGC = TestUtil.getYoungGC();
//FullGC时间
        long startFullGC = TestUtil.getFullGC();
       //COUNT = 1000 * 1000 * 1
        for (int i = 0; i < COUNT; ++i) {
            Connection conn = dataSource.getConnection();
            Statement stmt = conn.createStatement();
            conn.close();
        }
        long millis = System.currentTimeMillis() - startMillis;
        long ygc = TestUtil.getYoungGC() - startYGC;
        long fullGC = TestUtil.getFullGC() - startFullGC;

        System.out.println(name + " millis : " + NumberFormat.getInstance().format(millis) + ", YGC " + ygc + " FGC "
                           + fullGC);
    }


public void test_druid() throws Exception {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setInitialSize(initialSize);
    dataSource.setMaxActive(maxActive);
    dataSource.setMinIdle(minIdle);
    dataSource.setMaxIdle(maxIdle);
    dataSource.setPoolPreparedStatements(true);
    dataSource.setDriverClassName(driverClass);
    dataSource.setUrl(jdbcUrl);
    dataSource.setPoolPreparedStatements(true);
    dataSource.setUsername(user);
    dataSource.setPassword(password);
    dataSource.setValidationQuery(validationQuery);
    dataSource.setTestOnBorrow(testOnBorrow);
    dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
//LOOP_COUN = 5
    for (int i = 0; i < LOOP_COUNT; ++i) {
        p0(dataSource, "druid");
    }
    System.out.println();
}
-执行1- -执行2-
druid millis : 861, YGC 65 FGC 0 druid millis : 1,030, YGC 121 FGC 0
druid millis : 376, YGC 62 FGC 0 druid millis : 669, YGC 117 FGC 0
druid millis : 369, YGC 62 FGC 0 druid millis : 673, YGC 117 FGC 0
druid millis : 374, YGC 62 FGC 0 druid millis : 664, YGC 117 FGC 0
druid millis : 366, YGC 62 FGC 0 druid millis : 685, YGC 117 FGC 0
---dbcp----------
dbcp millis : 1,121, YGC 171 FGC 0 dbcp millis : 1,663, YGC 227 FGC 0
dbcp millis : 1,013, YGC 168 FGC 0 dbcp millis : 1,415, YGC 225 FGC 0
dbcp millis : 1,015, YGC 167 FGC 0 dbcp millis : 1,363, YGC 224 FGC 0
dbcp millis : 999, YGC 168 FGC 0 dbcp millis : 1,371, YGC 225 FGC 0
dbcp millis : 1,009, YGC 167 FGC 0 dbcp millis : 1,360, YGC 225 FGC 0
-----c3p0--------
c3p0 millis : 4,893, YGC 242 FGC 0 c3p0 millis : 6,112, YGC 300 FGC 0
c3p0 millis : 4,836, YGC 239 FGC 0 c3p0 millis : 5,840, YGC 293 FGC 0
c3p0 millis : 5,022, YGC 238 FGC 0 c3p0 millis : 5,852, YGC 294 FGC 0
c3p0 millis : 4,828, YGC 239 FGC 0 c3p0 millis : 5,508, YGC 293 FGC 0
c3p0 millis : 4,764, YGC 238 FGC 0 c3p0 millis : 5,509, YGC 294 FGC 0

Druid、c3p0、BasicDatasource 分别执行结果如下:

-执行1- -执行2-
druid millis : 861, YGC 65 FGC 0 druid millis : 1,030, YGC 121 FGC 0
druid millis : 376, YGC 62 FGC 0 druid millis : 669, YGC 117 FGC 0
druid millis : 369, YGC 62 FGC 0 druid millis : 673, YGC 117 FGC 0
druid millis : 374, YGC 62 FGC 0 druid millis : 664, YGC 117 FGC 0
druid millis : 366, YGC 62 FGC 0 druid millis : 685, YGC 117 FGC 0
---dbcp----------
dbcp millis : 1,121, YGC 171 FGC 0 dbcp millis : 1,663, YGC 227 FGC 0
dbcp millis : 1,013, YGC 168 FGC 0 dbcp millis : 1,415, YGC 225 FGC 0
dbcp millis : 1,015, YGC 167 FGC 0 dbcp millis : 1,363, YGC 224 FGC 0
dbcp millis : 999, YGC 168 FGC 0 dbcp millis : 1,371, YGC 225 FGC 0
dbcp millis : 1,009, YGC 167 FGC 0 dbcp millis : 1,360, YGC 225 FGC 0
-----c3p0--------
c3p0 millis : 4,893, YGC 242 FGC 0 c3p0 millis : 6,112, YGC 300 FGC 0
c3p0 millis : 4,836, YGC 239 FGC 0 c3p0 millis : 5,840, YGC 293 FGC 0
c3p0 millis : 5,022, YGC 238 FGC 0 c3p0 millis : 5,852, YGC 294 FGC 0
c3p0 millis : 4,828, YGC 239 FGC 0 c3p0 millis : 5,508, YGC 293 FGC 0
c3p0 millis : 4,764, YGC 238 FGC 0 c3p0 millis : 5,509, YGC 294 FGC 0

从上面可以看出执行效率 druid>dpcp>c3p0
网上已经有很多文章来描述了 笔者只是想告诉大家 druid包下有很多测试类可供我们借鉴。
比如 上文中它是如何监测youngGC、FullGC的,有机会会给大家带来druid的分析.需要的赶紧一睹为快吧

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

推荐阅读更多精彩内容