高并發(fā)場景下,Java 日志的最佳應(yīng)用實(shí)踐

日志技術(shù)選型

Log 門面層選型

《阿里巴巴 Java 開發(fā)規(guī)范》【強(qiáng)制】應(yīng)用中不可使用日志系統(tǒng)(Log4J、Logback)中的 API,而應(yīng)依賴使用日志框架 SLF4J 中的 API。使用門面模式的日志框架,有利于維護(hù)和各個(gè)類的日志處理方式統(tǒng)一。

上面所說的是阿里 Java 開發(fā)規(guī)范中的規(guī)則,應(yīng)用必須使用門面模式的日志框架,首選我們回顧一下門面設(shè)計(jì)模式。
門面模式,又叫外觀模式,它為子系統(tǒng)中的一組接口提供一個(gè)統(tǒng)一的高層接口,使的子系統(tǒng)更容易使用。使用門面模式的優(yōu)點(diǎn)是:

  • 解耦,通過統(tǒng)一對外的接口,屏蔽了與子系統(tǒng)的依賴和子系統(tǒng)的復(fù)雜性。
  • 提高了靈活性,子系統(tǒng)內(nèi)部的變化,只要不影響到門面對象,任其變化。其結(jié)構(gòu)見下圖。


    image.png

通過上面的回顧,我們也了解了門面設(shè)計(jì)模式。應(yīng)用使用門面模式的日志框架,可以部署時(shí),根據(jù)需要切換不同的日志框架。Java 中有哪些門面模式的日志框架呢?常見的有 Apache Common-Logging、SLF4J。
Commons Logging 實(shí)現(xiàn)機(jī)制:

Apache Common-Logging 使用了 ClassLoader 尋找和載入底層的日志庫,存在一定加載問題。

SLF4J 實(shí)現(xiàn)機(jī)制:

SLF4J 在編譯期間,靜態(tài)綁定本地的 Log 庫。它是通過查找類路徑下 org.slf4j.impl.StaticLoggerBinder,然后在 StaticLoggerBinder 中進(jìn)行綁定。使用 SLF4J 時(shí),需要指定具體的日志器實(shí)現(xiàn),常用的 Log4j、Log4j2、Logback、JDK-logging、SLF4J-simple 都可以實(shí)現(xiàn),對于不同的日志實(shí)現(xiàn)方案,封裝出不同的橋接組件。下圖是 SLF4J 與各組件 API 打通的的相關(guān)方案。


image.png

門面框架的選擇:

考慮到 SLF4J 的廣泛通用性及靜態(tài)編譯支持,門面層使用 SLF4J 作為日志 API 層選型。

日志引擎層技術(shù)選型

下圖是各個(gè)日志框架性能測試如下(來自 Log4j2 官方網(wǎng)站):


image.png

下圖是 garbage-free async loggers (無垃圾異步模式)在測試的所有配置中都具有最佳響應(yīng) time 行為。


image.png

根據(jù)以上數(shù)據(jù)顯示,Log4j2 通過異步化支持及隊(duì)列的使用使性能得到的極大的提升。這是如何工作的,應(yīng)用線程完成了最少量的工作來捕獲 log event 中的所有必需信息,然后將此 log event 放在隊(duì)列中以供后續(xù)線程稍后處理。如果隊(duì)列的大小足夠大,那么應(yīng)用線程應(yīng)該能夠在 logging 調(diào)用上花費(fèi)很少的 time,并且非常快速地返回到業(yè)務(wù)邏輯。

事實(shí)證明,隊(duì)列的選擇對于峰值吞吐量非常重要。 Log4j2 的 Async Loggers 使用無鎖數(shù)據(jù)結(jié)構(gòu)(Disruptor),而 Logback、Log4j 1.2 和 Log4j2 的異步 Appenders 使用 ArrayBlockingQueue。使用阻塞隊(duì)列時(shí),對線程應(yīng)用在嘗試將 log event 排入隊(duì)列時(shí)經(jīng)常會遇到鎖爭用。所以 Log4j2 的 Async Loggers 在一定程度上提供更好的吞吐量,但是一旦隊(duì)列已滿,appender 線程需要等待,直到隊(duì)列中的一個(gè)插槽可用,并且吞吐量將降至最大最好的基礎(chǔ) appenders 的持續(xù)吞吐量。

