接下来我们创建一个消费者服务。消费者服务从生产者服务拿取商品-价格信息,并保存在Redis中。同时,接收消息队列中生产者服务的更新提示,如果某个商品-价格被修改,则删除Redis中的缓存数据,并重新从生产者服务中取。

配置pom.xml

首先,在pom.xml中添加spring-cloud-stream,spring-cloud-starter-stream-kafka,spring-data-redis及Redis的客户端jedis依赖。

<!-- Spring cloud: stream -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<!-- Spring cloud starter: kafka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>
<!--Spring Data: Redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<!--Redis Client -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

配置通道(channel),绑定器(binder)接收器(sink)

public interface ProductPriceSource {
@Input("productPriceInput")
SubscribableChannel productPriceInput();
}

[注] 这里创建了一个叫“productPriceInput”的自定义接收通道,如果不使用自定义,可以直接使用org.springframework.cloud.stream.messaging.Sink接口及叫input的接收通道(下面的yml文件会讲如何配置)。

@Component
public class ProductPriceMessageReceiver { private static final Logger logger = LoggerFactory.getLogger(ProductPriceMessageReceiver.class); @Autowired
private ProductPriceRedisRepository productPriceRedisRepository; @StreamListener("productPriceInput")
public void receiveMessage(Long productId) { logger.info(String.format(">>>>> Received Kafka message productId=%s.", productId.toString()));
if (productPriceRedisRepository.contains(productId)) {
productPriceRedisRepository.delete(productId);
logger.info(
String.format(">>>>> Delete ProductPrice data productId=%s from cache.", productId.toString()));
} else {
logger.info(
String.format(">>>>> No ProductPrice data productId=%s in cache. Skip.", productId.toString()));
}
}
}

[注] 这里配置了一个接收器bean,当消息队列有消息时,接受该消息并清除相应product的缓存。

@SpringBootApplication
@EnableBinding({ ProductPriceSource.class })
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}

[注] Application中加入@EnableBinding注解,并把定义好的发射通道(output)或接收通道(input)绑定到该服务中。可以绑定多个。

配置RedisRepository

这里我们额外用到了Redis,当我们引入jar包后,系统会自动给我们创建一个RedisTemplate<Object, Object>。我们可以用这个RedisTemplate来对Redis进行增删改查操作。

public interface RedisRepository<HK, HV> {

    boolean contains(HK id);

    HV find(HK id);

    void save(HV value);

    void delete(HK id);
}
@Repository
public class ProductPriceRedisRepository implements RedisRepository<Long, ProductPriceEntity> { private static final String HASH_NAME = "product_price"; /**
* 自动注入的Bean有:<br>
* RedisTemplate<Object, Object>(in RedisAutoConfiguration)<br>
* StringRedisTemplate(in RedisAutoConfiguration)<br>
* RedisConnectionFactory(JedisConnectionFactory)<br>
*
* 所以只能定义成RedisTemplate<Object, Object>的形式<br>
*/
@Autowired
private RedisTemplate<Object, Object> redisTemplate; private HashOperations<Object, Long, ProductPriceEntity> hashOperations; @PostConstruct
private void init() {
hashOperations = redisTemplate.opsForHash();
} /* (non-Javadoc)
* @see com.mytools.repository.RedisRepository#contains(java.lang.Object)
*/
@Override
public boolean contains(Long id) {
return hashOperations.keys(HASH_NAME).contains(id);
} /* (non-Javadoc)
* @see com.mytools.repository.RedisRepository#find(java.lang.Object)
*/
@Override
public ProductPriceEntity find(Long productId) {
return hashOperations.get(HASH_NAME, productId);
} /* (non-Javadoc)
* @see com.mytools.repository.RedisRepository#save(java.lang.Object)
*/
@Override
public void save(ProductPriceEntity entity) {
hashOperations.put(HASH_NAME, entity.getProductId(), entity); } /* (non-Javadoc)
* @see com.mytools.repository.RedisRepository#delete(java.lang.Object)
*/
@Override
public void delete(Long productId) {
hashOperations.delete(HASH_NAME, productId);
}
}

