從源碼看ShardingSphere設(shè)計-執(zhí)行引擎篇

執(zhí)行引擎的職責定位是將改寫后的SQL發(fā)送到對應(yīng)數(shù)據(jù)庫(經(jīng)路由計算所得)執(zhí)行的過程。執(zhí)行引擎采用了callback回調(diào)的設(shè)計模式僻焚,對給定的輸入分組集合執(zhí)行指定的callback函數(shù)踱卵。

與Spring的JDBCTemplate今豆、TransactionTemplate類似灯蝴,ShardingSphere中的SQLExecuteTemplate撇簿、ExecutorEngine也是如此設(shè)計聂渊,引擎使用者提供CallBack實現(xiàn)類推汽,使用該模式是因為在SQL執(zhí)行時,需要支持更多類型的SQL歧沪,不同的SQL如DQL歹撒、DML、DDL诊胞、不帶參數(shù)的SQL暖夭、參數(shù)化SQL等,不同的SQL操作邏輯并不一樣撵孤,但執(zhí)行引擎需要提供一個通用的執(zhí)行策略迈着。

代碼執(zhí)行分析

繼續(xù)回到起點,在ShardingPreparedStatement類中

    @Override
    public ResultSet executeQuery() throws SQLException {
        ResultSet result;
        try {
…
            initPreparedStatementExecutor();//PreparedStatement執(zhí)行器初始化
            MergedResult mergedResult = mergeQuery(preparedStatementExecutor.executeQuery());
…
 }
    private void initPreparedStatementExecutor() throws SQLException {
        preparedStatementExecutor.init(executionContext);
        setParametersForStatements();// 設(shè)置Statement參數(shù)
        replayMethodForStatements();// satement設(shè)置方法調(diào)用
}
    private void setParametersForStatements() {
        for (int i = 0; i < preparedStatementExecutor.getStatements().size(); i++) {
            replaySetParameter((PreparedStatement) preparedStatementExecutor.getStatements().get(i), preparedStatementExecutor.getParameterSets().get(i));
        }
    }
    private void replayMethodForStatements() {
        for (Statement each : preparedStatementExecutor.getStatements()) {
            replayMethodsInvocation(each);
        }
    }

可以看到進行了preparedStatementExecutor的初始化邪码、Statement參數(shù)設(shè)置裕菠、方法回放等操作。進入PreparedStatementExecutor類中
org.apache.shardingsphere.shardingjdbc.executor.PreparedStatementExecutor

/**
 * Prepared statement executor.
 */
public final class PreparedStatementExecutor extends AbstractStatementExecutor {
    
    @Getter
    private final boolean returnGeneratedKeys;
    
    public PreparedStatementExecutor(
            final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability, final boolean returnGeneratedKeys, final ShardingConnection shardingConnection) {
        super(resultSetType, resultSetConcurrency, resultSetHoldability, shardingConnection);
        this.returnGeneratedKeys = returnGeneratedKeys;
    }
    
    /**
     * Initialize executor.
     *
     * @param executionContext execution context
     * @throws SQLException SQL exception
     */
    public void init(final ExecutionContext executionContext) throws SQLException {
        setSqlStatementContext(executionContext.getSqlStatementContext());
        getInputGroups().addAll(obtainExecuteGroups(executionContext.getExecutionUnits()));// 生成執(zhí)行分組
        cacheStatements();
    }

    private Collection<InputGroup<StatementExecuteUnit>> obtainExecuteGroups(final Collection<ExecutionUnit> executionUnits) throws SQLException {
        return getSqlExecutePrepareTemplate().getExecuteUnitGroups(executionUnits, new SQLExecutePrepareCallback() {

            @Override
            // 在指定數(shù)據(jù)源上創(chuàng)建要求數(shù)量的數(shù)據(jù)庫連接
            public List<Connection> getConnections(final ConnectionMode connectionMode, final String dataSourceName, final int connectionSize) throws SQLException {
                return PreparedStatementExecutor.super.getConnection().getConnections(connectionMode, dataSourceName, connectionSize);
            }

            @Override
            //根據(jù)執(zhí)行單元信息 創(chuàng)建Statement執(zhí)行單元對象
            public StatementExecuteUnit createStatementExecuteUnit(final Connection connection, final ExecutionUnit executionUnit, final ConnectionMode connectionMode) throws SQLException {
                return new StatementExecuteUnit(executionUnit, createPreparedStatement(connection, executionUnit.getSqlUnit().getSql()), connectionMode);
            }
        });
    }

    @SuppressWarnings("MagicConstant")
    private PreparedStatement createPreparedStatement(final Connection connection, final String sql) throws SQLException {
        return returnGeneratedKeys ? connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)
                : connection.prepareStatement(sql, getResultSetType(), getResultSetConcurrency(), getResultSetHoldability());
    }
    
    /**
     * Execute query.
     *
     * @return result set list
     * @throws SQLException SQL exception
     */
    public List<QueryResult> executeQuery() throws SQLException {
        final boolean isExceptionThrown = ExecutorExceptionHandler.isExceptionThrown();
        SQLExecuteCallback<QueryResult> executeCallback = new SQLExecuteCallback<QueryResult>(getDatabaseType(), isExceptionThrown) {
            
            @Override
            // 在指定的Statement上執(zhí)行SQL闭专,將JDBC結(jié)果集包裝成查詢QueryResult對象(基于流模式奴潘、基于內(nèi)存模式兩類)
            protected QueryResult executeSQL(final String sql, final Statement statement, final ConnectionMode connectionMode) throws SQLException {
                return getQueryResult(statement, connectionMode);
            }
        };
        return executeCallback(executeCallback);// 通過executeCallback操作
    }
    // 執(zhí)行SQL,然后將結(jié)果集轉(zhuǎn)成QueryResult對象
    private QueryResult getQueryResult(final Statement statement, final ConnectionMode connectionMode) throws SQLException {
        PreparedStatement preparedStatement = (PreparedStatement) statement;
        ResultSet resultSet = preparedStatement.executeQuery();
        getResultSets().add(resultSet);
        return ConnectionMode.MEMORY_STRICTLY == connectionMode ? new StreamQueryResult(resultSet) : new MemoryQueryResult(resultSet);
    }
…
}