并且 Log4j2 改版以后,組件和功能極大豐富,有興趣的同學(xué)可以去官網(wǎng):

http://logging.apache.org/log4j/2.x/manual/index.html

日志引擎框架的選擇:

經(jīng)過上述的分析,所以在日志引擎層毫無疑問選用 Log4j2 是最佳選擇。

Log4j2 簡介

關(guān)于 Log4j2

Apache Log4j 2 是 Log4j 1 的繼任者,2014 年 7 月其 GA 版本(正式發(fā)布版)發(fā)布。該框架被從頭重寫,并從現(xiàn)有的日志解決方案中獲得靈感(包括 Log4j 1 和 JUL)。該版本與 Log4j 1 的主要差異是:

  • 改進(jìn)的配置語法。
  • 支持 XML 和 JSON 配置。
  • 改進(jìn)的過濾器。
  • 屬性(Property)支持。
  • 標(biāo)記。
  • 提高速度。
  • 模塊化,Log4j 2 支持插件系統(tǒng)。
  • 提高了可靠性。
  • 配置自動重裝。
  • Log4j 2 的最被認(rèn)可的特點(diǎn)之一是“異步記錄器”的性能。Log4j 2 利用了 LMAX Disruptor。例如,在相同的環(huán)- 境下,Log4j 2 可以寫每秒超過 18,000,000 條信息,而其他框架(像 Logback 和 Log4j 1)每秒只能寫< 2,000,000 條消息。
  • Log4j 2 提供對 SLF4J、Commons Logging、Apache Flume 和 Log4j 1 的支持。

異步 Loggers

異步是 Log4j2 最大的亮點(diǎn),性能提升的關(guān)鍵,應(yīng)用無鎖模式(Disruptor)實(shí)現(xiàn)。在實(shí)踐中,官方推薦使用 Async Logger 的全異步方式,進(jìn)行異步日志的配置。唯一的缺點(diǎn)就是如果異常可能不能通知給應(yīng)用程序,這一點(diǎn)根據(jù)實(shí)際情況處理。

Garbage-free Logging

許多 logging libraries,包括以前版本的 Log4j,在穩(wěn)定 state logging 期間分配臨時(shí) objects,如 log event objects、Strings、char 數(shù)組、字節(jié)數(shù)組等。這會對垃圾收集器造成壓力并增加 GC 暫停發(fā)生的頻率。

從 version 2.6 開始,默認(rèn)情況下 Log4j 以“無垃圾”模式運(yùn)行,其中重復(fù)使用 objects 和 buffers,并且盡可能不分配臨時(shí) objects,原理是通過 ThreadLocal 實(shí)現(xiàn)復(fù)用。

Spring Boot 集成 Log4j2

  1. Maven 配置

首先剔除 Spring Boot 自帶的 Logback 配置、加入 Log4j2 starter 和異步依賴的 disruptor 配置。

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!--log4j2 異步依賴-->
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>
  1. 日志格式很重要,在微服務(wù)中,經(jīng)常會用到 elk 等組件來收集日志,統(tǒng)一查詢。為了方便查找問題,定位問題,我們實(shí)踐中最佳的輸出格式如下:

[機(jī)器 IP 地址] | [線程 ID] | [微服務(wù)名] | [時(shí)間] | [日志級別] | [包名] | [日志信息]

數(shù)據(jù)項(xiàng) 收集方式 配置方式
機(jī)器 IP 地址 在應(yīng)用啟動時(shí)通過 MDC 注入本機(jī) IP 如:MDC.put("ip", "129.12.31.1"); %X{ip}
線程 ID 日志組件自動收集 %t
微服務(wù)名 在應(yīng)用啟動時(shí)通過 MDC 注入服務(wù)名 如:MDC.put("serverName",”user-center“); %X{serverName}
時(shí)間 組件自動收集 %d{yyyy-MM-dd HH:mm:ss,SSS}
日志級別 配置 %p
包名 組件自動收集 %c
日志信息 程序輸出 如:log.info() %m
  1. Log4j2 配置文件在 resource 下生成 log4j2.xml。
    內(nèi)容如下,實(shí)際使用中根據(jù)情況修改:
