基于spring boot的JsonSerializer 业务内容国际化

说起国际化,真的是老生常谈了。后端有各种i18n的依赖组件,springboot本身也支持i18n的设置,前端vue也有i18n的设置,这些常规操作就不提了,大家可以去搜索其他博客,写的都很详细。

本篇博客主要写的是业务内容国际化。举一个最常用最简单的例子,学生选课,课程有"语文","数学","英语"。这个课程也是一张业务表,随着课程的增多数据是逐渐增多的。一个学生要查看自己选择的课程时,如何根据语言进行国际化的反显"数学"还是"mathematics"。

最开始我拿到这个需求的时候,很挠头,怎么办,难得不是把这个需求做出来,这个需求实现得方式很多:

  1. 多建冗余字段,把”数学“和”mathematics“都存到表里,这样有明显得缺点,语言增多时需要一直在表里加字段。
  2. 建一张code、language、value的对应关系,查询数据的时候根据code和language进行value的匹配,这种缺陷也很明显,业务侵入性很强。

我要做的事情是让业务开发人员在无感知的情况下或侵入性很小的情况下把需求实现。提到侵入性小,大家很容易联想到切面编程AOP。我个人认为AOP最好用的地方就是能拿到自定义注解,通过在java类或者java方法上增加注解,在切面获取引入的东西并将我们相要的东西织入。

灵感一来,我们就开干。

一、建表,并存储基础数据

表的作用是能将各种code对应的各种语言的各种value进行匹配,建表比写在配置文件的好处是显而易见的,因为我们做的是业务内容的国际化,而不是定死的几个值得国际化,我们需要根据业务动态得调整内容。这个表的数据可以开一个接口,业务数据发生变化时,可以直接调用这个接口,对表中数据进行更新。

表结构如下:

 LANGUAGE_ID 主键

LANGUAGE_KEY 存在业务表中得业务标识

LANGUAGE 语言标识

LANGUAGE_VALUE 国际化后的值

MODEL 模块名称,主要防止KEY重复,同一个key在不同的业务中代表的含义不同。

以上面选课为例,该表存放的值为

1 course math en mathematics

2 course math zh-CN 数学

二、获取表中数据放入缓存

数据咱们都有了,怎么把数据拿出来用呢,每次查库?肯定不现实,我们应该提前把准备好,放在缓存中,谁想用直接取。缓存有多种方式。我们做jvm和redis两种,让大家做选择,追求效率就用jvm缓存,不求效率就用redis,对本身服务影响小一些。

1、首先定义实体类

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * 系统语言ResultDTO
 */
@Getter
@Setter
@ToString
public class SysLanguageConfig {


    private Long languageId;

    private String model;

    private String languageKey;

    private String language;

    private String languageValue;
    private long currentPage;
    private long pageSize;
}

2、获取数据并缓存的配置类

package com.cnhtc.hdf.wf.common.i18n;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StopWatch;

import java.util.*;
import java.util.concurrent.*;

@Configuration  
@EnableFeignClients(clients = {SysLanguageConfigService.class})
@Slf4j
public class LanguageCahceConfigration {
    public static ConcurrentHashMap<String, String> localCacheMap = new ConcurrentHashMap<>(); //本地存储缓存的map

    /**
     * 存储redis 热点数据
     */
    public static ConcurrentHashMap<String, String> redisHotspotCacheMap = new ConcurrentHashMap<>();

    public final static String CACHE_KEY_JOIN_SYMBOL = "_";

    private static Boolean i18n;

    @Value("${i18nPageSize: 5000}")
    private Integer i18nPageSize;

    @Value("${i18nEnableInitDataParallel: false}")
    private Boolean i18nEnableInitDataParallel;

    /**
     * 是否开启redis热点数据缓存,默认不开启
     */
    private static Boolean i18nEnableRedisHotspotCache;

    /**
     * 开启缓存模式
     * local MAP
     * redis
     */
    private static String i18nEnableCacheMode;

    private final static String CACHE_MODE = "local";

    /**
     * 多少个元素拆分一个List
     */
    private final Integer splitListSize = 10;


    /**
     * 批量插入 条数
     */
    private final Integer REDIS_BATCH_SAVE_SIZE = 5000;

    /**
     * 失效时间
     */
    private final long EXPIRE_SECONDS = 3600 * 1000;

    @Autowired
    private SysLanguageConfigService sysLanguageConfigService;


    @Bean
    public SysLanguageConfigServiceFallback sysLanguageConfigServiceFallback() {
        return new SysLanguageConfigServiceFallback();
    }

    public LanguageCahceConfigration() {
        System.out.println("------------------- 加载 LanguageCahceConfigration ----------------------------------");
    }

