RPC之美團(tuán)pigeon源碼分析(三)調(diào)用方服務(wù)監(jiān)聽和調(diào)用

在此之前我們理清了pigeon服務(wù)方的初始化力图、注冊和消息處理邏輯瑞眼,本篇我們來看看pigeon調(diào)用方的實(shí)現(xiàn)鼠渺。

第一部分我們先看看服務(wù)調(diào)用的實(shí)現(xiàn)。
服務(wù)調(diào)用示例:

@RestController
@RequestMapping("/common")
public class CommonController {

    @Autowired
    private CommonService commonService;

    @RequestMapping(value = "/hello")
    @ResponseBody
    public String hello(@RequestParam("name") String name) {
        System.out.println("enter hello");
        return commonService.hello(name);
    }
}

CommonService 就是服務(wù)方發(fā)布的服務(wù)接口戚啥,可以看到在調(diào)用方只需要引入相應(yīng)服務(wù)的api jar包枉层,就可以像調(diào)用本地方法一樣調(diào)用對(duì)應(yīng)的服務(wù)接口速缆,這也是大部分RPC框架的實(shí)現(xiàn)效果懂扼。
CommonService 通過@Autowired注解在spring容器中找到對(duì)應(yīng)的bean,我們來看看相應(yīng)的bean配置

    <bean id="commonService" class="com.dianping.pigeon.remoting.invoker.config.spring.ReferenceBean" init-method="init">
        <!-- 服務(wù)全局唯一的標(biāo)識(shí)url嘹锁,默認(rèn)是服務(wù)接口類名葫录,必須設(shè)置 -->
        <property name="url" value="http://service.dianping.com/rpcserver/commonService_1.0.0" />
        <!-- 接口名稱,必須設(shè)置 -->
        <property name="interfaceName" value="com.study.rpcserver.api.CommonService" />
        <!-- 超時(shí)時(shí)間领猾,毫秒米同,默認(rèn)5000,建議自己設(shè)置 -->
        <property name="timeout" value="2000" />
        <!-- 序列化摔竿,hessian/fst/protostuff窍霞,默認(rèn)hessian,可不設(shè)置-->
        <property name="serialize" value="hessian" />
        <!-- 調(diào)用方式拯坟,sync/future/callback/oneway但金,默認(rèn)sync,可不設(shè)置 -->
        <property name="callType" value="sync" />
        <!-- 失敗策略郁季,快速失敗failfast/失敗轉(zhuǎn)移failover/失敗忽略failsafe/并發(fā)取最快返回forking冷溃,默認(rèn)failfast,可不設(shè)置 -->
        <property name="cluster" value="failfast" />
        <!-- 是否超時(shí)重試梦裂,默認(rèn)false似枕,可不設(shè)置 -->
        <property name="timeoutRetry" value="false" />
        <!-- 重試次數(shù),默認(rèn)1年柠,可不設(shè)置 -->
        <property name="retries" value="1" />
    </bean>

ReferenceBean繼承了spring的FactoryBean接口凿歼,來處理復(fù)雜bean的生成,通過getObject()方法來返回對(duì)應(yīng)bean實(shí)例冗恨。接下來我們就以ReferenceBean為入口來切入pigeon調(diào)用方的實(shí)現(xiàn)思路答憔。

    public void init() throws Exception {
        if (StringUtils.isBlank(interfaceName)) {
            throw new IllegalArgumentException("invalid interface:" + interfaceName);
        }
        this.objType = ClassUtils.loadClass(this.classLoader, this.interfaceName.trim());
        //服務(wù)調(diào)用相關(guān)的配置信息,就是我們對(duì)每一個(gè)接口服務(wù)在xml文件中的配置
        InvokerConfig<?> invokerConfig = new InvokerConfig(this.objType, this.url, this.timeout, this.callType,
                this.serialize, this.callback, this.group, this.writeBufferLimit, this.loadBalance, this.cluster,
                this.retries, this.timeoutRetry, this.vip, this.version, this.protocol);
        invokerConfig.setClassLoader(classLoader);
        invokerConfig.setSecret(secret);
        invokerConfig.setRegionPolicy(regionPolicy);

        if (!CollectionUtils.isEmpty(methods)) {
            Map<String, InvokerMethodConfig> methodMap = new HashMap<String, InvokerMethodConfig>();
            invokerConfig.setMethods(methodMap);
            for (InvokerMethodConfig method : methods) {
                methodMap.put(method.getName(), method);
            }
        }

        checkMock(); // 降級(jí)配置檢查
        invokerConfig.setMock(mock);
        checkRemoteAppkey();
        invokerConfig.setRemoteAppKey(remoteAppKey);
        //生成接口的代理對(duì)象
        this.obj = ServiceFactory.getService(invokerConfig);
        configLoadBalance(invokerConfig);
    }
    //FactoryBean返回的bean實(shí)例
    public Object getObject() {
        return this.obj;
    }