首先看init方法中調(diào)用obtainExecuteGroups方法影钉,obtainExecuteGroups方法中又調(diào)用SQLExecutePrepareTemplate.的getExecuteUnitGroups方法画髓,將輸入的ExecutionUnit集合和SQLExecutePrepareCallback生成InputGroup<StatementExecuteUnit>集合。
進入SQLExecutePrepareTemplate類看看getExecuteUnitGroups方法:
org.apache.shardingsphere.sharding.execute.sql.prepare.SQLExecutePrepareTemplate

/**
 * SQL execute prepare template.
 */
@RequiredArgsConstructor
public final class SQLExecutePrepareTemplate {
    
    private final int maxConnectionsSizePerQuery;
    
    /**
     * Get execute unit groups.
     *
     * @param executionUnits execution units
     * @param callback SQL execute prepare callback
     * @return statement execute unit groups
     * @throws SQLException SQL exception
     */
    public Collection<InputGroup<StatementExecuteUnit>> getExecuteUnitGroups(final Collection<ExecutionUnit> executionUnits, final SQLExecutePrepareCallback callback) throws SQLException {
        return getSynchronizedExecuteUnitGroups(executionUnits, callback);
    }

    // 生成同步執(zhí)行單元分組
    private Collection<InputGroup<StatementExecuteUnit>> getSynchronizedExecuteUnitGroups(
            final Collection<ExecutionUnit> executionUnits, final SQLExecutePrepareCallback callback) throws SQLException {
        Map<String, List<SQLUnit>> sqlUnitGroups = getSQLUnitGroups(executionUnits);// 生成數(shù)據(jù)源與其SQLUnit的對應(yīng)映射
        Collection<InputGroup<StatementExecuteUnit>> result = new LinkedList<>();
        for (Entry<String, List<SQLUnit>> entry : sqlUnitGroups.entrySet()) {
            result.addAll(getSQLExecuteGroups(entry.getKey(), entry.getValue(), callback));// 將SQLUnit轉(zhuǎn)化為InputGroup<StatementExecuteUnit>平委,對應(yīng)關(guān)系為1:1
        }
        return result;
    }

