上一篇简单的阐述了 spring-cloud-thrift-starter
这个插件的配置和使用,并引入了一个 calculator
的项目。本文将基于一个银行存款、取款的业务场景,给出一套 thrift
在生产环境的应用案例。
首先设计如下几张简单的数据库表:银行( bank
)、分支( branch
)、银行卡( deposit_card
)、客户( customer
)、存款历史纪录( deposit_history
)、取款历史纪录( withdraw_history
)。
项目结构如下,依然是由三个模块组成:
关于 thrift
更复杂的用法可以参考 Apache Thrift
基础学习系列,根据 数据库表 的设计编写 deposit.thrift
。
deposit.thrift
定义了以下四个部分: 命名空间 ( namespace
)、 枚举类型 ( enum
)、 结构类型 ( struct
)和 服务类型 ( service
)。
(a). 命名空间 ( namespace
)
// 指定编译生成的源代码的包路径名称 namespace java com.icekredit.rpc.thrift.examples.thrift
(b). 枚举类型 ( enum
)
// 通过枚举定义银行分支所属区域 enum ThriftRegion { NORTH = 1, CENTRAL = 2, SOUTH = 3, EAST = 4, SOUTHWEST = 5, NORTHWEST = 6, NORTHEAST = 7 } // 存款完成状态 enum ThriftDepositStatus { FINISHED = 1, PROCCEDING = 2, FAILED = 3 } // 取款完成状态 enum ThriftWithdrawStatus { FINISHED = 1, PROCCEDING = 2, FAILED = 3 }
(c). 结构类型 ( struct
)
// 银行 struct ThriftBank { 1: required i64 id, 2: required string code, 3: required string name, 4: optional string description, 5: optional map<ThriftRegion, list<ThriftBranch>> branches } // 银行分支 struct ThriftBranch { 1: required i64 id, 2: required string code, 3: required string name, 4: required string address, 5: optional i32 staffs, 6: optional ThriftBank bank, 7: optional ThriftRegion region } // 客户 struct ThriftCustomer { 1: required string IDNumber, 2: required string name, 3: required string birthday, 4: required i32 sex = 0, 5: required i32 age, 6: optional list<string> address, 7: optional set<ThriftDepositCard> depositCards } // 银行卡 struct ThriftDepositCard { 1: required string id, 2: required bool isVip, 3: required string openingTime, 4: required double accountBalance, 5: optional double accountFlow, 6: optional ThriftBranch branch, 7: optional ThriftCustomer customer, 8: optional list<ThriftDeposit> depositHistory, 9: optional list<ThriftWithdraw> WithdrawHistory } // 存款历史纪录 struct ThriftDeposit { 1: required string serialNumber, 2: required double transactionAmount, 3: required string submittedTime, 4: optional string finishedTime, 5: optional ThriftDepositStatus status, 6: optional ThriftDepositCard depositCard } // 取款历史纪录 struct ThriftWithdraw { 1: required string serialNumber, 2: required double transactionAmount, 3: required string submittedTime, 4: optional string finishedTime, 5: optional ThriftWithdrawStatus status, 6: optional ThriftDepositCard depositCard }
(d). 服务类型 ( service
)
// 银行 - 业务服务定义 service ThriftBankService { void registerNewBank(ThriftBank bank); list<ThriftBank> queryAllBanks(); ThriftBank getBankById(i64 bankId); map<ThriftRegion, list<ThriftBranch>> queryAllBranchesByRegion(i64 bankId); } // 银行分支 - 业务服务定义 service ThriftBranchService { void addNewBranch(i64 bankId, ThriftBranch branch); list<ThriftBranch> queryAllBranches(i64 bankId); ThriftBranch getBranchById(i64 branchId); } // 客户 - 业务服务定义 service ThriftCustomerService { ThriftCustomer getCustomerById(string customerId); list<ThriftCustomer> queryAllCustomers(); void addNewUser(ThriftCustomer customer); void modifyUserById(string customerId, ThriftCustomer customer); i32 getTotalDepositCard(string customerId); } // 银行卡 - 业务服务定义 service ThriftDepositCardService { set<ThriftDepositCard> queryAllDepositCards(string customerId); void addNewDepositCard(string customerId, ThriftDepositCard depositCard); ThriftDepositStatus depositMoney(string depositCardId, double money); ThriftWithdrawStatus withdrawMoney(string depositCardId, double money); list<ThriftDeposit> queryDepositHistorys(string depositCardId); list<ThriftWithdraw> queryWithdrawHistorys(string depositCardId); }
进入 src/main/thrift
目录,编译生成所需的 枚举类 、 结构类 和 业务服务类 的源文件。
thrift -gen java ./deposit.thrift
所有生成的源文件都位于同一个**命名空间(包)**下面: com.icekredit.rpc.thrift.examples.thrift
将 上述源文件 拷贝到 deposit-iface
模块中。
通过 Mybatis
逆向工程插件生成 SQLMapper
的 XML
和 接口 文件以及 实体类 。
友情提示: Mybatis
逆向工程生成的 实体类 ( entity
),需要和 Thrift
编译生成器生成的 结构类 ( struct
) 区分开来。而 Thrift
生成器生成的所有源文件,都一定程度封装了底层的 通信方式 和 相关协议 ,开发人员是不应该动手脚的。
为了在 Thrift
中通过 Mybatis
完成 数据持久化 ,必须在 实体类 ( entity
)包装一层与 结构类 ( struct
)相互转换的方法。 在每个实体类中,根据业务添加以下两个方法,以 DepositCard
为例:
public ThriftDepositCard toThrift() { ThriftDepositCard thriftDepositCard = new ThriftDepositCard(); thriftDepositCard.setId(this.getId()); thriftDepositCard.setAccountBalance(this.getAccountBalance()); thriftDepositCard.setAccountFlow(this.getAccountFlow()); thriftDepositCard.setIsVip(this.getIsVip()); thriftDepositCard.setOpeningTime(this.getOpeningTime()); ThriftBranch thriftBranch = new ThriftBranch(); thriftBranch.setId(this.getBranchId()); thriftDepositCard.setBranch(thriftBranch); ThriftCustomer thriftCustomer = new ThriftCustomer(); thriftCustomer.setIDNumber(this.getCustomerId()); thriftDepositCard.setCustomer(thriftCustomer); return thriftDepositCard; }
public static DepositCard fromThrift(ThriftDepositCard thriftDepositCard) { DepositCard depositCard = new DepositCard(); depositCard.setId(thriftDepositCard.getId()); depositCard.setAccountBalance(thriftDepositCard.getAccountBalance()); depositCard.setAccountFlow(thriftDepositCard.getAccountFlow()); depositCard.setIsVip(thriftDepositCard.isIsVip()); ThriftCustomer thriftCustomer = thriftDepositCard.getCustomer(); if (thriftCustomer != null) { String customerIDNumber = thriftCustomer.getIDNumber(); depositCard.setCustomerId(customerIDNumber); } ThriftBranch thriftBranch = thriftDepositCard.getBranch(); if (thriftBranch != null) { Long branchId = thriftBranch.getId(); depositCard.setBranchId(branchId); } depositCard.setOpeningTime(thriftDepositCard.getOpeningTime()); return depositCard; }
在服务端模块引入:
thrift
服务端的 starter
程序。 Skeleton
)程序。 pom.xml
<parent> <groupId>com.icekredit.rpc.thrift.examples</groupId> <artifactId>deposit</artifactId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>deposit-server</artifactId> <packaging>jar</packaging> <dependencies> <!-- Thrift相关依赖 --> <dependency> <groupId>com.icekredit.rpc.thrift</groupId> <artifactId>spring-cloud-starter-thrift-server</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.icekredit.rpc.thrift.examples</groupId> <artifactId>deposit-iface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- SpringBoot依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- 数据库相关依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.5</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <!-- Swagger依赖 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
在 application.yml
中配置 thrift
服务端的 运行参数 、 数据源连接池参数 和 Mybatis
相关属性:
application.yml
server: port: 8080 endpoints: actuator: sensitive: false enabled: true management: security: enabled: false spring: datasource: druid: url: jdbc:mysql://localhost:3306/deposit?useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.jdbc.Driver username: root password: root thrift: server: service-id: deposit-server-rpc service-model: hsHa port: 25000 worker-queue-capacity: 1000 hs-ha: min-worker-threads: 5 max-worker-threads: 20 keep-alived-time: 3 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.icekredit.rpc.thrift.examples.http.entities logging: level: root: INFO com: icekredit: rpc: thrift: examples: mapper: DEBUG
服务端程序启动入口类,设置 Swagger API
所在的包路径名称。
Application.java
@SpringBootApplication @EnableSwagger2 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public Docket createRestfulApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.icekredit.rpc.thrift.examples.service.http.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Deposit Server") .description("Deposit Server") .version("1.0") .build(); } }
编写服务端的 Thrift
的实现,以 ThriftDepositCardService
为例,由实现类 ThriftDepositCardServiceImpl
实现 ThriftDepositCardService.Iface
接口的方法:
ThriftDepositCardServiceImpl.java
@ThriftService(name = "thriftDepositCardService") public class ThriftDepositCardServiceImpl implements ThriftDepositCardService.Iface { private final BranchMapper branchMapper; private final DepositCardMapper depositCardMapper; private final CustomerMapper customerMapper; private final DepositHistoryMapper depositHistoryMapper; private final WithdrawHistoryMapper withdrawHistoryMapper; @Autowired public ThriftDepositCardServiceImpl(BranchMapper branchMapper, DepositCardMapper depositCardMapper, CustomerMapper customerMapper, DepositHistoryMapper depositHistoryMapper, WithdrawHistoryMapper withdrawHistoryMapper) { this.branchMapper = branchMapper; this.depositCardMapper = depositCardMapper; this.customerMapper = customerMapper; this.depositHistoryMapper = depositHistoryMapper; this.withdrawHistoryMapper = withdrawHistoryMapper; } @Override public Set<ThriftDepositCard> queryAllDepositCards(String customerId) throws TException { List<DepositCard> depositCardList = depositCardMapper.queryAllDepositCards(customerId); // 查询客户持有的银行卡 return depositCardList.stream().map(depositCard -> { ThriftDepositCard thriftDepositCard = depositCard.toThrift(); Long branchId = depositCard.getBranchId(); if (Objects.nonNull(branchId) && branchId > 0L) { Branch branch = branchMapper.findById(branchId); ThriftBranch thriftBranch = branch.toThrift(); ThriftBank thriftBank = new ThriftBank(); thriftBank.setId(branch.getBankId()); thriftBranch.setBank(thriftBank); thriftDepositCard.setBranch(thriftBranch); } Customer customer = customerMapper.findById(customerId); ThriftCustomer thriftCustomer = customer.toThrift(); thriftDepositCard.setCustomer(thriftCustomer); return thriftDepositCard; }).collect(Collectors.toSet()); } @Override @Transactional public void addNewDepositCard(String customerId, ThriftDepositCard depositCard) throws TException { DepositCard newDepositCard = DepositCard.fromThrift(depositCard); // 新增银行卡信息 depositCardMapper.save(newDepositCard); } @Override @Transactional public ThriftDepositStatus depositMoney(String depositCardId, double money) throws TException { SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); try { DepositHistory depositHistory = new DepositHistory(); depositHistory.setSubmittedTime(sf.format(new Date())); depositCardMapper.incrementMoney(depositCardId, money); depositHistory.setFinishedTime(sf.format(new Date())); depositHistory.setSerialNumber(UUID.randomUUID().toString().replace("-", "")); depositHistory.setTransactionAmount(money); depositHistory.setDepositCardId(depositCardId); depositHistory.setStatus(1); // 新增存款历史记录 depositHistoryMapper.save(depositHistory); return ThriftDepositStatus.FINISHED; } catch (Exception e) { e.printStackTrace(); return ThriftDepositStatus.FAILED; } } @Override @Transactional public ThriftWithdrawStatus withdrawMoney(String depositCardId, double money) throws TException { SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); try { WithdrawHistory withdrawHistory = new WithdrawHistory(); withdrawHistory.setSubmittedTime(sf.format(new Date())); depositCardMapper.decrementMoney(depositCardId, money); withdrawHistory.setFinishedTime(sf.format(new Date())); withdrawHistory.setSerialNumber(UUID.randomUUID().toString().replace("-", "")); withdrawHistory.setTransactionAmount(money); withdrawHistory.setDepositCardId(depositCardId); withdrawHistory.setStatus(1); // 新增取款历史记录 withdrawHistoryMapper.save(withdrawHistory); return ThriftWithdrawStatus.FINISHED; } catch (Exception e) { e.printStackTrace(); return ThriftWithdrawStatus.FAILED; } } @Override public List<ThriftDeposit> queryDepositHistorys(String depositCardId) throws TException { List<DepositHistory> depositHistory = depositHistoryMapper.queryDepositHistoryList(depositCardId); // 查询存款历史纪录 return depositHistory.stream().map(DepositHistory::toThrift).collect(Collectors.toList()); } @Override public List<ThriftWithdraw> queryWithdrawHistorys(String depositCardId) throws TException { List<WithdrawHistory> withdrawHistory = withdrawHistoryMapper.queryWithdrawHistoryList(depositCardId); // 查询取款历史纪录 return withdrawHistory.stream().map(WithdrawHistory::toThrift).collect(Collectors.toList()); } }
Mybatis
持久层,还是以 DepositCardMapper
为例:
DepositCardMapper.java
@Repository @Mapper public interface DepositCardMapper { int save(DepositCard record); List<DepositCard> queryAllDepositCards(@Param("customerId") String customerId); void decrementMoney(@Param("depositCardId") String depositCardId, @Param("money") Double money); void incrementMoney(@Param("depositCardId") String depositCardId, @Param("money") Double money); Long countRowsByCustomerId(@Param("customerId") String customerId); }
DepositCardMapper.xml
<insert id="save" parameterType="com.icekredit.rpc.thrift.examples.http.entities.DepositCard"> INSERT INTO deposit_card (id, is_vip, opening_time, account_balance, account_flow, branch_id, customer_id) VALUES (#{id,jdbcType=VARCHAR}, #{isVip,jdbcType=BIT}, #{openingTime,jdbcType=VARCHAR}, #{accountBalance,jdbcType=DOUBLE}, #{accountFlow,jdbcType=DOUBLE}, #{branchId,jdbcType=BIGINT}, #{customerId,jdbcType=VARCHAR}) </insert> <select id="queryAllDepositCards" resultMap="BaseResultMap" parameterType="java.lang.String"> SELECT <include refid="Base_Column_List"/> FROM deposit_card WHERE customer_id = #{customerId} </select> <select id="countRowsByCustomerId" resultType="java.lang.Long" parameterType="java.lang.String"> SELECT COUNT(id) FROM deposit_card WHERE customer_id = #{customerId} </select> <update id="decrementMoney"> UPDATE deposit_card <set> <if test="money != null"> account_balance = account_balance - #{money}, </if> </set> WHERE id = #{depositCardId} </update> <update id="incrementMoney"> UPDATE deposit_card <set> <if test="money != null"> account_balance = account_balance + #{money}, </if> </set> WHERE id = #{depositCardId} </update>
同样,在客户端模块引入:
thrift
客户端的 starter
程序。 Stub
)程序。 pom.xml
<parent> <groupId>com.icekredit.rpc.thrift.examples</groupId> <artifactId>deposit</artifactId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>deposit-client</artifactId> <dependencies> <!-- Thrift相关依赖 --> <dependency> <groupId>com.icekredit.rpc.thrift</groupId> <artifactId>spring-cloud-starter-thrift-client</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.icekredit.rpc.thrift.examples</groupId> <artifactId>deposit-iface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- SpringBoot依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Cloud Consul服务注册与发现 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <!-- Spring Cloud声明式Restful客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> <!-- Swagger依赖 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
在 application.yml
中配置 thrift
的客户端 的的运行参数和 Consul
的服务注册与发现 的参数:
application.yml
server: port: 8080 endpoints: actuator: sensitive: false enabled: true management: security: enabled: false spring: cloud: consul: host: 192.168.91.128 port: 8500 discovery: register: false register-health-check: true health-check-interval: 30s retry: max-attempts: 3 max-interval: 2000 thrift: client: package-to-scan: com.icekredit.rpc.thrift.examples.thrift.client service-model: hsHa pool: retry-times: 3 pool-max-total-per-key: 200 pool-min-idle-per-key: 10 pool-max-idle-per-key: 40 pool-max-wait: 10000 connect-timeout: 5000
客户端程序启动入口类,设置 Swagger API
所在的 包路径名称 ,同时允许自身作为 注册程序 注册到 注册中心 。
@SpringBootApplication @EnableFeignClients @EnableDiscoveryClient @EnableSwagger2 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public Docket createRestfulApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.icekredit.rpc.thrift.examples")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Deposit Client") .description("Deposit Client") .version("1.0") .build(); } }
在 客户端 使用 @ThriftClient
注解标识 服务端 的 thrift
服务代理接口 , 代理服务 ID
为 deposit-server-rpc
, 代理的目标类 是 ThriftDepositCardService
。
DepositCardThriftClient.java
@ThriftClient(serviceId = "deposit-server-rpc", refer = ThriftDepositCardService.class) public interface DepositCardThriftClient extends ThriftClientAware<ThriftDepositCardService.Client> { }
BankThriftClient.java
@ThriftClient(serviceId = "deposit-server-rpc", refer = ThriftBankService.class) public interface BankThriftClient extends ThriftClientAware<ThriftBankService.Client> { }
在客户端控制器中通过 ThriftReferer
注入需要使用的 服务代理接口 ,通过 thriftClient.client()
即可获取 Thrift
客户端 桩对象,然后实现远程服务的调用。
DepositCardRpcController.java
@RestController @RequestMapping("/rpc/deposit") public class DepositCardRpcController { @ThriftReferer private DepositCardThriftClient thriftClient; @GetMapping("/queryAllDepositCards") public List<DepositCard> queryAllDepositCards(@RequestParam("customerId") String customerId) throws Exception { return thriftClient.client().queryAllDepositCards(customerId) .stream().map(DepositCard::fromThrift) .collect(Collectors.toList()); } @PostMapping("/addNewDepositCard") public void addNewDepositCard(DepositCard depositCard) throws Exception { thriftClient.client().addNewDepositCard(depositCard.getCustomerId(), depositCard.toThrift()); } @GetMapping("/depositMoney") public ThriftDepositStatus depositMoney(@RequestParam("depositCardId") String depositCardId, @RequestParam("money") double money) throws Exception { return thriftClient.client().depositMoney(depositCardId, money); } @GetMapping("/withdrawMoney") public ThriftWithdrawStatus withdrawMoney(@RequestParam("depositCardId") String depositCardId, @RequestParam("money") double money) throws Exception { return thriftClient.client().withdrawMoney(depositCardId, money); } @GetMapping("/queryDepositHistory") public List<DepositHistory> queryDepositHistory(@RequestParam("depositCardId") String depositCardId) throws Exception { return thriftClient.client().queryDepositHistorys(depositCardId) .stream().map(DepositHistory::fromThrift) .collect(Collectors.toList()); } @GetMapping("/queryWithdrawHistory") public List<WithdrawHistory> queryWithdrawHistory(@RequestParam("depositCardId") String depositCardId) throws Exception { return thriftClient.client().queryWithdrawHistorys(depositCardId) .stream().map(WithdrawHistory::fromThrift) .collect(Collectors.toList()); } }
BankRpcController.java
@RestController @RequestMapping("/rpc/bank") public class BankRpcController { @ThriftReferer private BankThriftClient thriftClient; @PostMapping("/addNewBank") public void addNewBank(Bank bank) throws Exception { thriftClient.client().registerNewBank(bank.toThrift()); } @GetMapping("/getBankById") public Bank getBankById(@RequestParam("bankId") Long bankId) throws Exception { return Bank.fromThrift(thriftClient.client().getBankById(bankId)); } @GetMapping("/queryAllBranchesByRegion") public Map<Region, List<Branch>> queryAllBranchesByRegion(@RequestParam("bankId") Long bankId) throws Exception { Map<ThriftRegion, List<ThriftBranch>> thriftRegionListMap = thriftClient.client() .queryAllBranchesByRegion(bankId); Map<Region, List<Branch>> regionListMap = new HashMap<>(); for (Map.Entry<ThriftRegion, List<ThriftBranch>> entry : thriftRegionListMap.entrySet()) { ThriftRegion thriftRegion = entry.getKey(); Region region = Region.findByValue(thriftRegion.getValue()); List<ThriftBranch> thriftBranches = entry.getValue(); List<Branch> branchList = thriftBranches.stream().map(Branch::fromThrift).collect(Collectors.toList()); regionListMap.put(region, branchList); } return regionListMap; } }
因为 服务代理客户端接口 使用 @ThriftClient
标识,通过(服务ID + 客户端桩 + 版本号)唯一标识, 即使同时注入多个 服务代理客户端接口 , @ThriftReferer
也可忽略 注解属性 的配置。