接下来我们创建一个消费者服务。消费者服务从生产者服务拿取商品-价格信息,并保存在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. matlab(3) Logistic Regression: 求cost 和gradient \ 求sigmoid的值

    sigmoid.m文件 function g = sigmoid(z)%SIGMOID Compute sigmoid functoon% J = SIGMOID(z) computes the si ...

  2. TensorFlow(五):手写数字识别加强版

    # 该版本的最终识别准确率达到98%以上 import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_d ...

  3. 《论文翻译》 GIOU

    目录 广义交并比-GIOU(回归检测的一种指标和Loss) 注释 1. 摘要 2. 介绍 3. 相关工作 4. 广义交并比 5. GIOU作为目标检测的LOSS 6. 实验结果 7. 个人理解 单词汇 ...

  4. redis配置数据持久化---APPEND ONLY MODE

    Redis配置数据持久化---APPEND ONLY MODE 2016年04月01日 19:05:11 阅读数:9918 Redis可以实现数据的持久化存储,即将数据保存到磁盘上. Redis的持久 ...

  5. 数据结构实验之图论十一:AOE网上的关键路径【Bellman_Ford算法】

    Problem Description 一个无环的有向图称为无环图(Directed Acyclic Graph),简称DAG图.     AOE(Activity On Edge)网:顾名思义,用边 ...

  6. 函数第二部分:为什么说动态参数是没有计划好的参数-Python基础前传(11)

    动态参数1-一个星号变元组 动态参数存在的意义? 函数的作者有时候也不知道这个函数到底需要多少个参数,这时候动态参数就有存在的意义了 动态参数创建-加* 底层原理是:把数值型或其他数据类型变成了元组类 ...

  7. 前端逼死强迫症系列之javascript续集

    一.javascript函数 1.普通函数 function func(){ } 2.匿名函数 setInterval(function(){ console.log(123); },5000) 3. ...

  8. linux 网络带宽和延时测试

    Linux下使用qperf命令来测试网络带宽和网络延迟 参考文章:https://access.redhat.com/solutions/2122681 若是没有安装qperf命令,请使用yum 安装 ...

  9. 咏南中间件和开发框架全面支持DELPHI10.3.2

    咏南中间件和开发框架全面支持DELPHI10.3.2 易博龙公司2019年7月12日发布了RAD STUDIO10.3.2正式版本. 咏南中间件自2019年7月14日始,中间件.CS框架.WEB框架. ...

  10. 基于python的人脸识别(检测人脸、眼睛、嘴巴、鼻子......)

    本文链接:https://blog.csdn.net/James_Ray_Murphy/article/details/79209172 import numpy as np import cv2 # ...