    // 根據(jù)執(zhí)行單元ExecutionUnit奈虾,生成各數(shù)據(jù)源對應(yīng)的SQLUnit集合
    private Map<String, List<SQLUnit>> getSQLUnitGroups(final Collection<ExecutionUnit> executionUnits) {
        Map<String, List<SQLUnit>> result = new LinkedHashMap<>(executionUnits.size(), 1);
        for (ExecutionUnit each : executionUnits) {
            if (!result.containsKey(each.getDataSourceName())) {
                result.put(each.getDataSourceName(), new LinkedList<>());
            }
            result.get(each.getDataSourceName()).add(each.getSqlUnit());
        }
        return result;
    }
    // 生成SQL執(zhí)行分組
    private List<InputGroup<StatementExecuteUnit>> getSQLExecuteGroups(final String dataSourceName,
                                                                       final List<SQLUnit> sqlUnits, final SQLExecutePrepareCallback callback) throws SQLException {
        List<InputGroup<StatementExecuteUnit>> result = new LinkedList<>();
        int desiredPartitionSize = Math.max(0 == sqlUnits.size() % maxConnectionsSizePerQuery ? sqlUnits.size() / maxConnectionsSizePerQuery : sqlUnits.size() / maxConnectionsSizePerQuery + 1, 1);
        List<List<SQLUnit>> sqlUnitPartitions = Lists.partition(sqlUnits, desiredPartitionSize);
        ConnectionMode connectionMode = maxConnectionsSizePerQuery < sqlUnits.size() ? ConnectionMode.CONNECTION_STRICTLY : ConnectionMode.MEMORY_STRICTLY;
        List<Connection> connections = callback.getConnections(connectionMode, dataSourceName, sqlUnitPartitions.size()); // 根據(jù)要執(zhí)行的SQL數(shù)量和maxConnectionsSizePerQuery配置,計算
        int count = 0;
        for (List<SQLUnit> each : sqlUnitPartitions) {
            result.add(getSQLExecuteGroup(connectionMode, connections.get(count++), dataSourceName, each, callback));// 根據(jù)要執(zhí)行的SQLUnit廉赔,生成對應(yīng)StatementExecuteUnit對象肉微,添加到返回結(jié)果集中
        }
        return result;
    }
    
    private InputGroup<StatementExecuteUnit> getSQLExecuteGroup(final ConnectionMode connectionMode, final Connection connection,
                                                                final String dataSourceName, final List<SQLUnit> sqlUnitGroup, final SQLExecutePrepareCallback callback) throws SQLException {
        List<StatementExecuteUnit> result = new LinkedList<>();
        for (SQLUnit each : sqlUnitGroup) {
            result.add(callback.createStatementExecuteUnit(connection, new ExecutionUnit(dataSourceName, each), connectionMode));
        }
        return new InputGroup<>(result);
    }

可以看到,SQLExecutePrepareTemplate類就是將ExecutionUnit集合進行分組轉(zhuǎn)化為InputGroup<StatementExecuteUnit>集合蜡塌。其核心邏輯是根據(jù)maxConnectionsSizePerQuery值(每個SQL最多可以配置多少數(shù)據(jù)庫連接供使用)碉纳,計算出當前SQL需要多少個數(shù)據(jù)庫連接

    /**
     * Max opened connection size for each query.
     */
    MAX_CONNECTIONS_SIZE_PER_QUERY("max.connections.size.per.query", String.valueOf(1), int.class),

org.apache.shardingsphere.sharding.execute.sql.execute.SQLExecuteTemplate

public final class SQLExecuteTemplate {
    
    private final ExecutorEngine executorEngine;
    
    private final boolean serial;
    
    /**
     * Execute.
     *
     * @param inputGroups input groups
     * @param callback SQL execute callback
     * @param <T> class type of return value
     * @return execute result
     * @throws SQLException SQL exception
     */
    public <T> List<T> execute(final Collection<InputGroup<? extends StatementExecuteUnit>> inputGroups, final SQLExecuteCallback<T> callback) throws SQLException {
        return execute(inputGroups, null, callback);
    }
    
    /**
     * Execute.
     *
     * @param inputGroups input groups
     * @param firstCallback first SQL execute callback
     * @param callback SQL execute callback
     * @param <T> class type of return value
     * @return execute result
     * @throws SQLException SQL exception
     */
    @SuppressWarnings("unchecked")
    public <T> List<T> execute(final Collection<InputGroup<? extends StatementExecuteUnit>> inputGroups,
                               final SQLExecuteCallback<T> firstCallback, final SQLExecuteCallback<T> callback) throws SQLException {
        try {
            return executorEngine.execute((Collection) inputGroups, firstCallback, callback, serial);
        } catch (final SQLException ex) {
            ExecutorExceptionHandler.handleException(ex);
            return Collections.emptyList();
        }
    }
}

可以看到其內(nèi)部操作又是通過ExecutorEngine類完成,進入該類看看
org.apache.shardingsphere.underlying.executor.engine.ExecutorEngine

/**
 * Executor engine.
 */
public final class ExecutorEngine implements AutoCloseable {
    
    private final ShardingSphereExecutorService executorService;
    
    public ExecutorEngine(final int executorSize) {
        executorService = new ShardingSphereExecutorService(executorSize);
    }
    
    /**
     * Execute.
     *
     * @param inputGroups input groups
     * @param callback grouped callback
     * @param <I> type of input value
     * @param <O> type of return value
     * @return execute result
     * @throws SQLException throw if execute failure
     */
    public <I, O> List<O> execute(final Collection<InputGroup<I>> inputGroups, final GroupedCallback<I, O> callback) throws SQLException {
        return execute(inputGroups, null, callback, false);
    }
    
    /**
     * Execute.
     *
     * @param inputGroups input groups
     * @param firstCallback first grouped callback
     * @param callback other grouped callback
     * @param serial whether using multi thread execute or not
     * @param <I> type of input value
     * @param <O> type of return value
     * @return execute result
     * @throws SQLException throw if execute failure
     */
    public <I, O> List<O> execute(final Collection<InputGroup<I>> inputGroups, 
                                  final GroupedCallback<I, O> firstCallback, final GroupedCallback<I, O> callback, final boolean serial) throws SQLException {
        if (inputGroups.isEmpty()) {
            return Collections.emptyList();
        }
        return serial ? serialExecute(inputGroups, firstCallback, callback) : parallelExecute(inputGroups, firstCallback, callback);
    }

    // 串行執(zhí)行
    private <I, O> List<O> serialExecute(final Collection<InputGroup<I>> inputGroups, final GroupedCallback<I, O> firstCallback, final GroupedCallback<I, O> callback) throws SQLException {
        Iterator<InputGroup<I>> inputGroupsIterator = inputGroups.iterator();
        InputGroup<I> firstInputs = inputGroupsIterator.next();
        List<O> result = new LinkedList<>(syncExecute(firstInputs, null == firstCallback ? callback : firstCallback));
        for (InputGroup<I> each : Lists.newArrayList(inputGroupsIterator)) {
            result.addAll(syncExecute(each, callback));
        }
        return result;
    }

    // 并行執(zhí)行岗照,可以支持兩個回調(diào)函數(shù)村象,第一條記錄執(zhí)行第一個回調(diào)函數(shù),其它的執(zhí)行第二個回調(diào)函數(shù)
    private <I, O> List<O> parallelExecute(final Collection<InputGroup<I>> inputGroups, final GroupedCallback<I, O> firstCallback, final GroupedCallback<I, O> callback) throws SQLException {
        Iterator<InputGroup<I>> inputGroupsIterator = inputGroups.iterator();
        InputGroup<I> firstInputs = inputGroupsIterator.next();
        Collection<ListenableFuture<Collection<O>>> restResultFutures = asyncExecute(Lists.newArrayList(inputGroupsIterator), callback);
        return getGroupResults(syncExecute(firstInputs, null == firstCallback ? callback : firstCallback), restResultFutures);
    }

