JAVA 本地缓存使用-分布式时要使用路由

在redis盛行的今天很少有人还会选择本地缓存,但是在一次面试中有人问了,我就学习一下,为防止以后忘记,写篇博客记录一下。

使用本地缓存,需要使用一个路由策略,保证同一个单号的数据永远路由到同一台机器,这个可以使用Hash算法生成一个hashcode,然后和集群数量做取模运算。 直接记录代码,这个代码是借鉴的别人的。


import org.apache.commons.lang3.StringUtils;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;

public class ConsistentHashWithVirtualNode {
    /**
     * 虚拟节点信息
     * key:hash值
     * value:真实节点+"&"+序号
     * */
    private static SortedMap<Integer, String> virtualNodeMap = new TreeMap<>();
    //单机虚拟节点
    private static final int VIRTUAL_NODE_NUM = 5;
    //服务器配置信息(可配置)
    private static String[] servers = {"192.168.56.120:6379",
            "192.168.56.121:6379",
            "192.168.56.122:6379",
            "192.168.56.123:6379",
            "192.168.56.124:6379"};

    /**
     * 初始化
     * */
    static{
        for(int i=0; i< servers.length; i++){
            for(int j=0; j<VIRTUAL_NODE_NUM; j++){
                String virtualNodeName = servers[i] + "&" + j;
                virtualNodeMap.put(getHash(virtualNodeName), virtualNodeName);
            }
        }
        System.out.println("带虚拟节点的Hash环初始化完成!");

    }

    /**
     * 经典的Time33 hash算法
     * */
    public static int getHash(String key) {
        if(StringUtils.isEmpty(key))
            return 0;
        try{
            MessageDigest digest = MessageDigest.getInstance("MD5");
            key = new String(digest.digest(key.getBytes()));
        }catch(NoSuchAlgorithmException e){
            e.printStackTrace();
        }
        int hash = 5381;
        for (int i = 0; i < key.length(); i++) {
            int cc = key.charAt(i);
            hash += (hash << 5) + cc;
        }
        return hash<0 ? -hash : hash;
    }

    /**
     * 缓存路由算法
     * */
    public static String getServer(String key){
        int hash = getHash(key);
        //得到大于该Hash值的所有Map
        SortedMap<Integer, String> subMap = virtualNodeMap.tailMap(hash);
        if(subMap.isEmpty()){
            int index = virtualNodeMap.firstKey();
            System.out.printf("%s被路由到虚拟节点[%s]真实节点[%s]\n", key, virtualNodeMap.get(index),
                    virtualNodeMap.get(index).substring(0, virtualNodeMap.get(index).indexOf("&")));
            return virtualNodeMap.get(index).substring(0, virtualNodeMap.get(index).indexOf("&"));
        }else{
            int index = subMap.firstKey();
            System.out.printf("%s被路由到虚拟节点[%s]真实节点[%s]\n", key, virtualNodeMap.get(index),
                    virtualNodeMap.get(index).substring(0, virtualNodeMap.get(index).indexOf("&")));
            return virtualNodeMap.get(index).substring(0, virtualNodeMap.get(index).indexOf("&"));
        }
    }

    /**
     * 使用UUID模拟随机key
     * */
    public static void main(String[] args) {
//        for(int i=0; i<20; i++){
//            String str = UUID.randomUUID().toString();
//            getServer(str);
//        }


        String user = new String("Java4ye");
        ReferenceQueue<String> userReferenceQueue = new ReferenceQueue<>();
// 创建User对象的虚引用
        PhantomReference<String> phantomReference = new PhantomReference<>(user, userReferenceQueue);
        System.out.println(userReferenceQueue.poll().get());
// 去掉强引用
        user = null;
        System.out.println(phantomReference.get());

        System.gc();
        System.out.println("GC: " + phantomReference.get());
        Reference<? extends String> reference = null;
        try {
            reference = userReferenceQueue.remove(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (reference != null) {
            System.out.println("对象User被回收了:");
        }
    }

}

 Spring Cloud Gateway 自定义负载均衡

重写LoadBalancerClientFilter


import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import java.net.URI;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.web.server.ServerWebExchange;
public class UserLoadBalancerClientFilter extends LoadBalancerClientFilter {
    public UserLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
        super(loadBalancer, properties);
    }
    @Override
    protected ServiceInstance choose(ServerWebExchange exchange) {
        //这里可以拿到web请求的上下文,可以从header中取出来自己定义的数据。
        String hashcode= exchange.getRequest().getHeaders().getFirst("hashcode");
         if (hashcode== null) {
             return super.choose(exchange);
         }
        if (this.loadBalancer instanceof RibbonLoadBalancerClient) {
            RibbonLoadBalancerClient client = (RibbonLoadBalancerClient) this.loadBalancer;
            String serviceId = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost();
            //这里使用hashcode做为选择服务实例的key
            return client.choose(serviceId, hashcode);
        }
        return super.choose(exchange);
    }
}

 添加自定义的负载规则


import java.util.List;
import org.apache.commons.lang.math.RandomUtils;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.Server;
/**
 *
 * @ClassName: GameCenterBalanceRule
 * @Description: 根据userId对服务进行负载均衡。同一个用户id的请求,都转发到同一个服务实例上面。
 * @author: wgs
 * @date: 2019年3月15日 下午2:17:06
 */
public class GameCenterBalanceRule extends AbstractLoadBalancerRule {
    @Override
    public Server choose(Object key) {//这里的key就是过滤器中传过来的hashcode
        List<Server> servers = this.getLoadBalancer().getReachableServers();
        if (servers.isEmpty()) {
            return null;
        }
        if (servers.size() == 1) {
            return servers.get(0);
        }
        if (key == null) {
            return randomChoose(servers);
        }
        return hashKeyChoose(servers, key);
    }
    /**
     *
     * <p>Description:随机返回一个服务实例 </p>
     * @param servers
     * @return
     * @author wgs
     * @date  2019年3月15日 下午2:25:23
     *
     */
    private Server randomChoose(List<Server> servers) {
        int randomIndex = RandomUtils.nextInt(servers.size());
        return servers.get(randomIndex);
    }
    /**
     *
     * <p>Description:使用key的hash值,和服务实例数量求余,选择一个服务实例 </p>
     * @param servers
     * @param key
     * @return
     * @author wgs
     * @date  2019年3月15日 下午2:25:36
     *
     */
    private Server hashKeyChoose(List<Server> servers, Object key) {
        //此处可以使用上面所说的一致性hash 算法
        int hashCode = Math.abs(key.hashCode());
        if (hashCode < servers.size()) {
            return servers.get(hashCode);
        }
        int index = hashCode % servers.size();
        return servers.get(index);
    }
    @Override
    public void initWithNiwsConfig(IClientConfig config) {
    }
}

 添加Bean

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class LoadBalancedBean {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    @Bean
    public UserLoadBalancerClientFilter userLoadBalanceClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
        return new UserLoadBalancerClientFilter(client, properties);
    }
}

本地缓存框架有很多 如ehcache、GuavaCache、Caffeine等,因为这些框架使用都比较简单,网上有很多例子,我就不写了

Java高性能本地缓存框架Caffeine_C.-CSDN博客_java本地缓存框架

中介绍了Caffeine 的使用

java中使用Ehcache缓存数据 - shuaiflying - 博客园

中介绍了ehcache 的使用

Java Guava Cache 使用_编程战五渣-CSDN博客

中介绍了GuavaCache的使用

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值