Dubbo探索(四)

1、結果緩存git

結果緩存,用於加速熱門數據的訪問速度,Dubbo提供聲明式緩存,以減小用戶加緩存的工做量。github

lru 基於最近最少使用原則刪除多餘緩存,保持最熱的數據被緩存,實現以下:redis

    <dubbo:reference id="userService" group="*" interface="com.patty.dubbo.api.service.UserService"
                     timeout="10000" retries="3" mock="true" check="false">
        <dubbo:method name="findAllUsers" merger="myMerger" cache="lru">
        </dubbo:method>
    </dubbo:reference>

cache="lru"表示採用lru緩存策略,運行後,可在與數據庫交互的代碼塊上打個斷點,會發現,首次請求時會穿透數據庫,再次請求,則直接走dubbo緩存拿數據了。固然,實際應用中,最後仍是繼承redis到dubbo,實現緩存策略。spring

集成Redis(服務提供方):數據庫

1) pom.xml中加入redis依賴api

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

2)配置jedis緩存

/**
     * Jedis數據源配置
     *
     * @return JedisPoolConfig
     */
    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWait);
        return jedisPoolConfig;
    }

    /**
     * Jedis數據鏈接工場
     *
     * @return JedisConnectionFactory
     */
    @Bean
    public JedisConnectionFactory redisConnectionFactory(JedisPoolConfig poolConfig) {
        JedisConnectionFactory factory = new JedisConnectionFactory();
        factory.setHostName(host);
        factory.setPort(port);
        factory.setTimeout(timeout);
        if (StringUtils.isNotEmpty(password)) {
            factory.setPassword(password);
        }

        factory.setDatabase(database);
        factory.setPoolConfig(poolConfig);
        return factory;
    }

3)實現redis操做邏輯多線程

@Component
public class RedisBaseService {
    private Logger LOGGER = LoggerFactory.getLogger(RedisBaseService.class);

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 獲取Set集合數據
     * param key
     * return Set<String>
     */
    public Set<String> getSets(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 移除Set集合中的value
     * param k
     * param v
     */
    public Long removeSetValue(String key, String value) {
        if (key == null && value == null) {
            return 0L;
        }
        return redisTemplate.opsForSet().remove(key, value);
    }

    /**
     * 保存到Set集合中
     * param k
     * param v
     */
    public Long setSet(String k, String v) {
        if (k == null && v == null) {
            return 0L;
        }
        return redisTemplate.opsForSet().add(k, v);
    }

    /**
     * 存儲Map格式
     * param key
     * param hashKey
     * param hashValue
     */
    public void setMap(String key, String hashKey, Object hashValue) {
        redisTemplate.opsForHash().put(key, hashKey, hashValue);
    }

    /**
     * 根據key獲取map對象
     * param key
     */
    public Map<Object, Object> getMap(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 存儲帶有過時時間的key-value
     * param key
     * param value
     * param timeOut 過時時間
     * param unit 時間單位
     */
    public void setTime(String key, String value, Long timeOut, TimeUnit unit) {
        if (value == null) {
            LOGGER.info("redis存儲的value的值爲空");
            throw new IllegalArgumentException("redis存儲的value的值爲空");
        }
        if (timeOut > 0) {
            redisTemplate.opsForValue().set(key, value, timeOut, unit);
        } else {
            redisTemplate.opsForValue().set(key, value);
        }
    }

    /**
     * 存儲key-value
     * param key
     * return Object
     */
    public void set(String key, String value) {
        if (value == null) {
            LOGGER.info("redis存儲的value的值爲空");
            throw new IllegalArgumentException("redis存儲的value的值爲空");
        }
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 根據key獲取value
     * param key
     * return Object
     */
    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 判斷key是否存在
     * param key
     * return Boolean
     */
    public Boolean exists(String key) {
        return redisTemplate.hasKey(key);
    }


    /**
     * 刪除key對應的value
     * param key
     */
    public void removeValue(String key) {
        if (exists(key)) redisTemplate.delete(key);
    }

    /**
     * 模式匹配批量刪除key
     * param keyPattern
     */
    public void removePattern(String keyPattern) {
        Set<String> keys = redisTemplate.keys(keyPattern);
        if (keys.size() > 0) redisTemplate.delete(keys);
    }
}

4) 在業務邏輯中,加入緩存設置app

/**
     * 根據id查詢指定用戶
     *
     * @param id
     * @return
     */
    public UserVo findUserById(String id) {
        if (redisBaseService.exists(id)) {
            Map<Object, Object> userMap = redisBaseService.getMap(id);
            return new UserVo(
                    id,
                    (String) userMap.get("name"),
                    Integer.valueOf(userMap.get("age") + ""),
                    (String) userMap.get("phoneNo"));
        } else {
            User user = userDao.findUserById(id);
            redisBaseService.setMap(id, "name", user.getName());
            redisBaseService.setMap(id, "age", user.getAge() + "");
            redisBaseService.setMap(id, "phoneNo", user.getPhoneNo());
            return this.UserToUserVo(user);
        }
    }

5)具體代碼參見:https://github.com/pattywgm/dubbo-demo.git異步

 

2、上下文信息

上下文中存放的是當前調用過程當中所需的環境信息。RpcContext是一個ThreadLocal的臨時狀態記錄器,當接收到RPC請求,或發起RPC請求時,RpcContext的狀態都會變化。好比:A調B,B再調C,則B機器上,在B調C以前,RpcContext記錄的是A調B的信息,在B調C以後,RpcContext記錄的是B調C的信息。

服務提供方:

public class XxxServiceImpl implements XxxService {
 