    // 同步執(zhí)行
    private <I, O> Collection<O> syncExecute(final InputGroup<I> inputGroup, final GroupedCallback<I, O> callback) throws SQLException {
        return callback.execute(inputGroup.getInputs(), true, ExecutorDataMap.getValue());
    }

    // 異步執(zhí)行
    private <I, O> Collection<ListenableFuture<Collection<O>>> asyncExecute(final List<InputGroup<I>> inputGroups, final GroupedCallback<I, O> callback) {
        Collection<ListenableFuture<Collection<O>>> result = new LinkedList<>();
        for (InputGroup<I> each : inputGroups) {
            result.add(asyncExecute(each, callback));
        }
        return result;
    }
    
    private <I, O> ListenableFuture<Collection<O>> asyncExecute(final InputGroup<I> inputGroup, final GroupedCallback<I, O> callback) {
        final Map<String, Object> dataMap = ExecutorDataMap.getValue();
        return executorService.getExecutorService().submit(() -> callback.execute(inputGroup.getInputs(), false, dataMap));
    }
    
    private <O> List<O> getGroupResults(final Collection<O> firstResults, final Collection<ListenableFuture<Collection<O>>> restFutures) throws SQLException {
        List<O> result = new LinkedList<>(firstResults);
        for (ListenableFuture<Collection<O>> each : restFutures) {
            try {
                result.addAll(each.get());
            } catch (final InterruptedException | ExecutionException ex) {
                return throwException(ex);
            }
        }
        return result;
    }

ExecutorEngine類中方法主要分為兩個串行執(zhí)行serialExecute與并行執(zhí)行parallelExecute攒至,前者使用的是同步執(zhí)行即當前應(yīng)用線程厚者,后者則通過ShardingSphere內(nèi)置的線程池完成,該線程池類為ShardingSphereExecutorService迫吐。值得注意的是這些執(zhí)行方法中都對應(yīng)的有兩個CallBack參數(shù)库菲,在真正執(zhí)行時會對分組后的第一條記錄執(zhí)行第一個CallBack函數(shù),其它的執(zhí)行第二個CallBack函數(shù)志膀,這么設(shè)計的目的是有些操作只需執(zhí)行一次熙宇,例如獲取元數(shù)據(jù)鳖擒,只需要在第一條記錄操作生成,后續(xù)直接復(fù)用即可烫止。

例如在Sharding-proxy中
org.apache.shardingsphere.shardingproxy.backend.communication.jdbc.execute.JDBCExecuteEngine

public BackendResponse execute(final ExecutionContext executionContext) throws SQLException {
        SQLStatementContext sqlStatementContext = executionContext.getSqlStatementContext();
        boolean isReturnGeneratedKeys = sqlStatementContext.getSqlStatement() instanceof InsertStatement;
        boolean isExceptionThrown = ExecutorExceptionHandler.isExceptionThrown();
        Collection<InputGroup<StatementExecuteUnit>> inputGroups = sqlExecutePrepareTemplate.getExecuteUnitGroups(
                executionContext.getExecutionUnits(), new ProxyJDBCExecutePrepareCallback(backendConnection, jdbcExecutorWrapper, isReturnGeneratedKeys));
        Collection<ExecuteResponse> executeResponses = sqlExecuteTemplate.execute((Collection) inputGroups,
                new ProxySQLExecuteCallback(sqlStatementContext, backendConnection, jdbcExecutorWrapper, isExceptionThrown, isReturnGeneratedKeys, true),
                new ProxySQLExecuteCallback(sqlStatementContext, backendConnection, jdbcExecutorWrapper, isExceptionThrown, isReturnGeneratedKeys, false));
        ExecuteResponse executeResponse = executeResponses.iterator().next();
      …
    }

回頭看下ShardingSphere自定義的線程池
org.apache.shardingsphere.underlying.executor.engine.impl.ShardingSphereExecutorService

/**
 * ShardingSphere executor service.
 */
@Getter
public final class ShardingSphereExecutorService {
    
    private static final String DEFAULT_NAME_FORMAT = "%d";
    