配置application.yml

spring:
# Stream/Kafka info
cloud:
stream:
bindings:
# input -> productPriceInput (自定义通道)
productPriceInput:
# 要读到消息的消息队列的名称
destination: productPriceTopic
# 发送和接收消息类型
content-type: application/json
# 消费者组:保证消息只会被一组服务实例处理一次
group: productPriceGroup
# 使用kafka作为消息总线
kafka:
binder:
# 运行着kafka服务器的网络地址
brokers: www.mtools.com
# Redis/pool info (RedisProperties)
redis:
database:
host: www.mytools.com
port:
password:
timeout:
jedis:
pool:
# 最大连接数,0为没有限制
max-active:
# 最大空闲连接,0为没有限制
max-idle:
#最大建立连接等待时间,如果超过此时间将接到异常,设为-1表示无限制
max-wait: -1
#最小空闲连接,0为没有限制
min-idle:

API及其他业务逻辑

@Controller
@RequestMapping("pp")
public class ProductPriceController { @Autowired
private ProductPriceServiceImpl productPriceService; @GetMapping(value = "find/productId/{productId}")
@ResponseBody
public ProductPriceEntity find(@PathVariable String productId) {
return productPriceService.find(Long.valueOf(productId));
}
}

[注] 这里创建了一个API,用于查询商品价格。

@Service
@Transactional
public class ProductPriceServiceImpl { private static final Logger logger = LoggerFactory.getLogger(ProductPriceServiceImpl.class); @Autowired
private ProductPriceRedisRepository productPriceRedisRepository; @Autowired
private LoadBalancerClient loadBalancer; public ProductPriceEntity find(Long productId) { ProductPriceEntity result = null; if (productPriceRedisRepository.contains(productId)) {
result = productPriceRedisRepository.find(productId);
logger.info(">>>>> Get data from cache. {}", result.toString());
return result;
} ServiceInstance instance = loadBalancer.choose("app-db");
String path = String.format("http://%s:%s/app-db/pp/find/productId/%s", instance.getHost(), instance.getPort(),
productId.toString());
logger.info(path); RestTemplate restTemplate = new RestTemplate();
ResponseEntity<ProductPriceEntity> response = restTemplate.exchange(path, HttpMethod.GET, null,
ProductPriceEntity.class);
result = response.getBody();
logger.info(">>>>> Get data from downstream. {}", result == null ? "null" : result.toString()); if (result != null) {
productPriceRedisRepository.save(result);
} return result;
}
}

[注] 具体逻辑如下:

(1)如果缓存中有该数据,则从缓存中获取数据。

(2)如果缓存中没有该数据,则调用生产者服务获取数据。拿到数据后再存入缓存中。