    @Scheduled(initialDelay = 1000, fixedRateString = "${i18nScheduledFixedRate:3600000}")
    public void setLanguageCacheMap() {
        if (i18n) {
            if (!CACHE_MODE.equals(i18nEnableCacheMode)) {
                return;
            }
            CopyOnWriteArrayList<SysLanguageConfig> allList = new CopyOnWriteArrayList<>();
            StopWatch sw = new StopWatch();
            try {
                sw.start("数据查询");
                if (i18nEnableInitDataParallel) {
                    this.selectDataCompletableFuture(allList);
                } else {
                    this.selectData(allList);
                }
                sw.stop();
            } catch (Exception e) {
                e.printStackTrace();
                allList.clear();
            }
            log.debug("allList size = {}", allList.size());
            sw.start("本地缓存");
            localCacheMap.clear();
            localCacheMap.putAll(this.getCacheDataMap(allList));
            sw.stop();
            log.warn("初始化i18n 缓存耗时 , {}", sw.prettyPrint());
            log.warn("初始化i18n 缓存总耗时 , {}", sw.getTotalTimeSeconds());
        }
    }


    /**
     * 循环查询数据
     *
     * @param allList 数据集合
     */
    private void selectData(CopyOnWriteArrayList<SysLanguageConfig> allList) {
        int page = 1;
        boolean isContinue = false;
        do {
            SysLanguageConfig sysLanguageConfig = new SysLanguageConfig();
            sysLanguageConfig.setCurrentPage(page);
            sysLanguageConfig.setPageSize(i18nPageSize);
            Page<SysLanguageConfig> result = sysLanguageConfigService.getAll(sysLanguageConfig);
            if (result != null && !CollectionUtils.isEmpty(result.getRecords())) {
                allList.addAll(result.getRecords());
                if (result.getPages() > page) {
                    isContinue = true;
                    page = page + 1;
                } else {
                    isContinue = false;
                }
            }
        } while (isContinue);
    }


