Redis专题(三)

CentOS 6+Reids3.2.11多实例部署

单实例单线程的redis进程不足以高效率使用cpu和内存资源,所以一般来讲redis在同一台机器上要启动多个进程完成多实例部署;默认占用6379的情况下无法完成直接的3个实例启动,我们需要了解如何通过指定配置文件,将多实例部署在linux上

 

启动redis服务的命令redis-server 没有加载任何配置文件指定各种各样的配置信息(端口指定,ip绑定,后台运行,指定持久化文件等)

例如在根目录存在一个配置文件的模板(大部分与默认启动的配置相同)redis.conf

#redis-server 配置文件的名称

 

配置文件(在启动时指定的配置文件,核心的配置文件)

/redis根目录/redis.conf

 

一个redis实例默认占用所有物理内存(上限是物理内存大小),在实际使用中需要限制大小

配置文件的修改内容

p61 bind 用#注释掉

如果需要绑定监听的ip(客户端只有通过被绑定的ip才可以利用redis-cli -h ip地址链接服务器)

bing 127.0.0.1 192.168.65.128(外网可访问当前服务器的ip)

一旦用#注释bind,没有任何限制,只要可以链接服务器,都允许使用redis

 

p80 保护模式不启动

 

保护模式开启,需要登录密码,改成no

p84 6379是默认端口(要启动其他的redis实例需要修改端口)

p105 当客户端空闲时间达到一小时,就会自动断开连接,0秒表示

不启用超时配置

p128 daemonize 设置成yes让redis服务器启动有守护进程管理

(后台执行)

p150 对应不同的redis实例,pid的文件名称需要和端口同名

每个进程在linux或者其他操作系统中都会占用pid号,当系统中的进程过多时,需要查找redis进程号可能比较麻烦,直接打开pid文件查看即可

P163logfile 需要指定,利用端口号命名,放到redis根目录

save 900(秒) 1(变动的数据条数)

当900以内,至少有1条数据变动,看是flush保存数据到文件

save 300 10

300秒以内至少10条数据变动,保存文件

save 60 10000

P237左右,指定dump的持久化文件,每个服务单独指向一个文件,

重启时,数据不会错乱

启动第二和第三个redis实例

redis-server redis.conf(指定启动文件)

需要第二个实例的配置文件

需要第三个实例的配置文件

拷贝redis.conf为redis_6379.conf、redis_6380.conf、redis_6381.conf

将拷贝的文件中只修改与端口有关内容

 

启动另外两个节点

redis-server redis_6379.conf

redis-server redis_6380.conf

redis-server redis_6381.conf

 

指定端口登录客户端redis-cli -p [端口号] -h  [ip]

redis-cli -p 6379

redis-cli -p 6380

redis-cli -p 6381

 

ps -el | grep redis

此时就已经准备好了三个服务实例。

附上测试代码:

/*
java语言提供多种可以链接redis的包;
其中比较常用,更新比较快速;jedis
依赖jar包
	<dependency>
	           <groupId>org.springframework.boot</groupId>
	           <artifactId>spring-boot-starter-redis</artifactId>
	           <version>1.4.5.RELEASE</version>
   	</dependency>
*/
 /*
 * 1 使用jedis链接redis服务端
 */
		
		@Test
		public void test01(){
			//从代码链接任何技术,必须的参数
			//ip port,新建一个jedis对象,获取ip和端口信息
			//即可完成链接服务的操作
			Jedis jedis=new Jedis("192.168.65.128", 6379);
			//利用着一个链接对象,操作redis服务
			//jedis.set("name", "王湛鲲");
			System.out.println(jedis.get("city"));
			jedis.close();
			
		}
	
/*
 * 2 模拟用户访问某个商品时使用缓存的逻辑
 * 	
 * 	* 接收到用户的一次商品访问的请求 打印输出
 *  * 根据商品携带的参数,定义全局唯一的key值
 *  	key值的计算逻辑=“item_”+itemId  商品id
 *  * 判断缓存是否有数据
 *  	有:直接返回,做响应,不再访问数据库(第一次访问肯定访问数据库)
 *  	没有(说明是第一次访问该商品) 从数据库获取数据,存到缓存内存,将数据响应出去
 */
		@Test
		public void test02(){
			String id="1";
			String product="联想电脑 拯救者";
			System.out.println("用户开始访问商品,商品id"+id);
			//根据商品访问的业务逻辑,生成全局唯一的key对应商品的访问
			String key="ITEM_"+id;//item_123
			//链接redis
			Jedis jedis=new Jedis("192.168.65.128", 6380);
			//利用jedis客户端判断key是否存在
			if(jedis.exists(key)){//表示存在缓存数据
				String info=jedis.get(key);
				System.out.println("数据从缓存获取,商品信息是:"+info);
				jedis.close();
			}else{//缓存不存在数据,从数据库获取
				System.out.println("select * from product where id="+id);
				System.out.println("从数据库获取数据,并且存储到缓存");
				jedis.set(key, product);
			}
		}
	

 

