前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >dubbo富客户端

dubbo富客户端

作者头像
叔牙
发布2020-11-19 15:00:03
2.6K0
发布2020-11-19 15:00:03
举报
文章被收录于专栏:一个执拗的后端搬砖工

富客户端(Fat Client),是一个与瘦客户端(Thin Client)对立的概念。常见的C/S架构就是富客户端,B/S架构是典型的瘦客户端。

当然,“瘦”与“富”是相对而言,各有各自的优缺点。

富客户端

优点:

1)有一部分功能在C端可以完成,一定程度上减少了网络交互次数和开销

2)有独立的端,可以独立运行,不依赖其他平台

3)客户端体验好,可以完成复杂的功能

缺点:

1)客户端占用用户资源,有时对用户硬件或者系统要求高

2)如果服务端有更新或者升级,一般情况下C端也要跟着更新,并且更新成本高

瘦客户端

优点:

1)不需要用户单独下载应用C端,对用户硬件和操作系统基本无要求

2)服务端升级,B端完全无感知

缺点:

1)一般B端不适用于完成较复杂的功能

2)浏览器层面的一些全局设置会影响到功能使用

上边简单分析了常见的两种应用架构方式各自的优缺点,接下来进入主题,dubbo实现富客户端。这个概念是不太准确的,现在分布式架构是互联网的主流,所以叫做分布式富客户端更为合适。

那么为什么要在分布式服务中引入富客户端的概念?我们先看一张图:

如上图描述,Web层远程调用Rpc远程服务为B端(浏览器)提供服务,这样做是没有问题的,也符合当前流行的分布式架构方式;但是高并发是所有互联网企业都要面临的问题,当然缓存是解决高并发的神器,那么如果上述架构再做一下改造如下图:

从改造后的图中可以看到,rpc服务暴露给调用放的client很薄,只有简单的接口定义,具体的实现还在provider层,也就是具体的服务实现和提供者。这样上层Web层只需要引用暴露出的client利用Rpc框架就可以调用到具体的服务,在并发查询场景下,虽然引入了缓存,但是不知道有没有人注意到,缓存是和rpc服务提供者不在一个主机甚至可能不在一个机房,Web层和Rpc服务层也可能不在一个机房,也就是说从Web层调用Rpc服务,Rpc操作缓存都有网络开销, 举个例子,如果Web层查询的数据在缓存中已经存在,那么回去直接从缓存中获取,但是调用链路还是Web调用Rpc服务,Rpc服务再从缓存获取数据返回,会有两次网络开销,我们开发者都会有一个概念,调用链路边长会增加失败的概率,在网络环境不好的情况下,一次网络开销和两次网络开销相比,体验优劣是很明显的。所以这种架构方式在流量大的项目中是不太可取的。

根据上述描述,我们对方案再做改进:

图中加了一个步骤,Web层在调用Rpc服务之前,先去缓存拿数据,如果命中直接返回,否则继续调用Rpc服务。也就是说在缓存命中的场景下,减少了一次网络开销。增加的节点,其实是Rpc服务的client提供的,对缓存和rpc服务的一个封装,然后暴露给调用者,也即是我们所说的富客户端的概念。

描述了很多概念和理论性的东西,下边我们使用Dubb框架来实现富客户端。

I 服务端编写

服务端结构如下图(基于上一篇的代码):

具体实现参考徒手搭建dubbo服务

在dubbo-server-interface中添加两个关键类:

CacheManager

/**

* 缓存操作类

*/