Spring Cloud(7.3):配置Consumer Server的更多相关文章

  1. Spring Cloud Config(配置中心)

    每天学习一点点 编程PDF电子书.视频教程免费下载:http://www.shitanlife.com/code 一.简介 Spring Cloud Config为分布式系统中的外部配置提供服务器和客 ...

  2. spring cloud config将配置存储在数据库中

    Spring Cloud Config Server最常见是将配置文件放在本地或者远程Git仓库,放在本地是将将所有的配置文件统一写在Config Server工程目录下,如果需要修改配置,需要重启c ...

  3. 跟我学SpringCloud | 第六篇:Spring Cloud Config Github配置中心

    SpringCloud系列教程 | 第六篇:Spring Cloud Config Github配置中心 Springboot: 2.1.6.RELEASE SpringCloud: Greenwic ...

  4. Spring Cloud Config 实现配置中心,看这一篇就够了

    Spring Cloud Config 是 Spring Cloud 家族中最早的配置中心,虽然后来又发布了 Consul 可以代替配置中心功能,但是 Config 依然适用于 Spring Clou ...

  5. spring cloud 2.x版本 Eureka Server服务注册中心教程

    本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 1.创建服务注册中心 1.1 新建Spring boot工程:eureka-server 1 ...

  6. Spring Cloud Config的配置中心获取不到最新配置信息的问题

    Spring Cloud Config的配置中心获取不到最新配置信息的问题 http://blog.didispace.com/spring-cloud-tips-config-tmp-clear/

  7. Spring Cloud Feign 自定义配置(重试、拦截与错误码处理) 实践

    Spring Cloud Feign 自定义配置(重试.拦截与错误码处理) 实践 目录 Spring Cloud Feign 自定义配置(重试.拦截与错误码处理) 实践 引子 FeignClient的 ...

  8. spring cloud Eureka client配置(consumer通过Eureka发起对provider的调用)

    参考:http://www.ityouknow.com/springcloud/2017/05/12/eureka-provider-constomer.html springboot版本:2.0.3 ...

  9. 笔记:Spring Cloud Ribbon 客户端配置详解

    自动化配置 由于 Ribbon 中定义的每一个接口都有多种不同的策略实现,同时这些接口之间又有一定的依赖关系,Spring Cloud Ribbon 中的自动化配置能够很方便的自动化构建接口的具体实现 ...

随机推荐

  1. Git----查看提交日志

    Git log 只包括当前分支的commit. 截图示例: Git reflog 显示整个本地仓储的commit(所有branch,包括已撤销的commit) 截图示例: Git reflog --r ...

  2. Centos 拒绝ssh远程暴力破解方法

    佳木斯SEO摘要 有一天突然收到一封邮件,邮件内容告知我的ECS服务器作为肉鸡在攻击别的机器,期初一想,一定是我机器的账号密码被泄露,或者是被人暴力破解,于是乎,我就查询了一下我机器的账号登录记录. ...

  3. [cf1140D. Minimum Triangulation][dp]

    D. Minimum Triangulation time limit per test 2 seconds memory limit per test 256 megabytes input sta ...

  4. appium问题汇总(持续更新。。。)

    WEBVIEW_unknown adb版本较低,adb 1.0.32版本不支持安卓8.x版本,更新adb版本后正常 Install homebrew ruby -e "$(curl -fsS ...

  5. 【素数判定/筛法进阶算法】-C++

    今天我们来谈一谈素数的判定/筛法. 对于每一个OIer来说,在漫长的练习过程中,素数不可能不在我们的眼中出现,那么判定/筛素数也是每一个OIer应该掌握的操作,那么我们今天来分享几种从暴力到高效的判定 ...

  6. 010——C#选择文件路径

    (一)具体教程查看:011——C#创建ECXEL文件(附教程) (二)代码:foldPath 就是获取到的文件路径 private void button1_Click(object sender, ...

  7. 洛谷 P2580 于是他错误的点名开始了 题解

    每日一题 day10 打卡 Analysis trie树模板题,只需用到简单的插入和查询就好了 如果想要学trie树,见信息学奥赛一本通·提高篇P82 #include<iostream> ...

  8. mongodb 导入json文件遇到的坑

    使用mongoimport命令导入外部json文件时,发现一直报错 报错结果如下: json数据格式完全正确如下: 经过再三确认格式最终找到解决方案,原来用cmd导入数据时json  { }包含的数据 ...

  9. C#中的线程(一)入门 转载

    文章系参考转载,英文原文网址请参考:http://www.albahari.com/threading/ 转载:http://www.cnblogs.com/miniwiki/archive/2010 ...

  10. TensorFlow(十八):从零开始训练图片分类模型

    (一):进入GitHub下载模型-->下载地址 因为我们需要slim模块,所以将包中的slim文件夹复制出来使用. (1):在slim中新建images文件夹存放图片集 (2):新建model文 ...