假设1000条商品信息都需要存储到缓存中,均衡的分配到每个节点中存储;

脑海中计算逻辑,对应实现代码里,这个计算步骤--数据分片的计算;

数据分片:数据层分布式集群中,数据被切分存储/读取,从多个服务器集群中的过程,必须对应正确严谨的计算逻辑,被切分的每一份数据都称为一个数据分片

@Test
	public void test03(){
		//准备jedis链接6379,6380,6381的客户端
		Jedis jedis1=new Jedis("192.168.65.128", 6379);
		Jedis jedis2=new Jedis("192.168.65.128", 6380);
		Jedis jedis3=new Jedis("192.168.65.128", 6381);
		//海量数据的存储需求
		for(int i=0;i<1000;i++){
			//每次循环,相当于需要存储一个商品信息,生成对应的key-value
			String key="key_"+i;
			String value="value_"+i;
			
			if(i<333){
				jedis1.set(key, value);//333 以下的数据存储到6379
			}else if(i<666){
				jedis2.set(key, value);//6380
			}else{
				jedis3.set(key, value);
			}
			
		}
		//海量数据的获取需求
		for(int i=0;i<1000;i++){
			String key="key_"+i;
			if(i<333){
				System.out.println(jedis1.get(key));//333 以下的数据存储到6379
			}else if(i<666){
				System.out.println(jedis2.get(key));
			}else{
				System.out.println(jedis3.get(key));
			}
		}
	}

以上内容是根据数据情况(1000条商品)完成的自定义数据分片的计算逻辑实现;读和取必须保持同一个计算逻辑,才能正确的操作缓存分布式集群;

自定义的前提条件:必须对整体数据有所了解;

自定义的分片逻辑,是在早期数据增长数量不多,企业数据稳定的情况下,工程师根据企业具体情况定义的最高效的计算逻辑

随着数据的不断增长,和数据内容的不可控;

缺点:

1. 数据倾斜严重(修改自定义的计算逻辑)

2. key值的取值范围发生变动(改代码,重新定义计算逻辑)

需要一种能够随着数据发生变动,一劳永逸的计算逻辑

hash取余

计算公式:object表示任何一种类型的java对象,N表示分布式集群中的数据分片数量(3)

(object.hashCode()&Integer.MAX_VALUE)%N

保真计算 有符号数字最大值的二进制:首位0其余位是1 因此做与运算可以把负数转化为正数

	@Test
	public void test04() {
		for (int i = 1; i <= 100; i++) {
			String key = "";
			if (i > 1)
			{
				key = "wzk又收到了苹果:" + i + "个!目前拥有" + (1 + i) * i / 2 + "个!";// 1000次循环,生成0100个string类型的对象
			} 
			else {
				key = "wzk收到了苹果:1个!目前拥有1个!";
			}
			/* 
			 * hashCode方法是Object的方法,可以将任何类型的数据
			 * 计算映射到一个整数区间;
			 * hashCode&Integer的最大数,位的与运算,保真运算,31位2进制的值
			 * 将负的值,转化成了正数
			 * 对n取余,n取值是根据数据分片个数决定的,3
			 */
			System.out.println(key+"key.hashCode():"+key.hashCode()+"(key.hashCode() & Integer.MAX_VALUE):"+(key.hashCode() & Integer.MAX_VALUE)+"分片:"+(key.hashCode() & Integer.MAX_VALUE) % 3);

		}
	}

没有办法预测获取key值的取值结果范围时,可以利用hash取余计算,将任何key的值映射到一个循环的整数区间内[0,n-1]

整合到redis

/*
		* 5利用hash取余的公式,完成数据分片的计算逻辑
		*/
		@Test
		public void test05(){
			//准备jedis链接6379,6380,6381的客户端
			Jedis jedis1=new Jedis("192.168.65.128", 6379);
			Jedis jedis2=new Jedis("192.168.65.128", 6380);
			Jedis jedis3=new Jedis("192.168.65.128", 6381);
			for(int i=1;i<=1000;i++){
				String value = "";
				if (i > 1)
				{
					value = "wzk又收到了苹果:" + i + "个!目前拥有" + (1 + i) * i / 2 + "个!";// 100次循环,生成100个string类型的对象
				} 
				else {
					value = "wzk收到了苹果:1个!目前拥有1个!";
				}
				String key=value;
				//获取取余结果
				int result=(key.hashCode()&Integer.MAX_VALUE)%3;
				if(result==0){//存储到6379
					jedis1.set(key, value);	
				}
				if(result==1){//6380
					jedis2.set(key, value);
				}
				if(result==2){//6381
					jedis3.set(key, value);
				}
			}
			for(int i=1;i<=1000;i++){
				String value = "";
				if (i > 1)
				{
					value = "wzk又收到了苹果:" + i + "个!目前拥有" + (1 + i) * i / 2 + "个!";// 1000次循环,生成1000个string类型的对象
				} 
				else {
					value = "wzk收到了苹果:1个!目前拥有1个!";
				}
				String key=value;
				//获取取余结果
				int result=(key.hashCode()&Integer.MAX_VALUE)%3;
				if(result==0){//存储到6379
					System.out.println(jedis1.get(key));
				}
				if(result==1){//6380
					System.out.println(jedis2.get(key));
				}
				if(result==2){//6381
					System.out.println(jedis3.get(key));
				}
			}
			jedis1.flushAll();
			jedis2.flushAll();
			jedis3.flushAll();
			jedis1.close();
			jedis2.close();
			jedis3.close();
		}

