实现Ribbon规则
2022-08-08 本文已影响0人
zianL
自定义获取节点路由规则,可满足发布时不影响客户正常使用处理方式如下
新代码定义版本号2,存量旧代码版本号1,
1、先执行ng lua 脚本实现功能是针对公司内部的请求IP获取到最新的版本号,并将这个版本号放在请求上下文中传递;
2、将新版本2 代码更新一个节点;
3、产品在内部网络验证,因为是内部网络所以ng 处获取到的是最新的版本,当验证时最新的版本号随上下文传递,所以ribbon 获取节点规则中会优先命中新代码,即可验证新代码逻辑是否符合业务需要;
4、验证成功后将ng 版本号全部更新为最新,然后将所有服务节点同步新代码;即可完成灰度发布全部过程;
如果像手机开发版这种需要更多的客户参与验证,可将ng 版本 跟客户id 或者 区域IP段映射。即可满足多客户验证;
@Configuration
@ConditionalOnNacosDiscoveryEnabled
public class NacosAutoConfiguration {
@Bean
public RouteProperties routeProperties() {
return new RouteProperties();
}
@Bean
public NacosNamingService nacosNamingService() {
return new NacosNamingService();
}
@Bean
@Scope("prototype")
public IRule getRibbonRule() {
return new NacosWeightLoadBalancerRule();
}
}
@Slf4j
@Configuration
@ConditionalOnRouteEnabled
public class RouteAutoConfiguration {
@Autowired(required = false)
private RouteProperties routeProperties;
@Value("${spring.cloud.nacos.discovery.namespace:}")
private String namespace;
@PostConstruct
public void init() {
NacosExecutorService.setBaseUrl(routeProperties.getAddress());
NacosPullInstanceWorker worker = new NacosPullInstanceWorker();
//保存路由信息交由线程池执行
worker.run();
int period = routeProperties.getPeriod();
NacosExecutorService nacosExecutorService = new NacosExecutorService("nacos-pull-instance");
nacosExecutorService.execute(period, worker);
}
}
//创建一个调度线程执行拉取服务列表
public class NacosExecutorService {
final ScheduledExecutorService executorService;
private static String baseUrl;
public static String getBaseUrl() {
return baseUrl;
}
public static void setBaseUrl(String baseUrl) {
NacosExecutorService.baseUrl = baseUrl;
}
public void execute(int period, Runnable task) {
executorService.scheduleWithFixedDelay(task, 3, period, TimeUnit.SECONDS);
}
public NacosExecutorService(String name) {
executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName(name);
t.setDaemon(true);
return t;
}
});
}
}
//拉取服务线程实例
@Slf4j
public class NacosPullInstanceWorker implements Runnable {
@Override
public void run() {
HashMap<Object, Object> paramMap = new HashMap<>();
paramMap.put("md5", md5);
RestTemplate restTemplate = new RestTemplate();
TypeReference<List<SmartInstance>> forObject = restTemplate.getForObject(NacosExecutorService.getBaseUrl(), new TypeReference<List<SmartInstance>>() {
}.getClass(), paramMap);
List<SmartInstance> instances = (List<SmartInstance>) forObject.getType();
if (instances == null) {
log.warn("Nacos列表为空!");
return;
}
log.debug("Nacos列表数量:{}", instances.size());
NacosNamingService.setINSTANCE(instances);
}
}
//实际实现策略方法
@Slf4j
public class NacosNamingService {
@Autowired
private NacosDiscoveryProperties discoveryProperties;
@Autowired
private RouteProperties routeProperties;
public Instance getInstance(String serviceName, String groupName) throws NacosException {
Boolean routeEnable = routeProperties.isEnable();
if (!routeEnable) {
return discoveryProperties.namingServiceInstance().selectOneHealthyInstance(serviceName, groupName);
}
GlobalContext globalContext = ContextUtil.getCurrentContext();
if (globalContext == null) {
globalContext = new GlobalContext();
}
String env = globalContext.getEnv();
if (env == null) {
ApplicationContext context = SpringContextUtil.getContext();
if (context != null) {
Environment environment = context.getEnvironment();
env = environment.getProperty("spring.cloud.nacos.discovery.namespace", "");
} else {
env = "";
}
}
String group = globalContext.getGroup();
if (group == null) {
ApplicationContext context = SpringContextUtil.getContext();
if (context != null) {
Environment environment = context.getEnvironment();
group = environment.getProperty("spring.cloud.nacos.discovery.group", "");
} else {
group = CommonConstant.DEFAULT_GROUP;
}
}
return getInstance(serviceName, env, group, globalContext.getClientIp(),globalContext.getVersion());
}
public Instance getInstance(String serviceName, String envName, String groupName, String clientIp,String version) throws NacosException {
log.debug("getInstance serviceName:{}, envName:{}, groupName:{}, clientIp:{}", serviceName, envName, groupName, clientIp);
List<SmartInstance> envAndGroupInstanceList = new ArrayList<>();
List<SmartInstance> envDefaultGroupInstanceList = new ArrayList<>();
List<SmartInstance> defaultInstanceList = new ArrayList<>();
List<SmartInstance> instanceList = INSTANCE_MAP.get(serviceName);
//非空校验
if (instanceList == null || instanceList.size() == 0) {
log.warn("Nacos服务列表为空!");
return null;
}
//遍历搜索
for (SmartInstance instance : instanceList) {
//服务信息获取
//优先本机匹配
if (clientIp == null) {
clientIp = LocalIpUtil.getLocalIp();
}
if (clientIp.equals(instance.getIp())) {
return instance;
}
//如果ng 传进来的版本等于最新版本的代码就优先选择,每次发布需要更新ng的验证版本,验证通过后在全面更新所有节点
if(eq(version,instance.getVersion())){
defaultInstanceList.add(instance);
continue;
}
//按照下列规则路由: (环境,组) -> (环境,默认组) -> (默认环境,默认组)
if (eq(instance.getNamespace(), envName) && eq(instance.getGroupName(),groupName)) {
envAndGroupInstanceList.add(instance);
continue;
}
if (eq(instance.getNamespace(),envName) && eq(instance.getGroupName(),CommonConstant.DEFAULT_GROUP)) {
envDefaultGroupInstanceList.add(instance);
continue;
}
if (eq(instance.getNamespace(),CommonConstant.Env.MASTER) && eq(instance.getGroupName(),CommonConstant.DEFAULT_GROUP)) {
defaultInstanceList.add(instance);
}
}
//输出路由结果
log.debug("getInstance envAndGroupInstanceList:{} envDefaultGroupInstanceList:{} defaultInstanceList:{}",
envAndGroupInstanceList, envDefaultGroupInstanceList, defaultInstanceList);
//获取最合适实例
SmartInstance instance = getSuitableInstance(envAndGroupInstanceList);
if (instance != null) {
return instance;
}
instance = getSuitableInstance(envDefaultGroupInstanceList);
if (instance != null) {
return instance;
}
return getSuitableInstance(defaultInstanceList);
}
/**
* 获取包含测试网段的实例
* @param instanceList 实例列表
* @return
*/
private SmartInstance getSuitableInstance(List<SmartInstance> instanceList) {
if (instanceList.size() > 0) {
SmartInstance instance = instanceList.get(0);
for (SmartInstance instanceItem : instanceList) {
if(instanceItem.getWeight() > instance.getWeight()){
instance = instanceItem;
}
}
return instance;
}
return null;
}
static boolean eq(String s1, String s2){
return StringUtils.equals(s1, s2);
}
public static void setINSTANCE(List<SmartInstance> instances) {
if(instances != null){
Map<String, List<SmartInstance>> map = instances.stream()
.collect(Collectors.groupingBy(SmartInstance::getName));
if(map != null){
INSTANCE_MAP = map;
}
}
}
private static Map<String, List<SmartInstance>> INSTANCE_MAP = new HashMap<>();
}
//重写获取服务的方法
@Slf4j
public class NacosWeightLoadBalancerRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Autowired
private NacosNamingService nacosNamingService;
@Autowired
private ConfigurableApplicationContext applicationContext;
@Override
public Server choose(Object o) {
String groupName = applicationContext.getEnvironment().getProperty("spring.cloud.nacos.config.group");
if (groupName == null) {
groupName = "DEFAULT_GROUP";
}
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
try {
Instance instance = nacosNamingService.getInstance(name, groupName);
log.info("请求服务:{}", instance != null ? instance.getInstanceId() : null);
return new NacosServer(instance);
} catch (NacosException ee) {
log.error("请求服务异常!异常信息:{}", ee);
} catch (Exception e) {
log.error("请求服务异常!异常信息:{}", e);
}
return null;
}
}
public class SmartInstance extends Instance {
String groupName;
String name;
String namespace;
String version;
}
}