基于現(xiàn)代Java技術(shù)棧的微服務(wù)架構(gòu)

英文原文

現(xiàn)代Java技術(shù)棧里我們已經(jīng)有了JDK 11,Kotlin壤追,Spring 5撩轰,Spring Boot 2以及Gradle 5坟乾,還有可以用于生產(chǎn)環(huán)境的kotlin DSL档桃,Junit 5枪孩,以及一大堆SpringCloud的類庫(kù),它們可以用來進(jìn)行服務(wù)發(fā)現(xiàn)藻肄,創(chuàng)建API網(wǎng)關(guān)蔑舞,客戶端負(fù)載均衡,實(shí)現(xiàn)熔斷器模式嘹屯,編寫聲明式HTTP客戶端攻询,分布式跟蹤系統(tǒng),所有的這些抚垄。當(dāng)然,要?jiǎng)?chuàng)建一個(gè)微服務(wù)的架構(gòu)的話谋逻,并不需要上面所有的組件——但是這個(gè)過程會(huì)很有趣呆馁!

Microservices in Java

簡(jiǎn)介

在這篇文章里,你會(huì)了解一個(gè)使用Java技術(shù)棧的微服務(wù)架構(gòu)毁兆,主要的組件列表如下(下面所列的版本是截止文章目前發(fā)布所使用的):

micro-service

我們的項(xiàng)目包含5個(gè)微服務(wù):3個(gè)基礎(chǔ)服務(wù)(配置服務(wù)Config Server浙滤,服務(wù)發(fā)現(xiàn)Service discovery server,UI網(wǎng)關(guān) UI gateway)以及用于示例的前端(Item UI)和后端(Item Service):


micro-service2

接下來會(huì)依次介紹上面的組件气堕。在實(shí)際的項(xiàng)目中纺腊,要實(shí)現(xiàn)具體的業(yè)務(wù)邏輯畔咧,所使用的微服務(wù)會(huì)比這個(gè)多。但是揖膜,在這個(gè)架構(gòu)上只需要加上和Item UI以及Item Service類似的組件就可以了誓沸。

聲明

這篇文章沒有將容器化和微服務(wù)編排考慮進(jìn)來,因?yàn)槟壳斑@個(gè)項(xiàng)目里還沒有用到它們壹粟。

配置服務(wù)Config Server

我們這里使用Spring Cloud Config來作為統(tǒng)一的配置中心拜隧。配置可以從多種不同的數(shù)據(jù)源進(jìn)行讀取,例如趁仙,一個(gè)單獨(dú)的git倉(cāng)庫(kù)洪添。在這個(gè)項(xiàng)目里,為了方便雀费,我們把它們放在應(yīng)用資源里:


microservice-3

Config server的配置(application.yml)如下:

yml
spring:
 profiles:
   active: native
 cloud:
   config:
     server:
       native:
         search-locations: classpath:/config
server:
 port: 8888

使用8888端口干奢,可以讓Config service客戶端使用默認(rèn)的配置,不需要在bootstrap.yml里指定端口盏袄。在啟動(dòng)的時(shí)候忿峻,客戶端會(huì)用一個(gè)GET請(qǐng)求來通過Config server的HTTP API獲取配置。

這個(gè)微服務(wù)應(yīng)用本身的代碼只有一個(gè)文件貌矿,它里面包含應(yīng)用類(applicaiton class)的聲明以及main方法炭菌,main方法和java代碼有些不同,它是一個(gè)頂級(jí)函數(shù):

@SpringBootApplication
@EnableConfigServer
class ConfigServerApplication
fun main(args: Array<String>) {
   runApplication<ConfigServerApplication>(*args)
}

其它微服務(wù)里的應(yīng)用類(Application class)以及main方法都是類似的形式逛漫。

服務(wù)發(fā)現(xiàn)(Service Discover Service)