ServiceFactory.getService(invokerConfig);根據(jù)配置的interfaceName生成一個(gè)java代理對(duì)象

    private static ServiceProxy serviceProxy = ServiceProxyLoader.getServiceProxy();

    public static <T> T getService(InvokerConfig<T> invokerConfig) throws RpcException {
        return serviceProxy.getProxy(invokerConfig);
    }

跟蹤代碼掀抹,進(jìn)入AbstractServiceProxy.getProxy方法虐拓,核心代碼如下:

    protected final static Map<InvokerConfig<?>, Object> services = new ConcurrentHashMap<InvokerConfig<?>, Object>();
    @Override
    public <T> T getProxy(InvokerConfig<T> invokerConfig) {
        //InvokerConfig實(shí)現(xiàn)了自定義equals和hashCode方法
        service = services.get(invokerConfig);
        if (service == null) {
            synchronized (interner.intern(invokerConfig)) {
                service = services.get(invokerConfig);
                if (service == null) {
                    //此處執(zhí)行調(diào)用方的一些初始化邏輯,包括InvokerProcessHandlerFactory.init();初始化調(diào)用方Filter責(zé)任鏈等
                    InvokerBootStrap.startup();
                    //生成代理對(duì)象
                    service = SerializerFactory.getSerializer(invokerConfig.getSerialize()).proxyRequest(invokerConfig);
                    try {
                        //獲取服務(wù)信息傲武,創(chuàng)建Client實(shí)例
                        ClientManager.getInstance().registerClients(invokerConfig);
                    } catch (Throwable t) {
                        logger.warn("error while trying to setup service client:" + invokerConfig, t);
                    }
                    services.put(invokerConfig, service);
                }
        }
        return (T) service;
    }

AbstractSerializer.proxyRequest使用我們熟悉的JDK動(dòng)態(tài)代理來生成服務(wù)接口的代理對(duì)象

    @Override
    public Object proxyRequest(InvokerConfig<?> invokerConfig) throws SerializationException {
        return Proxy.newProxyInstance(ClassUtils.getCurrentClassLoader(invokerConfig.getClassLoader()),
                new Class[] { invokerConfig.getServiceInterface() }, new ServiceInvocationProxy(invokerConfig,
                        InvokerProcessHandlerFactory.selectInvocationHandler(invokerConfig)));
    }
        //InvokerProcessHandlerFactory.selectInvocationHandler獲取調(diào)用方請求責(zé)任鏈
        public static void init() {
        if (!isInitialized) {
            if (Constants.MONITOR_ENABLE) {
                registerBizProcessFilter(new RemoteCallMonitorInvokeFilter());
            }
            registerBizProcessFilter(new TraceFilter());
            registerBizProcessFilter(new DegradationFilter());
                        //關(guān)于ClusterInvokeFilter后文詳細(xì)介紹
            registerBizProcessFilter(new ClusterInvokeFilter());
            registerBizProcessFilter(new GatewayInvokeFilter());
            registerBizProcessFilter(new ContextPrepareInvokeFilter());
            registerBizProcessFilter(new SecurityFilter());
                        //遠(yuǎn)程調(diào)用
            registerBizProcessFilter(new RemoteCallInvokeFilter());
            bizInvocationHandler = createInvocationHandler(bizProcessFilters);
            isInitialized = true;
        }
    }

    public static ServiceInvocationHandler selectInvocationHandler(InvokerConfig<?> invokerConfig) {
        return bizInvocationHandler;
    }

ServiceInvocationProxy繼承了java.lang.reflect.InvocationHandler接口蓉驹,invoke實(shí)現(xiàn)邏輯如下:

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
                //代理對(duì)象的非服務(wù)方法調(diào)用走原有邏輯
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(handler, args);
        }
        if ("toString".equals(methodName) && parameterTypes.length == 0) {
            return handler.toString();
        }
        if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
            return handler.hashCode();
        }
        if ("equals".equals(methodName) && parameterTypes.length == 1) {
            return handler.equals(args[0]);
        }
                //服務(wù)接口執(zhí)行責(zé)任鏈處理邏輯
        return extractResult(handler.handle(new DefaultInvokerContext(invokerConfig, methodName, parameterTypes, args)),
                method.getReturnType());
    }

