Spring Cloud(7.3):配置Consumer Server

接下来我们创建一个消费者服务。消费者服务从生产者服务拿取商品-价格信息,并保存在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: 0
    host: www.mytools.com
    port: 6379
    password:
    timeout: 8000
    jedis:
      pool:
        # 最大连接数,0为没有限制
        max-active: 8
        # 最大空闲连接,0为没有限制
        max-idle: 8
        #最大建立连接等待时间,如果超过此时间将接到异常,设为-1表示无限制
        max-wait: -1
        #最小空闲连接,0为没有限制
        min-idle: 0

 

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)如果缓存中没有该数据,则调用生产者服务获取数据。拿到数据后再存入缓存中。

 

posted @ 2019-08-02 14:51  Storm_L  阅读(299)  评论(0编辑  收藏  举报