服務(wù)發(fā)現(xiàn)是一種微服務(wù)架構(gòu)模式黑低,它能隱藏應(yīng)用之間的交互細(xì)節(jié),讓你不用關(guān)心應(yīng)用實(shí)例的數(shù)量以及網(wǎng)絡(luò)位置的變動(dòng)酌毡。它的關(guān)鍵組件包含服務(wù)注冊(cè)克握,微服務(wù)的存儲(chǔ),微服務(wù)實(shí)例以及網(wǎng)絡(luò)位置(更多信息請(qǐng)參考這個(gè))枷踏。

在這個(gè)項(xiàng)目里菩暗,服務(wù)發(fā)現(xiàn)是基于Netflix Eureka實(shí)現(xiàn)的,它是一個(gè)客戶端服務(wù)發(fā)現(xiàn):Eureka服務(wù)端會(huì)負(fù)責(zé)服務(wù)注冊(cè)旭蠕,客戶端會(huì)請(qǐng)求Eureka服務(wù)端來獲取應(yīng)用實(shí)例列表停团,然后在向微服務(wù)發(fā)送請(qǐng)求之前通過Netflix Robbon來進(jìn)行負(fù)載均衡。Netflix Eureka和很多其他Netflix OSS技術(shù)棧的其他組件(例如Hystrix和Ribbon)相似掏熬,都使用Spring Cloud Netflix來和Spring進(jìn)行整合佑稠。

服務(wù)發(fā)現(xiàn)的配置文件,在資源文件里(bootstrap.yml)旗芬,它只包含應(yīng)用名以及標(biāo)明在連接不上Config server的時(shí)候是否要中斷服務(wù)啟動(dòng)的配置舌胶。

spring:
 application:
   name: eureka-server
 cloud:
   config:
     fail-fast: true

其他的配置都是在Config server的eureka-server.yml文件里進(jìn)行配置:

server:
 port: 8761
eureka:
 client:
   register-with-eureka: true
   fetch-registry: false

Eureka服務(wù)用的8761端口,這樣可以允許所有的Eureka客戶端使用默認(rèn)的配置疮丛。register-with-eureka這個(gè)參數(shù)是用來表示當(dāng)前服務(wù)是不是也要注冊(cè)到Eureka server上幔嫂。fetch-registry參數(shù)表示Eureka客戶端是否需要從服務(wù)端獲取數(shù)據(jù)辆它。

已注冊(cè)的服務(wù)列表和其他的信息可以在http://localhost:8761/上查看:

image

其他可以用作服務(wù)發(fā)現(xiàn)的選項(xiàng)有Consul,Zookeeper等等履恩。

Item Service

這是一個(gè)使用Spring 5里出現(xiàn)WebFlux框架來實(shí)現(xiàn)的一個(gè)后臺(tái)系統(tǒng)锰茉,使用Kotlin DSL的代碼如下:

@Bean
fun itemsRouter(handler: ItemHandler) = router {
   path("/items").nest {
       GET("/", handler::getAll)
       POST("/", handler::add)
       GET("/{id}", handler::getOne)
       PUT("/{id}", handler::update)
   }
}

HTTP請(qǐng)求都被代理到ItemHandler bean上。例如似袁,獲取一系列對(duì)象列表的實(shí)現(xiàn)類似于:

fun getAll(request: ServerRequest) = ServerResponse.ok()
       .contentType(APPLICATION_JSON_UTF8)
       .body(fromObject(itemRepository.findAll()))

因?yàn)橛辛?code>spring-cloud-starter-netflix-eureka-client的依賴洞辣,這個(gè)應(yīng)用就變成了Eureka的一個(gè)客戶端,它會(huì)向Eureka注冊(cè)中心發(fā)送和接受數(shù)據(jù)昙衅。注冊(cè)完成之后扬霜,它會(huì)定時(shí)向Eurake服務(wù)發(fā)送心跳信息,如果在一段時(shí)間內(nèi)Eureka服務(wù)端沒有收到心跳而涉,或者在一段時(shí)間內(nèi)都到的心跳值低于某個(gè)閾值的話著瓶,Eureka服務(wù)端就會(huì)將這個(gè)應(yīng)用實(shí)例從注冊(cè)中心移除。