同服務(wù)端責(zé)任鏈的分析一樣城榛,我們首先重點(diǎn)看下RemoteCallInvokeFilter的處理邏輯,核心代碼如下:

    @Override
    public InvocationResponse invoke(ServiceInvocationHandler handler, InvokerContext invocationContext)
            throws Throwable {
        Client client = invocationContext.getClient();
        InvocationRequest request = invocationContext.getRequest();
        InvokerConfig<?> invokerConfig = invocationContext.getInvokerConfig();    
        态兴。狠持。。
        //以同步調(diào)用場景分析下遠(yuǎn)程調(diào)用邏輯
        CallbackFuture future = new CallbackFuture();
        response = InvokerUtils.sendRequest(client, invocationContext.getRequest(), future);
        invocationContext.getTimeline().add(new TimePoint(TimePhase.Q));
        if (response == null) {
            response = future.getResponse(request.getTimeout());
        }        
        return response;
    }    

    public static InvocationResponse sendRequest(Client client, InvocationRequest request, Callback callback) {
        InvocationResponse response = response = client.write(request);
        return response;
    }

client.write(request);最終調(diào)用NettyClient或HttpInvokerClient的doWrite方法發(fā)送請求消息體瞻润。
至此我們理清了服務(wù)調(diào)用的邏輯工坊,簡單來說就是通過JDK動(dòng)態(tài)代理來生成服務(wù)方接口對(duì)應(yīng)的實(shí)例對(duì)象,在方法執(zhí)行邏輯中調(diào)用遠(yuǎn)程服務(wù)敢订。

但對(duì)于每一個(gè)服務(wù)接口,調(diào)用方是如何知道遠(yuǎn)程服務(wù)的訪問地址的呢罢吃?以及新注冊或者下線的服務(wù)地址楚午,調(diào)用方如何得到即時(shí)通知?
接下來進(jìn)入本篇第二部分尿招,遠(yuǎn)程調(diào)用Client的初始化和調(diào)用方對(duì)服務(wù)信息的心跳監(jiān)聽矾柜。
以請求責(zé)任鏈的ClusterInvokeFilter為入口:

    public InvocationResponse invoke(ServiceInvocationHandler handler, InvokerContext invocationContext)
            throws Throwable {
        InvokerConfig<?> invokerConfig = invocationContext.getInvokerConfig();
                //失敗策略cluster可配,默認(rèn)為快速失敗failfast
        Cluster cluster = ClusterFactory.selectCluster(invokerConfig.getCluster());
        if (cluster == null) {
            throw new IllegalArgumentException("Unsupported cluster type:" + cluster);
        }
        return cluster.invoke(handler, invocationContext);
    }

跟蹤代碼進(jìn)入FailfastCluster.invoke方法就谜,核心代碼如下:

    private ClientManager clientManager = ClientManager.getInstance();
    @Override
    public InvocationResponse invoke(ServiceInvocationHandler handler, InvokerContext invocationContext)
            throws Throwable {
        InvokerConfig<?> invokerConfig = invocationContext.getInvokerConfig();
        //構(gòu)造請求消息對(duì)象
        InvocationRequest request = InvokerUtils.createRemoteCallRequest(invocationContext, invokerConfig);
        //是否超時(shí)重試
        boolean timeoutRetry = invokerConfig.isTimeoutRetry();
        //重試次數(shù)
        int retry = invokerConfig.getRetries(invocationContext.getMethodName());
        //關(guān)于重試和重試次數(shù)的邏輯在此不做過多說明怪蔑,只摘取主干代碼
        //獲取遠(yuǎn)程客戶端
        Client remoteClient = clientManager.getClient(invokerConfig, request, null);
        //就是在這里設(shè)置的RemoteCallInvokeFilter中用到的客戶端Client
        invocationContext.setClient(remoteClient);   
        try {
            //向后執(zhí)行責(zé)任鏈
            return handler.handle(invocationContext);
        } catch (NetworkException e) {
            remoteClient = clientManager.getClient(invokerConfig, request, null);
            invocationContext.setClient(remoteClient);
            return handler.handle(invocationContext);
        }     
    }

