Redis集群一致性Hash效果的代码演示

在微服务领域,使用Redis做缓存可并不是一件容易的事情。

像新浪、推特这样的应用,许许多多的热点数据全都存放在Redis这一层,打到DB层的请求并不多,可以说非常依赖缓存了。如果缓存挂掉,流量全部穿透到DB层,其必然不堪其重,整个系统也会随之瘫痪,后果非常严重。 由于缓存数据量很大,Redis快正是快在其基于内存的快速存取,而计算机的内存资源又是十分有限的,故分布式缓存集群面临着伸缩性的要求。

一致性Hash存在的意义

Redis集群中的各实例之间是并不知道对方的,需要在客户端实现路由法来将key路由到不同的redis节点。

该路由算法是关键,它必须让新上线的缓存服务器对整个分布式缓存集群影响最小,使得扩容后,整个缓存服务器集群中已经缓存的数据尽可能还被访问到。

若是使用一般的对key进行一次hash的算法,则会导致扩容后命中率极低。 如下表所示,当集群由3个节点扩容到4个节点时,会有75%的key无法命中。

hash(key)hash(key)/3hash(key)/4是否命中
111
222
303
410
521
602
713
820
901
1012
1123
1200

这可太糟糕了,当服务器数量为100台时,再增加一台新服务器,不能命中率将达到99%,这和整个缓存服务挂了一个效果。

而一致性Hash正是为了解决这个问题而出现的,该路由算法通过引入一个一致性Hash环,以及进一步增加虚拟节点层,来实现尽可能高的命中率。 使用该算法,当节点由n扩容为n+1时,命中率可保持在n/(n+1)左右。

关于该算法的具体原理与网上已经有一些说得很透彻的文章,本文不再赘述。 下面主要从代码实现及运行的方式来对此算法的效果进行展示。

本机部署多个Redis节点

要对一致性Hash进行验证,要做好准备工作,首先要有一个Redis集群。 这里我通过使用在本机上部署多个Redis实例指向不同端口来模拟这一形态。

建立项目目录:$ mkdir redis-conf 将redis的配置copy一份过来并复制为5份,分别命名为redis-6379.conf~redis-6383.conf。

需要对其内容进行一些修改才能正常启动,分别找到配置文件中的如下两行并对数字进行相应修改。

port 6379
pidfile /var/run/redis_6379.pid
复制代码

然后就可以分别启动了:redis-server ./redis-6379 &

可以使用redis-cli -p 6379来指定连接的redis-server。 不妨进行一次尝试,比如在6379设置key 1 2,而到6380 get 1只能得到nil,说明它们是各自工作的,已经满足可以测试的条件。

代码实现

思路是这样的: 部署4个节点,从6379到6382,通过一致性Hash算法,将key: 0~99999共100000个key分别set到这4个服务器上,然后再部署一个节点6383,这时再从0到99999开始get一遍,统计get到的次数来验证命中率是否为期望的80%左右(4/5)。

一致性Hash算法的实现严重借鉴了这篇文章,使用红黑树来做数据结构,来实现log(n)的查找时间复杂度,使用FNV1_32_HASH哈希算法来尽可能使key与节点分布得更加均匀,引入了虚拟节点,来做负载均衡。

建议读者详细看下这篇文章,里面的讲解非常详细易懂。

下面是我改写过后的代码:

package org.guerbai.io.jedistry;import redis.clients.jedis.Jedis;
import java.util.*;class JedisProxy {private static String[][] redisNodeList = {{"localhost", "6379"},{"localhost", "6380"},{"localhost", "6381"},{"localhost", "6382"},};private static Map<String, Jedis> serverConnectMap = new HashMap<>();private static SortedMap<Integer, String> virtualNodes = new TreeMap<>();private static final int VIRTUAL_NODES = 100;static{for (String[] str: redisNodeList){addServer(str[0], str[1]);}System.out.println();}private static int getHash(String str){final int p = 16777619;int hash = (int)2166136261L;for (int i = 0; i < str.length(); i++)hash = (hash ^ str.charAt(i)) * p;hash += hash << 13;hash ^= hash >> 7;hash += hash << 3;hash ^= hash >> 17;hash += hash << 5;// 如果算出来的值为负数则取其绝对值if (hash < 0)hash = Math.abs(hash);return hash;}private static String getServer(String node){// 得到带路由的结点的Hash值int hash = getHash(node);// 得到大于该Hash值的所有MapSortedMap<Integer, String> subMap =virtualNodes.tailMap(hash);// 第一个Key就是顺时针过去离node最近的那个结点if (subMap.isEmpty()) {subMap = virtualNodes.tailMap(0);}Integer i = subMap.firstKey();// 返回对应的虚拟节点名称,这里字符串稍微截取一下String virtualNode = subMap.get(i);return virtualNode.substring(0, virtualNode.indexOf("&&"));}public static void addServer(String ip, String port) {for (int i = 0; i < VIRTUAL_NODES; i++){String virtualNodeName = ip + ":" + port + "&&VN" + String.valueOf(i);int hash = getHash(virtualNodeName);System.out.println("虚拟节点[" + virtualNodeName + "]被添加, hash值为" + hash);virtualNodes.put(hash, virtualNodeName);}serverConnectMap.put(ip+":"+port, new Jedis(ip, Integer.parseInt(port)));}public String get(String key) {String server = getServer(key);Jedis serverConnector = serverConnectMap.get(server);if (serverConnector.get(key) == null) {System.out.println(key + "not in host: " + server);}return serverConnector.get(key);}public void set(String key, String value) {String server = getServer(key);Jedis serverConnector = serverConnectMap.get(server);serverConnector.set(key, value);System.out.println("set " + key + " into host: " + server);}public void flushdb() {for (String str: serverConnectMap.keySet()) {System.out.println("清空host: " + str);serverConnectMap.get(str).flushDB();}}public float targetPercent(List<String> keyList) {int mingzhong = 0;for (String key: keyList) {String server = getServer(key);Jedis serverConnector = serverConnectMap.get(server);if (serverConnector.get(key) != null) {mingzhong++;}}return (float) mingzhong / keyList.size();}}public class ConsistencyHashDemo {public static void main(String[] args) {JedisProxy jedis = new JedisProxy();jedis.flushdb();List<String> keyList = new ArrayList<>();for (int i=0; i<100000; i++) {keyList.add(Integer.toString(i));jedis.set(Integer.toString(i), "value");}System.out.println("target percent before add a server node: " + jedis.targetPercent(keyList));JedisProxy.addServer("localhost", "6383");System.out.println("target percent after add a server node: " + jedis.targetPercent(keyList));}
}
复制代码

以上代码对参考文章进行了一些改进。

首先,参考文章的getServer方法会有些问题,当key大于最大的虚拟节点hash值时tailMap方法会返回空,找不到节点会报错,其实这时应该去找hash值最小的一个虚拟节点。我加了处理,把这个环连上了。

下面getHash方法为FNV1_32_HASH算法,可以不用太在意。

VIRTUAL_NODES的值比较重要,当节点数目较少时,虚拟节点数目越大,命中率越高。

在程序设计上也有很大的不同,我写了JedisProxy类,来做为client访问Redis的中间层,在该类的static块中利用服务器节点生成虚拟节点构造好红黑树,getServer里根据tailMap方法取出实际节点的地址,再由实际节点的地址直接拿到jedis对象,提供简单的get与set方法,先根据key拿特定的jedis对象,再进行get, set操作。

addServer静态方法给了其动态扩容的能力,可以看到在main方法中,通过调用JedisProxy.addServer("localhost", "6383")便直接增加了节点,不需要停应用。 targetPercent方法是用来统计命中率用。

当虚拟节点为5时,命中率约为60%左右,把它加大到100后,可以到达预期的80%的命中率。

好的,完美。

转载于:https://juejin.im/post/5c52cfcc51882542ff129941

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/537011.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

多线程-题

1、进程和线程之间有什么不同&#xff1f; 一个进程是一个独立&#xff08;self contained&#xff09;的运行环境&#xff0c;它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。java运行环境是一个包含了不同的类和程序的单一进程。线程可以被称为轻量级进…

JDK8那些惊为天人的新特性

分享一波:程序员赚外快-必看的巅峰干货 介绍 随着java的发展&#xff0c;越来越多的企业开始使用 java8 版本。Java8 是自 java5之后最重要的版本&#xff0c;这个版本包含语言、编译器、库、工具、JVM等方面的十多个新特性。本次课程将着重学习其中的一些重点特性。 Jdk8新…

mount 安卓system只读_Android如何让system分区可读写(MTK安卓6.0)-阿里云开发者社区...

Android 系统默认情况下&#xff0c;system 分区是只读 mount 的&#xff0c;因为无法进行往里写数据的&#xff0c;可以用 adb 命令 adb remount 重新 mount 一下。也可以通过在板子上&#xff0c;输入以下命令重新mount一下system分区命令使其可读可写。# mount -o remount /…

【数据结构和算法05】 红-黑树(转发)

2019独角兽企业重金招聘Python工程师标准>>> 【数据结构和算法05】 红-黑树&#xff08;看完包懂~&#xff09; 置顶 2016年04月13日 15:50:25 eson_15 阅读数&#xff1a;52681 标签&#xff1a; java数据结构算法红黑树 更多 个人分类&#xff1a; ● 结构算法---…

数据结构与算法——二叉树、堆、优先队列

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 七、树 7.1 树 7.1.1 树的定义 树是我们计算机中非常重要的一种数据结构&#xff0c;同时使用树这种数据结构&#xff0c;可以描述现实生活…

android组建之间通信_Android组件化(三)组件之间的通信

介绍在组件化开发的时候&#xff0c;组件之间是相互独立的没有依赖关系&#xff0c;我们不能在使用显示调用来跳转页面了&#xff0c;因为我们组件化的目的之一就是解决模块间的强依赖问题&#xff0c;假如现在要从A业务组件跳转到业务B组件&#xff0c;并且要携带参数跳转&…

继牛津大学后,加大伯克利分校等多家美国高校终止与华为合作

文&#xff0f;AI财经社 唐煜编&#xff0f;嵇国华据 Nature News 报道&#xff0c;在美国相关部门的压力之下&#xff0c;加州大学伯克利分校&#xff08;UC Berkeley&#xff09;近日宣布不再与华为签署新的研究合作&#xff1b;德州大学奥斯丁分校也正在审查自身与华为的关系…

为什么varchar字段长度最好是2的n次方-1

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 计算机是二进制计算的&#xff0c;1 bytes 8 bit ,一个字节最多可以代表的数据长度是2的8次方 11111111 在计算机中也就是-128到127。 而var…

运筹学状态转移方程例子_强化学习第4期:H-J-B方程

在上一篇文章中&#xff0c;我们介绍了一种最简单的MDP——s与a都是有限的MDP的求解方法。其中&#xff0c;我们用到了动态规划的思想&#xff0c;并且推出了“策略迭代”、“值迭代”这样的方法。今天&#xff0c;我们要来讲更加一般的最优控制问题——t、a与s都是连续的问题。…

Python之celery的简介与使用

celery的简介 celery是一个基于分布式消息传输的异步任务队列&#xff0c;它专注于实时处理&#xff0c;同时也支持任务调度。它的执行单元为任务&#xff08;task&#xff09;&#xff0c;利用多线程&#xff0c;如Eventlet&#xff0c;gevent等&#xff0c;它们能被并发地执行…

不使用比较运算符如何比较两个数的大小

分享一波:程序员赚外快-必看的巅峰干货 前言 今天在水群的过程中看到有位群员谈论到这个话题&#xff0c;是他找工作过程中某家公司的面试题&#xff08;到底是哪家公司才会出这种没营养的题目刁难别人&#xff09;&#xff0c;有点兴趣&#xff0c;就开始写了。 开搞 想了一…

java占位符填充_Java使用freemark生成word

1、制作模板先用office word做一个模板word文档&#xff0c;${usrName}、${nowDate}占位符 可以使用 office 或者 wps 先创建一个模板表格 &#xff08;替换$部分可以在 模板格式改变之后 在替换xml 格式改了后有些原本的字符会分开&#xff09;2、用office word将模板word另存…

Java中如何使用非阻塞异步编程——CompletableFuture

分享一波:程序员赚外快-必看的巅峰干货 对于Node开发者来说&#xff0c;非阻塞异步编程是他们引以为傲的地方。而在JDK8中&#xff0c;也引入了非阻塞异步编程的概念。所谓非阻塞异步编程&#xff0c;就是一种不需要等待返回结果的多线程的回调方法的封装。使用非阻塞异步编程…

城市运行一网统管_【宣传活动】持续开展城市运行“一网统管”建设宣传活动...

为进一步推进本镇城市运行“一网统管”建设工作&#xff0c;提高城市治理能力和治理水平&#xff0c;提升社会各界的知晓度和参与度&#xff0c;激发职能部门人员、党员、群众参与“一网统管”工作的热情。9月10日&#xff0c;镇网格中心于福泉居委会议室开展“推进城市运行‘一…

Java如何只使用位运算实现加减乘除

分享一波:程序员赚外快-必看的巅峰干货 前言 接前面一篇博客&#xff0c;这又是某个公司的奇葩面试题&#xff08;都说了到底是哪家公司才会出这种没营养的面试题&#xff09;。不过吐槽归吐槽&#xff0c;这个题目还是有点学问的&#xff0c;比前面那个 不使用比较运算符如何…

Netweaver里某个software component和C4C的版本

有同事问如何通过代码的方式获得Netweaver里某个Software component的版本信息&#xff0c;以及Cloud for Customer&#xff08;C4C&#xff09;的版本信息。 Netweaver 点了Detail按钮后&#xff1a; 这些版本信息存在表CVERS里&#xff1a; C4C C4C的版本号在Help->About …

pmc订单表格_复工了,读一则“如何提升订单准交率和生产效率”的真实故事

故事发生在中国南方小镇上一个做办公家具的公司……家具公司创建于1995年&#xff0c;是一家集研发、生产、销售、服务为一体的现代办公家具、酒店家具制造企业。主要产品有实木班台系列、会议台系列、职员桌系列、屏风系列、沙发系列、办公座椅、酒店家具系列。在省外还有两个…

GET和POST请求到底有什么区别?

分享一波:程序员赚外快-必看的巅峰干货 看到这个标题&#xff0c;想必大部分人都已经想关掉这篇博客了。先别急&#xff0c;你真的知道这两个的区别吗&#xff1f; 做过WEB开发的朋友可能很熟悉&#xff0c;看到这个问题能立马脱口而出二者的区别。 GET在浏览器回退时是无害的…

有赞电商云应用框架设计

背景 有赞是 SaaS 公司&#xff0c;向商家提供了全方位的软件服务&#xff0c;支撑商家进行采购、店铺、商品、营销、订单、物流等等管理服务。 在这个软件服务里&#xff0c;能够满足大部分的商家&#xff0c;为商家保驾护航。 但是很多大商家往往会有自己的特殊需求&#xff…

vivado 如何创建工程模式_基于Vivado的FPGA高性能开发研修班2019年8月30日上海举行...

一、课程介绍&#xff1a;从7系列FPGA开始&#xff0c;Xilinx提出了Vivado Design Suite设计软件&#xff0c;提供全新构建的SoC 增强型、以 IP 和系统为中心的下一代开发环境&#xff0c;以解决系统级集成和实现的生产力瓶颈。同时&#xff0c;Xilinx专门针对Vivado推出了Ultr…