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左右。如圖: