Posts Tagged ‘Code杂谈’

JDK8版本过高引起MySQL连接失败:javax.net.ssl.SSLHandshakeException: No appropriate protocol

September 28th, 2021

最近在做面向k8s的项目镜像,遇到很奇怪的问题,SpringCloud 项目连接mysql 抛 javax.net.ssl.SSLHandshakeException: No appropriate protocol,调查一段时间后,发现是Java security中的配置不对导致连接Mysql异常。

javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
	at sun.security.ssl.HandshakeContext.<init>(HandshakeContext.java:171) ~[na:1.8.0_292]
	at sun.security.ssl.ClientHandshakeContext.<init>(ClientHandshakeContext.java:98) ~[na:1.8.0_292]
	at sun.security.ssl.TransportContext.kickstart(TransportContext.java:220) ~[na:1.8.0_292]
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:428) ~[na:1.8.0_292]
	at com.mysql.cj.protocol.ExportControlled.performTlsHandshake(ExportControlled.java:316) ~[mysql-connector-java-8.0.17.jar:8.0.17]
	at com.mysql.cj.protocol.StandardSocketFactory.performTlsHandshake(StandardSocketFactory.java:188) ~[mysql-connector-java-8.0.17.jar:8.0.17]
	at com.mysql.cj.protocol.a.NativeSocketConnection.performTlsHandshake(NativeSocketConnection.java:99) ~[mysql-connector-java-8.0.17.jar:8.0.17]
	at com.mysql.cj.protocol.a.NativeProtocol.negotiateSSLConnection(NativeProtocol.java:331) ~[mysql-connector-java-8.0.17.jar:8.0.17]
	... 68 common frames omitted

看到SSLHandshakeException,心里打起了问号?这个错误比较反常,最终网上找了一番,问题定位了:JDK8高版本导致的,因为之前用的是oracle:1.8,然后换成了openjdk:8,然后发现项目无法启动了。

方法一:

此处连接的MySQL,导致的报错,修改jdbcUrl,在其后面加useSSL=false后运行正常

方法二:

删除SSLv3, TLSv1, TLSv1.1并保存java.security文件,重启项目即可解决问题,删除后此处为:

# Example:
#   jdk.tls.disabledAlgorithms=MD5, SSLv3, DSA, RSA keySize < 2048
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \
    DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \
    include jdk.disabled.namedCurves

方法三:

降低JDK版本,这个相当也容易操作,比如可以从1.8.0_292降到1.8.0_281,甚至是1.8.0_275版本,但个人不建议,因为Oracle对JDK 8的支持一直会到2030年:即使很长一段时间用JDK 8,但JDK 8本身也是有小版本迭代的,比如你明年换了电脑,安装JDK,基本是1.8.0_292之后的版本,那这个问题会一直存在。

方法四:

方法一 能解决由SSL调用权限导致的所有问题,但破坏了安全性
方法二 针对MySQL的问题,可以快速解决
方法三 不推荐

因此,碰到类似问题,基本的思路是兼容JDK 8高版本甚至JDK高版本,比如代码层面的:

private Socket overrideTlsProtocol(final Socket socket) {
    if (!(socket instanceof SSLSocket)) {
        throw new RuntimeException("Error, an instance of SSLSocket is expected");
    }
    ((SSLSocket) socket).setEnabledProtocols(new String[]{"SSLv3"});
    return socket;
}

修改为:

private Socket overrideTlsProtocol(final Socket socket) {
    if (!(socket instanceof SSLSocket)) {
        throw new RuntimeException("Error, an instance of SSLSocket is expected");
    }
    ((SSLSocket) socket).setEnabledProtocols(new String[]{"SSLv3", "TLSv1","TLSv1.1"});
    return socket;
}

设计微信朋友圈的社交系统

July 19th, 2020

你自己可以发朋友圈,刷朋友,看到你自己发的朋友圈以及你的好友发的朋友圈,你可以对朋友圈进行点赞,进行评论,你可以去设置权限,你不看某些人的朋友圈,不让某些人看你的朋友圈,拉黑或者删除某个好友再也不用看到他的朋友圈了,首先你发送朋友圈的时候,一般是9张图片配合一些文字,组成了一条朋友前,文字还好说,但是图片就会稍微有点大了,也可以是一个短视频配合一些文字,点击发送,假设你要同步发送,可能会导致你点击发送按钮之后,弹出一个旋转框,告诉你发送中,持续好几秒种中,用户体验是比较差的。

有一个好一点的办法,可以把这些数据在客户端本地暂存一下,然后直接让你发送成功返回,走一个异步发送状态,然后立马让你自己在刷朋友圈的时候,可以把你客户端本地的刚发的朋友圈加载出来看到仅仅是这样,就会变成,发朋友圈成了你自己的自娱自乐,因为你的朋友圈并没有发送出去让你的好朋友看到,可以走一个异步的模式,把你的朋友圈里的图片或者视频+文字,花费几秒钟的时间传送到你的后台服务器上去存储之后,你的朋友就可以从后台服务器上加载你的朋友圈里的图片和视频,可以看到了你的那些视频和图片,是不是可以就都直接就近上传到CDN(content delivery network),不是直接到朋友圈系统的后台,这样速度是很快的;

接着就是发送请求到朋友圈后端系统,请求包括图片的地址,你配的文字,发朋友圈的时候可以选择开放给谁看,这些数据写入到朋友圈发布表里去然后需要在相册表里写入索引数据,里面存放的是对你的发布表里的数据引用,这样你以后浏览相册的时候,都是根据相册里的索引数据到发布表里找实际对应的数据的接着就走一个离线批处理,通过批处理程序把这条朋友圈写入到你所有好友的时间线表里去,你好友的时间线表里就是存放了他刷朋友圈的时候,可以按照时间线刷到的所有好友的朋友圈你有3个好朋友,每个好朋友都在时间线表里有一个朋友圈的时间线,按照时间顺序排列了他可以查看的所有朋友圈,包括了他自己发的朋友圈以及他的好友发的朋友圈允许他看的那些,都会在这里然后你的好朋友刷朋友圈的时候,就会知道自己的时间线表里有个新的变化,就是有好友发了朋友圈,此时就会提示你一个红色的圆点,你就开始刷,刷的时候就根据图片url地址去cdn拉取。

 

权限控制

这条朋友圈的权限到了后台之后,会有一个离线批处理的程序跑起来,对最近发的一波朋友圈都找他们的朋友圈的权限的设置看一下,此时就会对你允许看到的好友,此时就在他们的时间线里插入这条朋友圈数据,那么这样的话,只有你允许的好友的时间线里才有你这条朋友圈

 

比如说王五发的朋友圈16931可以允许张三和李四看到,设置了一个标签组,标签名称是老铁三人组,里面就正好有张三和李四

 

张三       发表朋友圈的时间戳           朋友圈16931        王五

张三       发表朋友圈的时间戳           朋友圈16384        李四

李四       发表朋友圈的时间戳           朋友圈16931        王五

 

在redis里可以设置张三的朋友圈是有变动的一个状态,在上次拉取朋友圈的时间点之后的一些朋友圈都从时间线表里拉取出来,刷朋友圈的时候,如果说你的网速要是不太好的话,你会发现这样一个场景

就是你最新的一些朋友发的朋友圈是显示出来了,但是视频和图片都是一片灰色,仅仅能看到他的文字和其他的一些东西,比如说点赞之类的,图片和视频死活看不到,都是一片灰色,反正我自己网速不好的时候经常看到这样的情况

假设王五之前发了一条朋友圈,设置李四可以看到的,李四之前确实是看到了这条朋友圈的,但是有个问题,王五后来跟李四吵了一架,关系变得非常的不好,王五就对李四设置了一个朋友圈的权限,就是自己的朋友圈不允许李四看到,甚至可能会直接拉黑/删除李四这个好友,这个就够狠了

你设置自己的朋友圈对所有朋友都是仅仅三天之内可见

就是说你跟李四之间的朋友圈的权限总设置或者是朋友之间的关系,有了变化,或者是你的自己的朋友圈对外展示的总权限有了变化,此时每次如果有变动,那么这些设置,包括你对每个朋友的朋友圈权限的设置,跟朋友的关系,自己的朋友圈的总权限,这些设置都会统统的缓存起来包括缓存在你自己的客户端本地,也可以缓存在你的朋友的客户端本地。

但是你可能随时会拉黑、删除某个人,或者是突然设置对那个人朋友圈不可见,或者是突然你自己设置了朋友圈三天可见什么的,所以你设置的这些东西,都会被缓存起来,每次你好友刷朋友圈,查看自己的时间线表的时候,都会检查你的某条朋友圈根据你的一些行为,是否还对他可见。

李四会关注王五的各种朋友圈权限和朋友关系的一个变化,一旦说有变化了,可以缓存到自己的本地,下次在客户端里再次刷新朋友圈的时候,客户端对于王五的朋友圈会和王五的各种权限设置结合起来判断一下李四能否看到王五的这条朋友圈

点赞系统

我看到了你的朋友圈,此时我就可以对你的朋友圈去进行一个点赞,也可以取消点赞,假设要设计成支撑高并发的点赞系统,应该如何设计?

朋友圈的点赞和评论,是独立的数据,其实比如点赞,都是可以基于redis来做的,每个朋友圈里对应一个set数据结构,里面放谁给你点赞了,这样每条朋友圈的点赞人(smembers)和点赞数量(scard)直接从redis出就可以了。

评论也是可以存表里的,都是以朋友圈为粒度来存储。

那么刷朋友圈的时候,比如说你好友和你,另外一个好友都是好友,此时你好友刷到了你的朋友圈,就可以把另外一个好友对你的点赞和评论都拉出来,展示在客户端下面就可以了,这个展示过程可以是动态的。

你是王五,你的朋友圈被张三点赞了,李四跟你们也是好朋友,此时李四刷朋友圈看到了王五发的这条朋友圈,此时你可以在后台,对这条朋友圈的set用张三做一个sismember操作,就是判断一下你们俩的所有共同好友,有哪些人对这条朋友圈点赞了。

此时就可以看出来这条朋友圈被你们的共同好友多少人点赞了,哪些人点赞了。

比如你另外一个好友是否对这条朋友圈点赞了,直接sismember就可以判断出来,这样整个你基于redis,他都是非常高性能的。

对称加密和非对称加密

October 7th, 2019

对称加密和非对称加密的概念是两种非常重要的加密方式,二者各有应用的领域,今天看完一片博文系统性的把这个弄明白了。

在对称加密算法中,加密使用的密钥和解密使用的密钥是相同的。也就是说,加密和解密都是使用的同一个密钥(举例密钥A,甲方用密钥A加密报文,乙方用密钥A解密报文)。显而易见对称加密算法要保证安全性的话,密钥A非常重要,密钥A丢失的话,黑客就可以监听甲方和乙方之间的通信。

在非对称加密中,会同时存在两个密钥(公钥&私钥)然后可以使用公钥加密->私钥解密(或者私钥解密->公钥加密)。 加密使用的密钥和解密使用的密钥是不相同的,因此它是一个非对称加密算法。注意:公钥和私钥只是名义上这么说,主要表达的意思是公钥人人都可以获取,私钥只有自己留存。RSA就是一种非对称加密算法,这一对公钥、私钥都可以用来加密和解密,并且一方加密的内容可以由并且只能由对方进行解密。

什么时候用公钥加密->私钥解密,还是私钥解密->公钥加密主要依靠使用场景:

1.私钥加密公钥解密,能证明“私钥拥有者” 的唯一身份,用于签名。

2. 公钥加密私钥解密,确保发送的信息,只有有“私钥拥有者” 能够接收(如果用私钥加密,传递数据,则会被公钥持有者(可能有很多持有者) 解密,失去对信息的保护)

但是仅有RSA还无法完全保证通信的安全,因此一般通信流程是先使用非对称加密验证双方身份,然后采用对称加密进行数据通信。但是!谁都可以生成公钥私钥,如果黑客向“客户”发送他自己的私钥就可以冒充“服务器”了!因此对于验证身份采用证书方式是现在通用方式。申请证书后,公司会得到这个这个证书和私钥,公钥嵌入在证书中!私钥只能由公司自己知道。

一个证书包含下面的具体内容:

  • 证书的颁发者
  • 证书的有效期
  • 证书的使用者
  • 公钥(这个公司证书的公钥)
  • 证书所有者(Subject)
  • 签名所使用的算法
  • 指纹以及指纹算法(机构的证书)

指纹算法主要用来保证这个证书的完整性,机构会使用自己加密算法和私钥加密证书:生成方式就是”SecureTrust CA”这个证书机构利用签名算法(Signature algorithm)使用私钥加密hash(证书)后和证书放在一起,以保证证书完整性。用户只需要使用机构的公钥解密证书,得到hash’(证书)并和该证书hash对比得出是否被篡改。

如果证书没有篡改,那么使用指纹算法计算这个证书的指纹,将这个计算的指纹与证书中的指纹对比,如果一致,说明这个的证书肯定没有被修改过并且证书是由这个证书机构发布的,证书中的公钥肯定是这个证书的,客户然后就可以放心的使用这个公钥和服务器进行通信。

下面来说HTTPS通信模式:

step1: “客户”向服务端发送一个通信请求

“客户”->“服务器”:你好

step2: “服务器”向客户发送自己的数字证书。证书中有一个公钥用来加密信息,私钥由“服务器”持有