jedis的数据分片

利用hash取余完成了数据分片的计算,jedis的底层有自己的计算逻辑(hash一致性

/*
		 * jedis底层的数据分片计算逻辑是使用的hash一致性
		 */
		@Test
		public void test06(){
			//不能使用单独的链接对象jedis来操作集群,需要创建分片的对象
			//收集节点信息,告诉jedis集群的所有节点都是谁
			List<JedisShardInfo> infoList=new ArrayList<JedisShardInfo>();
			
			//封装3个节点的链接信息
			JedisShardInfo info1=new 
					JedisShardInfo("192.168.65.128", 6379);
			JedisShardInfo info2=new 
					JedisShardInfo("192.168.65.128", 6380);
			JedisShardInfo info3=new 
					JedisShardInfo("192.168.65.128", 6381);
			infoList.add(info1);
			infoList.add(info2);
			infoList.add(info3);
			
			//利用收集到的信息,创建一个分片对象,分片对象的操作和jedis对象一值
			//但是在存储,读取数据时,已经对key值做了数据分片的计算,从而可以正确的到
			//某个节点获取,存储数据
			ShardedJedis sJedis=new ShardedJedis(infoList);
			//sJedis.set("name", "haha");
			
			for(int i=1;i<=1000;i++){
				String value = "";
				if (i > 1)
				{
					value = "wzk又收到了苹果:" + i + "个!目前拥有" + (1 + i) * i / 2 + "个!";// 1000次循环,生成1000个string类型的对象
				} 
				else {
					value = "wzk收到了苹果:1个!目前拥有1个!";
				}
				String key=value;
				sJedis.set(key, value);
			}
			for(int i=1;i<=1000;i++){
				String value = "";
				if (i > 1)
				{
					value = "wzk又收到了苹果:" + i + "个!目前拥有" + (1 + i) * i / 2 + "个!";// 1000次循环,生成1000个string类型的对象
				} 
				else {
					value = "wzk收到了苹果:1个!目前拥有1个!";
				}
				String key=value;
				System.out.println(sJedis.get(key));
			}
			sJedis.close();
			
		}

 分片的连接池

/*
		 * 7 利用jedis完成分片连接池的配置,每次调用是从池中获取链接,返回资源
		 */
		@SuppressWarnings("deprecation")
		@Test
		public void test07(){
			//链接池中的每个链接对象,都需要链接3个节点的集群
			//收集节点信息,告诉jedis集群的所有节点都是谁
			List<JedisShardInfo> infoList=new ArrayList<JedisShardInfo>();
			
			//封装3个节点的链接信息
			JedisShardInfo info1=new 
					JedisShardInfo("192.168.65.128", 6379);
			JedisShardInfo info2=new 
					JedisShardInfo("192.168.65.128", 6380);
			JedisShardInfo info3=new 
					JedisShardInfo("192.168.65.128", 6381);
			infoList.add(info1);
			infoList.add(info2);
			infoList.add(info3);
			//连接池,具有自己的一些配置信息,最大连接数,最大空闲链接
			//准备一个配置对象
			JedisPoolConfig config=new JedisPoolConfig();
			//设置一些参数
			config.setMaxIdle(8);
			config.setMaxTotal(200);//最大连接数
			//创建分片连接池对象
			ShardedJedisPool pool=new 
					ShardedJedisPool(config, infoList);
			//获取链接对象
			ShardedJedis sJedis = pool.getResource();
			for(int i=1;i<=1000;i++){
				String value = "";
				if (i > 1)
				{
					value = "wzk又收到了苹果:" + i + "个!目前拥有" + (1 + i) * i / 2 + "个!";// 1000次循环,生成1000个string类型的对象
				} 
				else {
					value = "wzk收到了苹果:1个!目前拥有1个!";
				}
				String key=value;
				System.out.println(sJedis.get(key));
			}
			pool.returnResource(sJedis);
		}

下一篇会介绍一下hash一致性。。。

猜你喜欢

转载自blog.csdn.net/u010014073/article/details/83308155