spark使用redis的连接池

这几天碰到了类似的问题, 网上查的一些资料, 这里记录一下~

1. Master

将所有的数据全部回收到master, 然后在master进行集中处理

连接池代码:

public class TestRedisPool {
	private JedisPool pool = null;
	public TestRedisPool(String ip, int port, String passwd, int database) {
		if (pool == null) {
			JedisPoolConfig config = new JedisPoolConfig();
			config.setMaxTotal(500);
			config.setMaxIdle(30);
			config.setMinIdle(5);
			config.setMaxWaitMillis(1000 * 10);
			config.setTestWhileIdle(false);
			config.setTestOnBorrow(false);
			config.setTestOnReturn(false);
			pool = new JedisPool(config, ip, port, 10000, passwd, database);
			Logs.debug("init:" + pool);
		}
	}
	public JedisPool getRedisPool() {
		return pool;
	}
	public String set(String key,String value){
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			return jedis.set(key, value);
		} catch (Exception e) {
			e.printStackTrace();
			return "0";
		} finally {
			jedis.close();
		}
	}
}

使用方式:

List<String> list = Arrays.asList("a","b","c","d", "e");
JavaRDD<String> javaRDD = new JavaSparkContext(spark.sparkContext()).parallelize(list, 3);
TestRedisPool testRedisPool = new TestRedisPool(redisIp, port, passwd, dbNum);
List<String> lst = javaRDD.collect();
for(String s:lst) {
	testRedisPool.set(s, getDateString2(0));
}

2. Worker

在worker遍历的时候初始化连接池

javaRDD.foreach(new VoidFunction<String>() {
	@Override
	public void call(String s) throws Exception {
		TestRedisPool testRedisPool = new TestRedisPool(redisIp, port, passwd, dbNum);
		Logs.debug(testRedisPool.getRedisPool());
		testRedisPool.set(s, getDateString2(0));
	}
});

遍历所有元素,TestRedisPool不需要实现序列化;每一个RDD中的元素都需要创建很多的redis连接池,即便使用短连接也会对redis造成很大的压力。效率也是极其低下的。

3. Master上创建,Worker上遍历

在Master上创建一个实例,在进行分区遍历时使用Master上创建的实例,这种方式是可以的,只需要将类实现序列即可。同时还可以通过广播变量,将实例在Worker上持久化,减少实例使用时的网络传输。

TestRedisPool testRedisPool = new TestRedisPool(redisIp, port, passwd, dbNum);
javaRDD.foreach(new VoidFunction<String>() {
	@Override
	public void call(String s) throws Exception {
		Logs.debug(testRedisPool.getRedisPool());
		testRedisPool.set(s, getDateString2(0));
	}
});
Exception in thread "main" org.apache.spark.SparkException: Task not serializable
...
Serialization stack:
	- object not serializable (class: redis.clients.jedis.JedisPool, value: redis.clients.jedis.JedisPool@3e4f80cb)

报错jedispool无法序列化,即使TestRedisPool类实现了序列化,但因为其成员变量jedispool本身并不支持序列化,所以这种方式在有成员变量无法序列化时也不可用。

4. Worker上按分区遍历

javaRDD.foreachPartition(new VoidFunction<Iterator<String>>() {
	@Override
	public void call(Iterator<String> stringIterator) throws Exception {
		TestRedisPool testRedisPool = new TestRedisPool(redisIp, port, passwd, dbNum);
		while (stringIterator.hasNext()) {
			Logs.debug(testRedisPool.getRedisPool());
			testRedisPool.set(stringIterator.next(), getDateString2(0));
		}
	}
});

TestRedisPool不需要实现序列化,每个分区只需要创建一个redis连接池

5. 使用静态类型,按分区遍历

在上面,我们可以做到在每个分区上建立连接池,但是每台机器一般对应多个分区,怎么进一步减少连接池的创建呢。我们知道静态类型全局只有一份,如果将redis连接池定义为静态类型,做到每个worker上只创建一个redis连接池。

public class TestRedisPool {
	private static JedisPool pool = null;
	...
}

错误使用:

TestRedisPool testRedisPool = new TestRedisPool(redisIp, port, passwd, dbNum);
javaRDD.foreachPartition(new VoidFunction<Iterator<String>>() {
	@Override
	public void call(Iterator<String> stringIterator) throws Exception {
		while (stringIterator.hasNext()) {
			Logs.debug(testRedisPool.getRedisPool());
			testRedisPool.set(stringIterator.next(), getDateString2(0));
		}
	}
});

这种在Master上创建TestRedisPool实例的方式,在worker上无法获取到,会报java.lang.NullPointerException异常。

正确使用:

javaRDD.foreachPartition(new VoidFunction<Iterator<String>>() {
	@Override
	public void call(Iterator<String> stringIterator) throws Exception {
		TestRedisPool testRedisPool = new TestRedisPool(redisIp, port, passwd, dbNum);
		while (stringIterator.hasNext()) {
			Logs.debug(testRedisPool.getRedisPool());
			testRedisPool.set(stringIterator.next(), getDateString2(0));
		}
	}
});

TestRedisPool也不需要序列化。这种情况下是在分区上分别创建实例,分区对应的就是虚拟线程的个数,所以相当于3个线程同时去获取jedispool实现,所以一共init了三次。如果做成单例模式就能解决init多次的问题。

6. 使用单例模式,按分区遍历

连接池代码:

package com.project.uitl;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Redis 连接池工具包
 *
 */
public class JedisPoolUtil {
    
    private static final String HOST = "132.232.6.208";
    private static final int PORT = 6381;
    
    private static volatile JedisPool jedisPool = null;
    
    private JedisPoolUtil() {}
    
    /**
     * 获取RedisPool实例(单例)
     * @return RedisPool实例
     */
    public static JedisPool getJedisPoolInstance() {
        if (jedisPool == null) {
            synchronized (JedisPoolUtil.class) {
                if (jedisPool == null) {
                    
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    poolConfig.setMaxTotal(1000);           // 最大连接数
                    poolConfig.setMaxIdle(32);              // 最大空闲连接数
                    poolConfig.setMaxWaitMillis(100*1000);  // 最大等待时间
                    poolConfig.setTestOnBorrow(true);       // 检查连接可用性, 确保获取的redis实例可用
                    
                    jedisPool = new JedisPool(poolConfig, HOST, PORT);
                }
            }
        }
        
        return jedisPool;
    }
    
    /**
     * 从连接池中获取一个 Jedis 实例(连接)
     * @return Jedis 实例
     */
    public static Jedis getJedisInstance() {
        
        return getJedisPoolInstance().getResource();
    }
    
    /**
     * 将Jedis对象(连接)归还连接池
     * @param jedisPool 连接池
     * @param jedis 连接对象
     */
    public static void release(JedisPool jedisPool, Jedis jedis) {
        
        if (jedis != null) {
            jedisPool.returnResourceObject(jedis);  // 已废弃,推荐使用jedis.close()方法
        }
    }
}

以上volatile保证当jedispool未初始化完成是不能被获取到,synchronized解决多线程冲突的问题。这两个关键词的使用其实也就是lazy initialize的实现。

javaRDD.foreachPartition(new VoidFunction<Iterator<String>>() {
	@Override
	public void call(Iterator<String> stringIterator) throws Exception {
		TestRedisPool testRedisPool = new TestRedisPool(redisIp, port, passwd, dbNum);
		while (stringIterator.hasNext()) {
			Logs.debug("class:" + testRedisPool );
			Logs.debug("pool:" + testRedisPool .getRedisPool());
			testRedisPool .set(stringIterator.next(), getDateString2(0));
		}
	}
});

现在jedispool只init了一次,并且全局也只有一个jedispool。但是现在TestRedisPool对象还是被创建了多个,改为在Master上定义,并已广播变量的形式分发到Worker上可以解决这个问题,这种情况下TestRedisPool需要序列化。

7. 使用单例模式,Driver上定义,分区上遍历

TestRedisPool testRedisPool = new TestRedisPool(redisIp, port, passwd, dbNum);
final Broadcast<TestRedisPool> broadcastRedis = new JavaSparkContext(spark.sparkContext()).broadcast(testRedisPool);
javaRDD.foreachPartition(new VoidFunction<Iterator<String>>() {
	@Override
	public void call(Iterator<String> stringIterator) throws Exception {
		TestRedisPool redisClient1 = broadcastRedis.getValue();
		while (stringIterator.hasNext()) {
			Logs.debug("class:" + redisClient1);
			Logs.debug("pool:" + redisClient1.getRedisPool());
			redisClient1.set(stringIterator.next(), getDateString2(0));
		}
	}
});

现在是TestRedisPool在Master上定义,广播到各个Worker上;同时jedispool在每台worker上也始终只会有一个实例存在。

但是也会有人会疑问,为什么jedispool现在没有序列化的问题(方法三),或者定义成静态导致worker上获取不到jedispool(方法五第一种情况)的问题。这是因为方法三中jedispool为普通类型是和类一起序列化,因为其本身不支持序列化,所以报错 方法五中,定义成静态类型之后,静态类型不属于类,所以TestRedisPool序列化不会出错,但是因为jedispool在Master上定义和初始化,不会传输到节点上,节点上获取到的jedispool都为null,所以报错。而方法七中使用懒启动的方式,在使用时才会初始化jedispool,所以实际是在节点上完成的初始化,所以不会有问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值