public class CacheManager implements TCache,InitializingBean {

private JedisPool jedisPool;

public void setJedisPool(JedisPool jedisPool) {

this.jedisPool = jedisPool;

}

@Override

public <T> T get(Serializable key, Class<T> type) {

Jedis jedis = null;

try {

jedis = jedisPool.getResource();

byte[] data = jedis.get(key.toString().getBytes());

return SerializationUtil.deserialize(data,type);

} catch (Exception e) {

e.printStackTrace();

} finally {

if(null != jedis) {

jedis.close();

}

}

return null;

}

@Override

public void put(Serializable key, Object val) {

Jedis jedis = null;

try {

jedis = jedisPool.getResource();

byte[] data = SerializationUtil.serialize(val);

jedis.set(key.toString().getBytes(),data);

} catch (Exception e) {

e.printStackTrace();

} finally {

if(null != jedis) {

jedis.close();

}

}

}

@Override

public void afterPropertiesSet() throws Exception {

if(null == jedisPool) {

throw new RuntimeException("jedis没有初始化");

}

}

}

UserReadClient

/**

* 用户信息操作客户端

*

* @author Typhoon

* @date 2018-05-20 16:04 Sunday

* @since V2.0.0

*/

public class UserReadClient implements InitializingBean {

private static final String KEY_PREFIX = "userCachePrefix:";

private CacheManager cacheManager;

private UserService userService;

public void setCacheManager(CacheManager cacheManager) {

this.cacheManager = cacheManager;

}

public void setUserService(UserService userService) {

this.userService = userService;

}

/**

* 查询用户信息

*

* @param id

* @return

*/

public UserDto queryByPK(Long id) {

if(null == id || id <= 0) {

throw new IllegalArgumentException("参数非法");

}

UserDto userDto = this.cacheManager.get(KEY_PREFIX + id,UserDto.class);

if(null != userDto) {//走缓存

return userDto;

}

//走DB

userDto = this.userService.queryByPK(id);

try {

this.cacheManager.put(KEY_PREFIX + id,userDto);

} catch (Exception e) {

e.printStackTrace();

}

return userDto;

}

@Override

public void afterPropertiesSet() throws Exception {

if(null == cacheManager || null == userService) {

throw new IllegalArgumentException("tCache 或者userService没有初始化 ");

}

}

}

然后把dubbo-server-interface安装到本地maven仓库供其他消费端依赖,并启动服务.

II 消费端编写与测试

消费端redis的配置此处不做赘述,有兴趣可参见源码。

https://gitee.com/ScorpioAeolus/dubbo-consumer

在dubbo-consumer.xml中添加远程服务引用:

<dubbo:reference interface="com.typhoon.service.UserService" url="dubbo://localhost:20289" id="userService" protocol="dubbo" timeout="30000"/>

在主配置文件或者其他配置中增加:

<bean id = "cacheManager" class="com.typhoon.cache.CacheManager">

<property name="jedisPool" ref="jedisPool" />

</bean>

<bean id = "userReadClient" class="com.typhoon.client.UserReadClient">

<property name="cacheManager" ref="cacheManager" />

<property name="userService" ref="userService" />

</bean>

然后再在本地服务中注入UserReadClient:

@Autowired

private UserReadClient userReadClient;

@Override

public ResponseBase doQueryUserById(Long id) {

ResponseBase resp = new ResponseBase();

UserDto userDto = this.userReadClient.queryByPK(id);

resp.setAttach(userDto);

return resp;

}

编写单元测试:

@Test

public void testA() {

try {

ResponseBase resp = this.imitateConsumerService.doQueryUserById(1L);

System.out.println(JSON.toJSONString(resp));

} catch (Exception e) {

e.printStackTrace();

}

}

运行单元测试并debug:

1)第一次运行

可以看到查询没有命中缓存,走DB查询,调用了远程Rpc服务

2)第二次运行

debug代码看到查询命中了缓存不走DB查询,直接返回

总结

经过上述的描述和代码验证,我们使用Dubbo富客户端,其原理就是Rpc服务编写并暴露出一个复合客户端,里边包含了查询缓存和掉服务查DB的逻辑,

但是缓存的具体配置交给调用方,这样服务调用方使用富客户端的时候会优先走自己的缓存逻辑,如果缓存不命中就继续调用Rpc服务查询,这样就解决了

并发场景虽然后走了缓存但是依旧多走一次网络开销的问题。

此篇我们根据真实业务场景讲解了dubbo富客户端实现和引用,希望给大家在日常开发中带来帮助!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档