    private static final ExecutorService SHUTDOWN_EXECUTOR = Executors.newSingleThreadExecutor(ShardingSphereThreadFactoryBuilder.build("Executor-Engine-Closer"));
    
    private ListeningExecutorService executorService;
    
    public ShardingSphereExecutorService(final int executorSize) {
        this(executorSize, DEFAULT_NAME_FORMAT);
    }
    
    public ShardingSphereExecutorService(final int executorSize, final String nameFormat) {
        executorService = MoreExecutors.listeningDecorator(getExecutorService(executorSize, nameFormat));
        MoreExecutors.addDelayedShutdownHook(executorService, 60, TimeUnit.SECONDS);
    }
    
    private ExecutorService getExecutorService(final int executorSize, final String nameFormat) {
        ThreadFactory threadFactory = ShardingSphereThreadFactoryBuilder.build(nameFormat);
        return 0 == executorSize ? Executors.newCachedThreadPool(threadFactory) : Executors.newFixedThreadPool(executorSize, threadFactory);
    }
   …
} 

可以看到ShardingSphereExecutorService 類中用的并不是JDK中原生的蒋荚,而是google guava工具包中的可監(jiān)聽ExecutorService,不過目前ShardingSphere中沒看到使用其listen功能馆蠕,應(yīng)該是為后續(xù)擴展考慮期升。

總結(jié)

相比其它引擎,可以看到執(zhí)行引擎較為簡單互躬,主要包括三部分:1. 是SQLExecutePrepareTemplate播赁,2. 是SQLExecuteTemplate,3. ExecutorEngine吼渡。
SQLExecutePrepareTemplate類負責生成執(zhí)行分組信息容为,輸入為 Collection<ExecutionUnit> ,輸出為Collection<InputGroup<StatementExecuteUnit>>寺酪;SQLExecuteTemplate類負責執(zhí)行具體的SQL操作坎背,輸入為Collection<InputGroup<StatementExecuteUnit>>與SQLExecuteCallback,這個類目前并沒有自身邏輯房维,它就是直接調(diào)用了ExecutorEngine類完成SQL執(zhí)行沼瘫;ExecutorEngine則真正負責完成SQL的串行和并行執(zhí)行。

在5.x中執(zhí)行引擎的類名進行了調(diào)整咙俩,SQLExecuteTemplate修改為org.apache.shardingsphere.infra.executor.sql.resourced.jdbc.executor.SQLExecutor,
ExecutorEngine類修改為org.apache.shardingsphere.infra.executor.kernel.ExecutorKernel,但具體功能實現(xiàn)沒有太大變化

最后畫一個執(zhí)行引擎的流程圖:

執(zhí)行引擎流程圖
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末湿故,一起剝皮案震驚了整個濱河市阿趁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坛猪,老刑警劉巖脖阵,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異墅茉,居然都是意外死亡命黔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門就斤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悍募,“玉大人,你說我怎么就攤上這事洋机∽寡纾” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵绷旗,是天一觀的道長喜鼓。 經(jīng)常有香客問我副砍,道長,這世上最難降的妖魔是什么庄岖? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任豁翎,我火速辦了婚禮,結(jié)果婚禮上隅忿,老公的妹妹穿的比我還像新娘谨垃。我一直安慰自己,他們只是感情好硼控,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布刘陶。 她就那樣靜靜地躺著,像睡著了一般牢撼。 火紅的嫁衣襯著肌膚如雪匙隔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天熏版,我揣著相機與錄音纷责,去河邊找鬼。 笑死撼短,一個胖子當著我的面吹牛再膳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播曲横,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼喂柒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了禾嫉?” 一聲冷哼從身側(cè)響起灾杰,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熙参,沒想到半個月后艳吠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡孽椰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年昭娩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黍匾。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡栏渺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膀捷,到底是詐尸還是另有隱情迈嘹,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站秀仲,受9級特大地震影響融痛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜神僵,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一雁刷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧保礼,春花似錦沛励、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至胁赢,卻和暖如春企蹭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背智末。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工谅摄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人系馆。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓送漠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親由蘑。 傳聞我的和親對象是個殘疾皇子闽寡,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354