“服务器”->“客户”:你好,我是服务器,这里是我的数字证书

step3: “客户”收到“服务器”的证书后,它会去验证这个数字证书到底是不是“服务器”的,数字证书有没有什么问题,数字证书如果检查没有问题,就说明数字证书中的公钥确实是“服务器”的。检查数字证书后,“客户”会发送一个随机的字符串给“服务器”,“服务器”用私钥去加密然后返回给“客户”,“客户”用公钥解密这个返回结果,如果解密结果与之前生成的随机字符串一致,那说明对方确实是私钥的持有者,或者说对方确实是“服务器”。

“客户”->“服务器”:向我证明你就是服务器,这是一个随机字符串 //前面的例子中为了方便解释,用的是“你好”等内容,实际情况下一般是随机生成的一个字符串。

“服务器”->“客户”:{一个随机字符串}[私钥|RSA]

step4: 验证“服务器”的身份后,“客户”生成一个对称加密算法和密钥,用于后面的通信的加密和解密。这个对称加密算法和密钥,“客户”会用公钥加密后发送给“服务器”,别人截获了也没用,因为只有“服务器”手中有可以解密的私钥。这样,后面“服务器”和“客户”就都可以用对称加密算法来加密和解密通信内容了。

“服务器”->“客户”:{OK,已经收到你发来的对称加密算法和密钥!有什么可以帮到你的?}[密钥|对称加密算法]

“客户”->“服务器”:{我的帐号是aaa,密码是123,把我的余额的信息发给我看看}[密钥|对称加密算法]

“服务器”->“客户”:{你好,你的余额是100元}[密钥|对称加密算法]

…… //继续其它的通信

 

http://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html

Java Executor 框架使用的思考

April 18th, 2018

最近在使用Java的线程池做一些线程频繁创建销毁的事情,我的目标是利用已有的云Queue服务,将我们的服务接入这个Queue,目前我们使用的是一个基于内存Queue的异步队列框架,这就导致当进程因为某种原因导致崩溃后,内存Queue中的数据全部丢失,这是我们所不能容忍的。

因此我们在云Queue上创建两个Queue,在处理高峰时刻将一部分数据导入backup queue中,当主 queue中的Message消费完毕后,主动去消费backup queue 中的数据。开始我使用一种按需分配的方式,即先使用Excutor创建一个pool后,只有一个线程去轮询主Queue中是否有数据,云Queue中有数据就创建一个线程加入到pool中执行,最后自到触发RejectedExecutionException,这个时候我将主Queue中的数据放到backup Queue中执行。

另外一种比较土的方案就是Server一启动,直接启动400个线程加入到pool中执行业务逻辑,长轮询主Queue是否有数据,有就去消费,没有就去消费Backup Queue中的数据。

从直觉上,大家都会认为第一种方案资源消耗会更优,但是在实际的工程实现中,必然存在设计和实现的折中,我的每个task执行逻辑会有多大五六个返回值,有的返回值需要将消息推送给用户,如果我按照第一个方案去做,那么必然有一刻处理峰值,会将大量的Message都导入backup Queue,只要进入backup Queue中后,就无法给用户实时推送消息,换句话说,消息就丢失了,虽然我很想用第一种方案去实现复杂逻辑,但是在实际的实现中会导致我的backup queue会分成很多种情况,这就导致实现复杂度和queue的复杂度提升更快。经过半个月的讨论,我终于敲定使用简单粗暴的方式去实现我的功能。

以上是我的一个吐槽。

 

https://blog.csdn.net/pfnie/article/details/52755769

字符串乱码解决之道

January 5th, 2017

最近工作压力太大,blog也逐渐荒废,怎么也得写点东西了,要不说不过去。。。。。

Qt为字节流和字符串分别提供了QByteArray和QString两个类(还有QLatin1String等其他类,但这两个是最主要的)。当我们涉及到I/O时,比如读写文件、读写网络socket、控制台输入输出、读写串口… 操作的都是字节流,如果我们此时需要操作的内容是字符串,则需要二者之间的相互转换。在C和C++中,我们一般都是将 “Hello World!” 这种称为字符串。但是就目前而言,当我们提字符串时,一般是指一个Unicode字符串,其由一个一个的Unicode字符构成;当我们提字节流时,是指一个一个的字节。或许我们可以说,ANSI C/C++截止目前只有字节流,而缺乏对字符串的支持。另外各个编译器对编码的支持又严重不一, Qt为解决这个问题提供了QTextCodec。