    public void xxx() { // 服務方法實現
        boolean isProviderSide = RpcContext.getContext().isProviderSide(); // 本端是否爲提供端,這裏會返回true
        String clientIP = RpcContext.getContext().getRemoteHost(); // 獲取調用方IP地址
        String application = RpcContext.getContext().getUrl().getParameter("application"); // 獲取當前服務配置信息,全部配置信息都將轉換爲URL的參數
        // ...
        yyyService.yyy(); // 注意:每發起RPC調用,上下文狀態會變化
        boolean isProviderSide = RpcContext.getContext().isProviderSide(); // 此時本端變成消費端,這裏會返回false
        // ...
    }
 
}

服務消費方:

xxxService.xxx(); // 遠程調用
boolean isConsumerSide = RpcContext.getContext().isConsumerSide(); // 本端是否爲消費端,這裏會返回true
String serverIP = RpcContext.getContext().getRemoteHost(); // 獲取最後一次調用的提供方IP地址
String application = RpcContext.getContext().getUrl().getParameter("application"); // 獲取當前服務配置信息,全部配置信息都將轉換爲URL的參數
// ...
yyyService.yyy(); // 注意:每發起RPC調用,上下文狀態會變化
// ...

 

3、異步調用

基於NIO的非阻塞實現並行調用,客戶端不須要啓動多線程便可完成並行調用多個遠程服務,相對多線程開銷較小。

如今咱們在api包中,聲明兩個接口:ShoppingService和EatingService,並在服務方分別實現這兩個接口,以下:

public interface EatingService {
    public String eating();
}

@Service("eatingService")
public class EatingServiceImpl implements EatingService {
    public EatingServiceImpl() {

    }

    @Override
    public String eating() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Eating for 5 seconds";
    }
}
public interface ShoppingService {
    public String shopping();
}


@Service("shoppingService")
public class ShoppingServiceImpl implements ShoppingService {
    public ShoppingServiceImpl(){

    }

    @Override
    public String shopping() {
        try {

            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Shopping for 6 seconds";
    }
}

其中eatingService要運行大約5S, shoppingService運行大約6S實際。

在dubbo-provider.xml中配置這兩個服務,進行註冊。

 <dubbo:service ref="shoppingService" interface="com.patty.dubbo.api.service.ShoppingService"/>

 <dubbo:service ref="eatingService" interface="com.patty.dubbo.api.service.EatingService"/>

在消費方,訂閱這兩個服務:

1)不採用異步調用的方式

    <dubbo:reference id="shoppingService" interface="com.patty.dubbo.api.service.ShoppingService" timeout="200000" async="false"></dubbo:reference>
    <dubbo:reference id="eatingService" interface="com.patty.dubbo.api.service.EatingService" timeout="200000" async="false"></dubbo:reference>
    @RequestMapping(value = "/doSomething", method = RequestMethod.GET)
    @ResponseBody
    public String doSomething() {
        return "Doing: " + shoppingService.shopping(); + " " + eatingService.eating();;
    }

啓動程序,因爲未採用異步方式,整個rpc請求的返回時間大約11s左右,如圖:

 

2)採用異步調用方式

    <!-- NIO異步 -->
    <dubbo:reference id="shoppingService" interface="com.patty.dubbo.api.service.ShoppingService" timeout="200000" async="true"></dubbo:reference>
    <dubbo:reference id="eatingService" interface="com.patty.dubbo.api.service.EatingService" timeout="200000" async="true"></dubbo:reference>
    @RequestMapping(value = "/doSomething", method = RequestMethod.GET)
    @ResponseBody
    public String doSomething() {
        shoppingService.shopping();
        Future<String> shoppingFuture = RpcContext.getContext().getFuture(); // 拿到調用的Future引用,當結果返回後,會被通知和設置到此Future。
        eatingService.eating();
        Future<String> eatingFuture = RpcContext.getContext().getFuture();
        try {
            String doSth1 = shoppingFuture.get();
            String doSth2 = eatingFuture.get();
            return "Doing: " + doSth1 + " " + doSth2;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return "Doing: ";
    }

將async屬性值設爲"true", 表示採用異步調用,同時再客戶端doSomething()代碼塊中,利用Future來承載異步返回的結果,因爲是異步調用,整個調用返回的時間取決於運行時間較長的那個,本例中大概6s左右。如圖:

相關文章
相關標籤/搜索