    /**
     * 异步分页查询数据
     *
     * @param allList 数据集合
     * @throws Exception 异常
     */
    private void selectDataCompletableFuture(CopyOnWriteArrayList<SysLanguageConfig> allList) throws Exception {
        Page<SysLanguageConfig> result = this.getData();
        if (result != null && result.getPages() > 0) {
            allList.addAll(result.getRecords());
            if (result.getPages() > 1) {

                ForkJoinPool pool = new ForkJoinPool();
                List<Integer> pageList = new ArrayList<>();
                for (int i = 2; i <= result.getPages(); i++) {
                    pageList.add(i);
                }
                List<List<Integer>> partition = Lists.partition(pageList, splitListSize);
                for (List<Integer> pages : partition) {
                    List<CompletableFuture<Void>> futureList = new ArrayList<>();
                    for (Integer page : pages) {
                        SysLanguageConfig param = new SysLanguageConfig();
                        param.setCurrentPage(page);
                        param.setPageSize(i18nPageSize);

                        CompletableFuture<Void> future = CompletableFuture.runAsync(() ->
                                allList.addAll(sysLanguageConfigService.getAll(param).getRecords()), pool);
                        futureList.add(future);
                    }
                    CompletableFuture<Void> allSources = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[futureList.size()]));
                    allSources.get();
                }
            }
        }
    }

    /**
     * 获取数据
     * @return Page<SysLanguageConfig>
     */
    private Page<SysLanguageConfig> getData(){
        SysLanguageConfig sysLanguageConfig = new SysLanguageConfig();
        sysLanguageConfig.setCurrentPage(1);
        sysLanguageConfig.setPageSize(i18nPageSize);
        return sysLanguageConfigService.getAll(sysLanguageConfig);
    }

    /**
     * 批量插入并设置 失效时间,但是性能慢
     *
     * @param map 数据
     */
    private void redisPipelineInsert(ConcurrentHashMap<String, String> map) {
        StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
        RedisSerializer<String> serializer = stringRedisTemplate.getStringSerializer();
        stringRedisTemplate.executePipelined(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                map.forEach((key, value) -> {
                    connection.set(serializer.serialize(key), serializer.serialize(value), Expiration.seconds(EXPIRE_SECONDS), RedisStringCommands.SetOption.UPSERT);
                });
                return null;
            }
        }, serializer);
    }

    /**
     * 批量插入后 异步设置失效时间
     *
     * @param map 数据
     */
    //@Async
    public void setExpire(ConcurrentHashMap<String, String> map) {
        StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
        map.forEach((k, v) -> stringRedisTemplate.expire(k, EXPIRE_SECONDS, TimeUnit.SECONDS));
    }

    /**
     * 刷新redis缓存
     */
    @XxlJob("i18nRefreshRedisCache")
    public void refreshRedisCache() {
        XxlJobHelper.log("回调任务开始");
        if (i18n) {
            if (CACHE_MODE.equals(i18nEnableCacheMode)) {
                log.error("i18n国际化配置本地缓存,请勿用redis刷新缓存");
            }

            CopyOnWriteArrayList<SysLanguageConfig> allList = new CopyOnWriteArrayList<>();
            StopWatch sw = new StopWatch();
            try {
                sw.start("数据查询");
                if (i18nEnableInitDataParallel) {
                    this.selectDataCompletableFuture(allList);
                } else {
                    this.selectData(allList);
                }
                sw.stop();
            } catch (Exception e) {
                e.printStackTrace();
                allList.clear();
            }
            sw.start("redis缓存");
            StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
            if (ObjectUtils.isEmpty(stringRedisTemplate)) {
                throw new BaseException(ErrorEnum.NOTHROWABLE_ERROR, "StringRedisTemplate is null");
            }
            redisHotspotCacheMap.clear();
            ConcurrentHashMap<String, String> cacheDataMap = this.getCacheDataMap(allList);
            List<Map<String, String>> maps = splitMap(cacheDataMap, REDIS_BATCH_SAVE_SIZE);
            // multiSet 批量插入,key值存在会覆盖原值
            maps.forEach(data -> stringRedisTemplate.opsForValue().multiSet(data));
            sw.stop();
            log.warn("初始化i18n redis缓存耗时 , {}", sw.prettyPrint());
            log.warn("初始化i18n redis缓存总耗时 , {}", sw.getTotalTimeSeconds());
        }
        XxlJobHelper.log("回调任务结束");
    }

    /**
     * 刷新local缓存
     */
    //@XxlJob("i18nRefreshLocalCache")
    public ResponseDTO refreshLocalCache() {
        if (CACHE_MODE.equals(i18nEnableCacheMode)) {
            this.setLanguageCacheMap();
            return new ResponseDTO(SysErrEnum.SUCCESS);
        }
        return new ResponseDTO(SysErrEnum.ERROR.code(), "i18n国际化配置Redis缓存,请勿用本地刷新缓存");
    }

    /**
     * 获取缓存数据
     *
     * @param allList
     * @return
     */
    private ConcurrentHashMap<String, String> getCacheDataMap(List<SysLanguageConfig> allList) {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        allList.parallelStream().forEach(config -> map.put(config.getModel() + CACHE_KEY_JOIN_SYMBOL + config.getLanguageKey() + CACHE_KEY_JOIN_SYMBOL + config.getLanguage(), config.getLanguageValue()));
        log.warn("map size:{}", map.size());
        return map;
    }

    /**
     * 获取缓存数据值
     *
     * @param key key
     * @return value
     */
    public static String getCacheValueByKey(String key) {
        if (i18n) {
            String value;
            if (CACHE_MODE.equals(i18nEnableCacheMode)) {
                value = localCacheMap.get(key);
                log.debug("多语言转换:本地缓存数量 = {}, key = {}", +localCacheMap.values().size(), key);
            } else {
                if (i18nEnableRedisHotspotCache) {
                    if (redisHotspotCacheMap.containsKey(key)) {
                        value = redisHotspotCacheMap.get(key);
                    } else {
                        StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
                        value = stringRedisTemplate.opsForValue().get(key);
                        if (StringUtils.isNotBlank(value)) {
                            // 缓存热点数据
                            redisHotspotCacheMap.put(key, value);
                        }
                    }
                } else {
                    StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
                    value = stringRedisTemplate.opsForValue().get(key);
                }
            }
            return value;
        }
        return null;
    }

    @Value("${i18nEnableCacheMode: local}")
    private void setI18nEnableCacheMode(String i18nEnableCacheMode) {
        LanguageCahceConfigration.i18nEnableCacheMode = i18nEnableCacheMode;
    }

    @Value("${i18nEnableRedisHotspotCache: false}")
    private void setI18nEnableRedisHotspotCache(Boolean i18nEnableRedisHotspotCache) {
        LanguageCahceConfigration.i18nEnableRedisHotspotCache = i18nEnableRedisHotspotCache;
    }

    @Value("${i18n: false}")
    private void setI18n(Boolean i18n) {
        LanguageCahceConfigration.i18n = i18n;
    }

    /**
     * Map拆分 (指定分组大小)
     *
     * @param map       Map
     * @param chunkSize 每个分组的大小 (>=1)
     * @param <K>       Key
     * @param <V>       Value
     * @return 子Map列表
     */
    private <K, V> List<Map<K, V>> splitMap(Map<K, V> map, int chunkSize) {
        if (Objects.isNull(map) || map.isEmpty() || chunkSize < 1) {
            //空map或者分组大小<1,无法拆分
            return Collections.emptyList();
        }

        int mapSize = map.size(); //键值对总数
        int groupSize = mapSize / chunkSize + (mapSize % chunkSize == 0 ? 0 : 1); //计算分组个数
        List<Map<K, V>> list = Lists.newArrayListWithCapacity(groupSize); //子Map列表

        if (chunkSize >= mapSize) { //只能分1组的情况
            list.add(map);
            return list;
        }

        int count = 0; //每个分组的组内计数
        Map<K, V> subMap = Maps.newHashMapWithExpectedSize(chunkSize); //子Map

        for (Map.Entry<K, V> entry : map.entrySet()) {
            if (count < chunkSize) {
                //给每个分组放chunkSize个键值对,最后一个分组可能会装不满
                subMap.put(entry.getKey(), entry.getValue());
                count++; //组内计数+1
            } else {
                //结束上一个分组
                list.add(subMap); //当前分组装满了->加入列表

                //开始下一个分组
                subMap = Maps.newHashMapWithExpectedSize(chunkSize); //新的分组
                subMap.put(entry.getKey(), entry.getValue()); //添加当前键值对
                count = 1; //组内计数重置为1
            }
        }

        list.add(subMap);  //添加最后一个分组
        return list;
    }
}