<?xml version="1.0" encoding="UTF-8"?>
<!--日志級別以及優(yōu)先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--status="WARN" :用于設(shè)置 log4j2 自身內(nèi)部日志的信息輸出級別,默認(rèn)是 OFF-->
<!--monitorInterval="30"  :間隔秒數(shù),自動檢測配置文件的變更和重新配置本身-->
<configuration status="WARN" monitorInterval="60" strict="true">
    <properties>
        <!--自定義一些常量,之后使用${變量名}引用-->
        <property name="logPath">./logs</property>
        <property name="charset">UTF-8</property>
        <!--自定義的輸出格式-->
        <property name="pattern">%X{ip}|%t|%X{serverName}|%d{yyyy-MM-dd HH:mm:ss,SSS}|%p|%c|%m%n</property>
    </properties>
    <!--appenders:定義輸出內(nèi)容,輸出格式,輸出方式,日志保存策略等,常用其下三種標(biāo)簽[console,File,RollingFile]-->
    <!--Appender 可以理解為日志的輸出目的地-->
    <appenders>

        <!--console :控制臺輸出的配置-->
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="${pattern}" charset="${charset}"/>
        </Console>

        <!--RollingRandomAccessFile 性能比 RollingFile 提升官網(wǎng)宣稱是 20-200%-->
        <RollingRandomAccessFile name="DEMO.ERROR" immediateFlush="true" bufferSize="4096"
                                 fileName="${logPath}/error.log"
                                 filePattern="${logPath}/error.%d{yyyy-MM-dd}.gz"
                                 ignoreExceptions="false">
            <PatternLayout pattern="${pattern}" charset="${charset}"/>
            <Filters>
                <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <Policies>
                <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
                <SizeBasedTriggeringPolicy size="500MB"/>
            </Policies>
            <DefaultRolloverStrategy>
                <Delete basePath="${logPath}" maxDepth="2" followLinks="true">
                    <IfFileName glob="error.*.gz"/>
                    <IfLastModified age="7d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingRandomAccessFile>

        <RollingRandomAccessFile name="DEMO.WARN" immediateFlush="false" bufferSize="4096"
                                 fileName="${logPath}/warn.log"
                                 filePattern="${logPath}/warn.%d{yyyy-MM-dd}-%i.gz"
                                 ignoreExceptions="false">
            <PatternLayout pattern="${pattern}" charset="${charset}"/>
            <Filters>
                <!--ThresholdFilter :日志輸出過濾-->
                <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <Policies>
                <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
                <SizeBasedTriggeringPolicy size="500MB"/>
            </Policies>
            <DefaultRolloverStrategy>
                <Delete basePath="${logPath}" maxDepth="2" followLinks="true">
                    <IfFileName glob="warn.*.gz"/>
                    <IfLastModified age="7d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingRandomAccessFile>

        <RollingRandomAccessFile name="DEMO.INFO" immediateFlush="true"
                                 fileName="${logPath}/info.log"
                                 filePattern="${logPath}/info.%d{yyyy-MM-dd}-%i.gz"
                                 ignoreExceptions="false">
            <PatternLayout pattern="${pattern}" charset="${charset}"/>
            <Policies>
                <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
                <SizeBasedTriggeringPolicy size="500MB"/>
            </Policies>
            <DefaultRolloverStrategy>
                <Delete basePath="${logPath}" maxDepth="2" followLinks="true">
                    <IfFileName glob="info.*.gz"/>
                    <IfLastModified age="7d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingRandomAccessFile>

    </appenders>

    <!--然后定義 logger,只有定義了 logger 并引入的 appender,appender 才會生效-->
    <loggers>
        <Logger additivity="true" name="com.example" level="INFO">
        </Logger>
        <!-- Root 節(jié)點(diǎn)用來指定項(xiàng)目的根日志,如果沒有單獨(dú)指定 Logger,那么就會默認(rèn)使用該 Root 日志輸出 -->
        <Root level="INFO" includeLocation="true">
            <AppenderRef ref="console"/>
            <AppenderRef ref="DEMO.INFO"/>
            <AppenderRef ref="DEMO.ERROR"/>
            <AppenderRef ref="DEMO.WARN"/>
        </Root>
    </loggers>