ClientManager 為單例模式,我們看看內(nèi)部實(shí)現(xiàn)

        //私有構(gòu)造函數(shù) 
    private ClientManager() {
        this.providerAvailableListener = new ProviderAvailableListener();
        this.clusterListener = new DefaultClusterListener(providerAvailableListener);
        this.clusterListenerManager.addListener(this.clusterListener);
        providerAvailableThreadPool.execute(this.providerAvailableListener);
        RegistryEventListener.addListener(providerChangeListener);
        RegistryEventListener.addListener(registryConnectionListener);
        RegistryEventListener.addListener(groupChangeListener);
        registerThreadPool.getExecutor().allowCoreThreadTimeOut(true);
    }

    private RouteManager routerManager = DefaultRouteManager.INSTANCE;

    public Client getClient(InvokerConfig<?> invokerConfig, InvocationRequest request, List<Client> excludeClients) {
                //根據(jù)全局唯一標(biāo)識(shí)url獲取Client集合
        List<Client> clientList = clusterListener.getClientList(invokerConfig);
        List<Client> clientsToRoute = new ArrayList<Client>(clientList);
        if (excludeClients != null) {
            clientsToRoute.removeAll(excludeClients);
        }
                //根據(jù)負(fù)載均衡策略選取有效的Client
                //此處細(xì)節(jié)比較多丧荐,感興趣的朋友可以自行細(xì)致瀏覽下源碼缆瓣,限于篇幅不一一講解了
        return routerManager.route(clientsToRoute, invokerConfig, request);
    }

距離目標(biāo)越來越近了,我們繼續(xù)跟蹤代碼DefaultClusterListener的實(shí)現(xiàn)

    private ConcurrentHashMap<String, List<Client>> serviceClients = new ConcurrentHashMap<String, List<Client>>();

    public List<Client> getClientList(InvokerConfig<?> invokerConfig) {
        //根據(jù)url獲取對(duì)應(yīng)的Client集合
        List<Client> clientList = this.serviceClients.get(invokerConfig.getUrl());
        return clientList;
    }

問題來了虹统,serviceClients是在什么時(shí)候創(chuàng)建的Client實(shí)例呢弓坞?
我們回顧下AbstractServiceProxy.getProxy中的一段邏輯:

                    try {
                        ClientManager.getInstance().registerClients(invokerConfig);
                    } catch (Throwable t) {
                        logger.warn("error while trying to setup service client:" + invokerConfig, t);
                    }

從異常信息我們可以清晰的看到,這里就是創(chuàng)建service client的入口车荔,最終調(diào)用到DefaultClusterListener.addConnect添加Client映射關(guān)系到serviceClients渡冻。調(diào)用鏈路比較長,在此簡單貼一下線程調(diào)用棧:


image.png

至此我們理清了Client的創(chuàng)建忧便,接下來我們看看調(diào)用方的心跳監(jiān)聽族吻。
我們直接連接注冊中心zookeeper的相關(guān)類CuratorClient,用的是curator-framework-2.7.1.jar珠增,這個(gè)ZK客戶端功能很強(qiáng)大超歌,可以非常方便的對(duì)具體的zk節(jié)點(diǎn)添加listener回調(diào)。

    private boolean newCuratorClient() throws InterruptedException {
                //根據(jù)zk地址創(chuàng)建zkClient
        CuratorFramework client = CuratorFrameworkFactory.builder().connectString(address)
                .sessionTimeoutMs(sessionTimeout).connectionTimeoutMs(connectionTimeout)
                .retryPolicy(new MyRetryPolicy(retries, retryInterval)).build();
                //監(jiān)聽連接狀態(tài)蒂教,掉線重連
        client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
            @Override
            public void stateChanged(CuratorFramework client, ConnectionState newState) {
                logger.info("zookeeper state changed to " + newState);
                if (newState == ConnectionState.RECONNECTED) {
                    RegistryEventListener.connectionReconnected();
                }
                monitor.logEvent(EVENT_NAME, "zookeeper:" + newState.name().toLowerCase(), "");
            }
        });
                //監(jiān)聽change事件N沾 !悴品!
        client.getCuratorListenable().addListener(new CuratorEventListener(this), curatorEventListenerThreadPool);
        client.start();
        boolean isConnected = client.getZookeeperClient().blockUntilConnectedOrTimedOut();
        CuratorFramework oldClient = this.client;
        this.client = client;
        close(oldClient);
        return isConnected;
    }