整段代码其中区分了本地缓存、redis缓存等等,还有就是查刚才数据库表里得数据,因为我们才用了微服务得架构,所以获取数据得部分是通过feign的方式获取的,大家可以替换成自己的方法。另外,开启redis缓存的部分可以取舍,没必要这么完善,保留一种即可。本地缓存的定时任务是springboot的,redis的定时任务是xxl-job的,这些技术栈都可以替换。

其中最重要的一点,redis比本地缓存慢很多,100条数据的国际化反显,速度会差20倍。为什么差怎么多,接下来就到关键内容了。

三、注解定义

注解定义的意义就是在序列化的时候,能通过注解拿到切入点并获取注解的内容


import java.lang.annotation.*;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(
        using = I18nSerializer.class
)
public @interface I18n {

    String model() default "common";
    String language() default "";
    String key() default "";
}

四、基于JsonSerializer的序列化处理,进行国际化转换

大家现在都在用springboot的restController,也就是说,前后端分离之后,前后端的交互就是json,在controller返回的内容其实就是一个实体对象或者集合,那这个实体对象或者集合是怎么转换成json的,就是通过springboot中引入的jackson来实现的,具体实现原理不多说。

我们只需要知道,写一个子类,来继承JsonSerializer和实现ContextualSerializer就能实现序列化的时候进行织入操作。

其中language是通过header从前端传递过来的。

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.stdp.hdf.wf.common.core.constants.Constants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;

