微服務(wù)網(wǎng)關(guān)Zuul遷移到Spring Cloud Gateway

背景

在之前的文章中,我們介紹過微服務(wù)網(wǎng)關(guān)Spring Cloud Netflix Zuul腋粥,前段時(shí)間有兩篇文章專門介紹了Spring Cloud的全新項(xiàng)目Spring Cloud Gateway胶背,以及其中的過濾器工廠挽荠。本文將會(huì)介紹將微服務(wù)網(wǎng)關(guān)由Zuul遷移到Spring Cloud Gateway啦扬。

Spring Cloud Netflix Zuul是由Netflix開源的API網(wǎng)關(guān)镀迂,在微服務(wù)架構(gòu)下内狗,網(wǎng)關(guān)作為對外的門戶怪嫌,實(shí)現(xiàn)動(dòng)態(tài)路由、監(jiān)控柳沙、授權(quán)岩灭、安全、調(diào)度等功能偎行。

Zuul基于servlet 2.5(使用3.x)川背,使用阻塞API。 它不支持任何長連接蛤袒,如websockets熄云。而Gateway建立在Spring Framework 5,Project Reactor和Spring Boot 2之上妙真,使用非阻塞API缴允。 比較完美地支持異步非阻塞編程,先前的Spring系大多是同步阻塞的編程模式珍德,使用thread-per-request處理模型练般。即使在Spring MVC Controller方法上加@Async注解或返回DeferredResult、Callable類型的結(jié)果锈候,其實(shí)仍只是把方法的同步調(diào)用封裝成執(zhí)行任務(wù)放到線程池的任務(wù)隊(duì)列中薄料,還是thread-per-request模型。Gateway 中Websockets得到支持泵琳,并且由于它與Spring緊密集成摄职,所以將會(huì)是一個(gè)更好的開發(fā)體驗(yàn)。

在一個(gè)微服務(wù)集成的項(xiàng)目中microservice-integration获列,我們整合了包括網(wǎng)關(guān)谷市、auth權(quán)限服務(wù)和backend服務(wù)。提供了一套微服務(wù)架構(gòu)下击孩,網(wǎng)關(guān)服務(wù)路由迫悠、鑒權(quán)和授權(quán)認(rèn)證的項(xiàng)目案例。整個(gè)項(xiàng)目的架構(gòu)圖如下:

微服務(wù)架構(gòu)權(quán)限-aoho .png

具體參見:微服務(wù)架構(gòu)中整合網(wǎng)關(guān)巩梢、權(quán)限服務(wù)创泄。本文將以該項(xiàng)目中的Zuul網(wǎng)關(guān)升級作為示例艺玲。

Zuul網(wǎng)關(guān)

在該項(xiàng)目中,Zuul網(wǎng)關(guān)的主要功能為路由轉(zhuǎn)發(fā)鞠抑、鑒權(quán)授權(quán)和安全訪問等功能板驳。

Zuul中,很容易配置動(dòng)態(tài)路由轉(zhuǎn)發(fā)碍拆,如:

zuul:
  ribbon:
    eager-load:
      enabled: true     #zuul饑餓加載
  host:
    maxTotalConnections: 200
    maxPerRouteConnections: 20
  routes:
    user:
      path: /user/**
      ignoredPatterns: /consul
      serviceId: user
      sensitiveHeaders: Cookie,Set-Cookie

默認(rèn)情況下,Zuul在請求路由時(shí)慨蓝,會(huì)過濾HTTP請求頭信息中的一些敏感信息感混,這里我們不過多介紹。

網(wǎng)關(guān)中還配置了請求的鑒權(quán)礼烈,結(jié)合Auth服務(wù)弧满,通過Zuul自帶的Pre過濾器可以實(shí)現(xiàn)該功能。當(dāng)然還可以利用Post過濾器對請求結(jié)果進(jìn)行適配和修改等操作此熬。

除此之外庭呜,還可以配置限流過濾器和斷路器,下文中將會(huì)增加實(shí)現(xiàn)這部分功能犀忱。

遷移到Spring Cloud Gateway

筆者新建了一個(gè)gateway-enhanced的項(xiàng)目募谎,因?yàn)樽兓艽螅贿m合在之前的gateway項(xiàng)目基礎(chǔ)上修改阴汇。實(shí)現(xiàn)的主要功能如下:路由轉(zhuǎn)發(fā)数冬、權(quán)重路由、斷路器搀庶、限流拐纱、鑒權(quán)和黑白名單等。本文基于主要實(shí)現(xiàn)如下的三方面功能:

  • 路由斷言
  • 過濾器(包括全局過濾器哥倔,如斷路器秸架、限流等)
  • 全局鑒權(quán)
  • 路由配置
  • CORS

依賴

本文采用的Spring Cloud Gateway版本為2.0.0.RELEASE。增加的主要依賴如下咆蒿,具體的細(xì)節(jié)可以參見Github上的項(xiàng)目东抹。

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <!--<version>2.0.1.RELEASE</version>-->
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-gateway-webflux</artifactId>
        </dependency>
    </dependencies>
        
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>        

路由斷言

Spring Cloud Gateway對于路由斷言、過濾器和路由的定義蜡秽,同時(shí)支持配置文件的shortcut和Fluent API府阀。我們將以在本項(xiàng)目中實(shí)際使用的功能進(jìn)行講解。

路由斷言在網(wǎng)關(guān)進(jìn)行轉(zhuǎn)發(fā)請求之前進(jìn)行判斷路由的具體服務(wù)芽突,通呈哉悖可以根據(jù)請求的路徑、請求體寞蚌、請求方式(GET/POST)田巴、請求地址钠糊、請求時(shí)間、請求的HOST等信息壹哺。我們主要用到的是基于請求路徑的方式抄伍,如下:

spring:
  cloud:
    gateway:
      routes:
      - id: service_to_web
        uri: lb://authdemo
        predicates:
        - Path=/demo/**

我們定義了一個(gè)名為service_to_web的路由,將請求路徑以/demo/**的請求都轉(zhuǎn)發(fā)到authdemo服務(wù)實(shí)例管宵。

我們在本項(xiàng)目中路由斷言的需求并不復(fù)雜截珍,下面介紹通過Fluent API配置的其他路由斷言:

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.host("**.changeuri.org").and().header("X-Next-Url")
                        .uri("http://blueskykong.com"))
                .route(r -> r.host("**.changeuri.org").and().query("url")
                        .uri("http://blueskykong.com"))
                .build();
    }

在如上的路由定義中,我們配置了以及請求HOST箩朴、請求頭部和請求的參數(shù)岗喉。在一個(gè)路由定義中,可以配置多個(gè)斷言炸庞,采取與或非的關(guān)系判斷钱床。

以上增加的配置僅作為擴(kuò)展,讀者可以根據(jù)自己的需要進(jìn)行配置相應(yīng)的斷言埠居。

過濾器

過濾器分為全局過濾器和局部過濾器查牌。我們通過實(shí)現(xiàn)GlobalFilterGatewayFilter接口滥壕,自定義過濾器纸颜。

全局過濾器

本項(xiàng)目中,我們配置了如下的全局過濾器:

  • 基于令牌桶的限流過濾器
  • 基于漏桶算法的限流過濾器
  • 全局?jǐn)嗦菲?/li>
  • 全局鑒權(quán)過濾器

定義全局過濾器捏浊,可以通過在配置文件中懂衩,增加spring.cloud.gateway.default-filters,或者實(shí)現(xiàn)GlobalFilter接口金踪。

基于令牌桶的限流過濾器

隨著時(shí)間流逝浊洞,系統(tǒng)會(huì)按恒定 1/QPS 時(shí)間間隔(如果 QPS=100,則間隔是 10ms)往桶里加入 Token胡岔,如果桶已經(jīng)滿了就不再加了法希。每個(gè)請求來臨時(shí),會(huì)拿走一個(gè) Token靶瘸,如果沒有 Token 可拿了苫亦,就阻塞或者拒絕服務(wù)。

令牌桶的另外一個(gè)好處是可以方便的改變速度怨咪。一旦需要提高速率屋剑,則按需提高放入桶中的令牌的速率。一般會(huì)定時(shí)(比如 100 毫秒)往桶中增加一定數(shù)量的令牌诗眨,有些變種算法則實(shí)時(shí)的計(jì)算應(yīng)該增加的令牌的數(shù)量唉匾。

在Spring Cloud Gateway中提供了默認(rèn)的實(shí)現(xiàn),我們需要引入redis的依賴:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

并進(jìn)行如下的配置:

spring:
  redis:
    host: localhost
    password: pwd
    port: 6378
  cloud:
    default-filters:
      - name: RequestRateLimiter
        args:
          key-resolver: "#{@remoteAddrKeyResolver}"
          rate-limiter: "#{@customRateLimiter}"   # token

注意到,在配置中使用了兩個(gè)SpEL表達(dá)式巍膘,分別定義限流鍵和限流的配置厂财。因此,我們需要在實(shí)現(xiàn)中增加如下的配置:

    @Bean(name = "customRateLimiter")
    public RedisRateLimiter myRateLimiter(GatewayLimitProperties gatewayLimitProperties) {
        GatewayLimitProperties.RedisRate redisRate = gatewayLimitProperties.getRedisRate();
        if (Objects.isNull(redisRate)) {
            throw new ServerException(ErrorCodes.PROPERTY_NOT_INITIAL);
        }
        return new RedisRateLimiter(redisRate.getReplenishRate(), redisRate.getBurstCapacity());
    }
    
        @Bean(name = RemoteAddrKeyResolver.BEAN_NAME)
    public RemoteAddrKeyResolver remoteAddrKeyResolver() {
        return new RemoteAddrKeyResolver();
    }

在如上的實(shí)現(xiàn)中峡懈,初始化好RedisRateLimiterRemoteAddrKeyResolver兩個(gè)Bean實(shí)例璃饱,RedisRateLimiter是定義在Gateway中的redis限流屬性;而RemoteAddrKeyResolver使我們自定義的肪康,基于請求的地址作為限流鍵荚恶。如下為該限流鍵的定義:

public class RemoteAddrKeyResolver implements KeyResolver {
    private static final Logger LOGGER = LoggerFactory.getLogger(RemoteAddrKeyResolver.class);

    public static final String BEAN_NAME = "remoteAddrKeyResolver";

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        LOGGER.debug("token limit for ip: {} ", exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }

}

RemoteAddrKeyResolver實(shí)現(xiàn)了KeyResolver接口,覆寫其中定義的接口磷支,返回值為請求中的地址裆甩。

如上,即實(shí)現(xiàn)了基于令牌桶算法的鏈路過濾器齐唆,具體細(xì)節(jié)不再展開。

基于漏桶算法的限流過濾器

漏桶(Leaky Bucket)算法思路很簡單冻河,水(請求)先進(jìn)入到漏桶里箍邮,漏桶以一定的速度出水(接口有響應(yīng)速率),當(dāng)水流入速度過大會(huì)直接溢出(訪問頻率超過接口響應(yīng)速率)叨叙,然后就拒絕請求锭弊,可以看出漏桶算法能強(qiáng)行限制數(shù)據(jù)的傳輸速率。

這部分實(shí)現(xiàn)讀者參見GitHub項(xiàng)目以及文末配套的書擂错,此處略過味滞。

全局?jǐn)嗦菲?/h5>

關(guān)于Hystrix斷路器,是一種服務(wù)容錯(cuò)的保護(hù)措施钮呀。斷路器本身是一種開關(guān)裝置剑鞍,用于在電路上保護(hù)線路過載,當(dāng)線路中有發(fā)生短路狀況時(shí)爽醋,斷路器能夠及時(shí)的切斷故障電路蚁署,防止發(fā)生過載、起火等情況蚂四。

微服務(wù)架構(gòu)中光戈,斷路器模式的作用也是類似的,當(dāng)某個(gè)服務(wù)單元發(fā)生故障之后遂赠,通過斷路器的故障監(jiān)控久妆,直接切斷原來的主邏輯調(diào)用。關(guān)于斷路器的更多資料和Hystrix實(shí)現(xiàn)原理跷睦,讀者可以參考文末配套的書筷弦。

這里需要引入spring-cloud-starter-netflix-hystrix依賴:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <optional>true</optional>
        </dependency>

并增加如下的配置:

      default-filters:
      - name: Hystrix
        args:
          name: fallbackcmd
          fallbackUri: forward:/fallbackcontroller

如上的配置,將會(huì)使用HystrixCommand打包剩余的過濾器送讲,并命名為fallbackcmd奸笤,我們還配置了可選的參數(shù)fallbackUri惋啃,降級邏輯被調(diào)用,請求將會(huì)被轉(zhuǎn)發(fā)到URI為/fallbackcontroller的控制器處理监右。定義降級處理如下:

    @RequestMapping(value = "/fallbackcontroller")
    public Map<String, String> fallBackController() {
        Map<String, String> res = new HashMap();
        res.put("code", "-100");
        res.put("data", "service not available");
        return res;
    }
全局鑒權(quán)過濾器

我們通過自定義一個(gè)全局過濾器實(shí)現(xiàn)边灭,對請求合法性的鑒權(quán)。具體功能不再贅述了健盒,通過實(shí)現(xiàn)GlobalFilter接口绒瘦,區(qū)別的是Webflux傳入的是ServerWebExchange,通過判斷是不是外部接口(外部接口不需要登錄鑒權(quán))扣癣,執(zhí)行之前實(shí)現(xiàn)的處理邏輯惰帽。

public class AuthorizationFilter implements GlobalFilter, Ordered {

    //....

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        if (predicate(exchange)) {
            request = headerEnhanceFilter.doFilter(request);
            String accessToken = extractHeaderToken(request);

            customRemoteTokenServices.loadAuthentication(accessToken);
            LOGGER.info("success auth token and permission!");
        }

        return chain.filter(exchange);
    }
    //提出頭部的token
    protected String extractHeaderToken(ServerHttpRequest request) {
        List<String> headers = request.getHeaders().get("Authorization");
        if (Objects.nonNull(headers) && headers.size() > 0) { // typically there is only one (most servers enforce that)
            String value = headers.get(0);
            if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) {
                String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();
                // Add this here for the auth details later. Would be better to change the signature of this method.
                int commaIndex = authHeaderValue.indexOf(',');
                if (commaIndex > 0) {
                    authHeaderValue = authHeaderValue.substring(0, commaIndex);
                }
                return authHeaderValue;
            }
        }

        return null;
    }

}

定義好全局過濾器之后,只需要配置一下即可:

    @Bean
    public AuthorizationFilter authorizationFilter(CustomRemoteTokenServices customRemoteTokenServices,
                                                   HeaderEnhanceFilter headerEnhanceFilter,
                                                   PermitAllUrlProperties permitAllUrlProperties) {
        return new AuthorizationFilter(customRemoteTokenServices, headerEnhanceFilter, permitAllUrlProperties);
    }

局部過濾器

我們常用的局部過濾器有增減請求和相應(yīng)頭部父虑、增減請求的路徑等多種過濾器该酗。我們這里用到的是去除請求的指定前綴,這部分前綴只是用戶網(wǎng)關(guān)進(jìn)行路由判斷士嚎,在轉(zhuǎn)發(fā)到具體服務(wù)時(shí)呜魄,需要去除前綴:

      - id: service_to_user
        uri: lb://user
        order: 8000
        predicates:
        - Path=/user/**
        filters:
        - AddRequestHeader=X-Request-Foo, Bar
        - StripPrefix=1

還可以通過Fluent API,如下:

    @Bean
    public RouteLocator retryRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("retry_java", r -> r.path("/test/**")
                        .filters(f -> f.stripPrefix(1)
                                .retry(config -> config.setRetries(2).setStatuses(HttpStatus.INTERNAL_SERVER_ERROR)))
                        .uri("lb://user"))
                .build();
    }

除了設(shè)置前綴過濾器外莱衩,我們還設(shè)置了重試過濾器爵嗅,可以參見:Spring Cloud Gateway中的過濾器工廠:重試過濾器

路由配置

路由定義在上面的示例中已經(jīng)有列出,可以通過配置文件和定義RouteLocator的對象笨蚁。這里需要注意的是睹晒,配置中的uri屬性,可以是具體的服務(wù)地址(IP+端口號)括细,也可以是通過服務(wù)發(fā)現(xiàn)加上負(fù)載均衡定義的:lb://user伪很,表示轉(zhuǎn)發(fā)到user的服務(wù)實(shí)例。當(dāng)然這需要我們進(jìn)行一些配置奋单。

引入服務(wù)發(fā)現(xiàn)的依賴:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

網(wǎng)關(guān)中開啟spring.cloud.gateway.discovery.locator.enabled=true即可是掰。

CORS配置

在Spring 5 Webflux中,配置CORS辱匿,可以通過自定義WebFilter實(shí)現(xiàn):

    private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN";
    private static final String ALLOWED_METHODS = "GET, PUT, POST, DELETE, OPTIONS";
    private static final String ALLOWED_ORIGIN = "*";
    private static final String MAX_AGE = "3600";

    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                ServerHttpResponse response = ctx.getResponse();
                HttpHeaders headers = response.getHeaders();
                headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
                headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
                headers.add("Access-Control-Max-Age", MAX_AGE);
                headers.add("Access-Control-Allow-Headers",ALLOWED_HEADERS);
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }
            return chain.filter(ctx);
        };
    }

上述代碼實(shí)現(xiàn)比較簡單键痛,讀者根據(jù)實(shí)際的需要配置ALLOWED_ORIGIN等參數(shù)。

總結(jié)

在高并發(fā)和潛在的高延遲場景下匾七,網(wǎng)關(guān)要實(shí)現(xiàn)高性能高吞吐量的一個(gè)基本要求是全鏈路異步絮短,不要阻塞線程。Zuul網(wǎng)關(guān)采用同步阻塞模式不符合要求昨忆。

Spring Cloud Gateway基于Webflux丁频,比較完美地支持異步非阻塞編程,很多功能實(shí)現(xiàn)起來比較方便。Spring5必須使用java 8席里,函數(shù)式編程就是java8重要的特點(diǎn)之一叔磷,而WebFlux支持函數(shù)式編程來定義路由端點(diǎn)處理請求。

通過如上的實(shí)現(xiàn)奖磁,我們將網(wǎng)關(guān)從Zuul遷移到了Spring Cloud Gateway改基。在Gateway中定義了豐富的路由斷言和過濾器,通過配置文件或者Fluent API可以直接調(diào)用和使用咖为,非常方便秕狰。在性能上,也是勝于之前的Zuul網(wǎng)關(guān)躁染。

欲了解更詳細(xì)的實(shí)現(xiàn)原理和細(xì)節(jié)鸣哀,大家可以關(guān)注筆者本月底即將出版的《Spring Cloud 微服務(wù)架構(gòu)進(jìn)階》,本書中對Spring Cloud Finchley.RELEASE版本的各個(gè)主要組件進(jìn)行原理講解和實(shí)戰(zhàn)應(yīng)用吞彤,網(wǎng)關(guān)則是基于最新的Spring Cloud Gateway我衬。

Spring Cloud 微服務(wù)架構(gòu)進(jìn)階

本文的源碼地址:
GitHub:https://github.com/keets2012/microservice-integration
或者 碼云:https://gitee.com/keets/microservice-integration

訂閱最新文章,歡迎關(guān)注我的公眾號

微信公眾號

參考

Spring Cloud Gateway(限流)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饰恕,一起剝皮案震驚了整個(gè)濱河市低飒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌懂盐,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糕档,死亡現(xiàn)場離奇詭異莉恼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)速那,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門俐银,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人端仰,你說我怎么就攤上這事捶惜。” “怎么了荔烧?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵吱七,是天一觀的道長。 經(jīng)常有香客問我鹤竭,道長踊餐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任臀稚,我火速辦了婚禮吝岭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己窜管,他們只是感情好散劫,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著幕帆,像睡著了一般获搏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜓肆,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天颜凯,我揣著相機(jī)與錄音,去河邊找鬼仗扬。 笑死症概,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的早芭。 我是一名探鬼主播彼城,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼退个!你這毒婦竟也來了募壕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤语盈,失蹤者是張志新(化名)和其女友劉穎舱馅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刀荒,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡代嗤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缠借。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片干毅。...
    茶點(diǎn)故事閱讀 40,488評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖泼返,靈堂內(nèi)的尸體忽然破棺而出硝逢,到底是詐尸還是另有隱情,我是刑警寧澤绅喉,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布渠鸽,位于F島的核電站,受9級特大地震影響柴罐,放射性物質(zhì)發(fā)生泄漏拱绑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一丽蝎、第九天 我趴在偏房一處隱蔽的房頂上張望猎拨。 院中可真熱鬧膀藐,春花似錦、人聲如沸红省。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吧恃。三九已至虾啦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間痕寓,已是汗流浹背傲醉。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呻率,地道東北人硬毕。 一個(gè)月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像礼仗,于是被迫代替她去往敵國和親吐咳。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評論 2 359

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