</configuration>

然后在 application.properties 指定 Log4j2 的配置文件,如下:

logging.config=classpath:log4j2.xml

  1. 配置日志為全異步方式,在 resource 下創(chuàng)建 log4j2.component.properties 文件,內(nèi)容如下,開啟全異步模式:

Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

配置文件結(jié)構(gòu)如下圖:


image.png
  1. 主程序通過 MDC 方式注入 IP 和 serverName:
public class DemoApplication {
    public static void main(String[] args) {
        initMainThreadLogProperties();
        SpringApplication.run(DemoApplication.class, args);
    }

    private static void initMainThreadLogProperties() {
        PropertiesUtil propertiesUtil;
        try {
            propertiesUtil = new PropertiesUtil("application.properties");
            MDC.put("ip", getLocalIp());
            MDC.put("serverName", getServerName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  1. 業(yè)務(wù)線程通過 Filter 的方式注入 IP 和 serverName:
@Order(1)
@WebFilter(filterName = "logFilter", urlPatterns = "/*")
public class LogFilter implements Filter {

    @Value("${spring.application.name}")
    private String serverName;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        MDC.put("ip", request.getLocalAddr());
        MDC.put("serverName", serverName);
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
  1. 通過 AOP 打印入?yún)ⅰ⒊鰠ⅰ⒑徒涌诤臅r(shí)信息:
@Aspect
@Order(5)
@Component
@Slf4j
public class LogAspect {

    @Pointcut("execution(public * com.example.demo.controller..*(..))")
    public void weblog() {
    }

    /**
     * 請求執(zhí)行過程 記錄 ip、入?yún)ⅰ⒊鰠ⅰ⒑臅r(shí)
     *
     * @param joinPoint
     */
    @Around("weblog()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        HttpServletRequest request = getRequestContext();

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try {
            Object result = joinPoint.proceed(args);
            stopWatch.stop();
            if (log.isInfoEnabled()) {
                log.info("client_ip={},url={} ,cost_time={}ms,args={},result={}", request.getRemoteAddr(),
                        request.getRequestURL().toString(), stopWatch.getTotalTimeMillis(), JSON.toJSONString(args), JSON.toJSONString(result));
            }
            return result;
        } catch (Throwable e) {
            log.error("client_ip={},url={} ,cost_time={}ms,,args={}", request.getRemoteAddr(),
                    request.getRequestURL().toString(), stopWatch.getTotalTimeMillis(), JSON.toJSONString(args), e);
            throw e;
        }
    }

    /**
     * 獲取 HttpServletRequest
     *
     * @return
     */
    private HttpServletRequest getRequestContext() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        return request;
    }
}

以上就是日志集成的一個(gè)最佳實(shí)踐,我把具體的 Demo 上傳到 GitHub 上了,感興趣的朋友可以下載下來,里面的 Demo 都可以直接運(yùn)行,Demo 里還包括接口統(tǒng)一異常處理和日志打印的處理。地址:

https://github.com/zhaoyueoop/springboot-log4j2-demo

高并發(fā)場景下

日志打印的的正確姿勢

// 傳統(tǒng)的字符串產(chǎn)生方式,如果沒有要記錄 Debug 等級的信息,就會浪費(fèi)時(shí)間在產(chǎn)生不必要的信息上
logger.debug("There are now " + count + " user accounts: " + userAccountList);

// 為了避免上述問題,我們可以先檢查是不是開啟了 Debug 信息記錄功能,只是程序的編碼會比較復(fù)雜
if (logger.isDebugEnabled()) {
    logger.debug("There are now " + count + " user accounts: " + userAccountList);
}

//應(yīng)用占位符的方式, 如果 Debug 等級沒有開啟,則不會產(chǎn)生不必要的字符串,同時(shí)也能保持程序編碼的簡潔
logger.debug("There are now {} user accounts: {}", count, userAccountList); 

如上述第一行代碼,如果日志級別是 WARN 級別,雖然 logger.debug 不會輸出日志,但還會進(jìn)行字符串拼接,并發(fā)量大的時(shí)候存在性能問題。為了避免這個(gè)問題可以先檢查是否開啟了 Debug 級別,可以通過這個(gè) logger.isDebugEnabled() 方法,但這樣會使編碼比較復(fù)雜,代碼不夠簡潔。在這里推薦上述代碼中的第三中方式,占位符方式,這種方式即使沒有開啟 Debug 登記,也不會產(chǎn)生不必要的字符串拼接等操作,也能保持代碼的簡潔性。

高并發(fā)場景下,一定要用異步和緩沖模式來打印日志,同步方式會造成大量系統(tǒng)文件 IO 操作,導(dǎo)致應(yīng)用并發(fā)上不去,甚至掛掉。記得多年前,當(dāng)時(shí)配置日志,只會從網(wǎng)上 copy 一份修改一下,里面的參數(shù)并不清楚是什么作用,不知道讀者是不是也會這樣。所以配置日志的時(shí)候,一定要非常清楚各個(gè)參數(shù)的意義,如果不清楚,建議去官方看一看。

分布式系統(tǒng)日志鏈路跟蹤方案與原理

隨著系統(tǒng)的微服務(wù)化,一次業(yè)務(wù)接口的調(diào)用可能需要多個(gè)微服務(wù)間協(xié)同調(diào)用來完成。那如何直觀的了解系統(tǒng)的調(diào)用及運(yùn)行情況呢。可以在業(yè)務(wù)日志中增加調(diào)用鏈 ID, 把業(yè)務(wù)日志和調(diào)用鏈關(guān)聯(lián)起來,可以方便查看一次業(yè)務(wù)接口調(diào)用相關(guān)的所有日志信息,方便定位問題。

如何在日志中埋入調(diào)用鏈 ID 呢?

兩種方式,一是客戶端發(fā)送請求時(shí)生成調(diào)用鏈 Id(TraceId),二是服務(wù)端調(diào)用的首節(jié)點(diǎn)負(fù)責(zé)生成。生成方式可以通過 UUID 等方式。然后通過線程上下文(ThreadLocal)傳遞 TraceId,跨節(jié)點(diǎn)時(shí),通過 RPC 框架或 HTTP 通過傳參顯示的傳到下一節(jié)點(diǎn)。

如果使用 MQ 異步操作時(shí)怎么辦呢?

各位小伙伴可以自己想一想。這是我們自己實(shí)現(xiàn)日志追蹤的方案,有些代碼的侵入,那有沒有更方便的呢?答案肯定是有的,那就是 JavaAgent 技術(shù),如阿里開源的 SkyWalking 分布式追蹤系統(tǒng),通過動態(tài)字節(jié)碼技術(shù),無侵入的實(shí)現(xiàn)服務(wù)之間的追蹤,并提供 UI 界面查看調(diào)用鏈情況,非常方便,感興趣的可以深入研究。

結(jié)束語

希望上述寫的,能在實(shí)際項(xiàng)目中幫到你,感興趣的可以深入了解日志中應(yīng)用到的技術(shù),我在結(jié)尾附上,我覺得寫的不錯(cuò)的博客,大家也可以去看一看。歡迎留言討論。

關(guān)于 Disruptor:

https://tech.meituan.com/2016/11/18/disruptor.html

關(guān)于 MDC:

https://ketao1989.github.io/2015/04/29/LogBack-Implemention-And-Slf4j-Mdc/

關(guān)于 ThreadLocal:

https://juejin.im/post/5ac2eb52518825555e5e06ee

關(guān)于 SkyWalking:

https://skywalking.apache.org/zh/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評論 2 380