下面來看看給Eureka服務(wù)端發(fā)送消息的一種方式:

@PostConstruct
private fun addMetadata() = aim.registerAppMetadata(mapOf("description" to "Some description"))

你可以通過用Postman訪問 http://localhost:8761/eureka/apps/items-service 來驗(yàn)證發(fā)給Eureka服務(wù)端的數(shù)據(jù):

image

Items UI

這個(gè)微服務(wù)啼县,除了會(huì)和UI gateway(下一節(jié)介紹)交互之外材原,它也是Item service的前端,它可以通過以下幾種方式和Item service進(jìn)行交互:

  1. 客戶端到REST API季眷, 通過OpenFeign實(shí)現(xiàn)
interface ItemsServiceFeignClient {
   @GetMapping("/items/{id}")
   fun getItem(@PathVariable("id") id: Long): String
   @GetMapping("/not-existing-path")
   fun testHystrixFallback(): String
   @Component
   class ItemsServiceFeignClientFallbackFactory : FallbackFactory<ItemsServiceFeignClient> {
       private val log = LoggerFactory.getLogger(this::class.java)
       override fun create(cause: Throwable) = object : ItemsServiceFeignClient {
           override fun getItem(id: Long): String {
               log.error("Cannot get item with id=$id")
               throw ItemsUiException(cause)
           }
           override fun testHystrixFallback(): String {
               log.error("This is expected error")
               return "{\"error\" : \"Some error\"}"
           }
       }
   }
}
  1. 通過RestTemplate bean來實(shí)現(xiàn)

在java-config里余蟹,創(chuàng)建一個(gè)bean:

@Bean
@LoadBalanced
fun restTemplate() = RestTemplate()

然后這樣使用:

fun requestWithRestTemplate(id: Long): String =
       restTemplate.getForEntity("http://items-service/items/$id", String::class.java).body ?: "No result"
  1. WebClient bean(這個(gè)方式僅限于WebFlux框架)
    在java-config里,創(chuàng)建一個(gè)bean:
@Bean
fun webClient(loadBalancerClient: LoadBalancerClient) = WebClient.builder()
       .filter(LoadBalancerExchangeFilterFunction(loadBalancerClient))
       .build()

然后這樣使用:

       webClient.get().uri("http://items-service/items/$id").retrieve().bodyToMono(String::class.java)

你可以通過http://localhost:8081/exmple來驗(yàn)證這三種方式返回的都是一樣的結(jié)果:

  • 通過RestTemplate獲取Item: {"id":1, "name": "first"}
  • 通過WebClient獲取Item: {"id":1, "name": "first"}
  • 通過FeignClient獲取Item: {"id":1, "name": "first"}

我個(gè)人傾向于使用OpenFeign子刮,因?yàn)樗梢圆渴鹨粋€(gè)被調(diào)用服務(wù)的協(xié)議威酒,然后Spring會(huì)對(duì)它進(jìn)行實(shí)現(xiàn)。這個(gè)實(shí)現(xiàn)可以像一個(gè)正常的bean一樣來注入和使用:

itemsServiceFeignClient.getItem(1)

如果請(qǐng)求失敗了挺峡,F(xiàn)allFactory會(huì)被調(diào)用進(jìn)行錯(cuò)誤處理葵孤,然后返回相應(yīng)的相應(yīng)信息(或者繼續(xù)傳播異常)。在請(qǐng)求連續(xù)失敗的情況下橱赠,斷路器(Circuit breaker)會(huì)進(jìn)行斷路尤仍,給宕機(jī)的服務(wù)以時(shí)間來進(jìn)行恢復(fù)。

要使用Feign客戶端的話狭姨,需要在application class上加上@EnableFeignClients注解:

@SpringBootApplication
@EnableFeignClients(clients = [ItemsServiceFeignClient::class])
class ItemsUiApplication