@Slf4j
public class I18nSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private String model;

    private String language;

    private String key;

    public I18nSerializer(String model, String language, String key) {
        this.model = model;
        this.language = language;
        this.key = key;
    }

    public I18nSerializer() {
    }

    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        String requestLanguage = null;
        String mapkey = s;
        if (StringUtils.isBlank(language)) {
            requestLanguage = getLanguage();
        } else {
            requestLanguage = language;
        }
        if (StringUtils.isNotBlank(requestLanguage)) {
            if (StringUtils.isNotBlank(key)) {
                Object o = jsonGenerator.getCurrentValue();
                mapkey = getPropertyValue(o, key).toString();
            }
            String keyString = model + LanguageCahceConfigration.CACHE_KEY_JOIN_SYMBOL + mapkey + LanguageCahceConfigration.CACHE_KEY_JOIN_SYMBOL + requestLanguage;
            String keyName = LanguageCahceConfigration.getCacheValueByKey(keyString);
            if (StringUtils.isBlank(keyName)) {
                keyName = s;
            }
            jsonGenerator.writeString(keyName);
        } else {
            jsonGenerator.writeString(s);
        }

    }


    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) { // 为空直接跳过
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { // 非 String 类直接跳过
                I18n i18n = beanProperty.getAnnotation(I18n.class);
                if (i18n == null) {
                    i18n = beanProperty.getContextAnnotation(I18n.class);
                }
                if (i18n != null) { // 如果能得到注解,就将注解的 value 传入 I18nSerializer
                    return new I18nSerializer(i18n.model(), i18n.language(), i18n.key());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(beanProperty);
    }

    public String getLanguage() {
        //直接从request中获取language信息
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return null;
        }

        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();

        return request.getHeader(Constants.LANGUAGE);
    }

    public Object getPropertyValue(Object t, String objProperty) {
        Map<String, String> objMap = null;
        try {
            objMap = BeanUtils.describe(t);
            if (objMap.get(objProperty) != null) {
                return objMap.get(objProperty);
            }
            return "";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}

五、使用

还是以选课为例,返回的json信息,CourseName自动就转成了对应的语言。

@Getter
@Setter
@ToString
public Course implements Serializable {
    private String courseCode; //课程编号 

    @I18n(model = "course",key = "courseCode")
    private String courseName; //课程名称

}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目说明 该项目是一个典型的由Spring Cloud管理的微服务项目,主要包括如下模块 micro-service-cloud─────────────────顶层项目 ├──cloud-service-core───────────────基础核心模块 ├──cloud-service-tools──────────────全局通用工具类 ├──cloud-service-reids──────────────Redis二次封装 ├──cloud-eureka-server──────────────服务注册中心[8761] ├──cloud-turbine-server─────────────断路器聚合监控[8769] ├──cloud-zipkin-server──────────────链路追踪监控[9411] ├──cloud-zuul-server────────────────第一代服务网关(Zuul)[8080] ├──cloud-gateway-server─────────────第二代服务网关(Gateway)[8080] ├──cloud-modules-app────────────────App微服务模块 ├───────modules-app-user────────────App用户服务模块[努力更新中] ├───────modules-app-doctor──────────App医生服务模块[努力更新中] ├──cloud-modules-service────────────微服务通用服务模块 ├───────mongodb-file-service────────Mongodb文件服务模块[11010] ├───────redis-delay-service─────────延迟消费服务模块[11020] ├──cloud-modules-web────────────────Web微服务模块 ├───────modules-web-security────────Web医生服务模块[12010] ├───────modules-web-user────────────Web用户服务模块[12020] ├──cloud-modules-wechat─────────────Wechat微服务模块 ├───────modules-wechat-user─────────Wechat用户服务模块[努力更新中] └───────modules-wechat-doctor───────Wechat医生服务模块[努力更新中] 修改日志 修改日志 修改人 修改日期 版本计划 V1.0 刘岗强 2019-01-07 项目初始化 V1.1 刘岗强 待定 新增自动问答 项目介绍 基于Spring Cloud Finchley SR2 Spring Boot 2.0.7的最新版本。 核心基础项目内实现类自定义的权限注解,配合RBAC权限模型+拦截器即可实现权限的控制,具体的参考项目中的实现。同时也封装了一些顶层类和结果集等。 注册中心实现高可用配置,详情见eureka的one、two、three三个配置文件,摘要如下。 ------------------------------------------配置节点一---------------------------------------------- server: port: 8761 spring: application: name: cloud-eureka-server eureka: instance: hostname: cloud.server.one prefer-ip-address: true instance-id: ${spring.cloud.client.ip-address}:${server.port}:${spring.application.name} client: healthcheck: enabled: true register-with-eureka: false fetch-registry: false service-url: defaultZone: http://cloud.server.two:8762/eureka/,http://cloud.server.three:8763/eureka/ ------------------------------------------配置节点二----------------------------------------------
Spring Boot中使用Kafka需要在配置文件中指定Kafka的相关配置。下面是一个简单的Kafka配置文件示例: ``` spring.kafka.bootstrap-servers=localhost:9092 spring.kafka.consumer.group-id=my-group spring.kafka.consumer.auto-offset-reset=earliest spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer ``` 解释一下上述配置项的含义: - `spring.kafka.bootstrap-servers`:Kafka集群的地址,如果有多个地址,可以用逗号分隔。 - `spring.kafka.consumer.group-id`:消费者所属的组ID。 - `spring.kafka.consumer.auto-offset-reset`:指定消费者在没有初始偏移量的情况下该如何开始消费消息。这里配置为最早的偏移量。 - `spring.kafka.consumer.key-deserializer`:消费者使用的键反序列化器。 - `spring.kafka.consumer.value-deserializer`:消费者使用的值反序列化器。 - `spring.kafka.producer.key-serializer`:生产者使用的键序列化器。 - `spring.kafka.producer.value-serializer`:生产者使用的值序列化器。 注意:这里的序列化器和反序列化器需要根据实际情况进行替换。如果使用的是JSON格式的消息,可以使用`org.springframework.kafka.support.serializer.JsonSerializer`和`org.springframework.kafka.support.serializer.JsonDeserializer`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值