QTextCodec * textc = QTextCodec::codecForName("GBK");
1.QTextCodec::setCodecForCStrings(textc);
2.QTextCodec::setCodecForTr(textc); 
3.QTextCodec::setCodecForLocale(textc);

QString 是不存在中文支持问题的,很多人遇到问题,并不是本身 QString 的问题,而是没有将自己希望的字符串正确赋给QString。很简单的问题,”我是中文”这样写的时候,它是传统的char 类型的窄字符串,我们需要的只不过是通过某种方式告诉QString 这四个汉字采用的那种编码。而问题一般都出在很多用户对自己当前的编码没太多概念。另外文件是有编码的,但是这种纯文本文件却不会记录自己采用的编码,这个是问题的根源。真的是 QString 乱码了吗?其实很简单的一个问题,当你从窄字符串 char* 转成Unicode的QString字符串的时候,你需要告诉QString你的这串char* 中究竟用的是什么编码?GBK、BIG5还是Latin-1。理想情况就是:将char* 传给QString时,同时告诉QString自己的编码是什么;但是QString 提供的成员函数,远远满足不了大家的需求,于是只有采取语句1的办法。

tr(“中文”);与QString(“中文”);一样,你必须告诉tr这个窄字符串是何种编码?你不告诉它,它就用latin1。于是所谓的乱码问题就出来了。如何告诉tr你写的这几个汉字在磁盘中保存的是何种编码呢?这正是语句2所做的。如果你的编码采用的utf8,可以直接使用trUtf8而不必设置setCodecForTr()。

对于语句3应该没什么好说的,在绝大多数情况下,我们在代码中应该都用不到这个函数(默认的system应该比我们所能设置的要好)。
下面讲一下关于编码转换问题:
QT中的QString内容使用Unicode作为文本编码。但是实际系统中通常采用的是其他编码,例如GBK,utf8等。为了便于兼容这些格式,QT中还设置了两个字符串类型:
QCString类: C类型字符串,必须以\0结尾,也就是中间不能含有\0. 例如GBK编码的字符串
QByteArray类: 中间可以含有\0.例如utf8编码的字符串

在设置下面的代码基础上:

QTextCodec *gbk = QTextCodec::codecForName("gb18030"); 
QTextCodec *utg8 = QTextCodec::codecForName("utf-8");
QTextCodec::setCodecForTr(gbk);
QTextCodec::setCodecForLocale(gbk);
QTextCodec::setCodecForCStrings(gbk);

1. UTF-8 转换 GBK

QString U2G(QString utfStr)
{
   return gbk-&gt;toUnicode(utfStr.toLocal8Bit());
}

2 GBK 转换 UTF-8

QString U2G(QString gbkStr)
{
   return utg8-&gt;toUnicode(gbkStr.toUtf8());
}

代码示例:
———————————————————————————–

 QTextCodec *gbk = QTextCodec::codecForName("gb18030");
 QTextCodec *utf8 = QTextCodec::codecForName("utf-8");

 QTextCodec::setCodecForTr(gbk);
 QTextCodec::setCodecForLocale(gbk);
 QTextCodec::setCodecForCStrings(gbk);


 QFile file("../test.txt");
 file.open(QIODevice::ReadOnly);
 QByteArray readByte = file.readAll();
 QString readStr = utf8->toUnicode(readByte.data());
 file.close();
 QString utfStr = QObject::trUtf8(readByte); //utf-8
 QString gbkStr = QObject::tr("中文"); // gbk

 QString utf2gbk = gbk->toUnicode(readStr.toLocal8Bit()); // utf8 conver gbk
 QString gbk2utf1 = utf8->toUnicode(utf2gbk.toUtf8()); // gbk convert utf8
 QString g2u = gbk->toUnicode(gbk->fromUnicode(readStr)); // gbk convert utf8

 qDebug() << "gbk:" << gbkStr;
 qDebug() << "utf8:" << utfStr;
 qDebug() << "readStr:" << readStr;

 qDebug() << "read_size:" << readByte.length();
 qDebug() << "utf2gbk:" <<utf2gbk << "length:" << readStr.toLocal8Bit().length();
 qDebug() << "gbk2utf8-1:" << gbk2utf1 << " length: " << utf2gbk.toUtf8().length();
 qDebug() << "g2u" << g2u << "length:" << gbk->fromUnicode(utfStr).length();

 QLabel *label = new QLabel(utf2gbk);
 label->show();

参考:

【1】http://www.cnblogs.com/bingcaihuang/archive/2011/03/17/1986714.html

【2】http://knight4576.blog.51cto.com/2761974/703963