如果要在Feign客戶端里使用Hystrix異吃桌玻恢復(fù)機(jī)制的話,你需要添加以下配置:

feign:
 hystrix:
   enabled: true

你可以通過http://localhost:8081/hystrix-fallback這個(gè)路徑來測(cè)試Hystrix的異潮模恢復(fù)機(jī)制赡模。Feign客戶端會(huì)請(qǐng)求Item service里不存在的一個(gè)路徑,這樣會(huì)導(dǎo)致如下的錯(cuò)誤返回:

{"error" : "Some error"}

UI Gateway

API網(wǎng)關(guān)(API Gateway)模式可以幫助你講所有其他微服務(wù)提供的API集中到一個(gè)節(jié)點(diǎn)上惕耕。實(shí)現(xiàn)這個(gè)模式的應(yīng)用會(huì)將對(duì)應(yīng)的請(qǐng)求路由到底層的系統(tǒng)上纺裁,并且它還會(huì)有一些額外的功能诫肠,例如身份驗(yàn)證司澎。

在這個(gè)項(xiàng)目里欺缘,為了更加清楚地進(jìn)行區(qū)別,實(shí)現(xiàn)了一個(gè)單獨(dú)的UI Gateway挤安,一個(gè)集成了所有UI的節(jié)點(diǎn)谚殊;很顯然,API gateway也是類似的實(shí)現(xiàn)方式蛤铜。這個(gè)微服務(wù)是基于Sping Cloud Gateway框架進(jìn)行實(shí)現(xiàn)的嫩絮。另外一個(gè)可選的方案是Netflix Zuul,它包含在Netflix OSS里围肥,并且通過Spring Cloud Netflix和Spring Boot進(jìn)行集成剿干。

這個(gè)UI gateway使用443端口,使用生成的SSL證書(存放在項(xiàng)目里)穆刻。SSL和HTTPS的配置如下:

server:
 port: 443
 ssl:
   key-store: classpath:keystore.p12
   key-store-password: qwerty
   key-alias: test_key
   key-store-type: PKCS12

用戶名和密碼都存在基于WebFlux規(guī)范的ReactiveUserDetailsService里置尔,它是一個(gè)基于Map的實(shí)現(xiàn):

@Bean
fun reactiveUserDetailsService(): ReactiveUserDetailsService {
   val user = User.withDefaultPasswordEncoder()
           .username("john_doe").password("qwerty").roles("USER")
           .build()
   val admin = User.withDefaultPasswordEncoder()
           .username("admin").password("admin").roles("ADMIN")
           .build()
   return MapReactiveUserDetailsService(user, admin)
}

安全選項(xiàng)設(shè)置如下:

@Bean
fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain = http
       .formLogin().loginPage("/login")
       .and()
       .authorizeExchange()
       .pathMatchers("/login").permitAll()
       .pathMatchers("/static/**").permitAll()
       .pathMatchers("/favicon.ico").permitAll()
       .pathMatchers("/webjars/**").permitAll()
       .pathMatchers("/actuator/**").permitAll()
       .anyExchange().authenticated()
       .and()
       .csrf().disable()
       .build()