CuratorEventListener繼承org.apache.curator.framework.api.CuratorListener禀综,看下事件處理邏輯

    @Override
    public void eventReceived(CuratorFramework client, CuratorEvent curatorEvent) throws Exception {
        WatchedEvent event = (curatorEvent == null ? null : curatorEvent.getWatchedEvent());
                //過濾不敢興趣的EventType
        if (event == null
                || (event.getType() != EventType.NodeCreated && event.getType() != EventType.NodeDataChanged
                        && event.getType() != EventType.NodeDeleted && event.getType() != EventType.NodeChildrenChanged)) {
            return;
        }
        try {
                        //解析節(jié)點(diǎn)路徑并分類
            PathInfo pathInfo = parsePath(event.getPath());
                        
            if (pathInfo.type == ADDRESS) {//服務(wù)地址  
                addressChanged(pathInfo);
            } else if (pathInfo.type == WEIGHT) {//權(quán)重
                weightChanged(pathInfo);
            } else if (pathInfo.type == APP) {
                appChanged(pathInfo);
            } else if (pathInfo.type == VERSION) {
                versionChanged(pathInfo);
            } else if (pathInfo.type == PROTOCOL) {
                protocolChanged(pathInfo);
            } else if (pathInfo.type == HOST_CONFIG) {
                registryConfigChanged(pathInfo);
            }
        } catch (Throwable e) {
            logger.error("Error in ZookeeperWatcher.process()", e);
            return;
        }
    }
    /*
     * 1. Get newest value from ZK and watch again 2. Determine if changed
     * against cache 3. notify if changed 4. pay attention to group fallback
     * notification
     */
    private void addressChanged(PathInfo pathInfo) throws Exception {
        if (shouldNotify(pathInfo)) {
            String hosts = client.get(pathInfo.path);
            logger.info("Service address changed, path " + pathInfo.path + " value " + hosts);
            List<String[]> hostDetail = Utils.getServiceIpPortList(hosts);
            serviceChangeListener.onServiceHostChange(pathInfo.serviceName, hostDetail);
        }
        // Watch again
        client.watch(pathInfo.path);
    }

addressChanged難得加了注釋简烘,判斷是否需要回調(diào),回調(diào)定枷。

本篇到此結(jié)束孤澎,內(nèi)容較多,希望能對(duì)大家有所助益欠窒。

轉(zhuǎn)載請備注原文鏈接覆旭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評(píng)論聯(lián)系作者岖妄。
  • 序言:七十年代末型将,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子荐虐,更是在濱河造成了極大的恐慌七兜,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件福扬,死亡現(xiàn)場離奇詭異腕铸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)铛碑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門狠裹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汽烦,你說我怎么就攤上這事涛菠。” “怎么了撇吞?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵碗暗,是天一觀的道長。 經(jīng)常有香客問我梢夯,道長言疗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任颂砸,我火速辦了婚禮噪奄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘人乓。我一直安慰自己勤篮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布色罚。 她就那樣靜靜地躺著碰缔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪戳护。 梳的紋絲不亂的頭發(fā)上金抡,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天瀑焦,我揣著相機(jī)與錄音,去河邊找鬼梗肝。 笑死榛瓮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的巫击。 我是一名探鬼主播禀晓,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坝锰!你這毒婦竟也來了粹懒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤顷级,失蹤者是張志新(化名)和其女友劉穎凫乖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愕把,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年森爽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恨豁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡爬迟,死狀恐怖橘蜜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情付呕,我是刑警寧澤计福,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站徽职,受9級(jí)特大地震影響象颖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姆钉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一说订、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧潮瓶,春花似錦陶冷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至思恐,卻和暖如春沾谜,著一層夾襖步出監(jiān)牢的瞬間膊毁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工类早, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留媚媒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓涩僻,卻偏偏與公主長得像缭召,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子逆日,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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

  • 工作以來在微服務(wù)實(shí)踐中接觸到了很多的RPC實(shí)現(xiàn)方式嵌巷,一直沒能做一個(gè)系統(tǒng)的分析和總結(jié)。本系列將以美團(tuán)點(diǎn)評(píng)開源的pig...
    李亞林1990閱讀 2,324評(píng)論 0 4
  • 本文是我自己在秋招復(fù)習(xí)時(shí)的讀書筆記室抽,整理的知識(shí)點(diǎn)搪哪,也是為了防止忘記,尊重勞動(dòng)成果坪圾,轉(zhuǎn)載注明出處哦晓折!如果你也喜歡,那...
    波波波先森閱讀 12,291評(píng)論 6 86
  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981閱讀 15,911評(píng)論 2 11
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,101評(píng)論 1 32
  • 敬愛的李老師兽泄,智慧的馬教授漓概,親愛的家人們:大家好!我是(劉翠平)劉總的人病梢,來自博興金締尊.周大生珠寶的馬紀(jì)香胃珍。今天...
    m香香閱讀 64評(píng)論 0 0