上面的配置表示部分資源(例如靜態(tài)資源)是所有用戶可以訪問的,以及那些不需要鑒權(quán)的資源氢伟,最后其他的(.anyExchange())都只運(yùn)行登陸用戶訪問榜轿。當(dāng)你訪問一個(gè)需要鑒權(quán)的資源時(shí),你會(huì)被重定向到登陸界面(https://localhost/login):

microservice-5

這個(gè)界面通過Webjars來和我們的項(xiàng)目進(jìn)行交互朵锣,你可以像正趁危客戶端的庫(kù)來依賴管理。Thymeleaf是用來生成HTML頁面的诚些。Login頁面是通過WebFlux進(jìn)行配置的:

@Bean
fun routes() = router {
   GET("/login") { ServerResponse.ok().contentType(MediaType.TEXT_HTML).render("login") }
}

Spring Cloud Gateway的路由可以通過YAML或者java config來進(jìn)行配置飞傀。路由可以手動(dòng)進(jìn)行配置,也可以通過接收注冊(cè)中心的數(shù)據(jù)來進(jìn)行配置泣刹。如果需要路由的UI組件的數(shù)量比較大的話,通過注冊(cè)中心集成來進(jìn)行路由會(huì)方便很多椅您。

spring:
 cloud:
   gateway:
     discovery:
       locator:
         enabled: true
         lower-case-service-id: true
         include-expression: serviceId.endsWith('-UI')
         url-expression: "'lb:http://'+serviceId"

include-expression的值表示serviceId以“-UI”結(jié)尾,url-expression表示通過HTTP訪問的服務(wù)雪隧,這個(gè)和UI gateway使用HTTPS不同,客戶端的負(fù)載均衡(這里使用Netflix Ribbon)會(huì)被使用员舵。

接下來,我們看一個(gè)使用Java config手動(dòng)配置的一個(gè)實(shí)現(xiàn)(沒有和注冊(cè)中心集成):

@Bean
fun routeLocator(builder: RouteLocatorBuilder) = builder.routes {
   route("eureka-gui") {
       path("/eureka")
       filters {
           rewritePath("/eureka", "/")
       }
       uri("lb:http://eureka-server")
   }
   route("eureka-internals") {
       path("/eureka/**")
       uri("lb:http://eureka-server")
   }
}

第一個(gè)路由指向之前所展示的Eureka服務(wù)的主頁(http://localhost:8761)庄拇,第二條路用來加載當(dāng)前頁面的資源。

應(yīng)用創(chuàng)建的路由都可以通過訪問https://localhost/actuator/gateway/routes這個(gè)地址來查看。

在底層的微服務(wù)中措近,可能需要用戶在UI gateway里的賬號(hào)或者角色溶弟。為了實(shí)現(xiàn)這個(gè)瞭郑,我添加了一個(gè)過濾器(Filter)來往請(qǐng)求頭里添加相應(yīng)的信息:

@Component
class AddCredentialsGlobalFilter : GlobalFilter {
   private val loggedInUserHeader = "logged-in-user"
   private val loggedInUserRolesHeader = "logged-in-user-roles"
   override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain) = exchange.getPrincipal<Principal>()
           .flatMap {
               val request = exchange.request.mutate()
                       .header(loggedInUserHeader, it.name)
                       .header(loggedInUserRolesHeader, (it as Authentication).authorities?.joinToString(";") ?: "")
                       .build()
               chain.filter(exchange.mutate().request(request).build())
           }
}

現(xiàn)在屈张,讓我們通過UI gateway來訪問Item UI — https://localhost/items-ui/greeting - 立馬能夠驗(yàn)證Item UI已經(jīng)正確地處理了請(qǐng)求頭的信息:

microservice-6

Spring Cloud Sleuth是一個(gè)用來在分布式系統(tǒng)里追蹤請(qǐng)求的一個(gè)解決方案。Trace Id(通過identifier進(jìn)行傳遞)以及Span Id(用來區(qū)分一個(gè)事務(wù)單位)都被加入到跨多個(gè)微服務(wù)的請(qǐng)求里(為了便于理解碳抄,我簡(jiǎn)化了整個(gè)流程场绿,詳細(xì)的信息可以參考這里):

image

只需要添加spring-cloud-starter-sleuth的依賴裳凸,這個(gè)功能就可以使用了。

通過添加合適的日志配置逗宁,你就可以在控制臺(tái)里看到微服務(wù)相關(guān)的信息(Trace Id和Span Id都展示在微服務(wù)名稱之后):

DEBUG [ui-gateway,009b085bfab5d0f2,009b085bfab5d0f2,false] o.s.c.g.h.RoutePredicateHandlerMapping   : Route matched: CompositeDiscoveryClient_ITEMS-UI
DEBUG [items-ui,009b085bfab5d0f2,947bff0ce8d184f4,false] o.s.w.r.function.server.RouterFunctions  : Predicate "(GET && /example)" matches against "GET /example"
DEBUG [items-service,009b085bfab5d0f2,dd3fa674cd994b01,false] o.s.w.r.function.server.RouterFunctions  : Predicate "(GET && /{id})" matches against "GET /1"

如果你想展示調(diào)用關(guān)系的圖狀信息的話瞎颗,你可以使用Zapkin捌议,它會(huì)執(zhí)行服務(wù)請(qǐng)求,然后聚合微服務(wù)HTTP請(qǐng)求頭里的信息倦逐。

構(gòu)建

取決于你的操作系統(tǒng)宫补,使用gradlew clean build或者./gradlew clean build粉怕。

如果使用Gradle wrapper,就沒有必要安裝Gradle了贫贝。

在JDK 11.0.1上,能夠正常構(gòu)建并按順序啟動(dòng)崇堵。除此之外,這個(gè)項(xiàng)目在JDK 10上面也是可以工作的,所以我保證這個(gè)版本上運(yùn)行也沒有問題棍辕。但是對(duì)于更早的版本JDK还绘,我沒有任何數(shù)據(jù)支撐。另外需要考慮的一點(diǎn)就是抚太,這里使用的Gradle 5支持只支持JDK 8及以后的版本昔案。

發(fā)布

我建議按照本文介紹的順序來啟動(dòng)應(yīng)用踏揣。如果你使用Intellij IDEA,并且有Run Dashboard的話又谋,你會(huì)看到類似下圖的界面:


microservice-launch

結(jié)論

這篇文章里我們介紹了業(yè)內(nèi)建議的基于現(xiàn)代Java技術(shù)棧的微服務(wù)架構(gòu)的一個(gè)實(shí)例娱局,包含主要的組件以及一些特性衰齐。希望這篇文章對(duì)您有所幫助。謝謝仁卷!

參考

Github上的項(xiàng)目代碼
Chris Richardson的微服務(wù)相關(guān)的文章
Martin Fowler的微服務(wù)相關(guān)的文章
Martin Fowler的微服務(wù)的指南

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锦积,一起剝皮案震驚了整個(gè)濱河市歉嗓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哮幢,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垛叨,死亡現(xiàn)場(chǎng)離奇詭異嗽元,居然都是意外死亡喂击,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來监嗜,“玉大人裁奇,你說我怎么就攤上這事】文唬” “怎么了五垮?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵放仗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我莉撇,道長(zhǎng)惶傻,這世上最難降的妖魔是什么银室? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任励翼,我火速辦了婚禮汽抚,結(jié)果婚禮上伯病,老公的妹妹穿的比我還像新娘。我一直安慰自己惭蟋,他們只是感情好季研,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布与涡。 她就那樣靜靜地躺著持偏,像睡著了一般鸿秆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桥胞,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天贩虾,我揣著相機(jī)與錄音沥阱,去河邊找鬼。 笑死策精,一個(gè)胖子當(dāng)著我的面吹牛崇棠,可吹牛的內(nèi)容都是我干的枕稀。 我是一名探鬼主播及老,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼骄恶,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼匕垫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起寞秃,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤春寿,失蹤者是張志新(化名)和其女友劉穎忽孽,沒想到半個(gè)月后兄一,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡造壮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年耳璧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了展箱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片析藕。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡账胧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出筹煮,到底是詐尸還是另有隱情居夹,我是刑警寧澤本冲,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布檬洞,位于F島的核電站沟饥,受9級(jí)特大地震影響贤旷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜艾杏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一盅藻、第九天 我趴在偏房一處隱蔽的房頂上張望萧求。 院中可真熱鬧顶瞒,春花似錦、人聲如沸守问。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嗽仪,卻和暖如春柒莉,著一層夾襖步出監(jiān)牢的瞬間兢孝,已是汗流浹背仅偎。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工橘沥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留相种,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓箫措,卻偏偏與公主長(zhǎng)得像斤蔓,于是被迫代替她去往敵國(guó)和親镀岛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容